atime, mtime, ctime in Linux: Meaning and How to Change Them

 

atime, mtime, ctime in Linux: Meaning and How to Change Them

atime, mtime, ctime in Linux: Meaning and How to Change Them

— atime, mtime, ctime and How to Change Them

3
Timestamp Fields
4
APIs Covered
Nanosecond
Precision (Linux 2.6+)

What are File Timestamps?

Every file on Linux tracks three time values. Think of them as three separate clocks stored in the file’s i-node. They are updated automatically by the kernel whenever you perform certain operations.

Knowing these timestamps is very useful for tools like make (rebuild only if source is newer than binary), tar (preserve original timestamps in archives), backup tools, and log rotation.

Key Terms:

st_atime st_mtime st_ctime utime() utimes() utimensat() futimens() UTIME_NOW UTIME_OMIT struct timespec Epoch

1. The Three Timestamps — What Triggers Each One?

All three are stored as seconds since the Epoch (1 January 1970, 00:00:00 UTC).

Field Full Name Updated When? Shell check
st_atime Access time File contents are read (read(), mmap()) ls -lu
st_mtime Modification time File contents are written/changed (write(), truncate()) ls -l
st_ctime Status change time i-node data changes — permissions, owner, link count, rename ls -lc
Common Confusion: st_ctime is NOT creation time! There is no creation time field in standard Linux/POSIX. st_ctime is the last time the i-node metadata was changed (not the file data).

Which timestamps change for each operation?

Operation atime mtime ctime
read()
write()
chmod() / chown()
rename()
link() / unlink()
truncate()
exec()
open(O_CREAT) new file

2. APIs to Change Timestamps — Which One to Use?

The kernel normally updates timestamps automatically. But you can manually set them using these APIs. Tools like tar and unzip use these to restore original timestamps when extracting archives.

Function Precision Symlink handling File identified by
utime() Seconds Follows (dereferences) pathname
utimes() Microseconds Follows (dereferences) pathname
futimes() Microseconds N/A (fd) open fd
lutimes() Microseconds Does NOT follow pathname
utimensat() Nanoseconds ★ Configurable via flags dirfd + pathname
futimens() Nanoseconds ★ N/A (fd) open fd
Recommendation: Use utimensat() for new code. It has nanosecond precision, can change timestamps independently, and handles symlinks flexibly.

3. Code Examples

Example 1: Read and print all three timestamps

#include <stdio.h>
#include <sys/stat.h>
#include <time.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    struct stat sb;
    if (argc != 2) { fprintf(stderr, "Usage: %s file\n", argv[0]); exit(1); }

    if (stat(argv[1], &sb) == -1) { perror("stat"); exit(1); }

    /* ctime() converts time_t to human-readable string */
    printf("Last access   (atime): %s", ctime(&sb.st_atime));
    printf("Last modified (mtime): %s", ctime(&sb.st_mtime));
    printf("Last i-node chg(ctime): %s", ctime(&sb.st_ctime));

    /* Raw seconds since epoch */
    printf("\nRaw mtime (epoch secs): %ld\n", (long)sb.st_mtime);

    return 0;
}
/* Try: touch testfile; ./timestamps testfile; cat testfile; ./timestamps testfile
   Notice how atime changes after cat (read), mtime does not */

Example 2: utime() — set timestamps in seconds precision

#include <stdio.h>
#include <utime.h>
#include <sys/stat.h>
#include <stdlib.h>

int main(void) {
    struct utimbuf utb;
    struct stat sb;

    /* Case 1: Pass NULL — sets both atime and mtime to current time */
    if (utime("testfile", NULL) == -1) {
        perror("utime");
        exit(1);
    }
    printf("Reset both timestamps to NOW\n");

    /* Case 2: Set to specific values */
    /* Set mtime to same as atime (leave atime unchanged) */
    if (stat("testfile", &sb) == -1) { perror("stat"); exit(1); }

    utb.actime  = sb.st_atime;  /* keep atime unchanged */
    utb.modtime = sb.st_atime;  /* set mtime = atime */

    if (utime("testfile", &utb) == -1) {
        perror("utime");
        exit(1);
    }
    printf("Set mtime = atime\n");

    /* Note: utime() always sets ctime to current time */
    return 0;
}

Example 3: utimes() — microsecond precision

#include <stdio.h>
#include <sys/time.h>
#include <stdlib.h>

int main(void) {
    struct timeval tv[2];

    /* tv[0] = new atime, tv[1] = new mtime */
    /* Set atime to a fixed point: 2024-01-01 00:00:00 UTC */
    tv[0].tv_sec  = 1704067200;  /* seconds since epoch */
    tv[0].tv_usec = 500000;      /* + 500ms (microseconds) */

    /* Set mtime to current time */
    gettimeofday(&tv[1], NULL);  /* fill with current time */

    if (utimes("testfile", tv) == -1) {
        perror("utimes");
        exit(1);
    }
    printf("Timestamps updated with microsecond precision\n");

    /* Pass NULL to set both to current time (same as utime with NULL) */
    utimes("testfile", NULL);

    return 0;
}

Example 4: utimensat() — nanosecond precision (modern, recommended)

#define _XOPEN_SOURCE 700
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>

