]> git.ipfire.org Git - thirdparty/xfsprogs-dev.git/commitdiff
xfs: widen ondisk inode timestamps to deal with y2038+
authorDarrick J. Wong <darrick.wong@oracle.com>
Thu, 12 Nov 2020 01:08:14 +0000 (20:08 -0500)
committerEric Sandeen <sandeen@sandeen.net>
Thu, 12 Nov 2020 01:08:14 +0000 (20:08 -0500)
Source kernel commit: f93e5436f0ee5a85eaa3a86d2614d215873fb18b

Redesign the ondisk inode timestamps to be a simple unsigned 64-bit
counter of nanoseconds since 14 Dec 1901 (i.e. the minimum time in the
32-bit unix time epoch).  This enables us to handle dates up to 2486,
which solves the y2038 problem.

sandeen: update xfs_flags2diflags2() as well, to match

Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Gao Xiang <hsiangkao@redhat.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Eric Sandeen <sandeen@sandeen.net>
include/xfs_inode.h
libxfs/util.c
libxfs/xfs_format.h
libxfs/xfs_fs.h
libxfs/xfs_ialloc.c
libxfs/xfs_inode_buf.c
libxfs/xfs_inode_buf.h
libxfs/xfs_sb.c
libxfs/xfs_shared.h
libxfs/xfs_trans_inode.c

index 12676cb30bf22c6b1cb6298dcd03ea055e73576f..40310df6a785a0433f6200d34ea398f1bd0b3d4c 100644 (file)
@@ -140,6 +140,11 @@ static inline bool xfs_is_reflink_inode(struct xfs_inode *ip)
        return ip->i_d.di_flags2 & XFS_DIFLAG2_REFLINK;
 }
 
