Skip to content

Commit cb098dd

Browse files
amir73ilMiklos Szeredi
authored andcommitted
fuse: introduce inode io modes
The fuse inode io mode is determined by the mode of its open files/mmaps and parallel dio opens and expressed in the value of fi->iocachectr: > 0 - caching io: files open in caching mode or mmap on direct_io file < 0 - parallel dio: direct io mode with parallel dio writes enabled == 0 - direct io: no files open in caching mode and no files mmaped Note that iocachectr value of 0 might become positive or negative, while non-parallel dio is getting processed. direct_io mmap uses page cache, so first mmap will mark the file as ff->io_opened and increment fi->iocachectr to enter the caching io mode. If the server opens the file in caching mode while it is already open for parallel dio or vice versa the open fails. This allows executing parallel dio when inode is not in caching mode and no mmaps have been performed on the inode in question. Signed-off-by: Bernd Schubert <[email protected]> Signed-off-by: Amir Goldstein <[email protected]> Signed-off-by: Miklos Szeredi <[email protected]>
1 parent d2c487f commit cb098dd

File tree

4 files changed

+189
-2
lines changed

4 files changed

+189
-2
lines changed

fs/fuse/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ obj-$(CONFIG_CUSE) += cuse.o
88
obj-$(CONFIG_VIRTIO_FS) += virtiofs.o
99

1010
fuse-y := dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o ioctl.o
11+
fuse-y += iomode.o
1112
fuse-$(CONFIG_FUSE_DAX) += dax.o
1213

1314
virtiofs-y := virtio_fs.o

fs/fuse/file.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ static void fuse_file_put(struct fuse_file *ff, bool sync)
113113
struct fuse_release_args *ra = ff->release_args;
114114
struct fuse_args *args = (ra ? &ra->args : NULL);
115115

