ovl: store file handle of lower inode on copy up
authorAmir Goldstein <amir73il@gmail.com>
Thu, 30 Mar 2017 12:22:16 +0000 (15:22 +0300)
committerMiklos Szeredi <mszeredi@redhat.com>
Fri, 5 May 2017 09:38:58 +0000 (11:38 +0200)
Sometimes it is interesting to know if an upper file is pure upper or a
copy up target, and if it is a copy up target, it may be interesting to
find the copy up origin.

This will be used to preserve lower inode numbers across copy up.

Store the lower inode file handle in upper inode extended attribute
overlay.origin on copy up to use it later for these cases.  Store the lower
filesystem uuid along side the file handle, so we can validate that we are
looking for the origin file in the original fs.

If lower fs does not support NFS export ops store a zero sized xattr so we
can always use the overlay.origin xattr to distinguish between a copy up
and a pure upper inode.

Signed-off-by: Amir Goldstein <amir73il@gmail.com>
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
fs/overlayfs/copy_up.c
fs/overlayfs/overlayfs.h

index 906ea6c..9008ab9 100644 (file)
@@ -20,6 +20,7 @@
 #include <linux/namei.h>
 #include <linux/fdtable.h>
 #include <linux/ratelimit.h>
+#include <linux/exportfs.h>
 #include "overlayfs.h"
 #include "ovl_entry.h"
 
@@ -232,6 +233,79 @@ int ovl_set_attr(struct dentry *upperdentry, struct kstat *stat)
        return err;
 }
 
+static struct ovl_fh *ovl_encode_fh(struct dentry *lower, uuid_be *uuid)
+{
+       struct ovl_fh *fh;
+       int fh_type, fh_len, dwords;
+       void *buf;
+       int buflen = MAX_HANDLE_SZ;
+
+       buf = kmalloc(buflen, GFP_TEMPORARY);
+       if (!buf)
+               return ERR_PTR(-ENOMEM);
+
+       /*
+        * We encode a non-connectable file handle for non-dir, because we
+        * only need to find the lower inode number and we don't want to pay
+        * the price or reconnecting the dentry.
+        */
+       dwords = buflen >> 2;
+       fh_type = exportfs_encode_fh(lower, buf, &dwords, 0);
+       buflen = (dwords << 2);
+
+       fh = ERR_PTR(-EIO);
+       if (WARN_ON(fh_type < 0) ||
+           WARN_ON(buflen > MAX_HANDLE_SZ) ||
+           WARN_ON(fh_type == FILEID_INVALID))
+               goto out;
+
+       BUILD_BUG_ON(MAX_HANDLE_SZ + offsetof(struct ovl_fh, fid) > 255);
+       fh_len = offsetof(struct ovl_fh, fid) + buflen;
+       fh = kmalloc(fh_len, GFP_KERNEL);
+       if (!fh) {
+               fh = ERR_PTR(-ENOMEM);
+               goto out;
+       }
+
+       fh->version = OVL_FH_VERSION;
+       fh->magic = OVL_FH_MAGIC;
+       fh->type = fh_type;
+       fh->flags = OVL_FH_FLAG_CPU_ENDIAN;
+       fh->len = fh_len;
+       fh->uuid = *uuid;
+       memcpy(fh->fid, buf, buflen);
+
+out:
+       kfree(buf);
+       return fh;
+}
+
+static int ovl_set_origin(struct dentry *dentry, struct dentry *lower,
+                         struct dentry *upper)
+{
+       struct super_block *sb = lower->d_sb;
+       uuid_be *uuid = (uuid_be *) &sb->s_uuid;
+       const struct ovl_fh *fh = NULL;
+       int err;
+
+       /*
+        * When lower layer doesn't support export operations store a 'null' fh,
+        * so we can use the overlay.origin xattr to distignuish between a copy
+        * up and a pure upper inode.
+        */
+       if (sb->s_export_op && sb->s_export_op->fh_to_dentry &&
+           uuid_be_cmp(*uuid, NULL_UUID_BE)) {
+               fh = ovl_encode_fh(lower, uuid);
+               if (IS_ERR(fh))
+                       return PTR_ERR(fh);
+       }
+
+       err = ovl_do_setxattr(upper, OVL_XATTR_ORIGIN, fh, fh ? fh->len : 0, 0);
+       kfree(fh);
+
+       return err;
+}
+
 static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir,
                              struct dentry *dentry, struct path *lowerpath,
                              struct kstat *stat, const char *link,
@@ -316,6 +390,14 @@ static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir,
        if (err)
                goto out_cleanup;
 
+       /*
+        * Store identifier of lower inode in upper inode xattr to
+        * allow lookup of the copy up origin inode.
+        */
+       err = ovl_set_origin(dentry, lowerpath->dentry, temp);
+       if (err)
+               goto out_cleanup;
+
        if (tmpfile)
                err = ovl_do_link(temp, udir, upper, true);
        else
index c851158..77405e2 100644 (file)
@@ -8,6 +8,7 @@
  */
 
 #include <linux/kernel.h>
+#include <linux/uuid.h>
 
 enum ovl_path_type {
        __OVL_PATH_UPPER        = (1 << 0),
@@ -20,6 +21,41 @@ enum ovl_path_type {
 #define OVL_XATTR_PREFIX XATTR_TRUSTED_PREFIX "overlay."
 #define OVL_XATTR_OPAQUE OVL_XATTR_PREFIX "opaque"
 #define OVL_XATTR_REDIRECT OVL_XATTR_PREFIX "redirect"
+#define OVL_XATTR_ORIGIN OVL_XATTR_PREFIX "origin"
+
+/*
+ * The tuple (fh,uuid) is a universal unique identifier for a copy up origin,
+ * where:
+ * origin.fh   - exported file handle of the lower file
+ * origin.uuid - uuid of the lower filesystem
+ */
+#define OVL_FH_VERSION 0
+#define OVL_FH_MAGIC   0xfb
+
+/* CPU byte order required for fid decoding:  */
+#define OVL_FH_FLAG_BIG_ENDIAN (1 << 0)
+#define OVL_FH_FLAG_ANY_ENDIAN (1 << 1)
+
+#define OVL_FH_FLAG_ALL (OVL_FH_FLAG_BIG_ENDIAN | OVL_FH_FLAG_ANY_ENDIAN)
+
+#if defined(__LITTLE_ENDIAN)
+#define OVL_FH_FLAG_CPU_ENDIAN 0
+#elif defined(__BIG_ENDIAN)
+#define OVL_FH_FLAG_CPU_ENDIAN OVL_FH_FLAG_BIG_ENDIAN
+#else
+#error Endianness not defined
+#endif
+
+/* On-disk and in-memeory format for redirect by file handle */
+struct ovl_fh {
+       u8 version;     /* 0 */
+       u8 magic;       /* 0xfb */
+       u8 len;         /* size of this header + size of fid */
+       u8 flags;       /* OVL_FH_FLAG_* */
+       u8 type;        /* fid_type of fid */
+       uuid_be uuid;   /* uuid of filesystem */
+       u8 fid[0];      /* file identifier */
+} __packed;
 
 #define OVL_ISUPPER_MASK 1UL