# Copyright 2015 syzkaller project authors. All rights reserved.
# Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.

include <asm/ioctls.h>
include <linux/stat.h>
include <uapi/linux/fuse.h>
include <uapi/linux/fcntl.h>

resource fuse_unique[int64]
resource fd_fuse[fd]

openat$fuse(fd const[AT_FDCWD], file ptr[in, string["/dev/fuse"]], flags const[O_RDWR], mode const[0]) fd_fuse
openat$cuse(fd const[AT_FDCWD], file ptr[in, string["/dev/cuse"]], flags const[O_RDWR], mode const[0]) fd_fuse
ioctl$FUSE_DEV_IOC_CLONE(fd fd_fuse, cmd const[FUSE_DEV_IOC_CLONE], arg ptr[in, fd_fuse])
ioctl$FUSE_DEV_IOC_BACKING_OPEN(fd fd_fuse, cmd const[FUSE_DEV_IOC_BACKING_OPEN], arg ptr[in, fuse_backing_map])
ioctl$FUSE_DEV_IOC_BACKING_CLOSE(fd fd_fuse, cmd const[FUSE_DEV_IOC_BACKING_CLOSE], arg ptr[in, int32])

type read_buffer array[int8, FUSE_MIN_READ_BUFFER]
read$FUSE(fd fd_fuse, buf ptr[out, fuse_in[read_buffer]], len bytesize[buf])
mount$fuse(src const[0], dst ptr[in, filename], type ptr[in, string["fuse"]], flags flags[mount_flags], opts ptr[in, fuse_options])
mount$fuseblk(src ptr[in, string["/dev/loop0"]], dst ptr[in, filename], type ptr[in, string["fuseblk"]], flags flags[mount_flags], opts ptr[in, fuse_options])

write$FUSE_INTERRUPT(fd fd_fuse, arg ptr[in, fuse_out[void]], len bytesize[arg])
write$FUSE_INIT(fd fd_fuse, arg ptr[in, fuse_out[fuse_init_out]], len bytesize[arg])
write$FUSE_BMAP(fd fd_fuse, arg ptr[in, fuse_out[fuse_bmap_out]], len bytesize[arg])
write$FUSE_IOCTL(fd fd_fuse, arg ptr[in, fuse_out[fuse_ioctl_out]], len bytesize[arg])
write$FUSE_POLL(fd fd_fuse, arg ptr[in, fuse_out[fuse_poll_out]], len bytesize[arg])
write$FUSE_LSEEK(fd fd_fuse, arg ptr[in, fuse_out[fuse_lseek_out]], len bytesize[arg])
write$FUSE_LK(fd fd_fuse, arg ptr[in, fuse_out[fuse_lk_out]], len bytesize[arg])
write$FUSE_GETXATTR(fd fd_fuse, arg ptr[in, fuse_out[fuse_getxattr_out]], len bytesize[arg])
write$FUSE_STATFS(fd fd_fuse, arg ptr[in, fuse_out[fuse_statfs_out]], len bytesize[arg])
write$FUSE_WRITE(fd fd_fuse, arg ptr[in, fuse_out[fuse_write_out]], len bytesize[arg])
write$FUSE_OPEN(fd fd_fuse, arg ptr[in, fuse_out[fuse_open_out]], len bytesize[arg])
write$FUSE_ATTR(fd fd_fuse, arg ptr[in, fuse_out[fuse_attr_out]], len bytesize[arg])
write$FUSE_ENTRY(fd fd_fuse, arg ptr[in, fuse_out[fuse_entry_out]], len bytesize[arg])
write$FUSE_CREATE_OPEN(fd fd_fuse, arg ptr[in, fuse_out[fuse_create_open_out]], len bytesize[arg])
write$FUSE_DIRENT(fd fd_fuse, arg ptr[in, fuse_out[array[fuse_dirent]]], len bytesize[arg])
write$FUSE_DIRENTPLUS(fd fd_fuse, arg ptr[in, fuse_out[array[fuse_direntplus]]], len bytesize[arg])
write$FUSE_STATX(fd fd_fuse, arg ptr[in, fuse_out[fuse_statx_out]], len bytesize[arg])
write$FUSE_NOTIFY_POLL(fd fd_fuse, arg ptr[in, fuse_notify[FUSE_NOTIFY_POLL, fuse_notify_poll_wakeup_out]], len bytesize[arg])
write$FUSE_NOTIFY_INVAL_INODE(fd fd_fuse, arg ptr[in, fuse_notify[FUSE_NOTIFY_INVAL_INODE, fuse_notify_inval_inode_out]], len bytesize[arg])
write$FUSE_NOTIFY_INVAL_ENTRY(fd fd_fuse, arg ptr[in, fuse_notify[FUSE_NOTIFY_INVAL_ENTRY, fuse_notify_inval_entry_out]], len bytesize[arg])
write$FUSE_NOTIFY_STORE(fd fd_fuse, arg ptr[in, fuse_notify[FUSE_NOTIFY_STORE, fuse_notify_store_out]], len bytesize[arg])
write$FUSE_NOTIFY_RETRIEVE(fd fd_fuse, arg ptr[in, fuse_notify[FUSE_NOTIFY_RETRIEVE, fuse_notify_retrieve_out]], len bytesize[arg])
write$FUSE_NOTIFY_DELETE(fd fd_fuse, arg ptr[in, fuse_notify[FUSE_NOTIFY_DELETE, fuse_notify_delete_out]], len bytesize[arg])
write$FUSE_NOTIFY_RESEND(fd fd_fuse, arg ptr[in, fuse_notify[FUSE_NOTIFY_RESEND, const[0, int32]]], len bytesize[arg])

