112

I was just reading up on the Birth section of stat and it appears ext4 should support it, but even a file I just created leaves it empty.

 ~ % touch test slave-iv ~ % stat test.pl slave-iv File: ‘test.pl’ Size: 173 Blocks: 8 IO Block: 4096 regular file Device: 903h/2307d Inode: 41943086 Links: 1 Access: (0600/-rw-------) Uid: ( 1000/xenoterracide) Gid: ( 100/ users) Access: 2012-09-22 18:22:16.924634497 -0500 Modify: 2012-09-22 18:22:16.924634497 -0500 Change: 2012-09-22 18:22:16.947967935 -0500 Birth: - ~ % sudo tune2fs -l /dev/md3 | psp4 slave-iv tune2fs 1.42.5 (29-Jul-2012) Filesystem volume name: home Last mounted on: /home Filesystem UUID: ab2e39fb-acdd-416a-9e10-b501498056de Filesystem magic number: 0xEF53 Filesystem revision #: 1 (dynamic) Filesystem features: has_journal ext_attr resize_inode dir_index filetype needs_recovery extent flex_bg sparse_super large_file huge_file uninit_bg dir_nlink extra_isize Filesystem flags: signed_directory_hash Default mount options: journal_data Filesystem state: clean Errors behavior: Continue Filesystem OS type: Linux Inode count: 59736064 Block count: 238920960 Reserved block count: 11946048 Free blocks: 34486248 Free inodes: 59610013 First block: 0 Block size: 4096 Fragment size: 4096 Reserved GDT blocks: 967 Blocks per group: 32768 Fragments per group: 32768 Inodes per group: 8192 Inode blocks per group: 512 RAID stride: 128 RAID stripe width: 256 Flex block group size: 16 Filesystem created: Mon May 31 20:36:30 2010 Last mount time: Sat Oct 6 11:01:01 2012 Last write time: Sat Oct 6 11:01:01 2012 Mount count: 14 Maximum mount count: 34 Last checked: Tue Jul 10 08:26:37 2012 Check interval: 15552000 (6 months) Next check after: Sun Jan 6 07:26:37 2013 Lifetime writes: 7255 GB Reserved blocks uid: 0 (user root) Reserved blocks gid: 0 (group root) First inode: 11 Inode size: 256 Required extra isize: 28 Desired extra isize: 28 Journal inode: 8 First orphan inode: 55313243 Default directory hash: half_md4 Directory Hash Seed: 442c66e8-8b67-4a8c-92a6-2e2d0c220044 Journal backup: inode blocks 

Why doesn't my ext4 partition populate this field?

0

6 Answers 6

134

The field gets populated (see below) only coreutils stat does not display it. Apparently they're waiting1 for the xstat() interface.

coreutils patches - aug. 2012 - TODO

stat(1) and ls(1) support for birth time. Dependent on xstat() being provided by the kernel

You can get the creation time via debugfs:

debugfs -R 'stat <inode_number>' DEVICE 

e.g. for my /etc/profile which is on /dev/sda2 (see How to find out what device a file is on):

stat -c %i /etc/profile 398264
debugfs -R 'stat <398264>' /dev/sda2 debugfs 1.42.5 (29-Jul-2012) Inode: 398264 Type: regular Mode: 0644 Flags: 0x80000 Generation: 2058737571 Version: 0x00000000:00000001 User: 0 Group: 0 Size: 562 File ACL: 0 Directory ACL: 0 Links: 1 Blockcount: 8 Fragment: Address: 0 Number: 0 Size: 0 ctime: 0x506b860b:19fa3c34 -- Wed Oct 3 02:25:47 2012 atime: 0x50476677:dcd84978 -- Wed Sep 5 16:49:27 2012 mtime: 0x506b860b:19fa3c34 -- Wed Oct 3 02:25:47 2012 crtime: 0x50476677:dcd84978 -- Wed Sep 5 16:49:27 2012 Size of extra inode fields: 28 EXTENTS: (0):3308774 

Time fields meaning:

  • ctime: file change time.
  • atime: file access time.
  • mtime: file modification time.
  • crtime: file creation time.

