mirror of
https://github.com/torvalds/linux.git
synced 2026-04-18 06:44:00 -04:00
Merge tag 'fuse-update-7.1' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/fuse
Pull fuse update from Miklos Szeredi: - Fix possible hang in virtiofs when cleaning up a DAX inode (Sergio Lopez) - Fix a warning when using large folio as the source of SPLICE_F_MOVE on the fuse device (Bernd) - Fix uninitialized value found by KMSAN (Luis Henriques) - Fix synchronous INIT hang (Miklos) - Fix race between inode initialization and FUSE_NOTIFY_INVAL_INODE (Horst) - Allow fd to be closed after passing fuse device fd to fsconfig(..., "fd", ...) (Miklos) - Support FSCONFIG_SET_FD for "fd" option (Miklos) - Misc fixes and cleanups * tag 'fuse-update-7.1' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/fuse: (21 commits) fuse: support FSCONFIG_SET_FD for "fd" option fuse: clean up device cloning fuse: don't require /dev/fuse fd to be kept open during mount fuse: add refcount to fuse_dev fuse: create fuse_dev on /dev/fuse open instead of mount fuse: check connection state on notification fuse: fuse_dev_ioctl_clone() should wait for device file to be initialized fuse: fix inode initialization race fuse: abort on fatal signal during sync init fuse: fix uninit-value in fuse_dentry_revalidate() fuse: use offset_in_page() for page offset calculations fuse: use DIV_ROUND_UP() for page count calculations fuse: simplify logic in fuse_notify_store() and fuse_retrieve() fuse: validate outarg offset and size in notify store/retrieve fuse: Check for large folio with SPLICE_F_MOVE fuse: quiet down complaints in fuse_conn_limit_write fuse: drop unnecessary argument from fuse_lookup_init() fuse: fix premature writetrhough request for large folio fuse: refactor duplicate queue teardown operation virtiofs: add FUSE protocol validation ...
This commit is contained in:
@@ -121,7 +121,7 @@ static ssize_t fuse_conn_max_background_write(struct file *file,
|
||||
const char __user *buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
unsigned val;
|
||||
unsigned int val = 0;
|
||||
ssize_t ret;
|
||||
|
||||
ret = fuse_conn_limit_write(file, buf, count, ppos, &val,
|
||||
@@ -163,7 +163,7 @@ static ssize_t fuse_conn_congestion_threshold_write(struct file *file,
|
||||
const char __user *buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
unsigned val;
|
||||
unsigned int val = 0;
|
||||
struct fuse_conn *fc;
|
||||
ssize_t ret;
|
||||
|
||||
|
||||
@@ -527,7 +527,7 @@ static int cuse_channel_open(struct inode *inode, struct file *file)
|
||||
cc->fc.initialized = 1;
|
||||
rc = cuse_send_init(cc);
|
||||
if (rc) {
|
||||
fuse_dev_free(fud);
|
||||
fuse_dev_put(fud);
|
||||
return rc;
|
||||
}
|
||||
file->private_data = fud;
|
||||
|
||||
156
fs/fuse/dev.c
156
fs/fuse/dev.c
@@ -570,6 +570,11 @@ static void request_wait_answer(struct fuse_req *req)
|
||||
if (!err)
|
||||
return;
|
||||
|
||||
if (req->args->abort_on_kill) {
|
||||
fuse_abort_conn(fc);
|
||||
return;
|
||||
}
|
||||
|
||||
if (test_bit(FR_URING, &req->flags))
|
||||
removed = fuse_uring_remove_pending_req(req);
|
||||
else
|
||||
@@ -676,7 +681,8 @@ ssize_t __fuse_simple_request(struct mnt_idmap *idmap,
|
||||
fuse_force_creds(req);
|
||||
|
||||
__set_bit(FR_WAITING, &req->flags);
|
||||
__set_bit(FR_FORCE, &req->flags);
|
||||
if (!args->abort_on_kill)
|
||||
__set_bit(FR_FORCE, &req->flags);
|
||||
} else {
|
||||
WARN_ON(args->nocreds);
|
||||
req = fuse_get_req(idmap, fm, false);
|
||||
@@ -1011,6 +1017,9 @@ static int fuse_try_move_folio(struct fuse_copy_state *cs, struct folio **foliop
|
||||
folio_clear_uptodate(newfolio);
|
||||
folio_clear_mappedtodisk(newfolio);
|
||||
|
||||
if (folio_test_large(newfolio))
|
||||
goto out_fallback_unlock;
|
||||
|
||||
if (fuse_check_folio(newfolio) != 0)
|
||||
goto out_fallback_unlock;
|
||||
|
||||
@@ -1539,32 +1548,24 @@ out_end:
|
||||
|
||||
static int fuse_dev_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
/*
|
||||
* The fuse device's file's private_data is used to hold
|
||||
* the fuse_conn(ection) when it is mounted, and is used to
|
||||
* keep track of whether the file has been mounted already.
|
||||
*/
|
||||
file->private_data = NULL;
|
||||
struct fuse_dev *fud = fuse_dev_alloc();
|
||||
|
||||
if (!fud)
|
||||
return -ENOMEM;
|
||||
|
||||
file->private_data = fud;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct fuse_dev *fuse_get_dev(struct file *file)
|
||||
{
|
||||
struct fuse_dev *fud = __fuse_get_dev(file);
|
||||
struct fuse_dev *fud = fuse_file_to_fud(file);
|
||||
int err;
|
||||
|
||||
if (likely(fud))
|
||||
return fud;
|
||||
|
||||
err = wait_event_interruptible(fuse_dev_waitq,
|
||||
READ_ONCE(file->private_data) != FUSE_DEV_SYNC_INIT);
|
||||
err = wait_event_interruptible(fuse_dev_waitq, fuse_dev_fc_get(fud) != NULL);
|
||||
if (err)
|
||||
return ERR_PTR(err);
|
||||
|
||||
fud = __fuse_get_dev(file);
|
||||
if (!fud)
|
||||
return ERR_PTR(-EPERM);
|
||||
|
||||
return fud;
|
||||
}
|
||||
|
||||
@@ -1764,10 +1765,9 @@ static int fuse_notify_store(struct fuse_conn *fc, unsigned int size,
|
||||
struct address_space *mapping;
|
||||
u64 nodeid;
|
||||
int err;
|
||||
pgoff_t index;
|
||||
unsigned int offset;
|
||||
unsigned int num;
|
||||
loff_t file_size;
|
||||
loff_t pos;
|
||||
loff_t end;
|
||||
|
||||
if (size < sizeof(outarg))
|
||||
@@ -1780,7 +1780,12 @@ static int fuse_notify_store(struct fuse_conn *fc, unsigned int size,
|
||||
if (size - sizeof(outarg) != outarg.size)
|
||||
return -EINVAL;
|
||||
|
||||
if (outarg.offset >= MAX_LFS_FILESIZE)
|
||||
return -EINVAL;
|
||||
|
||||
nodeid = outarg.nodeid;
|
||||
pos = outarg.offset;
|
||||
num = min(outarg.size, MAX_LFS_FILESIZE - pos);
|
||||
|
||||
down_read(&fc->killsb);
|
||||
|
||||
@@ -1790,33 +1795,29 @@ static int fuse_notify_store(struct fuse_conn *fc, unsigned int size,
|
||||
goto out_up_killsb;
|
||||
|
||||
mapping = inode->i_mapping;
|
||||
index = outarg.offset >> PAGE_SHIFT;
|
||||
offset = outarg.offset & ~PAGE_MASK;
|
||||
file_size = i_size_read(inode);
|
||||
end = outarg.offset + outarg.size;
|
||||
end = pos + num;
|
||||
if (end > file_size) {
|
||||
file_size = end;
|
||||
fuse_write_update_attr(inode, file_size, outarg.size);
|
||||
fuse_write_update_attr(inode, file_size, num);
|
||||
}
|
||||
|
||||
num = outarg.size;
|
||||
while (num) {
|
||||
struct folio *folio;
|
||||
unsigned int folio_offset;
|
||||
unsigned int nr_bytes;
|
||||
unsigned int nr_pages;
|
||||
pgoff_t index = pos >> PAGE_SHIFT;
|
||||
|
||||
folio = filemap_grab_folio(mapping, index);
|
||||
err = PTR_ERR(folio);
|
||||
if (IS_ERR(folio))
|
||||
goto out_iput;
|
||||
|
||||
folio_offset = ((index - folio->index) << PAGE_SHIFT) + offset;
|
||||
folio_offset = offset_in_folio(folio, pos);
|
||||
nr_bytes = min(num, folio_size(folio) - folio_offset);
|
||||
nr_pages = (offset + nr_bytes + PAGE_SIZE - 1) >> PAGE_SHIFT;
|
||||
|
||||
err = fuse_copy_folio(cs, &folio, folio_offset, nr_bytes, 0);
|
||||
if (!folio_test_uptodate(folio) && !err && offset == 0 &&
|
||||
if (!folio_test_uptodate(folio) && !err && folio_offset == 0 &&
|
||||
(nr_bytes == folio_size(folio) || file_size == end)) {
|
||||
folio_zero_segment(folio, nr_bytes, folio_size(folio));
|
||||
folio_mark_uptodate(folio);
|
||||
@@ -1827,9 +1828,8 @@ static int fuse_notify_store(struct fuse_conn *fc, unsigned int size,
|
||||
if (err)
|
||||
goto out_iput;
|
||||
|
||||
pos += nr_bytes;
|
||||
num -= nr_bytes;
|
||||
offset = 0;
|
||||
index += nr_pages;
|
||||
}
|
||||
|
||||
err = 0;
|
||||
@@ -1861,7 +1861,6 @@ static int fuse_retrieve(struct fuse_mount *fm, struct inode *inode,
|
||||
{
|
||||
int err;
|
||||
struct address_space *mapping = inode->i_mapping;
|
||||
pgoff_t index;
|
||||
loff_t file_size;
|
||||
unsigned int num;
|
||||
unsigned int offset;
|
||||
@@ -1872,17 +1871,18 @@ static int fuse_retrieve(struct fuse_mount *fm, struct inode *inode,
|
||||
size_t args_size = sizeof(*ra);
|
||||
struct fuse_args_pages *ap;
|
||||
struct fuse_args *args;
|
||||
loff_t pos = outarg->offset;
|
||||
|
||||
offset = outarg->offset & ~PAGE_MASK;
|
||||
offset = offset_in_page(pos);
|
||||
file_size = i_size_read(inode);
|
||||
|
||||
num = min(outarg->size, fc->max_write);
|
||||
if (outarg->offset > file_size)
|
||||
if (pos > file_size)
|
||||
num = 0;
|
||||
else if (outarg->offset + num > file_size)
|
||||
num = file_size - outarg->offset;
|
||||
else if (num > file_size - pos)
|
||||
num = file_size - pos;
|
||||
|
||||
num_pages = (num + offset + PAGE_SIZE - 1) >> PAGE_SHIFT;
|
||||
num_pages = DIV_ROUND_UP(num + offset, PAGE_SIZE);
|
||||
num_pages = min(num_pages, fc->max_pages);
|
||||
num = min(num, num_pages << PAGE_SHIFT);
|
||||
|
||||
@@ -1903,31 +1903,27 @@ static int fuse_retrieve(struct fuse_mount *fm, struct inode *inode,
|
||||
args->in_pages = true;
|
||||
args->end = fuse_retrieve_end;
|
||||
|
||||
index = outarg->offset >> PAGE_SHIFT;
|
||||
|
||||
while (num && ap->num_folios < num_pages) {
|
||||
struct folio *folio;
|
||||
unsigned int folio_offset;
|
||||
unsigned int nr_bytes;
|
||||
unsigned int nr_pages;
|
||||
pgoff_t index = pos >> PAGE_SHIFT;
|
||||
|
||||
folio = filemap_get_folio(mapping, index);
|
||||
if (IS_ERR(folio))
|
||||
break;
|
||||
|
||||
folio_offset = ((index - folio->index) << PAGE_SHIFT) + offset;
|
||||
folio_offset = offset_in_folio(folio, pos);
|
||||
nr_bytes = min(folio_size(folio) - folio_offset, num);
|
||||
nr_pages = (offset + nr_bytes + PAGE_SIZE - 1) >> PAGE_SHIFT;
|
||||
|
||||
ap->folios[ap->num_folios] = folio;
|
||||
ap->descs[ap->num_folios].offset = folio_offset;
|
||||
ap->descs[ap->num_folios].length = nr_bytes;
|
||||
ap->num_folios++;
|
||||
|
||||
offset = 0;
|
||||
pos += nr_bytes;
|
||||
num -= nr_bytes;
|
||||
total_len += nr_bytes;
|
||||
index += nr_pages;
|
||||
}
|
||||
ra->inarg.offset = outarg->offset;
|
||||
ra->inarg.size = total_len;
|
||||
@@ -1961,6 +1957,9 @@ static int fuse_notify_retrieve(struct fuse_conn *fc, unsigned int size,
|
||||
|
||||
fuse_copy_finish(cs);
|
||||
|
||||
if (outarg.offset >= MAX_LFS_FILESIZE)
|
||||
return -EINVAL;
|
||||
|
||||
down_read(&fc->killsb);
|
||||
err = -ENOENT;
|
||||
nodeid = outarg.nodeid;
|
||||
@@ -2091,6 +2090,13 @@ static int fuse_notify_prune(struct fuse_conn *fc, unsigned int size,
|
||||
static int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code,
|
||||
unsigned int size, struct fuse_copy_state *cs)
|
||||
{
|
||||
/*
|
||||
* Only allow notifications during while the connection is in an
|
||||
* initialized and connected state
|
||||
*/
|
||||
if (!fc->initialized || !fc->connected)
|
||||
return -EINVAL;
|
||||
|
||||
/* Don't try to move folios (yet) */
|
||||
cs->move_folios = false;
|
||||
|
||||
@@ -2533,13 +2539,15 @@ void fuse_wait_aborted(struct fuse_conn *fc)
|
||||
|
||||
int fuse_dev_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct fuse_dev *fud = __fuse_get_dev(file);
|
||||
struct fuse_dev *fud = fuse_file_to_fud(file);
|
||||
/* Pairs with cmpxchg() in fuse_dev_install() */
|
||||
struct fuse_conn *fc = xchg(&fud->fc, FUSE_DEV_FC_DISCONNECTED);
|
||||
|
||||
if (fud) {
|
||||
struct fuse_conn *fc = fud->fc;
|
||||
if (fc) {
|
||||
struct fuse_pqueue *fpq = &fud->pq;
|
||||
LIST_HEAD(to_end);
|
||||
unsigned int i;
|
||||
bool last;
|
||||
|
||||
spin_lock(&fpq->lock);
|
||||
WARN_ON(!list_empty(&fpq->io));
|
||||
@@ -2549,13 +2557,19 @@ int fuse_dev_release(struct inode *inode, struct file *file)
|
||||
|
||||
fuse_dev_end_requests(&to_end);
|
||||
|
||||
spin_lock(&fc->lock);
|
||||
list_del(&fud->entry);
|
||||
/* Are we the last open device? */
|
||||
if (atomic_dec_and_test(&fc->dev_count)) {
|
||||
last = list_empty(&fc->devices);
|
||||
spin_unlock(&fc->lock);
|
||||
|
||||
if (last) {
|
||||
WARN_ON(fc->iq.fasync != NULL);
|
||||
fuse_abort_conn(fc);
|
||||
}
|
||||
fuse_dev_free(fud);
|
||||
fuse_conn_put(fc);
|
||||
}
|
||||
fuse_dev_put(fud);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fuse_dev_release);
|
||||
@@ -2571,28 +2585,10 @@ static int fuse_dev_fasync(int fd, struct file *file, int on)
|
||||
return fasync_helper(fd, file, on, &fud->fc->iq.fasync);
|
||||
}
|
||||
|
||||
static int fuse_device_clone(struct fuse_conn *fc, struct file *new)
|
||||
{
|
||||
struct fuse_dev *fud;
|
||||
|
||||
if (__fuse_get_dev(new))
|
||||
return -EINVAL;
|
||||
|
||||
fud = fuse_dev_alloc_install(fc);
|
||||
if (!fud)
|
||||
return -ENOMEM;
|
||||
|
||||
new->private_data = fud;
|
||||
atomic_inc(&fc->dev_count);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long fuse_dev_ioctl_clone(struct file *file, __u32 __user *argp)
|
||||
{
|
||||
int res;
|
||||
int oldfd;
|
||||
struct fuse_dev *fud = NULL;
|
||||
struct fuse_dev *fud, *new_fud;
|
||||
|
||||
if (get_user(oldfd, argp))
|
||||
return -EFAULT;
|
||||
@@ -2605,17 +2601,20 @@ static long fuse_dev_ioctl_clone(struct file *file, __u32 __user *argp)
|
||||
* Check against file->f_op because CUSE
|
||||
* uses the same ioctl handler.
|
||||
*/
|
||||
if (fd_file(f)->f_op == file->f_op)
|
||||
fud = __fuse_get_dev(fd_file(f));
|
||||
if (fd_file(f)->f_op != file->f_op)
|
||||
return -EINVAL;
|
||||
|
||||
res = -EINVAL;
|
||||
if (fud) {
|
||||
mutex_lock(&fuse_mutex);
|
||||
res = fuse_device_clone(fud->fc, file);
|
||||
mutex_unlock(&fuse_mutex);
|
||||
}
|
||||
fud = fuse_get_dev(fd_file(f));
|
||||
if (IS_ERR(fud))
|
||||
return PTR_ERR(fud);
|
||||
|
||||
return res;
|
||||
new_fud = fuse_file_to_fud(file);
|
||||
if (fuse_dev_fc_get(new_fud))
|
||||
return -EINVAL;
|
||||
|
||||
fuse_dev_install(new_fud, fud->fc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long fuse_dev_ioctl_backing_open(struct file *file,
|
||||
@@ -2656,10 +2655,11 @@ static long fuse_dev_ioctl_backing_close(struct file *file, __u32 __user *argp)
|
||||
static long fuse_dev_ioctl_sync_init(struct file *file)
|
||||
{
|
||||
int err = -EINVAL;
|
||||
struct fuse_dev *fud = fuse_file_to_fud(file);
|
||||
|
||||
mutex_lock(&fuse_mutex);
|
||||
if (!__fuse_get_dev(file)) {
|
||||
WRITE_ONCE(file->private_data, FUSE_DEV_SYNC_INIT);
|
||||
if (!fuse_dev_fc_get(fud)) {
|
||||
fud->sync_init = true;
|
||||
err = 0;
|
||||
}
|
||||
mutex_unlock(&fuse_mutex);
|
||||
|
||||
@@ -397,6 +397,20 @@ static void fuse_uring_teardown_entries(struct fuse_ring_queue *queue)
|
||||
FRRS_AVAILABLE);
|
||||
}
|
||||
|
||||
static void fuse_uring_teardown_all_queues(struct fuse_ring *ring)
|
||||
{
|
||||
int qid;
|
||||
|
||||
for (qid = 0; qid < ring->nr_queues; qid++) {
|
||||
struct fuse_ring_queue *queue = READ_ONCE(ring->queues[qid]);
|
||||
|
||||
if (!queue)
|
||||
continue;
|
||||
|
||||
fuse_uring_teardown_entries(queue);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Log state debug info
|
||||
*/
|
||||
@@ -431,19 +445,10 @@ static void fuse_uring_log_ent_state(struct fuse_ring *ring)
|
||||
|
||||
static void fuse_uring_async_stop_queues(struct work_struct *work)
|
||||
{
|
||||
int qid;
|
||||
struct fuse_ring *ring =
|
||||
container_of(work, struct fuse_ring, async_teardown_work.work);
|
||||
|
||||
/* XXX code dup */
|
||||
for (qid = 0; qid < ring->nr_queues; qid++) {
|
||||
struct fuse_ring_queue *queue = READ_ONCE(ring->queues[qid]);
|
||||
|
||||
if (!queue)
|
||||
continue;
|
||||
|
||||
fuse_uring_teardown_entries(queue);
|
||||
}
|
||||
fuse_uring_teardown_all_queues(ring);
|
||||
|
||||
/*
|
||||
* Some ring entries might be in the middle of IO operations,
|
||||
@@ -469,16 +474,7 @@ static void fuse_uring_async_stop_queues(struct work_struct *work)
|
||||
*/
|
||||
void fuse_uring_stop_queues(struct fuse_ring *ring)
|
||||
{
|
||||
int qid;
|
||||
|
||||
for (qid = 0; qid < ring->nr_queues; qid++) {
|
||||
struct fuse_ring_queue *queue = READ_ONCE(ring->queues[qid]);
|
||||
|
||||
if (!queue)
|
||||
continue;
|
||||
|
||||
fuse_uring_teardown_entries(queue);
|
||||
}
|
||||
fuse_uring_teardown_all_queues(ring);
|
||||
|
||||
if (atomic_read(&ring->queue_refs) > 0) {
|
||||
ring->teardown_time = jiffies;
|
||||
|
||||
@@ -354,8 +354,8 @@ static void fuse_invalidate_entry(struct dentry *entry)
|
||||
fuse_invalidate_entry_cache(entry);
|
||||
}
|
||||
|
||||
static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args,
|
||||
u64 nodeid, const struct qstr *name,
|
||||
static void fuse_lookup_init(struct fuse_args *args, u64 nodeid,
|
||||
const struct qstr *name,
|
||||
struct fuse_entry_out *outarg)
|
||||
{
|
||||
memset(outarg, 0, sizeof(struct fuse_entry_out));
|
||||
@@ -421,8 +421,7 @@ static int fuse_dentry_revalidate(struct inode *dir, const struct qstr *name,
|
||||
|
||||
attr_version = fuse_get_attr_version(fm->fc);
|
||||
|
||||
fuse_lookup_init(fm->fc, &args, get_node_id(dir),
|
||||
name, &outarg);
|
||||
fuse_lookup_init(&args, get_node_id(dir), name, &outarg);
|
||||
ret = fuse_simple_request(fm, &args);
|
||||
/* Zero nodeid is same as -ENOENT */
|
||||
if (!ret && !outarg.nodeid)
|
||||
@@ -481,6 +480,11 @@ static int fuse_dentry_init(struct dentry *dentry)
|
||||
fd->dentry = dentry;
|
||||
RB_CLEAR_NODE(&fd->node);
|
||||
dentry->d_fsdata = fd;
|
||||
/*
|
||||
* Initialising d_time (epoch) to '0' ensures the dentry is invalid
|
||||
* if compared to fc->epoch, which is initialized to '1'.
|
||||
*/
|
||||
dentry->d_time = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -570,7 +574,7 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name
|
||||
attr_version = fuse_get_attr_version(fm->fc);
|
||||
evict_ctr = fuse_get_evict_ctr(fm->fc);
|
||||
|
||||
fuse_lookup_init(fm->fc, &args, nodeid, name, outarg);
|
||||
fuse_lookup_init(&args, nodeid, name, outarg);
|
||||
err = fuse_simple_request(fm, &args);
|
||||
/* Zero nodeid is same as -ENOENT, but with valid timeout */
|
||||
if (err || !outarg->nodeid)
|
||||
|
||||
@@ -117,6 +117,12 @@ static void fuse_file_put(struct fuse_file *ff, bool sync)
|
||||
fuse_simple_request(ff->fm, args);
|
||||
fuse_release_end(ff->fm, args, 0);
|
||||
} else {
|
||||
/*
|
||||
* DAX inodes may need to issue a number of synchronous
|
||||
* request for clearing the mappings.
|
||||
*/
|
||||
if (ra && ra->inode && FUSE_IS_DAX(ra->inode))
|
||||
args->may_block = true;
|
||||
args->end = fuse_release_end;
|
||||
if (fuse_simple_background(ff->fm, args,
|
||||
GFP_KERNEL | __GFP_NOFAIL))
|
||||
@@ -1243,7 +1249,6 @@ static ssize_t fuse_fill_write_pages(struct fuse_io_args *ia,
|
||||
{
|
||||
struct fuse_args_pages *ap = &ia->ap;
|
||||
struct fuse_conn *fc = get_fuse_conn(mapping->host);
|
||||
unsigned offset = pos & (PAGE_SIZE - 1);
|
||||
size_t count = 0;
|
||||
unsigned int num;
|
||||
int err = 0;
|
||||
@@ -1270,7 +1275,7 @@ static ssize_t fuse_fill_write_pages(struct fuse_io_args *ia,
|
||||
if (mapping_writably_mapped(mapping))
|
||||
flush_dcache_folio(folio);
|
||||
|
||||
folio_offset = ((index - folio->index) << PAGE_SHIFT) + offset;
|
||||
folio_offset = offset_in_folio(folio, pos);
|
||||
bytes = min(folio_size(folio) - folio_offset, num);
|
||||
|
||||
tmp = copy_folio_from_iter_atomic(folio, folio_offset, bytes, ii);
|
||||
@@ -1300,9 +1305,6 @@ static ssize_t fuse_fill_write_pages(struct fuse_io_args *ia,
|
||||
count += tmp;
|
||||
pos += tmp;
|
||||
num -= tmp;
|
||||
offset += tmp;
|
||||
if (offset == folio_size(folio))
|
||||
offset = 0;
|
||||
|
||||
/* If we copied full folio, mark it uptodate */
|
||||
if (tmp == folio_size(folio))
|
||||
@@ -1314,7 +1316,9 @@ static ssize_t fuse_fill_write_pages(struct fuse_io_args *ia,
|
||||
ia->write.folio_locked = true;
|
||||
break;
|
||||
}
|
||||
if (!fc->big_writes || offset != 0)
|
||||
if (!fc->big_writes)
|
||||
break;
|
||||
if (folio_offset + tmp != folio_size(folio))
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2173,7 +2177,7 @@ static bool fuse_folios_need_send(struct fuse_conn *fc, loff_t pos,
|
||||
WARN_ON(!ap->num_folios);
|
||||
|
||||
/* Reached max pages */
|
||||
if ((bytes + PAGE_SIZE - 1) >> PAGE_SHIFT > fc->max_pages)
|
||||
if (DIV_ROUND_UP(bytes, PAGE_SIZE) > fc->max_pages)
|
||||
return true;
|
||||
|
||||
if (bytes > max_bytes)
|
||||
|
||||
@@ -39,18 +39,36 @@ struct fuse_copy_state {
|
||||
} ring;
|
||||
};
|
||||
|
||||
#define FUSE_DEV_SYNC_INIT ((struct fuse_dev *) 1)
|
||||
#define FUSE_DEV_PTR_MASK (~1UL)
|
||||
/* fud->fc gets assigned to this value when /dev/fuse is closed */
|
||||
#define FUSE_DEV_FC_DISCONNECTED ((struct fuse_conn *) 1)
|
||||
|
||||
/*
|
||||
* Lockless access is OK, because fud->fc is set once during mount and is valid
|
||||
* until the file is released.
|
||||
*
|
||||
* fud->fc is set to FUSE_DEV_FC_DISCONNECTED only after the containing file is
|
||||
* released, so result is safe to dereference in most cases. Exceptions are:
|
||||
* fuse_dev_put() and fuse_fill_super_common().
|
||||
*/
|
||||
static inline struct fuse_conn *fuse_dev_fc_get(struct fuse_dev *fud)
|
||||
{
|
||||
/* Pairs with xchg() in fuse_dev_install() */
|
||||
return smp_load_acquire(&fud->fc);
|
||||
}
|
||||
|
||||
static inline struct fuse_dev *fuse_file_to_fud(struct file *file)
|
||||
{
|
||||
return file->private_data;
|
||||
}
|
||||
|
||||
static inline struct fuse_dev *__fuse_get_dev(struct file *file)
|
||||
{
|
||||
/*
|
||||
* Lockless access is OK, because file->private data is set
|
||||
* once during mount and is valid until the file is released.
|
||||
*/
|
||||
struct fuse_dev *fud = READ_ONCE(file->private_data);
|
||||
struct fuse_dev *fud = fuse_file_to_fud(file);
|
||||
|
||||
return (typeof(fud)) ((unsigned long) fud & FUSE_DEV_PTR_MASK);
|
||||
if (!fuse_dev_fc_get(fud))
|
||||
return NULL;
|
||||
|
||||
return fud;
|
||||
}
|
||||
|
||||
struct fuse_dev *fuse_get_dev(struct file *file);
|
||||
|
||||
@@ -345,6 +345,7 @@ struct fuse_args {
|
||||
bool is_ext:1;
|
||||
bool is_pinned:1;
|
||||
bool invalidate_vmap:1;
|
||||
bool abort_on_kill:1;
|
||||
struct fuse_in_arg in_args[4];
|
||||
struct fuse_arg out_args[2];
|
||||
void (*end)(struct fuse_mount *fm, struct fuse_args *args, int error);
|
||||
@@ -576,6 +577,12 @@ struct fuse_pqueue {
|
||||
* Fuse device instance
|
||||
*/
|
||||
struct fuse_dev {
|
||||
/** Reference count of this object */
|
||||
refcount_t ref;
|
||||
|
||||
/** Issue FUSE_INIT synchronously */
|
||||
bool sync_init;
|
||||
|
||||
/** Fuse connection for this device */
|
||||
struct fuse_conn *fc;
|
||||
|
||||
@@ -599,13 +606,11 @@ static inline bool fuse_is_inode_dax_mode(enum fuse_dax_mode mode)
|
||||
}
|
||||
|
||||
struct fuse_fs_context {
|
||||
int fd;
|
||||
struct file *file;
|
||||
struct fuse_dev *fud;
|
||||
unsigned int rootmode;
|
||||
kuid_t user_id;
|
||||
kgid_t group_id;
|
||||
bool is_bdev:1;
|
||||
bool fd_present:1;
|
||||
bool rootmode_present:1;
|
||||
bool user_id_present:1;
|
||||
bool group_id_present:1;
|
||||
@@ -622,9 +627,6 @@ struct fuse_fs_context {
|
||||
|
||||
/* DAX device, may be NULL */
|
||||
struct dax_device *dax_dev;
|
||||
|
||||
/* fuse_dev pointer to fill in, should contain NULL on entry */
|
||||
void **fudptr;
|
||||
};
|
||||
|
||||
struct fuse_sync_bucket {
|
||||
@@ -648,9 +650,6 @@ struct fuse_conn {
|
||||
/** Refcount */
|
||||
refcount_t count;
|
||||
|
||||
/** Number of fuse_dev's */
|
||||
atomic_t dev_count;
|
||||
|
||||
/** Current epoch for up-to-date dentries */
|
||||
atomic_t epoch;
|
||||
|
||||
@@ -1343,7 +1342,7 @@ void fuse_conn_put(struct fuse_conn *fc);
|
||||
struct fuse_dev *fuse_dev_alloc_install(struct fuse_conn *fc);
|
||||
struct fuse_dev *fuse_dev_alloc(void);
|
||||
void fuse_dev_install(struct fuse_dev *fud, struct fuse_conn *fc);
|
||||
void fuse_dev_free(struct fuse_dev *fud);
|
||||
void fuse_dev_put(struct fuse_dev *fud);
|
||||
int fuse_send_init(struct fuse_mount *fm);
|
||||
|
||||
/**
|
||||
|
||||
136
fs/fuse/inode.c
136
fs/fuse/inode.c
@@ -470,6 +470,7 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
|
||||
struct inode *inode;
|
||||
struct fuse_inode *fi;
|
||||
struct fuse_conn *fc = get_fuse_conn_super(sb);
|
||||
bool is_new_inode = false;
|
||||
|
||||
/*
|
||||
* Auto mount points get their node id from the submount root, which is
|
||||
@@ -505,13 +506,13 @@ retry:
|
||||
if (!inode)
|
||||
return NULL;
|
||||
|
||||
if ((inode_state_read_once(inode) & I_NEW)) {
|
||||
is_new_inode = inode_state_read_once(inode) & I_NEW;
|
||||
if (is_new_inode) {
|
||||
inode->i_flags |= S_NOATIME;
|
||||
if (!fc->writeback_cache || !S_ISREG(attr->mode))
|
||||
inode->i_flags |= S_NOCMTIME;
|
||||
inode->i_generation = generation;
|
||||
fuse_init_inode(inode, attr, fc);
|
||||
unlock_new_inode(inode);
|
||||
} else if (fuse_stale_inode(inode, generation, attr)) {
|
||||
/* nodeid was reused, any I/O on the old inode should fail */
|
||||
fuse_make_bad(inode);
|
||||
@@ -528,6 +529,8 @@ retry:
|
||||
done:
|
||||
fuse_change_attributes_i(inode, attr, NULL, attr_valid, attr_version,
|
||||
evict_ctr);
|
||||
if (is_new_inode)
|
||||
unlock_new_inode(inode);
|
||||
return inode;
|
||||
}
|
||||
|
||||
@@ -788,7 +791,7 @@ enum {
|
||||
|
||||
static const struct fs_parameter_spec fuse_fs_parameters[] = {
|
||||
fsparam_string ("source", OPT_SOURCE),
|
||||
fsparam_u32 ("fd", OPT_FD),
|
||||
fsparam_fd ("fd", OPT_FD),
|
||||
fsparam_u32oct ("rootmode", OPT_ROOTMODE),
|
||||
fsparam_uid ("user_id", OPT_USER_ID),
|
||||
fsparam_gid ("group_id", OPT_GROUP_ID),
|
||||
@@ -800,6 +803,25 @@ static const struct fs_parameter_spec fuse_fs_parameters[] = {
|
||||
{}
|
||||
};
|
||||
|
||||
static int fuse_opt_fd(struct fs_context *fsc, struct file *file)
|
||||
{
|
||||
struct fuse_fs_context *ctx = fsc->fs_private;
|
||||
|
||||
if (file->f_op != &fuse_dev_operations)
|
||||
return invalfc(fsc, "fd is not a fuse device");
|
||||
/*
|
||||
* Require mount to happen from the same user namespace which
|
||||
* opened /dev/fuse to prevent potential attacks.
|
||||
*/
|
||||
if (file->f_cred->user_ns != fsc->user_ns)
|
||||
return invalfc(fsc, "wrong user namespace for fuse device");
|
||||
|
||||
ctx->fud = file->private_data;
|
||||
refcount_inc(&ctx->fud->ref);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fuse_parse_param(struct fs_context *fsc, struct fs_parameter *param)
|
||||
{
|
||||
struct fs_parse_result result;
|
||||
@@ -839,9 +861,15 @@ static int fuse_parse_param(struct fs_context *fsc, struct fs_parameter *param)
|
||||
return 0;
|
||||
|
||||
case OPT_FD:
|
||||
ctx->fd = result.uint_32;
|
||||
ctx->fd_present = true;
|
||||
break;
|
||||
if (param->type == fs_value_is_file) {
|
||||
return fuse_opt_fd(fsc, param->file);
|
||||
} else {
|
||||
struct file *file __free(fput) = fget(result.uint_32);
|
||||
if (!file)
|
||||
return -EBADF;
|
||||
|
||||
return fuse_opt_fd(fsc, file);
|
||||
}
|
||||
|
||||
case OPT_ROOTMODE:
|
||||
if (!fuse_valid_type(result.uint_32))
|
||||
@@ -904,6 +932,8 @@ static void fuse_free_fsc(struct fs_context *fsc)
|
||||
struct fuse_fs_context *ctx = fsc->fs_private;
|
||||
|
||||
if (ctx) {
|
||||
if (ctx->fud)
|
||||
fuse_dev_put(ctx->fud);
|
||||
kfree(ctx->subtype);
|
||||
kfree(ctx);
|
||||
}
|
||||
@@ -975,7 +1005,6 @@ void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm,
|
||||
spin_lock_init(&fc->bg_lock);
|
||||
init_rwsem(&fc->killsb);
|
||||
refcount_set(&fc->count, 1);
|
||||
atomic_set(&fc->dev_count, 1);
|
||||
atomic_set(&fc->epoch, 1);
|
||||
INIT_WORK(&fc->epoch_work, fuse_epoch_work);
|
||||
init_waitqueue_head(&fc->blocked_waitq);
|
||||
@@ -1551,6 +1580,7 @@ int fuse_send_init(struct fuse_mount *fm)
|
||||
int err;
|
||||
|
||||
if (fm->fc->sync_init) {
|
||||
ia->args.abort_on_kill = true;
|
||||
err = fuse_simple_request(fm, &ia->args);
|
||||
/* Ignore size of init reply */
|
||||
if (err > 0)
|
||||
@@ -1622,6 +1652,7 @@ struct fuse_dev *fuse_dev_alloc(void)
|
||||
if (!fud)
|
||||
return NULL;
|
||||
|
||||
refcount_set(&fud->ref, 1);
|
||||
pq = kzalloc_objs(struct list_head, FUSE_PQ_HASH_SIZE);
|
||||
if (!pq) {
|
||||
kfree(fud);
|
||||
@@ -1637,9 +1668,26 @@ EXPORT_SYMBOL_GPL(fuse_dev_alloc);
|
||||
|
||||
void fuse_dev_install(struct fuse_dev *fud, struct fuse_conn *fc)
|
||||
{
|
||||
fud->fc = fuse_conn_get(fc);
|
||||
struct fuse_conn *old_fc;
|
||||
|
||||
spin_lock(&fc->lock);
|
||||
list_add_tail(&fud->entry, &fc->devices);
|
||||
/*
|
||||
* Pairs with:
|
||||
* - xchg() in fuse_dev_release()
|
||||
* - smp_load_acquire() in fuse_dev_fc_get()
|
||||
*/
|
||||
old_fc = cmpxchg(&fud->fc, NULL, fc);
|
||||
if (old_fc) {
|
||||
/*
|
||||
* failed to set fud->fc because
|
||||
* - it was already set to a different fc
|
||||
* - it was set to disconneted
|
||||
*/
|
||||
fc->connected = 0;
|
||||
} else {
|
||||
list_add_tail(&fud->entry, &fc->devices);
|
||||
fuse_conn_get(fc);
|
||||
}
|
||||
spin_unlock(&fc->lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fuse_dev_install);
|
||||
@@ -1657,11 +1705,16 @@ struct fuse_dev *fuse_dev_alloc_install(struct fuse_conn *fc)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fuse_dev_alloc_install);
|
||||
|
||||
void fuse_dev_free(struct fuse_dev *fud)
|
||||
void fuse_dev_put(struct fuse_dev *fud)
|
||||
{
|
||||
struct fuse_conn *fc = fud->fc;
|
||||
struct fuse_conn *fc;
|
||||
|
||||
if (fc) {
|
||||
if (!refcount_dec_and_test(&fud->ref))
|
||||
return;
|
||||
|
||||
fc = fuse_dev_fc_get(fud);
|
||||
if (fc && fc != FUSE_DEV_FC_DISCONNECTED) {
|
||||
/* This is the virtiofs case (fuse_dev_release() not called) */
|
||||
spin_lock(&fc->lock);
|
||||
list_del(&fud->entry);
|
||||
spin_unlock(&fc->lock);
|
||||
@@ -1671,7 +1724,7 @@ void fuse_dev_free(struct fuse_dev *fud)
|
||||
kfree(fud->pq.processing);
|
||||
kfree(fud);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fuse_dev_free);
|
||||
EXPORT_SYMBOL_GPL(fuse_dev_put);
|
||||
|
||||
static void fuse_fill_attr_from_inode(struct fuse_attr *attr,
|
||||
const struct fuse_inode *fi)
|
||||
@@ -1823,7 +1876,7 @@ EXPORT_SYMBOL_GPL(fuse_init_fs_context_submount);
|
||||
|
||||
int fuse_fill_super_common(struct super_block *sb, struct fuse_fs_context *ctx)
|
||||
{
|
||||
struct fuse_dev *fud = NULL;
|
||||
struct fuse_dev *fud = ctx->fud;
|
||||
struct fuse_mount *fm = get_fuse_mount_super(sb);
|
||||
struct fuse_conn *fc = fm->fc;
|
||||
struct inode *root;
|
||||
@@ -1857,18 +1910,11 @@ int fuse_fill_super_common(struct super_block *sb, struct fuse_fs_context *ctx)
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (ctx->fudptr) {
|
||||
err = -ENOMEM;
|
||||
fud = fuse_dev_alloc_install(fc);
|
||||
if (!fud)
|
||||
goto err_free_dax;
|
||||
}
|
||||
|
||||
fc->dev = sb->s_dev;
|
||||
fm->sb = sb;
|
||||
err = fuse_bdi_init(fc, sb);
|
||||
if (err)
|
||||
goto err_dev_free;
|
||||
goto err_free_dax;
|
||||
|
||||
/* Handle umasking inside the fuse code */
|
||||
if (sb->s_flags & SB_POSIXACL)
|
||||
@@ -1890,15 +1936,15 @@ int fuse_fill_super_common(struct super_block *sb, struct fuse_fs_context *ctx)
|
||||
set_default_d_op(sb, &fuse_dentry_operations);
|
||||
root_dentry = d_make_root(root);
|
||||
if (!root_dentry)
|
||||
goto err_dev_free;
|
||||
goto err_free_dax;
|
||||
|
||||
mutex_lock(&fuse_mutex);
|
||||
err = -EINVAL;
|
||||
if (ctx->fudptr && *ctx->fudptr) {
|
||||
if (*ctx->fudptr == FUSE_DEV_SYNC_INIT)
|
||||
fc->sync_init = 1;
|
||||
else
|
||||
if (fud) {
|
||||
if (fuse_dev_fc_get(fud))
|
||||
goto err_unlock;
|
||||
if (fud->sync_init)
|
||||
fc->sync_init = 1;
|
||||
}
|
||||
|
||||
err = fuse_ctl_add_conn(fc);
|
||||
@@ -1907,8 +1953,8 @@ int fuse_fill_super_common(struct super_block *sb, struct fuse_fs_context *ctx)
|
||||
|
||||
list_add_tail(&fc->entry, &fuse_conn_list);
|
||||
sb->s_root = root_dentry;
|
||||
if (ctx->fudptr) {
|
||||
*ctx->fudptr = fud;
|
||||
if (fud) {
|
||||
fuse_dev_install(fud, fc);
|
||||
wake_up_all(&fuse_dev_waitq);
|
||||
}
|
||||
mutex_unlock(&fuse_mutex);
|
||||
@@ -1917,9 +1963,6 @@ int fuse_fill_super_common(struct super_block *sb, struct fuse_fs_context *ctx)
|
||||
err_unlock:
|
||||
mutex_unlock(&fuse_mutex);
|
||||
dput(root_dentry);
|
||||
err_dev_free:
|
||||
if (fud)
|
||||
fuse_dev_free(fud);
|
||||
err_free_dax:
|
||||
if (IS_ENABLED(CONFIG_FUSE_DAX))
|
||||
fuse_dax_conn_free(fc);
|
||||
@@ -1934,24 +1977,13 @@ static int fuse_fill_super(struct super_block *sb, struct fs_context *fsc)
|
||||
struct fuse_mount *fm;
|
||||
int err;
|
||||
|
||||
if (!ctx->file || !ctx->rootmode_present ||
|
||||
if (!ctx->fud || !ctx->rootmode_present ||
|
||||
!ctx->user_id_present || !ctx->group_id_present)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Require mount to happen from the same user namespace which
|
||||
* opened /dev/fuse to prevent potential attacks.
|
||||
*/
|
||||
if ((ctx->file->f_op != &fuse_dev_operations) ||
|
||||
(ctx->file->f_cred->user_ns != sb->s_user_ns))
|
||||
return -EINVAL;
|
||||
ctx->fudptr = &ctx->file->private_data;
|
||||
|
||||
err = fuse_fill_super_common(sb, ctx);
|
||||
if (err)
|
||||
return err;
|
||||
/* file->private_data shall be visible on all CPUs after this */
|
||||
smp_mb();
|
||||
|
||||
fm = get_fuse_mount_super(sb);
|
||||
|
||||
@@ -1969,14 +2001,14 @@ static int fuse_set_no_super(struct super_block *sb, struct fs_context *fsc)
|
||||
|
||||
static int fuse_test_super(struct super_block *sb, struct fs_context *fsc)
|
||||
{
|
||||
struct fuse_dev *fud = fsc->sget_key;
|
||||
|
||||
return fsc->sget_key == get_fuse_conn_super(sb);
|
||||
return fuse_dev_fc_get(fud) == get_fuse_conn_super(sb);
|
||||
}
|
||||
|
||||
static int fuse_get_tree(struct fs_context *fsc)
|
||||
{
|
||||
struct fuse_fs_context *ctx = fsc->fs_private;
|
||||
struct fuse_dev *fud;
|
||||
struct fuse_conn *fc;
|
||||
struct fuse_mount *fm;
|
||||
struct super_block *sb;
|
||||
@@ -1997,9 +2029,6 @@ static int fuse_get_tree(struct fs_context *fsc)
|
||||
|
||||
fsc->s_fs_info = fm;
|
||||
|
||||
if (ctx->fd_present)
|
||||
ctx->file = fget(ctx->fd);
|
||||
|
||||
if (IS_ENABLED(CONFIG_BLOCK) && ctx->is_bdev) {
|
||||
err = get_tree_bdev(fsc, fuse_fill_super);
|
||||
goto out;
|
||||
@@ -2009,16 +2038,15 @@ static int fuse_get_tree(struct fs_context *fsc)
|
||||
* (found by device name), normal fuse mounts can't
|
||||
*/
|
||||
err = -EINVAL;
|
||||
if (!ctx->file)
|
||||
if (!ctx->fud)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* Allow creating a fuse mount with an already initialized fuse
|
||||
* connection
|
||||
*/
|
||||
fud = __fuse_get_dev(ctx->file);
|
||||
if (ctx->file->f_op == &fuse_dev_operations && fud) {
|
||||
fsc->sget_key = fud->fc;
|
||||
if (fuse_dev_fc_get(ctx->fud)) {
|
||||
fsc->sget_key = ctx->fud;
|
||||
sb = sget_fc(fsc, fuse_test_super, fuse_set_no_super);
|
||||
err = PTR_ERR_OR_ZERO(sb);
|
||||
if (!IS_ERR(sb))
|
||||
@@ -2029,8 +2057,6 @@ static int fuse_get_tree(struct fs_context *fsc)
|
||||
out:
|
||||
if (fsc->s_fs_info)
|
||||
fuse_mount_destroy(fm);
|
||||
if (ctx->file)
|
||||
fput(ctx->file);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ static void fuse_add_dirent_to_cache(struct file *file,
|
||||
}
|
||||
version = fi->rdc.version;
|
||||
size = fi->rdc.size;
|
||||
offset = size & ~PAGE_MASK;
|
||||
offset = offset_in_page(size);
|
||||
index = size >> PAGE_SHIFT;
|
||||
/* Dirent doesn't fit in current page? Jump to next page. */
|
||||
if (offset + reclen > PAGE_SIZE) {
|
||||
@@ -392,7 +392,7 @@ static enum fuse_parse_result fuse_parse_cache(struct fuse_file *ff,
|
||||
void *addr, unsigned int size,
|
||||
struct dir_context *ctx)
|
||||
{
|
||||
unsigned int offset = ff->readdir.cache_off & ~PAGE_MASK;
|
||||
unsigned int offset = offset_in_page(ff->readdir.cache_off);
|
||||
enum fuse_parse_result res = FOUND_NONE;
|
||||
|
||||
WARN_ON(offset >= size);
|
||||
@@ -518,13 +518,13 @@ retry_locked:
|
||||
index = ff->readdir.cache_off >> PAGE_SHIFT;
|
||||
|
||||
if (index == (fi->rdc.size >> PAGE_SHIFT))
|
||||
size = fi->rdc.size & ~PAGE_MASK;
|
||||
size = offset_in_page(fi->rdc.size);
|
||||
else
|
||||
size = PAGE_SIZE;
|
||||
spin_unlock(&fi->rdc.lock);
|
||||
|
||||
/* EOF? */
|
||||
if ((ff->readdir.cache_off & ~PAGE_MASK) == size)
|
||||
if (offset_in_page(ff->readdir.cache_off) == size)
|
||||
return 0;
|
||||
|
||||
page = find_get_page_flags(file->f_mapping, index,
|
||||
|
||||
@@ -486,7 +486,7 @@ static void virtio_fs_free_devs(struct virtio_fs *fs)
|
||||
if (!fsvq->fud)
|
||||
continue;
|
||||
|
||||
fuse_dev_free(fsvq->fud);
|
||||
fuse_dev_put(fsvq->fud);
|
||||
fsvq->fud = NULL;
|
||||
}
|
||||
}
|
||||
@@ -758,6 +758,27 @@ static void copy_args_from_argbuf(struct fuse_args *args, struct fuse_req *req)
|
||||
req->argbuf = NULL;
|
||||
}
|
||||
|
||||
/* Verify that the server properly follows the FUSE protocol */
|
||||
static bool virtio_fs_verify_response(struct fuse_req *req, unsigned int len)
|
||||
{
|
||||
struct fuse_out_header *oh = &req->out.h;
|
||||
|
||||
if (len < sizeof(*oh)) {
|
||||
pr_warn("virtio-fs: response too short (%u)\n", len);
|
||||
return false;
|
||||
}
|
||||
if (oh->len != len) {
|
||||
pr_warn("virtio-fs: oh.len mismatch (%u != %u)\n", oh->len, len);
|
||||
return false;
|
||||
}
|
||||
if (oh->unique != req->in.h.unique) {
|
||||
pr_warn("virtio-fs: oh.unique mismatch (%llu != %llu)\n",
|
||||
oh->unique, req->in.h.unique);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Work function for request completion */
|
||||
static void virtio_fs_request_complete(struct fuse_req *req,
|
||||
struct virtio_fs_vq *fsvq)
|
||||
@@ -767,10 +788,6 @@ static void virtio_fs_request_complete(struct fuse_req *req,
|
||||
unsigned int len, i, thislen;
|
||||
struct folio *folio;
|
||||
|
||||
/*
|
||||
* TODO verify that server properly follows FUSE protocol
|
||||
* (oh.uniq, oh.len)
|
||||
*/
|
||||
args = req->args;
|
||||
copy_args_from_argbuf(args, req);
|
||||
|
||||
@@ -824,6 +841,10 @@ static void virtio_fs_requests_done_work(struct work_struct *work)
|
||||
virtqueue_disable_cb(vq);
|
||||
|
||||
while ((req = virtqueue_get_buf(vq, &len)) != NULL) {
|
||||
if (!virtio_fs_verify_response(req, len)) {
|
||||
req->out.h.error = -EIO;
|
||||
req->out.h.len = sizeof(struct fuse_out_header);
|
||||
}
|
||||
spin_lock(&fpq->lock);
|
||||
list_move_tail(&req->list, &reqs);
|
||||
spin_unlock(&fpq->lock);
|
||||
@@ -1569,8 +1590,6 @@ static int virtio_fs_fill_super(struct super_block *sb, struct fs_context *fsc)
|
||||
goto err_free_fuse_devs;
|
||||
}
|
||||
|
||||
/* virtiofs allocates and installs its own fuse devices */
|
||||
ctx->fudptr = NULL;
|
||||
if (ctx->dax_mode != FUSE_DAX_NEVER) {
|
||||
if (ctx->dax_mode == FUSE_DAX_ALWAYS && !fs->dax_dev) {
|
||||
err = -EINVAL;
|
||||
|
||||
Reference in New Issue
Block a user