Skip to content

Commit 4a90451

Browse files
amir73ilMiklos Szeredi
authored andcommitted
fuse: implement open in passthrough mode
After getting a backing file id with FUSE_DEV_IOC_BACKING_OPEN ioctl, a FUSE server can reply to an OPEN request with flag FOPEN_PASSTHROUGH and the backing file id. The FUSE server should reuse the same backing file id for all the open replies of the same FUSE inode and open will fail (with -EIO) if a the server attempts to open the same inode with conflicting io modes or to setup passthrough to two different backing files for the same FUSE inode. Using the same backing file id for several different inodes is allowed. Opening a new file with FOPEN_DIRECT_IO for an inode that is already open for passthrough is allowed, but only if the FOPEN_PASSTHROUGH flag and correct backing file id are specified as well. The read/write IO of such files will not use passthrough operations to the backing file, but mmap, which does not support direct_io, will use the backing file insead of using the page cache as it always did. Even though all FUSE passthrough files of the same inode use the same backing file as a backing inode reference, each FUSE file opens a unique instance of a backing_file object to store the FUSE path that was used to open the inode and the open flags of the specific open file. The per-file, backing_file object is released along with the FUSE file. The inode associated fuse_backing object is released when the last FUSE passthrough file of that inode is released AND when the backing file id is closed by the server using the FUSE_DEV_IOC_BACKING_CLOSE ioctl. Signed-off-by: Amir Goldstein <[email protected]> Signed-off-by: Miklos Szeredi <[email protected]>
1 parent fc8ff39 commit 4a90451

File tree

4 files changed

+155
-8
lines changed

4 files changed

+155
-8
lines changed

