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
Timestamp Fields
APIs Covered
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:
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 |
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 | ✓ | ✓ | ✓ |
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 |
utimensat() for new code. It has nanosecond precision, can change timestamps independently, and handles symlinks flexibly.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;
}
| 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) |
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
