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.
From d3fd3e98a5116b8a9f2075a1a9f8e3fc94620aa9 Mon Sep 17 00:00:00 2001 From: Paul Eggert <[email protected]> Date: Fri, 31 Jul 2026 08:37:55 -0700 Subject: [PROPOSED] Port zic link handling to more platforms Problem reported by Tom Lane in: https://lists.iana.org/hyperkitty/list/[email protected]/message/RG2LECHFCNCUPH6D6KLQCPTQD2MP36GS/ * NEWS: Mention this. * private.h (ENOSYS, EPERM): New macros, if not already defined. * zic.c (dolink): Treat ENOSYS and EPERM like ENOTSUP. --- NEWS | 4 ++-- private.h | 6 ++++++ zic.c | 10 +++++++++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 49c8c28a..4948ca73 100644 --- a/NEWS +++ b/NEWS @@ -49,8 +49,8 @@ Unreleased, experimental changes zic now rejects Link targets that would have invalid names. (Thanks to Darren Carreras.) - zic now ports to MS-Windows file system drivers that misreport - lack of support for hard links. (Thanks to Tom Lane.) + zic now ports to systems that report lack of link support via + EINVAL, ENOSYS or EPERM errno values. (Thanks to Tom Lane.) Release 2026c - 2026-07-08 10:23:58 -0700 diff --git a/private.h b/private.h index 7918082d..8ef03daf 100644 --- a/private.h +++ b/private.h @@ -255,6 +255,9 @@ strnlen (char const *s, size_t maxlen) #ifndef ENOMEM # define ENOMEM EINVAL #endif +#ifndef ENOSYS +# define ENOSYS EINVAL +#endif #ifndef ENOTCAPABLE # define ENOTCAPABLE EINVAL #endif @@ -264,6 +267,9 @@ strnlen (char const *s, size_t maxlen) #ifndef EOVERFLOW # define EOVERFLOW EINVAL #endif +#ifndef EPERM +# define EPERM EINVAL +#endif #if HAVE_GETTEXT # include <libintl.h> diff --git a/zic.c b/zic.c index f6d2ecc0..5ed884e5 100644 --- a/zic.c +++ b/zic.c @@ -1788,6 +1788,13 @@ dolink(char const *target, char const *linkname, bool staysymlink) link_errno = ENOTSUP; } #endif + /* On platforms like AIX, the Linux kernel, macOS, and Solaris, + link/linkat can fail with ENOSYS or EPERM if the file system + does not support hard links, or if other problems occur. + It is too much trouble to suss out the other problems. */ + if (link_errno == ENOSYS || link_errno == EPERM) + link_errno = ENOTSUP; + if (link_errno == EXDEV || link_errno == ENOTSUP) break; @@ -1854,7 +1861,8 @@ dolink(char const *target, char const *linkname, bool staysymlink) strerror(link_errno)); else if (symlink_errno < 0) warning(N_("copy used because symbolic link not obvious")); - else if (symlink_errno != ENOTSUP) + else if (symlink_errno != ENOSYS && symlink_errno != ENOTSUP + && symlink_errno != EPERM) warning(N_("copy used because symbolic link failed: %s"), strerror(symlink_errno)); } -- 2.53.0
