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?

up vote 67 down vote accepted

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
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

1 Linus' reply on LKML thread

  • sudo debugfs -R 'stat /path/to/foo' /dev/sda2 gives me /path/to/foo: File not found by ext2_lookup. stat /path/to/foo works (with Birth empty). Also, ext2? – Sparhawk Nov 9 '13 at 3:01
  • 2
    @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 – jpfleury Apr 17 '14 at 2:39
  • @jpfleury Thanks. That works now. – Sparhawk May 1 '14 at 1:13
  • Equivalent of stat /home/richard is sudo debugfs -R 'stat /richard' /dev/disk/by-label/home — assuming that /home is a separate file-system, and you file-systems are labelled. – ctrl-alt-delor Aug 11 '14 at 11:27
  • @jpfleury My situation is like yours with a separation home partition. I can get the creation time by your method. But I don't know why. For example, home partition is sda5, and current directory is ~, and I want to get crtime of file.txt under ~. I use sudo debugfs -R 'stat file.txt' /dev/sda5. Why debufs cannot find the file.txt on sda5? – Zachary Sep 16 '14 at 11:17

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}"

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
  • is there a particular reason for not using inode=$(stat -c %i "${target}") instead? It's easier and simpler.. – yat0 Oct 10 '15 at 17:33
  • @BrunoCasteleiro basically because that was the one that occurred to me and I like the chance to safely parse ls. It doesn't happen often :). You're right though, stat is probably better, thanks. – terdon Oct 12 '15 at 11:39

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.

However, userland has yet to catch up - it's not easy to call system calls directly in a C program. Typically glibc provides a wrapper that makes the job easy, but glibc hasn't yet added a wrapper for statx(2). 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.

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
#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
#define __NR_statx 383

#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;
            case 'l':
                flags &= ~AT_SYMLINK_NOFOLLOW;
            case 'f':
                flags &= ~AT_STATX_SYNC_TYPE;
                flags |= AT_STATX_FORCE_SYNC;
            case 'd':
                flags &= ~AT_STATX_SYNC_TYPE;
                flags |= AT_STATX_DONT_SYNC;

    if (optind >= argc) {

    for (; optind < argc; optind++) {
        memset(&stxbuf, 0xbf, sizeof(stxbuf));
        ret = statx(dirfd, argv[optind], flags, mask, &stxbuf);
        if( ret < 0)
            return EXIT_FAILURE;
        printf("%lld.%u\n", *&stxbuf.stx_btime.tv_sec, *&stxbuf.stx_btime.tv_nsec);
    return EXIT_SUCCESS;


$ make birth
$ ./birth ./birth.c
$ ./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.

If, or rather when, glibc adds support for the statx(2) system call, stat will follow soon and we'll be able to use the plain old stat command for this.

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: -  


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).

  • Correct (I remember reading about this on vger). The 512MB limit is defined in mke2fs.c at line 1275 – don_crissti Mar 27 '15 at 23:22

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.

  • 1
    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. – Stéphane Chazelas Jun 3 '17 at 9:35
  • 1
    Awesome! Good news indeed. – Bernd Wechner Jun 3 '17 at 10:18
  • 4
    With apologies for being pedantic, you seem not to understand the meaning of "pedantic". – Nick Sep 22 '17 at 9:53

Your Answer


By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.