Commit d40b2f4c authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull fuse updates from Miklos Szeredi:

 - Fix regression in fileattr permission checking

 - Fix possible hang during PID namespace destruction

 - Add generic support for request extensions

 - Add supplementary group list extension

 - Add limited support for supplying supplementary groups in create
   requests

 - Documentation fixes

* tag 'fuse-update-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/fuse:
  fuse: add inode/permission checks to fileattr_get/fileattr_set
  fuse: fix all W=1 kernel-doc warnings
  fuse: in fuse_flush only wait if someone wants the return code
  fuse: optional supplementary group in create requests
  fuse: add request extension
parents da15efe1 1cc4606d
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -256,7 +256,7 @@ static int cuse_parse_one(char **pp, char *end, char **keyp, char **valp)
}

/**
 * cuse_parse_dev_info - parse device info
 * cuse_parse_devinfo - parse device info
 * @p: device info string
 * @len: length of device info string
 * @devinfo: out parameter for parsed device info
+3 −1
Original line number Diff line number Diff line
@@ -204,7 +204,7 @@ static unsigned int fuse_req_hash(u64 unique)
	return hash_long(unique & ~FUSE_INT_REQ_BIT, FUSE_PQ_HASH_BITS);
}

/**
/*
 * A new request is available, wake fiq->waitq
 */
static void fuse_dev_wake_and_unlock(struct fuse_iqueue *fiq)
@@ -476,6 +476,8 @@ static void fuse_args_to_req(struct fuse_req *req, struct fuse_args *args)
	req->in.h.opcode = args->opcode;
	req->in.h.nodeid = args->nodeid;
	req->args = args;
	if (args->is_ext)
		req->in.h.total_extlen = args->in_args[args->ext_idx].size / 8;
	if (args->end)
		__set_bit(FR_ASYNC, &req->flags);
}
+97 −29
Original line number Diff line number Diff line
@@ -145,7 +145,7 @@ static void fuse_dir_changed(struct inode *dir)
	inode_maybe_inc_iversion(dir, false);
}

/**
/*
 * Mark the attributes as stale due to an atime change.  Avoid the invalidate if
 * atime is not used.
 */
@@ -466,7 +466,7 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry,
}

static int get_security_context(struct dentry *entry, umode_t mode,
				void **security_ctx, u32 *security_ctxlen)
				struct fuse_in_arg *ext)
{
	struct fuse_secctx *fctx;
	struct fuse_secctx_header *header;
@@ -513,14 +513,100 @@ static int get_security_context(struct dentry *entry, umode_t mode,

		memcpy(ptr, ctx, ctxlen);
	}
	*security_ctxlen = total_len;
	*security_ctx = header;
	ext->size = total_len;
	ext->value = header;
	err = 0;
out_err:
	kfree(ctx);
	return err;
}

static void *extend_arg(struct fuse_in_arg *buf, u32 bytes)
{
	void *p;
	u32 newlen = buf->size + bytes;

	p = krealloc(buf->value, newlen, GFP_KERNEL);
	if (!p) {
		kfree(buf->value);
		buf->size = 0;
		buf->value = NULL;
		return NULL;
	}

	memset(p + buf->size, 0, bytes);
	buf->value = p;
	buf->size = newlen;

	return p + newlen - bytes;
}

static u32 fuse_ext_size(size_t size)
{
	return FUSE_REC_ALIGN(sizeof(struct fuse_ext_header) + size);
}

/*
 * This adds just a single supplementary group that matches the parent's group.
 */
static int get_create_supp_group(struct inode *dir, struct fuse_in_arg *ext)
{
	struct fuse_conn *fc = get_fuse_conn(dir);
	struct fuse_ext_header *xh;
	struct fuse_supp_groups *sg;
	kgid_t kgid = dir->i_gid;
	gid_t parent_gid = from_kgid(fc->user_ns, kgid);
	u32 sg_len = fuse_ext_size(sizeof(*sg) + sizeof(sg->groups[0]));

	if (parent_gid == (gid_t) -1 || gid_eq(kgid, current_fsgid()) ||
	    !in_group_p(kgid))
		return 0;

	xh = extend_arg(ext, sg_len);
	if (!xh)
		return -ENOMEM;

	xh->size = sg_len;
	xh->type = FUSE_EXT_GROUPS;

	sg = (struct fuse_supp_groups *) &xh[1];
	sg->nr_groups = 1;
	sg->groups[0] = parent_gid;

	return 0;
}