116+
if (ra && ra->inode)
117+
fuse_file_io_release(ff, ra->inode);
118+
116119
if (!args) {
117120
/* Do nothing when server does not implement 'open' */
118121
} else if (sync) {
@@ -204,6 +207,11 @@ int fuse_finish_open(struct inode *inode, struct file *file)
204207
{
205208
struct fuse_file *ff = file->private_data;
206209
struct fuse_conn *fc = get_fuse_conn(inode);
210+
int err;
211+
212+
err = fuse_file_io_open(file, inode);
213+
if (err)
214+
return err;
207215

208216
if (ff->open_flags & FOPEN_STREAM)
209217
stream_open(inode, file);
@@ -2509,6 +2517,7 @@ static int fuse_file_mmap(struct file *file, struct vm_area_struct *vma)
25092517
{
25102518
struct fuse_file *ff = file->private_data;
25112519
struct fuse_conn *fc = ff->fm->fc;
2520+
int rc;
25122521

25132522
/* DAX mmap is superior to direct_io mmap */
25142523
if (FUSE_IS_DAX(file_inode(file)))
@@ -2528,6 +2537,11 @@ static int fuse_file_mmap(struct file *file, struct vm_area_struct *vma)
25282537
/* MAP_PRIVATE */
25292538
return generic_file_mmap(file, vma);
25302539
}
2540+
2541+
/* First mmap of direct_io file enters caching inode io mode. */
2542+
rc = fuse_file_cached_io_start(file_inode(file), ff);
2543+
if (rc)
2544+
return rc;
25312545
}
25322546

25332547
if ((vma->vm_flags & VM_SHARED) && (vma->vm_flags & VM_MAYWRITE))
@@ -3296,6 +3310,7 @@ void fuse_init_file_inode(struct inode *inode, unsigned int flags)
32963310
INIT_LIST_HEAD(&fi->write_files);
32973311
INIT_LIST_HEAD(&fi->queued_writes);
32983312
fi->writectr = 0;
3313+
fi->iocachectr = 0;
32993314
init_waitqueue_head(&fi->page_waitq);
33003315
fi->writepages = RB_ROOT;
33013316

fs/fuse/fuse_i.h

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ struct fuse_inode {
111111
u64 attr_version;
112112

113113
union {
114-
/* Write related fields (regular file only) */
114+
/* read/write io cache (regular file only) */
115115
struct {
116116
/* Files usable in writepage. Protected by fi->lock */
117117
struct list_head write_files;
@@ -123,6 +123,9 @@ struct fuse_inode {
123123
* (FUSE_NOWRITE) means more writes are blocked */
124124
int writectr;
125125

126+
/** Number of files/maps using page cache */
127+
int iocachectr;
128+
126129
/* Waitq for writepage completion */
127130
wait_queue_head_t page_waitq;
128131

@@ -187,6 +190,8 @@ enum {
187190
FUSE_I_BAD,
188191
/* Has btime */
189192
FUSE_I_BTIME,
193+
/* Wants or already has page cache IO */
194+
FUSE_I_CACHE_IO_MODE,
190195
};
191196

192197
struct fuse_conn;
@@ -244,6 +249,9 @@ struct fuse_file {
244249
/** Wait queue head for poll */
245250
wait_queue_head_t poll_wait;
246251

252+
/** Does file hold a fi->iocachectr refcount? */
253+
enum { IOM_NONE, IOM_CACHED, IOM_UNCACHED } iomode;
254+
247255
/** Has flock been performed on this file? */
248256
bool flock:1;
249257
};
@@ -1343,8 +1351,13 @@ int fuse_fileattr_get(struct dentry *dentry, struct fileattr *fa);
13431351
int fuse_fileattr_set(struct mnt_idmap *idmap,
13441352
struct dentry *dentry, struct fileattr *fa);
13451353

1346-
/* file.c */
1354+
/* iomode.c */
1355+
int fuse_file_cached_io_start(struct inode *inode, struct fuse_file *ff);
13471356

1357+
int fuse_file_io_open(struct file *file, struct inode *inode);
1358+
void fuse_file_io_release(struct fuse_file *ff, struct inode *inode);
1359+
1360+
/* file.c */
13481361
struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid,
13491362
unsigned int open_flags, bool isdir);
13501363
void fuse_file_release(struct inode *inode, struct fuse_file *ff,

fs/fuse/iomode.c

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* FUSE inode io modes.
4+
*
5+
* Copyright (c) 2024 CTERA Networks.
6+
*/
7+
8+
#include "fuse_i.h"
9+
10+
#include <linux/kernel.h>
11+
#include <linux/sched.h>
12+
#include <linux/file.h>
13+
#include <linux/fs.h>
14+
15+
/*
16+
* Start cached io mode, where parallel dio writes are not allowed.
17+
*/
18+
int fuse_file_cached_io_start(struct inode *inode, struct fuse_file *ff)
19+
{
20+
struct fuse_inode *fi = get_fuse_inode(inode);
21+
int err = 0;
22+
23+
/* There are no io modes if server does not implement open */
24+
if (!ff->release_args)
25+
return 0;
26+
27+
spin_lock(&fi->lock);
28+
if (fi->iocachectr < 0) {
29+
err = -ETXTBSY;
30+
goto unlock;
31+
}
32+
WARN_ON(ff->iomode == IOM_UNCACHED);
33+
if (ff->iomode == IOM_NONE) {
34+
ff->iomode = IOM_CACHED;
35+
if (fi->iocachectr == 0)
36+
set_bit(FUSE_I_CACHE_IO_MODE, &fi->state);
37+
fi->iocachectr++;
38+
}
39+
unlock:
40+
spin_unlock(&fi->lock);
41+
return err;
42+
}
43+
44+
static void fuse_file_cached_io_end(struct inode *inode, struct fuse_file *ff)
45+
{
46+
struct fuse_inode *fi = get_fuse_inode(inode);
47+
48+
spin_lock(&fi->lock);
49+
WARN_ON(fi->iocachectr <= 0);
50+
WARN_ON(ff->iomode != IOM_CACHED);
51+
ff->iomode = IOM_NONE;
52+
fi->iocachectr--;
53+
if (fi->iocachectr == 0)
54+
clear_bit(FUSE_I_CACHE_IO_MODE, &fi->state);
55+
spin_unlock(&fi->lock);
56+
}
57+
58+
/* Start strictly uncached io mode where cache access is not allowed */
59+
static int fuse_file_uncached_io_start(struct inode *inode, struct fuse_file *ff)
60+
{
61+
struct fuse_inode *fi = get_fuse_inode(inode);
62+
int err = 0;
63+
64+
spin_lock(&fi->lock);
65+
if (fi->iocachectr > 0) {
66+
err = -ETXTBSY;
67+
goto unlock;
68+
}
69+
WARN_ON(ff->iomode != IOM_NONE);
70+
fi->iocachectr--;
71+
ff->iomode = IOM_UNCACHED;
72+
unlock:
73+
spin_unlock(&fi->lock);
74+
return err;
75+
}
76+
77+
static void fuse_file_uncached_io_end(struct inode *inode, struct fuse_file *ff)
78+
{
79+
struct fuse_inode *fi = get_fuse_inode(inode);
80+
81+
spin_lock(&fi->lock);
82+
WARN_ON(fi->iocachectr >= 0);
83+
WARN_ON(ff->iomode != IOM_UNCACHED);
84+
ff->iomode = IOM_NONE;
85+
fi->iocachectr++;
86+
spin_unlock(&fi->lock);
87+
}
88+
89+
/* Request access to submit new io to inode via open file */
90+
int fuse_file_io_open(struct file *file, struct inode *inode)
91+
{
92+
struct fuse_file *ff = file->private_data;
93+
int err;
94+
95+
/*
96+
* io modes are not relevant with DAX and with server that does not
97+
* implement open.
98+
*/
99+
if (FUSE_IS_DAX(inode) || !ff->release_args)
100+
return 0;
101+
102+
/*
103+
* FOPEN_PARALLEL_DIRECT_WRITES requires FOPEN_DIRECT_IO.
104+
*/
105+
if (!(ff->open_flags & FOPEN_DIRECT_IO))
106+
ff->open_flags &= ~FOPEN_PARALLEL_DIRECT_WRITES;
107+
108+
/*
109+
* First parallel dio open denies caching inode io mode.
110+
* First caching file open enters caching inode io mode.
111+
*
112+
* Note that if user opens a file open with O_DIRECT, but server did
113+
* not specify FOPEN_DIRECT_IO, a later fcntl() could remove O_DIRECT,
114+
* so we put the inode in caching mode to prevent parallel dio.
115+
*/
116+
if (ff->open_flags & FOPEN_DIRECT_IO) {
117+
if (ff->open_flags & FOPEN_PARALLEL_DIRECT_WRITES)
118+
err = fuse_file_uncached_io_start(inode, ff);
119+
else
120+
return 0;
121+
} else {
122+
err = fuse_file_cached_io_start(inode, ff);
123+
}
124+
if (err)
125+
goto fail;
126+
127+
return 0;
128+
129+
fail:
130+
pr_debug("failed to open file in requested io mode (open_flags=0x%x, err=%i).\n",
131+
ff->open_flags, err);
132+
/*
133+
* The file open mode determines the inode io mode.
134+
* Using incorrect open mode is a server mistake, which results in
135+
* user visible failure of open() with EIO error.
136+
*/
137+
return -EIO;
138+
}
139+
140+
/* No more pending io and no new io possible to inode via open/mmapped file */
141+
void fuse_file_io_release(struct fuse_file *ff, struct inode *inode)
142+
{
143+
/*
144+
* Last parallel dio close allows caching inode io mode.
145+
* Last caching file close exits caching inode io mode.
146+
*/
147+
switch (ff->iomode) {
148+
case IOM_NONE:
149+
/* Nothing to do */
150+
break;
151+
case IOM_UNCACHED:
152+
fuse_file_uncached_io_end(inode, ff);
153+
break;
154+
case IOM_CACHED:
155+
fuse_file_cached_io_end(inode, ff);
156+
break;
157+
}
158+
}

0 commit comments

Comments
 (0)