xfs: widen ondisk inode timestamps to deal with y2038+
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. 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>
This commit is contained in:
@@ -467,6 +467,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| \
|
||||
@@ -565,6 +566,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
|
||||
@@ -858,6 +865,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;
|
||||
|
||||
@@ -879,6 +893,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.
|
||||
*
|
||||
@@ -1104,12 +1162,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:
|
||||
|
@@ -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.
|
||||
|
@@ -2807,6 +2807,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);
|
||||
|
@@ -157,14 +157,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);
|
||||
@@ -226,9 +241,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);
|
||||
@@ -241,7 +256,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);
|
||||
}
|
||||
@@ -266,11 +281,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);
|
||||
@@ -297,9 +316,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);
|
||||
@@ -318,7 +337,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);
|
||||
@@ -538,6 +557,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;
|
||||
}
|
||||
|
||||
|
@@ -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__ */
|
||||
|
@@ -1166,6 +1166,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
|
||||
|
@@ -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__ */
|
||||
|
@@ -131,6 +131,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
|
||||
|
Reference in New Issue
Block a user