1 Linus' reply on LKML thread

12
  • 10
    @Sparhawk: I had this problem too with a file /home/user/path/to/file because /home was on a separate partition. In that case, the path provided to stat must be relative to /home. Example: sudo debugfs -R 'stat user/path/to/file' /dev/sda2. To get rid of the path handling, we can provide to stat the inode number instead of the path: sudo debugfs -R "stat <$(stat -c %i /home/user/path/to/file)>" /dev/sda5 Commented Apr 17, 2014 at 2:39
  • 3
    Can this be used to get creation time of files from a network-mounted filesystem? Commented Sep 20, 2018 at 18:32
  • 2
    So this is not a time stamp that goes beyond the creation of the file system. It means that if a file was created 25 years ago and copied through lots of different physical or mounted systems, there is no way at all to find the information of the date of creation in any of the metadata? So the only way to know when a file was made is to type it into the file name? Or inside the content? Is there any reason for this seemingly odd non implementation? Commented Mar 1, 2019 at 3:10
  • 2
    @sinekonata file metadata is very system dependent (as this answer shows, every layer of the OS must be able to process it) and keeping it across copies between machines relies on the support for that metadata format by both systems and the copying tool. What this means is: you're lucky if you even get the file name non-mangled. Alternatively, some file formats allow you to insert metadata inside the file (e.g. ID3), and that generally carries well, but many formats don't have such feature. Finally, you can put the file inside an archive file like Commented Apr 4, 2019 at 11:06
  • 7
    Note that the < and > around the inode number are required. They are often used in examples to surround a variable that should be adjusted, but in this case they must be entered literally. Without them, the inode number is treated as a path, and you get a File not found by ext2_lookup error. Commented Oct 15, 2019 at 15:49
48

I combined this into a simple shell function:

get_crtime() { for target in "${@}"; do inode=$(stat -c %i "${target}") fs=$(df --output=source "${target}" | tail -1) crtime=$(sudo debugfs -R 'stat <'"${inode}"'>' "${fs}" 2>/dev/null | grep -oP 'crtime.*--\s*\K.*') printf "%s\t%s\n" "${target}" "${crtime}" done } 

You can then run it with

$ get_crtime foo foo/file /etc/ foo Wed May 21 17:11:08 2014 foo/file Wed May 21 17:11:27 2014 /etc/ Wed Aug 1 20:42:03 2012 
8
  • it didn't work for me on my raspbian jessie. Commented Dec 3, 2019 at 15:01
  • 1
    @sergius yes, rapsbian does something strange and makes df return /dev/root which doesn't actually exist. I don't know what it's doing, but you might want to ask a new question about why df $HOME returns /dev/root on raspbian. That's what is making this fail. For more info on /dev/root see: Why on some Linux systems, does the root filesystem appear as /dev/root instead of /dev/in mtab? Commented Dec 3, 2019 at 16:00
  • yes, you're right. The correct path is /dev/mmcblk0p2 for raspbian (on a raspberry, of course). Thanks, i'll try to be more specific over this. Commented Dec 3, 2019 at 18:26
  • It would be convenient if you prefix sudo to all commands, because sometimes the target files can not be accessed under current user. Commented Apr 13, 2023 at 3:21
  • 1
    @osexp2003 oh duh, of course! I shouldn't answer comments before coffee, sorry. I that case though, I would just add this to root's configuration files. I don't want to run commands with sudo when it isn't absolutely needed since that adds unnecessary risk. Commented Apr 13, 2023 at 11:44
35

The xstat function never got merged into mainline. However, a new statx call was proposed later on, and was merged in Linux 4.11. The new statx(2) system call does include a creation time in its return struct. A wrapper for statx(2) was added to glibc only in 2.28 (release August 2018). And support for using this wrapper was added in GNU coreutils 8.31 (released March 2019):

stat now prints file creation time when supported by the file system, on GNU Linux systems with glibc >= 2.28 and kernel >= 4.11.