syz_mount_image$fuse(fs ptr[in, string["fuse"]], dir ptr[in, filename], flags flags[mount_flags], opts ptr[in, fuse_options], chdir bool8, size const[0], img ptr[in, array[int8]]) fd_dir
syz_fuse_handle_req(fd fd_fuse, buf ptr[in, read_buffer], len bytesize[buf], res ptr[in, syz_fuse_req_out])

type fuse_ino int64[0:6]
type fuse_gen int64[0:3]

type fuse_notify[MSG, PAYLOAD] {
	len	len[parent, int32]
	err	const[MSG, int32]
	unique	const[0, int64]
	payload	PAYLOAD
} [packed]

type fuse_in[PAYLOAD] {
	len	len[parent, int32]
	opcode	int32
	unique	fuse_unique
	uid	uid
	gid	gid
	pid	pid
	padding	int32
	payload	PAYLOAD
} [packed]

type fuse_out_t[UNIQUE, PAYLOAD] {
	len	len[parent, int32]
	err	flags[fuse_errors, int32]
	unique	UNIQUE
	payload	PAYLOAD
} [packed]

type fuse_out[PAYLOAD] fuse_out_t[fuse_unique, PAYLOAD]
# This response header is used by syz_fuse_handle_req(). It defines the FUSE unique
# identifier as int64 because syz_fuse_handle_req() retrieves it internally
# (defining it as a resource would create a dependency with read$FUSE() which is
# incorrect).
type syz_fuse_out[PAYLOAD] fuse_out_t[int64, PAYLOAD]

# -ENOENT, -EAGAIN, -ENOSYS
fuse_errors = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2, -11, -38

fuse_init_out {
	major			const[FUSE_KERNEL_VERSION, int32]
	minor			const[FUSE_KERNEL_MINOR_VERSION, int32]
	max_readahead		int32
	flags			flags[fuse_init_flags, int32]
	max_background		int16
	congestion_threshold	int16
	max_write		int32
	time_gran		int32
	max_pages		const[0, int16]
	map_alignment		const[0, int16]
	flags2			flags[fuse_init_flags2, int32]
	max_stack_depth		int32
	unused			array[const[0, int32], 6]
}

fuse_init_flags = FUSE_ASYNC_READ, FUSE_POSIX_LOCKS, FUSE_FILE_OPS, FUSE_ATOMIC_O_TRUNC, FUSE_EXPORT_SUPPORT, FUSE_BIG_WRITES, FUSE_DONT_MASK, FUSE_SPLICE_WRITE, FUSE_SPLICE_MOVE, FUSE_SPLICE_READ, FUSE_FLOCK_LOCKS, FUSE_HAS_IOCTL_DIR, FUSE_AUTO_INVAL_DATA, FUSE_DO_READDIRPLUS, FUSE_READDIRPLUS_AUTO, FUSE_ASYNC_DIO, FUSE_WRITEBACK_CACHE, FUSE_NO_OPEN_SUPPORT, FUSE_PARALLEL_DIROPS, FUSE_HANDLE_KILLPRIV, FUSE_POSIX_ACL, FUSE_ABORT_ERROR, FUSE_MAX_PAGES, FUSE_CACHE_SYMLINKS, FUSE_NO_OPENDIR_SUPPORT, FUSE_EXPLICIT_INVAL_DATA, FUSE_MAP_ALIGNMENT, FUSE_SUBMOUNTS, FUSE_HANDLE_KILLPRIV_V2, FUSE_SETXATTR_EXT, FUSE_INIT_EXT, FUSE_INIT_RESERVED
fuse_init_flags2 = FUSE_SECURITY_CTX_FLAG2, FUSE_HAS_INODE_DAX_FLAG2, FUSE_CREATE_SUPP_GROUP_FLAG2, FUSE_HAS_EXPIRE_ONLY_FLAG2, FUSE_DIRECT_IO_ALLOW_MMAP_FLAG2, FUSE_PASSTHROUGH_FLAG2, FUSE_NO_EXPORT_SUPPORT_FLAG2, FUSE_HAS_RESEND_FLAG2, FUSE_DIRECT_IO_RELAX_FLAG2, FUSE_ALLOW_IDMAP_FLAG2

