static int fuse_do_getattr(struct inode *inode, struct kstat *stat, struct file *file) { int err; struct fuse_getattr_in inarg; struct fuse_attr_out outarg; struct fuse_conn *fc = get_fuse_conn(inode); struct fuse_req *req; u64 attr_version; req = fuse_get_req_nopages(fc); if (IS_ERR(req)) return PTR_ERR(req); attr_version = fuse_get_attr_version(fc); memset(&inarg, 0, sizeof(inarg)); memset(&outarg, 0, sizeof(outarg)); /* Directories have separate file-handle space */ if (file && S_ISREG(inode->i_mode)) { struct fuse_file *ff = file->private_data; inarg.getattr_flags |= FUSE_GETATTR_FH; inarg.fh = ff->fh; } req->in.h.opcode = FUSE_GETATTR; req->in.h.nodeid = get_node_id(inode); req->in.numargs = 1; req->in.args[0].size = sizeof(inarg); req->in.args[0].value = &inarg; req->out.numargs = 1; if (fc->minor < 9) req->out.args[0].size = FUSE_COMPAT_ATTR_OUT_SIZE; else req->out.args[0].size = sizeof(outarg); req->out.args[0].value = &outarg; fuse_request_send(fc, req); err = req->out.h.error; fuse_put_request(fc, req); if (!err) { if ((inode->i_mode ^ outarg.attr.mode) & S_IFMT) { make_bad_inode(inode); err = -EIO; } else { fuse_change_attributes(inode, &outarg.attr, attr_timeout(&outarg), attr_version); if (stat) fuse_fillattr(inode, &outarg.attr, stat); } } return err; }
static int fuse_rename(struct inode *olddir, struct dentry *oldent, struct inode *newdir, struct dentry *newent) { int err; struct fuse_rename_in inarg; struct fuse_conn *fc = get_fuse_conn(olddir); struct fuse_req *req = fuse_get_req(fc); if (IS_ERR(req)) return PTR_ERR(req); memset(&inarg, 0, sizeof(inarg)); inarg.newdir = get_node_id(newdir); req->in.h.opcode = FUSE_RENAME; req->in.h.nodeid = get_node_id(olddir); req->in.numargs = 3; req->in.args[0].size = sizeof(inarg); req->in.args[0].value = &inarg; req->in.args[1].size = oldent->d_name.len + 1; req->in.args[1].value = oldent->d_name.name; req->in.args[2].size = newent->d_name.len + 1; req->in.args[2].value = newent->d_name.name; fuse_request_send(fc, req); err = req->out.h.error; fuse_put_request(fc, req); if (!err) { /* */ fuse_invalidate_attr(oldent->d_inode); fuse_invalidate_attr(olddir); if (olddir != newdir) fuse_invalidate_attr(newdir); /* */ if (newent->d_inode) { fuse_invalidate_attr(newent->d_inode); fuse_invalidate_entry_cache(newent); } } else if (err == -EINTR) { /* */ fuse_invalidate_entry(oldent); if (newent->d_inode) fuse_invalidate_entry(newent); } return err; }
static int fuse_rename(struct inode *olddir, struct dentry *oldent, struct inode *newdir, struct dentry *newent) { int err; struct fuse_rename_in inarg; struct fuse_conn *fc = get_fuse_conn(olddir); struct fuse_req *req = fuse_get_req_nopages(fc); if (IS_ERR(req)) return PTR_ERR(req); memset(&inarg, 0, sizeof(inarg)); inarg.newdir = get_node_id(newdir); req->in.h.opcode = FUSE_RENAME; req->in.h.nodeid = get_node_id(olddir); req->in.numargs = 3; req->in.args[0].size = sizeof(inarg); req->in.args[0].value = &inarg; req->in.args[1].size = oldent->d_name.len + 1; req->in.args[1].value = oldent->d_name.name; req->in.args[2].size = newent->d_name.len + 1; req->in.args[2].value = newent->d_name.name; fuse_request_send(fc, req); err = req->out.h.error; fuse_put_request(fc, req); if (!err) { /* ctime changes */ fuse_invalidate_attr(oldent->d_inode); fuse_invalidate_attr(olddir); if (olddir != newdir) fuse_invalidate_attr(newdir); /* newent will end up negative */ if (newent->d_inode) { fuse_invalidate_attr(newent->d_inode); fuse_invalidate_entry_cache(newent); } } else if (err == -EINTR) { /* If request was interrupted, DEITY only knows if the rename actually took place. If the invalidation fails (e.g. some process has CWD under the renamed directory), then there can be inconsistency between the dcache and the real filesystem. Tough luck. */ fuse_invalidate_entry(oldent); if (newent->d_inode) fuse_invalidate_entry(newent); } return err; }
static ssize_t fuse_getxattr(struct dentry *entry, const char *name, void *value, size_t size) { struct inode *inode = entry->d_inode; struct fuse_conn *fc = get_fuse_conn(inode); struct fuse_req *req; struct fuse_getxattr_in inarg; struct fuse_getxattr_out outarg; ssize_t ret; if (fc->no_getxattr) return -EOPNOTSUPP; req = fuse_get_request(fc); if (!req) return -EINTR; memset(&inarg, 0, sizeof(inarg)); inarg.size = size; req->in.h.opcode = FUSE_GETXATTR; req->in.h.nodeid = get_node_id(inode); req->inode = inode; req->in.numargs = 2; req->in.args[0].size = sizeof(inarg); req->in.args[0].value = &inarg; req->in.args[1].size = strlen(name) + 1; req->in.args[1].value = name; /* This is really two different operations rolled into one */ req->out.numargs = 1; if (size) { req->out.argvar = 1; req->out.args[0].size = size; req->out.args[0].value = value; } else { req->out.args[0].size = sizeof(outarg); req->out.args[0].value = &outarg; } request_send(fc, req); ret = req->out.h.error; if (!ret) ret = size ? req->out.args[0].size : outarg.size; else { if (ret == -ENOSYS) { fc->no_getxattr = 1; ret = -EOPNOTSUPP; } } fuse_put_request(fc, req); return ret; }
/* * Get the canonical path. Since we must translate to a path, this must be done * in the context of the userspace daemon, however, the userspace daemon cannot * look up paths on its own. Instead, we handle the lookup as a special case * inside of the write request. */ static void fuse_dentry_canonical_path(const struct path *path, struct path *canonical_path) { struct inode *inode = path->dentry->d_inode; struct fuse_conn *fc = get_fuse_conn(inode); struct fuse_req *req; int err; char *path_name; req = fuse_get_req(fc); err = PTR_ERR(req); if (IS_ERR(req)) goto default_path; path_name = (char*)__get_free_page(GFP_KERNEL); if (!path_name) { fuse_put_request(fc, req); goto default_path; } req->in.h.opcode = FUSE_CANONICAL_PATH; req->in.h.nodeid = get_node_id(inode); req->in.numargs = 0; req->out.numargs = 1; req->out.args[0].size = PATH_MAX; req->out.args[0].value = path_name; req->canonical_path = canonical_path; req->out.argvar = 1; fuse_request_send(fc, req); err = req->out.h.error; fuse_put_request(fc, req); free_page((unsigned long)path_name); if (!err) return; default_path: canonical_path->dentry = path->dentry; canonical_path->mnt = path->mnt; path_get(canonical_path); }
static int fuse_readdir(struct file *file, void *dstbuf, filldir_t filldir) { int err; size_t nbytes; struct page *page; struct inode *inode = file->f_path.dentry->d_inode; struct fuse_conn *fc = get_fuse_conn(inode); struct fuse_req *req; if (is_bad_inode(inode)) return -EIO; req = fuse_get_req(fc); if (IS_ERR(req)) return PTR_ERR(req); page = alloc_page(GFP_KERNEL); if (!page) { fuse_put_request(fc, req); return -ENOMEM; } req->out.argpages = 1; req->num_pages = 1; req->pages[0] = page; fuse_read_fill(req, file, file->f_pos, PAGE_SIZE, FUSE_READDIR); fuse_request_send(fc, req); nbytes = req->out.args[0].size; err = req->out.h.error; fuse_put_request(fc, req); if (!err) err = parse_dirfile(page_address(page), nbytes, file, dstbuf, filldir); __free_page(page); fuse_invalidate_attr(inode); /* atime changed */ return err; }
static ssize_t fuse_listxattr(struct dentry *entry, char *list, size_t size) { struct inode *inode = entry->d_inode; struct fuse_conn *fc = get_fuse_conn(inode); struct fuse_req *req; struct fuse_getxattr_in inarg; struct fuse_getxattr_out outarg; ssize_t ret; if (!fuse_allow_current_process(fc)) return -EACCES; if (fc->no_listxattr) return -EOPNOTSUPP; req = fuse_get_req_nopages(fc); if (IS_ERR(req)) return PTR_ERR(req); memset(&inarg, 0, sizeof(inarg)); inarg.size = size; req->in.h.opcode = FUSE_LISTXATTR; req->in.h.nodeid = get_node_id(inode); req->in.numargs = 1; req->in.args[0].size = sizeof(inarg); req->in.args[0].value = &inarg; /* This is really two different operations rolled into one */ req->out.numargs = 1; if (size) { req->out.argvar = 1; req->out.args[0].size = size; req->out.args[0].value = list; } else { req->out.args[0].size = sizeof(outarg); req->out.args[0].value = &outarg; } fuse_request_send(fc, req); ret = req->out.h.error; if (!ret) ret = size ? req->out.args[0].size : outarg.size; else { if (ret == -ENOSYS) { fc->no_listxattr = 1; ret = -EOPNOTSUPP; } } fuse_put_request(fc, req); return ret; }
static void fuse_readpages_end(struct fuse_conn *fc, struct fuse_req *req) { int i; fuse_invalidate_attr(req->pages[0]->mapping->host); /* atime changed */ for (i = 0; i < req->num_pages; i++) { struct page *page = req->pages[i]; if (!req->out.h.error) SetPageUptodate(page); else SetPageError(page); unlock_page(page); } fuse_put_request(fc, req); }
static int fuse_lsof(struct inode *dir, struct dentry *entry) { struct fuse_conn *fc = get_fuse_conn(dir); struct fuse_req *req = fuse_get_req(fc); if (IS_ERR(req)) return PTR_ERR(req); req->in.h.opcode = FUSE_LSOF; req->in.h.nodeid = get_node_id(dir); req->in.numargs = 1; req->in.args[0].size = entry->d_name.len + 1; req->in.args[0].value = entry->d_name.name; fuse_request_send(fc, req); fuse_put_request(fc, req); return 0; }
/* * Check whether the dentry is still valid * * If the entry validity timeout has expired and the dentry is * positive, try to redo the lookup. If the lookup results in a * different inode, then let the VFS invalidate the dentry and redo * the lookup once more. If the lookup results in the same inode, * then refresh the attributes, timeouts and mark the dentry valid. */ static int fuse_dentry_revalidate(struct dentry *entry, struct nameidata *nd) { struct inode *inode = entry->d_inode; if (inode && is_bad_inode(inode)) return 0; else if (time_after(jiffies, entry->d_time)) { int err; struct fuse_entry_out outarg; struct fuse_conn *fc; struct fuse_req *req; /* Doesn't hurt to "reset" the validity timeout */ fuse_invalidate_entry_cache(entry); if (!inode) return 0; fc = get_fuse_conn(inode); req = fuse_get_request(fc); if (!req) return 0; fuse_lookup_init(req, entry->d_parent->d_inode, entry, &outarg); request_send(fc, req); err = req->out.h.error; /* Zero nodeid is same as -ENOENT */ if (!err && !outarg.nodeid) err = -ENOENT; if (!err) { struct fuse_inode *fi = get_fuse_inode(inode); if (outarg.nodeid != get_node_id(inode)) { fuse_send_forget(fc, req, outarg.nodeid, 1); return 0; } fi->nlookup ++; } fuse_put_request(fc, req); if (err || (outarg.attr.mode ^ inode->i_mode) & S_IFMT) return 0; fuse_change_attributes(inode, &outarg.attr); fuse_change_timeout(entry, &outarg); } return 1; }
int fuse_fsync_common(struct file *file, struct dentry *de, int datasync, int isdir) { struct inode *inode = de->d_inode; struct fuse_conn *fc = get_fuse_conn(inode); struct fuse_file *ff = file->private_data; struct fuse_req *req; struct fuse_fsync_in inarg; int err; if (is_bad_inode(inode)) return -EIO; if ((!isdir && fc->no_fsync) || (isdir && fc->no_fsyncdir)) return 0; req = fuse_get_request(fc); if (!req) return -EINTR; memset(&inarg, 0, sizeof(inarg)); inarg.fh = ff->fh; inarg.fsync_flags = datasync ? 1 : 0; req->in.h.opcode = isdir ? FUSE_FSYNCDIR : FUSE_FSYNC; req->in.h.nodeid = get_node_id(inode); req->inode = inode; req->file = file; req->in.numargs = 1; req->in.args[0].size = sizeof(inarg); req->in.args[0].value = &inarg; request_send(fc, req); err = req->out.h.error; fuse_put_request(fc, req); if (err == -ENOSYS) { if (isdir) fc->no_fsyncdir = 1; else fc->no_fsync = 1; err = 0; } return err; }
static int fuse_commit_write(struct file *file, struct page *page, unsigned offset, unsigned to) { int err; size_t nres; unsigned count = to - offset; struct inode *inode = page->mapping->host; struct fuse_conn *fc = get_fuse_conn(inode); loff_t pos = page_offset(page) + offset; struct fuse_req *req; if (is_bad_inode(inode)) return -EIO; req = fuse_get_request(fc); if (!req) return -EINTR; req->num_pages = 1; req->pages[0] = page; req->page_offset = offset; nres = fuse_send_write(req, file, inode, pos, count); err = req->out.h.error; fuse_put_request(fc, req); if (!err && nres != count) err = -EIO; if (!err) { pos += count; if (pos > i_size_read(inode)) i_size_write(inode, pos); if (offset == 0 && to == PAGE_CACHE_SIZE) { clear_page_dirty(page); SetPageUptodate(page); } } fuse_invalidate_attr(inode); return err; }
static void process_init_reply(struct fuse_conn *fc, struct fuse_req *req) { struct fuse_init_out *arg = &req->misc.init_out; if (req->out.h.error || arg->major != FUSE_KERNEL_VERSION) fc->conn_error = 1; else { unsigned long ra_pages; if (arg->minor >= 6) { ra_pages = arg->max_readahead / PAGE_CACHE_SIZE; if (arg->flags & FUSE_ASYNC_READ) fc->async_read = 1; if (!(arg->flags & FUSE_POSIX_LOCKS)) fc->no_lock = 1; if (arg->flags & FUSE_ATOMIC_O_TRUNC) fc->atomic_o_trunc = 1; if (arg->minor >= 9) { /* LOOKUP has dependency on proto version */ if (arg->flags & FUSE_EXPORT_SUPPORT) fc->export_support = 1; } if (arg->flags & FUSE_BIG_WRITES) fc->big_writes = 1; } else { ra_pages = fc->max_read / PAGE_CACHE_SIZE; fc->no_lock = 1; } fc->bdi.ra_pages = min(fc->bdi.ra_pages, ra_pages); fc->minor = arg->minor; fc->max_write = arg->minor < 5 ? 4096 : arg->max_write; fc->max_write = max_t(unsigned, 4096, fc->max_write); fc->conn_init = 1; } fuse_put_request(fc, req); fc->blocked = 0; wake_up_all(&fc->blocked_waitq); }
static int fuse_setxattr(struct dentry *entry, const char *name, const void *value, size_t size, int flags) { struct inode *inode = entry->d_inode; struct fuse_conn *fc = get_fuse_conn(inode); struct fuse_req *req; struct fuse_setxattr_in inarg; int err; if (fc->no_setxattr) return -EOPNOTSUPP; req = fuse_get_req_nopages(fc); if (IS_ERR(req)) return PTR_ERR(req); memset(&inarg, 0, sizeof(inarg)); inarg.size = size; inarg.flags = flags; req->in.h.opcode = FUSE_SETXATTR; req->in.h.nodeid = get_node_id(inode); req->in.numargs = 3; req->in.args[0].size = sizeof(inarg); req->in.args[0].value = &inarg; req->in.args[1].size = strlen(name) + 1; req->in.args[1].value = name; req->in.args[2].size = size; req->in.args[2].value = value; fuse_request_send(fc, req); err = req->out.h.error; fuse_put_request(fc, req); if (err == -ENOSYS) { fc->no_setxattr = 1; err = -EOPNOTSUPP; } if (!err) fuse_invalidate_attr(inode); return err; }
static int fuse_unlink(struct inode *dir, struct dentry *entry) { int err; struct fuse_conn *fc = get_fuse_conn(dir); struct fuse_req *req = fuse_get_req_nopages(fc); if (IS_ERR(req)) return PTR_ERR(req); req->in.h.opcode = FUSE_UNLINK; req->in.h.nodeid = get_node_id(dir); req->in.numargs = 1; req->in.args[0].size = entry->d_name.len + 1; req->in.args[0].value = entry->d_name.name; fuse_request_send(fc, req); err = req->out.h.error; fuse_put_request(fc, req); if (!err) { struct inode *inode = entry->d_inode; struct fuse_inode *fi = get_fuse_inode(inode); spin_lock(&fc->lock); fi->attr_version = ++fc->attr_version; /* * If i_nlink == 0 then unlink doesn't make sense, yet this can * happen if userspace filesystem is careless. It would be * difficult to enforce correct nlink usage so just ignore this * condition here */ if (inode->i_nlink > 0) drop_nlink(inode); spin_unlock(&fc->lock); fuse_invalidate_attr(inode); fuse_invalidate_attr(dir); fuse_invalidate_entry_cache(entry); } else if (err == -EINTR) fuse_invalidate_entry(entry); return err; }
static int fuse_flush(struct file *file) { struct inode *inode = file->f_dentry->d_inode; struct fuse_conn *fc = get_fuse_conn(inode); struct fuse_file *ff = file->private_data; struct fuse_req *req; struct fuse_flush_in inarg; int err; if (is_bad_inode(inode)) return -EIO; if (fc->no_flush) return 0; req = fuse_get_request(fc); if (!req) return -EINTR; memset(&inarg, 0, sizeof(inarg)); inarg.fh = ff->fh; req->in.h.opcode = FUSE_FLUSH; req->in.h.nodeid = get_node_id(inode); req->inode = inode; req->file = file; req->in.numargs = 1; req->in.args[0].size = sizeof(inarg); req->in.args[0].value = &inarg; request_send(fc, req); err = req->out.h.error; fuse_put_request(fc, req); if (err == -ENOSYS) { fc->no_flush = 1; err = 0; } return err; }
static int fuse_readlink_page(struct inode *inode, struct page *page) { struct fuse_conn *fc = get_fuse_conn(inode); struct fuse_req *req; int err; req = fuse_get_req(fc, 1); if (IS_ERR(req)) return PTR_ERR(req); req->out.page_zeroing = 1; req->out.argpages = 1; req->num_pages = 1; req->pages[0] = page; req->page_descs[0].length = PAGE_SIZE - 1; req->in.h.opcode = FUSE_READLINK; req->in.h.nodeid = get_node_id(inode); req->out.argvar = 1; req->out.numargs = 1; req->out.args[0].size = PAGE_SIZE - 1; fuse_request_send(fc, req); err = req->out.h.error; if (!err) { char *link = page_address(page); size_t len = req->out.args[0].size; BUG_ON(len >= PAGE_SIZE); link[len] = '\0'; } fuse_put_request(fc, req); fuse_invalidate_atime(inode); return err; }
static int fuse_unlink(struct inode *dir, struct dentry *entry) { int err; struct fuse_conn *fc = get_fuse_conn(dir); struct fuse_req *req; FUSE_MIGHT_FREEZE(dir->i_sb, "fuse_unlink"); req = fuse_get_req(fc); if (IS_ERR(req)) return PTR_ERR(req); req->in.h.opcode = FUSE_UNLINK; req->in.h.nodeid = get_node_id(dir); req->in.numargs = 1; req->in.args[0].size = entry->d_name.len + 1; req->in.args[0].value = entry->d_name.name; fuse_request_send(fc, req); err = req->out.h.error; fuse_put_request(fc, req); if (!err) { struct inode *inode = entry->d_inode; /* * Set nlink to zero so the inode can be cleared, if the inode * does have more links this will be discovered at the next * lookup/getattr. */ clear_nlink(inode); fuse_invalidate_attr(inode); fuse_invalidate_attr(dir); fuse_invalidate_entry_cache(entry); } else if (err == -EINTR) fuse_invalidate_entry(entry); return err; }
int fuse_do_getattr(struct inode *inode) { int err; struct fuse_attr_out arg; struct fuse_conn *fc = get_fuse_conn(inode); struct fuse_req *req = fuse_get_request(fc); if (!req) return -EINTR; req->in.h.opcode = FUSE_GETATTR; req->in.h.nodeid = get_node_id(inode); req->inode = inode; req->out.numargs = 1; req->out.args[0].size = sizeof(arg); req->out.args[0].value = &arg; request_send(fc, req); err = req->out.h.error; fuse_put_request(fc, req); if (!err) { if ((inode->i_mode ^ arg.attr.mode) & S_IFMT) { #ifndef KERNEL_2_6_12_PLUS if (get_node_id(inode) != FUSE_ROOT_ID) make_bad_inode(inode); #else make_bad_inode(inode); #endif err = -EIO; } else { struct fuse_inode *fi = get_fuse_inode(inode); fuse_change_attributes(inode, &arg.attr); fi->i_time = time_to_jiffies(arg.attr_valid, arg.attr_valid_nsec); } } return err; }
static int fuse_rmdir(struct inode *dir, struct dentry *entry) { int err; struct fuse_conn *fc = get_fuse_conn(dir); struct fuse_req *req = fuse_get_req_nopages(fc); if (IS_ERR(req)) return PTR_ERR(req); req->in.h.opcode = FUSE_RMDIR; req->in.h.nodeid = get_node_id(dir); req->in.numargs = 1; req->in.args[0].size = entry->d_name.len + 1; req->in.args[0].value = entry->d_name.name; fuse_request_send(fc, req); err = req->out.h.error; fuse_put_request(fc, req); if (!err) { clear_nlink(entry->d_inode); fuse_invalidate_attr(dir); fuse_invalidate_entry_cache(entry); } else if (err == -EINTR) fuse_invalidate_entry(entry); return err; }
/* * Code shared between mknod, mkdir, symlink and link */ static int create_new_entry(struct fuse_conn *fc, struct fuse_req *req, struct _inode *dir, struct dentry *entry, int mode) { struct fuse_entry_out outarg; struct inode *inode; struct _inode *_inode; int err; struct fuse_req *forget_req; forget_req = fuse_get_req(fc); if (IS_ERR(forget_req)) { fuse_put_request(fc, req); return PTR_ERR(forget_req); } req->in.h.nodeid = _get_node_id(dir); req->out.numargs = 1; req->out.args[0].size = sizeof(outarg); req->out.args[0].value = &outarg; request_send(fc, req); err = req->out.h.error; fuse_put_request(fc, req); if (err) goto out_put_forget_req; err = -EIO; if (invalid_nodeid(outarg.nodeid)) goto out_put_forget_req; if ((outarg.attr.mode ^ mode) & S_IFMT) goto out_put_forget_req; inode = fuse_iget(dir->i_sb, outarg.nodeid, outarg.generation, &outarg.attr); if (!inode) { fuse_send_forget(fc, forget_req, outarg.nodeid, 1); return -ENOMEM; } fuse_put_request(fc, forget_req); _inode = tx_cache_get_inode(inode); if (S_ISDIR(_inode->i_mode)) { struct dentry *alias; mutex_lock(&fc->inst_mutex); alias = d_find_alias(_inode); if (alias) { /* New directory must have moved since mkdir */ mutex_unlock(&fc->inst_mutex); dput(alias); iput(inode); return -EBUSY; } d_instantiate(entry, _inode); mutex_unlock(&fc->inst_mutex); } else d_instantiate(entry, _inode); fuse_change_timeout(entry, &outarg); fuse_invalidate_attr(dir); return 0; out_put_forget_req: fuse_put_request(fc, forget_req); return err; }
/* * Atomic create+open operation * * If the filesystem doesn't support this, then fall back to separate * 'mknod' + 'open' requests. */ static int fuse_create_open(struct inode *dir, struct dentry *entry, struct file *file, unsigned flags, umode_t mode, int *opened) { int err; struct inode *inode; struct fuse_conn *fc = get_fuse_conn(dir); struct fuse_req *req; struct fuse_forget_link *forget; struct fuse_create_in inarg; struct fuse_open_out outopen; struct fuse_entry_out outentry; struct fuse_file *ff; /* Userspace expects S_IFREG in create mode */ BUG_ON((mode & S_IFMT) != S_IFREG); forget = fuse_alloc_forget(); err = -ENOMEM; if (!forget) goto out_err; req = fuse_get_req_nopages(fc); err = PTR_ERR(req); if (IS_ERR(req)) goto out_put_forget_req; err = -ENOMEM; ff = fuse_file_alloc(fc); if (!ff) goto out_put_request; if (!fc->dont_mask) mode &= ~current_umask(); flags &= ~O_NOCTTY; memset(&inarg, 0, sizeof(inarg)); memset(&outentry, 0, sizeof(outentry)); inarg.flags = flags; inarg.mode = mode; inarg.umask = current_umask(); req->in.h.opcode = FUSE_CREATE; req->in.h.nodeid = get_node_id(dir); req->in.numargs = 2; req->in.args[0].size = fc->minor < 12 ? sizeof(struct fuse_open_in) : sizeof(inarg); req->in.args[0].value = &inarg; req->in.args[1].size = entry->d_name.len + 1; req->in.args[1].value = entry->d_name.name; req->out.numargs = 2; if (fc->minor < 9) req->out.args[0].size = FUSE_COMPAT_ENTRY_OUT_SIZE; else req->out.args[0].size = sizeof(outentry); req->out.args[0].value = &outentry; req->out.args[1].size = sizeof(outopen); req->out.args[1].value = &outopen; fuse_request_send(fc, req); err = req->out.h.error; if (err) goto out_free_ff; err = -EIO; if (!S_ISREG(outentry.attr.mode) || invalid_nodeid(outentry.nodeid)) goto out_free_ff; fuse_put_request(fc, req); ff->fh = outopen.fh; ff->nodeid = outentry.nodeid; ff->open_flags = outopen.open_flags; inode = fuse_iget(dir->i_sb, outentry.nodeid, outentry.generation, &outentry.attr, entry_attr_timeout(&outentry), 0); if (!inode) { flags &= ~(O_CREAT | O_EXCL | O_TRUNC); fuse_sync_release(ff, flags); fuse_queue_forget(fc, forget, outentry.nodeid, 1); err = -ENOMEM; goto out_err; } kfree(forget); d_instantiate(entry, inode); fuse_change_entry_timeout(entry, &outentry); fuse_invalidate_attr(dir); err = finish_open(file, entry, generic_file_open, opened); if (err) { fuse_sync_release(ff, flags); } else { file->private_data = fuse_file_get(ff); fuse_finish_open(inode, file); } return err; out_free_ff: fuse_file_free(ff); out_put_request: fuse_put_request(fc, req); out_put_forget_req: kfree(forget); out_err: return err; }
/* * Code shared between mknod, mkdir, symlink and link */ static int create_new_entry(struct fuse_conn *fc, struct fuse_req *req, struct inode *dir, struct dentry *entry, umode_t mode) { struct fuse_entry_out outarg; struct inode *inode; int err; struct fuse_forget_link *forget; forget = fuse_alloc_forget(); if (!forget) { fuse_put_request(fc, req); return -ENOMEM; } memset(&outarg, 0, sizeof(outarg)); req->in.h.nodeid = get_node_id(dir); req->out.numargs = 1; if (fc->minor < 9) req->out.args[0].size = FUSE_COMPAT_ENTRY_OUT_SIZE; else req->out.args[0].size = sizeof(outarg); req->out.args[0].value = &outarg; fuse_request_send(fc, req); err = req->out.h.error; fuse_put_request(fc, req); if (err) goto out_put_forget_req; err = -EIO; if (invalid_nodeid(outarg.nodeid)) goto out_put_forget_req; if ((outarg.attr.mode ^ mode) & S_IFMT) goto out_put_forget_req; inode = fuse_iget(dir->i_sb, outarg.nodeid, outarg.generation, &outarg.attr, entry_attr_timeout(&outarg), 0); if (!inode) { fuse_queue_forget(fc, forget, outarg.nodeid, 1); return -ENOMEM; } kfree(forget); if (S_ISDIR(inode->i_mode)) { struct dentry *alias; mutex_lock(&fc->inst_mutex); alias = d_find_alias(inode); if (alias) { /* New directory must have moved since mkdir */ mutex_unlock(&fc->inst_mutex); dput(alias); iput(inode); return -EBUSY; } d_instantiate(entry, inode); mutex_unlock(&fc->inst_mutex); } else d_instantiate(entry, inode); fuse_change_entry_timeout(entry, &outarg); fuse_invalidate_attr(dir); return 0; out_put_forget_req: kfree(forget); return err; }
/* * Check whether the dentry is still valid * * If the entry validity timeout has expired and the dentry is * positive, try to redo the lookup. If the lookup results in a * different inode, then let the VFS invalidate the dentry and redo * the lookup once more. If the lookup results in the same inode, * then refresh the attributes, timeouts and mark the dentry valid. */ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags) { struct inode *inode; struct dentry *parent; struct fuse_conn *fc; int ret; inode = ACCESS_ONCE(entry->d_inode); if (inode && is_bad_inode(inode)) goto invalid; else if (fuse_dentry_time(entry) < get_jiffies_64()) { int err; struct fuse_entry_out outarg; struct fuse_req *req; struct fuse_forget_link *forget; u64 attr_version; /* For negative dentries, always do a fresh lookup */ if (!inode) goto invalid; ret = -ECHILD; if (flags & LOOKUP_RCU) goto out; fc = get_fuse_conn(inode); req = fuse_get_req_nopages(fc); ret = PTR_ERR(req); if (IS_ERR(req)) goto out; forget = fuse_alloc_forget(); if (!forget) { fuse_put_request(fc, req); ret = -ENOMEM; goto out; } attr_version = fuse_get_attr_version(fc); parent = dget_parent(entry); fuse_lookup_init(fc, req, get_node_id(parent->d_inode), &entry->d_name, &outarg); fuse_request_send(fc, req); dput(parent); err = req->out.h.error; fuse_put_request(fc, req); /* Zero nodeid is same as -ENOENT */ if (!err && !outarg.nodeid) err = -ENOENT; if (!err) { struct fuse_inode *fi = get_fuse_inode(inode); if (outarg.nodeid != get_node_id(inode)) { fuse_queue_forget(fc, forget, outarg.nodeid, 1); goto invalid; } spin_lock(&fc->lock); fi->nlookup++; spin_unlock(&fc->lock); } kfree(forget); if (err || (outarg.attr.mode ^ inode->i_mode) & S_IFMT) goto invalid; fuse_change_attributes(inode, &outarg.attr, entry_attr_timeout(&outarg), attr_version); fuse_change_entry_timeout(entry, &outarg); } else if (inode) { fc = get_fuse_conn(inode); if (fc->readdirplus_auto) { parent = dget_parent(entry); fuse_advise_use_readdirplus(parent->d_inode); dput(parent); } } ret = 1; out: return ret; invalid: ret = 0; if (check_submounts_and_drop(entry) != 0) ret = 1; goto out; }
static int fuse_do_setattr(struct dentry *entry, struct iattr *attr, struct file *file) { struct inode *inode = entry->d_inode; struct fuse_conn *fc = get_fuse_conn(inode); struct fuse_inode *fi = get_fuse_inode(inode); struct fuse_req *req; struct fuse_setattr_in inarg; struct fuse_attr_out outarg; bool is_truncate = false; loff_t oldsize; int err; if (!fuse_allow_task(fc, current)) return -EACCES; if (!(fc->flags & FUSE_DEFAULT_PERMISSIONS)) attr->ia_valid |= ATTR_FORCE; err = inode_change_ok(inode, attr); if (err) return err; if (attr->ia_valid & ATTR_OPEN) { if (fc->atomic_o_trunc) return 0; file = NULL; } if (attr->ia_valid & ATTR_SIZE) is_truncate = true; req = fuse_get_req(fc); if (IS_ERR(req)) return PTR_ERR(req); if (is_truncate) { fuse_set_nowrite(inode); set_bit(FUSE_I_SIZE_UNSTABLE, &fi->state); } memset(&inarg, 0, sizeof(inarg)); memset(&outarg, 0, sizeof(outarg)); iattr_to_fattr(attr, &inarg); if (file) { struct fuse_file *ff = file->private_data; inarg.valid |= FATTR_FH; inarg.fh = ff->fh; } if (attr->ia_valid & ATTR_SIZE) { inarg.valid |= FATTR_LOCKOWNER; inarg.lock_owner = fuse_lock_owner_id(fc, current->files); } req->in.h.opcode = FUSE_SETATTR; req->in.h.nodeid = get_node_id(inode); req->in.numargs = 1; req->in.args[0].size = sizeof(inarg); req->in.args[0].value = &inarg; req->out.numargs = 1; if (fc->minor < 9) req->out.args[0].size = FUSE_COMPAT_ATTR_OUT_SIZE; else req->out.args[0].size = sizeof(outarg); req->out.args[0].value = &outarg; fuse_request_send(fc, req); err = req->out.h.error; fuse_put_request(fc, req); if (err) { if (err == -EINTR) fuse_invalidate_attr(inode); goto error; } if ((inode->i_mode ^ outarg.attr.mode) & S_IFMT) { make_bad_inode(inode); err = -EIO; goto error; } spin_lock(&fc->lock); fuse_change_attributes_common(inode, &outarg.attr, attr_timeout(&outarg)); oldsize = inode->i_size; i_size_write(inode, outarg.attr.size); if (is_truncate) { __fuse_release_nowrite(inode); } spin_unlock(&fc->lock); if (S_ISREG(inode->i_mode) && oldsize != outarg.attr.size) { truncate_pagecache(inode, oldsize, outarg.attr.size); invalidate_inode_pages2(inode->i_mapping); } clear_bit(FUSE_I_SIZE_UNSTABLE, &fi->state); return 0; error: if (is_truncate) fuse_release_nowrite(inode); clear_bit(FUSE_I_SIZE_UNSTABLE, &fi->state); return err; }
/* * Set attributes, and at the same time refresh them. * * Truncation is slightly complicated, because the 'truncate' request * may fail, in which case we don't want to touch the mapping. * vmtruncate() doesn't allow for this case, so do the rlimit checking * and the actual truncation by hand. */ int fuse_do_setattr(struct inode *inode, struct iattr *attr, struct file *file) { struct fuse_conn *fc = get_fuse_conn(inode); struct fuse_req *req; struct fuse_setattr_in inarg; struct fuse_attr_out outarg; bool is_truncate = false; loff_t oldsize; int err; if (!(fc->flags & FUSE_DEFAULT_PERMISSIONS)) attr->ia_valid |= ATTR_FORCE; err = inode_change_ok(inode, attr); if (err) return err; if (attr->ia_valid & ATTR_OPEN) { if (fc->atomic_o_trunc) return 0; file = NULL; } if (attr->ia_valid & ATTR_SIZE) is_truncate = true; req = fuse_get_req_nopages(fc); if (IS_ERR(req)) return PTR_ERR(req); if (is_truncate) fuse_set_nowrite(inode); memset(&inarg, 0, sizeof(inarg)); memset(&outarg, 0, sizeof(outarg)); iattr_to_fattr(attr, &inarg); if (file) { struct fuse_file *ff = file->private_data; inarg.valid |= FATTR_FH; inarg.fh = ff->fh; } if (attr->ia_valid & ATTR_SIZE) { /* For mandatory locking in truncate */ inarg.valid |= FATTR_LOCKOWNER; inarg.lock_owner = fuse_lock_owner_id(fc, current->files); } req->in.h.opcode = FUSE_SETATTR; req->in.h.nodeid = get_node_id(inode); req->in.numargs = 1; req->in.args[0].size = sizeof(inarg); req->in.args[0].value = &inarg; req->out.numargs = 1; if (fc->minor < 9) req->out.args[0].size = FUSE_COMPAT_ATTR_OUT_SIZE; else req->out.args[0].size = sizeof(outarg); req->out.args[0].value = &outarg; fuse_request_send(fc, req); err = req->out.h.error; fuse_put_request(fc, req); if (err) { if (err == -EINTR) fuse_invalidate_attr(inode); goto error; } if ((inode->i_mode ^ outarg.attr.mode) & S_IFMT) { make_bad_inode(inode); err = -EIO; goto error; } spin_lock(&fc->lock); fuse_change_attributes_common(inode, &outarg.attr, attr_timeout(&outarg)); oldsize = inode->i_size; i_size_write(inode, outarg.attr.size); if (is_truncate) { /* NOTE: this may release/reacquire fc->lock */ __fuse_release_nowrite(inode); } spin_unlock(&fc->lock); /* * Only call invalidate_inode_pages2() after removing * FUSE_NOWRITE, otherwise fuse_launder_page() would deadlock. */ if (S_ISREG(inode->i_mode) && oldsize != outarg.attr.size) { truncate_pagecache(inode, oldsize, outarg.attr.size); invalidate_inode_pages2(inode->i_mapping); } return 0; error: if (is_truncate) fuse_release_nowrite(inode); return err; }
/* * Atomic create+open operation * * If the filesystem doesn't support this, then fall back to separate * 'mknod' + 'open' requests. */ static int fuse_create_open(struct inode *dir, struct dentry *entry, int mode, struct nameidata *nd) { int err; struct inode *inode; struct fuse_conn *fc = get_fuse_conn(dir); struct fuse_req *req; struct fuse_req *forget_req; struct fuse_create_in inarg; struct fuse_open_out outopen; struct fuse_entry_out outentry; struct fuse_file *ff; struct file *file; int flags = nd->intent.open.flags - 1; if (fc->no_create) return -ENOSYS; if (flags & O_DIRECT) return -EINVAL; forget_req = fuse_get_req(fc); if (IS_ERR(forget_req)) return PTR_ERR(forget_req); req = fuse_get_req(fc); err = PTR_ERR(req); if (IS_ERR(req)) goto out_put_forget_req; err = -ENOMEM; ff = fuse_file_alloc(fc); if (!ff) goto out_put_request; if (!fc->dont_mask) mode &= ~current_umask(); flags &= ~O_NOCTTY; memset(&inarg, 0, sizeof(inarg)); memset(&outentry, 0, sizeof(outentry)); inarg.flags = flags; inarg.mode = mode; inarg.umask = current_umask(); req->in.h.opcode = FUSE_CREATE; req->in.h.nodeid = get_node_id(dir); req->in.numargs = 2; req->in.args[0].size = fc->minor < 12 ? sizeof(struct fuse_open_in) : sizeof(inarg); req->in.args[0].value = &inarg; req->in.args[1].size = entry->d_name.len + 1; req->in.args[1].value = entry->d_name.name; req->out.numargs = 2; if (fc->minor < 9) req->out.args[0].size = FUSE_COMPAT_ENTRY_OUT_SIZE; else req->out.args[0].size = sizeof(outentry); req->out.args[0].value = &outentry; req->out.args[1].size = sizeof(outopen); req->out.args[1].value = &outopen; fuse_request_send(fc, req); err = req->out.h.error; if (err) { if (err == -ENOSYS) fc->no_create = 1; goto out_free_ff; } err = -EIO; if (!S_ISREG(outentry.attr.mode) || invalid_nodeid(outentry.nodeid)) goto out_free_ff; fuse_put_request(fc, req); ff->fh = outopen.fh; ff->nodeid = outentry.nodeid; ff->open_flags = outopen.open_flags; inode = fuse_iget(dir->i_sb, outentry.nodeid, outentry.generation, &outentry.attr, entry_attr_timeout(&outentry), 0); if (!inode) { flags &= ~(O_CREAT | O_EXCL | O_TRUNC); fuse_sync_release(ff, flags); fuse_send_forget(fc, forget_req, outentry.nodeid, 1); return -ENOMEM; } fuse_put_request(fc, forget_req); d_instantiate(entry, inode); fuse_change_entry_timeout(entry, &outentry); fuse_invalidate_attr(dir); file = lookup_instantiate_filp(nd, entry, generic_file_open); if (IS_ERR(file)) { fuse_sync_release(ff, flags); return PTR_ERR(file); } file->private_data = fuse_file_get(ff); fuse_finish_open(inode, file); return 0; out_free_ff: fuse_file_free(ff); out_put_request: fuse_put_request(fc, req); out_put_forget_req: fuse_put_request(fc, forget_req); return err; }
/* * Check whether the dentry is still valid * * If the entry validity timeout has expired and the dentry is * positive, try to redo the lookup. If the lookup results in a * different inode, then let the VFS invalidate the dentry and redo * the lookup once more. If the lookup results in the same inode, * then refresh the attributes, timeouts and mark the dentry valid. */ static int fuse_dentry_revalidate(struct dentry *entry, struct nameidata *nd) { struct inode *inode = entry->d_inode; if (inode && is_bad_inode(inode)) return 0; else if (fuse_dentry_time(entry) < get_jiffies_64()) { int err; struct fuse_entry_out outarg; struct fuse_conn *fc; struct fuse_req *req; struct fuse_req *forget_req; struct dentry *parent; u64 attr_version; /* For negative dentries, always do a fresh lookup */ if (!inode) return 0; fc = get_fuse_conn(inode); req = fuse_get_req(fc); if (IS_ERR(req)) return 0; forget_req = fuse_get_req(fc); if (IS_ERR(forget_req)) { fuse_put_request(fc, req); return 0; } attr_version = fuse_get_attr_version(fc); parent = dget_parent(entry); fuse_lookup_init(fc, req, get_node_id(parent->d_inode), &entry->d_name, &outarg); fuse_request_send(fc, req); dput(parent); err = req->out.h.error; fuse_put_request(fc, req); /* Zero nodeid is same as -ENOENT */ if (!err && !outarg.nodeid) err = -ENOENT; if (!err) { struct fuse_inode *fi = get_fuse_inode(inode); if (outarg.nodeid != get_node_id(inode)) { fuse_send_forget(fc, forget_req, outarg.nodeid, 1); return 0; } spin_lock(&fc->lock); fi->nlookup++; spin_unlock(&fc->lock); } fuse_put_request(fc, forget_req); if (err || (outarg.attr.mode ^ inode->i_mode) & S_IFMT) return 0; fuse_change_attributes(inode, &outarg.attr, entry_attr_timeout(&outarg), attr_version); fuse_change_entry_timeout(entry, &outarg); } return 1; }
static int fuse_dentry_revalidate(struct dentry *entry, struct nameidata *nd) { struct inode *inode; inode = ACCESS_ONCE(entry->d_inode); if (inode && is_bad_inode(inode)) return 0; else if (fuse_dentry_time(entry) < get_jiffies_64()) { int err; struct fuse_entry_out outarg; struct fuse_conn *fc; struct fuse_req *req; struct fuse_forget_link *forget; struct dentry *parent; u64 attr_version; if (!inode) return 0; if (nd && (nd->flags & LOOKUP_RCU)) return -ECHILD; fc = get_fuse_conn(inode); req = fuse_get_req(fc); if (IS_ERR(req)) return 0; forget = fuse_alloc_forget(); if (!forget) { fuse_put_request(fc, req); return 0; } attr_version = fuse_get_attr_version(fc); parent = dget_parent(entry); fuse_lookup_init(fc, req, get_node_id(parent->d_inode), &entry->d_name, &outarg); fuse_request_send(fc, req); dput(parent); err = req->out.h.error; fuse_put_request(fc, req); if (!err && !outarg.nodeid) err = -ENOENT; if (!err) { struct fuse_inode *fi = get_fuse_inode(inode); if (outarg.nodeid != get_node_id(inode)) { fuse_queue_forget(fc, forget, outarg.nodeid, 1); return 0; } spin_lock(&fc->lock); fi->nlookup++; spin_unlock(&fc->lock); } kfree(forget); if (err || (outarg.attr.mode ^ inode->i_mode) & S_IFMT) return 0; fuse_change_attributes(inode, &outarg.attr, entry_attr_timeout(&outarg), attr_version); fuse_change_entry_timeout(entry, &outarg); } return 1; }
static void fuse_file_put_inner_sync(struct fuse_conn *fc, struct fuse_req *req) { fuse_request_send(fc, req); path_put(&req->misc.release.path); fuse_put_request(fc, req); }