int fuse_lookup_name(struct super_block *sb, u64 nodeid, struct qstr *name, struct fuse_entry_out *outarg, struct inode **inode) { struct fuse_conn *fc = get_fuse_conn_super(sb); struct fuse_req *req; struct fuse_req *forget_req; u64 attr_version; int err; *inode = NULL; err = -ENAMETOOLONG; if (name->len > FUSE_NAME_MAX) goto out; FUSE_MIGHT_FREEZE(sb, "fuse_lookup_name"); req = fuse_get_req(fc); err = PTR_ERR(req); if (IS_ERR(req)) goto out; forget_req = fuse_get_req(fc); err = PTR_ERR(forget_req); if (IS_ERR(forget_req)) { fuse_put_request(fc, req); goto out; } attr_version = fuse_get_attr_version(fc); fuse_lookup_init(fc, req, nodeid, name, outarg); fuse_request_send(fc, req); err = req->out.h.error; fuse_put_request(fc, req); /* Zero nodeid is same as -ENOENT, but with valid timeout */ if (err || !outarg->nodeid) goto out_put_forget; err = -EIO; if (!outarg->nodeid) goto out_put_forget; if (!fuse_valid_type(outarg->attr.mode)) goto out_put_forget; *inode = fuse_iget(sb, outarg->nodeid, outarg->generation, &outarg->attr, entry_attr_timeout(outarg), attr_version); err = -ENOMEM; if (!*inode) { fuse_send_forget(fc, forget_req, outarg->nodeid, 1); goto out; } err = 0; out_put_forget: fuse_put_request(fc, forget_req); out: return err; }
int fuse_lookup_name(struct super_block *sb, u64 nodeid, struct qstr *name, struct fuse_entry_out *outarg, struct inode **inode) { struct fuse_conn *fc = get_fuse_conn_super(sb); struct fuse_req *req; struct fuse_forget_link *forget; u64 attr_version; int err; *inode = NULL; err = -ENAMETOOLONG; if (name->len > FUSE_NAME_MAX) goto out; req = fuse_get_req(fc); err = PTR_ERR(req); if (IS_ERR(req)) goto out; forget = fuse_alloc_forget(); err = -ENOMEM; if (!forget) { fuse_put_request(fc, req); goto out; } attr_version = fuse_get_attr_version(fc); fuse_lookup_init(fc, req, nodeid, name, outarg); fuse_request_send(fc, req); err = req->out.h.error; fuse_put_request(fc, req); if (err || !outarg->nodeid) goto out_put_forget; err = -EIO; if (!outarg->nodeid) goto out_put_forget; if (!fuse_valid_type(outarg->attr.mode)) goto out_put_forget; *inode = fuse_iget(sb, outarg->nodeid, outarg->generation, &outarg->attr, entry_attr_timeout(outarg), attr_version); err = -ENOMEM; if (!*inode) { fuse_queue_forget(fc, forget, outarg->nodeid, 1); goto out; } err = 0; out_put_forget: kfree(forget); out: return err; }
int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name, struct fuse_entry_out *outarg, struct inode **inode) { struct fuse_conn *fc = get_fuse_conn_super(sb); FUSE_ARGS(args); struct fuse_forget_link *forget; u64 attr_version; int err; *inode = NULL; err = -ENAMETOOLONG; if (name->len > FUSE_NAME_MAX) goto out; forget = fuse_alloc_forget(); err = -ENOMEM; if (!forget) goto out; attr_version = fuse_get_attr_version(fc); fuse_lookup_init(fc, &args, nodeid, name, outarg); err = fuse_simple_request(fc, &args); /* Zero nodeid is same as -ENOENT, but with valid timeout */ if (err || !outarg->nodeid) goto out_put_forget; err = -EIO; if (!outarg->nodeid) goto out_put_forget; if (!fuse_valid_type(outarg->attr.mode)) goto out_put_forget; *inode = fuse_iget(sb, outarg->nodeid, outarg->generation, &outarg->attr, entry_attr_timeout(outarg), attr_version); err = -ENOMEM; if (!*inode) { fuse_queue_forget(fc, forget, outarg->nodeid, 1); goto out; } err = 0; out_put_forget: kfree(forget); out: return err; }
static int fuse_direntplus_link(struct file *file, struct fuse_direntplus *direntplus, u64 attr_version) { int err; struct fuse_entry_out *o = &direntplus->entry_out; struct fuse_dirent *dirent = &direntplus->dirent; struct dentry *parent = file->f_path.dentry; struct qstr name = QSTR_INIT(dirent->name, dirent->namelen); struct dentry *dentry; struct dentry *alias; struct inode *dir = parent->d_inode; struct fuse_conn *fc; struct inode *inode; if (!o->nodeid) { /* * Unlike in the case of fuse_lookup, zero nodeid does not mean * ENOENT. Instead, it only means the userspace filesystem did * not want to return attributes/handle for this entry. * * So do nothing. */ return 0; } if (name.name[0] == '.') { /* * We could potentially refresh the attributes of the directory * and its parent? */ if (name.len == 1) return 0; if (name.name[1] == '.' && name.len == 2) return 0; } if (invalid_nodeid(o->nodeid)) return -EIO; if (!fuse_valid_type(o->attr.mode)) return -EIO; fc = get_fuse_conn(dir); name.hash = full_name_hash(name.name, name.len); dentry = d_lookup(parent, &name); if (dentry) { inode = dentry->d_inode; if (!inode) { d_drop(dentry); } else if (get_node_id(inode) != o->nodeid || ((o->attr.mode ^ inode->i_mode) & S_IFMT)) { err = d_invalidate(dentry); if (err) goto out; } else if (is_bad_inode(inode)) { err = -EIO; goto out; } else { struct fuse_inode *fi; fi = get_fuse_inode(inode); spin_lock(&fc->lock); fi->nlookup++; spin_unlock(&fc->lock); fuse_change_attributes(inode, &o->attr, entry_attr_timeout(o), attr_version); /* * The other branch to 'found' comes via fuse_iget() * which bumps nlookup inside */ goto found; } dput(dentry); } dentry = d_alloc(parent, &name); err = -ENOMEM; if (!dentry) goto out; inode = fuse_iget(dir->i_sb, o->nodeid, o->generation, &o->attr, entry_attr_timeout(o), attr_version); if (!inode) goto out; alias = fuse_materialise_dentry(dentry, inode); err = PTR_ERR(alias); if (IS_ERR(alias)) goto out; if (alias) { dput(dentry); dentry = alias; } found: fuse_change_entry_timeout(dentry, o); err = 0; out: dput(dentry); return err; }
static int fuse_direntplus_link(struct file *file, struct fuse_direntplus *direntplus, u64 attr_version) { struct fuse_entry_out *o = &direntplus->entry_out; struct fuse_dirent *dirent = &direntplus->dirent; struct dentry *parent = file->f_path.dentry; struct qstr name = QSTR_INIT(dirent->name, dirent->namelen); struct dentry *dentry; struct dentry *alias; struct inode *dir = d_inode(parent); struct fuse_conn *fc; struct inode *inode; DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wq); if (!o->nodeid) { /* * Unlike in the case of fuse_lookup, zero nodeid does not mean * ENOENT. Instead, it only means the userspace filesystem did * not want to return attributes/handle for this entry. * * So do nothing. */ return 0; } if (name.name[0] == '.') { /* * We could potentially refresh the attributes of the directory * and its parent? */ if (name.len == 1) return 0; if (name.name[1] == '.' && name.len == 2) return 0; } if (invalid_nodeid(o->nodeid)) return -EIO; if (!fuse_valid_type(o->attr.mode)) return -EIO; fc = get_fuse_conn(dir); name.hash = full_name_hash(parent, name.name, name.len); dentry = d_lookup(parent, &name); if (!dentry) { retry: dentry = d_alloc_parallel(parent, &name, &wq); if (IS_ERR(dentry)) return PTR_ERR(dentry); } if (!d_in_lookup(dentry)) { struct fuse_inode *fi; inode = d_inode(dentry); if (!inode || get_node_id(inode) != o->nodeid || ((o->attr.mode ^ inode->i_mode) & S_IFMT)) { d_invalidate(dentry); dput(dentry); goto retry; } if (is_bad_inode(inode)) { dput(dentry); return -EIO; } fi = get_fuse_inode(inode); spin_lock(&fc->lock); fi->nlookup++; spin_unlock(&fc->lock); forget_all_cached_acls(inode); fuse_change_attributes(inode, &o->attr, entry_attr_timeout(o), attr_version); /* * The other branch comes via fuse_iget() * which bumps nlookup inside */ } else { inode = fuse_iget(dir->i_sb, o->nodeid, o->generation, &o->attr, entry_attr_timeout(o), attr_version); if (!inode) inode = ERR_PTR(-ENOMEM); alias = d_splice_alias(inode, dentry); d_lookup_done(dentry); if (alias) { dput(dentry); dentry = alias; } if (IS_ERR(dentry)) return PTR_ERR(dentry); } if (fc->readdirplus_auto) set_bit(FUSE_I_INIT_RDPLUS, &get_fuse_inode(inode)->state); fuse_change_entry_timeout(dentry, o); dput(dentry); return 0; }
static struct dentry *fuse_lookup(struct _inode *dir, struct dentry *entry, struct nameidata *nd) { int err; struct fuse_entry_out outarg; struct inode *inode = NULL; struct _inode *_inode = NULL; struct fuse_conn *fc = get_fuse_conn(dir); struct fuse_req *req; struct fuse_req *forget_req; if (entry->d_name.len > FUSE_NAME_MAX) return ERR_PTR(-ENAMETOOLONG); req = fuse_get_req(fc); if (IS_ERR(req)) return ERR_PTR(PTR_ERR(req)); forget_req = fuse_get_req(fc); if (IS_ERR(forget_req)) { fuse_put_request(fc, req); return ERR_PTR(PTR_ERR(forget_req)); } fuse_lookup_init(req, dir, entry, &outarg); request_send(fc, req); err = req->out.h.error; fuse_put_request(fc, req); /* Zero nodeid is same as -ENOENT, but with valid timeout */ if (!err && outarg.nodeid && (invalid_nodeid(outarg.nodeid) || !fuse_valid_type(outarg.attr.mode))) err = -EIO; if (!err && outarg.nodeid) { inode = fuse_iget(dir->i_sb, outarg.nodeid, outarg.generation, &outarg.attr); if (!inode) { fuse_send_forget(fc, forget_req, outarg.nodeid, 1); return ERR_PTR(-ENOMEM); } } fuse_put_request(fc, forget_req); if (err && err != -ENOENT) return ERR_PTR(err); _inode = tx_cache_get_inode(inode); if (inode && S_ISDIR(_inode->i_mode)) { mutex_lock(&fc->inst_mutex); err = fuse_d_add_directory(entry, _inode); mutex_unlock(&fc->inst_mutex); if (err) { iput(inode); return ERR_PTR(err); } } else d_add(entry, _inode); entry->d_op = &fuse_dentry_operations; if (!err) fuse_change_timeout(entry, &outarg); else fuse_invalidate_entry_cache(entry); return NULL; }
static int parse_fuse_opt(char *opt, struct fuse_mount_data *d, int is_bdev) { char *p; memset(d, 0, sizeof(struct fuse_mount_data)); d->max_read = ~0; d->blksize = FUSE_DEFAULT_BLKSIZE; while ((p = strsep(&opt, ",")) != NULL) { int token; int value; substring_t args[MAX_OPT_ARGS]; if (!*p) continue; token = match_token(p, tokens, args); switch (token) { case OPT_FD: if (match_int(&args[0], &value)) return 0; d->fd = value; d->fd_present = 1; break; case OPT_ROOTMODE: if (match_octal(&args[0], &value)) return 0; if (!fuse_valid_type(value)) return 0; d->rootmode = value; d->rootmode_present = 1; break; case OPT_USER_ID: if (match_int(&args[0], &value)) return 0; d->user_id = value; d->user_id_present = 1; break; case OPT_GROUP_ID: if (match_int(&args[0], &value)) return 0; d->group_id = value; d->group_id_present = 1; break; case OPT_DEFAULT_PERMISSIONS: d->flags |= FUSE_DEFAULT_PERMISSIONS; break; case OPT_ALLOW_OTHER: d->flags |= FUSE_ALLOW_OTHER; break; case OPT_MAX_READ: if (match_int(&args[0], &value)) return 0; d->max_read = value; break; case OPT_BLKSIZE: if (!is_bdev || match_int(&args[0], &value)) return 0; d->blksize = value; break; default: return 0; } } if (!d->fd_present || !d->rootmode_present || !d->user_id_present || !d->group_id_present) return 0; return 1; }
static int fuse_direntplus_link(struct file *file, struct fuse_direntplus *direntplus, u64 attr_version) { int err; struct fuse_entry_out *o = &direntplus->entry_out; struct fuse_dirent *dirent = &direntplus->dirent; struct dentry *parent = file->f_path.dentry; struct qstr name = { .len = dirent->namelen, .name = dirent->name }; struct dentry *dentry; struct dentry *alias; struct inode *dir = parent->d_inode; struct fuse_conn *fc; struct inode *inode; if (!o->nodeid) { /* * Unlike in the case of fuse_lookup, zero nodeid does not mean * ENOENT. Instead, it only means the userspace filesystem did * not want to return attributes/handle for this entry. * * So do nothing. */ return 0; } if (name.name[0] == '.') { /* * We could potentially refresh the attributes of the directory * and its parent? */ if (name.len == 1) return 0; if (name.name[1] == '.' && name.len == 2) return 0; } if (invalid_nodeid(o->nodeid)) return -EIO; if (!fuse_valid_type(o->attr.mode)) return -EIO; fc = get_fuse_conn(dir); name.hash = full_name_hash(name.name, name.len); dentry = d_lookup(parent, &name); if (dentry) { inode = dentry->d_inode; if (!inode) { d_drop(dentry); } else if (get_node_id(inode) != o->nodeid || ((o->attr.mode ^ inode->i_mode) & S_IFMT)) { err = d_invalidate(dentry); if (err) goto out; } else if (is_bad_inode(inode)) { err = -EIO; goto out; } else { struct fuse_inode *fi; fi = get_fuse_inode(inode); spin_lock(&fc->lock); fi->nlookup++; spin_unlock(&fc->lock); fuse_change_attributes(inode, &o->attr, entry_attr_timeout(o), attr_version); /* * The other branch to 'found' comes via fuse_iget() * which bumps nlookup inside */ goto found; } dput(dentry); } dentry = d_alloc(parent, &name); err = -ENOMEM; if (!dentry) goto out; dentry->d_op = &fuse_dentry_operations; inode = fuse_iget(dir->i_sb, o->nodeid, o->generation, &o->attr, entry_attr_timeout(o), attr_version); if (!inode) goto out; alias = fuse_materialise_dentry(dentry, inode); err = PTR_ERR(alias); if (IS_ERR(alias)) goto out; if (alias) { dput(dentry); dentry = alias; } found: fuse_change_entry_timeout(dentry, o); err = 0; out: dput(dentry); return err; } static int parse_dirplusfile(char *buf, size_t nbytes, struct file *file, void *dstbuf, filldir_t filldir, u64 attr_version) { struct fuse_direntplus *direntplus; struct fuse_dirent *dirent; size_t reclen; int over = 0; int ret; while (nbytes >= FUSE_NAME_OFFSET_DIRENTPLUS) { direntplus = (struct fuse_direntplus *) buf; dirent = &direntplus->dirent; reclen = FUSE_DIRENTPLUS_SIZE(direntplus); if (!dirent->namelen || dirent->namelen > FUSE_NAME_MAX) return -EIO; if (reclen > nbytes) break; if (memchr(dirent->name, '/', dirent->namelen) != NULL) return -EIO; if (!over) { /* We fill entries into dstbuf only as much as it can hold. But we still continue iterating over remaining entries to link them. If not, we need to send a FORGET for each of those which we did not link. */ over = filldir(dstbuf, dirent->name, dirent->namelen, file->f_pos, dirent->ino, dirent->type); file->f_pos = dirent->off; } buf += reclen; nbytes -= reclen; ret = fuse_direntplus_link(file, direntplus, attr_version); if (ret) fuse_force_forget(file, direntplus->entry_out.nodeid); } return 0; } static int fuse_readdir(struct file *file, void *dstbuf, filldir_t filldir) { int plus, 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; u64 attr_version = 0; if (is_bad_inode(inode)) return -EIO; req = fuse_get_req(fc, 1); if (IS_ERR(req)) return PTR_ERR(req); page = alloc_page(GFP_KERNEL); if (!page) { fuse_put_request(fc, req); return -ENOMEM; } plus = fuse_use_readdirplus(inode, file); req->out.argpages = 1; req->num_pages = 1; req->pages[0] = page; req->page_descs[0].length = PAGE_SIZE; if (plus) { attr_version = fuse_get_attr_version(fc); fuse_read_fill(req, file, file->f_pos, PAGE_SIZE, FUSE_READDIRPLUS); } else { 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) { if (plus) { err = parse_dirplusfile(page_address(page), nbytes, file, dstbuf, filldir, attr_version); } else { err = parse_dirfile(page_address(page), nbytes, file, dstbuf, filldir); } } __free_page(page); fuse_invalidate_attr(inode); /* atime changed */ return err; }