define FUSE_SECURITY_CTX_FLAG2	FUSE_SECURITY_CTX >> 32
define FUSE_HAS_INODE_DAX_FLAG2	FUSE_HAS_INODE_DAX >> 32
define FUSE_CREATE_SUPP_GROUP_FLAG2	FUSE_CREATE_SUPP_GROUP >> 32
define FUSE_HAS_EXPIRE_ONLY_FLAG2	FUSE_HAS_EXPIRE_ONLY >> 32
define FUSE_DIRECT_IO_ALLOW_MMAP_FLAG2	FUSE_DIRECT_IO_ALLOW_MMAP >> 32
define FUSE_PASSTHROUGH_FLAG2	FUSE_PASSTHROUGH >> 32
define FUSE_NO_EXPORT_SUPPORT_FLAG2	FUSE_NO_EXPORT_SUPPORT >> 32
define FUSE_HAS_RESEND_FLAG2	FUSE_HAS_RESEND >> 32
define FUSE_DIRECT_IO_RELAX_FLAG2	FUSE_DIRECT_IO_RELAX >> 32
define FUSE_ALLOW_IDMAP_FLAG2	FUSE_ALLOW_IDMAP >> 32 

fuse_lseek_out {
	offset	int64
}

fuse_bmap_out {
	block	int64
}

fuse_ioctl_out {
	res		int32
	flags		flags[fuse_ioctl_flags, int32]
	in_iovs		int32
	out_iovs	int32
}

fuse_ioctl_flags = 0, FUSE_IOCTL_RETRY

fuse_poll_out {
	revents	int32
	padding	const[0, int32]
}

fuse_notify_poll_wakeup_out {
	kh	int64
}

fuse_getxattr_out {
	size	int32
	padding	const[0, int32]
}

fuse_lk_out {
	lk	fuse_file_lock
}

fuse_file_lock {
	start	int64
	end	int64
	type	flags[fuse_lock_type, int32]
	pid	pid
}

fuse_lock_type = F_UNLCK, F_RDLCK, F_WRLCK

fuse_statfs_out {
	st	fuse_kstatfs
}

fuse_kstatfs {
	blocks	int64
	bfree	int64
	bavail	int64
	files	int64
	ffree	int64
	bsize	int32
	namelen	int32
	frsize	int32
	padding	const[0, int32]
	spare	array[const[0, int32], 6]
}

fuse_write_out {
	size	int32
	padding	const[0, int32]
}

fuse_read_out {
	content	string
}

fuse_open_out {
	fh		const[0, int64]
	open_flags	flags[fuse_open_flags, int32]
	padding		const[0, int32]
}

fuse_open_flags = FOPEN_DIRECT_IO, FOPEN_KEEP_CACHE, FOPEN_NONSEEKABLE, FOPEN_CACHE_DIR, FOPEN_STREAM

fuse_attr_out {
	attr_valid	int64
	attr_valid_nsec	int32
	dummy		const[0, int32]
	attr		fuse_attr
}

fuse_entry_out {
	nodeid			fuse_ino
	generation		fuse_gen
	entry_valid		int64
	attr_valid		int64
	entry_valid_nsec	int32
	attr_valid_nsec		int32
	attr			fuse_attr
}

fuse_create_open_out {
	entry	fuse_entry_out
	open	fuse_open_out
}

fuse_attr {
	ino		fuse_ino
	size		int64
	blocks		int64
	atime		int64
	mtime		int64
	ctime		int64
	atimensec	int32
	mtimensec	int32
	ctimensec	int32
	mode		flags[fuse_mode, int32]
	nlink		int32
	uid		uid
	gid		gid
	rdev		int32
	blksize		int32
	padding		const[0, int32]
}

fuse_dirent {
	ino	fuse_ino
	off	int64
	namelen	len[name, int32]
	type	int32
	name	stringnoz
} [align[8]]

fuse_direntplus {
	entry	fuse_entry_out
	dirent	fuse_dirent
}

fuse_notify_inval_inode_out {
	ino	fuse_ino
	off	int64
	len	int64
}

fuse_notify_inval_entry_out {
	parent1	fuse_ino
	namelen	len[name, int32]
	padding	const[0, int32]
	name	stringnoz
	zero	const[0, int8]
} [packed]

