Re: [yocto-patches] [pseudo] [PATCH v2 1/2] ports/linux/guts: Implement close_range() instead of returning ENOSYS

Richard Purdie <[email protected]> Tue, 28 Jul 2026 11:15:41 +0100
Newsgroups org.yoctoproject.lists.yocto-patches
Message-ID <75f931b54054383a3de7f871ecaa10928c36a5b9.camel@linuxfoundation.org>
On Tue, 2026-07-28 at 10:22 +0100, Richard Purdie via lists.yoctoproject.or=
g wrote:
> On Thu, 2026-07-16 at 05:56 +0000, Babanpreet Singh wrote:
> > The close_range() wrapper added in 35433e6 ("ports/linux/guts: Add
> > close_range wrapper for glibc 2.34") returns ENOSYS on the assumption
> > that callers handle that. systemd v260 no longer does: it removed its
> > /proc/self/fd fallback and treats a close_range() failure as fatal, so
> > every fork+exec under pseudo aborts:
> >=20
> > =C2=A0=C2=A0=C2=A0 Failed to close all file descriptors: Function not i=
mplemented
> > =C2=A0=C2=A0=C2=A0 '(mkfs)' failed with exit status 1.
> >=20
> > A client side op closes the low descriptors one at a time, skipping the
> > ones pseudo needs for itself, and returns the first fd above pseudo's
> > own so the caller can pass the rest of the range to the kernel directly=
.
> >=20
> > CLOSE_RANGE_UNSHARE is handled by calling unshare(CLONE_FILES) before
> > closing anything, so the closes only affect the caller and not other
> > processes sharing the descriptor table. Unknown flags and an inverted
> > range are rejected with EINVAL before anything is closed.
> >=20
> > A range starting entirely above INT_MAX cannot contain any of pseudo's
> > fds and is also passed straight through.
> >=20
> > [YOCTO #16339]
> >=20
> > AI-Generated: Uses Claude (claude-opus-4-8)
> > Signed-off-by: Babanpreet Singh <[email protected]>
> > ---
> > =C2=A0enums/op.in=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=
=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 |=C2=A0 1 +
> > =C2=A0ports/linux/guts/close_range.c | 54 ++++++++++++++++++++++++++---=
-
> > =C2=A0ports/linux/portdefs.h=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=
=C2=A0 | 16 +++++++++
> > =C2=A0pseudo_client.c=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=
=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 | 60 +++++++++++++++++++++++++++=
+++++++
> > =C2=A04 files changed, 124 insertions(+), 7 deletions(-)
>=20
> Unfortunately there is something wrong in this patch. I added this
> series and your other series fixing the closed fds and with this patch
> present, we see failures:
>=20
> https://autobuilder.yoctoproject.org/valkyrie/#/builders/50/builds/4259
>=20
> i.e. linux-libc-headers do_install fails, which is one of the first
> pieces of the OE build to use pseudo. This should be reproducible in a
> OE build with "bitbake linux-libc-headers -c install", with a patch
> like the one below applied to update pseudo.
>=20
> As soon as I revert this patch, the builds work ok. I also tested with
> the second series applied without this patch (patches tweaked to apply)
> and that also seems to build ok, so it would appear to be something in
> this change.
>=20
> I've not dug into it in detail yet but I wanted to share the issue now
> I've isolated it.

I had a look at the code and had a theory about the type conversions on
maxfd being an issue. This patch appears to help:

diff --git a/ports/linux/guts/close_range.c b/ports/linux/guts/close_range.=
c
index c615148..784d066 100644
--- a/ports/linux/guts/close_range.c
+++ b/ports/linux/guts/close_range.c
@@ -7,6 +7,7 @@
  *      int rc =3D -1;
  */
        pseudo_msg_t *msg;
+       int maxintfd;
=20
        /* The kernel rejects both of these outright and closes nothing whe=
n
         * it does, so validate before touching anything.
@@ -43,17 +44,21 @@
         */
        if (lowfd > INT_MAX)
                return real_close_range(lowfd, maxfd, flags);
+       if (maxfd > INT_MAX)
+               maxintfd =3D INT_MAX;
+       else
+               maxintfd =3D (int) maxfd;=20
=20
        /* Same shape as closefrom(): the client op closes the descriptors =
its
         * own are mixed in with by hand, stepping around the ones pseudo n=
eeds
         * to keep, and hands back the first fd the kernel can safely be tu=
rned
         * loose on.
         */
-       //msg =3D pseudo_client_op(OP_CLOSE_RANGE, 0, lowfd, -1, 0, 0, maxf=
d);
-       //if (maxfd >=3D (unsigned int) msg->fd)
-       //      rc =3D real_close_range(msg->fd, maxfd, flags);
-       //else
-       //      rc =3D 0;
+       msg =3D pseudo_client_op(OP_CLOSE_RANGE, 0, lowfd, -1, 0, 0, maxint=
fd);
+       if (maxfd >=3D (unsigned int) msg->fd)
+               rc =3D real_close_range(msg->fd, maxfd, flags);
+       else
+               rc =3D 0;
        errno =3D ENOSYS;
        rc =3D -1;
=20
diff --git a/pseudo_client.c b/pseudo_client.c
index 98112bc..e846dc9 100644
--- a/pseudo_client.c
+++ b/pseudo_client.c
@@ -1653,7 +1653,7 @@ pseudo_client_op(pseudo_op_t op, int access, int fd, =
int dirfd, const char *path
        static size_t alloced_len =3D 0;
        int strip_slash;
        int startfd, i;
-       unsigned int close_range_maxfd =3D 0;
+       int close_range_maxfd =3D 0;
=20
 #ifdef PSEUDO_PROFILING
        struct timeval tv1_op, tv2_op;
@@ -1777,7 +1777,7 @@ pseudo_client_op(pseudo_op_t op, int access, int fd, =
int dirfd, const char *path
        if (op =3D=3D OP_CLOSE_RANGE) {
                va_list ap;
                va_start(ap, buf);
-               close_range_maxfd =3D va_arg(ap, unsigned int);
+               close_range_maxfd =3D va_arg(ap, int);
                va_end(ap);
        }
=20
@@ -2017,7 +2017,7 @@ pseudo_client_op(pseudo_op_t op, int access, int fd, =
int dirfd, const char *path
                /* the fds below startfd are the ones our own are mixed in
                 * with, so close those by hand and skip the ones we need
                 */
-               for (i =3D fd; i < startfd && (unsigned int) i <=3D close_r=
ange_maxfd; ++i) {
+               for (i =3D fd; i < startfd && i <=3D close_range_maxfd; ++i=
) {
                        if (i =3D=3D pseudo_util_debug_fd || i =3D=3D pseud=
o_util_evlog_fd ||
                                        i =3D=3D pseudo_prefix_dir_fd || i =
=3D=3D pseudo_localstate_dir_fd ||
                                        i =3D=3D pseudo_pwd_fd || i =3D=3D =
pseudo_pwd_lck_fd ||
@@ -2026,7 +2026,7 @@ pseudo_client_op(pseudo_op_t op, int access, int fd, =
int dirfd, const char *path
                        pseudo_client_close(i);
                        close(i);
                }
-               if (close_range_maxfd >=3D (unsigned int) startfd)
+               if (close_range_maxfd >=3D startfd)
                        pseudo_client_close_range(startfd, close_range_maxf=
d);
                /* tell the caller to start at startfd instead of fd */
                result =3D &msg;

which is I suspect because there is code doing:

close_range(3, ~0U, xxx);

Whether we should just force maxfd to INT_MAX always, I'm not sure. I
don't think the high fds are usable.

Cheers,

Richard