int main(void) {
    struct timespec times[2];

    /* Example A: Set BOTH timestamps to current time */
    times[0].tv_sec  = 0;
    times[0].tv_nsec = UTIME_NOW;   /* special value: use current time */
    times[1].tv_sec  = 0;
    times[1].tv_nsec = UTIME_NOW;

    if (utimensat(AT_FDCWD, "testfile", times, 0) == -1) {
        perror("utimensat");
        exit(1);
    }
    printf("Both timestamps set to NOW\n");

    /* Example B: Set atime to now, leave mtime unchanged */
    times[0].tv_nsec = UTIME_NOW;   /* atime = now */
    times[1].tv_nsec = UTIME_OMIT;  /* mtime = leave as-is */

    utimensat(AT_FDCWD, "testfile", times, 0);
    printf("atime updated, mtime untouched\n");

    /* Example C: Set a specific nanosecond timestamp */
    times[0].tv_sec  = 1704067200;   /* seconds */
    times[0].tv_nsec = 123456789;    /* nanoseconds */
    times[1].tv_nsec = UTIME_OMIT;   /* leave mtime alone */

    utimensat(AT_FDCWD, "testfile", times, 0);
    printf("atime set to specific nanosecond value\n");

    /* Example D: Change symlink's own timestamps (not target) */
    utimensat(AT_FDCWD, "mylink", times, AT_SYMLINK_NOFOLLOW);

    return 0;
}

Example 5: futimens() — update timestamps via file descriptor

#define _GNU_SOURCE
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

int main(void) {
    int fd;
    struct timespec times[2];

    fd = open("testfile", O_RDWR);
    if (fd == -1) { perror("open"); exit(1); }

    times[0].tv_nsec = UTIME_NOW;   /* atime = now */
    times[1].tv_nsec = UTIME_NOW;   /* mtime = now */

    /* Uses fd, not pathname — race-condition-safe */
    if (futimens(fd, times) == -1) {
        perror("futimens");
    }

    close(fd);
    return 0;
}

Example 6: Compare two files by mtime (like make does)

#include <stdio.h>
#include <sys/stat.h>

/* Returns 1 if src is newer than dst (src_mtime > dst_mtime) */
int is_newer(const char *src, const char *dst) {
    struct stat s_src, s_dst;

    if (stat(src, &s_src) == -1) return 0;
    if (stat(dst, &s_dst) == -1) return 1; /* dst doesn't exist = rebuild */

    return s_src.st_mtime > s_dst.st_mtime;
}

int main(void) {
    /* Simulate what make(1) does */
    if (is_newer("main.c", "main.o")) {
        printf("main.c is newer — recompile needed\n");
    } else {
        printf("main.o is up to date\n");
    }
    return 0;
}

Example 7: Nanosecond timestamps (Linux 2.6+ with glibc 2.3+)

#include <stdio.h>
#include <sys/stat.h>

int main(void) {
    struct stat sb;
    stat("testfile", &sb);

    /* Traditional field: seconds only */
    printf("mtime seconds: %ld\n", (long)sb.st_mtime);

    /* Nanosecond field (glibc 2.3+, Linux 2.6+):
       st_mtim is a struct timespec inside stat
       Access nanoseconds via .tv_nsec */
    printf("mtime nanoseconds: %ld\n", sb.st_mtim.tv_nsec);
    printf("atime nanoseconds: %ld\n", sb.st_atim.tv_nsec);
    printf("ctime nanoseconds: %ld\n", sb.st_ctim.tv_nsec);

    /* Not all filesystems support nanoseconds:
       ext4, XFS, Btrfs, JFS — YES
       ext2, ext3, Reiserfs  — NO  */
    return 0;
}

4. Who is Allowed to Change Timestamps?
Scenario Who can do it?
buf = NULL (set to current time) Owner, anyone with write permission, privileged process
buf = specific value (arbitrary time) Only owner or privileged process (write permission is NOT enough)
This asymmetry makes sense: anyone with write access to a file can effectively update its mtime/atime as a side effect of writing. But arbitrarily setting timestamps to any value requires stronger authority (ownership or root).

5. Interview Questions

Q1 What are the three file timestamp fields in the stat structure?

A st_atime (last access — when file was read), st_mtime (last modification — when file data was written), and st_ctime (last status change — when i-node metadata changed).

Q2 Is st_ctime the file creation time?

A No. st_ctime is the last status change time (i-node change). Linux/POSIX does not store a creation time in the standard stat structure. Recent BSD systems have st_birthtime for creation time.

Q3 If I call chmod() on a file, which timestamp(s) change?

A Only st_ctime changes, because the i-node metadata (permissions) changed. The file data is untouched so st_mtime stays the same.

Q4 What is the difference between utime() and utimensat()?

A utime() uses seconds precision and changes both atime and mtime together. utimensat() uses nanosecond precision, can change atime and mtime independently (using UTIME_OMIT), and can optionally not follow symlinks.

Q5 What does UTIME_NOW and UTIME_OMIT mean in utimensat()?

A UTIME_NOW in tv_nsec means “set this timestamp to the current time”. UTIME_OMIT in tv_nsec means “leave this timestamp unchanged”. Both are used in the struct timespec array passed to utimensat().

Q6 Can a regular user set a file’s mtime to a date in the past?

A Only if they are the file owner. Having write permission on the file is NOT sufficient for setting arbitrary timestamp values — that’s a stronger privilege requirement. Write permission only lets you implicitly update atime/mtime as a side effect of writing.

Q7 Why might a filesystem admin mount with the noatime option?

A Every file read would normally update st_atime, causing a disk write to the i-node. For read-heavy workloads this is wasteful. The noatime mount option skips atime updates, significantly reducing disk I/O and improving performance.

Q8 Write code to touch a file (set its mtime to now) without using the touch command.

A

#define _XOPEN_SOURCE 700
#include <sys/stat.h>
#include <fcntl.h>

void touch(const char *path) {
    struct timespec times[2];
    times[0].tv_nsec = UTIME_NOW;
    times[1].tv_nsec = UTIME_NOW;
    utimensat(AT_FDCWD, path, times, 0);
}

Next: File Ownership — chown(), lchown(), fchown() and group inheritance rules

← Previous Next Topic →

Leave a Reply

Your email address will not be published. Required fields are marked *