ovl: support [S|G]ETFLAGS and FS[S|G]ETXATTR ioctls for directories
[linux-2.6-microblaze.git] / fs / overlayfs / readdir.c
index 6918b98..01620eb 100644 (file)
@@ -606,6 +606,7 @@ static struct ovl_dir_cache *ovl_cache_get_impure(struct path *path)
 {
        int res;
        struct dentry *dentry = path->dentry;
+       struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
        struct ovl_dir_cache *cache;
 
        cache = ovl_dir_cache(d_inode(dentry));
@@ -632,7 +633,7 @@ static struct ovl_dir_cache *ovl_cache_get_impure(struct path *path)
                 * Removing the "impure" xattr is best effort.
                 */
                if (!ovl_want_write(dentry)) {
-                       ovl_do_removexattr(ovl_dentry_upper(dentry),
+                       ovl_do_removexattr(ofs, ovl_dentry_upper(dentry),
                                           OVL_XATTR_IMPURE);
                        ovl_drop_write(dentry);
                }
@@ -839,7 +840,7 @@ out_unlock:
        return res;
 }
 
-static struct file *ovl_dir_open_realfile(struct file *file,
+static struct file *ovl_dir_open_realfile(const struct file *file,
                                          struct path *realpath)
 {
        struct file *res;
@@ -852,16 +853,22 @@ static struct file *ovl_dir_open_realfile(struct file *file,
        return res;
 }
 
-static int ovl_dir_fsync(struct file *file, loff_t start, loff_t end,
-                        int datasync)
+/*
+ * Like ovl_real_fdget(), returns upperfile if dir was copied up since open.
+ * Unlike ovl_real_fdget(), this caches upperfile in file->private_data.
+ *
+ * TODO: use same abstract type for file->private_data of dir and file so
+ * upperfile could also be cached for files as well.
+ */
+struct file *ovl_dir_real_file(const struct file *file, bool want_upper)
 {
+
        struct ovl_dir_file *od = file->private_data;
        struct dentry *dentry = file->f_path.dentry;
        struct file *realfile = od->realfile;
 
-       /* Nothing to sync for lower */
        if (!OVL_TYPE_UPPER(ovl_path_type(dentry)))
-               return 0;
+               return want_upper ? NULL : realfile;
 
        /*
         * Need to check if we started out being a lower dir, but got copied up
@@ -880,7 +887,7 @@ static int ovl_dir_fsync(struct file *file, loff_t start, loff_t end,
                        if (!od->upperfile) {
                                if (IS_ERR(realfile)) {
                                        inode_unlock(inode);
-                                       return PTR_ERR(realfile);
+                                       return realfile;
                                }
                                smp_store_release(&od->upperfile, realfile);
                        } else {
@@ -893,6 +900,25 @@ static int ovl_dir_fsync(struct file *file, loff_t start, loff_t end,
                }
        }
 
+       return realfile;
+}
+
+static int ovl_dir_fsync(struct file *file, loff_t start, loff_t end,
+                        int datasync)
+{
+       struct file *realfile;
+       int err;
+
+       if (!ovl_should_sync(OVL_FS(file->f_path.dentry->d_sb)))
+               return 0;
+
+       realfile = ovl_dir_real_file(file, true);
+       err = PTR_ERR_OR_ZERO(realfile);
+
+       /* Nothing to sync for lower */
+       if (!realfile || err)
+               return err;
+
        return vfs_fsync_range(realfile, start, end, datasync);
 }
 
@@ -945,6 +971,10 @@ const struct file_operations ovl_dir_operations = {
        .llseek         = ovl_dir_llseek,
        .fsync          = ovl_dir_fsync,
        .release        = ovl_dir_release,
+       .unlocked_ioctl = ovl_ioctl,
+#ifdef CONFIG_COMPAT
+       .compat_ioctl   = ovl_compat_ioctl,
+#endif
 };
 
 int ovl_check_empty_dir(struct dentry *dentry, struct list_head *list)
@@ -1051,7 +1081,9 @@ int ovl_check_d_type_supported(struct path *realpath)
        return rdd.d_type_supported;
 }
 
-static void ovl_workdir_cleanup_recurse(struct path *path, int level)
+#define OVL_INCOMPATDIR_NAME "incompat"
+
+static int ovl_workdir_cleanup_recurse(struct path *path, int level)
 {
        int err;
        struct inode *dir = path->dentry->d_inode;
@@ -1065,6 +1097,19 @@ static void ovl_workdir_cleanup_recurse(struct path *path, int level)
                .root = &root,
                .is_lowest = false,
        };
+       bool incompat = false;
+
+       /*
+        * The "work/incompat" directory is treated specially - if it is not
+        * empty, instead of printing a generic error and mounting read-only,
+        * we will error about incompat features and fail the mount.
+        *
+        * When called from ovl_indexdir_cleanup(), path->dentry->d_name.name
+        * starts with '#'.
+        */
+       if (level == 2 &&
+           !strcmp(path->dentry->d_name.name, OVL_INCOMPATDIR_NAME))
+               incompat = true;
 
        err = ovl_dir_read(path, &rdd);
        if (err)
@@ -1079,17 +1124,25 @@ static void ovl_workdir_cleanup_recurse(struct path *path, int level)
                                continue;
                        if (p->len == 2 && p->name[1] == '.')
                                continue;
+               } else if (incompat) {
+                       pr_err("overlay with incompat feature '%s' cannot be mounted\n",
+                               p->name);
+                       err = -EINVAL;
+                       break;
                }
                dentry = lookup_one_len(p->name, path->dentry, p->len);
                if (IS_ERR(dentry))
                        continue;
                if (dentry->d_inode)
-                       ovl_workdir_cleanup(dir, path->mnt, dentry, level);
+                       err = ovl_workdir_cleanup(dir, path->mnt, dentry, level);
                dput(dentry);
+               if (err)
+                       break;
        }
        inode_unlock(dir);
 out:
        ovl_cache_free(&list);
+       return err;
 }
 
 int ovl_workdir_cleanup(struct inode *dir, struct vfsmount *mnt,
@@ -1106,9 +1159,10 @@ int ovl_workdir_cleanup(struct inode *dir, struct vfsmount *mnt,
                struct path path = { .mnt = mnt, .dentry = dentry };
 
                inode_unlock(dir);
-               ovl_workdir_cleanup_recurse(&path, level + 1);
+               err = ovl_workdir_cleanup_recurse(&path, level + 1);
                inode_lock_nested(dir, I_MUTEX_PARENT);
-               err = ovl_cleanup(dir, dentry);
+               if (!err)
+                       err = ovl_cleanup(dir, dentry);
        }
 
        return err;