% stat --version stat (GNU coreutils) 8.31 Copyright (C) 2019 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>. This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Written by Michael Meskes. % stat / File: / Size: 4096 Blocks: 8 IO Block: 4096 directory Device: b302h/45826d Inode: 2 Links: 17 Access: (0755/drwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2019-06-06 20:03:12.898725626 +0900 Modify: 2019-05-28 05:15:44.452651395 +0900 Change: 2019-05-28 05:15:44.452651395 +0900 Birth: 2018-06-07 20:35:54.000000000 +0900 

What follows is a demo of statx where userland has yet to catch up (older glibc or coreutils). It's not easy to call system calls directly in a C program. Typically glibc provides a wrapper that makes the job easy, but Luckily, @whotwagner wrote a sample C program that shows how to use the statx(2) system call on x86 and x86-64 systems. Its output is the same format as stat's default, without any formatting options, but it's simple to modify it to print just the birth time. (If you have a new enough glibc, you won't need this - you can use statx directly as described in man 2 statx).

First, clone it:

git clone https://github.com/whotwagner/statx-fun 

You can compile the statx.c code, or, if you just want the birth time, create a birth.c in the cloned directory with the following code (which is a minimal version of statx.c printing just the creation timestamp including nanosecond precision):

#define _GNU_SOURCE #define _ATFILE_SOURCE #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> #include <fcntl.h> #include "statx.h" #include <time.h> #include <getopt.h> #include <string.h> // does not (yet) provide a wrapper for the statx() system call #include <sys/syscall.h> /* this code works ony with x86 and x86_64 */ #if __x86_64__ #define __NR_statx 332 #else #define __NR_statx 383 #endif #define statx(a,b,c,d,e) syscall(__NR_statx,(a),(b),(c),(d),(e)) int main(int argc, char *argv[]) { int dirfd = AT_FDCWD; int flags = AT_SYMLINK_NOFOLLOW; unsigned int mask = STATX_ALL; struct statx stxbuf; long ret = 0; int opt = 0; while(( opt = getopt(argc, argv, "alfd")) != -1) { switch(opt) { case 'a': flags |= AT_NO_AUTOMOUNT; break; case 'l': flags &= ~AT_SYMLINK_NOFOLLOW; break; case 'f': flags &= ~AT_STATX_SYNC_TYPE; flags |= AT_STATX_FORCE_SYNC; break; case 'd': flags &= ~AT_STATX_SYNC_TYPE; flags |= AT_STATX_DONT_SYNC; break; default: exit(EXIT_SUCCESS); break; } } if (optind >= argc) { exit(EXIT_FAILURE); } for (; optind < argc; optind++) { memset(&stxbuf, 0xbf, sizeof(stxbuf)); ret = statx(dirfd, argv[optind], flags, mask, &stxbuf); if( ret < 0) { perror("statx"); return EXIT_FAILURE; } printf("%lld.%u\n", *&stxbuf.stx_btime.tv_sec, *&stxbuf.stx_btime.tv_nsec); } return EXIT_SUCCESS; } 

Then:

$ make birth $ ./birth ./birth.c 1511793291.254337149 $ ./birth ./birth.c | xargs -I {} date -d @{} Mon Nov 27 14:34:51 UTC 2017 

In theory this should make the creation time accessible on more filesystems than just the ext* ones (debugfs is a tool for ext2/3/4 filesystems, and unusable on others). It did work for an XFS system, but not for NTFS and exfat. I guess the FUSE filesystems for those didn't include the creation time.

12

As of version 8.31 of GNU coreutils, stat “now prints file creation time when supported by the file system, on GNU Linux systems with glibc >= 2.28 and kernel >= 4.11.”

My OS (Ubuntu 20.04, which comes with Linux kernel 5.4.0-28 and GLIBC 2.31) only came with GNU coreutils 8.30, so I had to verify by compiling version 8.32 of GNU coreutils from source.

Output of ls -l --time=birth --time-style=full-iso --no-group:

$ ls -l --time=birth --time-style=full-iso --no-group total 13477610 -rwxr--r-- 1 systemd-coredump 13801071714 2017-06-30 04:53:33.211517792 -0400 file 

Output of stat on a btrfs file system:

