]> Pileus Git - ~andy/linux/blobdiff - fs/exportfs/expfs.c
exportfs: more detailed comment for path_reconnect
[~andy/linux] / fs / exportfs / expfs.c
index 293bc2e47a735807a75eaad424764315172367b6..87e6dca69e438efcf0f584540d2bcd8447154d0b 100644 (file)
@@ -93,7 +93,19 @@ find_disconnected_root(struct dentry *dentry)
 /*
  * Make sure target_dir is fully connected to the dentry tree.
  *
- * It may already be, as the flag isn't always updated when connection happens.
+ * On successful return, DCACHE_DISCONNECTED will be cleared on
+ * target_dir, and target_dir->d_parent->...->d_parent will reach the
+ * root of the filesystem.
+ *
+ * Whenever DCACHE_DISCONNECTED is unset, target_dir is fully connected.
+ * But the converse is not true: target_dir may have DCACHE_DISCONNECTED
+ * set but already be connected.  In that case we'll verify the
+ * connection to root and then clear the flag.
+ *
+ * Note that target_dir could be removed by a concurrent operation.  In
+ * that case reconnect_path may still succeed with target_dir fully
+ * connected, but further operations using the filehandle will fail when
+ * necessary (due to S_DEAD being set on the directory).
  */
 static int
 reconnect_path(struct vfsmount *mnt, struct dentry *target_dir, char *nbuf)
@@ -112,18 +124,14 @@ reconnect_path(struct vfsmount *mnt, struct dentry *target_dir, char *nbuf)
        while (target_dir->d_flags & DCACHE_DISCONNECTED && noprogress++ < 10) {
                struct dentry *pd = find_disconnected_root(target_dir);
 
+               BUG_ON(pd == mnt->mnt_sb->s_root);
+
                if (!IS_ROOT(pd)) {
                        /* must have found a connected parent - great */
                        spin_lock(&pd->d_lock);
                        pd->d_flags &= ~DCACHE_DISCONNECTED;
                        spin_unlock(&pd->d_lock);
                        noprogress = 0;
-               } else if (pd == mnt->mnt_sb->s_root) {
-                       printk(KERN_ERR "export: Eeek filesystem root is not connected, impossible\n");
-                       spin_lock(&pd->d_lock);
-                       pd->d_flags &= ~DCACHE_DISCONNECTED;
-                       spin_unlock(&pd->d_lock);
-                       noprogress = 0;
                } else {
                        /*
                         * We have hit the top of a disconnected path, try to
@@ -215,7 +223,7 @@ struct getdents_callback {
        struct dir_context ctx;
        char *name;             /* name that was found. It already points to a
                                   buffer NAME_MAX+1 is size */
-       unsigned long ino;      /* the inum we are looking for */
+       u64 ino;                /* the inum we are looking for */
        int found;              /* inode matched? */
        int sequence;           /* sequence counter */
 };
@@ -231,7 +239,7 @@ static int filldir_one(void * __buf, const char * name, int len,
        int result = 0;
 
        buf->sequence++;
-       if (buf->ino == ino) {
+       if (buf->ino == ino && len <= NAME_MAX) {
                memcpy(buf->name, name, len);
                buf->name[len] = '\0';
                buf->found = 1;
@@ -255,10 +263,14 @@ static int get_name(const struct path *path, char *name, struct dentry *child)
        struct inode *dir = path->dentry->d_inode;
        int error;
        struct file *file;
+       struct kstat stat;
+       struct path child_path = {
+               .mnt = path->mnt,
+               .dentry = child,
+       };
        struct getdents_callback buffer = {
                .ctx.actor = filldir_one,
                .name = name,
-               .ino = child->d_inode->i_ino
        };
 
        error = -ENOTDIR;
@@ -267,6 +279,16 @@ static int get_name(const struct path *path, char *name, struct dentry *child)
        error = -EINVAL;
        if (!dir->i_fop)
                goto out;
+       /*
+        * inode->i_ino is unsigned long, kstat->ino is u64, so the
+        * former would be insufficient on 32-bit hosts when the
+        * filesystem supports 64-bit inode numbers.  So we need to
+        * actually call ->getattr, not just read i_ino:
+        */
+       error = vfs_getattr_nosec(&child_path, &stat);
+       if (error)
+               return error;
+       buffer.ino = stat.ino;
        /*
         * Open the directory ...
         */