372 lines
13 KiB
Diff
Executable File
372 lines
13 KiB
Diff
Executable File
From e7e3beb95efd751f227a0ced4c83fc5b88582e2e Mon Sep 17 00:00:00 2001
|
|
From: "Darrick J. Wong" <darrick.wong@oracle.com>
|
|
Date: Wed, 11 Nov 2020 20:08:14 -0500
|
|
Subject: [PATCH] xfs: widen ondisk inode timestamps to deal with y2038+
|
|
|
|
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>
|
|
---
|
|
|
|
NOTE: xfs_trans_inode.c was not brought over in this version, so code
|
|
landed in trans.c
|
|
|
|
We also do not have the pre-computed geometry, so that needs to be
|
|
explicitly added to libxfs_ialloc rather than inheriting from igeo.
|
|
|
|
diff --git a/include/xfs_inode.h b/include/xfs_inode.h
|
|
index ddd48be..25f2eac 100644
|
|
--- a/include/xfs_inode.h
|
|
+++ b/include/xfs_inode.h
|
|
@@ -146,6 +146,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;
|
|
diff --git a/libxfs/trans.c b/libxfs/trans.c
|
|
index db90624..54e4dd6 100644
|
|
--- a/libxfs/trans.c
|
|
+++ b/libxfs/trans.c
|
|
@@ -415,6 +415,17 @@ xfs_trans_log_inode(
|
|
tp->t_flags |= XFS_TRANS_DIRTY;
|
|
set_bit(XFS_LI_DIRTY, &ip->i_itemp->ili_item.li_flags);
|
|
|
|
+ /*
|
|
+ * 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;
|
|
+ }
|
|
+
|
|
/*
|
|
* Always OR in the bits from the ili_last_fields field.
|
|
* This is to coordinate with the xfs_iflush() and xfs_iflush_done()
|
|
diff --git a/libxfs/util.c b/libxfs/util.c
|
|
index 9383bb8..7a8729f 100644
|
|
--- a/libxfs/util.c
|
|
+++ b/libxfs/util.c
|
|
@@ -222,7 +222,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;
|
|
@@ -317,8 +318,14 @@ 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);
|
|
+ if (pip) {
|
|
+ ip->i_d.di_flags2 = 0;
|
|
+ if (xfs_sb_version_hasbigtime(&ip->i_mount->m_sb))
|
|
+ ip->i_d.di_flags2 |= XFS_DIFLAG2_BIGTIME;
|
|
+ } else {
|
|
+ ip->i_d.di_flags2 = 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;
|
|
diff --git a/libxfs/xfs_format.h b/libxfs/xfs_format.h
|
|
index 371f5cd..b1f6219 100644
|
|
--- a/libxfs/xfs_format.h
|
|
+++ b/libxfs/xfs_format.h
|
|
@@ -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| \
|
|
@@ -580,6 +581,12 @@ xfs_is_quota_inode(struct xfs_sb *sbp, xfs_ino_t ino)
|
|
#define XFS_FSB_TO_DADDR(mp,fsbno) XFS_AGB_TO_DADDR(mp, \
|
|
XFS_FSB_TO_AGNO(mp,fsbno), XFS_FSB_TO_AGBNO(mp,fsbno))
|
|
|
|
+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);
|
|
+}
|
|
+
|
|
/*
|
|
* File system sector to basic block conversions.
|
|
*/
|
|
@@ -849,6 +856,13 @@ typedef 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;
|
|
|
|
@@ -870,6 +884,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.
|
|
*
|
|
@@ -1096,12 +1154,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:
|
|
diff --git a/libxfs/xfs_fs.h b/libxfs/xfs_fs.h
|
|
index 4fa9852..714dba1 100644
|
|
--- a/libxfs/xfs_fs.h
|
|
+++ b/libxfs/xfs_fs.h
|
|
@@ -231,6 +231,7 @@ typedef struct xfs_fsop_resblks {
|
|
#define XFS_FSOP_GEOM_FLAGS_SPINODES 0x40000 /* sparse inode chunks */
|
|
#define XFS_FSOP_GEOM_FLAGS_RMAPBT 0x80000 /* reverse mapping btree */
|
|
#define XFS_FSOP_GEOM_FLAGS_REFLINK 0x100000 /* files can share blocks */
|
|
+#define XFS_FSOP_GEOM_FLAGS_BIGTIME 0x200000 /* 64-bit nsec timestamps */
|
|
|
|
/*
|
|
* Minimum and maximum sizes need for growth checks.
|
|
diff --git a/libxfs/xfs_inode_buf.c b/libxfs/xfs_inode_buf.c
|
|
index d8831a1..8cd16bf 100644
|
|
--- a/libxfs/xfs_inode_buf.c
|
|
+++ b/libxfs/xfs_inode_buf.c
|
|
@@ -195,14 +195,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);
|
|
@@ -246,9 +261,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);
|
|
|
|
inode->i_generation = be32_to_cpu(from->di_gen);
|
|
inode->i_mode = be16_to_cpu(from->di_mode);
|
|
@@ -267,7 +282,7 @@ xfs_inode_from_disk(
|
|
if (to->di_version == 3) {
|
|
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);
|
|
}
|
|
@@ -276,11 +291,15 @@ xfs_inode_from_disk(
|
|
/* 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);
|
|
@@ -308,9 +327,9 @@ xfs_inode_to_disk(
|
|
to->di_projid_hi = cpu_to_be16(from->di_projid_hi);
|
|
|
|
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);
|
|
@@ -328,7 +347,7 @@ xfs_inode_to_disk(
|
|
|
|
if (from->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);
|
|
@@ -547,6 +566,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;
|
|
}
|
|
|
|
diff --git a/libxfs/xfs_inode_buf.h b/libxfs/xfs_inode_buf.h
|
|
index 6147f42..2b91e60 100644
|
|
--- a/libxfs/xfs_inode_buf.h
|
|
+++ b/libxfs/xfs_inode_buf.h
|
|
@@ -40,6 +40,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.
|
|
@@ -76,6 +81,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__ */
|
|
diff --git a/libxfs/xfs_sb.c b/libxfs/xfs_sb.c
|
|
index cee77a6..d11545b 100644
|
|
--- a/libxfs/xfs_sb.c
|
|
+++ b/libxfs/xfs_sb.c
|
|
@@ -1124,6 +1124,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
|