$ stat file File: /mnt/btrfs/file Size: 13801071714 Blocks: 26955224 IO Block: 4096 regular file Device: 33h/51d Inode: 4998 Links: 1 Access: (0744/-rwxr--r--) Uid: ( 999/systemd-coredump) Gid: ( 999/systemd-coredump) Access: 2020-05-04 12:21:51.640487614 -0400 Modify: 2016-01-19 10:32:19.272000000 -0500 Change: 2017-06-30 04:55:14.910839537 -0400 Birth: 2017-06-30 04:53:33.211517792 -0400 

(Strangely, I was unable to get any birth time to show on Arch Linux using a dummy ext4 file system, despite it having met all the above requirements.)


OLD ANSWER:

Well, as of version 8.32 of GNU coreutils (stable release), both stat and ls use the statx call.

ls will show the creation/birth time.

** New Features

ls now supports the --time=birth option to display and sort by
file creation time, where available.

And presumably, stat will too.

** Improvements

stat and ls now use the statx() system call where available, which can operate more efficiently by only retrieving requested attributes.

It’s brand-spanking new, having been posted just March 5, 2020, so it may take a while to trickle down unless you’re using a bleeding-edge distro like Arch Linux. (Arch Linux got version 8.32-1 of the coreutils package on April 1, 2020).

3
  • stat itself has been showing the creation date sine 8.31 Commented Apr 30, 2020 at 4:25
  • I just got Ubuntu 20.04 up and running on my system. Unfortunately, it has version 8.30 of coreutils and that doesn’t show birth time, so I’m at least sure of 8.30 not having the functionality. EDIT: seems to have been described here savannah.gnu.org/forum/forum.php?forum_id=9394 And apparently it was a year ago. Commented Apr 30, 2020 at 4:31
  • unix.stackexchange.com/a/407305/70524 Commented Apr 30, 2020 at 5:00
10

There's another case where Birth time will be empty/zero/dash: Ext4's Inode size has to be at least 256bytes to store crtime. The problem occur if you initially created the filesystem smaller than 512MB ( the default Inode size will be 128 bytes, see /etc/mke2fs.conf and mkfs.ext4 manpage).

stat -c '%n: %w' testfile testfile: - 

and/or

stat -c '%n: %W' testfile testfile: 0 

Now check the filesystem inode (is it big enough to store crtime?):

tune2fs -l $(df . --output=source | grep ^/) | grep "Inode size:" Inode size: 128 

Technical information: On the Ext4 Disk Layout page, note that some attributes of the inode tables are beyond 0x80 (128).

2
  • Correct (I remember reading about this on vger). The 512MB limit is defined in mke2fs.c at line 1275 Commented Mar 27, 2015 at 23:22
  • 2
    I thought I was getting crazy - my boot partition is missing crtime. Thanks to your answer I know understand what's going on. I've tried to change the inode size using, tune2fs -I 256 /dev/DEVICE but it failed with Changing the inode size not supported for filesystems with the flex_bg feature enabled. Darn. Commented Apr 8, 2023 at 7:59
4

For what it's worth I was feeling pedantic so wrote a bash wrapper around stat to silently support crtime using debugfs to fetch it from an underlying ext4 filesystem if available. I hope it's robust. Find it here.

Note that a fix is ostensibly on the todo list for Linux as documented in that script. So this wrapper has a nominal lifespan only until that is done and is more an exercise in what's doable.

4
  • 4
    Note that xstat() has eventually been added to Linux, so it's only a matter of time before the GNU libc and find add support for it. Commented Jun 3, 2017 at 9:35
  • 1
    Awesome! Good news indeed. Commented Jun 3, 2017 at 10:18
  • I am very late to the party but I just stumbled across this answer. I have got your code but my filename starts with a $ sign e.g. $ncomdtl.dbf when i try your code on this file i get the following stat: cannot stat `/media/pcontrol/.dbf': No such file or directory Commented May 27, 2022 at 8:19
  • Totally credible that the script fails with a $ in the filename. Common bug. Would need to check it, as $ is of course a special chart in bash, and so needs escaping or the string in single quotes etc. Easy to diagnose and fix and PR is welcome, or I can check it when time permits (which is scant). Commented May 30, 2022 at 8:39

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.