88

I need to find the creation time of a file, when I read some articles about this issue, all mentioned that there is no solution (like Site1, Site2).

When I tried the stat command, it states Birth: -.

So how can I find the creation time of a file?

3
  • 2
    Keep in mind that the 'creation time' of a file is not guaranteed to be accurate. There are many ways to 'fudge' the creation dates on a file.
    – Thomas Ward
    Nov 27, 2017 at 15:00
  • 1
    @ThomasWard Many more than ways to fudge other file data? Feb 9, 2019 at 6:21
  • See also: unix.stackexchange.com/questions/304779 Feb 3, 2020 at 9:59

6 Answers 6

81

There is a way to know the creation date of a directory , just follow these steps :

  1. Know the inode of the directory by ls -i command (lets say for example its X)

  2. Know on which partition your directory is saved by df -T /path command ( lets say its on /dev/sda1 )

  3. Now use this command : sudo debugfs -R 'stat <X>' /dev/sda1

You will see in the output :

crtime: 0x4e81cacc:966104fc -- mon Sep 27 14:38:28 2013

crtime is the creation date of your file .

What I tested :

  1. Created a directory at specific time .
  2. Accessed it .
  3. Modified it by creating a file .

  4. I tried the command and it gave an exact time .

  5. Then i modify it , and test again , the crtime remained the same , but modify and access time changed .
7
  • I post this , because i like to discuss so i can understand better , i am wonder why people say that Linux doesn't support this feature
    – nux
    May 21, 2014 at 15:21
  • 17
    Because Linux itself does not. The ext4 filesystem does have this information but the kernel does not provide an API to access it. Apparently, debugfs extracts it directly from the filesystem so it does not need to use the kernel's API. See here.
    – terdon
    May 21, 2014 at 15:31
  • I tested it. It worked perfectly on ext4 file system Sep 21, 2016 at 14:35
  • 2
    Seems like this is ext4 specific? It didn't work with XFS for me.
    – Quantum7
    May 3, 2019 at 8:32
  • 1
    @hippietrail do you have the version numbers? Feb 2, 2020 at 14:32
66

@Nux found a great solution for this which you should all upvote. I decided to write a little function that can be used to run everything directly. Just add this to your ~/.bashrc.

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
}

Now, you can run get_crtime to print the creation dates of as many files or directories as you like:

$ get_crtime foo foo/file 
foo Wed May 21 17:11:08 2014
foo/file    Wed May 21 17:11:27 2014
11
  • 1
    Note that the creation date is not the creation date of the original file if the file is a copy (like it is with the modification date). Once a file is copied, the modification date is from the original, but the creation date is from the copy. (theres is some misunderstanding in this question: askubuntu.com/questions/529885/…) Sep 29, 2014 at 10:57
  • 2
    @JacobVlijm well, yes, of course. Isn't that obvious? How could it be otherwise? A copy is a new file that just happens to have the same contents as another. The modification time also changes for a copy by the way. It is set to the moment the copy was created unless you explicitly choose for that not to happen using cp -p or similar.
    – terdon
    Sep 29, 2014 at 12:42
  • Absolutely, but it at the same time, it wouldn't be that unlogical if, like the mod. date, somewhere in the file the date would be stored when it originated. I must admit I didn't know that was not the case until I answered the linked question. Sep 29, 2014 at 12:45
  • Just tried by the way, I just copied files in nautilus, modification date stays as it is (was), m. date is earlier than creation date. Sep 29, 2014 at 13:42
  • 1
    @demongolem yes, the CentOS version of df doesn't seem to support the --output option. In that case, you can replace that line with fs=$(df foo | awk '{a=$1}END{print a}' and the function will work as well. All I'm showing in this answer is a way to wrap the command from the accepted answer in a way that can be run directly for file/directory targets.
    – terdon
    Feb 24, 2016 at 18:52
20

The inability of stat to show the creation time is due to limitation of the stat(2) system call, whose return struct doesn't include a field for the creation time. Starting with Linux 4.11 (i.e., 17.10 and newer*), however, the new statx(2) system call is available, which does include a creation time in its return struct.

* And possibly on older LTS releases using the hardware enablement stack (HWE) kernels. Check uname -r to see if you are using a kernel at least at 4.11 to confirm.

Unfortunately, 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 only added a wrapper for statx(2) in August 2018 (version 2.28, available in 18.10). The stat command itself gained support for statx(2) only in GNU coreutils 8.31 (released in March 2019), however, even Ubuntu 20.04 only has coreutils 8.30.

