utimensat(2)

๐Ÿ’– utimensat(2) โ€” the System Calls Manual page, but make it cute ๐Ÿ’–

okay bestie, so this is the page for utimensat(2) and futimens from the System Calls Manual. it's the one about changing file timestamps with โœจnanosecond precisionโœจ and honestly? she's giving precision. she's giving "i care about your atime AND your mtime down to the literal nanosecond". periodt.


๐ŸŒธ NAME ๐ŸŒธ

utimensat, futimens โ€” change file timestamps with nanosecond precision

๐Ÿ“š LIBRARY ๐Ÿ“š

Standard C library (libc, -lc)

โœจ SYNOPSIS โœจ

#include <fcntl.h>            /* Definition of AT_* constants */
#include <sys/stat.h>

int utimensat(int dirfd, const char *path,
              const struct timespec times[_Nullable 2], int flags);
int futimens(int fd, const struct timespec times[_Nullable 2]);

๐ŸŽ€ Feature Test Macro Requirements for glibc (see feature_test_macros(7)): ๐ŸŽ€

utimensat():
    Since glibc 2.10:
        _POSIX_C_SOURCE >= 200809L
    Before glibc 2.10:
        _ATFILE_SOURCE

futimens():
    Since glibc 2.10:
        _POSIX_C_SOURCE >= 200809L
    Before glibc 2.10:
        _GNU_SOURCE

like, yes, you do have to define the right macro before glibc 2.10, iykyk ๐Ÿ˜ญ


๐Ÿ’… DESCRIPTION ๐Ÿ’…

utimensat() and futimens() update the timestamps of a file with nanosecond precision. and listen, this is a glow-up compared to the historical utime(2) and utimes(2), which only do second and microsecond precision respectively when setting file timestamps. we've upgraded, bestie. we're not living like that anymore.

with utimensat() you point at the file using the pathname in path. with futimens() you point at the file using an open file descriptor, fd. both valid, both iconic, pick your fighter.

for both calls, the new file timestamps live in the array times:

  • times[0] = the new "last access time" (atime)
  • times[1] = the new "last modification time" (mtime)

each element of times gives a time as the number of seconds and nanoseconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC). this info travels in a timespec(3) structure. slay.

updated file timestamps get set to the greatest value the filesystem supports that is not greater than the specified time. no overshooting, we stay in budget ๐Ÿ’–

now here's the fun part โ€” the special values:

  • if the tv_nsec field of one of the timespec structures has the special value UTIME_NOW, then that corresponding file timestamp is set to the current time.
  • if the tv_nsec field of one of the timespec structures has the special value UTIME_OMIT, then that corresponding file timestamp is left unchanged.

and in both of these cases, the value of the corresponding tv_sec field is ignored. bye felicia.

if times is NULL, then both timestamps get set to the current time.

and the status change time (ctime) will be set to the current time, even if the other time stamps don't actually change. she's committed. she showed up anyway. respect.

๐Ÿ” Permissions requirements ๐Ÿ”

to set both file timestamps to the current time (i.e., times is NULL, or both tv_nsec fields specify UTIME_NOW), you need either:

  • the caller must have write access to the file;
  • the caller's effective user ID must match the owner of the file; or
  • the caller must have appropriate privileges.

to make any change other than setting both timestamps to the current time (i.e., times is not NULL, and neither tv_nsec field is UTIME_NOW and neither tv_nsec field is UTIME_OMIT), then condition 2 or 3 above must apply. no shortcuts bestie.

if both tv_nsec fields are specified as UTIME_OMIT, then no file ownership or permission checks are performed, and the file timestamps are not modified โ€” but other error conditions may still be detected. sneaky. watch out. ๐Ÿ™ˆ

๐Ÿฆ‹ utimensat() specifics ๐Ÿฆ‹

if path is relative, then by default it's interpreted relative to the directory referred to by the open file descriptor, dirfd โ€” rather than relative to the current working directory of the calling process, which is what utimes(2) does for a relative pathname. see openat(2) for an explanation of why this can be useful.

if path is relative and dirfd is the special value AT_FDCWD, then path is interpreted relative to the current working directory of the calling process (like utimes(2)).