+static inline bool xfs_inode_has_bigtime(struct xfs_inode *ip)
+{
+       return ip->i_d.di_flags2 & XFS_DIFLAG2_BIGTIME;
+}
+
 typedef struct cred {
        uid_t   cr_uid;
        gid_t   cr_gid;
index c78074a01dab71229e2d8c177a3cf732eb43ea13..252cf91e851be3798e6209b6fedcb68dc445d4c0 100644 (file)
@@ -197,7 +197,8 @@ xfs_flags2diflags2(
        unsigned int            xflags)
 {
        uint64_t                di_flags2 =
-               (ip->i_d.di_flags2 & XFS_DIFLAG2_REFLINK);
+               (ip->i_d.di_flags2 & (XFS_DIFLAG2_REFLINK |
+                                     XFS_DIFLAG2_BIGTIME));
 
        if (xflags & FS_XFLAG_DAX)
                di_flags2 |= XFS_DIFLAG2_DAX;
@@ -307,8 +308,8 @@ libxfs_ialloc(
                ASSERT(ip->i_d.di_ino == ino);
                ASSERT(uuid_equal(&ip->i_d.di_uuid, &mp->m_sb.sb_meta_uuid));
                VFS_I(ip)->i_version = 1;
-               ip->i_d.di_flags2 = pip ? 0 : xfs_flags2diflags2(ip,
-                               fsx->fsx_xflags);
+               ip->i_d.di_flags2 = pip ? ip->i_mount->m_ino_geo.new_diflags2 :
+                               xfs_flags2diflags2(ip, fsx->fsx_xflags);
                ip->i_d.di_crtime.tv_sec = (int32_t)VFS_I(ip)->i_mtime.tv_sec;
                ip->i_d.di_crtime.tv_nsec = (int32_t)VFS_I(ip)->i_mtime.tv_nsec;
                ip->i_d.di_cowextsize = pip ? 0 : fsx->fsx_cowextsize;
index 43565a1719d20b2b76969ec7a2e5edf35e5dfa31..d71228a6e06c6d1721e6949165a94b081e498e28 100644 (file)
@@ -466,6 +466,7 @@ xfs_sb_has_ro_compat_feature(
 #define XFS_SB_FEAT_INCOMPAT_FTYPE     (1 << 0)        /* filetype in dirent */
 #define XFS_SB_FEAT_INCOMPAT_SPINODES  (1 << 1)        /* sparse inode chunks */
 #define XFS_SB_FEAT_INCOMPAT_META_UUID (1 << 2)        /* metadata UUID */
+#define XFS_SB_FEAT_INCOMPAT_BIGTIME   (1 << 3)        /* large timestamps */
 #define XFS_SB_FEAT_INCOMPAT_ALL \
                (XFS_SB_FEAT_INCOMPAT_FTYPE|    \
                 XFS_SB_FEAT_INCOMPAT_SPINODES| \
@@ -564,6 +565,12 @@ static inline bool xfs_sb_version_hasreflink(struct xfs_sb *sbp)
                (sbp->sb_features_ro_compat & XFS_SB_FEAT_RO_COMPAT_REFLINK);
 }
 
+static inline bool xfs_sb_version_hasbigtime(struct xfs_sb *sbp)
+{
+       return XFS_SB_VERSION_NUM(sbp) == XFS_SB_VERSION_5 &&
+               (sbp->sb_features_incompat & XFS_SB_FEAT_INCOMPAT_BIGTIME);
+}
+
 /*
  * Inode btree block counter.  We record the number of inobt and finobt blocks
  * in the AGI header so that we can skip the finobt walk at mount time when
@@ -857,6 +864,13 @@ struct xfs_agfl {
  * Therefore, the ondisk min and max defined here can be used directly to
  * constrain the incore timestamps on a Unix system.  Note that we actually
  * encode a __be64 value on disk.
+ *
+ * When the bigtime feature is enabled, ondisk inode timestamps become an
+ * unsigned 64-bit nanoseconds counter.  This means that the bigtime inode
+ * timestamp epoch is the start of the classic timestamp range, which is
+ * Dec 31 20:45:52 UTC 1901.  Because the epochs are not the same, callers
+ * /must/ use the bigtime conversion functions when encoding and decoding raw
+ * timestamps.
  */
 typedef __be64 xfs_timestamp_t;
 
@@ -878,6 +892,50 @@ struct xfs_legacy_timestamp {
  */
 #define XFS_LEGACY_TIME_MAX    ((int64_t)S32_MAX)
 
+/*
+ * Smallest possible ondisk seconds value with bigtime timestamps.  This
+ * corresponds (after conversion to a Unix timestamp) with the traditional
+ * minimum timestamp of Dec 13 20:45:52 UTC 1901.
+ */
+#define XFS_BIGTIME_TIME_MIN   ((int64_t)0)
+
+/*
+ * Largest supported ondisk seconds value with bigtime timestamps.  This
+ * corresponds (after conversion to a Unix timestamp) with an incore timestamp
+ * of Jul  2 20:20:24 UTC 2486.
+ *
+ * We round down the ondisk limit so that the bigtime quota and inode max
+ * timestamps will be the same.
+ */
+#define XFS_BIGTIME_TIME_MAX   ((int64_t)((-1ULL / NSEC_PER_SEC) & ~0x3ULL))
+
+/*
+ * Bigtime epoch is set exactly to the minimum time value that a traditional
+ * 32-bit timestamp can represent when using the Unix epoch as a reference.
+ * Hence the Unix epoch is at a fixed offset into the supported bigtime
+ * timestamp range.
+ *
+ * The bigtime epoch also matches the minimum value an on-disk 32-bit XFS
+ * timestamp can represent so we will not lose any fidelity in converting
+ * to/from unix and bigtime timestamps.
+ *
+ * The following conversion factor converts a seconds counter from the Unix
+ * epoch to the bigtime epoch.
+ */
+#define XFS_BIGTIME_EPOCH_OFFSET       (-(int64_t)S32_MIN)
+
+/* Convert a timestamp from the Unix epoch to the bigtime epoch. */
+static inline uint64_t xfs_unix_to_bigtime(time64_t unix_seconds)
+{
+       return (uint64_t)unix_seconds + XFS_BIGTIME_EPOCH_OFFSET;
+}
+
+/* Convert a timestamp from the bigtime epoch to the Unix epoch. */
+static inline time64_t xfs_bigtime_to_unix(uint64_t ondisk_seconds)
+{
+       return (time64_t)ondisk_seconds - XFS_BIGTIME_EPOCH_OFFSET;
+}
+
 /*
  * On-disk inode structure.
  *
@@ -1103,12 +1161,22 @@ static inline void xfs_dinode_put_rdev(struct xfs_dinode *dip, xfs_dev_t rdev)
 #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_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_ANY \
-       (XFS_DIFLAG2_DAX | XFS_DIFLAG2_REFLINK | XFS_DIFLAG2_COWEXTSIZE)
+       (XFS_DIFLAG2_DAX | XFS_DIFLAG2_REFLINK | XFS_DIFLAG2_COWEXTSIZE | \
+        XFS_DIFLAG2_BIGTIME)
+
+static inline bool xfs_dinode_has_bigtime(const struct xfs_dinode *dip)
+{
+       return dip->di_version >= 3 &&
+              (dip->di_flags2 & cpu_to_be64(XFS_DIFLAG2_BIGTIME));
+}
 
 /*
  * Inode number format:
index 84bcffa8775315891385bd984e8b8181d60fd1eb..2a2e3cfd94f0ccaacf9ea871a0c428e83640b2b1 100644 (file)
@@ -249,6 +249,7 @@ typedef struct xfs_fsop_resblks {
 #define XFS_FSOP_GEOM_FLAGS_SPINODES   (1 << 18) /* sparse inode chunks   */
 #define XFS_FSOP_GEOM_FLAGS_RMAPBT     (1 << 19) /* reverse mapping btree */
 #define XFS_FSOP_GEOM_FLAGS_REFLINK    (1 << 20) /* files can share blocks */
+#define XFS_FSOP_GEOM_FLAGS_BIGTIME    (1 << 21) /* 64-bit nsec timestamps */
 
 /*
  * Minimum and maximum sizes need for growth checks.
index 8adeb10e5d2e6211c4afb12dd915193098cb66dd..d78f960c6d441e1189380810abd748ba45f6152c 100644 (file)
@@ -2802,6 +2802,10 @@ xfs_ialloc_setup_geometry(
        uint64_t                icount;
        uint                    inodes;
 
+       igeo->new_diflags2 = 0;
+       if (xfs_sb_version_hasbigtime(&mp->m_sb))
+               igeo->new_diflags2 |= XFS_DIFLAG2_BIGTIME;
+
        /* Compute inode btree geometry. */
        igeo->agino_log = sbp->sb_inopblog + sbp->sb_agblklog;
        igeo->inobt_mxr[0] = xfs_inobt_maxrecs(mp, sbp->sb_blocksize, 1);
index 1b9f63ebe9f9ec81de54ab2fe479a89976fd73fe..6722d5afddacb84adc1a94c000986e08f93e8695 100644 (file)
@@ -154,14 +154,29 @@ xfs_imap_to_bp(
        return 0;
 }
 
+static inline struct timespec64 xfs_inode_decode_bigtime(uint64_t ts)
+{
+       struct timespec64       tv;
+       uint32_t                n;
+
+       tv.tv_sec = xfs_bigtime_to_unix(div_u64_rem(ts, NSEC_PER_SEC, &n));
+       tv.tv_nsec = n;
+
+       return tv;
+}
+
 /* Convert an ondisk timestamp to an incore timestamp. */
 struct timespec64
 xfs_inode_from_disk_ts(
+       struct xfs_dinode               *dip,
        const xfs_timestamp_t           ts)
 {
        struct timespec64               tv;
        struct xfs_legacy_timestamp     *lts;
 
+       if (xfs_dinode_has_bigtime(dip))
+               return xfs_inode_decode_bigtime(be64_to_cpu(ts));
+
        lts = (struct xfs_legacy_timestamp *)&ts;
        tv.tv_sec = (int)be32_to_cpu(lts->t_sec);
        tv.tv_nsec = (int)be32_to_cpu(lts->t_nsec);
@@ -223,9 +238,9 @@ xfs_inode_from_disk(
         * a time before epoch is converted to a time long after epoch
         * on 64 bit systems.
         */
-       inode->i_atime = xfs_inode_from_disk_ts(from->di_atime);
-       inode->i_mtime = xfs_inode_from_disk_ts(from->di_mtime);
-       inode->i_ctime = xfs_inode_from_disk_ts(from->di_ctime);
+       inode->i_atime = xfs_inode_from_disk_ts(from, from->di_atime);
+       inode->i_mtime = xfs_inode_from_disk_ts(from, from->di_mtime);
+       inode->i_ctime = xfs_inode_from_disk_ts(from, from->di_ctime);
 
        to->di_size = be64_to_cpu(from->di_size);
        to->di_nblocks = be64_to_cpu(from->di_nblocks);
@@ -238,7 +253,7 @@ xfs_inode_from_disk(
        if (xfs_sb_version_has_v3inode(&ip->i_mount->m_sb)) {
                inode_set_iversion_queried(inode,
                                           be64_to_cpu(from->di_changecount));
-               to->di_crtime = xfs_inode_from_disk_ts(from->di_crtime);
+               to->di_crtime = xfs_inode_from_disk_ts(from, from->di_crtime);
                to->di_flags2 = be64_to_cpu(from->di_flags2);
                to->di_cowextsize = be32_to_cpu(from->di_cowextsize);
        }
@@ -263,11 +278,15 @@ out_destroy_data_fork:
 /* Convert an incore timestamp to an ondisk timestamp. */
 static inline xfs_timestamp_t
 xfs_inode_to_disk_ts(
+       struct xfs_inode                *ip,
        const struct timespec64         tv)
 {
        struct xfs_legacy_timestamp     *lts;
        xfs_timestamp_t                 ts;
 
+       if (xfs_inode_has_bigtime(ip))
+               return cpu_to_be64(xfs_inode_encode_bigtime(tv));
+
        lts = (struct xfs_legacy_timestamp *)&ts;
        lts->t_sec = cpu_to_be32(tv.tv_sec);
        lts->t_nsec = cpu_to_be32(tv.tv_nsec);
@@ -294,9 +313,9 @@ xfs_inode_to_disk(
        to->di_projid_hi = cpu_to_be16(from->di_projid >> 16);
 
        memset(to->di_pad, 0, sizeof(to->di_pad));
-       to->di_atime = xfs_inode_to_disk_ts(inode->i_atime);
-       to->di_mtime = xfs_inode_to_disk_ts(inode->i_mtime);
-       to->di_ctime = xfs_inode_to_disk_ts(inode->i_ctime);
+       to->di_atime = xfs_inode_to_disk_ts(ip, inode->i_atime);
+       to->di_mtime = xfs_inode_to_disk_ts(ip, inode->i_mtime);
+       to->di_ctime = xfs_inode_to_disk_ts(ip, inode->i_ctime);
        to->di_nlink = cpu_to_be32(inode->i_nlink);
        to->di_gen = cpu_to_be32(inode->i_generation);
        to->di_mode = cpu_to_be16(inode->i_mode);
@@ -315,7 +334,7 @@ xfs_inode_to_disk(
        if (xfs_sb_version_has_v3inode(&ip->i_mount->m_sb)) {
                to->di_version = 3;
                to->di_changecount = cpu_to_be64(inode_peek_iversion(inode));
-               to->di_crtime = xfs_inode_to_disk_ts(from->di_crtime);
+               to->di_crtime = xfs_inode_to_disk_ts(ip, from->di_crtime);
                to->di_flags2 = cpu_to_be64(from->di_flags2);
                to->di_cowextsize = cpu_to_be32(from->di_cowextsize);
                to->di_ino = cpu_to_be64(ip->i_ino);
@@ -535,6 +554,11 @@ xfs_dinode_verify(
        if (fa)
                return fa;
 
+       /* bigtime iflag can only happen on bigtime filesystems */
+       if (xfs_dinode_has_bigtime(dip) &&
+           !xfs_sb_version_hasbigtime(&mp->m_sb))
+               return __this_address;
+
        return NULL;
 }
 
index 3060ecd24a2ef51050ecf21ef18ebd66e63f8892..536666143fe7b2ab04a48924954fc75ffc48ffa4 100644 (file)
@@ -32,6 +32,11 @@ struct xfs_icdinode {
        struct timespec64 di_crtime;    /* time created */
 };
 
+static inline bool xfs_icdinode_has_bigtime(const struct xfs_icdinode *icd)
+{
+       return icd->di_flags2 & XFS_DIFLAG2_BIGTIME;
+}
+
 /*
  * Inode location information.  Stored in the inode and passed to
  * xfs_imap_to_bp() to get a buffer and dinode for a given inode.
@@ -58,6 +63,12 @@ xfs_failaddr_t xfs_inode_validate_cowextsize(struct xfs_mount *mp,
                uint32_t cowextsize, uint16_t mode, uint16_t flags,
                uint64_t flags2);
 
-struct timespec64 xfs_inode_from_disk_ts(const xfs_timestamp_t ts);
+static inline uint64_t xfs_inode_encode_bigtime(struct timespec64 tv)
+{
+       return xfs_unix_to_bigtime(tv.tv_sec) * NSEC_PER_SEC + tv.tv_nsec;
+}
+
+struct timespec64 xfs_inode_from_disk_ts(struct xfs_dinode *dip,
+               const xfs_timestamp_t ts);
 
 #endif /* __XFS_INODE_BUF_H__ */
index 7c7e56a8979c105e38b6e0d05e2346418a520ba8..fb2212b899121f3f49c9d871781fc5f1043d117a 100644 (file)
@@ -1143,6 +1143,8 @@ xfs_fs_geometry(
                geo->flags |= XFS_FSOP_GEOM_FLAGS_RMAPBT;
        if (xfs_sb_version_hasreflink(sbp))
                geo->flags |= XFS_FSOP_GEOM_FLAGS_REFLINK;
+       if (xfs_sb_version_hasbigtime(sbp))
+               geo->flags |= XFS_FSOP_GEOM_FLAGS_BIGTIME;
        if (xfs_sb_version_hassector(sbp))
                geo->logsectsize = sbp->sb_logsectsize;
        else
index 708feb8eac7663916c62156f89d5952c9226102c..c795ae47b3c915a199ef09873110f4d034a1913b 100644 (file)
@@ -176,6 +176,9 @@ struct xfs_ino_geometry {
        unsigned int    ialloc_align;
 
        unsigned int    agino_log;      /* #bits for agino in inum */
+
+       /* precomputed value for di_flags2 */
+       uint64_t        new_diflags2;
 };
 
 #endif /* __XFS_SHARED_H__ */
index a392fd293d25bd2d333ac0cb57dc6b557b53c086..66dadd8716eef94d993c46963b301f8df0cc76d3 100644 (file)
@@ -128,6 +128,17 @@ xfs_trans_log_inode(
                        iversion_flags = XFS_ILOG_CORE;
        }
 
+       /*
+        * If we're updating the inode core or the timestamps and it's possible
+        * to upgrade this inode to bigtime format, do so now.
+        */
+       if ((flags & (XFS_ILOG_CORE | XFS_ILOG_TIMESTAMP)) &&
+           xfs_sb_version_hasbigtime(&ip->i_mount->m_sb) &&
+           !xfs_inode_has_bigtime(ip)) {
+               ip->i_d.di_flags2 |= XFS_DIFLAG2_BIGTIME;
+               flags |= XFS_ILOG_CORE;
+       }
+
        /*
         * Record the specific change for fdatasync optimisation. This allows
         * fdatasync to skip log forces for inodes that are only timestamp