fs/fuse/file.c

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,9 @@ static void fuse_prepare_release(struct fuse_inode *fi, struct fuse_file *ff,
295295
struct fuse_conn *fc = ff->fm->fc;
296296
struct fuse_release_args *ra = &ff->args->release_args;
297297

298+
if (fuse_file_passthrough(ff))
299+
fuse_passthrough_release(ff, fuse_inode_backing(fi));
300+
298301
/* Inode is NULL on error path of fuse_create_open() */
299302
if (likely(fi)) {
300303
spin_lock(&fi->lock);
@@ -1374,7 +1377,7 @@ static void fuse_dio_lock(struct kiocb *iocb, struct iov_iter *from,
13741377
* have raced, so check it again.
13751378
*/
13761379
if (fuse_io_past_eof(iocb, from) ||
1377-
fuse_file_uncached_io_start(inode, ff) != 0) {
1380+
fuse_file_uncached_io_start(inode, ff, NULL) != 0) {
13781381
inode_unlock_shared(inode);
13791382
inode_lock(inode);
13801383
*exclusive = true;
@@ -2527,6 +2530,10 @@ static int fuse_file_mmap(struct file *file, struct vm_area_struct *vma)
25272530
if (FUSE_IS_DAX(file_inode(file)))
25282531
return fuse_dax_mmap(file, vma);
25292532

2533+
/* TODO: implement mmap to backing file */
2534+
if (fuse_file_passthrough(ff))
2535+
return -ENODEV;
2536+
25302537
/*
25312538
* FOPEN_DIRECT_IO handling is special compared to O_DIRECT,
25322539
* as does not allow MAP_SHARED mmap without FUSE_DIRECT_IO_ALLOW_MMAP.

fs/fuse/fuse_i.h

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,12 @@ struct fuse_file {
269269
/** Does file hold a fi->iocachectr refcount? */
270270
enum { IOM_NONE, IOM_CACHED, IOM_UNCACHED } iomode;
271271

272+
#ifdef CONFIG_FUSE_PASSTHROUGH
273+
/** Reference to backing file in passthrough mode */
274+
struct file *passthrough;
275+
const struct cred *cred;
276+
#endif
277+
272278
/** Has flock been performed on this file? */
273279
bool flock:1;
274280
};
@@ -1394,7 +1400,7 @@ int fuse_fileattr_set(struct mnt_idmap *idmap,
13941400

13951401
/* iomode.c */
13961402
int fuse_file_cached_io_start(struct inode *inode, struct fuse_file *ff);
1397-
int fuse_file_uncached_io_start(struct inode *inode, struct fuse_file *ff);
1403+
int fuse_file_uncached_io_start(struct inode *inode, struct fuse_file *ff, struct fuse_backing *fb);
13981404
void fuse_file_uncached_io_end(struct inode *inode, struct fuse_file *ff);
13991405

14001406
int fuse_file_io_open(struct file *file, struct inode *inode);
@@ -1426,11 +1432,38 @@ static inline struct fuse_backing *fuse_inode_backing_set(struct fuse_inode *fi,
14261432
#endif
14271433
}
14281434

1435+
#ifdef CONFIG_FUSE_PASSTHROUGH
14291436
struct fuse_backing *fuse_backing_get(struct fuse_backing *fb);
14301437
void fuse_backing_put(struct fuse_backing *fb);
1438+
#else
1439+
1440+
static inline struct fuse_backing *fuse_backing_get(struct fuse_backing *fb)
1441+
{
1442+
return NULL;
1443+
}
1444+
1445+
static inline void fuse_backing_put(struct fuse_backing *fb)
1446+
{
1447+
}
1448+
#endif
1449+
14311450
void fuse_backing_files_init(struct fuse_conn *fc);
14321451
void fuse_backing_files_free(struct fuse_conn *fc);
14331452
int fuse_backing_open(struct fuse_conn *fc, struct fuse_backing_map *map);
14341453
int fuse_backing_close(struct fuse_conn *fc, int backing_id);
14351454

1455+
struct fuse_backing *fuse_passthrough_open(struct file *file,
1456+
struct inode *inode,
1457+
int backing_id);
1458+
void fuse_passthrough_release(struct fuse_file *ff, struct fuse_backing *fb);
1459+
1460+
static inline struct file *fuse_file_passthrough(struct fuse_file *ff)
1461+
{
1462+
#ifdef CONFIG_FUSE_PASSTHROUGH
1463+
return ff->passthrough;
1464+
#else
1465+
return NULL;
1466+
#endif
1467+
}
1468+
14361469
#endif /* _FS_FUSE_I_H */

fs/fuse/iomode.c

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
*/
1818
static inline bool fuse_is_io_cache_wait(struct fuse_inode *fi)
1919
{
20-
return READ_ONCE(fi->iocachectr) < 0;
20+
return READ_ONCE(fi->iocachectr) < 0 && !fuse_inode_backing(fi);
2121
}
2222

2323
/*
@@ -45,6 +45,17 @@ int fuse_file_cached_io_start(struct inode *inode, struct fuse_file *ff)
4545
wait_event(fi->direct_io_waitq, !fuse_is_io_cache_wait(fi));
4646
spin_lock(&fi->lock);
4747
}
48+
49+
/*
50+
* Check if inode entered passthrough io mode while waiting for parallel
51+
* dio write completion.
52+
*/
53+
if (fuse_inode_backing(fi)) {
54+
clear_bit(FUSE_I_CACHE_IO_MODE, &fi->state);
55+
spin_unlock(&fi->lock);
56+
return -ETXTBSY;
57+
}
58+
4859
WARN_ON(ff->iomode == IOM_UNCACHED);
4960
if (ff->iomode == IOM_NONE) {
5061
ff->iomode = IOM_CACHED;
@@ -71,19 +82,34 @@ static void fuse_file_cached_io_end(struct inode *inode, struct fuse_file *ff)
7182
}
7283

7384
/* Start strictly uncached io mode where cache access is not allowed */
74-
int fuse_file_uncached_io_start(struct inode *inode, struct fuse_file *ff)
85+
int fuse_file_uncached_io_start(struct inode *inode, struct fuse_file *ff, struct fuse_backing *fb)
7586
{
7687
struct fuse_inode *fi = get_fuse_inode(inode);
88+
struct fuse_backing *oldfb;
7789
int err = 0;
7890

7991
spin_lock(&fi->lock);
92+
/* deny conflicting backing files on same fuse inode */
93+
oldfb = fuse_inode_backing(fi);
94+
if (oldfb && oldfb != fb) {
95+
err = -EBUSY;
96+
goto unlock;
97+
}
8098
if (fi->iocachectr > 0) {
8199
err = -ETXTBSY;
82100
goto unlock;
83101
}
84102
WARN_ON(ff->iomode != IOM_NONE);
85103
fi->iocachectr--;
86104
ff->iomode = IOM_UNCACHED;
105+
106+
/* fuse inode holds a single refcount of backing file */
107+
if (!oldfb) {
108+
oldfb = fuse_inode_backing_set(fi, fb);
109+
WARN_ON_ONCE(oldfb != NULL);
110+
} else {
111+
fuse_backing_put(fb);
112+
}
87113
unlock:
88114
spin_unlock(&fi->lock);
89115
return err;
@@ -92,15 +118,20 @@ int fuse_file_uncached_io_start(struct inode *inode, struct fuse_file *ff)
92118
void fuse_file_uncached_io_end(struct inode *inode, struct fuse_file *ff)
93119
{
94120
struct fuse_inode *fi = get_fuse_inode(inode);
121+
struct fuse_backing *oldfb = NULL;
95122

96123
spin_lock(&fi->lock);
97124
WARN_ON(fi->iocachectr >= 0);
98125
WARN_ON(ff->iomode != IOM_UNCACHED);
99126
ff->iomode = IOM_NONE;
100127
fi->iocachectr++;
101-
if (!fi->iocachectr)
128+
if (!fi->iocachectr) {
102129
wake_up(&fi->direct_io_waitq);
130+
oldfb = fuse_inode_backing_set(fi, NULL);
131+
}
103132
spin_unlock(&fi->lock);
133+
if (oldfb)
134+
fuse_backing_put(oldfb);
104135
}
105136

106137
/*
@@ -118,18 +149,26 @@ static int fuse_file_passthrough_open(struct inode *inode, struct file *file)
118149
{
119150
struct fuse_file *ff = file->private_data;
120151
struct fuse_conn *fc = get_fuse_conn(inode);
152+
struct fuse_backing *fb;
121153
int err;
122154

123155
/* Check allowed conditions for file open in passthrough mode */
124156
if (!IS_ENABLED(CONFIG_FUSE_PASSTHROUGH) || !fc->passthrough ||
125157
(ff->open_flags & ~FOPEN_PASSTHROUGH_MASK))
126158
return -EINVAL;
127159

128-
/* TODO: implement backing file open */
129-
return -EOPNOTSUPP;
160+
fb = fuse_passthrough_open(file, inode,
161+
ff->args->open_outarg.backing_id);
162+
if (IS_ERR(fb))
163+
return PTR_ERR(fb);
130164

131165
/* First passthrough file open denies caching inode io mode */
132-
err = fuse_file_uncached_io_start(inode, ff);
166+
err = fuse_file_uncached_io_start(inode, ff, fb);
167+
if (!err)
168+
return 0;
169+
170+
fuse_passthrough_release(ff, fb);
171+
fuse_backing_put(fb);
133172

134173
return err;
135174
}
@@ -138,6 +177,7 @@ static int fuse_file_passthrough_open(struct inode *inode, struct file *file)
138177
int fuse_file_io_open(struct file *file, struct inode *inode)
139178
{
140179
struct fuse_file *ff = file->private_data;
180+
struct fuse_inode *fi = get_fuse_inode(inode);
141181
int err;
142182

143183
/*
@@ -147,6 +187,14 @@ int fuse_file_io_open(struct file *file, struct inode *inode)
147187
if (FUSE_IS_DAX(inode) || !ff->args)
148188
return 0;
149189

190+
/*
191+
* Server is expected to use FOPEN_PASSTHROUGH for all opens of an inode
192+
* which is already open for passthrough.
193+
*/
194+
err = -EINVAL;
195+
if (fuse_inode_backing(fi) && !(ff->open_flags & FOPEN_PASSTHROUGH))
196+
goto fail;
197+
150198
/*
151199
* FOPEN_PARALLEL_DIRECT_WRITES requires FOPEN_DIRECT_IO.
152200
*/

fs/fuse/passthrough.c

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "fuse_i.h"
99

1010
#include <linux/file.h>
11+
#include <linux/backing-file.h>
1112

1213
struct fuse_backing *fuse_backing_get(struct fuse_backing *fb)
1314
{
@@ -164,3 +165,61 @@ int fuse_backing_close(struct fuse_conn *fc, int backing_id)
164165

165166
return err;
166167
}
168+
169+
/*
170+
* Setup passthrough to a backing file.
171+
*
172+
* Returns an fb object with elevated refcount to be stored in fuse inode.
173+
*/
174+
struct fuse_backing *fuse_passthrough_open(struct file *file,
175+
struct inode *inode,
176+
int backing_id)
177+
{
178+
struct fuse_file *ff = file->private_data;
179+
struct fuse_conn *fc = ff->fm->fc;
180+
struct fuse_backing *fb = NULL;
181+
struct file *backing_file;
182+
int err;
183+
184+
err = -EINVAL;
185+
if (backing_id <= 0)
186+
goto out;
187+
188+
rcu_read_lock();
189+
fb = idr_find(&fc->backing_files_map, backing_id);
190+
fb = fuse_backing_get(fb);
191+
rcu_read_unlock();
192+
193+
err = -ENOENT;
194+
if (!fb)
195+
goto out;
196+
197+
/* Allocate backing file per fuse file to store fuse path */
198+
backing_file = backing_file_open(&file->f_path, file->f_flags,
199+
&fb->file->f_path, fb->cred);
200+
err = PTR_ERR(backing_file);
201+
if (IS_ERR(backing_file)) {
202+
fuse_backing_put(fb);
203+
goto out;
204+
}
205+
206+
err = 0;
207+
ff->passthrough = backing_file;
208+
ff->cred = get_cred(fb->cred);
209+
out:
210+
pr_debug("%s: backing_id=%d, fb=0x%p, backing_file=0x%p, err=%i\n", __func__,
211+
backing_id, fb, ff->passthrough, err);
212+
213+
return err ? ERR_PTR(err) : fb;
214+
}
215+
216+
void fuse_passthrough_release(struct fuse_file *ff, struct fuse_backing *fb)
217+
{
218+
pr_debug("%s: fb=0x%p, backing_file=0x%p\n", __func__,
219+
fb, ff->passthrough);
220+
221+
fput(ff->passthrough);
222+
ff->passthrough = NULL;
223+
put_cred(ff->cred);
224+
ff->cred = NULL;
225+
}

0 commit comments

Comments
 (0)