static int get_create_ext(struct fuse_args *args,
			  struct inode *dir, struct dentry *dentry,
			  umode_t mode)
{
	struct fuse_conn *fc = get_fuse_conn_super(dentry->d_sb);
	struct fuse_in_arg ext = { .size = 0, .value = NULL };
	int err = 0;

	if (fc->init_security)
		err = get_security_context(dentry, mode, &ext);
	if (!err && fc->create_supp_group)
		err = get_create_supp_group(dir, &ext);

	if (!err && ext.size) {
		WARN_ON(args->in_numargs >= ARRAY_SIZE(args->in_args));
		args->is_ext = true;
		args->ext_idx = args->in_numargs++;
		args->in_args[args->ext_idx] = ext;
	} else {
		kfree(ext.value);
	}

	return err;
}

static void free_ext_value(struct fuse_args *args)
{
	if (args->is_ext)
		kfree(args->in_args[args->ext_idx].value);
}

/*
 * Atomic create+open operation
 *
@@ -541,8 +627,6 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry,
	struct fuse_entry_out outentry;
	struct fuse_inode *fi;
	struct fuse_file *ff;
	void *security_ctx = NULL;
	u32 security_ctxlen;
	bool trunc = flags & O_TRUNC;

	/* Userspace expects S_IFREG in create mode */
@@ -586,19 +670,12 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry,
	args.out_args[1].size = sizeof(outopen);
	args.out_args[1].value = &outopen;

	if (fm->fc->init_security) {
		err = get_security_context(entry, mode, &security_ctx,
					   &security_ctxlen);
	err = get_create_ext(&args, dir, entry, mode);
	if (err)
		goto out_put_forget_req;

		args.in_numargs = 3;
		args.in_args[2].size = security_ctxlen;
		args.in_args[2].value = security_ctx;
	}

	err = fuse_simple_request(fm, &args);
	kfree(security_ctx);
	free_ext_value(&args);
	if (err)
		goto out_free_ff;

