[2/2] posix: Add terminal control setting support for posix_spawn

Message ID 20210617175751.1619846-2-adhemerval.zanella@linaro.org
State Superseded
Headers
Series [1/2] posix: Remove spawni.c |

Checks

Context Check Description
dj/TryBot-apply_patch success Patch applied to master at the time it was sent

Commit Message

Adhemerval Zanella Netto June 17, 2021, 5:57 p.m. UTC
  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, 0);
    posix_spawnattr_setpgroup (&attr, 0), 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] https://github.com/ksh93/ksh/issues/79
[2] https://www.qnx.com/developers/docs/7.0.0/index.html#com.qnx.doc.neutrino.lib_ref/topic/p/posix_spawn.html
---
 NEWS                                          |   5 +
 include/unistd.h                              |   5 +-
 posix/Makefile                                |   4 +-
 posix/Versions                                |   2 +
 posix/spawn.h                                 |  16 +-
 posix/spawnattr_setflags.c                    |   3 +-
 posix/spawnattr_tcgetpgrp.c                   |  26 +++
 posix/spawnattr_tcsetpgrp.c                   |  26 +++
 posix/tst-spawn5.c                            | 152 ++++++++++++++++++
 sysdeps/generic/libc.abilist                  |   2 +
 sysdeps/mach/hurd/i386/libc.abilist           |   2 +
 sysdeps/mach/hurd/spawni.c                    |  13 ++
 sysdeps/unix/bsd/tcsetpgrp.c                  |   4 +-
 sysdeps/unix/sysv/linux/aarch64/libc.abilist  |   2 +
 sysdeps/unix/sysv/linux/alpha/libc.abilist    |   2 +
 sysdeps/unix/sysv/linux/arc/libc.abilist      |   2 +
 sysdeps/unix/sysv/linux/arm/be/libc.abilist   |   2 +
 sysdeps/unix/sysv/linux/arm/le/libc.abilist   |   2 +
 sysdeps/unix/sysv/linux/csky/libc.abilist     |   2 +
 sysdeps/unix/sysv/linux/hppa/libc.abilist     |   2 +
 sysdeps/unix/sysv/linux/i386/libc.abilist     |   2 +
 sysdeps/unix/sysv/linux/ia64/libc.abilist     |   2 +
 .../sysv/linux/m68k/coldfire/libc.abilist     |   2 +
 .../unix/sysv/linux/m68k/m680x0/libc.abilist  |   2 +
 .../sysv/linux/microblaze/be/libc.abilist     |   2 +
 .../sysv/linux/microblaze/le/libc.abilist     |   2 +
 .../sysv/linux/mips/mips32/fpu/libc.abilist   |   2 +
 .../sysv/linux/mips/mips32/nofpu/libc.abilist |   2 +
 .../sysv/linux/mips/mips64/n32/libc.abilist   |   2 +
 .../sysv/linux/mips/mips64/n64/libc.abilist   |   2 +
 sysdeps/unix/sysv/linux/nios2/libc.abilist    |   2 +
 .../linux/powerpc/powerpc32/fpu/libc.abilist  |   2 +
 .../powerpc/powerpc32/nofpu/libc.abilist      |   2 +
 .../linux/powerpc/powerpc64/be/libc.abilist   |   2 +
 .../linux/powerpc/powerpc64/le/libc.abilist   |   2 +
 .../unix/sysv/linux/riscv/rv32/libc.abilist   |   2 +
 .../unix/sysv/linux/riscv/rv64/libc.abilist   |   2 +
 .../unix/sysv/linux/s390/s390-32/libc.abilist |   2 +
 .../unix/sysv/linux/s390/s390-64/libc.abilist |   2 +
 sysdeps/unix/sysv/linux/sh/be/libc.abilist    |   2 +
 sysdeps/unix/sysv/linux/sh/le/libc.abilist    |   2 +
 .../sysv/linux/sparc/sparc32/libc.abilist     |   2 +
 .../sysv/linux/sparc/sparc64/libc.abilist     |   2 +
 sysdeps/unix/sysv/linux/spawni.c              |  14 ++
 sysdeps/unix/sysv/linux/syscalls.list         |   2 +-
 .../unix/sysv/linux/x86_64/64/libc.abilist    |   2 +
 .../unix/sysv/linux/x86_64/x32/libc.abilist   |   2 +
 termios/tcsetpgrp.c                           |   5 +-
 48 files changed, 337 insertions(+), 8 deletions(-)
 create mode 100644 posix/spawnattr_tcgetpgrp.c
 create mode 100644 posix/spawnattr_tcsetpgrp.c
 create mode 100644 posix/tst-spawn5.c
  