But I don't think this will be backported to LTS releases even if they do get, or are already on, newer kernels or glibcs. So, I don't expect stat on any current LTS release (16.04, 18.04 or 20.04) to ever print the creation time without manual intervention.

On 18.10 and newer, you can directly use the statx function as described in man 2 statx (note that the 18.10 manpage is incorrect in stating that glibc hasn't added the wrapper yet).

And in Ubuntu 20.10, you will be able to use stat directly:

# stat --version
stat (GNU coreutils) 8.32
Copyright (C) 2020 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: 88h/136d    Inode: 57279593    Links: 1
Access: (0755/drwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2020-08-18 06:57:46.912243164 +0000
Modify: 2020-08-18 06:57:06.768492334 +0000
Change: 2020-08-18 06:57:59.136165661 +0000
 Birth: 2020-08-18 06:57:06.768492334 +0000

For older systems, 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
#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 more accessible:

  • more filesystems should be supported than just the ext* ones (debugfs is a tool for ext2/3/4 filesystems, and unusable on others)
  • you don't need root to use this (except for installing some required packages, like make and linux-libc-dev).

Testing out an xfs system, for example:

$ truncate -s 1G temp; mkfs -t xfs temp; mkdir foo; sudo mount temp foo; sudo chown $USER foo
$ touch foo/bar
$ # some time later
$ echo > foo/bar
$ chmod og-w foo/bar
$ ./birth foo/bar | xargs -I {} date -d @{}
Mon Nov 27 14:43:21 UTC 2017
$ stat foo/bar                             
  File: foo/bar
  Size: 1           Blocks: 8          IO Block: 4096   regular file
Device: 700h/1792d  Inode: 99          Links: 1
Access: (0644/-rw-r--r--)  Uid: ( 1000/ muru)      Gid: ( 1000/ muru)
Access: 2017-11-27 14:43:32.845579010 +0000
Modify: 2017-11-27 14:44:38.809696644 +0000
Change: 2017-11-27 14:44:45.536112317 +0000
 Birth: -

However, this didn't work for NTFS and exfat. I guess the FUSE filesystems for those didn't include the creation time.

3
  • Thanks for linking to github. I searched a few months ago when 4.11 came out and didn't find anything and then forgot about it. Nov 28, 2017 at 0:34
  • Now that the feature is available, would you know how to modify that field? I may try to create a ctypes wrapper to do it in python. Thanks. Feb 14, 2019 at 2:09
  • Nice answer! It would be nice to see this get incorporated into a standard tool (ideally stat itself).
    – Quantum7
    May 3, 2019 at 8:36
6

TL;DR: Just run: sudo debugfs -R 'stat /path/to/your/file' /dev/<your fs>

(To figure out your fs, run df -T /path/to/your/file, most likely it's going to be /dev/sda1).

Long version:

We are going to run two commands:

  1. Find out the name of partition name for your file.

    df -T /path/to/your/file
    

    The output is going to look like this (partition name is first):

    Filesystem     Type 1K-blocks    Used Available Use% Mounted on
    /dev/<your fs> ext4   7251432 3481272   3509836  50% /
    
  2. Find out creation time for that file.

    sudo debugfs -R 'stat /path/to/your/file' /dev/<your fs>
    

    In the output, look for ctime.

4

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 get it to work by compiling version 8.32 of GNU coreutils from source. It was a relatively pain-free procedure.

Afterwards, both ls and stat could work with birth time.

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

Output of stat on an ext4 file system and a btrfs file system: stat with birth time

(Copied from my other answer on UNIX & Linux Stack Exchange)

1
  • 2
    hmmm well it does do all that ie shows you "birth:" on 20.04 if you upgrade to coreutils_8.32-3ubuntu1_amd64.deb and libselinux1_3.1-2_amd64.deb but then it wipes out huge amounts of :i386 files and totally disables wine on my system so maybe to warn folks that it does do that [prob 200 files on my system] as it is quite a heavy price to pay :] basically it broke my set up here and had to track back to original setup
    – shantiq
    Apr 24, 2021 at 18:20
0

You may need the file creation time in human readable but not standard format to use in bash shell script pipeline. For example, to prefixing the file name for appropriate sorting in the file manager. I modified @terdon solution for this purpose as following.

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 $(date -d "${crtime}" | sed 's/://g'| sed 's/-//g')
    done
}

  • date -d ... parses date time string;
  • sed removes special characters : and -;
  • no end of line added.

Which produses:

get_crtime .bashrc
20201023T181422
1
  • Thanks. I tried yours and got just "Tue".
    – omsrisagar
    Jun 7, 2022 at 21:17

You must log in to answer this question.

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