]> git.ipfire.org Git - thirdparty/xfsprogs-dev.git/commitdiff
xfs: define the on-disk format for the metadir feature
authorDarrick J. Wong <djwong@kernel.org>
Mon, 25 Nov 2024 21:14:16 +0000 (13:14 -0800)
committerDarrick J. Wong <djwong@kernel.org>
Tue, 24 Dec 2024 02:01:25 +0000 (18:01 -0800)
Source kernel commit: 4f3d4dd1b04b2ba0bf236fbaa3c3c0c669aa5a47

Define the on-disk layout and feature flags for the metadata inode
directory feature.  Add a xfs_sb_version_hasmetadir for benefit of
xfs_repair, which needs to know where the new end of the superblock
lies.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
12 files changed:
db/check.c
db/inode.c
include/xfs_inode.h
include/xfs_mount.h
libxfs/xfs_format.h
libxfs/xfs_inode_buf.c
libxfs/xfs_inode_util.c
libxfs/xfs_log_format.h
libxfs/xfs_ondisk.h
libxfs/xfs_sb.c
repair/dino_chunks.c
repair/dinode.c

index 0a6e5c3280e1cf8ac0d2677f90afbaedd7dbda64..fb7b6cb41a3fbf11de50d3f758a5cafa99caacd7 100644 (file)
@@ -2823,7 +2823,7 @@ process_inode(
                return;
        }
        if (dip->di_version == 1) {
-               nlink = be16_to_cpu(dip->di_onlink);
+               nlink = be16_to_cpu(dip->di_metatype);
                prid = 0;
        } else {
                nlink = be32_to_cpu(dip->di_nlink);
index 7a5f5a0cb987aa6be765a51aee3f91543efcda9b..246febb5929aa1e2f6da95d6b44a9bba0528ef11 100644 (file)
@@ -90,9 +90,9 @@ const field_t inode_core_flds[] = {
        { "mode", FLDT_UINT16O, OI(COFF(mode)), C1, 0, TYP_NONE },
        { "version", FLDT_INT8D, OI(COFF(version)), C1, 0, TYP_NONE },
        { "format", FLDT_DINODE_FMT, OI(COFF(format)), C1, 0, TYP_NONE },
-       { "nlinkv1", FLDT_UINT16D, OI(COFF(onlink)), inode_core_nlinkv1_count,
+       { "nlinkv1", FLDT_UINT16D, OI(COFF(metatype)), inode_core_nlinkv1_count,
          FLD_COUNT, TYP_NONE },
-       { "onlink", FLDT_UINT16D, OI(COFF(onlink)), inode_core_onlink_count,
+       { "onlink", FLDT_UINT16D, OI(COFF(metatype)), inode_core_onlink_count,
          FLD_COUNT, TYP_NONE },
        { "uid", FLDT_UINT32D, OI(COFF(uid)), C1, 0, TYP_NONE },
        { "gid", FLDT_UINT32D, OI(COFF(gid)), C1, 0, TYP_NONE },
index f250102ff19d65384233b84f5de20309f2cb4224..e03521bc9aaaa202c8c7bb24254758667e1dd52f 100644 (file)
@@ -230,6 +230,7 @@ typedef struct xfs_inode {
                uint16_t        i_flushiter;    /* incremented on flush */
        };
        uint8_t                 i_forkoff;      /* attr fork offset >> 3 */
+       enum xfs_metafile_type  i_metatype;     /* XFS_METAFILE_* */
        uint16_t                i_diflags;      /* XFS_DIFLAG_... */
        uint64_t                i_diflags2;     /* XFS_DIFLAG2_... */
        struct timespec64       i_crtime;       /* time created */
@@ -396,6 +397,11 @@ static inline bool xfs_is_always_cow_inode(struct xfs_inode *ip)
        return false;
 }
 
+static inline bool xfs_is_metadir_inode(const struct xfs_inode *ip)
+{
+       return ip->i_diflags2 & XFS_DIFLAG2_METADATA;
+}
+
 extern void    libxfs_trans_inode_alloc_buf (struct xfs_trans *,
                                struct xfs_buf *);
 
index 1179a80d9df94eaa30fae9d000a9d327adc2a0aa..c7fada9e2a6d70aa6cb7a15ccdaa9179a4dd09c8 100644 (file)
@@ -201,6 +201,7 @@ typedef struct xfs_mount {
 #define XFS_FEAT_NEEDSREPAIR   (1ULL << 25)    /* needs xfs_repair */
 #define XFS_FEAT_NREXT64       (1ULL << 26)    /* large extent counters */
 #define XFS_FEAT_EXCHANGE_RANGE        (1ULL << 27)    /* exchange range */
+#define XFS_FEAT_METADIR       (1ULL << 28)    /* metadata directory tree */
 
 #define __XFS_HAS_FEAT(name, NAME) \
 static inline bool xfs_has_ ## name (struct xfs_mount *mp) \
@@ -246,6 +247,7 @@ __XFS_HAS_FEAT(bigtime, BIGTIME)
 __XFS_HAS_FEAT(needsrepair, NEEDSREPAIR)
 __XFS_HAS_FEAT(large_extent_counts, NREXT64)
 __XFS_HAS_FEAT(exchange_range, EXCHANGE_RANGE)
+__XFS_HAS_FEAT(metadir, METADIR)
 
 /* Kernel mount features that we don't support */
 #define __XFS_UNSUPP_FEAT(name) \
index a24ab46aaebc7e01a1a23b53f760ef5c7594d389..616f81045921b74a5d00ec528371abd1e08800ee 100644 (file)
@@ -174,6 +174,8 @@ typedef struct xfs_sb {
        xfs_lsn_t       sb_lsn;         /* last write sequence */
        uuid_t          sb_meta_uuid;   /* metadata file system unique id */
 
+       xfs_ino_t       sb_metadirino;  /* metadata directory tree root */
+
        /* must be padded to 64 bit alignment */
 } xfs_sb_t;
 
@@ -259,6 +261,8 @@ struct xfs_dsb {
        __be64          sb_lsn;         /* last write sequence */
        uuid_t          sb_meta_uuid;   /* metadata file system unique id */
 
+       __be64          sb_metadirino;  /* metadata directory tree root */
+
        /* must be padded to 64 bit alignment */
 };
 
@@ -374,6 +378,7 @@ xfs_sb_has_ro_compat_feature(
 #define XFS_SB_FEAT_INCOMPAT_NREXT64   (1 << 5)  /* large extent counters */
 #define XFS_SB_FEAT_INCOMPAT_EXCHRANGE (1 << 6)  /* exchangerange supported */
 #define XFS_SB_FEAT_INCOMPAT_PARENT    (1 << 7)  /* parent pointers */
+#define XFS_SB_FEAT_INCOMPAT_METADIR   (1 << 8)  /* metadata dir tree */
 #define XFS_SB_FEAT_INCOMPAT_ALL \
                (XFS_SB_FEAT_INCOMPAT_FTYPE | \
                 XFS_SB_FEAT_INCOMPAT_SPINODES | \
@@ -790,6 +795,27 @@ static inline time64_t xfs_bigtime_to_unix(uint64_t ondisk_seconds)
        return (time64_t)ondisk_seconds - XFS_BIGTIME_EPOCH_OFFSET;
 }
 
+enum xfs_metafile_type {
+       XFS_METAFILE_UNKNOWN,           /* unknown */
+       XFS_METAFILE_DIR,               /* metadir directory */
+       XFS_METAFILE_USRQUOTA,          /* user quota */
+       XFS_METAFILE_GRPQUOTA,          /* group quota */
+       XFS_METAFILE_PRJQUOTA,          /* project quota */
+       XFS_METAFILE_RTBITMAP,          /* rt bitmap */
+       XFS_METAFILE_RTSUMMARY,         /* rt summary */
+
+       XFS_METAFILE_MAX
+} __packed;
+
+#define XFS_METAFILE_TYPE_STR \
+       { XFS_METAFILE_UNKNOWN,         "unknown" }, \
+       { XFS_METAFILE_DIR,             "dir" }, \
+       { XFS_METAFILE_USRQUOTA,        "usrquota" }, \
+       { XFS_METAFILE_GRPQUOTA,        "grpquota" }, \
+       { XFS_METAFILE_PRJQUOTA,        "prjquota" }, \
+       { XFS_METAFILE_RTBITMAP,        "rtbitmap" }, \
+       { XFS_METAFILE_RTSUMMARY,       "rtsummary" }
+
 /*
  * On-disk inode structure.
  *
@@ -812,7 +838,7 @@ struct xfs_dinode {
        __be16          di_mode;        /* mode and type of file */
        __u8            di_version;     /* inode version */
        __u8            di_format;      /* format of di_c data */
-       __be16          di_onlink;      /* old number of links to file */
+       __be16          di_metatype;    /* XFS_METAFILE_*; was di_onlink */
        __be32          di_uid;         /* owner's user id */
        __be32          di_gid;         /* owner's group id */
        __be32          di_nlink;       /* number of links to file */
@@ -1088,21 +1114,60 @@ static inline void xfs_dinode_put_rdev(struct xfs_dinode *dip, xfs_dev_t rdev)
  * Values for di_flags2 These start by being exposed to userspace in the upper
  * 16 bits of the XFS_XFLAG_s range.
  */
-#define XFS_DIFLAG2_DAX_BIT    0       /* use DAX for this inode */
-#define XFS_DIFLAG2_REFLINK_BIT        1       /* file's blocks may be shared */
-#define XFS_DIFLAG2_COWEXTSIZE_BIT   2  /* copy on write extent size hint */
-#define XFS_DIFLAG2_BIGTIME_BIT        3       /* big timestamps */
-#define XFS_DIFLAG2_NREXT64_BIT 4      /* large extent counters */
+/* use DAX for this inode */
+#define XFS_DIFLAG2_DAX_BIT            0
+
+/* file's blocks may be shared */
+#define XFS_DIFLAG2_REFLINK_BIT                1
 
-#define XFS_DIFLAG2_DAX                (1 << XFS_DIFLAG2_DAX_BIT)
-#define XFS_DIFLAG2_REFLINK     (1 << XFS_DIFLAG2_REFLINK_BIT)
-#define XFS_DIFLAG2_COWEXTSIZE  (1 << XFS_DIFLAG2_COWEXTSIZE_BIT)
-#define XFS_DIFLAG2_BIGTIME    (1 << XFS_DIFLAG2_BIGTIME_BIT)
-#define XFS_DIFLAG2_NREXT64    (1 << XFS_DIFLAG2_NREXT64_BIT)
+/* copy on write extent size hint */
+#define XFS_DIFLAG2_COWEXTSIZE_BIT     2
+
+/* big timestamps */
+#define XFS_DIFLAG2_BIGTIME_BIT                3
+
+/* large extent counters */
+#define XFS_DIFLAG2_NREXT64_BIT                4
+
+/*
+ * The inode contains filesystem metadata and can be found through the metadata
+ * directory tree.  Metadata inodes must satisfy the following constraints:
+ *
+ * - V5 filesystem (and ftype) are enabled;
+ * - The only valid modes are regular files and directories;
+ * - The access bits must be zero;
+ * - DMAPI event and state masks are zero;
+ * - The user and group IDs must be zero;
+ * - The project ID can be used as a u32 annotation;
+ * - The immutable, sync, noatime, nodump, nodefrag flags must be set.
+ * - The dax flag must not be set.
+ * - Directories must have nosymlinks set.
+ *
+ * These requirements are chosen defensively to minimize the ability of
+ * userspace to read or modify the contents, should a metadata file ever
+ * escape to userspace.
+ *
+ * There are further constraints on the directory tree itself:
+ *
+ * - Metadata inodes must never be resolvable through the root directory;
+ * - They must never be accessed by userspace;
+ * - Metadata directory entries must have correct ftype.
+ *
+ * Superblock-rooted metadata files must have the METADATA iflag set even
+ * though they do not have a parent directory.
+ */
+#define XFS_DIFLAG2_METADATA_BIT       5
+
+#define XFS_DIFLAG2_DAX                (1ULL << XFS_DIFLAG2_DAX_BIT)
+#define XFS_DIFLAG2_REFLINK    (1ULL << XFS_DIFLAG2_REFLINK_BIT)
+#define XFS_DIFLAG2_COWEXTSIZE (1ULL << XFS_DIFLAG2_COWEXTSIZE_BIT)
+#define XFS_DIFLAG2_BIGTIME    (1ULL << XFS_DIFLAG2_BIGTIME_BIT)
+#define XFS_DIFLAG2_NREXT64    (1ULL << XFS_DIFLAG2_NREXT64_BIT)
+#define XFS_DIFLAG2_METADATA   (1ULL << XFS_DIFLAG2_METADATA_BIT)
 
 #define XFS_DIFLAG2_ANY \
        (XFS_DIFLAG2_DAX | XFS_DIFLAG2_REFLINK | XFS_DIFLAG2_COWEXTSIZE | \
-        XFS_DIFLAG2_BIGTIME | XFS_DIFLAG2_NREXT64)
+        XFS_DIFLAG2_BIGTIME | XFS_DIFLAG2_NREXT64 | XFS_DIFLAG2_METADATA)
 
 static inline bool xfs_dinode_has_bigtime(const struct xfs_dinode *dip)
 {
@@ -1117,6 +1182,12 @@ static inline bool xfs_dinode_has_large_extent_counts(
               (dip->di_flags2 & cpu_to_be64(XFS_DIFLAG2_NREXT64));
 }
 
+static inline bool xfs_dinode_is_metadir(const struct xfs_dinode *dip)
+{
+       return dip->di_version >= 3 &&
+              (dip->di_flags2 & cpu_to_be64(XFS_DIFLAG2_METADATA));
+}
+
 /*
  * Inode number format:
  * low inopblog bits - offset in block
index 5970ee705bc5a2fbd5a15beb83dc40e219ac7f5d..981113f6acd37aacd22c5d1fce48e20d3fa24e90 100644 (file)
@@ -206,12 +206,15 @@ xfs_inode_from_disk(
         * They will also be unconditionally written back to disk as v2 inodes.
         */
        if (unlikely(from->di_version == 1)) {
-               set_nlink(inode, be16_to_cpu(from->di_onlink));
+               /* di_metatype used to be di_onlink */
+               set_nlink(inode, be16_to_cpu(from->di_metatype));
                ip->i_projid = 0;
        } else {
                set_nlink(inode, be32_to_cpu(from->di_nlink));
                ip->i_projid = (prid_t)be16_to_cpu(from->di_projid_hi) << 16 |
                                        be16_to_cpu(from->di_projid_lo);
+               if (xfs_dinode_is_metadir(from))
+                       ip->i_metatype = be16_to_cpu(from->di_metatype);
        }
 
        i_uid_write(inode, be32_to_cpu(from->di_uid));
@@ -312,7 +315,10 @@ xfs_inode_to_disk(
        struct inode            *inode = VFS_I(ip);
 
        to->di_magic = cpu_to_be16(XFS_DINODE_MAGIC);
-       to->di_onlink = 0;
+       if (xfs_is_metadir_inode(ip))
+               to->di_metatype = cpu_to_be16(ip->i_metatype);
+       else
+               to->di_metatype = 0;
 
        to->di_format = xfs_ifork_format(&ip->i_df);
        to->di_uid = cpu_to_be32(i_uid_read(inode));
@@ -520,8 +526,11 @@ xfs_dinode_verify(
         * di_nlink==0 on a V1 inode.  V2/3 inodes would get written out with
         * di_onlink==0, so we can check that.
         */
-       if (dip->di_version >= 2) {
-               if (dip->di_onlink)
+       if (dip->di_version == 2) {
+               if (dip->di_metatype)
+                       return __this_address;
+       } else if (dip->di_version >= 3) {
+               if (!xfs_dinode_is_metadir(dip) && dip->di_metatype)
                        return __this_address;
        }
 
@@ -543,7 +552,8 @@ xfs_dinode_verify(
                        if (dip->di_nlink)
                                return __this_address;
                } else {
-                       if (dip->di_onlink)
+                       /* di_metatype used to be di_onlink */
+                       if (dip->di_metatype)
                                return __this_address;
                }
        }
index f9f16c7e2d0788e69ad8a8923befbe5be8e1515f..edc985eb9a4e457ee5ae24cbcf19d428e76f5019 100644 (file)
@@ -221,6 +221,8 @@ xfs_inode_inherit_flags2(
        }
        if (pip->i_diflags2 & XFS_DIFLAG2_DAX)
                ip->i_diflags2 |= XFS_DIFLAG2_DAX;
+       if (xfs_is_metadir_inode(pip))
+               ip->i_diflags2 |= XFS_DIFLAG2_METADATA;
 
        /* Don't let invalid cowextsize hints propagate. */
        failaddr = xfs_inode_validate_cowextsize(ip->i_mount, ip->i_cowextsize,
index 3e6682ed656b30b8d9189324cc8a57735b9c318d..ace7384a275bfb7926952dde67d1e9c090b9adc7 100644 (file)
@@ -404,7 +404,7 @@ struct xfs_log_dinode {
        uint16_t        di_mode;        /* mode and type of file */
        int8_t          di_version;     /* inode version */
        int8_t          di_format;      /* format of di_c data */
-       uint8_t         di_pad3[2];     /* unused in v2/3 inodes */
+       uint16_t        di_metatype;    /* metadata type, if DIFLAG2_METADATA */
        uint32_t        di_uid;         /* owner's user id */
        uint32_t        di_gid;         /* owner's group id */
        uint32_t        di_nlink;       /* number of links to file */
index 23c133fd36f5bb10a3f867224c028a8ba1de4178..8bca86e350fdc11980a00cf52a492fc9b7504496 100644 (file)
@@ -37,7 +37,7 @@ xfs_check_ondisk_structs(void)
        XFS_CHECK_STRUCT_SIZE(struct xfs_dinode,                176);
        XFS_CHECK_STRUCT_SIZE(struct xfs_disk_dquot,            104);
        XFS_CHECK_STRUCT_SIZE(struct xfs_dqblk,                 136);
-       XFS_CHECK_STRUCT_SIZE(struct xfs_dsb,                   264);
+       XFS_CHECK_STRUCT_SIZE(struct xfs_dsb,                   272);
        XFS_CHECK_STRUCT_SIZE(struct xfs_dsymlink_hdr,          56);
        XFS_CHECK_STRUCT_SIZE(struct xfs_inobt_key,             4);
        XFS_CHECK_STRUCT_SIZE(struct xfs_inobt_rec,             16);
index 9120a3377735b0885a250c7434ce88e02224d4de..de12edd8192e1c290f31616d20654af0a38b6b8b 100644 (file)
@@ -177,6 +177,8 @@ xfs_sb_version_to_features(
                features |= XFS_FEAT_EXCHANGE_RANGE;
        if (sbp->sb_features_incompat & XFS_SB_FEAT_INCOMPAT_PARENT)
                features |= XFS_FEAT_PARENT;
+       if (sbp->sb_features_incompat & XFS_SB_FEAT_INCOMPAT_METADIR)
+               features |= XFS_FEAT_METADIR;
 
        return features;
 }
@@ -700,6 +702,11 @@ __xfs_sb_from_disk(
        /* Convert on-disk flags to in-memory flags? */
        if (convert_xquota)
                xfs_sb_quota_from_disk(to);
+
+       if (to->sb_features_incompat & XFS_SB_FEAT_INCOMPAT_METADIR)
+               to->sb_metadirino = be64_to_cpu(from->sb_metadirino);
+       else
+               to->sb_metadirino = NULLFSINO;
 }
 
 void
@@ -847,6 +854,9 @@ xfs_sb_to_disk(
        to->sb_lsn = cpu_to_be64(from->sb_lsn);
        if (from->sb_features_incompat & XFS_SB_FEAT_INCOMPAT_META_UUID)
                uuid_copy(&to->sb_meta_uuid, &from->sb_meta_uuid);
+
+       if (from->sb_features_incompat & XFS_SB_FEAT_INCOMPAT_METADIR)
+               to->sb_metadirino = cpu_to_be64(from->sb_metadirino);
 }
 
 /*
index 86e29dd9ae05eb3e16793b411b42529b3415cdf5..49d57948c7eca86eaabf36bd3d61bd2a6ef4faa6 100644 (file)
@@ -896,7 +896,7 @@ next_readbuf:
                        set_inode_disk_nlinks(ino_rec, irec_offset,
                                dino->di_version > 1
                                        ? be32_to_cpu(dino->di_nlink)
-                                       : be16_to_cpu(dino->di_onlink));
+                                       : be16_to_cpu(dino->di_metatype));
 
                } else  {
                        set_inode_free(ino_rec, irec_offset);
index 2c9d9acfa10be5e6c398fc88700227a493964696..e217e037f8862df7cbf8003743d45fd9dfff2216 100644 (file)
@@ -2231,20 +2231,22 @@ process_check_inode_nlink_version(
        int                     dirty = 0;
 
        /*
-        * if it's a version 2 inode, it should have a zero
+        * if it's a version 2 non-metadir inode, it should have a zero
         * onlink field, so clear it.
         */
-       if (dino->di_version > 1 && dino->di_onlink != 0) {
+       if (dino->di_version > 1 &&
+           !(dino->di_flags2 & cpu_to_be64(XFS_DIFLAG2_METADATA)) &&
+           dino->di_metatype != 0) {
                if (!no_modify) {
                        do_warn(
 _("clearing obsolete nlink field in version 2 inode %" PRIu64 ", was %d, now 0\n"),
-                               lino, be16_to_cpu(dino->di_onlink));
-                       dino->di_onlink = 0;
+                               lino, be16_to_cpu(dino->di_metatype));
+                       dino->di_metatype = 0;
                        dirty = 1;
                } else  {
                        do_warn(
 _("would clear obsolete nlink field in version 2 inode %" PRIu64 ", currently %d\n"),
-                               lino, be16_to_cpu(dino->di_onlink));
+                               lino, be16_to_cpu(dino->di_metatype));
                }
        }
        return dirty;