Comments

Andreas Schwab June 17, 2021, 6:15 p.m. UTC | #1
On Jun 17 2021, Adhemerval Zanella via Libc-alpha wrote:

> @@ -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 section.  */

Missing close paren.  Did you mean session?

Andreas.
  
Godmar Back June 28, 2021, 1:11 a.m. UTC | #2
Thank you for cc'ing me. I am assuming you're asking for feedback on your patch.

A few notes with what I've noticed.

(1)
+/* Returh the associated terminal FD in the attribute structure.  */

Should it be "Return?"

(2)
+  int __tcpgrp;

This field holds a file descriptor, but the name '__tcpgrp' could be
misinterpreted as a "pgrp" - as in "terminal control process group"
If would suggest a different name perhaps, such as '__ctty_fd' or
'__tcfd' or something like that to make it more clear that the field
contains a file descriptor.

(3)
Is passing '0600' necessary if you open with O_RDONLY as in:

+  int fd = xopen (_PATH_TTY, O_RDONLY, 0600);


(4)
I didn't see any tests, and I'm not sure the code would work as it's written.
Specifically, the child process needs to block (mask) SIGTTOU around
the tcsetpgrp() call.
This is because after it calls `setpgid` to place itself into a new
process group, it will no longer be in the terminal's foreground
process group (by definition, all process groups that are not the
foreground process group are "background process groups").
Consequently, attempts to call `tcsetpgrp()` will result in SIGTTOU,
which must be blocked.

See tcgetpgrp(3):

   If tcsetpgrp() is called by a member of a background process group
in its session, and the calling process is not
   blocking or ignoring  SIGTTOU, a SIGTTOU signal is sent to all
members of this background process group.

Try the following test program to see this:

#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>

