Skip to content

Commit

Permalink
posix: Add terminal control setting support for posix_spawn
Browse files Browse the repository at this point in the history
Currently there is no proper way to set the controlling terminal through
posix_spawn() in race free manner [1].  This forces shell implementations
to keep using fork()+exec() when launching background process groups,
even when using posix_spawn() yields better performance.

This patch adds a new GNU extension so the creating process can
configure the creating process terminal group.  This is done with a new
flag, POSIX_SPAWN_TCSETPGROUP, along with two new attribute functions,
posix_spawnattr_tcsetpgrp_np(), and posix_spawnattr_tcgetpgrp_np().
The function sets a new attribute, spawn-tcgroupfd, that references to
the controlling terminal.

The controlling terminal is set after the spawn-pgroup attribute, and
uses the spawn-tcgroupfd along with current creating process group
(so it is composable with POSIX_SPAWN_SETPGROUP).

To create a process and set the controlling terminal, one can use the
following sequence:

    posix_spawnattr_t attr;
    posix_spawnattr_init (&attr);
    posix_spawnattr_setflags (&attr, POSIX_SPAWN_TCSETPGROUP);
    posix_spawnattr_tcsetpgrp_np (&attr, tcfd);

If the idea is also to create a new process groups:

    posix_spawnattr_t attr;
    posix_spawnattr_init (&attr);
    posix_spawnattr_setflags (&attr, POSIX_SPAWN_TCSETPGROUP
				     | POSIX_SPAWN_SETPGROUP);
    posix_spawnattr_tcsetpgrp_np (&attr, tcfd);
    posix_spawnattr_setpgroup (&attr, 0);

The controlling terminal file descriptor is ignored if the new flag is
not set.

This interface is slight different than the one provided by QNX [2],
which it only provides the POSIX_SPAWN_TCSETPGROUP flag.  The QNX
documentation is not on how the controlling terminal is obtained
not how it iteracts with POSIX_SPAWN_SETPGROUP.  Since a glibc
implementation is library based, I moving the controlling terminal
open to the caller should be more straighforward since it mimics the
tcsetpgrp(), and allow less error handling by posix_spawn().

Checked on x86_64-linux-gnu and i686-linux-gnu.

[1] ksh93/ksh#79
[2] https://www.qnx.com/developers/docs/7.0.0/index.html#com.qnx.doc.neutrino.lib_ref/topic/p/posix_spawn.html
  • Loading branch information
zatrazz committed Jun 29, 2021
1 parent 09ef018 commit 4051c3a
Show file tree
Hide file tree
Showing 48 changed files with 352 additions and 8 deletions.
5 changes: 5 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ Major new features:
to call async-signal-safe functions (such as raise or execve). This function
is currently a GNU extension.

* The functions posix_spawnattr_tcsetpgrp_np and posix_spawnattr_tcgetpgrp_np
have benn added, enabling posix_spawn and posix_spawnp to set the
controlling terminal in the new process in a non race manner. These
functions are GNU extensions.

Deprecated and removed features, and other changes affecting compatibility:

* The function pthread_mutex_consistent_np has been deprecated; programs
Expand Down
5 changes: 4 additions & 1 deletion include/unistd.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,10 @@ extern int __ftruncate64 (int __fd, __off64_t __length) attribute_hidden;
extern int __truncate (const char *path, __off_t __length);
extern void *__sbrk (intptr_t __delta);
libc_hidden_proto (__sbrk)

extern int __tcsetpgrp (int fd, __pid_t pgrp);
libc_hidden_proto (__tcsetpgrp)
extern pid_t __getpgrp (void);
libc_hidden_proto (__getpgrp);

