@@ -234,10 +234,18 @@ __releases(fiq->lock)
}
void fuse_queue_forget(struct fuse_conn *fc, struct fuse_forget_link *forget,
- u64 nodeid, u64 nlookup)
+ u64 nodeid, u64 nlookup)
{
struct fuse_iqueue *fiq = &fc->iq;
+ /*
+ * The nullptr means that fuse_queue_forget() is used in error cases.
+ * Avoid preallocating this structure because it is unlikely used.
+ * Use __GFP_NOFAIL to make memory allocation always succeed.
+ */
+ if (forget == NULL)
+ forget = fuse_alloc_forget(GFP_KERNEL_ACCOUNT | __GFP_NOFAIL);
+
forget->forget_one.nodeid = nodeid;
forget->forget_one.nlookup = nlookup;
@@ -508,8 +516,7 @@ ssize_t fuse_simple_request(struct fuse_mount *fm, struct fuse_args *args)
fuse_adjust_compat(fc, args);
fuse_args_to_req(req, args);
- if (!args->noreply)
- __set_bit(FR_ISREPLY, &req->flags);
+ __set_bit(FR_ISREPLY, &req->flags);
__fuse_request_send(req);
ret = req->out.h.error;
if (!ret && args->out_argvar) {
@@ -207,7 +207,6 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags)
(flags & (LOOKUP_EXCL | LOOKUP_REVAL | LOOKUP_RENAME_TARGET))) {
struct fuse_entry_out outarg;
FUSE_ARGS(args);
- struct fuse_forget_link *forget;
u64 attr_version;
/* For negative dentries, always do a fresh lookup */
@@ -220,11 +219,6 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags)
fm = get_fuse_mount(inode);
- forget = fuse_alloc_forget();
- ret = -ENOMEM;
- if (!forget)
- goto out;
-
attr_version = fuse_get_attr_version(fm->fc);
parent = dget_parent(entry);
@@ -239,15 +233,13 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags)
fi = get_fuse_inode(inode);
if (outarg.nodeid != get_node_id(inode) ||
(bool) IS_AUTOMOUNT(inode) != (bool) (outarg.attr.flags & FUSE_ATTR_SUBMOUNT)) {
- fuse_queue_forget(fm->fc, forget,
- outarg.nodeid, 1);
+ fuse_queue_forget(fm->fc, NULL, outarg.nodeid, 1);
goto invalid;
}
spin_lock(&fi->lock);
fi->nlookup++;
spin_unlock(&fi->lock);
}
- kfree(forget);
if (ret == -ENOMEM || ret == -EINTR)
goto out;
if (ret || fuse_invalid_attr(&outarg.attr) ||
@@ -365,7 +357,6 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name
{
struct fuse_mount *fm = get_fuse_mount_super(sb);
FUSE_ARGS(args);
- struct fuse_forget_link *forget;
u64 attr_version;
int err;
@@ -374,23 +365,17 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name
if (name->len > FUSE_NAME_MAX)
goto out;
-
- forget = fuse_alloc_forget();
- err = -ENOMEM;
- if (!forget)
- goto out;
-
attr_version = fuse_get_attr_version(fm->fc);
fuse_lookup_init(fm->fc, &args, nodeid, name, outarg);
err = fuse_simple_request(fm, &args);
/* Zero nodeid is same as -ENOENT, but with valid timeout */
if (err || !outarg->nodeid)
- goto out_put_forget;
+ goto out;
err = -EIO;
if (fuse_invalid_attr(&outarg->attr))
- goto out_put_forget;
+ goto out;
if (outarg->nodeid == FUSE_ROOT_ID && outarg->generation != 0) {
pr_warn_once("root generation should be zero\n");
outarg->generation = 0;
@@ -401,13 +386,11 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name
attr_version);
err = -ENOMEM;
if (!*inode) {
- fuse_queue_forget(fm->fc, forget, outarg->nodeid, 1);
+ fuse_queue_forget(fm->fc, NULL, outarg->nodeid, 1);
goto out;
}
err = 0;
- out_put_forget:
- kfree(forget);
out:
return err;
}
@@ -617,7 +600,6 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry,
struct inode *inode;
struct fuse_mount *fm = get_fuse_mount(dir);
FUSE_ARGS(args);
- struct fuse_forget_link *forget;
struct fuse_create_in inarg;
struct fuse_open_out *outopenp;
struct fuse_entry_out outentry;
@@ -628,15 +610,10 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry,
/* Userspace expects S_IFREG in create mode */
BUG_ON((mode & S_IFMT) != S_IFREG);
- forget = fuse_alloc_forget();
- err = -ENOMEM;
- if (!forget)
- goto out_err;
-
err = -ENOMEM;
ff = fuse_file_alloc(fm, true);
if (!ff)
- goto out_put_forget_req;
+ goto out_err;
if (!fm->fc->dont_mask)
mode &= ~current_umask();
@@ -670,7 +647,7 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry,
err = get_create_ext(&args, dir, entry, mode);
if (err)
- goto out_put_forget_req;
+ goto out_err;
err = fuse_simple_request(fm, &args);
free_ext_value(&args);
@@ -690,11 +667,10 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry,
if (!inode) {
flags &= ~(O_CREAT | O_EXCL | O_TRUNC);
fuse_sync_release(NULL, ff, flags);
- fuse_queue_forget(fm->fc, forget, outentry.nodeid, 1);
+ fuse_queue_forget(fm->fc, NULL, outentry.nodeid, 1);
err = -ENOMEM;
goto out_err;
}
- kfree(forget);
d_instantiate(entry, inode);
fuse_change_entry_timeout(entry, &outentry);
fuse_dir_changed(dir);
@@ -716,8 +692,6 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry,
out_free_ff:
fuse_file_free(ff);
-out_put_forget_req:
- kfree(forget);
out_err:
return err;
}
@@ -782,15 +756,10 @@ static int create_new_entry(struct fuse_mount *fm, struct fuse_args *args,
struct inode *inode;
struct dentry *d;
int err;
- struct fuse_forget_link *forget;
if (fuse_is_bad(dir))
return -EIO;
- forget = fuse_alloc_forget();
- if (!forget)
- return -ENOMEM;
-
memset(&outarg, 0, sizeof(outarg));
args->nodeid = get_node_id(dir);
args->out_numargs = 1;
@@ -800,28 +769,27 @@ static int create_new_entry(struct fuse_mount *fm, struct fuse_args *args,
if (args->opcode != FUSE_LINK) {
err = get_create_ext(args, dir, entry, mode);
if (err)
- goto out_put_forget_req;
+ goto out_err;
}
err = fuse_simple_request(fm, args);
free_ext_value(args);
if (err)
- goto out_put_forget_req;
+ goto out_err;
err = -EIO;
if (invalid_nodeid(outarg.nodeid) || fuse_invalid_attr(&outarg.attr))
- goto out_put_forget_req;
+ goto out_err;
if ((outarg.attr.mode ^ mode) & S_IFMT)
- goto out_put_forget_req;
+ goto out_err;
inode = fuse_iget(dir->i_sb, outarg.nodeid, outarg.generation,
&outarg.attr, ATTR_TIMEOUT(&outarg), 0);
if (!inode) {
- fuse_queue_forget(fm->fc, forget, outarg.nodeid, 1);
+ fuse_queue_forget(fm->fc, NULL, outarg.nodeid, 1);
return -ENOMEM;
}
- kfree(forget);
d_drop(entry);
d = d_splice_alias(inode, entry);
@@ -837,10 +805,9 @@ static int create_new_entry(struct fuse_mount *fm, struct fuse_args *args,
fuse_dir_changed(dir);
return 0;
- out_put_forget_req:
+ out_err:
if (err == -EEXIST)
fuse_invalidate_entry(entry);
- kfree(forget);
return err;
}
@@ -298,7 +298,6 @@ struct fuse_args {
uint8_t out_numargs;
uint8_t ext_idx;
bool force:1;
- bool noreply:1;
bool nocreds:1;
bool in_pages:1;
bool out_pages:1;
@@ -1048,10 +1047,13 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name
/**
* Send FORGET command
*/
-void fuse_queue_forget(struct fuse_conn *fc, struct fuse_forget_link *forget,
- u64 nodeid, u64 nlookup);
+static inline struct fuse_forget_link *fuse_alloc_forget(gfp_t flags)
+{
+ return kzalloc(sizeof(struct fuse_forget_link), flags);
+}
-struct fuse_forget_link *fuse_alloc_forget(void);
+void fuse_queue_forget(struct fuse_conn *fc, struct fuse_forget_link* forget,
+ u64 nodeid, u64 nlookup);
struct fuse_forget_link *fuse_dequeue_forget(struct fuse_iqueue *fiq,
unsigned int max,
@@ -63,11 +63,6 @@ MODULE_PARM_DESC(max_user_congthresh,
static struct file_system_type fuseblk_fs_type;
#endif
-struct fuse_forget_link *fuse_alloc_forget(void)
-{
- return kzalloc(sizeof(struct fuse_forget_link), GFP_KERNEL_ACCOUNT);
-}
-
static struct fuse_submount_lookup *fuse_alloc_submount_lookup(void)
{
struct fuse_submount_lookup *sl;
@@ -75,7 +70,7 @@ static struct fuse_submount_lookup *fuse_alloc_submount_lookup(void)
sl = kzalloc(sizeof(struct fuse_submount_lookup), GFP_KERNEL_ACCOUNT);
if (!sl)
return NULL;
- sl->forget = fuse_alloc_forget();
+ sl->forget = fuse_alloc_forget(GFP_KERNEL_ACCOUNT);
if (!sl->forget)
goto out_free;
@@ -104,7 +99,7 @@ static struct inode *fuse_alloc_inode(struct super_block *sb)
fi->submount_lookup = NULL;
mutex_init(&fi->mutex);
spin_lock_init(&fi->lock);
- fi->forget = fuse_alloc_forget();
+ fi->forget = fuse_alloc_forget(GFP_KERNEL_ACCOUNT);
if (!fi->forget)
goto out_free;
@@ -262,27 +262,6 @@ static int fuse_direntplus_link(struct file *file,
return 0;
}
-static void fuse_force_forget(struct file *file, u64 nodeid)
-{
- struct inode *inode = file_inode(file);
- struct fuse_mount *fm = get_fuse_mount(inode);
- struct fuse_forget_in inarg;
- FUSE_ARGS(args);
-
- memset(&inarg, 0, sizeof(inarg));
- inarg.nlookup = 1;
- args.opcode = FUSE_FORGET;
- args.nodeid = nodeid;
- args.in_numargs = 1;
- args.in_args[0].size = sizeof(inarg);
- args.in_args[0].value = &inarg;
- args.force = true;
- args.noreply = true;
-
- fuse_simple_request(fm, &args);
- /* ignore errors */
-}
-
static int parse_dirplusfile(char *buf, size_t nbytes, struct file *file,
struct dir_context *ctx, u64 attr_version)
{
@@ -320,8 +299,13 @@ static int parse_dirplusfile(char *buf, size_t nbytes, struct file *file,
nbytes -= reclen;
ret = fuse_direntplus_link(file, direntplus, attr_version);
- if (ret)
- fuse_force_forget(file, direntplus->entry_out.nodeid);
+ if (ret) {
+ struct inode *inode = file_inode(file);
+ struct fuse_mount *fm = get_fuse_mount(inode);
+
+ fuse_queue_forget(fm->fc, NULL,
+ direntplus->entry_out.nodeid, 1);
+ }
}
return 0;
The `struct fuse_forget_link` is allocated outside `fuse_queue_forget()` before this patch. This requires the allocation in advance. In some cases (e.g., directory operations without errors), this struct is not needed but allocated, which contributes to memory usage and performance degradation. Besides, this messes up the code to some extent. So move the `fuse_forget_link` allocation inside `fuse_queue_forget()` with __GFP_NOFAIL in these cases. Since the inode eviction path can be triggered from memory reclaim and may block the system, it is better to preallocate it on inode creation. `fuse_force_forget()` is used by `readdirplus` before this patch for the reason that we do not know how many 'fuse_forget_link' structures will be allocated in advance when error happens. After this patch, this function is not needed any more and can be removed (The `noreply` fuse_args can also be removed). By this way, all FUSE_FORGET requests are sent by using `fuse_queue_forget()` function as e.g. virtiofs handles them differently from regular requests. Signed-off-by: yangyun <yangyun50@huawei.com> --- fs/fuse/dev.c | 13 ++++++++--- fs/fuse/dir.c | 59 +++++++++++------------------------------------ fs/fuse/fuse_i.h | 10 ++++---- fs/fuse/inode.c | 9 ++------ fs/fuse/readdir.c | 30 ++++++------------------ 5 files changed, 38 insertions(+), 83 deletions(-)