Something went wrong. Try again.
Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367// SPDX-License-Identifier: GPL-2.0/* FUSE: Filesystem in Userspace Copyright (C) 2001-2008 Miklos Szeredi <miklos@szeredi.hu>*/
#include "fuse_i.h"#include "dev.h"
#include <linux/init.h>#include <linux/module.h>#include <linux/fs_context.h>#include <linux/namei.h>
#define FUSE_CTL_SUPER_MAGIC 0x65735543
/* * This is non-NULL when the single instance of the control filesystem * exists. Protected by fuse_mutex */static struct super_block *fuse_control_sb;
static struct fuse_conn *fuse_ctl_file_conn_get(struct file *file){ struct fuse_conn *fc; mutex_lock(&fuse_mutex); fc = file_inode(file)->i_private; if (fc) fc = fuse_conn_get(fc); mutex_unlock(&fuse_mutex); return fc;}
static ssize_t fuse_conn_abort_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos){ struct fuse_conn *fc = fuse_ctl_file_conn_get(file); if (fc) { fuse_chan_abort(fc->chan, fc->abort_err); fuse_conn_put(fc); } return count;}
static ssize_t fuse_conn_waiting_read(struct file *file, char __user *buf, size_t len, loff_t *ppos){ char tmp[32]; size_t size;
if (!*ppos) { long value; struct fuse_conn *fc = fuse_ctl_file_conn_get(file); if (!fc) return 0;
value = fuse_chan_num_waiting(fc->chan); file->private_data = (void *)value; fuse_conn_put(fc); } size = sprintf(tmp, "%ld\n", (long)file->private_data); return simple_read_from_buffer(buf, len, ppos, tmp, size);}
static ssize_t fuse_conn_limit_read(struct file *file, char __user *buf, size_t len, loff_t *ppos, unsigned val){ char tmp[32]; size_t size = sprintf(tmp, "%u\n", val);
return simple_read_from_buffer(buf, len, ppos, tmp, size);}
static ssize_t fuse_conn_limit_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos, unsigned *val, unsigned global_limit){ unsigned long t; unsigned limit = (1 << 16) - 1; int err;
if (*ppos) return -EINVAL;
err = kstrtoul_from_user(buf, count, 0, &t); if (err) return err;
if (!capable(CAP_SYS_ADMIN)) limit = min(limit, global_limit);
if (t > limit) return -EINVAL;
*val = t;
return count;}
static ssize_t fuse_conn_max_background_read(struct file *file, char __user *buf, size_t len, loff_t *ppos){ struct fuse_conn *fc; unsigned val;
fc = fuse_ctl_file_conn_get(file); if (!fc) return 0;
val = fuse_chan_max_background(fc->chan); fuse_conn_put(fc);
return fuse_conn_limit_read(file, buf, len, ppos, val);}
static ssize_t fuse_conn_max_background_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos){ unsigned int val = 0; ssize_t ret;
ret = fuse_conn_limit_write(file, buf, count, ppos, &val, max_user_bgreq); if (ret > 0) { struct fuse_conn *fc = fuse_ctl_file_conn_get(file); if (fc) { fuse_chan_max_background_set(fc->chan, val); fuse_conn_put(fc); } }
return ret;}
static ssize_t fuse_conn_congestion_threshold_read(struct file *file, char __user *buf, size_t len, loff_t *ppos){ struct fuse_conn *fc; unsigned val;
fc = fuse_ctl_file_conn_get(file); if (!fc) return 0;
val = READ_ONCE(fc->congestion_threshold); fuse_conn_put(fc);
return fuse_conn_limit_read(file, buf, len, ppos, val);}
static ssize_t fuse_conn_congestion_threshold_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos){ unsigned int val = 0; struct fuse_conn *fc; ssize_t ret;
ret = fuse_conn_limit_write(file, buf, count, ppos, &val, max_user_congthresh); if (ret <= 0) goto out; fc = fuse_ctl_file_conn_get(file); if (!fc) goto out;
WRITE_ONCE(fc->congestion_threshold, val); fuse_conn_put(fc);out: return ret;}
static const struct file_operations fuse_ctl_abort_ops = { .open = nonseekable_open, .write = fuse_conn_abort_write,};
static const struct file_operations fuse_ctl_waiting_ops = { .open = nonseekable_open, .read = fuse_conn_waiting_read,};
static const struct file_operations fuse_conn_max_background_ops = { .open = nonseekable_open, .read = fuse_conn_max_background_read, .write = fuse_conn_max_background_write,};
static const struct file_operations fuse_conn_congestion_threshold_ops = { .open = nonseekable_open, .read = fuse_conn_congestion_threshold_read, .write = fuse_conn_congestion_threshold_write,};
static struct dentry *fuse_ctl_add_dentry(struct dentry *parent, struct fuse_conn *fc, const char *name, int mode, const struct inode_operations *iop, const struct file_operations *fop){ struct dentry *dentry; struct inode *inode;
dentry = d_alloc_name(parent, name); if (!dentry) return NULL;
inode = new_inode(fuse_control_sb); if (!inode) { dput(dentry); return NULL; }
inode->i_ino = get_next_ino(); inode->i_mode = mode; inode->i_uid = fc->user_id; inode->i_gid = fc->group_id; simple_inode_init_ts(inode); /* setting ->i_op to NULL is not allowed */ if (iop) inode->i_op = iop; inode->i_fop = fop; if (S_ISDIR(mode)) { inc_nlink(d_inode(parent)); inc_nlink(inode); } inode->i_private = fc; d_make_persistent(dentry, inode); dput(dentry);
/* * We are returning a borrowed reference here - it's only good while * fuse_mutex is held. Actually it's d_make_persistent() return * value... */ return dentry;}
/* * Add a connection to the control filesystem (if it exists). Caller * must hold fuse_mutex */int fuse_ctl_add_conn(struct fuse_conn *fc){ struct dentry *parent; char name[32];
if (!fuse_control_sb || fc->no_control) return 0;
parent = fuse_control_sb->s_root; sprintf(name, "%u", fc->dev); parent = fuse_ctl_add_dentry(parent, fc, name, S_IFDIR | 0500, &simple_dir_inode_operations, &simple_dir_operations); if (!parent) goto err;
if (!fuse_ctl_add_dentry(parent, fc, "waiting", S_IFREG | 0400, NULL, &fuse_ctl_waiting_ops) || !fuse_ctl_add_dentry(parent, fc, "abort", S_IFREG | 0200, NULL, &fuse_ctl_abort_ops) || !fuse_ctl_add_dentry(parent, fc, "max_background", S_IFREG | 0600, NULL, &fuse_conn_max_background_ops) || !fuse_ctl_add_dentry(parent, fc, "congestion_threshold", S_IFREG | 0600, NULL, &fuse_conn_congestion_threshold_ops)) goto err;
return 0;
err: fuse_ctl_remove_conn(fc); return -ENOMEM;}
static void remove_one(struct dentry *dentry){ d_inode(dentry)->i_private = NULL;}
/* * Remove a connection from the control filesystem (if it exists). * Caller must hold fuse_mutex */void fuse_ctl_remove_conn(struct fuse_conn *fc){ char name[32];
if (!fuse_control_sb || fc->no_control) return;
sprintf(name, "%u", fc->dev); simple_remove_by_name(fuse_control_sb->s_root, name, remove_one);}
static int fuse_ctl_fill_super(struct super_block *sb, struct fs_context *fsc){ static const struct tree_descr empty_descr = {""}; struct fuse_conn *fc; int err;
err = simple_fill_super(sb, FUSE_CTL_SUPER_MAGIC, &empty_descr); if (err) return err;
mutex_lock(&fuse_mutex); BUG_ON(fuse_control_sb); fuse_control_sb = sb; list_for_each_entry(fc, &fuse_conn_list, entry) { err = fuse_ctl_add_conn(fc); if (err) { fuse_control_sb = NULL; mutex_unlock(&fuse_mutex); return err; } } mutex_unlock(&fuse_mutex);
return 0;}
static int fuse_ctl_get_tree(struct fs_context *fsc){ return get_tree_single(fsc, fuse_ctl_fill_super);}
static const struct fs_context_operations fuse_ctl_context_ops = { .get_tree = fuse_ctl_get_tree,};
static int fuse_ctl_init_fs_context(struct fs_context *fsc){ fsc->ops = &fuse_ctl_context_ops; return 0;}
static void fuse_ctl_kill_sb(struct super_block *sb){ mutex_lock(&fuse_mutex); fuse_control_sb = NULL; mutex_unlock(&fuse_mutex);
kill_anon_super(sb);}
static struct file_system_type fuse_ctl_fs_type = { .owner = THIS_MODULE, .name = "fusectl", .init_fs_context = fuse_ctl_init_fs_context, .kill_sb = fuse_ctl_kill_sb,};MODULE_ALIAS_FS("fusectl");
int __init fuse_ctl_init(void){ return register_filesystem(&fuse_ctl_fs_type);}
void __exit fuse_ctl_cleanup(void){ unregister_filesystem(&fuse_ctl_fs_type);}