/* This variable is set nonzero at startup if the process's effective
IDs differ from its real IDs, or it is otherwise indicated that
Expand Down
4 changes: 3 additions & 1 deletion posix/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ routines := \
spawnattr_getpgroup spawnattr_setpgroup spawn spawnp spawni \
spawnattr_getsigmask spawnattr_getschedpolicy spawnattr_getschedparam \
spawnattr_setsigmask spawnattr_setschedpolicy spawnattr_setschedparam \
spawnattr_tcgetpgrp spawnattr_tcsetpgrp \
posix_madvise \
get_child_max sched_cpucount sched_cpualloc sched_cpufree \
streams-compat \
Expand Down Expand Up @@ -106,7 +107,7 @@ tests := test-errno tstgetopt testfnm runtests runptests \
tst-sysconf-empty-chroot tst-glob_symlinks tst-fexecve \
tst-glob-tilde test-ssize-max tst-spawn4 bug-regex37 \
bug-regex38 tst-regcomp-truncated tst-spawn-chdir \
tst-wordexp-nocmd tst-execveat
tst-wordexp-nocmd tst-execveat tst-spawn5

# Test for the glob symbol version that was replaced in glibc 2.27.
ifeq ($(have-GLIBC_2.26)$(build-shared),yesyes)
Expand Down Expand Up @@ -275,6 +276,7 @@ tst-exec-ARGS = -- $(host-test-program-cmd)
tst-exec-static-ARGS = $(tst-exec-ARGS)
tst-execvpe5-ARGS = -- $(host-test-program-cmd)
tst-spawn-ARGS = -- $(host-test-program-cmd)
tst-spawn5-ARGS = -- $(host-test-program-cmd)
tst-spawn-static-ARGS = $(tst-spawn-ARGS)
tst-dir-ARGS = `pwd` `cd $(common-objdir)/$(subdir); pwd` `cd $(common-objdir); pwd` $(objpfx)tst-dir
tst-chmod-ARGS = $(objdir)
Expand Down
2 changes: 2 additions & 0 deletions posix/Versions
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ libc {
GLIBC_2.34 {
_Fork;
execveat;
posix_spawnattr_tcgetpgrp_np;
posix_spawnattr_tcsetpgrp_np;
}
GLIBC_PRIVATE {
__libc_fork; __libc_pread; __libc_pwrite;
Expand Down
16 changes: 15 additions & 1 deletion posix/spawn.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ typedef struct
sigset_t __ss;
struct sched_param __sp;
int __policy;
int __pad[16];
int __ctty_fd;
int __pad[15];
} posix_spawnattr_t;


Expand All @@ -59,6 +60,7 @@ typedef struct
#ifdef __USE_GNU
# define POSIX_SPAWN_USEVFORK 0x40
# define POSIX_SPAWN_SETSID 0x80
# define POSIX_SPAWN_TCSETPGROUP 0x100
#endif


Expand Down Expand Up @@ -166,6 +168,18 @@ extern int posix_spawnattr_setschedparam (posix_spawnattr_t *__restrict __attr,
__restrict __schedparam)
__THROW __nonnull ((1, 2));

#ifdef __USE_GNU
/* Make the spawned process the foreground process group on the terminal
associated with FD (which must be a controlling terminal, and still be
associated with its session). */
extern int posix_spawnattr_tcsetpgrp_np (posix_spawnattr_t *__attr, int fd)
__THROW __nonnull ((1));

/* Return the associated terminal FD in the attribute structure. */
extern int posix_spawnattr_tcgetpgrp_np (const posix_spawnattr_t *
__restrict __attr, int *fd)
__THROW __nonnull ((1, 2));
#endif

/* Initialize data structure for file attribute for `spawn' call. */
extern int posix_spawn_file_actions_init (posix_spawn_file_actions_t *
Expand Down
3 changes: 2 additions & 1 deletion posix/spawnattr_setflags.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
| POSIX_SPAWN_SETSCHEDPARAM \
| POSIX_SPAWN_SETSCHEDULER \
| POSIX_SPAWN_SETSID \
| POSIX_SPAWN_USEVFORK)
| POSIX_SPAWN_USEVFORK \
| POSIX_SPAWN_TCSETPGROUP)

/* Store flags in the attribute structure. */
int
Expand Down
26 changes: 26 additions & 0 deletions posix/spawnattr_tcgetpgrp.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/* Get the controlling terminal option.
Copyright (C) 2000-2021 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 <spawn.h>

int
posix_spawnattr_tcgetpgrp_np (const posix_spawnattr_t *attr, int *fd)
{
*fd = attr->__ctty_fd;
return 0;
}
26 changes: 26 additions & 0 deletions posix/spawnattr_tcsetpgrp.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/* Set the controlling terminal option.
Copyright (C) 2021 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 <spawn.h>

int
posix_spawnattr_tcsetpgrp_np (posix_spawnattr_t *attr, int fd)
{
attr->__ctty_fd = fd;
return 0;
}
167 changes: 167 additions & 0 deletions posix/tst-spawn5.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/* Check posix_spawn set controlling terminal extension.
Copyright (C) 2021 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 <fcntl.h>
#include <getopt.h>
#include <intprops.h>
#include <paths.h>
#include <spawn.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <support/check.h>
#include <support/xunistd.h>
#include <sys/wait.h>

static int
handle_restart (const char *argv1)
{
bool setpgrp = strcmp (argv1, "setgrpr") == 0;
int fd = xopen (_PATH_TTY, O_RDONLY, 0600);

/* If process group is not changed (POSIX_SPAWN_SETPGROUP), then check
the creating process one, otherwise check against the process group
itself. */
pid_t pgrp;
if (!setpgrp)
TEST_COMPARE (sscanf (argv1, "%d", &pgrp), 1);
else
pgrp = getpgrp ();
TEST_COMPARE (tcgetpgrp (fd), pgrp);

xclose (fd);
return 0;
}

static int restart;
#define CMDLINE_OPTIONS \
{ "restart", no_argument, &restart, 1 },

static void
run_subprogram (int argc, char *argv[], const posix_spawnattr_t *attr,
int exp_err)
{
short int flags;
TEST_COMPARE (posix_spawnattr_getflags (attr, &flags), 0);
bool setpgrp = flags & POSIX_SPAWN_SETPGROUP;

char *spargv[9];
char pgrp[INT_STRLEN_BOUND (pid_t)];

int i = 0;
for (; i < argc - 1; i++)
spargv[i] = argv[i + 1];
spargv[i++] = (char *) "--direct";
spargv[i++] = (char *) "--restart";
if (setpgrp)
spargv[i++] = (char *) "setgrpr";
else
{
snprintf (pgrp, sizeof pgrp, "%d", getpgrp ());
spargv[i++] = pgrp;
}
spargv[i] = NULL;

pid_t pid;
TEST_COMPARE (posix_spawn (&pid, argv[1], NULL, attr, spargv, environ),
exp_err);
if (exp_err != 0)
return;

int status;
TEST_COMPARE (xwaitpid (pid, &status, WUNTRACED), pid);
TEST_VERIFY (WIFEXITED (status));
TEST_VERIFY (!WIFSTOPPED (status));
TEST_VERIFY (!WIFSIGNALED (status));
TEST_COMPARE (WEXITSTATUS (status), 0);
}

static int
do_test (int argc, char *argv[])
{
/* We must have either:
- One our fource parameters left if called initially:
+ path to ld.so optional
+ "--library-path" optional
+ the library path optional
+ the application name
- six parameters left if called through re-execution:
+ --setgrpr optional
*/

if (restart)
return handle_restart (argv[1]);

int tcfd = xopen (_PATH_TTY, O_RDONLY, 0600);

/* Check getters and setters. */
{
posix_spawnattr_t attr;
TEST_COMPARE (posix_spawnattr_init (&attr), 0);
TEST_COMPARE (posix_spawnattr_tcsetpgrp_np (&attr, tcfd), 0);

int fd;
TEST_COMPARE (posix_spawnattr_tcgetpgrp_np (&attr, &fd), 0);
TEST_COMPARE (tcfd, fd);
}

/* Check setting the controlling terminal without changing the group. */
{
posix_spawnattr_t attr;
TEST_COMPARE (posix_spawnattr_init (&attr), 0);
TEST_COMPARE (posix_spawnattr_setflags (&attr, POSIX_SPAWN_TCSETPGROUP),
0);
TEST_COMPARE (posix_spawnattr_tcsetpgrp_np (&attr, tcfd), 0);

run_subprogram (argc, argv, &attr, 0);
}

/* Check setting both the controlling terminal and the create a new process
group. */
{
posix_spawnattr_t attr;
TEST_COMPARE (posix_spawnattr_init (&attr), 0);
TEST_COMPARE (posix_spawnattr_setflags (&attr, POSIX_SPAWN_TCSETPGROUP
| POSIX_SPAWN_SETPGROUP),
0);
TEST_COMPARE (posix_spawnattr_setpgroup (&attr, 0), 0);
TEST_COMPARE (posix_spawnattr_tcsetpgrp_np (&attr, tcfd), 0);

run_subprogram (argc, argv, &attr, 0);
}

/* Trying to set the controlling terminal after a setsid() incurs in a ENOTTY
from tcsetpgrp. */
{
posix_spawnattr_t attr;
TEST_COMPARE (posix_spawnattr_init (&attr), 0);
TEST_COMPARE (posix_spawnattr_setflags (&attr, POSIX_SPAWN_TCSETPGROUP
| POSIX_SPAWN_SETSID), 0);
TEST_COMPARE (posix_spawnattr_tcsetpgrp_np (&attr, tcfd), 0);

run_subprogram (argc, argv, &attr, ENOTTY);
}

xclose (tcfd);

return 0;
}

#define TEST_FUNCTION_ARGV do_test
#include <support/test-driver.c>
2 changes: 2 additions & 0 deletions sysdeps/generic/libc.abilist
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
GLIBC_2.34 posix_spawnattr_tcgetpgrp_np F
GLIBC_2.34 posix_spawnattr_tcsetpgrp_np F
2 changes: 2 additions & 0 deletions sysdeps/mach/hurd/i386/libc.abilist
Original file line number Diff line number Diff line change
Expand Up @@ -2229,6 +2229,8 @@ GLIBC_2.34 dlopen F
GLIBC_2.34 dlsym F
GLIBC_2.34 dlvsym F
GLIBC_2.34 execveat F
GLIBC_2.34 posix_spawnattr_tcgetpgrp_np F
GLIBC_2.34 posix_spawnattr_tcsetpgrp_np F
GLIBC_2.34 shm_open F
GLIBC_2.34 shm_unlink F
GLIBC_2.34 timespec_getres F
Expand Down
13 changes: 13 additions & 0 deletions sysdeps/mach/hurd/spawni.c
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,19 @@ __spawni (pid_t *pid, const char *file,
if (!err && (flags & POSIX_SPAWN_SETPGROUP) != 0)
err = __proc_setpgrp (proc, new_pid, attrp->__pgrp);

/* Set the controlling terminal. */
if (!err && (flags & POSIX_SPAWN_TCSETPGROUP) != 0)
{
pid_t pgrp;
/* Check if it is possible to avoid an extra syscall. */
if ((attrp->__flags & POSIX_SPAWN_SETPGROUP) != 0 && attrp->__pgrp != 0)
pgrp = attrp->__pgrp;
else
err = __proc_getpgrp (proc, new_pid, &pgrp);
if (!err)
err = __tcsetpgrp (attrp->__tcpgrp, pgrp);
}

/* Set the effective user and group IDs. */
if (!err && (flags & POSIX_SPAWN_RESETIDS) != 0)
{
Expand Down
4 changes: 3 additions & 1 deletion sysdeps/unix/bsd/tcsetpgrp.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@

/* Set the foreground process group ID of FD set PGRP_ID. */
int
tcsetpgrp (int fd, pid_t pgrp_id)
__tcsetpgrp (int fd, pid_t pgrp_id)
{
return __ioctl (fd, TIOCSPGRP, &pgrp_id);
}
weak_alias (__tcsetpgrp, tcsetpgrp)
libc_hidden_def (__tcsetpgrp)
2 changes: 2 additions & 0 deletions sysdeps/unix/sysv/linux/aarch64/libc.abilist
Original file line number Diff line number Diff line change
Expand Up @@ -2433,6 +2433,8 @@ GLIBC_2.34 mtx_lock F
GLIBC_2.34 mtx_timedlock F
GLIBC_2.34 mtx_trylock F
GLIBC_2.34 mtx_unlock F
GLIBC_2.34 posix_spawnattr_tcgetpgrp_np F
GLIBC_2.34 posix_spawnattr_tcsetpgrp_np F
GLIBC_2.34 pthread_attr_getaffinity_np F
GLIBC_2.34 pthread_attr_getguardsize F
GLIBC_2.34 pthread_attr_getstack F
Expand Down
2 changes: 2 additions & 0 deletions sysdeps/unix/sysv/linux/alpha/libc.abilist
Original file line number Diff line number Diff line change
Expand Up @@ -2532,6 +2532,8 @@ GLIBC_2.34 mtx_lock F
GLIBC_2.34 mtx_timedlock F
GLIBC_2.34 mtx_trylock F
GLIBC_2.34 mtx_unlock F
GLIBC_2.34 posix_spawnattr_tcgetpgrp_np F
GLIBC_2.34 posix_spawnattr_tcsetpgrp_np F
GLIBC_2.34 pthread_attr_getaffinity_np F
GLIBC_2.34 pthread_attr_getguardsize F
GLIBC_2.34 pthread_attr_getstack F
Expand Down
2 changes: 2 additions & 0 deletions sysdeps/unix/sysv/linux/arc/libc.abilist
Original file line number Diff line number Diff line change
Expand Up @@ -2192,6 +2192,8 @@ GLIBC_2.34 mtx_lock F
GLIBC_2.34 mtx_timedlock F
GLIBC_2.34 mtx_trylock F
GLIBC_2.34 mtx_unlock F
GLIBC_2.34 posix_spawnattr_tcgetpgrp_np F
GLIBC_2.34 posix_spawnattr_tcsetpgrp_np F
GLIBC_2.34 pthread_attr_getaffinity_np F
GLIBC_2.34 pthread_attr_getguardsize F
GLIBC_2.34 pthread_attr_getstack F
Expand Down
Loading

0 comments on commit 4051c3a

Please sign in to comment.