Commit f93e5436 authored by Darrick J. Wong's avatar Darrick J. Wong
Browse files

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: default avatarDarrick J. Wong <darrick.wong@oracle.com>
Reviewed-by: default avatarChristoph Hellwig <hch@lst.de>
Reviewed-by: default avatarGao Xiang <hsiangkao@redhat.com>
Reviewed-by: default avatarDave Chinner <dchinner@redhat.com>
parent 30e05599
Loading
Loading
Loading
Loading
+69 −1
Original line number Diff line number Diff line
@@ -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:
+1 −0
Original line number Diff line number Diff line
@@ -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.
+4 −0
Original line number Diff line number Diff line
@@ -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);
+32 −8
Original line number Diff line number Diff line
@@ -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;
}

+12 −1
Original line number Diff line number Diff line
@@ -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__ */
Loading