fuse_notify_delete_out {
	parent1	fuse_ino
	child	fuse_ino
	namelen	len[name, int32]
	padding	const[0, int32]
	name	stringnoz
	zero	const[0, int8]
} [packed]

fuse_notify_store_out {
	nodeid	fuse_ino
	off	int64
	size	len[data, int32]
	padding	const[0, int32]
	data	array[const[0, int8]]
} [packed]

fuse_notify_retrieve_out {
	notify_unique	const[0, int64]
	nodeid		fuse_ino
	offset		int64
	size		int32
	padding		const[0, int32]
} [packed]

fuse_statx_out {
	attr_valid	int64
	attr_valid_nsec	int32
	flags		const[0, int32]
	spare		array[const[0, int64], 2]
	stat		fuse_statx
}

fuse_statx {
	mask		flags[fuse_statx_masx, int32]
	blksize		int32
	attributes	int64
	nlink		int32
	uid		uid
	gid		gid
	mode		flags[fuse_valid_type, int16]
	__spare0	array[const[0, int16], 1]
	ino		int64
	size		int64
	blocks		int64
	attributes_mask	int64
	atime		fuse_sx_time
	btime		fuse_sx_time
	ctime		fuse_sx_time
	mtime		fuse_sx_time
	rdev_major	int32
	rdev_minor	int32
	dev_major	int32
	dev_minor	int32
	__spare2	array[const[0, int64], 14]
}

fuse_statx_masx = STATX_TYPE, STATX_MODE, STATX_NLINK, STATX_UID, STATX_GID, STATX_ATIME, STATX_MTIME, STATX_CTIME, STATX_INO, STATX_SIZE, STATX_BLOCKS, STATX_BASIC_STATS, STATX_BTIME, STATX_MNT_ID, STATX_DIOALIGN, STATX_MNT_ID_UNIQUE, STATX_SUBVOL, STATX_WRITE_ATOMIC

fuse_valid_type = S_IFLNK, S_IFREG, S_IFDIR, S_IFCHR, S_IFBLK, S_IFIFO, S_IFSOCK

fuse_sx_time {
	tv_sec		int64
	tv_nsec		int32
	__reserved	const[0, int32]
}

# Mount options.

fuse_options {
	fd		fs_opt_hex["fd", fd_fuse]
	comma0		const[',', int8]
	rootmode	fs_opt_oct["rootmode", flags[fuse_mode]]
	comma1		const[',', int8]
	user_id		fs_opt_dec["user_id", uid]
	comma2		const[',', int8]
	group_id	fs_opt_dec["group_id", gid]
	comma3		const[',', int8]
	opts		fs_options[fuse_opts]
} [packed]

fuse_opts [
	max_read		fs_opt_hex["max_read", int32]
	allow_other		stringnoz["allow_other"]
	default_permissions	stringnoz["default_permissions"]
	blksize			fs_opt_hex["blksize", flags[fuse_block_sizes]]
] [varlen]

fuse_mode = S_IFREG, S_IFCHR, S_IFBLK, S_IFIFO, S_IFSOCK, S_IFLNK, S_IFDIR
fuse_block_sizes = 512, 1024, 2048, 4096

# Used by syz_fuse_handle_req() to mimic a FUSE daemon.
syz_fuse_req_out {
	init		ptr[in, syz_fuse_out[fuse_init_out]]
	lseek		ptr[in, syz_fuse_out[fuse_lseek_out]]
	bmap		ptr[in, syz_fuse_out[fuse_bmap_out]]
	poll		ptr[in, syz_fuse_out[fuse_poll_out]]
	getxattr	ptr[in, syz_fuse_out[fuse_getxattr_out]]
	lk		ptr[in, syz_fuse_out[fuse_lk_out]]
	statfs		ptr[in, syz_fuse_out[fuse_statfs_out]]
	write		ptr[in, syz_fuse_out[fuse_write_out]]
	read		ptr[in, syz_fuse_out[fuse_read_out]]
	open		ptr[in, syz_fuse_out[fuse_open_out]]
	attr		ptr[in, syz_fuse_out[fuse_attr_out]]
	entry		ptr[in, syz_fuse_out[fuse_entry_out]]
	dirent		ptr[in, syz_fuse_out[array[fuse_dirent]]]
	direntplus	ptr[in, syz_fuse_out[array[fuse_direntplus]]]
	create_open	ptr[in, syz_fuse_out[fuse_create_open_out]]
	ioctl		ptr[in, syz_fuse_out[fuse_ioctl_out]]
	statx		ptr[in, syz_fuse_out[fuse_statx_out]]
}

fuse_backing_map {
	fd	fd_fuse
	flags	const[0, int32]
	padding	const[0, int64]
}
