111

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

133

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
    – jep
    Apr 17, 2014 at 2:39
  • 3
    Can this be used to get creation time of files from a network-mounted filesystem?
    – taranaki
    Sep 20, 2018 at 18:32
  • 1
    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?
    – sinekonata
    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 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.
    – mivk
    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.
    – sergius
    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?
    – terdon
    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.
    – sergius
    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.
    – osexp2000
    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.
    – terdon
    Apr 13, 2023 at 11:44
34

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.

11

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
    – muru
    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.
    – Kevin Li
    Apr 30, 2020 at 4:31
  • unix.stackexchange.com/a/407305/70524
    – muru
    Apr 30, 2020 at 5:00
9

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 Mar 27, 2015 at 23:22
  • 1
    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. 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. Jun 3, 2017 at 9:35
  • 1
    Awesome! Good news indeed. 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
    – l0ckm4
    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). May 30, 2022 at 8:39

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .