I wrote this patch to have 'split' use posix_spawn. While writing it I realized that the setup for posix_spawnattr_t and posix_spawn_file_actions_t takes many lines to check for errors. My assumption is that these functions will almost never fail. If they do, it is probably because of ENOMEM.
Not checking for errors would be wrong because, for example, not dup2'ing the pipe file descriptor to become standard input would make the program behave incorrectly. Therefore, I'm considering a xposix-spawnattr and xposix-spawn-file-action Gnulib module. But I haven't decided yet. Collin
>From 2af52e88a61c2dfa43656ec2a1f9b29b2db59d12 Mon Sep 17 00:00:00 2001 Message-ID: <2af52e88a61c2dfa43656ec2a1f9b29b2db59d12.1761207533.git.collin.fu...@gmail.com> From: Collin Funk <[email protected]> Date: Thu, 23 Oct 2025 01:11:50 -0700 Subject: [PATCH] split: prefer posix_spawn to fork and execl * NEWS: Mention the change. * bootstrap.conf (gnulib_modules): Add posix_spawn, posix_spawnattr_setsigdefault, posix_spawn_file_actions_addclose, posix_spawn_file_actions_adddup2, and posix_spawn_file_actions_init. * src/split.c: Include spawn.h. (create): Use posix_spawn instead of fork and execl. --- NEWS | 3 ++ bootstrap.conf | 5 +++ src/split.c | 96 +++++++++++++++++++++++++++++++++----------------- 3 files changed, 71 insertions(+), 33 deletions(-) diff --git a/NEWS b/NEWS index a5471f375..4ac636a5f 100644 --- a/NEWS +++ b/NEWS @@ -54,6 +54,9 @@ GNU coreutils NEWS -*- outline -*- - supports a multi-byte --delimiter character - no longer processes input indefinitely in the presence of write errors + 'split' now uses posix_spawn() to invoke the shell command specified by + --filter efficiently. + wc -l now operates 10% faster on hosts that support AVX512 instructions. diff --git a/bootstrap.conf b/bootstrap.conf index 523c4923a..fdab0725f 100644 --- a/bootstrap.conf +++ b/bootstrap.conf @@ -213,9 +213,14 @@ gnulib_modules=" pipe-posix pipe2 posix-shell + posix_spawn posix_spawnattr_destroy posix_spawnattr_init posix_spawnattr_setflags + posix_spawnattr_setsigdefault + posix_spawn_file_actions_addclose + posix_spawn_file_actions_adddup2 + posix_spawn_file_actions_init posix_spawnp posixtm posixver diff --git a/src/split.c b/src/split.c index e58e0db54..bd6d2bb56 100644 --- a/src/split.c +++ b/src/split.c @@ -27,6 +27,7 @@ #include <signal.h> #include <sys/types.h> #include <sys/wait.h> +#include <spawn.h> #include "system.h" #include "alignalloc.h" @@ -499,48 +500,77 @@ create (char const *name) { int fd_pair[2]; pid_t child_pid; - char const *shell_prog = getenv ("SHELL"); - if (shell_prog == nullptr) - shell_prog = "/bin/sh"; if (setenv ("FILE", name, 1) != 0) error (EXIT_FAILURE, errno, _("failed to set FILE environment variable")); if (verbose) fprintf (stdout, _("executing with FILE=%s\n"), quotef (name)); + + posix_spawnattr_t attr; + int result = posix_spawnattr_init (&attr); + if (result != 0) + error (EXIT_FAILURE, result, _("posix_spawnattr_init failed")); + result = posix_spawnattr_setflags (&attr, + (POSIX_SPAWN_USEVFORK + | POSIX_SPAWN_SETSIGDEF)); + if (result != 0) + error (EXIT_FAILURE, result, _("posix_spawnattr_setflags failed")); + + sigset_t set; + sigemptyset (&set); + if (default_SIGPIPE) + sigaddset (&set, SIGPIPE); + result = posix_spawnattr_setsigdefault (&attr, &set); + if (result != 0) + error (EXIT_FAILURE, result, _("posix_spawnattr_setsigdefault failed")); + if (pipe (fd_pair) != 0) error (EXIT_FAILURE, errno, _("failed to create pipe")); - child_pid = fork (); - if (child_pid == 0) + + posix_spawn_file_actions_t actions; + result = posix_spawn_file_actions_init (&actions); + if (result != 0) + error (EXIT_FAILURE, result, _("posix_spawn_file_actions_init failed")); + /* We have to close any pipes that were opened during an + earlier call, otherwise this process will be holding a + write-pipe that will prevent the earlier process from + reading an EOF on the corresponding read-pipe. */ + for (int i = 0; i < n_open_pipes; ++i) { - /* This is the child process. If an error occurs here, the - parent will eventually learn about it after doing a wait, - at which time it will emit its own error message. */ - int j; - /* We have to close any pipes that were opened during an - earlier call, otherwise this process will be holding a - write-pipe that will prevent the earlier process from - reading an EOF on the corresponding read-pipe. */ - for (j = 0; j < n_open_pipes; ++j) - if (close (open_pipes[j]) != 0) - error (EXIT_FAILURE, errno, _("closing prior pipe")); - if (close (fd_pair[1])) - error (EXIT_FAILURE, errno, _("closing output pipe")); - if (fd_pair[0] != STDIN_FILENO) - { - if (dup2 (fd_pair[0], STDIN_FILENO) != STDIN_FILENO) - error (EXIT_FAILURE, errno, _("moving input pipe")); - if (close (fd_pair[0]) != 0) - error (EXIT_FAILURE, errno, _("closing input pipe")); - } - if (default_SIGPIPE) - signal (SIGPIPE, SIG_DFL); - execl (shell_prog, last_component (shell_prog), "-c", - filter_command, (char *) nullptr); - error (EXIT_FAILURE, errno, _("failed to run command: \"%s -c %s\""), - shell_prog, filter_command); + result = posix_spawn_file_actions_addclose (&actions, open_pipes[i]); + if (result != 0) + error (EXIT_FAILURE, result, + _("posix_spawn_file_actions_addclose failed")); } - if (child_pid < 0) - error (EXIT_FAILURE, errno, _("fork system call failed")); + result = posix_spawn_file_actions_addclose (&actions, fd_pair[1]); + if (result != 0) + error (EXIT_FAILURE, result, + _("posix_spawn_file_actions_addclose failed")); + if (fd_pair[0] != STDIN_FILENO) + { + result = posix_spawn_file_actions_adddup2 (&actions, fd_pair[0], + STDIN_FILENO); + if (result != 0) + error (EXIT_FAILURE, result, + _("posix_spawn_file_actions_adddup2 failed")); + result = posix_spawn_file_actions_addclose (&actions, fd_pair[0]); + if (result != 0) + error (EXIT_FAILURE, result, + _("posix_spawn_file_actions_addclose failed")); + } + + char const *shell_prog = getenv ("SHELL"); + if (shell_prog == nullptr) + shell_prog = "/bin/sh"; + char const *const argv[] = { last_component (shell_prog), "-c", + filter_command, nullptr }; + + result = posix_spawn (&child_pid, shell_prog, &actions, &attr, + (char * const *) argv, environ); + if (result != 0) + error (EXIT_FAILURE, errno, _("failed to run command: \"%s -c %s\""), + shell_prog, filter_command); + if (close (fd_pair[0]) != 0) error (EXIT_FAILURE, errno, _("failed to close input pipe")); filter_pid = child_pid; -- 2.51.0
