Search code examples
touchsymlinkapfs

Changing the timestamp of a symlink in macOS with APFS?


According to the man pages of touch, in order to change the timestamp on a symbolic link, one can use touch -h -t MMDDhhmm mylink.

This does not work on my macOS machine with APFS, the timestamp of the link is unmodified and touch follows the link to modify the timestamp of the target.

Is this a known fact about APFS or is there something I haven't understood?


Solution

  • It seems you have found a bug in macOS. The next-to-last major version of the operating system works according to the manual paage, but the latest version does not.

    Either the manual page is outdated, or the software has a bug (most likely).

    You can send feedback to Apple as a general user here:'

    https://www.apple.com/feedback/macos.html

    Or if you are a developer, you can use the Feedback Assistant to file a bug report as described here:

    https://developer.apple.com/bug-reporting/

    There's currently no newer version of touch available from Apple so you cannot as such upgrade your way out of the problem. As you indicate yourself, you can instead use the GNU version of touch to get the job done. You can find that in Homebrew by installing coreutils.

    The GNU version of touch works by calling the newer futimens()/utimensat() functions (in the latter case with the flag argument set to AT_SYMLINK_NOFOLLOW to change the timestamp for the link itself).

    The Catalina version of touch (287.100.2) works by calling the older lutimes() function, which explicitly sets the timestamp on the link itself. One of the core differences between the newer and older APIs are that the newer supports timestamps in nanoseconds, whereas the older has a lower resolution.

    The lutimes() function on Big Sur actually doesn't implement a system call itself, but is actually contained fully in the standards library, using the setattrlist() function (which results in a system call) to actually perform the file system modification. setattrlist() is highly file system dependent (i.e. how it works on HFS+ file systems would be different from how it works on APFS file systems).

    The Big Sur version of touch (321.100.11) works by calling the setattrlist() function directly, and only if that fails, fall back to lutimes. Unfortunately, it seems the programmer forgot about the need to specify that when -h is specified, the modification must take place on the link itself.

    The actual bug is in line 219 of touch.c, where this line:

    if (!setattrlist(*argv, &ts_req, &ts_struct, sizeof(ts_struct), 0))
    

    should have been:

    if (!setattrlist(*argv, &ts_req, &ts_struct, sizeof(ts_struct), utimes_f == lutimes ? FSOPT_NOFOLLOW : 0))
    

    You could change that in touch.c, recompile it, and get a working binary.