if path is absolute, then dirfd is ignored. she said "i don't need you".

the flags argument is a bit mask created by ORing together zero or more of these values defined in <fcntl.h>:

  • AT_EMPTY_PATH (since Linux 5.8) โ€” if path is an empty string, operate on the file referred to by dirfd (which may have been obtained using the open(2) O_PATH flag). in this case, dirfd can refer to any type of file, not just a directory. if dirfd is AT_FDCWD, the call operates on the current working directory. this flag is Linux-specific; define _GNU_SOURCE to obtain its definition.
  • AT_SYMLINK_NOFOLLOW โ€” if path specifies a symbolic link, then update the timestamps of the link itself, rather than the file to which it refers.

๐Ÿซถ RETURN VALUE ๐Ÿซถ

on success, utimensat() and futimens() return 0. on error, -1 is returned and errno is set to indicate the error. okayyy ๐Ÿ˜ญ

๐Ÿ’€ ERRORS ๐Ÿ’€

  • EACCES โ€” times is NULL, or both tv_nsec values are UTIME_NOW, and the effective user ID of the caller does not match the owner of the file, the caller does not have write access to the file, and the caller is not privileged (Linux: does not have either the CAP_FOWNER or the CAP_DAC_OVERRIDE capability).
  • EBADF โ€” (futimens()) fd is not a valid file descriptor.
  • EBADF โ€” (utimensat()) path is relative but dirfd is neither AT_FDCWD nor a valid file descriptor.
  • EFAULT โ€” times pointed to an invalid address; or, dirfd was AT_FDCWD, and path is NULL or an invalid address.
  • EINVAL โ€” invalid value in flags.
  • EINVAL โ€” invalid value in one of the tv_nsec fields (value outside range [0, 999,999,999], and not UTIME_NOW or UTIME_OMIT); or an invalid value in one of the tv_sec fields.
  • EINVAL โ€” path is NULL, dirfd is not AT_FDCWD, and flags contains AT_SYMLINK_NOFOLLOW.
  • ELOOP โ€” (utimensat()) too many symbolic links were encountered in resolving path.
  • ENAMETOOLONG โ€” (utimensat()) path is too long.
  • ENOENT โ€” (utimensat()) a component of path does not refer to an existing directory or file, or path is an empty string.
  • ENOTDIR โ€” (utimensat()) path is relative, but dirfd is neither AT_FDCWD nor a file descriptor referring to a directory; or, one of the prefix components of path is not a directory.
  • EPERM โ€” the caller attempted to change one or both timestamps to a value other than the current time, or to change one of the timestamps to the current time while leaving the other timestamp unchanged, (i.e., times is not NULL, neither tv_nsec field is UTIME_NOW, and neither tv_nsec field is UTIME_OMIT) and either:
    • the caller's effective user ID does not match the owner of file, and the caller is not privileged (Linux: does not have the CAP_FOWNER capability); or,
    • the file is marked append-only or immutable (see chattr(1)).
  • EROFS โ€” the file is on a read-only filesystem.
  • ESRCH โ€” (utimensat()) search permission is denied for one of the prefix components of path.

๐Ÿ‘‘ ATTRIBUTES ๐Ÿ‘‘

for an explanation of the terms used in this section, see attributes(7).

  • Interface: utimensat(), futimens()
  • Attribute: Thread safety
  • Value: MT-Safe

she's MT-Safe. she can handle multiple threads at once. main character energy fr fr ๐Ÿ”ฅ

๐ŸŒˆ VERSIONS ๐ŸŒˆ

๐Ÿงš C library/kernel ABI differences ๐Ÿงš

on Linux, futimens() is a library function implemented on top of the utimensat() system call. to support this, the Linux utimensat() system call implements a nonstandard feature: if path is NULL, then the call modifies the timestamps of the file referred to by the file descriptor dirfd (which may refer to any type of file). using this feature, the call futimens(fd, times) is implemented as:

utimensat(fd, NULL, times, 0);

