tldr;
これの詳しい話. また, オンラインでも変換できることを示す.
XFSのbigtimeとは?
XFSのタイムスタンプは現状では, signed 32bitがUNIXエポックタイム(1970年1月1日 00:00:00 UTC)からの秒数で, 32bitがナノ秒部分となっています. これだと2038年以後の日時を表現できなくなります. そこで, その2つのフィールドをまとめて64bitにして, signed 32bitで表現できていた最小のタイムスタンプ(1901年12月13日 20:45:52 UTC, signedなのでマイナスも表現できてエポックより前のここが最小)からの経過ナノ秒でタイムスタンプを表現することにします. これがbigtimeという機能で, 2486年7月頃までの表現が可能になります.
ようするに, これが
struct xfs_legacy_timestamp {
__be32 t_sec;
__be32 t_nsec;
};
これになる
typedef __be64 xfs_timestamp_t;
bigtimeの変換を見てみよう
おおよそ10年以内, 2013年以後に作られたXFSであれば, オフラインで(mountしていない状態で)bigtimeを有効にできます. 実験してみましょう.
まずは, 適当なところにXFSのファイルシステムイメージを作りmountします. もうデフォルトでbigtimeが有効な場合もあるので, "-m bigtime=0"でbigtimeを明示的に無効化しておきます.
$ rm xfs2.img
$ truncate -s 10G xfs2.img
$ mkfs.xfs -m bigtime=0 xfs2.img
meta-data=xfs2.img isize=512 agcount=4, agsize=655360 blks
= sectsz=512 attr=2, projid32bit=1
= crc=1 finobt=1, sparse=1, rmapbt=1
= reflink=1 bigtime=0 inobtcount=1 nrext64=1
= exchange=0
data = bsize=4096 blocks=2621440, imaxpct=25
= sunit=0 swidth=0 blks
naming =version 2 bsize=4096 ascii-ci=0, ftype=1, parent=0
log =internal log bsize=4096 blocks=16384, version=2
= sectsz=512 sunit=0 blks, lazy-count=1
realtime =none extsz=4096 blocks=0, rtextents=0
$ sudo mount xfs2.img /mnt/scratch
"old"というファイルに適当なタイムスタンプをつけて, umountします.
$ sudo touch -t 202410160900 /mnt/scratch/old
$ sudo umount /mnt/scratch
xfs_adminでbigtimeを有効にします. さくっと終わります.
$ xfs_admin -O bigtime=1 xfs2.img
Running xfs_repair to upgrade filesystem.
...
Phase 1 - find and verify superblock...
...
Phase 7 - verify and correct link counts...
done
新しいファイル"new"に同じタイムスタンプをつけて, umountします.
$ sudo mount xfs2.img /mnt/scratch
$ sudo touch -t 202410160900 /mnt/scratch/new
$ sudo umount /mnt/scratch
ここからxfs_dbを使ってinodeをチェックします. まず"info"でスーパーブロックの情報を見ると, "bigtime=1"とフラグがついていることがわかります.
xfs_db> info
meta-data=xfs.img isize=512 agcount=4, agsize=655360 blks
= sectsz=512 attr=2, projid32bit=1
= crc=1 finobt=1, sparse=1, rmapbt=1
= reflink=1 bigtime=1 inobtcount=1 nrext64=1
= exchange=0
data = bsize=4096 blocks=2621440, imaxpct=25
= sunit=0 swidth=0 blks
naming =version 2 bsize=4096 ascii-ci=0, ftype=1, parent=0
log =internal log bsize=4096 blocks=16384, version=2
= sectsz=512 sunit=0 blks, lazy-count=1
realtime =none extsz=4096 blocks=0, rtextents=0
次に2つのファイルのinodeの中の情報を見ます. まずは"ls /"で2つのファイルのinode番号を確認. 131と132です.
$ xfs_db xfs2.img
xfs_db> ls /
/:
8 128 directory 0x0000002e 1 . (good)
10 128 directory 0x0000172e 2 .. (good)
12 131 regular 0x001bf664 3 old (good)
14 132 regular 0x001bb2f7 3 new (good)
inode番号131であるファイル"old"を見ます. "v3.bigtime = 0"とinodeの内部にもフラグがあります.
xfs_db> inode 131
xfs_db> p
...
core.atime.sec = Wed Oct 16 09:00:00 2024
core.atime.nsec = 0
core.mtime.sec = Wed Oct 16 09:00:00 2024
core.mtime.nsec = 0
core.ctime.sec = Wed Oct 16 09:21:57 2024
core.ctime.nsec = 153635926
...
v3.bigtime = 0
...
一方, inode番号132であるファイル"new"を見ます. 今度は"v3.bigtime = 1"です.
xfs_db> inode 132
xfs_db> p
...
core.atime.sec = Wed Oct 16 09:00:00 2024
core.atime.nsec = 0
core.mtime.sec = Wed Oct 16 09:00:00 2024
core.mtime.nsec = 0
core.ctime.sec = Wed Oct 16 09:23:32 2024
core.ctime.nsec = 281349756
...
v3.bigtime = 1
...
すなわち, スーパーブロックとそれぞれのinodeの両方にbigtimeのフラグがあります. それぞれのinodeのbigtimeフラグは, そのinode内のタイムスタンプが新旧どちらの表現であるかを示します. スーパーブロックのフラグが立っていれば, 新しいファイルやファイルの書きかえ時に新しいフォーマットのタイムスタンプに書き変わります.
ダンプを見よう
バイナリダンプを見とかないと終われないので見ましょう.
ファイル"old"はinode番号が131で, inodeのサイズが512バイト(infoのisizeに書いてる)なので, これで見れます.
先頭の"IN"でちゃんとinodeをdumpできていることが確認できます. 周りをあけている, 0x10620からの2行がタイムスタンプ(atime, mtime, ctime)です. dateコマンドでチェックしたところ(big-endianに注意), ちゃんと設定したタイムスタンプが入っています.
$ hexdump -C -s $((131 * 512)) -n 512 xfs2.img
00010600 49 4e 81 a4 03 02 00 00 00 00 00 00 00 00 00 00 |IN..............|
00010610 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00010620 67 0f 02 00 00 00 00 00 67 0f 02 00 00 00 00 00 |g.......g.......|
00010630 67 0f 07 25 09 28 4c 56 00 00 00 00 00 00 00 00 |g..%.(LV........|
00010640 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00010650 00 00 18 01 00 00 00 00 00 00 00 00 56 5c 1c 43 |............V\.C|
00010660 ff ff ff ff f4 58 94 32 00 00 00 00 00 00 00 04 |.....X.2........|
00010670 00 00 00 01 00 00 00 04 00 00 00 00 00 00 00 10 |................|
00010680 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00010690 67 0f 07 25 09 28 4c 56 00 00 00 00 00 00 00 83 |g..%.(LV........|
000106a0 2c 82 1d 4d 14 58 4a 73 8c dd 20 29 7f c7 88 dd |,..M.XJs.. )....|
000106b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00010770 00 2b 01 00 07 1d 04 73 65 6c 69 6e 75 78 73 74 |.+.....selinuxst|
00010780 61 66 66 5f 75 3a 6f 62 6a 65 63 74 5f 72 3a 75 |aff_u:object_r:u|
00010790 6e 6c 61 62 65 6c 65 64 5f 74 00 00 00 00 00 00 |nlabeled_t......|
000107a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00010800
$ date --date=@$((0x670f0200))
Wed Oct 16 09:00:00 AM JST 2024
一方, ファイル"new"の方はこうです. 同様にタイムスタンプの周囲をあけています.
$ hexdump -C -s $((132 * 512)) -n 512 xfs2.img
00010800 49 4e 81 a4 03 02 00 00 00 00 00 00 00 00 00 00 |IN..............|
00010810 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00010820 35 cc 2a cf 0b 94 00 00 35 cc 2a cf 0b 94 00 00 |5.*.....5.*.....|
00010830 35 cc 2c 17 de 1b 36 7c 00 00 00 00 00 00 00 00 |5.,...6|........|
00010840 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00010850 00 00 18 01 00 00 00 00 00 00 00 00 36 2f 08 59 |............6/.Y|
00010860 ff ff ff ff 38 e7 18 5c 00 00 00 00 00 00 00 04 |....8..\........|
00010870 00 00 00 01 00 00 00 0f 00 00 00 00 00 00 00 18 |................|
00010880 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00010890 35 cc 2c 17 de 1b 36 7c 00 00 00 00 00 00 00 84 |5.,...6|........|
000108a0 2c 82 1d 4d 14 58 4a 73 8c dd 20 29 7f c7 88 dd |,..M.XJs.. )....|
000108b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00010970 00 2b 01 00 07 1d 04 73 65 6c 69 6e 75 78 73 74 |.+.....selinuxst|
00010980 61 66 66 5f 75 3a 6f 62 6a 65 63 74 5f 72 3a 75 |aff_u:object_r:u|
00010990 6e 6c 61 62 65 6c 65 64 5f 74 00 00 00 00 00 00 |nlabeled_t......|
000109a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00010a00
タイムスタンプはナノ秒なので…とdateコマンドにスケールだけ変えてつっこむと未来が見えてしまいます. 最初に書いたようにこれは, 昔のタイムスタンプの最小値からのナノ秒です. その値は…"-2147483648" …ってイコール 2の31乗ですね. signed 32bitの最小なのでそれはそう. そのぶん補正すると, ちゃんと同じタイムスタンプがとれます.
$ date --date=@$((0x35cc2acf0b940000/1000000000))
Mon Nov 3 12:14:08 PM JST 2092
$ date --date='Dec 13 20:45:52 UTC 1901' +%s
-2147483648
$ date --date=@$((0x35cc2acf0b940000/1000000000 - 2**31))
Wed Oct 16 09:00:00 AM JST 2024
ここでもう一度"old"を同じタイムスタンプでtouchして, umountして, dumpをとります. するとすると, ちゃんと古いフォーマットからbigtimeのフォーマットに変換されているのが見てとれます.
$ hexdump -C -s $((131 * 512)) -n $((512 * 1)) xfs2.img
00010600 49 4e 81 a4 03 02 00 00 00 00 00 00 00 00 00 00 |IN..............|
00010610 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00010620 35 cc 2a cf 0b 94 00 00 35 cc 2a cf 0b 94 00 00 |5.*.....5.*.....|
00010630 35 cc 2d fa 8d a4 e9 c7 00 00 00 00 00 00 00 00 |5.-.............|
00010640 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00010650 00 00 18 01 00 00 00 00 00 00 00 00 56 5c 1c 43 |............V\.C|
...
むりやり online convertしよう!
さて上記まで見たところ, bigtimeを有効にしても既存のファイルのタイムスタンプはただちに変換されていないことがわかります. 各inodeにフラグがあるので, 次の更新までは元のフォーマットのまま放置していてもかまいせん. xfs_adminがやっていることは結局スーパーブロックのbitを1つ立てているにすぎません.
そんなたかだか1bitのために, umountするなんて嫌じゃないですか? bit1つぐらい実行時で立ててもいいじゃん. 理論上できるでしょ. 立ててやりましょう. kernel moduleで.
!!! 以下のコードは絶対に実行しないようにしましょう. ファイルシステムやシステムが破壊される可能性があります. 責任は一切とれません !!!
お使いのエディタから以下のコードを書きました. 今日はnvimにしました.
"/mnt/tmp"にmountされているXFSを確認し, そのfeatureフラグを立てます. そのフラグはメモリ上にしか影響しないので, diskに書かれるsuperblockのフラグも立てて, 後でxfs_repairに怒られないようにします. ただそれだけのkernel moduleです.
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/nsproxy.h>
#include <linux/namei.h>
#if 0
#endif
static int init_xfs_conv(void)
{
return 0;
const char *MNT_DIR = "/mnt/tmp";
pr_info("xfs online convert start");
struct path path __free(path_put) = {};
int ret;
ret = kern_path(MNT_DIR, LOOKUP_FOLLOW, &path);
if (ret) {
pr_err("failed to open %s %d", MNT_DIR, ret);
return ret;
}
struct super_block *sb = path.mnt->mnt_sb;
pr_info("xfs sb %px", sb);
struct xfs_mount *mp = XFS_M(sb);
pr_info("xfs magic %x", mp->m_sb.sb_magicnum);
if (mp->m_sb.sb_magicnum != XFS_SB_MAGIC) {
pr_err("invalid xfs magic");
return -EINVAL;
}
uint64_t features = READ_ONCE(mp->m_features);
pr_info("xfs feature %llx", features);
if (features & XFS_FEAT_BIGTIME) {
pr_info("already has XFS_FEAT_BIGTIME");
return 0;
}
return 0;
features |= XFS_FEAT_BIGTIME;
WRITE_ONCE(mp->m_features, features);
uint64_t sb_feautres = READ_ONCE(mp->m_sb.sb_features_incompat);
sb_feautres |= XFS_SB_FEAT_INCOMPAT_BIGTIME;
WRITE_ONCE(mp->m_sb.sb_features_incompat, sb_feautres);
pr_info("BIGTIME enabled");
return 0;
}
static void exit_xfs_conv(void)
{
pr_info("xfs online convert finish");
}
module_init(init_xfs_conv);
module_exit(exit_xfs_conv);
MODULE_DESCRIPTION("DO NOT USE!!! Online Convert XFS!");
MODULE_LICENSE("GPL");
同様に, XFSをmountして"old"をtouchします. さっきのコードをコンパイルしてinsmodしてrmmodします. なんかいい感じに動いたみたい.
$ (mountとか)
$ sudo touch -t 202410160900 /mnt/tmp/old
$ sudo insmod ./xfs-conv.ko; sudo rmmod xfs-conv; dmesg|grep xfs_conv:
[86968.662739] xfs_conv: xfs online convert start
[86968.662765] xfs_conv: xfs sb ffff8ad326471000
[86968.662768] xfs_conv: xfs magic 58465342
[86968.662769] xfs_conv: xfs feature 49ff6ab
[86968.662771] xfs_conv: BIGTIME enabled
[86968.694864] xfs_conv: xfs online convert finish
できたかな?ということで, 同様に"new"を作ってsyncします. それぞれdumpを見ます.
まずは"old"の方. ちゃんと昔のタイムスタンプ.
$ hexdump -C -s $((131 * 512)) -n $((512 * 1)) xfs.img
00010600 49 4e 81 a4 03 02 00 00 00 00 00 00 00 00 00 00 |IN..............|
00010610 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00010620 67 0f 02 00 00 00 00 00 67 0f 02 00 00 00 00 00 |g.......g.......|
00010630 67 0f 46 7e 03 cf 92 12 00 00 00 00 00 00 00 00 |g.F~............|
00010640 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00010650 00 00 18 01 00 00 00 00 00 00 00 00 e2 73 9e fe |.............s..|
...
そして"new"の方. やったぜ新しいタイムスタンプフォーマットで書かれています.
$ hexdump -C -s $((132 * 512)) -n $((512 * 1)) xfs.img
00010800 49 4e 81 a4 03 02 00 00 00 00 00 00 00 00 00 00 |IN..............|
00010810 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00010820 35 cc 2a cf 0b 94 00 00 35 cc 2a cf 0b 94 00 00 |5.*.....5.*.....|
00010830 35 cc 3a cc e7 3c 8c d0 00 00 00 00 00 00 00 00 |5.:..<..........|
00010840 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00010850 00 00 18 01 00 00 00 00 00 00 00 00 41 48 72 11 |............AHr.|
...
"old"の方をもう一度touchしてsyncなどします. dumpを見ると…新しいフォーマットに変換されました.
$ sudo touch /mnt/tmp/old
$ hexdump -C -s $((131 * 512)) -n $((512 * 1)) xfs.img
00010600 49 4e 81 a4 03 02 00 00 00 00 00 00 00 00 00 00 |IN..............|
00010610 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00010620 35 cc 2a cf 0b 94 00 00 35 cc 2a cf 0b 94 00 00 |5.*.....5.*.....|
00010630 35 cc 3a d4 a5 d3 2f 09 00 00 00 00 00 00 00 00 |5.:.../.........|
00010640 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00010650 00 00 18 01 00 00 00 00 00 00 00 00 e2 73 9e fe |.............s..|
...
つまり, kernel moduleを書いてbitを立てればいいだけなので, XFSのbigtimeはonline変換できて安心だよということになります.
…というのは言いすぎにしろ, このように(trivialなcaseだけど)実際にonline変換はできるのであるから, そのうちちゃんと実装されるかもしれませんね.
convertできない場合
XFSのbigtime有効化について, offlineでの変換・onlineでの変換を見てきました. しかし, このような変換ができない場合もあります. 現在のXFSのフォーマットはv5ですが, v4以前のフォーマットのものは, このような変換はできません. v4からv5へはバックアップ・リストア以外に方法はないです. v5が出たのは2013年なので, その頃より前のファイルシステムではv4であるかもしれません.
v5では, メタデータにCRCがついたり, 全てのメタデータがself describing metadata (metadata冒頭に"IN"など, マジックが入る)となるなどの変更が入っています. この変更はあまりに大規模なので, バックアップ・リストアになるしかないのでしょう.
ということでみなさん快適な2038年をお過ごしください.
P.S.
こういう話が好きな人は11月9日に富山に行くといいようですよ.
kernelvm.connpass.com