int
main()
{
    pid_t pgid = 0;
    int ttyfd = open("/dev/tty", O_RDONLY);

    // typical sequence if user types 'a | b' (without the pipes)
    for (int i = 0; i < 2; i++) {
        int child = fork();
        if (child == 0) {
            setpgid(0, pgid);
            // you need this call
            // signal(SIGTTOU, SIG_IGN);
            int rc = tcsetpgrp(ttyfd, getpgrp());
            if (rc != 0) {
                perror("tcsetpgrp");
                abort();
            }
            printf("child process %d group %d successfully has
terminal ownership\n", getpid(), getpgrp());
            exit(0);
        } else {
            setpgid(child, pgid);
        }

        if (pgid == 0)
            pgid = child;

        printf("spawned #%d\n", i+1);
    }

    for (int i = 0; i < 2; i++) {
        int status;
        int child = waitpid(-1, &status, WUNTRACED);
        if (WIFSTOPPED(status)) {
            printf("child %d was stopped with %s\n", child,
strsignal(WSTOPSIG(status)));
        } else
        if (WIFEXITED(status)) {
            printf("child %d exited with %d\n", child, WEXITSTATUS(status));
        }
    }
}

(5) Since you have to block (or ignore) SIGTTOU around the call to
tcsetpgrp, you would have to possibly restore the original signal
state before exec'ing the child.
This may interact with POSIX_SPAWN_SETSIGMASK and/or
POSIX_SPAWN_SETSIGDEF if the caller to posix_spawn() includes SIGTTOU
there. In either case, the correct semantics would have to be
implemented.
  
Adhemerval Zanella Netto June 28, 2021, 8:10 p.m. UTC | #3
On 17/06/2021 15:15, Andreas Schwab wrote:
> On Jun 17 2021, Adhemerval Zanella via Libc-alpha wrote:
> 
>> @@ -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 section.  */
> 
> Missing close paren.  Did you mean session?
> 
> Andreas.
> 

Ack and I did mean session, I fixed both thanks.
  
Godmar Back June 28, 2021, 10:01 p.m. UTC | #4
PS:

+  have benn added, enabling posix_spawn and posix_spawnp to set the

should be "have been"
  
Adhemerval Zanella Netto June 28, 2021, 10:03 p.m. UTC | #5
On 27/06/2021 22:11, Godmar Back wrote:
> Thank you for cc'ing me. I am assuming you're asking for feedback on your patch.

Yes,  if you could review the API from the user perspective it would be really
helpful (and the patch review itself would be even more helpful).

> 
> A few notes with what I've noticed.
> 
> (1)
> +/* Returh the associated terminal FD in the attribute structure.  */
> 
> Should it be "Return?"

It should, I have fixed.

> 
> (2)
> +  int __tcpgrp;
> 
> This field holds a file descriptor, but the name '__tcpgrp' could be
> misinterpreted as a "pgrp" - as in "terminal control process group"
> If would suggest a different name perhaps, such as '__ctty_fd' or
> '__tcfd' or something like that to make it more clear that the field
> contains a file descriptor.

Indeed, I have changed to __ctty_fd.

> 
> (3)
> Is passing '0600' necessary if you open with O_RDONLY as in:
> 
> +  int fd = xopen (_PATH_TTY, O_RDONLY, 0600);

No really, we can use a different value for testing.

> 
> 
> (4)
> I didn't see any tests, and I'm not sure the code would work as it's written.
> Specifically, the child process needs to block (mask) SIGTTOU around
> the tcsetpgrp() call.
> This is because after it calls `setpgid` to place itself into a new
> process group, it will no longer be in the terminal's foreground
> process group (by definition, all process groups that are not the
> foreground process group are "background process groups").
> Consequently, attempts to call `tcsetpgrp()` will result in SIGTTOU,
> which must be blocked.
> 
> See tcgetpgrp(3):
> 
>    If tcsetpgrp() is called by a member of a background process group
> in its session, and the calling process is not
>    blocking or ignoring  SIGTTOU, a SIGTTOU signal is sent to all
> members of this background process group.
> 
> Try the following test program to see this:
> 
> #include <unistd.h>
> #include <fcntl.h>
> #include <signal.h>
> #include <stdlib.h>
> #include <stdio.h>
> #include <string.h>
> #include <sys/wait.h>
> 
> int
> main()
> {
>     pid_t pgid = 0;
>     int ttyfd = open("/dev/tty", O_RDONLY);
> 
>     // typical sequence if user types 'a | b' (without the pipes)
>     for (int i = 0; i < 2; i++) {
>         int child = fork();
>         if (child == 0) {
>             setpgid(0, pgid);
>             // you need this call
>             // signal(SIGTTOU, SIG_IGN);
>             int rc = tcsetpgrp(ttyfd, getpgrp());
>             if (rc != 0) {
>                 perror("tcsetpgrp");
>                 abort();
>             }
>             printf("child process %d group %d successfully has
> terminal ownership\n", getpid(), getpgrp());
>             exit(0);
>         } else {
>             setpgid(child, pgid);
>         }
> 
>         if (pgid == 0)
>             pgid = child;
> 
>         printf("spawned #%d\n", i+1);
>     }
> 
>     for (int i = 0; i < 2; i++) {
>         int status;
>         int child = waitpid(-1, &status, WUNTRACED);
>         if (WIFSTOPPED(status)) {
>             printf("child %d was stopped with %s\n", child,
> strsignal(WSTOPSIG(status)));
>         } else
>         if (WIFEXITED(status)) {
>             printf("child %d exited with %d\n", child, WEXITSTATUS(status));
>         }
>     }
> }

The second subtest on posix/tst-spawn5.c should check for it:

  {
    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);

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

But it contains an issue where the controlling terminal is not correctly
set.  Fixing it:

  {
    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);
  }

It issues the following syscalls:

[pid 3367391] clone(child_stack=0x7f3bec54fff0, flags=CLONE_VM|CLONE_VFORK|SIGCHLD
[...]
[pid 3367392] rt_sigaction(SIGTTOU, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[pid 3367392] rt_sigaction(SIGTTOU, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f3bec590560}, NULL, 8) = 0
[...]
[pid 3367392] setpgid(0, 0)             = 0
[pid 3367392] getpgrp()                 = 3367392
[pid 3367392] ioctl(4, TIOCSPGRP, [3367392]) = 0
[pid 3367392] rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0  
[pid 3367392] execve("/home/azanella/Projects/glibc/build/x86_64-linux-gnu-hp/posix/tst-spawn5", ["/home/azanella/Projects/glibc/bu"..., "--direct", "--restart", "setgrpr"], 0x7ffef7371ca8 /* 61 vars */ <unfinished ...>

So there is no SIGTTOU being generated.  I even tried to add a timeout on
the helper process to check if the problem is the signal being generated
asynchronously, but it is works as intended. 

> 
> (5) Since you have to block (or ignore) SIGTTOU around the call to
> tcsetpgrp, you would have to possibly restore the original signal
> state before exec'ing the child.
> This may interact with POSIX_SPAWN_SETSIGMASK and/or
> POSIX_SPAWN_SETSIGDEF if the caller to posix_spawn() includes SIGTTOU
> there. In either case, the correct semantics would have to be
> implemented.
> 

I need to understand better why I am not seeing this behavior in the
posix_spawn implementation before.
  
Godmar Back June 28, 2021, 10:17 p.m. UTC | #6
On Mon, Jun 28, 2021 at 6:03 PM Adhemerval Zanella
<adhemerval.zanella@linaro.org> wrote:
>
>
> So there is no SIGTTOU being generated.  I even tried to add a timeout on
> the helper process to check if the problem is the signal being generated
> asynchronously, but it is works as intended.
>

I'm not familiar with the libc testing environment. When a process
runs in it, what is its controlling terminal? Is it part of a session?
Note that SIGTTOU is generated only if the process is part of a
session and if the terminal in question has a foreground process
group.

See https://github.com/torvalds/linux/blob/master/drivers/tty/tty_jobctrl.c#L49-L60
  
Andreas Schwab June 29, 2021, 8:12 a.m. UTC | #7
On Jun 28 2021, Godmar Back via Libc-alpha wrote:

> I'm not familiar with the libc testing environment. When a process
> runs in it, what is its controlling terminal? Is it part of a session?

It uses whatever the environment provides.  There is no special setup.

Andreas.
  
Adhemerval Zanella Netto June 29, 2021, 5:03 p.m. UTC | #8
On 28/06/2021 19:17, Godmar Back wrote:
> On Mon, Jun 28, 2021 at 6:03 PM Adhemerval Zanella
> <adhemerval.zanella@linaro.org> wrote:
>>
>>
>> So there is no SIGTTOU being generated.  I even tried to add a timeout on
>> the helper process to check if the problem is the signal being generated
>> asynchronously, but it is works as intended.
>>
> 
> I'm not familiar with the libc testing environment. When a process
> runs in it, what is its controlling terminal? Is it part of a session?
> Note that SIGTTOU is generated only if the process is part of a
> session and if the terminal in question has a foreground process
> group.

OK, I completely forgot that we explicit block *all* signals (including
internals ones) on the helper process to avoid any signal handler (I had 
to debug the kernel to remind it):

sysdeps/unix/sysv/linux/spawni.c

130   /* The child must ensure that no signal handler are enabled because it shared
131      memory with parent, so the signal disposition must be either SIG_DFL or
132      SIG_IGN.  It does by iterating over all signals and although it could
133      possibly be more optimized (by tracking which signal potentially have a
134      signal handler), it might requires system specific solutions (since the
135      sigset_t data type can be very different on different architectures).  */
136   struct sigaction sa;
137   memset (&sa, '\0', sizeof (sa));
138 
139   sigset_t hset;
140   __sigprocmask (SIG_BLOCK, 0, &hset);

SO there is no need to handle SIGTTOU here and we already either set the
expected mask or restore the previous defined one just before execve.
  
Godmar Back June 29, 2021, 7:35 p.m. UTC | #9
On Tue, Jun 29, 2021 at 1:03 PM Adhemerval Zanella <
adhemerval.zanella@linaro.org> wrote:

>
> OK, I completely forgot that we explicit block *all* signals (including
> internals ones) on the helper process to avoid any signal handler (I had
> to debug the kernel to remind it):  (...)
>
SO there is no need to handle SIGTTOU here and we already either set the
> expected mask or restore the previous defined one just before execve.
>

Yeah! So it should work.

One other thought that occurred to me is that the test program could check
that getpgid(getppid()) != getpgrp() to make sure that a new pgrp was
actually created.

I would love to start using this code before this fix hits the next
release. To that end, I'll plan on
carving out the corresponding files from the GNU libc source code and place
them earlier on the link command line so that they'll get picked up by the
linker in lieu of the libc equivalent.
I hope this will work.
  

Patch

diff --git a/NEWS b/NEWS
index 58cf1dab68..c892f0ebfa 100644
--- a/NEWS
+++ b/NEWS
@@ -39,6 +39,11 @@  Major new features:
   supported when LFS (_FILE_OFFSET_BITS=64) is also enabled.  It is only
   enabled for Linux and the full support requires a minimum version of 5.1.
 
+* 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
diff --git a/include/unistd.h b/include/unistd.h
index 8ed8b1ea4b..ecca5b8a6d 100644
--- a/include/unistd.h
+++ b/include/unistd.h
@@ -167,7 +167,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
diff --git a/posix/Makefile b/posix/Makefile
index 8d139e54f6..1942ff9ddb 100644
--- a/posix/Makefile
+++ b/posix/Makefile
@@ -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 \
@@ -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)
@@ -274,6 +275,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)
diff --git a/posix/Versions b/posix/Versions
index 5983144d01..da1b8c50c3 100644
--- a/posix/Versions
+++ b/posix/Versions
@@ -153,6 +153,8 @@  libc {
   }
   GLIBC_2.34 {
     execveat;
+    posix_spawnattr_tcgetpgrp_np;
+    posix_spawnattr_tcsetpgrp_np;
   }
   GLIBC_PRIVATE {
     __libc_fork; __libc_pread; __libc_pwrite;
diff --git a/posix/spawn.h b/posix/spawn.h
index a29da028cc..34f3e9eee1 100644
--- a/posix/spawn.h
+++ b/posix/spawn.h
@@ -34,7 +34,8 @@  typedef struct
   sigset_t __ss;
   struct sched_param __sp;
   int __policy;
-  int __pad[16];
+  int __tcpgrp;
+  int __pad[15];
 } posix_spawnattr_t;
 
 
@@ -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
 
 
@@ -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 section.  */
+extern int posix_spawnattr_tcsetpgrp_np (posix_spawnattr_t *__attr, int fd)
+     __THROW __nonnull ((1));
+
+/* Returh 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 *
diff --git a/posix/spawnattr_setflags.c b/posix/spawnattr_setflags.c
index 2b033a50fc..95f521d04d 100644
--- a/posix/spawnattr_setflags.c
+++ b/posix/spawnattr_setflags.c
@@ -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
diff --git a/posix/spawnattr_tcgetpgrp.c b/posix/spawnattr_tcgetpgrp.c
new file mode 100644
index 0000000000..5823943b41
--- /dev/null
+++ b/posix/spawnattr_tcgetpgrp.c
@@ -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->__tcpgrp;
+  return 0;
+}
diff --git a/posix/spawnattr_tcsetpgrp.c b/posix/spawnattr_tcsetpgrp.c
new file mode 100644
index 0000000000..e729cad701
--- /dev/null
+++ b/posix/spawnattr_tcsetpgrp.c
@@ -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->__tcpgrp = fd;
+  return 0;
+}
diff --git a/posix/tst-spawn5.c b/posix/tst-spawn5.c
new file mode 100644
index 0000000000..4fb4e965b6
--- /dev/null
+++ b/posix/tst-spawn5.c
@@ -0,0 +1,152 @@ 
+/* 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 < 4; 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, 0), pid);
+  TEST_VERIFY (WIFEXITED (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);
+
+    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);
+
+    int fd;
+    TEST_COMPARE (posix_spawnattr_tcgetpgrp_np (&attr, &fd), 0);
+    TEST_COMPARE (tcfd, fd);
+
+    run_subprogram (argc, argv, &attr, 0);
+
+    xclose (tcfd);
+  }
+
+  {
+    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);
+
+    run_subprogram (argc, argv, &attr, 0);
+  }
+
+  {
+    posix_spawnattr_t attr;
+    TEST_COMPARE (posix_spawnattr_init (&attr), 0);
+    TEST_COMPARE (posix_spawnattr_setflags (&attr, POSIX_SPAWN_TCSETPGROUP
+						   | POSIX_SPAWN_SETSID), 0);
+
+    run_subprogram (argc, argv, &attr, ENOTTY);
+  }
+
+  return 0;
+}
+
+#define TEST_FUNCTION_ARGV do_test
+#include <support/test-driver.c>
diff --git a/sysdeps/generic/libc.abilist b/sysdeps/generic/libc.abilist
index e69de29bb2..72f9b9c5b0 100644
--- a/sysdeps/generic/libc.abilist
+++ b/sysdeps/generic/libc.abilist
@@ -0,0 +1,2 @@ 
+GLIBC_2.34 posix_spawnattr_tcgetpgrp_np F
+GLIBC_2.34 posix_spawnattr_tcsetpgrp_np F
diff --git a/sysdeps/mach/hurd/i386/libc.abilist b/sysdeps/mach/hurd/i386/libc.abilist
index f651989962..09ffa89dd6 100644
--- a/sysdeps/mach/hurd/i386/libc.abilist
+++ b/sysdeps/mach/hurd/i386/libc.abilist
@@ -2226,6 +2226,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 timespec_getres F
 GLIBC_2.4 __confstr_chk F
 GLIBC_2.4 __fgets_chk F
diff --git a/sysdeps/mach/hurd/spawni.c b/sysdeps/mach/hurd/spawni.c
index b5c92365f2..2646ad128a 100644
--- a/sysdeps/mach/hurd/spawni.c
+++ b/sysdeps/mach/hurd/spawni.c
@@ -390,6 +390,19 @@  retry:
   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)
     {
diff --git a/sysdeps/unix/bsd/tcsetpgrp.c b/sysdeps/unix/bsd/tcsetpgrp.c
index 98c88db3ae..3930b4f674 100644
--- a/sysdeps/unix/bsd/tcsetpgrp.c
+++ b/sysdeps/unix/bsd/tcsetpgrp.c
@@ -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)
diff --git a/sysdeps/unix/sysv/linux/aarch64/libc.abilist b/sysdeps/unix/sysv/linux/aarch64/libc.abilist
index ad4c83787d..6636cd6f02 100644
--- a/sysdeps/unix/sysv/linux/aarch64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/aarch64/libc.abilist
@@ -2369,6 +2369,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
diff --git a/sysdeps/unix/sysv/linux/alpha/libc.abilist b/sysdeps/unix/sysv/linux/alpha/libc.abilist
index 30dfd0baa1..b40195a3d2 100644
--- a/sysdeps/unix/sysv/linux/alpha/libc.abilist
+++ b/sysdeps/unix/sysv/linux/alpha/libc.abilist
@@ -2462,6 +2462,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
diff --git a/sysdeps/unix/sysv/linux/arc/libc.abilist b/sysdeps/unix/sysv/linux/arc/libc.abilist
index 72ff8c5bdc..ae4fc6131b 100644
--- a/sysdeps/unix/sysv/linux/arc/libc.abilist
+++ b/sysdeps/unix/sysv/linux/arc/libc.abilist
@@ -2128,6 +2128,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
diff --git a/sysdeps/unix/sysv/linux/arm/be/libc.abilist b/sysdeps/unix/sysv/linux/arm/be/libc.abilist
index 4c870e6abb..85d49a4a1a 100644
--- a/sysdeps/unix/sysv/linux/arm/be/libc.abilist
+++ b/sysdeps/unix/sysv/linux/arm/be/libc.abilist
@@ -301,6 +301,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
diff --git a/sysdeps/unix/sysv/linux/arm/le/libc.abilist b/sysdeps/unix/sysv/linux/arm/le/libc.abilist
index 1f0dbc990a..a30b5ec5e1 100644
--- a/sysdeps/unix/sysv/linux/arm/le/libc.abilist
+++ b/sysdeps/unix/sysv/linux/arm/le/libc.abilist
@@ -298,6 +298,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
diff --git a/sysdeps/unix/sysv/linux/csky/libc.abilist b/sysdeps/unix/sysv/linux/csky/libc.abilist
index 9756eaeccf..231e075178 100644
--- a/sysdeps/unix/sysv/linux/csky/libc.abilist
+++ b/sysdeps/unix/sysv/linux/csky/libc.abilist
@@ -2388,6 +2388,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
diff --git a/sysdeps/unix/sysv/linux/hppa/libc.abilist b/sysdeps/unix/sysv/linux/hppa/libc.abilist
index e78cb54597..62676ecd04 100644
--- a/sysdeps/unix/sysv/linux/hppa/libc.abilist
+++ b/sysdeps/unix/sysv/linux/hppa/libc.abilist
@@ -2342,6 +2342,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
diff --git a/sysdeps/unix/sysv/linux/i386/libc.abilist b/sysdeps/unix/sysv/linux/i386/libc.abilist
index d1583a5465..507aca35bf 100644
--- a/sysdeps/unix/sysv/linux/i386/libc.abilist
+++ b/sysdeps/unix/sysv/linux/i386/libc.abilist
@@ -2526,6 +2526,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
diff --git a/sysdeps/unix/sysv/linux/ia64/libc.abilist b/sysdeps/unix/sysv/linux/ia64/libc.abilist
index 4a608b5d29..795b3d6236 100644
--- a/sysdeps/unix/sysv/linux/ia64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/ia64/libc.abilist
@@ -2302,6 +2302,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
diff --git a/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist b/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist
index 72db6c699b..54f7a1e929 100644
--- a/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist
+++ b/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist
@@ -302,6 +302,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
diff --git a/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist b/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist
index bd4166dbb1..4068d844d7 100644
--- a/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist
+++ b/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist
@@ -2469,6 +2469,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
diff --git a/sysdeps/unix/sysv/linux/microblaze/be/libc.abilist b/sysdeps/unix/sysv/linux/microblaze/be/libc.abilist
index 83a6edae67..18bd67e126 100644
--- a/sysdeps/unix/sysv/linux/microblaze/be/libc.abilist
+++ b/sysdeps/unix/sysv/linux/microblaze/be/libc.abilist
@@ -2439,6 +2439,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
diff --git a/sysdeps/unix/sysv/linux/microblaze/le/libc.abilist b/sysdeps/unix/sysv/linux/microblaze/le/libc.abilist
index accdc16e0c..9aa2941429 100644
--- a/sysdeps/unix/sysv/linux/microblaze/le/libc.abilist
+++ b/sysdeps/unix/sysv/linux/microblaze/le/libc.abilist
@@ -2436,6 +2436,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
diff --git a/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist
index 26149e7d3c..c8f84b6d37 100644
--- a/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist
+++ b/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist
@@ -2434,6 +2434,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
diff --git a/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist
index 8f9b7f088d..56d2d2aa2d 100644
--- a/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist
+++ b/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist
@@ -2432,6 +2432,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
diff --git a/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist
index 57500f91ce..b3d7a5e0fe 100644
--- a/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist
@@ -2440,6 +2440,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
diff --git a/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist
index 485a0121dd..4db0cb900c 100644
--- a/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist
@@ -2358,6 +2358,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
diff --git a/sysdeps/unix/sysv/linux/nios2/libc.abilist b/sysdeps/unix/sysv/linux/nios2/libc.abilist
index d07e736980..1ee9db21d9 100644
--- a/sysdeps/unix/sysv/linux/nios2/libc.abilist
+++ b/sysdeps/unix/sysv/linux/nios2/libc.abilist
@@ -2478,6 +2478,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
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist
index 5a87b36192..fc53a61f07 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist
@@ -2496,6 +2496,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
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist
index 413bb1d87a..247b3cf79a 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist
@@ -2529,6 +2529,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
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist
index 3ff4d977f7..81c1dcbb08 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist
@@ -2266,6 +2266,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
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist
index c2c5c605ac..a80c60ed77 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist
@@ -2565,6 +2565,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
diff --git a/sysdeps/unix/sysv/linux/riscv/rv32/libc.abilist b/sysdeps/unix/sysv/linux/riscv/rv32/libc.abilist
index 2abe414bda..3c75c8c69e 100644
--- a/sysdeps/unix/sysv/linux/riscv/rv32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/riscv/rv32/libc.abilist
@@ -2130,6 +2130,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
diff --git a/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist b/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist
index 947fc8072d..7d5faba14a 100644
--- a/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist
@@ -2330,6 +2330,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
diff --git a/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist b/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist
index 1da934084c..b411caab9c 100644
--- a/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist
@@ -2494,6 +2494,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
diff --git a/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist b/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist
index 084b419cf6..5a4c785b6d 100644
--- a/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist
@@ -2303,6 +2303,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
diff --git a/sysdeps/unix/sysv/linux/sh/be/libc.abilist b/sysdeps/unix/sysv/linux/sh/be/libc.abilist
index 240e315a05..e2c5cbb8c1 100644
--- a/sysdeps/unix/sysv/linux/sh/be/libc.abilist
+++ b/sysdeps/unix/sysv/linux/sh/be/libc.abilist
@@ -2349,6 +2349,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
diff --git a/sysdeps/unix/sysv/linux/sh/le/libc.abilist b/sysdeps/unix/sysv/linux/sh/le/libc.abilist
index c8b6e4745e..b927d7a946 100644
--- a/sysdeps/unix/sysv/linux/sh/le/libc.abilist
+++ b/sysdeps/unix/sysv/linux/sh/le/libc.abilist
@@ -2346,6 +2346,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
diff --git a/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist b/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist
index 3ed972b8c9..3b6fbda777 100644
--- a/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist
@@ -2487,6 +2487,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
diff --git a/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist b/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist
index 27b3443394..42f11bf242 100644
--- a/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist
@@ -2323,6 +2323,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
diff --git a/sysdeps/unix/sysv/linux/spawni.c b/sysdeps/unix/sysv/linux/spawni.c
index 501f8fbccd..1fe94f0582 100644
--- a/sysdeps/unix/sysv/linux/spawni.c
+++ b/sysdeps/unix/sysv/linux/spawni.c
@@ -33,6 +33,9 @@ 
 #include <ldsodefs.h>
 #include "spawn_int.h"
 
+_Static_assert (sizeof (posix_spawnattr_t) == 336,
+		"sizeof (posix_spawnattr_t) != 336");
+
 /* The Linux implementation of posix_spawn{p} uses the clone syscall directly
    with CLONE_VM and CLONE_VFORK flags and an allocated stack.  The new stack
    and start function solves most the vfork limitation (possible parent
@@ -184,6 +187,17 @@  __spawni_child (void *arguments)
       && __setpgid (0, attr->__pgrp) != 0)
     goto fail;
 
+  /* Set the controlling terminal.  */
+  if ((attr->__flags & POSIX_SPAWN_TCSETPGROUP) != 0)
+    {
+      /* Check if it is possible to avoid an extra syscall.  */
+      pid_t pgrp = (attr->__flags & POSIX_SPAWN_SETPGROUP) != 0
+		    && attr->__pgrp != 0
+		   ? attr->__pgrp : __getpgrp ();
+      if (__tcsetpgrp (attr->__tcpgrp, pgrp) != 0)
+	goto fail;
+    }
+
   /* Set the effective user and group IDs.  */
   if ((attr->__flags & POSIX_SPAWN_RESETIDS) != 0
       && (local_seteuid (__getuid ()) != 0
diff --git a/sysdeps/unix/sysv/linux/syscalls.list b/sysdeps/unix/sysv/linux/syscalls.list
index 01ec2bfa95..07f9cfbe2d 100644
--- a/sysdeps/unix/sysv/linux/syscalls.list
+++ b/sysdeps/unix/sysv/linux/syscalls.list
@@ -17,7 +17,7 @@  getpid          -       getpid          Ei:     __getpid        getpid
 getegid		-	getegid		Ei:	__getegid	getegid
 geteuid		-	geteuid		Ei:	__geteuid	geteuid
 getpgid		-	getpgid		i:i	__getpgid	getpgid
-getpgrp		-	getpgrp		Ei:	getpgrp
+getpgrp		-	getpgrp		Ei:	__getpgrp	getpgrp
 getppid		-	getppid		Ei:	__getppid	getppid
 getresuid	-	getresuid	i:ppp	getresuid
 getresgid	-	getresgid	i:ppp	getresgid
diff --git a/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist b/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
index 2e5c5957b0..d9b76589d5 100644
--- a/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
@@ -2281,6 +2281,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
diff --git a/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist b/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist
index 0b0d1ac43f..601499f829 100644
--- a/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist
@@ -2384,6 +2384,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
diff --git a/termios/tcsetpgrp.c b/termios/tcsetpgrp.c
index 05630cd04c..9bd94a70bc 100644
--- a/termios/tcsetpgrp.c
+++ b/termios/tcsetpgrp.c
@@ -21,7 +21,7 @@ 
 
 /* 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)
 {
   if (fd < 0)
     {
@@ -32,6 +32,7 @@  tcsetpgrp (int fd, pid_t pgrp_id)
   __set_errno (ENOSYS);
   return -1;
 }
-
+weak_alias (__tcsetpgrp, tcsetpgrp);
+libc_hidden_def (__tcsetpgrp)
 
 stub_warning (tcsetpgrp)