zic.c's dolink() fails on some Windows filesystems
I have a report[1] that tzcode 2026b fails on Windows filesystems that lack hard links, because it copes if link() fails with ENOTSUP but what is actually returned is EINVAL. I suggest something along the lines of if (link(target, outname) == 0) { link_errno = 0; break; } link_errno = errno; + /* Windows returns EINVAL if filesystem lacks hard links. */ + if (link_errno == EINVAL) + link_errno = ENOTSUP; } paralleling the workaround for ancient Linux a few lines above. regards, tom lane [1] https://www.postgresql.org/message-id/e6122f9b2eef9096f1f11ecc058bcd91%40pos...
Thanks for reporting that. I installed into the TZDB development repository the first attached patch, which has the code you suggested and with fancier commentary. A couple of questions, if you have the time: * Does the 'symlink' system call have the same problem on this MS-Windows platform? Should zic worry about them as well? (But if so, why didn't zic complain to your users about symlink?) * I guess the glitch occurred because the PostgreSQL copy of zic.c links to the compatibility shim in postgresql/src/port/win32link.c. If other code links to that shim too, perhaps this should also be fixed there? Something like the second attached patch, which I have not compiled or tested as I don't use MS-Windows.
Paul Eggert <eggert@cs.ucla.edu> writes:
Thanks for reporting that. I installed into the TZDB development repository the first attached patch, which has the code you suggested and with fancier commentary. A couple of questions, if you have the time:
* Does the 'symlink' system call have the same problem on this MS-Windows platform? Should zic worry about them as well? (But if so, why didn't zic complain to your users about symlink?)
On our Windows build, HAVE_SYMLINK is not defined and we #ifdef out dolink's attempt to use symlink(), so that aspect of it doesn't concern us. But see below.
* I guess the glitch occurred because the PostgreSQL copy of zic.c links to the compatibility shim in postgresql/src/port/win32link.c.
You're right to push back on this, but the locus that's worth questioning is our code that maps Windows error numbers to POSIX symbols, _dosmaperr() in src/port/win32error.c. Specifically, on further discussion with my reporter [1]: Vladlen Popolitov <v.popolitov@postgrespro.ru> writes:
Tom Lane писал(а) 2026-07-31 02:57:
Also, it occurs to me to wonder if we're doing this to ourselves. Specifically, it looks like src/port/win32error.c's _dosmaperr will map ERROR_NOT_SUPPORTED to EINVAL, due to the lack of any table entry for ERROR_NOT_SUPPORTED. Can you confirm which underlying Windows error code is being returned?
I have confirmed that the Windows error code returned by CreateHardLinkA() on exFAT is ERROR_INVALID_FUNCTION (numeric value 1). This code has no mapping in PostgreSQL's _dosmaperr(), which falls back to returning EINVAL (errno == 22). This is why zic receives EINVAL instead of ENOTSUP.
So the first reaction is "this is _dosmaperr's fault". But Vladlen did a little further investigation:
Also I see in man: Linux returns EPERM for unsupported filesystems, FreeBSD returns EOPNOTSUPP. Neither returns ENOTSUP.
Oooh. I didn't experiment on either, but I concur with your reading of their man pages. Also, NetBSD's man page says the same as FreeBSD, and I quickly verified on NetBSD 10 that EOPNOTSUPP (45) is different from ENOTSUP (86), unlike the situation on Linux. So tzcode's expectation of ENOTSUP is pretty widely broken already.
I also found that both Linux and FreeBSD document the same failure codes (EPERM, EOPNOTSUPP respectively) for symlink() in the case that the filesystem doesn't support symlinks. So my recommendation yesterday was very inadequately researched, for which I apologize. Instead of what you've installed, we need to fix _dosmaperr. However, it does appear that dolink() has not been adequately hardened against lack-of-filesystem-support cases; testing for ENOTSUP isn't sufficient -- and maybe isn't correct anywhere -- for either link() or symlink(). regards, tom lane [1] https://www.postgresql.org/message-id/flat/e6122f9b2eef9096f1f11ecc058bcd91%...
On 2026-07-31 07:33, Tom Lane wrote:
it does appear that dolink() has not been adequately hardened against lack-of-filesystem-support cases; testing for ENOTSUP isn't sufficient -- and maybe isn't correct anywhere -- for either link() or symlink().
Thanks for reporting that. It looks like EPERM can also happen on Solaris/Illumos, AIX, and FreeBSD/macOS. This is due to a longstanding confusion dating back to 6th Edition Unix: way back then, hard links to directories were allowed if you were the superuser (this was because there was no mkdir syscall, the mkdir command was setuid root, and it created "." and ".." entries by using the link syscall), and if link(A,B) failed with EACCES it meant you lacked permission to the parent of A or B, whereas if it failed with EPERM it meant that A was a directory and you were not the superuser. As time evolved, for this syscall EPERM came to mean "wrong file type" rather than "permission denied", even though nowadays even the superuser cannot hard link to directories. What a mess, right? Also, AIX and Solaris can return ENOSYS in some cases when hard links are not supported. FreeBSD's EOPNOTSUPP should not be a problem, as EOPNOTSUPP == ENOTSUP there. So ENOTSUP is correct for FreeBSD at least. symlink is similar to linkat/link here. ENOSYS and EPERM are a bit of a pain as they can also stand for problems other than lack of link support. However, it's not worth our trouble to chase that rabbit, as this is merely about whether to output a diagnostic. After researching the above, I installed the attached proposed further patch.
I spoke too soon about EOPNOTSUPP. Although it equals ENOTSUP on FreeBSD, this is not true of NetBSD and OpenBSD. * private.h (EOPNOTSUPP): Default to ENOTSUP. * zic.c (dolink): Treat EOPNOTSUPP like ENOTSUP. --- private.h | 4 ++++ zic.c | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/private.h b/private.h index 8ef03daf..f6a649f5 100644 --- a/private.h +++ b/private.h @@ -271,6 +271,10 @@ strnlen (char const *s, size_t maxlen) # define EPERM EINVAL #endif +#ifndef EOPNOTSUPP +# define EOPNOTSUPP ENOTSUP +#endif + #if HAVE_GETTEXT # include <libintl.h> #endif /* HAVE_GETTEXT */ diff --git a/zic.c b/zic.c index 5ed884e5..fe475abb 100644 --- a/zic.c +++ b/zic.c @@ -1764,6 +1764,8 @@ dolink(char const *target, char const *linkname, bool staysymlink) break; } link_errno = errno; + if (link_errno == EOPNOTSUPP) + link_errno = ENOTSUP; /* Linux 2.6.16 and 2.6.17 mishandle AT_SYMLINK_FOLLOW. */ if (link_errno == EINVAL) link_errno = ENOTSUP; @@ -1781,6 +1783,8 @@ dolink(char const *target, char const *linkname, bool staysymlink) break; } link_errno = errno; + if (link_errno == EOPNOTSUPP) + link_errno = ENOTSUP; /* When hard links are not supported, some MS-Windows file system drivers fail with EINVAL, contrary to the intent of MS-FSA 42.0 (2025) section 2.1.5.15.7. */ @@ -1862,7 +1866,7 @@ dolink(char const *target, char const *linkname, bool staysymlink) else if (symlink_errno < 0) warning(N_("copy used because symbolic link not obvious")); else if (symlink_errno != ENOSYS && symlink_errno != ENOTSUP - && symlink_errno != EPERM) + && symlink_errno != EOPNOTSUPP && symlink_errno != EPERM) warning(N_("copy used because symbolic link failed: %s"), strerror(symlink_errno)); } -- 2.53.0
On 7/31/26 10:14, Paul Eggert via tz wrote:
Thanks for reporting that. It looks like EPERM can also happen on Solaris/Illumos, AIX, and FreeBSD/macOS. This is due to a longstanding confusion dating back to 6th Edition Unix: way back then, hard links to directories were allowed if you were the superuser (this was because there was no mkdir syscall, the mkdir command was setuid root, and it created "." and ".." entries by using the link syscall), and if link(A,B) failed with EACCES it meant you lacked permission to the parent of A or B, whereas if it failed with EPERM it meant that A was a directory and you were not the superuser. As time evolved, for this syscall EPERM came to mean "wrong file type" rather than "permission denied", even though nowadays even the superuser cannot hard link to directories.
macOS Time Machine backup facility uses hard links to directories. If a directory has not been modified it hard links to a prior version. Kids, don't try this at home! MacOS delivers TZ data using copies in place of ssymlinks. Because their package manager doesn't support symlinks Cygwin avoids symbolic links because they don't conform to POSIX. it uses an alternative. -- gil
On 2026-07-31 10:59, Paul Gilmartin via tz wrote:
On 7/31/26 10:14, Paul Eggert via tz wrote:
Thanks for reporting that. It looks like EPERM can also happen on Solaris/ Illumos, AIX, and FreeBSD/macOS. This is due to a longstanding confusion dating back to 6th Edition Unix: way back then, hard links to directories were allowed if you were the superuser (this was because there was no mkdir syscall, the mkdir command was setuid root, and it created "." and ".." entries by using the link syscall), and if link(A,B) failed with EACCES it meant you lacked permission to the parent of A or B, whereas if it failed with EPERM it meant that A was a directory and you were not the superuser. As time evolved, for this syscall EPERM came to mean "wrong file type" rather than "permission denied", even though nowadays even the superuser cannot hard link to directories.
macOS Time Machine backup facility uses hard links to directories. If a directory has not been modified it hard links to a prior version. Kids, don't try this at home!
MacOS delivers TZ data using copies in place of symlinks. Because their package manager doesn't support symlinks
Cygwin avoids symbolic links because they don't conform to POSIX. it uses an alternative.
Cygwin uses many alternatives depending on FS: - create and use reparse points as used by WSL on NT FS with W10 1607+ - will recognize NT FS directory junctions - create and use AFS and NFS symlinks - create and use files containing a magic cookie and UTF16LE path with SYSTEM attribute set on legacy NT or ex/V/FAT/12/16/32 FS - create and use .lnk shortcuts with RO attribute set on MV FS or similar see https://cygwin.com/cygwin-ug-net/using.html#pathnames-symlinks -- Take care. Thanks, Brian Inglis Calgary, Alberta, Canada La perfection est atteinte Perfection is achieved non pas lorsqu'il n'y a plus rien à ajouter not when there is no more to add mais lorsqu'il n'y a plus rien à retrancher but when there is no more to cut -- Antoine de Saint-Exupéry
participants (4)
-
Brian Inglis -
Paul Eggert -
Paul Gilmartin -
Tom Lane