@@ -27,6 +27,13 @@ Major new features:
The pidfd functionality avoids the issue of PID reuse with the traditional
posix_spawn interface.
+* On Linux, the fork_np has been added. It has a similar semantic as ai
+ fork or _Fork, where it clones the calling process; and allows to extend
+ of the fork functionality by allowing the return of a process file
+ descriptor (as pidfd_spawn), setting a cgroupv2 of the new process (as
+ posix_spawnattr_getcgroup_np), setting a different signal on process
+ termination, and making the function act as _Fork.
+
Deprecated and removed features, and other changes affecting compatibility:
[Add deprecations, removals and changes affecting compatibility here]
@@ -2,6 +2,8 @@
#define _CLONE_INTERNAL_H
#include <clone3.h>
+#include <stdbool.h>
+#include <stdint.h>
/* The clone3 syscall provides a superset of the functionality of the clone
interface. The kernel might extend __CL_ARGS struct in the future, with
@@ -35,6 +37,21 @@ extern int __clone_internal_fallback (struct clone_args *__cl_args,
void *__arg)
attribute_hidden;
+/* Call the clone3/clone syscall with fork semantic (i.e. no stack setting
+ required). The EXTRA_FLAGS define any additional flag to be used besides
+ CLONE_CHILD_SETTID and CLONE_CHILD_CLEARTID, the PIDFD indicates where
+ the process file descriptor (set with CLONE_PIDFD) should be returned,
+ and the CGROUP specifies the cgroupsv2 (set with CLONE_INTO_CGROUP).
+
+ Similar to __clone3_internal, it uses the stick check to avoid re-issue
+ the clone3 syscall if kernel does not support it.
+
+ It does not provide CLONE_INTO_CGROUP/CGROUP fallback if clone3 is not
+ supported, in this case the function returns -1/ENOTSUP. */
+extern int __clone_fork (uint64_t __extra_flags, void *__pidfd, int __cgroup,
+ int __exit_signal)
+ attribute_hidden;
+
/* Return whether the kernel supports pid file descriptor, including clone
with CLONE_PIDFD and waitid with P_PIDFD. */
extern bool __clone_pidfd_supported (void) attribute_hidden;
@@ -137,12 +137,12 @@ creating a process and making it run another program.
@cindex subprocess
A new processes is created when one of the functions
@code{posix_spawn}, @code{fork}, @code{_Fork}, @code{vfork}, or
-@code{pidfd_spawn} is called. (The @code{system} and @code{popen} also
-create new processes internally.) Due to the name of the @code{fork}
-function, the act of creating a new process is sometimes called
-@dfn{forking} a process. Each new process (the @dfn{child process} or
-@dfn{subprocess}) is allocated a process ID, distinct from the process
-ID of the parent process. @xref{Process Identification}.
+@code{pidfd_spawn}, or @code{fork_np} is called. (The @code{system}
+and @code{popen} also create new processes internally.) Due to the name
+of the @code{fork} function, the act of creating a new process is
+sometimes called @dfn{forking} a process. Each new process (the
+@dfn{child process} or @dfn{subprocess}) is allocated a process ID,
+distinct from the process ID of the parent process. @xref{Process Identification}.
After forking a child process, both the parent and child processes
continue to execute normally. If you want your program to wait for a
@@ -153,10 +153,10 @@ limited information about why the child terminated---for example, its
exit status code.
A newly forked child process continues to execute the same program as
-its parent process, at the point where the @code{fork} or @code{_Fork}
-call returns. You can use the return value from @code{fork} or
-@code{_Fork} to tell whether the program is running in the parent process
-or the child.
+its parent process, at the point where the @code{fork}, @code{_Fork},
+or @code{fork_np} call returns. You can use the return value from
+@code{fork}, @code{_Fork}, or @code{fork_np} to tell whether the
+program is running in the parent process or the child.
@cindex process image
Having several processes run the same program is only occasionally
@@ -362,6 +362,68 @@ the proper precautions for using @code{vfork}, your program will still
work even if the system uses @code{fork} instead.
@end deftypefun
+@deftp {Data Type} {fork_np_args_t}
+@standards{GNU, unistd.h}
+This structure is used to along @code{fork_np} to enable extra
+functionality.
+
+@table @code
+@item uint64_t fork_np_flags
+If @code{FORK_NP_PIDFD} is set, the process file descriptor will be
+returned on @code{pidfd}.
+If @code{FORK_NP_CGROUP} is set, the value from @code{cgroup} will be used
+to specify a different cgroupv2 to start the new process.
+If @code{FORK_NP_ASYNCSAFE} is set, @code{fork_np} will not issue any
+atfork handler (similar to @code{_Fork}).
+If @code{PIDFDFORK_EXIT_SIGNAL} is set, the signal defined at @code{exit_signal}
+will be send on process termination.
+
+@item int fork_np_pidfd
+Return the process file descriptor if @code{FORK_NP_PIDFD} is set.
+
+@item int fork_np_cgroup
+Set the cgroupv2 to be used on the new process.
+
+@item int fork_np_exit_signal;
+Define which signal to send on process termination.
+@end table
+
+This union is a GNU extension.
+@end deftp
+
+@deftypefun pid_t fork_np (fork_np_args_t @var{args}, size_t @var{len})
+@standards{GNU, unistd.h}
+@safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
+The @code{fork_np} function is similar to @code{fork} on both semantic and
+return code value, but allows extra functionality through the @code{args}
+parameter. The @code{len} must be the size of @code{args}, otherwise the
+function returns with a failure.
+
+If @code{FORK_NP_PIDFD} is set on @code{fork_np_flags}, and the process is
+correctly created the @code{fork_np_pidfd} frkm @var{args} will contain a
+file descriptor that can be used along other pidfd functions (like
+@code{pidfd_send_signal} or with @code{waitid} along with@code{P_PIDFD}.
+
+If @code{FORK_NP_CGROUP} is set on @code{fork_np_flags}, the
+@code{fork_np_cgroup} value from @var{args} will be used as the cgroups v2
+control group on process creation. There is no fallback implementation,
+meaning If the kernel does not provide the required support an error is returned.
+
+If @code{FORK_NP_ASYNCSAFE} is set on @code{fork_np_flags}, @code{fork_np}
+acts as @code{_Fork}, where it does not invoke any callbacks registered with
+@code{pthread_atfork}, nor does it reset internal state or locks (such as the
+@code{malloc} locks).
+
+If @code{FORK_NP_EXIT_SIGNAL} is set on @code{flags}, the signal number
+@code{fork_np_exit_signal} from @code{args} will be sent on process
+termination. The @code{0} value is also valid, meaning that no signal
+will be sent. @strong{NB:} When using this flag, the parent process must
+specify the @code{__WALL} or @code{__WCLONE} options when waiting for the
+child with @code{wait} or @code{waitid}.
+
+This function is a GNU extension and specific to Linux.
+@end deftypefun
+
@node Executing a File
@section Executing a File
@cindex executing a file
@@ -85,6 +85,7 @@ routines := \
fexecve \
fnmatch \
fork \
+ fork-internal \
fpathconf \
gai_strerror \
get_child_max \
@@ -589,7 +590,7 @@ CFLAGS-execl.os = -fomit-frame-pointer
CFLAGS-execvp.os = -fomit-frame-pointer
CFLAGS-execlp.os = -fomit-frame-pointer
CFLAGS-nanosleep.c += -fexceptions -fasynchronous-unwind-tables
-CFLAGS-fork.c = $(libio-mtsafe) $(config-cflags-wno-ignored-attributes)
+CFLAGS-fork-internal.c = $(libio-mtsafe) $(config-cflags-wno-ignored-attributes)
tstgetopt-ARGS = -a -b -cfoobar --required foobar --optional=bazbug \
--none random --col --color --colour
new file mode 100644
@@ -0,0 +1,127 @@
+/* Internal fork definitions.
+ Copyright (C) 2023 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <fork.h>
+#include <fork-internal.h>
+#include <ldsodefs.h>
+#include <libio/libioP.h>
+#include <malloc/malloc-internal.h>
+#include <register-atfork.h>
+#include <stdio-lock.h>
+#include <unwind-link.h>
+
+static void
+fresetlockfiles (void)
+{
+ _IO_ITER i;
+
+ for (i = _IO_iter_begin(); i != _IO_iter_end(); i = _IO_iter_next(i))
+ if ((_IO_iter_file (i)->_flags & _IO_USER_LOCK) == 0)
+ _IO_lock_init (*((_IO_lock_t *) _IO_iter_file(i)->_lock));
+}
+
+uint64_t
+__fork_pre (bool multiple_threads, struct nss_database_data *nss_database_data)
+{
+ uint64_t lastrun = __run_prefork_handlers (multiple_threads);
+
+ /* If we are not running multiple threads, we do not have to
+ preserve lock state. If fork runs from a signal handler, only
+ async-signal-safe functions can be used in the child. These data
+ structures are only used by unsafe functions, so their state does
+ not matter if fork was called from a signal handler. */
+ if (multiple_threads)
+ {
+ call_function_static_weak (__nss_database_fork_prepare_parent,
+ nss_database_data);
+
+ _IO_list_lock ();
+
+ /* Acquire malloc locks. This needs to come last because fork
+ handlers may use malloc, and the libio list lock has an
+ indirect malloc dependency as well (via the getdelim
+ function). */
+ call_function_static_weak (__malloc_fork_lock_parent);
+ }
+
+ return lastrun;
+}
+
+void
+__fork_post (struct fork_post_state_t *state,
+ struct nss_database_data *nss_database_data)
+{
+ if (state->pid == 0)
+ {
+ fork_system_setup ();
+
+ /* Reset the lock state in the multi-threaded case. */
+ if (state->multiple_threads)
+ {
+ __libc_unwind_link_after_fork ();
+
+ fork_system_setup_after_fork ();
+
+ /* Release malloc locks. */
+ call_function_static_weak (__malloc_fork_unlock_child);
+
+ /* Reset the file list. These are recursive mutexes. */
+ fresetlockfiles ();
+
+ /* Reset locks in the I/O code. */
+ _IO_list_resetlock ();
+
+ call_function_static_weak (__nss_database_fork_subprocess,
+ nss_database_data);
+ }
+
+ /* Reset the lock the dynamic loader uses to protect its data. */
+ __rtld_lock_initialize (GL(dl_load_lock));
+
+ /* Reset the lock protecting dynamic TLS related data. */
+ __rtld_lock_initialize (GL(dl_load_tls_lock));
+
+ reclaim_stacks ();
+
+ /* Run the handlers registered for the child. */
+ __run_postfork_handlers (atfork_run_child, state->multiple_threads,
+ state->lastrun);
+ }
+ else
+ {
+ /* If _Fork failed, preserve its errno value. */
+ int save_errno = errno;
+
+ /* Release acquired locks in the multi-threaded case. */
+ if (state->multiple_threads)
+ {
+ /* Release malloc locks, parent process variant. */
+ call_function_static_weak (__malloc_fork_unlock_parent);
+
+ /* We execute this even if the 'fork' call failed. */
+ _IO_list_unlock ();
+ }
+
+ /* Run the handlers registered for the parent. */
+ __run_postfork_handlers (atfork_run_parent, state->multiple_threads,
+ state->lastrun);
+
+ if (state->pid < 0)
+ __set_errno (save_errno);
+ }
+}
new file mode 100644
@@ -0,0 +1,36 @@
+/* Internal fork definitions.
+ Copyright (C) 2023 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#ifndef _FORK_INTERNAL_H
+#define _FORK_INTERNAL_H
+
+#include <stdint.h>
+#include <nss/nss_database.h>
+
+struct fork_post_state_t
+{
+ bool multiple_threads;
+ pid_t pid;
+ uint64_t lastrun;
+};
+
+uint64_t __fork_pre (bool, struct nss_database_data *) attribute_hidden;
+void __fork_post (struct fork_post_state_t *, struct nss_database_data *)
+ attribute_hidden;
+
+#endif
@@ -16,25 +16,10 @@
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
-#include <fork.h>
-#include <libio/libioP.h>
-#include <ldsodefs.h>
-#include <malloc/malloc-internal.h>
-#include <nss/nss_database.h>
-#include <register-atfork.h>
-#include <stdio-lock.h>
+#include <fork-internal.h>
#include <sys/single_threaded.h>
#include <unwind-link.h>
-
-static void
-fresetlockfiles (void)
-{
- _IO_ITER i;
-
- for (i = _IO_iter_begin(); i != _IO_iter_end(); i = _IO_iter_next(i))
- if ((_IO_iter_file (i)->_flags & _IO_USER_LOCK) == 0)
- _IO_lock_init (*((_IO_lock_t *) _IO_iter_file(i)->_lock));
-}
+#include <unistd.h>
pid_t
__libc_fork (void)
@@ -45,92 +30,18 @@ __libc_fork (void)
requirement for fork (Austin Group tracker issue #62) this is
best effort to make is async-signal-safe at least for single-thread
case. */
- bool multiple_threads = !SINGLE_THREAD_P;
- uint64_t lastrun;
-
- lastrun = __run_prefork_handlers (multiple_threads);
-
+ struct fork_post_state_t state = {
+ .multiple_threads = !SINGLE_THREAD_P
+ };
struct nss_database_data nss_database_data;
- /* If we are not running multiple threads, we do not have to
- preserve lock state. If fork runs from a signal handler, only
- async-signal-safe functions can be used in the child. These data
- structures are only used by unsafe functions, so their state does
- not matter if fork was called from a signal handler. */
- if (multiple_threads)
- {
- call_function_static_weak (__nss_database_fork_prepare_parent,
- &nss_database_data);
-
- _IO_list_lock ();
-
- /* Acquire malloc locks. This needs to come last because fork
- handlers may use malloc, and the libio list lock has an
- indirect malloc dependency as well (via the getdelim
- function). */
- call_function_static_weak (__malloc_fork_lock_parent);
- }
-
- pid_t pid = _Fork ();
-
- if (pid == 0)
- {
- fork_system_setup ();
-
- /* Reset the lock state in the multi-threaded case. */
- if (multiple_threads)
- {
- __libc_unwind_link_after_fork ();
-
- fork_system_setup_after_fork ();
-
- /* Release malloc locks. */
- call_function_static_weak (__malloc_fork_unlock_child);
-
- /* Reset the file list. These are recursive mutexes. */
- fresetlockfiles ();
-
- /* Reset locks in the I/O code. */
- _IO_list_resetlock ();
-
- call_function_static_weak (__nss_database_fork_subprocess,
- &nss_database_data);
- }
-
- /* Reset the lock the dynamic loader uses to protect its data. */
- __rtld_lock_initialize (GL(dl_load_lock));
-
- /* Reset the lock protecting dynamic TLS related data. */
- __rtld_lock_initialize (GL(dl_load_tls_lock));
-
- reclaim_stacks ();
-
- /* Run the handlers registered for the child. */
- __run_postfork_handlers (atfork_run_child, multiple_threads, lastrun);
- }
- else
- {
- /* If _Fork failed, preserve its errno value. */
- int save_errno = errno;
-
- /* Release acquired locks in the multi-threaded case. */
- if (multiple_threads)
- {
- /* Release malloc locks, parent process variant. */
- call_function_static_weak (__malloc_fork_unlock_parent);
-
- /* We execute this even if the 'fork' call failed. */
- _IO_list_unlock ();
- }
+ state.lastrun = __fork_pre (state.multiple_threads, &nss_database_data);
- /* Run the handlers registered for the parent. */
- __run_postfork_handlers (atfork_run_parent, multiple_threads, lastrun);
+ state.pid = _Fork ();
- if (pid < 0)
- __set_errno (save_errno);
- }
+ __fork_post (&state, &nss_database_data);
- return pid;
+ return state.pid;
}
weak_alias (__libc_fork, __fork)
libc_hidden_def (__fork)
@@ -22,7 +22,7 @@
pid_t
_Fork (void)
{
- pid_t pid = arch_fork (&THREAD_SELF->tid);
+ pid_t pid = arch_fork (SIGCHLD, NULL, &THREAD_SELF->tid);
if (pid == 0)
{
struct pthread *self = THREAD_SELF;
@@ -493,6 +493,7 @@ sysdep_headers += \
sysdep_routines += \
getcpu \
oldglob \
+ fork_np \
pidfd_spawn \
pidfd_spawnp \
sched_getcpu \
@@ -503,6 +504,8 @@ sysdep_routines += \
tests += \
tst-affinity \
tst-affinity-pid \
+ tst-fork_np \
+ tst-fork_np-cgroup \
tst-posix_spawn-setsid-pidfd \
tst-spawn-cgroup \
tst-spawn-chdir-pidfd \
@@ -322,6 +322,7 @@ libc {
%endif
}
GLIBC_2.39 {
+ fork_np;
pidfd_spawn;
pidfd_spawnp;
posix_spawnattr_getcgroup_np;
@@ -2673,6 +2673,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
@@ -2782,6 +2782,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
@@ -2434,6 +2434,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
@@ -32,24 +32,24 @@
override it with one of the supported calling convention (check generic
kernel-features.h for the clone abi variants). */
static inline pid_t
-arch_fork (void *ctid)
+arch_fork (int flags, void *ptid, void *ctid)
{
- const int flags = CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD;
long int ret;
+ flags |= CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID;
#ifdef __ASSUME_CLONE_BACKWARDS
# ifdef INLINE_CLONE_SYSCALL
- ret = INLINE_CLONE_SYSCALL (flags, 0, NULL, 0, ctid);
+ ret = INLINE_CLONE_SYSCALL (flags, 0, ptid, 0, ctid);
# else
- ret = INLINE_SYSCALL_CALL (clone, flags, 0, NULL, 0, ctid);
+ ret = INLINE_SYSCALL_CALL (clone, flags, 0, ptid, 0, ctid);
# endif
#elif defined(__ASSUME_CLONE_BACKWARDS2)
- ret = INLINE_SYSCALL_CALL (clone, 0, flags, NULL, ctid, 0);
+ ret = INLINE_SYSCALL_CALL (clone, 0, flags, ptid, ctid, 0);
#elif defined(__ASSUME_CLONE_BACKWARDS3)
- ret = INLINE_SYSCALL_CALL (clone, flags, 0, 0, NULL, ctid, 0);
+ ret = INLINE_SYSCALL_CALL (clone, flags, 0, 0, ptid, ctid, 0);
#elif defined(__ASSUME_CLONE2)
- ret = INLINE_SYSCALL_CALL (clone2, flags, 0, 0, NULL, ctid, 0);
+ ret = INLINE_SYSCALL_CALL (clone2, flags, 0, 0, ptid, ctid, 0);
#elif defined(__ASSUME_CLONE_DEFAULT)
- ret = INLINE_SYSCALL_CALL (clone, flags, 0, NULL, ctid, 0);
+ ret = INLINE_SYSCALL_CALL (clone, flags, 0, ptid, ctid, 0);
#else
# error "Undefined clone variant"
#endif
@@ -554,6 +554,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
@@ -551,6 +551,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
@@ -47,4 +47,55 @@ extern __pid_t gettid (void) __THROW;
# define CLOSE_RANGE_CLOEXEC (1U << 2)
#endif
+#define FORK_NP_ARGS_SIZE_VER0 24
+typedef union
+{
+ struct
+ {
+ __uint64_t fork_np_flags;
+ int fork_np_pidfd;
+ int fork_np_cgroup;
+ int fork_np_exit_signal;
+#define fork_np_flags __data.fork_np_flags
+#define fork_np_pidfd __data.fork_np_pidfd
+#define fork_np_cgroup __data.fork_np_cgroup
+#define fork_np_exit_signal __data.fork_np_exit_signal
+ } __data;
+ char __size [FORK_NP_ARGS_SIZE_VER0];
+} fork_np_args_t;
+
+/* Return the process file descriptor. */
+#define FORK_NP_PIDFD (1ULL << 1)
+/* Specify a different cgroup2 than the default one. */
+#define FORK_NP_CGROUP (1ULL << 2)
+/* Do not issue the pthread_atfork on process creation. */
+#define FORK_NP_ASYNCSAFE (1ULL << 3)
+/* Send a different signal to parent on child termination. */
+#define FORK_NP_EXIT_SIGNAL (1ULL << 4)
+
+/* Clone the calling process, creating an exact copy and return a file
+ descriptor that can be used along other pidfd functions.
+
+ The ARGS changes how the process creation is done.
+
+ If FORK_NP_PIDFD is set on FLAGS, a process file descriptor is returned on
+ PIDFD (which can be used along other pidfd function, like pidfd_signal).
+
+ If FORK_NP_CGROUP is set on FLAGS, the CGROUP file descriptor must
+ reference a cgroup v2 control group which will be used on process
+ creation.
+
+ If FORK_NP_ASYNCSAFE is set on FLAGS, fork_np does not invoke the
+ registered pthread_atfork callacks (similar to _Fork).
+
+ If FORK_NP_EXIT_SIGNAL is set on FLAGS, send the EXIT_SIGNAL signal
+ on process termination.
+
+ On success, the PID of the child process is returned in the parent,
+ and 0 is returned to child. On failure, -1 is returned in the
+ parent, no child process is created. */
+extern pid_t fork_np (fork_np_args_t *__args, __SIZE_TYPE__ __size)
+ __THROW;
+
+
#endif /* __USE_GNU */
@@ -16,6 +16,7 @@
License along with the GNU C Library. If not, see
<https://www.gnu.org/licenses/>. */
+#include <arch-fork.h>
#include <sysdep.h>
#include <stddef.h>
#include <errno.h>
@@ -43,6 +44,11 @@ _Static_assert (offsetofend (struct clone_args, cgroup) == CLONE_ARGS_SIZE_VER2,
_Static_assert (sizeof (struct clone_args) == CLONE_ARGS_SIZE_VER2,
"sizeof (struct clone_args) != CLONE_ARGS_SIZE_VER2");
+#if !__ASSUME_CLONE3 && defined __NR_clone3
+/* Set to 0 if kernel does not support clone3 syscall. */
+static int clone3_supported = 1;
+#endif
+
int
__clone_internal_fallback (struct clone_args *cl_args,
int (*func) (void *arg), void *arg)
@@ -84,7 +90,6 @@ __clone3_internal (struct clone_args *cl_args, int (*func) (void *args),
# if __ASSUME_CLONE3
return __clone3 (cl_args, sizeof (*cl_args), func, arg);
# else
- static int clone3_supported = 1;
if (atomic_load_relaxed (&clone3_supported) == 1)
{
int ret = __clone3 (cl_args, sizeof (*cl_args), func, arg);
@@ -118,3 +123,54 @@ __clone_internal (struct clone_args *cl_args,
}
libc_hidden_def (__clone_internal)
+
+int
+__clone_fork (uint64_t extra_flags, void *pidfd, int cgroup, int exit_signal)
+{
+#ifdef __NR_clone3
+ struct clone_args clone_args =
+ {
+ .flags = extra_flags
+ | CLONE_CHILD_SETTID
+ | CLONE_CHILD_CLEARTID,
+ .exit_signal = exit_signal,
+ .cgroup = cgroup,
+ .child_tid = (uintptr_t) &THREAD_SELF->tid,
+ .pidfd = (uintptr_t) pidfd,
+ .parent_tid = (uintptr_t) pidfd
+ };
+#endif
+
+#if __ASSUME_CLONE3
+ return INLINE_SYSCALL_CALL (clone3, &clone_args, sizeof (clone_args));
+#else
+ /* Some architecture still does not export clone3. */
+ pid_t pid;
+# ifdef __NR_clone3
+ if (atomic_load_relaxed (&clone3_supported) == 1)
+ {
+ pid = INLINE_SYSCALL_CALL (clone3, &clone_args, sizeof (clone_args));
+ if (pid != -1 || errno != ENOSYS)
+ return pid;
+
+ atomic_store_relaxed (&clone3_supported, 0);
+ }
+# endif
+
+ if (!(extra_flags & CLONE_INTO_CGROUP))
+ {
+ int flags = extra_flags | (exit_signal & 0xff);
+ pid = arch_fork (flags, pidfd, &THREAD_SELF->tid);
+ }
+ else
+ {
+ /* No fallback for POSIX_SPAWN_SETCGROUP if clone3 is not supported. */
+ pid = -1;
+# ifdef __NR_clone3
+ if (errno == ENOSYS)
+# endif
+ errno = ENOTSUP;
+ }
+ return pid;
+#endif
+}
@@ -2710,6 +2710,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
new file mode 100644
@@ -0,0 +1,97 @@
+/* fork_np - Duplicated calling process and return a process file
+ descriptor.
+ Copyright (C) 2023 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <unistd.h>
+#include <clone_internal.h>
+#include <fork-internal.h>
+
+static pid_t
+fork_syscall (fork_np_args_t *args)
+{
+ bool use_pidfd = args->fork_np_flags & FORK_NP_PIDFD;
+ bool use_cgroup = args->fork_np_flags & FORK_NP_CGROUP;
+
+ int *pidfd = use_pidfd ? &args->fork_np_pidfd : NULL;
+ int cgroup = use_cgroup ? args->fork_np_cgroup : 0;
+
+ uint64_t extra_flags = (use_pidfd ? CLONE_PIDFD : 0)
+ | (use_cgroup ? CLONE_INTO_CGROUP : 0);
+ int exit_signal = (args->fork_np_flags & FORK_NP_EXIT_SIGNAL)
+ ? args->fork_np_exit_signal : SIGCHLD;
+
+ pid_t pid = __clone_fork (extra_flags, pidfd, cgroup, exit_signal);
+
+ if (pid == 0)
+ {
+ struct pthread *self = THREAD_SELF;
+
+ /* Initialize the robust mutex, check _Fork implementation for a full
+ description why this is required. */
+#if __PTHREAD_MUTEX_HAVE_PREV
+ self->robust_prev = &self->robust_head;
+#endif
+ self->robust_head.list = &self->robust_head;
+ INTERNAL_SYSCALL_CALL (set_robust_list, &self->robust_head,
+ sizeof (struct robust_list_head));
+ }
+ return pid;
+}
+
+#define SUPPORTED_FLAGS (FORK_NP_PIDFD \
+ | FORK_NP_CGROUP \
+ | FORK_NP_ASYNCSAFE \
+ | FORK_NP_EXIT_SIGNAL)
+
+_Static_assert (sizeof (fork_np_args_t) == FORK_NP_ARGS_SIZE_VER0,
+ "sizeof (fork_np_args_t) != FORK_NP_ARGS_SIZE_VER0");
+
+pid_t
+fork_np (fork_np_args_t *args, size_t size)
+{
+ if (size != FORK_NP_ARGS_SIZE_VER0)
+ return INLINE_SYSCALL_ERROR_RETURN_VALUE (EINVAL);
+
+ if (args->fork_np_flags & ~(SUPPORTED_FLAGS))
+ return INLINE_SYSCALL_ERROR_RETURN_VALUE (EINVAL);
+
+ if ((args->fork_np_flags & FORK_NP_CGROUP) && !__clone_pidfd_supported ())
+ return INLINE_SYSCALL_ERROR_RETURN_VALUE (ENOSYS);
+
+ pid_t pid;
+ if (!(args->fork_np_flags & FORK_NP_ASYNCSAFE))
+ {
+ bool multiple_threads = !SINGLE_THREAD_P;
+ struct fork_post_state_t state = {
+ .multiple_threads = !SINGLE_THREAD_P
+ };
+ struct nss_database_data nss_database_data;
+
+ state.lastrun = __fork_pre (multiple_threads, &nss_database_data);
+ state.pid = fork_syscall (args);
+ /* It follow the usual fork semantic, where a positive or negative
+ value is returned to parent, and 0 for the child. */
+ __fork_post (&state, &nss_database_data);
+
+ pid = state.pid;
+ }
+ else
+ pid = fork_syscall (args);
+
+ return pid;
+}
@@ -2659,6 +2659,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
@@ -2843,6 +2843,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
@@ -2608,6 +2608,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
@@ -2194,6 +2194,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
@@ -555,6 +555,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
@@ -2786,6 +2786,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
@@ -2759,6 +2759,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
@@ -2756,6 +2756,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
@@ -2751,6 +2751,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
@@ -2749,6 +2749,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
@@ -2757,6 +2757,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
@@ -2659,6 +2659,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
@@ -2798,6 +2798,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
@@ -2180,6 +2180,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
@@ -2825,6 +2825,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
@@ -2858,6 +2858,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
@@ -2579,6 +2579,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
@@ -2893,6 +2893,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
@@ -2436,6 +2436,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
@@ -2636,6 +2636,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
@@ -2823,6 +2823,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
@@ -2616,6 +2616,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
@@ -2666,6 +2666,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
@@ -2663,6 +2663,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
@@ -2818,6 +2818,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
@@ -2631,6 +2631,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
new file mode 100644
@@ -0,0 +1,170 @@
+/* fork_np test using cgroupsv2.
+ Copyright (C) 2023 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <errno.h>
+#include <sched.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/support.h>
+#include <support/xstdio.h>
+#include <support/xunistd.h>
+#include <support/temp_file.h>
+#include <sys/pidfd.h>
+#include <sys/vfs.h>
+#include <sys/wait.h>
+
+#include <dirent.h>
+
+#define CGROUPFS "/sys/fs/cgroup/"
+#ifndef CGROUP2_SUPER_MAGIC
+# define CGROUP2_SUPER_MAGIC 0x63677270
+#endif
+
+#define F_TYPE_EQUAL(a, b) (a == (typeof(a)) b)
+
+static inline char *
+startswith(const char *s, const char *prefix)
+{
+ size_t l = strlen (prefix);
+ if (strncmp (s, prefix, l) == 0)
+ return (char*) s + l;
+ return NULL;
+}
+
+static char *
+get_cgroup (void)
+{
+ FILE *f = xfopen ("/proc/self/cgroup", "re");
+
+ char *cgroup = NULL;
+
+ char *line = NULL;
+ size_t linesiz = 0;
+ while (xgetline (&line, &linesiz, f) > 0)
+ {
+ char *entry = startswith (line, "0:");
+ if (entry == NULL)
+ continue;
+
+ entry = strchr (entry, ':');
+ if (entry == NULL)
+ continue;
+
+ cgroup = entry + 1;
+ size_t l = strlen (cgroup);
+ if (cgroup[l - 1] == '\n')
+ cgroup[l - 1] = '\0';
+
+ cgroup = xstrdup (entry + 1);
+ break;
+ }
+
+ xfclose (f);
+ free (line);
+
+ return cgroup;
+}
+
+static int
+do_test (void)
+{
+ struct statfs fs;
+ if (statfs (CGROUPFS, &fs) < 0)
+ {
+ if (errno == ENOENT)
+ FAIL_UNSUPPORTED ("not cgroupv2 mount found");
+ FAIL_EXIT1 ("statfs (%s): %m\n", CGROUPFS);
+ }
+
+ if (!F_TYPE_EQUAL (fs.f_type, CGROUP2_SUPER_MAGIC))
+ FAIL_UNSUPPORTED ("%s is not a cgroupv2", CGROUPFS);
+
+ char *cgroup = get_cgroup ();
+ TEST_VERIFY_EXIT (cgroup != NULL);
+ char *newcgroup = xasprintf ("%s/%s", cgroup, "test-fork_np-cgroup");
+ char *cgpath = xasprintf ("%s%s/test-fork_np-cgroup", CGROUPFS, cgroup);
+ free (cgroup);
+
+ if (mkdir (cgpath, 0755) == -1 && errno != EEXIST)
+ {
+ if (errno == EACCES || errno == EPERM || errno == EROFS)
+ FAIL_UNSUPPORTED ("can not create a new cgroupv2 group");
+ FAIL_EXIT1 ("mkdir (%s): %m", cgpath);
+ }
+ add_temp_file (cgpath);
+
+ int dfd = xopen (cgpath, O_DIRECTORY | O_RDONLY | O_CLOEXEC, 0666);
+
+ /* Check if the cgroup used at creation is the same returned by the kernel
+ and not as the parent. */
+ {
+ fork_np_args_t pidfd_args = {
+ .fork_np_flags = FORK_NP_CGROUP,
+ .fork_np_cgroup = dfd,
+ };
+ pid_t pid = fork_np (&pidfd_args, sizeof pidfd_args);
+ if (pid == -1 && errno == ENOTSUP)
+ FAIL_UNSUPPORTED ("kernel does not support CLONE_PIDFD clone flag");
+ TEST_VERIFY_EXIT (pid != -1);
+ if (pid == 0)
+ {
+ char *child_cgroup = get_cgroup ();
+ TEST_VERIFY_EXIT (child_cgroup != NULL);
+ TEST_COMPARE_STRING (newcgroup, child_cgroup);
+ _exit (EXIT_SUCCESS);
+ }
+
+ siginfo_t sinfo;
+ TEST_COMPARE (waitid (P_PID, pid, &sinfo, WEXITED), 0);
+ TEST_COMPARE (sinfo.si_signo, SIGCHLD);
+ TEST_COMPARE (sinfo.si_code, CLD_EXITED);
+ TEST_COMPARE (sinfo.si_status, 0);
+ }
+
+ /* Same as before, but also check along with process file descriptor. */
+ {
+ fork_np_args_t pidfd_args = {
+ .fork_np_flags = FORK_NP_PIDFD | FORK_NP_CGROUP,
+ .fork_np_cgroup = dfd,
+ };
+ pid_t pid = fork_np (&pidfd_args, sizeof pidfd_args);
+ TEST_VERIFY_EXIT (pid != -1);
+ if (pid == 0)
+ {
+ char *child_cgroup = get_cgroup ();
+ TEST_VERIFY_EXIT (child_cgroup != NULL);
+ TEST_COMPARE_STRING (newcgroup, child_cgroup);
+ _exit (EXIT_SUCCESS);
+ }
+
+ siginfo_t sinfo;
+ TEST_COMPARE (waitid (P_PIDFD, pidfd_args.fork_np_pidfd, &sinfo,
+ WEXITED), 0);
+ TEST_COMPARE (sinfo.si_signo, SIGCHLD);
+ TEST_COMPARE (sinfo.si_code, CLD_EXITED);
+ TEST_COMPARE (sinfo.si_status, 0);
+ }
+
+ free (cgpath);
+ free (newcgroup);
+
+ return 0;
+}
+
+#include <support/test-driver.c>
new file mode 100644
@@ -0,0 +1,236 @@
+/* Basic tests for fork_np.
+ Copyright (C) 2023 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <array_length.h>
+#include <errno.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <support/check.h>
+#include <support/temp_file.h>
+#include <support/xunistd.h>
+#include <support/xsignal.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#define SIG_PID_EXIT_CODE 20
+
+static bool atfork_prepare_var;
+static bool atfork_parent_var;
+static bool atfork_child_var;
+
+static sig_atomic_t sigchld_called;
+
+static void
+sigchld_handler (int sig)
+{
+ sigchld_called = 1;
+}
+
+static void
+atfork_prepare (void)
+{
+ atfork_prepare_var = true;
+}
+
+static void
+atfork_parent (void)
+{
+ atfork_parent_var = true;
+}
+
+static void
+atfork_child (void)
+{
+ atfork_child_var = true;
+}
+
+static int
+singlethread_test (bool async, bool wait_with_pid, bool nosigchld)
+{
+ const char testdata1[] = "abcdefghijklmnopqrtuvwxz";
+ enum { testdatalen1 = array_length (testdata1) };
+ const char testdata2[] = "01234567890";
+ enum { testdatalen2 = array_length (testdata2) };
+
+ pid_t ppid = getpid ();
+
+ int tempfd = create_temp_file ("tst-fork_np", NULL);
+
+ /* Check if the opened file is shared between process by read and write
+ some data on parent and child processes. */
+ xwrite (tempfd, testdata1, testdatalen1);
+ off_t off = xlseek (tempfd, 0, SEEK_CUR);
+ TEST_COMPARE (off, testdatalen1);
+
+ fork_np_args_t fork_args = {
+ .fork_np_flags = FORK_NP_PIDFD
+ | (async ? FORK_NP_ASYNCSAFE : 0)
+ | (nosigchld ? FORK_NP_EXIT_SIGNAL : 0)
+ };
+ pid_t pid = fork_np (&fork_args, sizeof fork_args);
+ TEST_VERIFY_EXIT (pid != -1);
+
+ sigchld_called = 0;
+
+ if (pid == 0)
+ {
+ if (async)
+ TEST_VERIFY (!atfork_child_var);
+ else
+ TEST_VERIFY (atfork_child_var);
+
+ TEST_VERIFY_EXIT (getpid () != ppid);
+ TEST_COMPARE (getppid(), ppid);
+
+ TEST_COMPARE (xlseek (tempfd, 0, SEEK_CUR), testdatalen1);
+
+ xlseek (tempfd, 0, SEEK_SET);
+ char buf[testdatalen1];
+ TEST_COMPARE (read (tempfd, buf, sizeof (buf)), testdatalen1);
+ TEST_COMPARE_BLOB (buf, testdatalen1, testdata1, testdatalen1);
+
+ xlseek (tempfd, 0, SEEK_SET);
+ xwrite (tempfd, testdata2, testdatalen2);
+
+ xclose (tempfd);
+
+ _exit (EXIT_SUCCESS);
+ }
+
+ {
+ siginfo_t sinfo;
+ int options = WEXITED | (nosigchld ? __WCLONE : 0);
+ if (wait_with_pid)
+ TEST_COMPARE (waitid (P_PID, pid, &sinfo, options), 0);
+ else
+ TEST_COMPARE (waitid (P_PIDFD, fork_args.fork_np_pidfd, &sinfo,
+ options), 0);
+ TEST_COMPARE (sinfo.si_signo, SIGCHLD);
+ TEST_COMPARE (sinfo.si_code, CLD_EXITED);
+ TEST_COMPARE (sinfo.si_status, 0);
+
+ /* If nosigchld is specified no SIGCHLD should be sent by the kernel. */
+ TEST_COMPARE (sigchld_called, nosigchld ? 0 : 1);
+ }
+
+ TEST_COMPARE (xlseek (tempfd, 0, SEEK_CUR), testdatalen2);
+
+ xlseek (tempfd, 0, SEEK_SET);
+ char buf[testdatalen2];
+ TEST_COMPARE (read (tempfd, buf, sizeof (buf)), testdatalen2);
+
+ TEST_COMPARE_BLOB (buf, testdatalen2, testdata2, testdatalen2);
+
+ return 0;
+}
+
+static int
+do_test (void)
+{
+ /* Sanity check for pidfd support. */
+ TEST_COMPARE (fork_np (NULL, -1), -1);
+ TEST_COMPARE (errno, EINVAL);
+
+ {
+ fork_np_args_t fork_args = {
+ .fork_np_flags = FORK_NP_PIDFD,
+ };
+ pid_t pid = fork_np (&fork_args, sizeof fork_args);
+ if (pid == -1 && errno == ENOSYS)
+ FAIL_UNSUPPORTED ("kernel does not support CLONE_PIDFD clone flag");
+ TEST_VERIFY_EXIT (pid != -1);
+ if (pid == 0)
+ _exit (EXIT_SUCCESS);
+
+ siginfo_t sinfo;
+ TEST_COMPARE (waitid (P_PID, pid, &sinfo, WEXITED), 0);
+ TEST_COMPARE (sinfo.si_signo, SIGCHLD);
+ TEST_COMPARE (sinfo.si_code, CLD_EXITED);
+ TEST_COMPARE (sinfo.si_status, 0);
+ }
+
+ {
+ struct sigaction sa;
+ sa.sa_handler = sigchld_handler;
+ sa.sa_flags = 0;
+ sigemptyset (&sa.sa_mask);
+ xsigaction (SIGCHLD, &sa, NULL);
+ }
+
+ pthread_atfork (atfork_prepare, atfork_parent, atfork_child);
+
+ /* With default flags, fork_np acts as fork and run the pthread_atfork
+ handlers. */
+ {
+ atfork_prepare_var = atfork_parent_var = atfork_child_var = false;
+ singlethread_test (false, false, false);
+ TEST_VERIFY (atfork_prepare_var);
+ TEST_VERIFY (atfork_parent_var);
+ TEST_VERIFY (!atfork_child_var);
+ }
+
+ /* Same as before, but also wait using the PID instead of pidfd. */
+ {
+ atfork_prepare_var = atfork_parent_var = atfork_child_var = false;
+ singlethread_test (false, true, false);
+ TEST_VERIFY (atfork_prepare_var);
+ TEST_VERIFY (atfork_parent_var);
+ TEST_VERIFY (!atfork_child_var);
+ }
+
+ /* Using pidfd and disable SIGCHLD. */
+ {
+ atfork_prepare_var = atfork_parent_var = atfork_child_var = false;
+ singlethread_test (false, false, true);
+ TEST_VERIFY (atfork_prepare_var);
+ TEST_VERIFY (atfork_parent_var);
+ TEST_VERIFY (!atfork_child_var);
+ }
+
+ /* With FORK_NP_ASYNCSAFE, fork_np acts as _Fork. */
+ {
+ atfork_prepare_var = atfork_parent_var = atfork_child_var = false;
+ pthread_atfork (atfork_prepare, atfork_parent, atfork_child);
+ singlethread_test (true, false, false);
+ TEST_VERIFY (!atfork_prepare_var);
+ TEST_VERIFY (!atfork_parent_var);
+ TEST_VERIFY (!atfork_child_var);
+ }
+
+ {
+ atfork_prepare_var = atfork_parent_var = atfork_child_var = false;
+ pthread_atfork (atfork_prepare, atfork_parent, atfork_child);
+ singlethread_test (true, true, false);
+ TEST_VERIFY (!atfork_prepare_var);
+ TEST_VERIFY (!atfork_parent_var);
+ TEST_VERIFY (!atfork_child_var);
+ }
+
+ {
+ atfork_prepare_var = atfork_parent_var = atfork_child_var = false;
+ singlethread_test (true, true, true);
+ TEST_VERIFY (!atfork_prepare_var);
+ TEST_VERIFY (!atfork_parent_var);
+ TEST_VERIFY (!atfork_child_var);
+ }
+
+ return 0;
+}
+
+#include <support/test-driver.c>
@@ -2582,6 +2582,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F
@@ -2688,6 +2688,7 @@ GLIBC_2.38 strlcat F
GLIBC_2.38 strlcpy F
GLIBC_2.38 wcslcat F
GLIBC_2.38 wcslcpy F
+GLIBC_2.39 fork_np F
GLIBC_2.39 pidfd_spawn F
GLIBC_2.39 pidfd_spawnp F
GLIBC_2.39 posix_spawnattr_getcgroup_np F