On 2025-11-26 09:05, Pavel Cahyna wrote:
I think though that the latter shows that the errno reported by the openat2 module should be more consistent. I.e. that ENOTCAPABLE should be translated to EXDEV.
Thanks, good idea. I installed the attached.
From d1aeb7388926e045bdec0f7934c5522c4745f02c Mon Sep 17 00:00:00 2001 From: Paul Eggert <[email protected]> Date: Wed, 26 Nov 2025 13:08:34 -0800 Subject: [PATCH] =?UTF-8?q?openat2:=20don=E2=80=99t=20fail=20with=20ENOTCA?= =?UTF-8?q?PABLE=20on=20FreeBSD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 | 10 ++++++++++ lib/openat2.c | 15 +++++++++++++-- tests/test-openat2.c | 27 +++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index e6c596a9cf..8019977c73 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +2025-11-26 Paul Eggert <[email protected]> + + 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 <[email protected]> threads-h: have mtx_lock join the throng diff --git a/lib/openat2.c b/lib/openat2.c index d616b80a55..d8087a0d2a 100644 --- a/lib/openat2.c +++ b/lib/openat2.c @@ -60,6 +60,10 @@ # 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; diff --git a/tests/test-openat2.c b/tests/test-openat2.c index 12830a0bc9..da2dde415e 100644 --- a/tests/test-openat2.c +++ b/tests/test-openat2.c @@ -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); -- 2.51.0
