本文来自依云's Blog,转载请注明。
其实 Linux 是支持文件的创建时间的呢。不过不是所有文件系统都支持,比如 ext4、xfs、btrfs 都支持,zfs、vfat、ntfs 不支持。
但是呢,用户基本上是看不到的。文件系统有记录,但是没有 API 可以获取到这个数据。所以你用 stat 命令的话,会看到「创建时间」一行总是「-」。用 debugfs 搞 ext4 是可以的,但是那个需要 root 权限,并且一不小心会搞坏文件系统。
最近,我阅读内核源码时,忽然发现内核已经通过 4.11 版本引入的 statx 系统调用支持获取创建时间了。字段名里用的是 btime(birth time),没有用 crtime(creation time),也没有用大写的 Btime 呢。
但是 glibc 并没有支持,所以要用 syscall 函数来调用。也不是很复杂。不过我正着手用 Rust 实现的时候,却在内核源码树里找到了 samples/statx/test-statx.c 这么个文件。原来有现成的啊!
gcc 编译一下,还真好用:
>>> statx / statx(/) = 0 results=fff Size: 224 Blocks: 0 IO Block: 4096 directory Device: fe:01 Inode: 96 Links: 17 Access: (0755/drwxr-xr-x) Uid: 0 Gid: 0 Access: 2018-07-11 13:33:08.659477830+0800 Modify: 2018-03-30 15:06:02.645864827+0800 Change: 2018-03-30 15:06:02.645864827+0800 Birth: 2017-06-19 21:07:53.653467000+0800
2019年09月03日更新:现在(coreutils 8.31)stat 命令已经支持创建时间了。
Jul 11, 2018 07:02:11 PM
https://www.phoronix.com/scan.php?page=news_item&px=Glibc-Statx-Support
Jul 12, 2018 12:27:49 AM
撒花~(本来想用 emoji 的,然后想起来这里的数据库是 MySQL……
Jul 12, 2018 05:37:37 AM
不会编译 - - 求指导!
Jul 12, 2018 10:02:43 AM
gcc -O2 -o statx test-statx.c
Jul 12, 2018 12:10:59 PM
谢!
Jul 13, 2018 11:29:50 AM
这样方便多了 我一直有这个需求都是 debugfs 来搞
Jul 13, 2018 03:38:09 PM
好东西,本来想通过crtime来确定客户软件包的安装时间的,因为不知道搞定这个crtime,改用了yum的记录。。。
不过内核还是3.x的这个技术还是不能用。。。
Jul 14, 2018 04:29:03 PM
原来你还是没能在 2018 年摆脱 MySQL!
向你祈祷。
Jul 22, 2018 03:28:44 PM
试着用rust包了一个, 这个只支持4.11+不太方便, 也不知道以后rust会不会标准库就封装它,干脆就把它扔 utils 里了, https://github.com/biluohc/utils/statx
Jul 22, 2018 03:30:51 PM
https://github.com/biluohc/utils/tree/master/statx
Jul 22, 2018 07:26:14 PM
呀,你也写了~
PS: 不需要写 C 代码的哦,直接 ffi 调用 syscall 也是可以的。另外,那个 args() 不用转成 String 的,可以直接交给底层用,以便处理乱码文件名。
Aug 01, 2018 09:38:08 AM
谢谢指导了=_=
那个args确实应该直接使用,习惯性的转成String了,导致要来回倒格式,还有乱码问题。
不过这样写调用c就不用理会各种宏定义什么的了。
另外就是 syscall 的函数签名不清楚。
libc里封装的也有些奇妙,原来c支持变参啊。
[rustc] only foreign functions are allowed to be variadic
。
pub fn syscall(num: ::c_long, ...) -> ::c_long;
Aug 01, 2018 09:45:13 AM
对啊,用 syscall 的时候,把参数依次传过去就可以了。C 里一大堆函数都是变参的呢,连 open 都是。那个路径我是直接:
let path: &OsStr = path.as_ref().as_ref();
let path = path.as_bytes().as_ptr();
然后就可以传给 C 用了。
不过这种搞法在非系统 API 上不好办。比如 API 接收一个 C++ 的 std::string,我只好 &str -> &CStr -> const char* -> std::string 这么转过去了。
Oct 16, 2018 05:49:25 PM
把 charset 改成 utf8mb4 就支持 emoji 了(逃~