Linux file creation date

While I am working on some code to copy files from one place to another, I was looking at preserving the metadata. Something equivalent to cp -a in shell. In Rust, std::fs::copy does preserve the attributes, but unfortunately not the file modification dates so I needed to explore what are the options.

While I will be mentioning Rust, this is not a Rust specific issue. However it is Linux oriented.

On Linux (and to some extent other UNIX-like), there are 4 timestamps: mtime is the modification date, atime is the access date, ctime the change date, and then btime the file creation date. The b stands for birth.

In the shell

You can view these using the stat command.

$ stat meson.build
  File: meson.build
  Size: 4616      	Blocks: 16         IO Block: 4096   regular file
Device: 0,40	Inode: 290010300   Links: 1
Access: (0644/-rw-r--r--)  Uid: ( 1000/     hub)   Gid: ( 1000/     hub)
Access: 2023-03-28 23:24:14.871754275 -0400
Modify: 2023-03-14 22:32:36.909471008 -0400
Change: 2023-03-14 22:32:36.909471008 -0400
 Birth: 2023-03-14 22:32:36.909471008 -0400
$

Using the command touch allow changing the modification date mtime.

$ touch meson.build
$ stat meson.build
  File: meson.build
  Size: 4616      	Blocks: 16         IO Block: 4096   regular file
Device: 0,40	Inode: 290010300   Links: 1
Access: (0644/-rw-r--r--)  Uid: ( 1000/     hub)   Gid: ( 1000/     hub)
Access: 2023-03-29 11:20:20.635884381 -0400
Modify: 2023-03-29 11:20:20.635884381 -0400
Change: 2023-03-29 11:20:20.635884381 -0400
 Birth: 2023-03-14 22:32:36.909471008 -0400
$

It also changed access atime and changed ctime. The latter is changed because setting any of these attribue is a change. Timestamp precision up to the nanosecond is supported since Linux 2.6, on some filesystems (notably not ext2/ext3 and reiserfs) and this is reflected here.

What does cp -a do?

$ cp -a meson.build meson.build-copy
$ stat meson.build-copy
  File: meson.build-copy
  Size: 4616      	Blocks: 16         IO Block: 4096   regular file
Device: 0,40	Inode: 291019850   Links: 1
Access: (0644/-rw-r--r--)  Uid: ( 1000/     hub)   Gid: ( 1000/     hub)
Access: 2023-03-29 11:20:20.635884381 -0400
Modify: 2023-03-29 11:20:20.635884381 -0400
Change: 2023-03-29 12:42:37.084867384 -0400
 Birth: 2023-03-29 12:42:37.080867326 -0400

While it does preserve the access and modification times, the birth and change time are new.

This mean that setting the creation date of the file is unecessary to mimic cp -a behaviour.

Programatically

Underneath the stat commands used stat() from the libc, or more likely the newer and Linux specific statx() (Linux kernel 4.11 / glibc 2.28).

stat() allows you to get informations about a file, including atime, mtime, and ctime, which are the only one we care about here. If you want to write portable code (ie not specific to modern Linux), this is what you should use. In Rust, these are exposed through the std::os::unix::fs::MetadataExt trait that is, as its path implies, UNIX specific. This mean (mostly) everything but Windows. You can also use stat from the nix crate, which is a direct safe binding on the libc function.

You can set the modification and access times using the utimes() API, but it doesn’t provide nanosecond precision. The newer Linux utimensat() allow this precision. In Rust there is nothing on the standard library to perform this, so you need to use the nix crate again, or if you prefer the unsafe libc crate that exposes the libc API.

Here is an example in Rust:

use nix::sys::stat::{stat, utimensat, UtimensatFlags};
use nix::sys::time::TimeSpec;

pub fn copy_utimes<P, Q>(from: P, to: Q) -> std::io::Result<()>
where
    P: AsRef<Path>,
    Q: AsRef<Path>,
{
    let file_stat = stat(from.as_ref())?;
    utimensat(
        None,
        to.as_ref(),
        &TimeSpec::new(file_stat.st_atime, file_stat.st_atime_nsec),
        &TimeSpec::new(file_stat.st_mtime, file_stat.st_mtime_nsec),
        UtimensatFlags::NoFollowSymlink,
    )?;
    Ok(())
}

But what about the birth time btime? You have to use statx(), on Linux 4.11 or later to get it. In Rust, statx is not in the nix crate. You can eventually get a SystemTime with std::fs::Metadata::created() or call the unsafe libc::statx().

If you check the man page for inode, you will learn that btime not returned by stat(), since it was not historically present on UNIX, that it is not supported by most Linux filesystems, and, that’s the important detail, that it is not changed after being set at file creation. And I couldn’t find any API that would allow to set it.

So to answer the question How to set the file creation date on Linux?: you can’t.

Rationale

It took a bit of more research as to why this is not possible. Among the reasons, the lack of support for the attribute in several Linux filesystems, I found a thread for a patch submission to implement setting the birth time in the Linux kernel with utimensat(). The few arguments for the rejection are that birth time is not user creation time, that it is useful for forensic, either for post-mortem corruption analysis or compromise investigation. While it is not full proof, its immutability has a certain value when investigating and making it mutable would complicate things a lot in the filesystem code to be safely implemented. Foreign virtual filesystems (ie file sharing like CIFS) can use extended attributes to do the job; it is what Samba does for its CIFS implementation.

Conclusion

Let’s copy mtime and atime and leave the rest alone. I should investigate the extended attributes, but in that context it might not be useful, and portability might be a concern.