fuse: implement NFS-like readdirplus support
[deliverable/linux.git] / fs / fuse / dir.c
index b7c09f9eb40cd7ac26f626a5bcadd26aeaf06ec5..dcc1e522c7d4aa44ecd51447ed16e2676f5f3876 100644 (file)
@@ -1155,6 +1155,143 @@ static int parse_dirfile(char *buf, size_t nbytes, struct file *file,
        return 0;
 }
 
+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;
+       }
+       fc = get_fuse_conn(dir);
+
+       name.hash = full_name_hash(name.name, name.len);
+       dentry = d_lookup(parent, &name);
+       if (dentry && dentry->d_inode) {
+               inode = dentry->d_inode;
+               if (get_node_id(inode) == o->nodeid) {
+                       struct fuse_inode *fi;
+                       fi = get_fuse_inode(inode);
+                       spin_lock(&fc->lock);
+                       fi->nlookup++;
+                       spin_unlock(&fc->lock);
+
+                       /*
+                        * The other branch to 'found' comes via fuse_iget()
+                        * which bumps nlookup inside
+                        */
+                       goto found;
+               }
+               err = d_invalidate(dentry);
+               if (err)
+                       goto out;
+               dput(dentry);
+               dentry = NULL;
+       }
+
+       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 = d_materialise_unique(dentry, inode);
+       err = PTR_ERR(alias);
+       if (IS_ERR(alias))
+               goto out;
+       if (alias) {
+               dput(dentry);
+               dentry = alias;
+       }
+
+found:
+       fuse_change_attributes(inode, &o->attr, entry_attr_timeout(o),
+                              attr_version);
+
+       fuse_change_entry_timeout(dentry, o);
+
+       err = 0;
+out:
+       if (dentry)
+               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 (!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 err;
@@ -1163,6 +1300,7 @@ static int fuse_readdir(struct file *file, void *dstbuf, filldir_t filldir)
        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;
@@ -1179,14 +1317,28 @@ static int fuse_readdir(struct file *file, void *dstbuf, filldir_t filldir)
        req->out.argpages = 1;
        req->num_pages = 1;
        req->pages[0] = page;
-       fuse_read_fill(req, file, file->f_pos, PAGE_SIZE, FUSE_READDIR);
+       if (fc->do_readdirplus) {
+               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)
-               err = parse_dirfile(page_address(page), nbytes, file, dstbuf,
-                                   filldir);
+       if (!err) {
+               if (fc->do_readdirplus) {
+                       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 */
This page took 0.02682 seconds and 5 git commands to generate.