+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
# define E2BIG EINVAL
#endif
+#ifndef ENOTCAPABLE /* A FreeBSD error number. */
+# define ENOTCAPABLE 0
+#endif
+
#ifndef PATH_MAX
# define PATH_MAX IDX_MAX
#endif
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;
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;
|- valid_link -> some_file
|- subdir/
|- some_file
+ |- aunt_link -> ../some_file
*/
ASSERT (symlinkat ("subdir", dfd, "dirlink") == 0);
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 }),
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",
}),
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);
}
}
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);