| /* |
| FUSE: Filesystem in Userspace |
| Copyright (C) 2001-2004 Miklos Szeredi <miklos@szeredi.hu> |
| |
| This program can be distributed under the terms of the GNU GPL. |
| See the file COPYING. |
| */ |
| |
| #include "fuse_i.h" |
| |
| #include <linux/poll.h> |
| #include <linux/proc_fs.h> |
| #include <linux/file.h> |
| |
| /* If more requests are outstanding, then the operation will block */ |
| #define MAX_OUTSTANDING 10 |
| |
| static struct proc_dir_entry *proc_fs_fuse; |
| struct proc_dir_entry *proc_fuse_dev; |
| static kmem_cache_t *fuse_req_cachep; |
| |
| struct fuse_req *fuse_request_alloc(void) |
| { |
| struct fuse_req *req; |
| |
| req = (struct fuse_req *) kmem_cache_alloc(fuse_req_cachep, SLAB_KERNEL); |
| if (req) { |
| memset(req, 0, sizeof(*req)); |
| INIT_LIST_HEAD(&req->list); |
| init_waitqueue_head(&req->waitq); |
| } |
| |
| return req; |
| } |
| |
| void fuse_request_free(struct fuse_req *req) |
| { |
| kmem_cache_free(fuse_req_cachep, req); |
| } |
| |
| static int request_restartable(enum fuse_opcode opcode) |
| { |
| switch (opcode) { |
| case FUSE_LOOKUP: |
| case FUSE_GETATTR: |
| case FUSE_SETATTR: |
| case FUSE_READLINK: |
| case FUSE_GETDIR: |
| case FUSE_OPEN: |
| case FUSE_READ: |
| case FUSE_WRITE: |
| case FUSE_STATFS: |
| case FUSE_FSYNC: |
| case FUSE_GETXATTR: |
| case FUSE_SETXATTR: |
| case FUSE_LISTXATTR: |
| return 1; |
| |
| default: |
| return 0; |
| } |
| } |
| |
| /* Called with fuse_lock held. Releases, and then reaquires it. */ |
| static void request_wait_answer(struct fuse_req *req, int interruptible) |
| { |
| int intr; |
| |
| spin_unlock(&fuse_lock); |
| if (interruptible) |
| intr = wait_event_interruptible(req->waitq, req->finished); |
| else { |
| wait_event(req->waitq, req->finished); |
| intr = 0; |
| } |
| spin_lock(&fuse_lock); |
| if (!intr) |
| return; |
| |
| /* Request interrupted... Wait for it to be unlocked */ |
| while (req->locked) { |
| req->interrupted = 1; |
| spin_unlock(&fuse_lock); |
| wait_event(req->waitq, !req->locked); |
| spin_lock(&fuse_lock); |
| } |
| if (req->finished) |
| return; |
| |
| /* Operations which modify the filesystem cannot safely be |
| restarted, because it is uncertain whether the operation has |
| completed or not... */ |
| if (req->sent && !request_restartable(req->in.h.opcode)) |
| req->out.h.error = -EINTR; |
| else |
| req->out.h.error = -ERESTARTSYS; |
| } |
| |
| static int get_unique(struct fuse_conn *fc) |
| { |
| do fc->reqctr++; |
| while (!fc->reqctr); |
| return fc->reqctr; |
| } |
| |
| void fuse_reset_request(struct fuse_req *req) |
| { |
| int preallocated = req->preallocated; |
| |
| memset(req, 0, sizeof(*req)); |
| INIT_LIST_HEAD(&req->list); |
| init_waitqueue_head(&req->waitq); |
| req->preallocated = preallocated; |
| } |
| |
| static struct fuse_req *do_get_request(struct fuse_conn *fc) |
| { |
| struct fuse_req *req; |
| |
| spin_lock(&fuse_lock); |
| BUG_ON(list_empty(&fc->unused_list)); |
| req = list_entry(fc->unused_list.next, struct fuse_req, list); |
| list_del_init(&req->list); |
| spin_unlock(&fuse_lock); |
| fuse_reset_request(req); |
| req->in.h.uid = current->fsuid; |
| req->in.h.gid = current->fsgid; |
| req->in.h.pid = current->pid; |
| return req; |
| } |
| |
| struct fuse_req *fuse_get_request(struct fuse_conn *fc) |
| { |
| struct fuse_req *req; |
| |
| if (down_interruptible(&fc->unused_sem)) |
| return NULL; |
| |
| req = do_get_request(fc); |
| return req; |
| } |
| |
| struct fuse_req *fuse_get_request_nonblock(struct fuse_conn *fc) |
| { |
| struct fuse_req *req; |
| |
| if (down_trylock(&fc->unused_sem)) |
| return NULL; |
| |
| req = do_get_request(fc); |
| return req; |
| } |
| |
| void fuse_put_request(struct fuse_conn *fc, struct fuse_req *req) |
| { |
| if (!req->preallocated) |
| fuse_request_free(req); |
| else { |
| spin_lock(&fuse_lock); |
| list_add(&req->list, &fc->unused_list); |
| spin_unlock(&fuse_lock); |
| up(&fc->unused_sem); |
| } |
| } |
| |
| /* Must be called with fuse_lock held, and unlocks it */ |
| static void request_end(struct fuse_conn *fc, struct fuse_req *req) |
| { |
| fuse_reqend_t endfunc = req->end; |
| |
| if (!endfunc) { |
| wake_up(&req->waitq); |
| spin_unlock(&fuse_lock); |
| } else { |
| spin_unlock(&fuse_lock); |
| endfunc(fc, req); |
| } |
| } |
| |
| static void __request_send(struct fuse_conn *fc, struct fuse_req *req, |
| int interruptible) |
| { |
| req->issync = 1; |
| req->end = NULL; |
| |
| spin_lock(&fuse_lock); |
| req->out.h.error = -ENOTCONN; |
| if (fc->file) { |
| req->in.h.unique = get_unique(fc); |
| list_add_tail(&req->list, &fc->pending); |
| wake_up(&fc->waitq); |
| request_wait_answer(req, interruptible); |
| list_del(&req->list); |
| } |
| spin_unlock(&fuse_lock); |
| } |
| |
| void request_send(struct fuse_conn *fc, struct fuse_req *req) |
| { |
| /* There are problems with interrupted requests so it's |
| disabled for now */ |
| __request_send(fc, req, 0); |
| } |
| |
| void request_send_nonint(struct fuse_conn *fc, struct fuse_req *req) |
| { |
| __request_send(fc, req, 0); |
| } |
| |
| void request_send_noreply(struct fuse_conn *fc, struct fuse_req *req) |
| { |
| req->issync = 0; |
| |
| if (fc->file) { |
| spin_lock(&fuse_lock); |
| list_add_tail(&req->list, &fc->pending); |
| wake_up(&fc->waitq); |
| spin_unlock(&fuse_lock); |
| } else |
| fuse_put_request(fc, req); |
| } |
| |
| void request_send_nonblock(struct fuse_conn *fc, struct fuse_req *req, |
| fuse_reqend_t end, void *data) |
| { |
| req->end = end; |
| req->data = data; |
| req->issync = 1; |
| |
| spin_lock(&fuse_lock); |
| if (fc->file) { |
| req->in.h.unique = get_unique(fc); |
| list_add_tail(&req->list, &fc->pending); |
| wake_up(&fc->waitq); |
| spin_unlock(&fuse_lock); |
| } else { |
| req->out.h.error = -ENOTCONN; |
| request_end(fc, req); |
| } |
| } |
| |
| static void request_wait(struct fuse_conn *fc) |
| { |
| DECLARE_WAITQUEUE(wait, current); |
| |
| add_wait_queue_exclusive(&fc->waitq, &wait); |
| while (fc->sb != NULL && list_empty(&fc->pending)) { |
| set_current_state(TASK_INTERRUPTIBLE); |
| if (signal_pending(current)) |
| break; |
| |
| spin_unlock(&fuse_lock); |
| schedule(); |
| spin_lock(&fuse_lock); |
| } |
| set_current_state(TASK_RUNNING); |
| remove_wait_queue(&fc->waitq, &wait); |
| } |
| |
| static inline int copy_in_one(const void *src, size_t srclen, char **dstp, |
| size_t *dstlenp) |
| { |
| if (*dstlenp < srclen) { |
| printk("fuse_dev_read: buffer too small\n"); |
| return -EINVAL; |
| } |
| |
| if (srclen && copy_to_user(*dstp, src, srclen)) |
| return -EFAULT; |
| |
| *dstp += srclen; |
| *dstlenp -= srclen; |
| |
| return 0; |
| } |
| |
| static inline int copy_in_args(struct fuse_in *in, char *buf, size_t nbytes) |
| { |
| int err; |
| int i; |
| size_t orignbytes = nbytes; |
| |
| err = copy_in_one(&in->h, sizeof(in->h), &buf, &nbytes); |
| if (err) |
| return err; |
| |
| for (i = 0; i < in->numargs; i++) { |
| struct fuse_in_arg *arg = &in->args[i]; |
| err = copy_in_one(arg->value, arg->size, &buf, &nbytes); |
| if (err) |
| return err; |
| } |
| |
| return orignbytes - nbytes; |
| } |
| |
| static ssize_t fuse_dev_read(struct file *file, char *buf, size_t nbytes, |
| loff_t *off) |
| { |
| ssize_t ret; |
| struct fuse_conn *fc = DEV_FC(file); |
| struct fuse_req *req = NULL; |
| |
| spin_lock(&fuse_lock); |
| request_wait(fc); |
| if (fc->sb != NULL && !list_empty(&fc->pending)) { |
| req = list_entry(fc->pending.next, struct fuse_req, list); |
| list_del_init(&req->list); |
| req->locked = 1; |
| } |
| spin_unlock(&fuse_lock); |
| if (fc->sb == NULL) |
| return -ENODEV; |
| if (req == NULL) |
| return -EINTR; |
| |
| ret = copy_in_args(&req->in, buf, nbytes); |
| spin_lock(&fuse_lock); |
| if (req->issync) { |
| if (ret < 0) { |
| req->out.h.error = -EPROTO; |
| req->finished = 1; |
| } else { |
| list_add_tail(&req->list, &fc->processing); |
| req->sent = 1; |
| } |
| req->locked = 0; |
| if (ret < 0 || req->interrupted) |
| /* Unlocks fuse_lock: */ |
| request_end(fc, req); |
| else |
| spin_unlock(&fuse_lock); |
| } else { |
| spin_unlock(&fuse_lock); |
| fuse_put_request(fc, req); |
| } |
| return ret; |
| } |
| |
| static struct fuse_req *request_find(struct fuse_conn *fc, unsigned int unique) |
| { |
| struct list_head *entry; |
| struct fuse_req *req = NULL; |
| |
| list_for_each(entry, &fc->processing) { |
| struct fuse_req *tmp; |
| tmp = list_entry(entry, struct fuse_req, list); |
| if (tmp->in.h.unique == unique) { |
| req = tmp; |
| break; |
| } |
| } |
| |
| return req; |
| } |
| |
| static void process_getdir(struct fuse_req *req) |
| { |
| struct fuse_getdir_out_i *arg; |
| arg = (struct fuse_getdir_out_i *) req->out.args[0].value; |
| arg->file = fget(arg->fd); |
| } |
| |
| static inline int copy_out_one(struct fuse_out_arg *arg, const char **srcp, |
| size_t *srclenp, int allowvar) |
| { |
| size_t dstlen = arg->size; |
| if (*srclenp < dstlen) { |
| if (!allowvar) { |
| printk("fuse_dev_write: write is short\n"); |
| return -EINVAL; |
| } |
| dstlen = *srclenp; |
| } |
| |
| if (dstlen && copy_from_user(arg->value, *srcp, dstlen)) |
| return -EFAULT; |
| |
| *srcp += dstlen; |
| *srclenp -= dstlen; |
| arg->size = dstlen; |
| |
| return 0; |
| } |
| |
| static inline int copy_out_args(struct fuse_req *req, const char *buf, |
| size_t nbytes) |
| { |
| struct fuse_out *out = &req->out; |
| int err; |
| int i; |
| |
| buf += sizeof(struct fuse_out_header); |
| nbytes -= sizeof(struct fuse_out_header); |
| |
| if (!out->h.error) { |
| if (req->copy_out) |
| return req->copy_out(req, buf, nbytes); |
| else { |
| for (i = 0; i < out->numargs; i++) { |
| struct fuse_out_arg *arg = &out->args[i]; |
| int allowvar; |
| |
| if (out->argvar && i == out->numargs - 1) |
| allowvar = 1; |
| else |
| allowvar = 0; |
| |
| err = copy_out_one(arg, &buf, &nbytes, allowvar); |
| if (err) |
| return err; |
| } |
| } |
| } |
| |
| if (nbytes != 0) { |
| printk("fuse_dev_write: write is long\n"); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static inline int copy_out_header(struct fuse_out_header *oh, const char *buf, |
| size_t nbytes) |
| { |
| if (nbytes < sizeof(struct fuse_out_header)) { |
| printk("fuse_dev_write: write is short\n"); |
| return -EINVAL; |
| } |
| |
| if (copy_from_user(oh, buf, sizeof(struct fuse_out_header))) |
| return -EFAULT; |
| |
| return 0; |
| } |
| |
| static int fuse_invalidate(struct fuse_conn *fc, struct fuse_user_header *uh) |
| { |
| struct inode *inode = fuse_ilookup(fc, uh->ino, uh->nodeid); |
| if (!inode) |
| return -ENOENT; |
| fuse_sync_inode(inode); |
| #ifdef KERNEL_2_6 |
| invalidate_inode_pages(inode->i_mapping); |
| #else |
| invalidate_inode_pages(inode); |
| #endif |
| |
| iput(inode); |
| return 0; |
| } |
| |
| static int fuse_user_request(struct fuse_conn *fc, const char *buf, |
| size_t nbytes) |
| { |
| struct fuse_user_header uh; |
| int err; |
| |
| if (nbytes < sizeof(struct fuse_user_header)) { |
| printk("fuse_dev_write: write is short\n"); |
| return -EINVAL; |
| } |
| |
| if (copy_from_user(&uh, buf, sizeof(struct fuse_user_header))) |
| return -EFAULT; |
| |
| switch (uh.opcode) { |
| case FUSE_INVALIDATE: |
| err = fuse_invalidate(fc, &uh); |
| break; |
| |
| default: |
| err = -ENOSYS; |
| } |
| return err; |
| } |
| |
| |
| static ssize_t fuse_dev_write(struct file *file, const char *buf, |
| size_t nbytes, loff_t *off) |
| { |
| int err; |
| struct fuse_conn *fc = DEV_FC(file); |
| struct fuse_req *req; |
| struct fuse_out_header oh; |
| |
| if (!fc->sb) |
| return -EPERM; |
| |
| err = copy_out_header(&oh, buf, nbytes); |
| if (err) |
| return err; |
| |
| if (!oh.unique) { |
| err = fuse_user_request(fc, buf, nbytes); |
| goto out; |
| } |
| |
| if (oh.error <= -1000 || oh.error > 0) { |
| printk("fuse_dev_write: bad error value\n"); |
| return -EINVAL; |
| } |
| |
| spin_lock(&fuse_lock); |
| req = request_find(fc, oh.unique); |
| if (req != NULL) { |
| list_del_init(&req->list); |
| req->locked = 1; |
| } |
| spin_unlock(&fuse_lock); |
| if (!req) |
| return -ENOENT; |
| |
| req->out.h = oh; |
| err = copy_out_args(req, buf, nbytes); |
| |
| spin_lock(&fuse_lock); |
| if (err) |
| req->out.h.error = -EPROTO; |
| else { |
| /* fget() needs to be done in this context */ |
| if (req->in.h.opcode == FUSE_GETDIR && !oh.error) |
| process_getdir(req); |
| } |
| req->finished = 1; |
| req->locked = 0; |
| /* Unlocks fuse_lock: */ |
| request_end(fc, req); |
| |
| out: |
| if (!err) |
| return nbytes; |
| else |
| return err; |
| } |
| |
| |
| static unsigned int fuse_dev_poll(struct file *file, poll_table *wait) |
| { |
| struct fuse_conn *fc = DEV_FC(file); |
| unsigned int mask = POLLOUT | POLLWRNORM; |
| |
| if (!fc->sb) |
| return -EPERM; |
| |
| poll_wait(file, &fc->waitq, wait); |
| |
| spin_lock(&fuse_lock); |
| if (!list_empty(&fc->pending)) |
| mask |= POLLIN | POLLRDNORM; |
| spin_unlock(&fuse_lock); |
| |
| return mask; |
| } |
| |
| static void free_conn(struct fuse_conn *fc) |
| { |
| while (!list_empty(&fc->unused_list)) { |
| struct fuse_req *req; |
| req = list_entry(fc->unused_list.next, struct fuse_req, list); |
| list_del(&req->list); |
| fuse_request_free(req); |
| } |
| kfree(fc); |
| } |
| |
| /* Must be called with the fuse lock held */ |
| void fuse_release_conn(struct fuse_conn *fc) |
| { |
| if (fc->sb == NULL && fc->file == NULL) { |
| free_conn(fc); |
| } |
| } |
| |
| static struct fuse_conn *new_conn(void) |
| { |
| struct fuse_conn *fc; |
| |
| fc = kmalloc(sizeof(*fc), GFP_KERNEL); |
| if (fc != NULL) { |
| int i; |
| memset(fc, 0, sizeof(*fc)); |
| fc->sb = NULL; |
| fc->file = NULL; |
| fc->flags = 0; |
| fc->uid = 0; |
| init_waitqueue_head(&fc->waitq); |
| INIT_LIST_HEAD(&fc->pending); |
| INIT_LIST_HEAD(&fc->processing); |
| INIT_LIST_HEAD(&fc->unused_list); |
| sema_init(&fc->unused_sem, MAX_OUTSTANDING); |
| for (i = 0; i < MAX_OUTSTANDING; i++) { |
| struct fuse_req *req = fuse_request_alloc(); |
| if (!req) { |
| free_conn(fc); |
| return NULL; |
| } |
| req->preallocated = 1; |
| list_add(&req->list, &fc->unused_list); |
| } |
| fc->reqctr = 1; |
| } |
| return fc; |
| } |
| |
| static int fuse_dev_open(struct inode *inode, struct file *file) |
| { |
| struct fuse_conn *fc; |
| |
| fc = new_conn(); |
| if (!fc) |
| return -ENOMEM; |
| |
| fc->file = file; |
| file->private_data = fc; |
| |
| return 0; |
| } |
| |
| static void end_requests(struct fuse_conn *fc, struct list_head *head) |
| { |
| while (!list_empty(head)) { |
| struct fuse_req *req; |
| req = list_entry(head->next, struct fuse_req, list); |
| list_del_init(&req->list); |
| if (req->issync) { |
| req->out.h.error = -ECONNABORTED; |
| req->finished = 1; |
| /* Unlocks fuse_lock: */ |
| request_end(fc, req); |
| spin_lock(&fuse_lock); |
| } else { |
| spin_unlock(&fuse_lock); |
| fuse_put_request(fc, req); |
| spin_lock(&fuse_lock); |
| } |
| } |
| } |
| |
| static int fuse_dev_release(struct inode *inode, struct file *file) |
| { |
| struct fuse_conn *fc = DEV_FC(file); |
| |
| spin_lock(&fuse_lock); |
| fc->file = NULL; |
| end_requests(fc, &fc->pending); |
| end_requests(fc, &fc->processing); |
| fuse_release_conn(fc); |
| spin_unlock(&fuse_lock); |
| return 0; |
| } |
| |
| static struct file_operations fuse_dev_operations = { |
| .owner = THIS_MODULE, |
| .read = fuse_dev_read, |
| .write = fuse_dev_write, |
| .poll = fuse_dev_poll, |
| .open = fuse_dev_open, |
| .release = fuse_dev_release, |
| }; |
| |
| static int read_version(char *page, char **start, off_t off, int count, |
| int *eof, void *data) |
| { |
| char *s = page; |
| s += sprintf(s, "%i.%i\n", FUSE_KERNEL_VERSION, |
| FUSE_KERNEL_MINOR_VERSION); |
| return s - page; |
| } |
| |
| int fuse_dev_init() |
| { |
| proc_fs_fuse = NULL; |
| proc_fuse_dev = NULL; |
| |
| fuse_req_cachep = kmem_cache_create("fuser_request", |
| sizeof(struct fuse_req), |
| 0, 0, NULL, NULL); |
| if (!fuse_req_cachep) |
| return -ENOMEM; |
| |
| proc_fs_fuse = proc_mkdir("fuse", proc_root_fs); |
| if (proc_fs_fuse) { |
| struct proc_dir_entry *de; |
| |
| proc_fs_fuse->owner = THIS_MODULE; |
| proc_fuse_dev = create_proc_entry("dev", S_IFSOCK | 0666, |
| proc_fs_fuse); |
| if (proc_fuse_dev) { |
| proc_fuse_dev->owner = THIS_MODULE; |
| proc_fuse_dev->proc_fops = &fuse_dev_operations; |
| } |
| de = create_proc_entry("version", S_IFREG | 0444, proc_fs_fuse); |
| if (de) { |
| de->owner = THIS_MODULE; |
| de->read_proc = read_version; |
| } |
| } |
| return 0; |
| } |
| |
| void fuse_dev_cleanup() |
| { |
| if (proc_fs_fuse) { |
| remove_proc_entry("dev", proc_fs_fuse); |
| remove_proc_entry("version", proc_fs_fuse); |
| remove_proc_entry("fuse", proc_root_fs); |
| } |
| |
| kmem_cache_destroy(fuse_req_cachep); |
| } |
| |
| /* |
| * Local Variables: |
| * indent-tabs-mode: t |
| * c-basic-offset: 8 |
| * End: |
| */ |