// SPDX-License-Identifier: GPL-2.0-only /* * CUSE: Character device in Userspace * * Copyright (C) 2008-2009 SUSE Linux Products GmbH * Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org> * * CUSE enables character devices to be implemented from userland much * like FUSE allows filesystems. On initialization /dev/cuse is * created. By opening the file and replying to the CUSE_INIT request * userland CUSE server can create a character device. After that the * operation is very similar to FUSE. * * A CUSE instance involves the following objects. * * cuse_conn : contains fuse_conn and serves as bonding structure * channel : file handle connected to the userland CUSE server * cdev : the implemented character device * dev : generic device for cdev * * Note that 'channel' is what 'dev' is in FUSE. As CUSE deals with * devices, it's called 'channel' to reduce confusion. * * channel determines when the character device dies. When channel is * closed, everything begins to destruct. The cuse_conn is taken off * the lookup table preventing further access from cdev, cdev and * generic device are removed and the base reference of cuse_conn is * put. * * On each open, the matching cuse_conn is looked up and if found an * additional reference is taken which is released when the file is * closed.
*/
/************************************************************************** * CUSE frontend operations * * These are file operations for the character device. * * On open, CUSE opens a file from the FUSE mnt and stores it to * private_data of the open file. All other ops call FUSE ops on the * FUSE file.
*/
/* look up and get the connection */
mutex_lock(&cuse_lock);
list_for_each_entry(pos, cuse_conntbl_head(devt), list) if (pos->dev->devt == devt) {
fuse_conn_get(&pos->fc);
cc = pos; break;
}
mutex_unlock(&cuse_lock);
/* dead? */ if (!cc) return -ENODEV;
/* * Generic permission check is already done against the chrdev * file, proceed to open.
*/
rc = fuse_do_open(&cc->fm, 0, file, 0); if (rc)
fuse_conn_put(&cc->fc); return rc;
}
/************************************************************************** * CUSE channel initialization and destruction
*/
struct cuse_devinfo { constchar *name;
};
/** * cuse_parse_one - parse one key=value pair * @pp: i/o parameter for the current position * @end: points to one past the end of the packed string * @keyp: out parameter for key * @valp: out parameter for value * * *@pp points to packed strings - "key0=val0\0key1=val1\0" which ends * at @end - 1. This function parses one pair and set *@keyp to the * start of the key and *@valp to the start of the value. Note that * the original string is modified such that the key string is * terminated with '\0'. *@pp is updated to point to the next string. * * RETURNS: * 1 on successful parse, 0 on EOF, -errno on failure.
*/ staticint cuse_parse_one(char **pp, char *end, char **keyp, char **valp)
{ char *p = *pp; char *key, *val;
while (p < end && *p == '\0')
p++; if (p == end) return 0;
if (end[-1] != '\0') {
pr_err("info not properly terminated\n"); return -EINVAL;
}
key = val = p;
p += strlen(p);
if (valp) {
strsep(&val, "="); if (!val)
val = key + strlen(key);
key = strstrip(key);
val = strstrip(val);
} else
key = strstrip(key);
if (!strlen(key)) {
pr_err("zero length info key specified\n"); return -EINVAL;
}
*pp = p;
*keyp = key; if (valp)
*valp = val;
return 1;
}
/** * cuse_parse_devinfo - parse device info * @p: device info string * @len: length of device info string * @devinfo: out parameter for parsed device info * * Parse @p to extract device info and store it into @devinfo. String * pointed to by @p is modified by parsing and @devinfo points into * them, so @p shouldn't be freed while @devinfo is in use. * * RETURNS: * 0 on success, -errno on failure.
*/ staticint cuse_parse_devinfo(char *p, size_t len, struct cuse_devinfo *devinfo)
{ char *end = p + len; char *key, *val; int rc;
while (true) {
rc = cuse_parse_one(&p, end, &key, &val); if (rc < 0) return rc; if (!rc) break; if (strcmp(key, "DEVNAME") == 0)
devinfo->name = val; else
pr_warn("unknown device info \"%s\"\n", key);
}
if (!devinfo->name || !strlen(devinfo->name)) {
pr_err("DEVNAME unspecified\n"); return -EINVAL;
}
/** * cuse_process_init_reply - finish initializing CUSE channel * * @fm: The fuse mount information containing the CUSE connection. * @args: The arguments passed to the init reply. * @error: The error code signifying if any error occurred during the process. * * This function creates the character device and sets up all the * required data structures for it. Please read the comment at the * top of this file for high level overview.
*/ staticvoid cuse_process_init_reply(struct fuse_mount *fm, struct fuse_args *args, int error)
{ struct fuse_conn *fc = fm->fc; struct cuse_init_args *ia = container_of(args, typeof(*ia), ap.args); struct fuse_args_pages *ap = &ia->ap; struct cuse_conn *cc = fc_to_cc(fc), *pos; struct cuse_init_out *arg = &ia->out; struct folio *folio = ap->folios[0]; struct cuse_devinfo devinfo = { }; struct device *dev; struct cdev *cdev;
dev_t devt; int rc, i;
/* make sure the device-name is unique */ for (i = 0; i < CUSE_CONNTBL_LEN; ++i) {
list_for_each_entry(pos, &cuse_conntbl[i], list) if (!strcmp(dev_name(pos->dev), dev_name(dev))) goto err_unlock;
}
/** * cuse_channel_open - open method for /dev/cuse * @inode: inode for /dev/cuse * @file: file struct being opened * * Userland CUSE server can create a CUSE device by opening /dev/cuse * and replying to the initialization request kernel sends. This * function is responsible for handling CUSE device initialization. * Because the fd opened by this function is used during * initialization, this function only creates cuse_conn and sends * init. The rest is delegated to a kthread. * * RETURNS: * 0 on success, -errno on failure.
*/ staticint cuse_channel_open(struct inode *inode, struct file *file)
{ struct fuse_dev *fud; struct cuse_conn *cc; int rc;
/* set up cuse_conn */
cc = kzalloc(sizeof(*cc), GFP_KERNEL); if (!cc) return -ENOMEM;
/* * Limit the cuse channel to requests that can * be represented in file->f_cred->user_ns.
*/
fuse_conn_init(&cc->fc, &cc->fm, file->f_cred->user_ns,
&fuse_dev_fiq_ops, NULL);
cc->fc.release = cuse_fc_release;
fud = fuse_dev_alloc_install(&cc->fc);
fuse_conn_put(&cc->fc); if (!fud) return -ENOMEM;
/** * cuse_channel_release - release method for /dev/cuse * @inode: inode for /dev/cuse * @file: file struct being closed * * Disconnect the channel, deregister CUSE device and initiate * destruction by putting the default reference. * * RETURNS: * 0 on success, -errno on failure.
*/ staticint cuse_channel_release(struct inode *inode, struct file *file)
{ struct fuse_dev *fud = file->private_data; struct cuse_conn *cc = fc_to_cc(fud->fc);
/* remove from the conntbl, no more access from this point on */
mutex_lock(&cuse_lock);
list_del_init(&cc->list);
mutex_unlock(&cuse_lock);
/* remove device */ if (cc->dev)
device_unregister(cc->dev); if (cc->cdev) {
unregister_chrdev_region(cc->cdev->dev, 1);
cdev_del(cc->cdev);
}
return fuse_dev_release(inode, file);
}
staticstruct file_operations cuse_channel_fops; /* initialized during init */
/************************************************************************** * Misc stuff and module initializatiion * * CUSE exports the same set of attributes to sysfs as fusectl.
*/
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.