@@ -705,8 +782,6 @@ static int create_new_entry(struct fuse_mount *fm, struct fuse_args *args,
	struct dentry *d;
	int err;
	struct fuse_forget_link *forget;
	void *security_ctx = NULL;
	u32 security_ctxlen;

	if (fuse_is_bad(dir))
		return -EIO;
@@ -721,21 +796,14 @@ static int create_new_entry(struct fuse_mount *fm, struct fuse_args *args,
	args->out_args[0].size = sizeof(outarg);
	args->out_args[0].value = &outarg;

	if (fm->fc->init_security && args->opcode != FUSE_LINK) {
		err = get_security_context(entry, mode, &security_ctx,
					   &security_ctxlen);
	if (args->opcode != FUSE_LINK) {
		err = get_create_ext(args, dir, entry, mode);
		if (err)
			goto out_put_forget_req;

		BUG_ON(args->in_numargs != 2);

		args->in_numargs = 3;
		args->in_args[2].size = security_ctxlen;
		args->in_args[2].value = security_ctx;
	}

	err = fuse_simple_request(fm, args);
	kfree(security_ctx);
	free_ext_value(args);
	if (err)
		goto out_put_forget_req;

+64 −27
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@
#include <linux/uio.h>
#include <linux/fs.h>
#include <linux/filelock.h>
#include <linux/file.h>

static int fuse_send_open(struct fuse_mount *fm, u64 nodeid,
			  unsigned int open_flags, int opcode,
@@ -478,48 +479,36 @@ static void fuse_sync_writes(struct inode *inode)
	fuse_release_nowrite(inode);
}

static int fuse_flush(struct file *file, fl_owner_t id)
{
	struct inode *inode = file_inode(file);
	struct fuse_mount *fm = get_fuse_mount(inode);
	struct fuse_file *ff = file->private_data;
struct fuse_flush_args {
	struct fuse_args args;
	struct fuse_flush_in inarg;
	FUSE_ARGS(args);
	int err;

	if (fuse_is_bad(inode))
		return -EIO;
	struct work_struct work;
	struct file *file;
};

	if (ff->open_flags & FOPEN_NOFLUSH && !fm->fc->writeback_cache)
		return 0;
static int fuse_do_flush(struct fuse_flush_args *fa)
{
	int err;
	struct inode *inode = file_inode(fa->file);
	struct fuse_mount *fm = get_fuse_mount(inode);

	err = write_inode_now(inode, 1);
	if (err)
		return err;
		goto out;

	inode_lock(inode);
	fuse_sync_writes(inode);
	inode_unlock(inode);

	err = filemap_check_errors(file->f_mapping);
	err = filemap_check_errors(fa->file->f_mapping);
	if (err)
		return err;
		goto out;

	err = 0;
	if (fm->fc->no_flush)
		goto inval_attr_out;

	memset(&inarg, 0, sizeof(inarg));
	inarg.fh = ff->fh;
	inarg.lock_owner = fuse_lock_owner_id(fm->fc, id);
	args.opcode = FUSE_FLUSH;
	args.nodeid = get_node_id(inode);
	args.in_numargs = 1;
	args.in_args[0].size = sizeof(inarg);
	args.in_args[0].value = &inarg;
	args.force = true;

	err = fuse_simple_request(fm, &args);
	err = fuse_simple_request(fm, &fa->args);
	if (err == -ENOSYS) {
		fm->fc->no_flush = 1;
		err = 0;
@@ -532,9 +521,57 @@ static int fuse_flush(struct file *file, fl_owner_t id)
	 */
	if (!err && fm->fc->writeback_cache)
		fuse_invalidate_attr_mask(inode, STATX_BLOCKS);

out:
	fput(fa->file);
	kfree(fa);
	return err;
}

static void fuse_flush_async(struct work_struct *work)
{
	struct fuse_flush_args *fa = container_of(work, typeof(*fa), work);

	fuse_do_flush(fa);
}

static int fuse_flush(struct file *file, fl_owner_t id)
{
	struct fuse_flush_args *fa;
	struct inode *inode = file_inode(file);
	struct fuse_mount *fm = get_fuse_mount(inode);
	struct fuse_file *ff = file->private_data;

	if (fuse_is_bad(inode))
		return -EIO;

	if (ff->open_flags & FOPEN_NOFLUSH && !fm->fc->writeback_cache)
		return 0;

	fa = kzalloc(sizeof(*fa), GFP_KERNEL);
	if (!fa)
		return -ENOMEM;

	fa->inarg.fh = ff->fh;
	fa->inarg.lock_owner = fuse_lock_owner_id(fm->fc, id);
	fa->args.opcode = FUSE_FLUSH;
	fa->args.nodeid = get_node_id(inode);
	fa->args.in_numargs = 1;
	fa->args.in_args[0].size = sizeof(fa->inarg);
	fa->args.in_args[0].value = &fa->inarg;
	fa->args.force = true;
	fa->file = get_file(file);

	/* Don't wait if the task is exiting */
	if (current->flags & PF_EXITING) {
		INIT_WORK(&fa->work, fuse_flush_async);
		schedule_work(&fa->work);
		return 0;
	}

	return fuse_do_flush(fa);
}

int fuse_fsync_common(struct file *file, loff_t start, loff_t end,
		      int datasync, int opcode)
{
@@ -653,7 +690,7 @@ static ssize_t fuse_get_res_by_io(struct fuse_io_priv *io)
	return io->bytes < 0 ? io->size : io->bytes;
}

/**
/*
 * In case of short read, the caller sets 'pos' to the position of
 * actual end of fuse request in IO request. Otherwise, if bytes_requested
 * == bytes_transferred or rw == WRITE, the caller sets 'pos' to -1.
+7 −2
Original line number Diff line number Diff line
@@ -249,8 +249,9 @@ struct fuse_page_desc {
struct fuse_args {
	uint64_t nodeid;
	uint32_t opcode;
	unsigned short in_numargs;
	unsigned short out_numargs;
	uint8_t in_numargs;
	uint8_t out_numargs;
	uint8_t ext_idx;
	bool force:1;
	bool noreply:1;
	bool nocreds:1;
@@ -261,6 +262,7 @@ struct fuse_args {
	bool page_zeroing:1;
	bool page_replace:1;
	bool may_block:1;
	bool is_ext:1;
	struct fuse_in_arg in_args[3];
	struct fuse_arg out_args[2];
	void (*end)(struct fuse_mount *fm, struct fuse_args *args, int error);
@@ -781,6 +783,9 @@ struct fuse_conn {
	/* Initialize security xattrs when creating a new inode */
	unsigned int init_security:1;

	/* Add supplementary group info when creating a new inode */
	unsigned int create_supp_group:1;

	/* Does the filesystem support per inode DAX? */
	unsigned int inode_dax:1;

Loading