st_atim and st_atimespec
2025-03-06, Thu
I was testing the following code snippet from a book1 today:
struct stat statbuf;
struct timespec times[2];
if (stat("some-file-name", &statbuf) < 0) {
fprintf(stderr, "stat error: %s\n", strerror(errno));
exit(1);
}
times[0] = statbuf.st_atim;
times[1] = statbuf.st_mtim;
only to get a compilation error that complains missing field st_atim:
src/figure-4-21.c:56:24: error: no member named 'st_atim' in 'struct stat'
56 | times[0] = statbuf.st_atim;
| ~~~~~~~ ^
I was confused for a moment, because st_atim is defined in
POSIX.1-2017, and also enlisted in the Debian manual for
stat(3type). So I checked manual of stat(2) in macOS, which
states that the stat structure is defined as:
struct stat { /* when _DARWIN_FEATURE_64_BIT_INODE is NOT defined */
dev_t st_dev; /* device inode resides on */
ino_t st_ino; /* inode's number */
mode_t st_mode; /* inode protection mode */
nlink_t st_nlink; /* number of hard links to the file */
uid_t st_uid; /* user-id of owner */
gid_t st_gid; /* group-id of owner */
dev_t st_rdev; /* device type, for special file inode */
struct timespec st_atimespec; /* time of last access */
struct timespec st_mtimespec; /* time of last data modification */
struct timespec st_ctimespec; /* time of last file status change */
off_t st_size; /* file size, in bytes */
quad_t st_blocks; /* blocks allocated for file */
u_long st_blksize;/* optimal file sys I/O ops blocksize */
u_long st_flags; /* user defined flags for file */
u_long st_gen; /* file generation number */
};
Hmm, so it is st_atimespec here? Interesting. I udpated the code accordingly:
struct stat statbuf;
struct timespec times[2];
if (stat("some-file-name", &statbuf) < 0) {
fprintf(stderr, "%s: stat error", strerror(errno));
exit(1);
}
times[0] = statbuf.st_atimespec;
times[1] = statbuf.st_mtimespec;
and got similar error:
src/figure-4-21.c:56:24: error: no member named 'st_atimespec' in 'struct stat'
56 | times[0] = statbuf.st_atimespec;
| ~~~~~~~ ^
What went wrong? I scratched my head and did some search. It turns out
the answer might lie in the header file, which could be accessed
through less $(xcrun --sdk macosx
--show-sdk-path)/usr/include/sys/stat.h2. Scroll down a few
pages and the definition of stat is right there:
struct stat {
dev_t st_dev; /* [XSI] ID of device containing file */
ino_t st_ino; /* [XSI] File serial number */
mode_t st_mode; /* [XSI] Mode of file (see below) */
nlink_t st_nlink; /* [XSI] Number of hard links */
uid_t st_uid; /* [XSI] User ID of the file */
gid_t st_gid; /* [XSI] Group ID of the file */
dev_t st_rdev; /* [XSI] Device ID */
#if !defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE)
struct timespec st_atimespec; /* time of last access */
struct timespec st_mtimespec; /* time of last data modification */
struct timespec st_ctimespec; /* time of last status change */
#else
time_t st_atime; /* [XSI] Time of last access */
long st_atimensec; /* nsec of last access */
time_t st_mtime; /* [XSI] Last data modification time */
long st_mtimensec; /* last data modification nsec */
time_t st_ctime; /* [XSI] Time of last status change */
long st_ctimensec; /* nsec of last status change */
#endif
off_t st_size; /* [XSI] file size, in bytes */
blkcnt_t st_blocks; /* [XSI] blocks allocated for file */
blksize_t st_blksize; /* [XSI] optimal blocksize for I/O */
__uint32_t st_flags; /* user defined flags for file */
__uint32_t st_gen; /* file generation number */
__int32_t st_lspare; /* RESERVED: DO NOT USE! */
__int64_t st_qspare[2]; /* RESERVED: DO NOT USE! */
};
Based on this snippet, the st_atimesepc and its siblings are guarded
by macro _DARWIN_C_SOURCE. Hence in order to make things work, we better
provide it either in the source file, or through GCC option, e.g.
figure-4-21: src/figure-4-21.c ${OTHER_FILES}
${CC} -g -D _DARWIN_C_SOURCE -o out/$@ $^
which indeed solves the problem.
Footnotes:
Advanced Unix Programming Environment, http://www.apuebook.com/. The code snippet comes from Figure 4.21.
The include path is also enlisted in the verbose output of GCC.