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

Reply via email to