]> git.ipfire.org Git - thirdparty/gnulib.git/commitdiff
openat2: don’t fail with ENOTCAPABLE on FreeBSD
authorPaul Eggert <eggert@cs.ucla.edu>
Wed, 26 Nov 2025 21:08:34 +0000 (13:08 -0800)
committerPaul Eggert <eggert@cs.ucla.edu>
Wed, 26 Nov 2025 21:10:50 +0000 (13:10 -0800)
Problem reported by Pavel Cahyna in:
https://lists.gnu.org/r/bug-gnulib/2025-11/msg00257.html
* lib/openat2.c (ENOTCAPABLE): Default to 0.
(do_openat2, openat2): Map FreeBSD’s ENOTCAPABLE to GNU/Linux’s EXDEV.
* tests/test-openat2.c (do_prepare_symlinks, do_test_resolve)
(do-test-basic): Add related tests.

ChangeLog
lib/openat2.c
tests/test-openat2.c

index e6c596a9cf56cbc7172a52fe04e5de6b4b6709ab..8019977c73f03827ec9336519f62f152dae6cd91 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,13 @@
+2025-11-26  Paul Eggert  <eggert@cs.ucla.edu>
+
+       openat2: don’t fail with ENOTCAPABLE on FreeBSD
+       Problem reported by Pavel Cahyna in:
+       https://lists.gnu.org/r/bug-gnulib/2025-11/msg00257.html
+       * lib/openat2.c (ENOTCAPABLE): Default to 0.
+       (do_openat2, openat2): Map FreeBSD’s ENOTCAPABLE to GNU/Linux’s EXDEV.
+       * tests/test-openat2.c (do_prepare_symlinks, do_test_resolve)
+       (do_test_basic): Add related tests.
+
 2025-11-25  Paul Eggert  <eggert@cs.ucla.edu>
 
        threads-h: have mtx_lock join the throng
index d616b80a5586081a45c71413aea951b225fef7bd..d8087a0d2a28cd3b9cdef108f3f3038832a26b25 100644 (file)
 # define E2BIG EINVAL
 #endif
 
+#ifndef ENOTCAPABLE /* A FreeBSD error number.  */
+# define ENOTCAPABLE 0
+#endif
+
 #ifndef PATH_MAX
 # define PATH_MAX IDX_MAX
 #endif
@@ -333,6 +337,8 @@ do_openat2 (int *fd, char const *filename,
           if (subfd < 0)
             {
               int openerr = negative_errno ();
+              if (O_RESOLVE_BENEATH && openerr == -ENOTCAPABLE)
+                return -EXDEV;
               if (! ((openerr == -_GL_OPENAT_ESYMLINK)
                      | (!!(subflags & O_DIRECTORY) & (openerr == -ENOTDIR))))
                 return openerr;
@@ -553,16 +559,21 @@ openat2 (int dfd, char const *filename,
       char resolve = how->resolve;
       mode_t mode = how->mode;
 
+      int fd;
+
       /* For speed use openat if it suffices, though it is unlikely a
          caller would use openat2 when openat's simpler API would do.  */
       if (O_RESOLVE_BENEATH ? !(resolve & ~RESOLVE_BENEATH) : !resolve)
         {
           if (resolve & RESOLVE_BENEATH)
             flags |= O_RESOLVE_BENEATH;
-          return openat (dfd, filename, flags, mode);
+          fd = openat (dfd, filename, flags, mode);
+          if (O_RESOLVE_BENEATH && fd < 0 && errno == ENOTCAPABLE)
+            errno = EXDEV;
+          return fd;
         }
 
-      int fd = dfd;
+      fd = dfd;
       char stackbuf[256];
       char *buf = stackbuf;
 
index 12830a0bc9fbe502a1ce019530a1f197fe8c3d47..da2dde415ef430164dddc558829a64a0162e799c 100644 (file)
@@ -98,6 +98,7 @@ do_prepare_symlinks ()
         |- valid_link -> some_file
         |- subdir/
            |- some_file
+           |- aunt_link -> ../some_file
   */
 
   ASSERT (symlinkat ("subdir", dfd, "dirlink") == 0);
@@ -107,6 +108,7 @@ do_prepare_symlinks ()
   ASSERT (symlinkat ("some_file/invalid", dfd, "invalid_link") == 0);
   ASSERT (symlinkat ("some_file", dfd, "valid_link") == 0);
   ASSERT (mkdirat (dfd, "subdir", 0700) == 0);
+  ASSERT (symlinkat ("../some_file", dfd, "subdir/aunt_link") == 0);
   ASSERT (close (openat2 (dfd, "some_file",
                           (&(struct open_how) { .flags = O_CREAT,
                                                 .mode = 0600 }),
@@ -457,6 +459,16 @@ do_test_resolve (void)
   ASSERT ((errno == ENOENT) | is_nofollow_error (errno));
   ASSERT (fd == -1);
 
+  fd = openat2 (dfd,
+                "subdir/aunt_link",
+                (&(struct open_how)
+                 {
+                   .flags = O_RDONLY,
+                   .resolve = RESOLVE_BENEATH,
+                 }),
+                sizeof (struct open_how));
+  ASSERT (close (fd) == 0);
+
   {
     int subdfd = openat2 (dfd,
                           "subdir",
@@ -489,6 +501,20 @@ do_test_resolve (void)
                    }),
                   sizeof (struct open_how));
     ASSERT (close (fd) == 0);
+
+    /* Check that aunt_link cannot escape subdir.  */
+    fd = openat2 (subdfd,
+                  "aunt_link",
+                  (&(struct open_how)
+                   {
+                     .flags = O_RDONLY,
+                     .resolve = RESOLVE_BENEATH,
+                   }),
+                  sizeof (struct open_how));
+    ASSERT (errno == EXDEV);
+    ASSERT (fd == -1);
+
+    ASSERT (close (subdfd) == 0);
   }
 }
 
@@ -531,6 +557,7 @@ do_test_basic ()
   ASSERT (unlinkat (dfd, "escaping_link_2", 0) == 0);
   ASSERT (unlinkat (dfd, "invalid_link", 0) == 0);
   ASSERT (unlinkat (dfd, "some_file", 0) == 0);
+  ASSERT (unlinkat (dfd, "subdir/aunt_link", 0) == 0);
   ASSERT (unlinkat (dfd, "subdir/some_file", 0) == 0);
   ASSERT (unlinkat (dfd, "subdir", AT_REMOVEDIR) == 0);
   ASSERT (unlinkat (dfd, "valid_link", 0) == 0);