note, however, that the glibc wrapper for utimensat() disallows passing NULL as the value for path: the wrapper function returns the error EINVAL in this case. so like, the kernel is fine with it, the wrapper said "no โค๏ธ". watch out for that.

โš ๏ธ bestie, don't try to lean on that NULL-path trick through glibc's utimensat() wrapper โ€” she will hand you an EINVAL and walk away.

๐Ÿ“œ STANDARDS ๐Ÿ“œ

POSIX.1-2024.

๐Ÿ•ฐ๏ธ HISTORY ๐Ÿ•ฐ๏ธ

  • utimensat() โ€” Linux 2.6.22, glibc 2.6. POSIX.1-2008.
  • futimens() โ€” glibc 2.6. POSIX.1-2008.

๐Ÿ“ NOTES ๐Ÿ“

utimensat() obsoletes futimesat(2). she's the newer model, the upgrade, the reboot. it's giving evolution.

on Linux, timestamps cannot be changed for a file marked immutable, and the only change permitted for files marked append-only is to set the timestamps to the current time. (this is consistent with the historical behavior of utime(2) and utimes(2) on Linux.)

if both tv_nsec fields are specified as UTIME_OMIT, then the Linux implementation of utimensat() succeeds even if the file referred to by dirfd and path does not exist. yes really. no file? no problem. nothing to change! ๐Ÿ’…

๐Ÿ› BUGS ๐Ÿ›

several bugs afflict utimensat() and futimens() before Linux 2.6.26. these bugs are either nonconformances with the POSIX.1 draft specification or inconsistencies with historical Linux behavior. ancient drama. we're talking about it anyway because history matters, bestie.

  • POSIX.1 specifies that if one of the tv_nsec fields has the value UTIME_NOW or UTIME_OMIT, then the value of the corresponding tv_sec field should be ignored. instead, the value of the tv_sec field is required to be 0 (or the error EINVAL results).
  • various bugs mean that for the purposes of permission checking, the case where both tv_nsec fields are set to UTIME_NOW isn't always treated the same as specifying times as NULL, and the case where one tv_nsec value is UTIME_NOW and the other is UTIME_OMIT isn't treated the same as specifying times as a pointer to an array of structures containing arbitrary time values. as a result, in some cases:
    • file timestamps can be updated by a process that shouldn't have permission to perform updates;
    • file timestamps can't be updated by a process that should have permission to perform updates; and
    • the wrong errno value is returned in case of an error.
  • POSIX.1 says that a process that has write access to the file can make a call with times as NULL, or with times pointing to an array of structures in which both tv_nsec fields are UTIME_NOW, in order to update both timestamps to the current time. however, futimens() instead checks whether the access mode of the file descriptor allows writing.

๐Ÿ”— SEE ALSO ๐Ÿ”—

chattr(1), touch(1), futimesat(2), openat(2), stat(2), utimes(2), futimes(3), timespec(3), inode(7), path_resolution(7), symlink(7)


okay that's the whole page bestie, you're now officially fluent in nanosecond-precision timestamp manipulation ๐ŸŽ€

and a lil sign-off for the credits moment: this beauty came from Linux man-pages 6.20, dated 2025-10-29, and her name is utimensat(2). go set some timestamps. slay. ๐Ÿ’–โœจ๐Ÿฆ‹

๐Ÿ“ฆ where this page comes from
  • arch rolling: man-pages 6.20-1 ยท BSD-2-Clause AND BSD-3-Clause AND BSD-4.3TAHOE AND BSD-4-Clause-UC AND GPL-1.0-or-later AND GPL-2.0-only AND GPL-2.0-or-later AND GPL-3.0-or-later AND LGPL-3.0-linking-exception AND LGPL-3.0-or-later AND LicenseRef-Public-Domain AND LicenseRef-UltraPermissive AND Linux-man-pages-1-para AND Linux-man-pages-copyleft AND Linux-man-pages-copyleft-2-para AND Linux-man-pages-copyleft-var AND MIT AND Spencer-94
  • nixpkgs unstable: man-pages 6.20
  • ๐Ÿ“œ the page's license: Linux-man-pages-copyleft
  • ๐Ÿ’– girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms