]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
Add new setpgid utility
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Sun, 20 Aug 2023 09:42:51 +0000 (11:42 +0200)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Thu, 31 Aug 2023 08:13:30 +0000 (10:13 +0200)
This program allows running a command in a new process group and
optionally makes the new process group the foreground process group
of the ctty.

This is useful when running programs through wrappers programs (think
bubblewrap, ...) and wanting to make sure that SIGINT is only sent to
the innermost process. This is possible by putting the innermost process
in a new process group and making that process group the foreground process
group of the controlling terminal.

By adding a separate utility to util-linux, we can apply this to any
program even if the program itself doesn't implement this functionality.

33 files changed:
bash-completion/Makemodule.am
bash-completion/setpgid [new file with mode: 0644]
configure.ac
meson.build
sys-utils/Makemodule.am
sys-utils/meson.build
sys-utils/setpgid.1.adoc [new file with mode: 0644]
sys-utils/setpgid.c [new file with mode: 0644]
tests/commands.sh
tests/expected/build-sys/config-all
tests/expected/build-sys/config-all-devel
tests/expected/build-sys/config-all-non-nls
tests/expected/build-sys/config-audit
tests/expected/build-sys/config-chfnsh-libuser
tests/expected/build-sys/config-chfnsh-no-password
tests/expected/build-sys/config-chfnsh-pam
tests/expected/build-sys/config-core
tests/expected/build-sys/config-cryptsetup
tests/expected/build-sys/config-devel
tests/expected/build-sys/config-devel-new-mount
tests/expected/build-sys/config-devel-non-asan
tests/expected/build-sys/config-devel-non-docs
tests/expected/build-sys/config-non-libblkid
tests/expected/build-sys/config-non-libmount
tests/expected/build-sys/config-non-libs
tests/expected/build-sys/config-non-libsmartcols
tests/expected/build-sys/config-non-libuuid
tests/expected/build-sys/config-non-nls
tests/expected/build-sys/config-selinux
tests/expected/build-sys/config-slang
tests/expected/build-sys/config-static
tests/expected/misc/setpgid [new file with mode: 0644]
tests/ts/misc/setpgid [new file with mode: 0755]

index 2763895e898ab725695546e82714fe14413bae9e..c836d30b81b392a81893d7fa1d04c31de49c2e5c 100644 (file)
@@ -99,6 +99,9 @@ endif
 if BUILD_SCRIPTLIVE
 dist_bashcompletion_DATA += bash-completion/scriptlive
 endif
+if BUILD_SETPGID
+dist_bashcompletion_DATA += bash-completion/setpgid
+endif
 if BUILD_SETSID
 dist_bashcompletion_DATA += bash-completion/setsid
 endif
diff --git a/bash-completion/setpgid b/bash-completion/setpgid
new file mode 100644 (file)
index 0000000..5ad9be5
--- /dev/null
@@ -0,0 +1,23 @@
+_setpgid_module()
+{
+       local cur prev OPTS
+       COMPREPLY=()
+       cur="${COMP_WORDS[COMP_CWORD]}"
+       prev="${COMP_WORDS[COMP_CWORD-1]}"
+       case $prev in
+               '-h'|'--help'|'-V'|'--version')
+                       return 0
+                       ;;
+       esac
+       case $cur in
+               -*)
+                       OPTS="--foreground --help --version"
+                       COMPREPLY=( $(compgen -W "${OPTS[*]}" -- $cur) )
+                       return 0
+                       ;;
+       esac
+       compopt -o bashdefault
+       COMPREPLY=( $(compgen -c -- $cur) )
+       return 0
+}
+complete -F _setpgid_module setpgid
index 738b369ee81bc2e784e1d5da14f90e1a568a56e0..af58cb20e2b314f2768c425990e2f7827162206d 100644 (file)
@@ -2077,6 +2077,9 @@ UL_REQUIRES_BUILD([rfkill], [libsmartcols])
 AM_CONDITIONAL([BUILD_RFKILL], [test "x$build_rfkill" = xyes])
 
 
+UL_BUILD_INIT([setpgid], [yes])
+AM_CONDITIONAL([BUILD_SETPGID], [test "x$build_setpgid" = xyes])
+
 UL_BUILD_INIT([setsid], [yes])
 AM_CONDITIONAL([BUILD_SETSID], [test "x$build_setsid" = xyes])
 
index 221ae373b6446449bd041fe1dea8b07c4168be2a..cfb76aafff847531da489cf6f05280491fcfbaa4 100644 (file)
@@ -1371,6 +1371,20 @@ if not is_disabler(exe)
   bashcompletions += ['renice']
 endif
 
+exe = executable(
+  'setpgid',
+  setpgid_sources,
+  include_directories: includes,
+  link_with : [lib_common,
+               lib_smartcols],
+  install_dir : usrbin_exec_dir,
+  install : true)
+if opt and not is_disabler(exe)
+  exes += exe
+  manadocs += ['sys-utils/setpgid.1.adoc']
+  bashcompletions += ['setpgid']
+endif
+
 exe = executable(
   'setsid',
   setsid_sources,
index 00f7b51ca311267550f3908a0f077b4c147fb44e..4d2728c191f3ff93983805c30bc1fa01e1328b1c 100644 (file)
@@ -113,6 +113,13 @@ rfkill_LDADD = $(LDADD) libcommon.la libsmartcols.la
 rfkill_CFLAGS = $(AM_CFLAGS) -I$(ul_libsmartcols_incdir)
 endif
 
+if BUILD_SETPGID
+usrbin_exec_PROGRAMS += setpgid
+MANPAGES += sys-utils/setpgid.1
+dist_noinst_DATA += sys-utils/setpgid.1.adoc
+setpgid_SOURCES = sys-utils/setpgid.c
+endif
+
 if BUILD_SETSID
 usrbin_exec_PROGRAMS += setsid
 MANPAGES += sys-utils/setsid.1
index ee00d1d39e3d78061fabf143b3499ae97044402e..0e9857349e640a4672596309b1795706bbe91dbb 100644 (file)
@@ -36,6 +36,10 @@ renice_sources = files(
   'renice.c',
 )
 
+setpgid_sources = files(
+  'setpgid.c',
+)
+
 setsid_sources = files(
   'setsid.c',
 )
diff --git a/sys-utils/setpgid.1.adoc b/sys-utils/setpgid.1.adoc
new file mode 100644 (file)
index 0000000..c0c53bf
--- /dev/null
@@ -0,0 +1,45 @@
+//po4a: entry man manual
+// Daan De Meyer <daan.j.demeyer@gmail.com>
+// In the public domain.
+= setpgid(1)
+:doctype: manpage
+:man manual: User Commands
+:man source: util-linux {release-version}
+:page-layout: base
+:command: setpgid
+
+== NAME
+
+setpgid - run a program in a new process group
+
+== SYNOPSIS
+
+*setpgid* [options] _program_ [_arguments_]
+
+== DESCRIPTION
+
+*setpgid* runs a program in a new process group.
+
+== OPTIONS
+
+*-f*, *--foreground*::
+Make the new process group the foreground process group of the controlling 
+terminal if there is a controlling terminal.
+
+include::man-common/help-version.adoc[]
+
+== AUTHORS
+
+mailto:daan.j.demeyer@gmail.com[Daan De Meyer]
+
+== SEE ALSO
+
+*setpgid*(2)
+
+include::man-common/bugreports.adoc[]
+
+include::man-common/footer.adoc[]
+
+ifdef::translation[]
+include::man-common/translation.adoc[]
+endif::[]
diff --git a/sys-utils/setpgid.c b/sys-utils/setpgid.c
new file mode 100644 (file)
index 0000000..d5806a4
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * setpgid.c -- execute a command in a new process group
+ * Daan De Meyer <daan.j.demeyer@gmail.com>
+ * In the public domain.
+ */
+
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include "closestream.h"
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+       FILE *out = stdout;
+       fputs(USAGE_HEADER, out);
+       fprintf(out, _(
+               " %s [options] <program> [arguments ...]\n"),
+               program_invocation_short_name);
+
+       fputs(USAGE_SEPARATOR, out);
+       fputs(_("Run a program in a new process group.\n"), out);
+
+       fputs(USAGE_OPTIONS, out);
+       fputs(_(" -f, --foregound     Make a foreground process group\n"), out);
+
+       printf(USAGE_HELP_OPTIONS(16));
+
+       printf(USAGE_MAN_TAIL("setpgid(1)"));
+       exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char **argv)
+{
+       int ch, foreground = 0, fd;
+       sigset_t s, old;
+
+       static const struct option longopts[] = {
+               {"foreground", no_argument, NULL, 'f'},
+               {"version", no_argument, NULL, 'V'},
+               {"help", no_argument, NULL, 'h'},
+               {NULL, 0, NULL, 0}
+       };
+
+       setlocale(LC_ALL, "");
+       bindtextdomain(PACKAGE, LOCALEDIR);
+       textdomain(PACKAGE);
+       close_stdout_atexit();
+
+       while ((ch = getopt_long(argc, argv, "+Vh", longopts, NULL)) != -1)
+               switch (ch) {
+               case 'f':
+                       foreground = 1;
+                       break;
+               case 'h':
+                       usage();
+               case 'V':
+                       print_version(EXIT_SUCCESS);
+               default:
+                       errtryhelp(EXIT_FAILURE);
+               }
+
+       if (argc - optind < 1) {
+               warnx(_("no command specified"));
+               errtryhelp(EXIT_FAILURE);
+       }
+
+       if (setpgid(0, 0) < 0)
+               err(EXIT_FAILURE, _("setpgid failed"));
+
+       if (foreground) {
+               fd = open("/dev/tty", O_RDONLY|O_CLOEXEC);
+               if (fd >= 0) {
+                       if (sigemptyset(&s) < 0)
+                               err(EXIT_FAILURE, _("sigemptyset failed"));
+                       if (sigaddset(&s, SIGTTOU) < 0)
+                               err(EXIT_FAILURE, _("sigaddset failed"));
+                       if (sigprocmask(SIG_BLOCK, &s, &old) < 0)
+                               err(EXIT_FAILURE, _("sigprocmask failed"));
+                       if (tcsetpgrp(fd, getpgid(0)) < 0)
+                               err(EXIT_FAILURE, _("tcsetpgrp failed"));
+                       if (sigprocmask(SIG_SETMASK, &old, NULL) < 0)
+                               err(EXIT_FAILURE, _("sigprocmask failed"));
+               }
+       }
+
+       execvp(argv[optind], argv + optind);
+       errexec(argv[optind]);
+}
index 41c0ee98ce8ae1be54ad0c6de4576de78fbbdad4..7dbe45a3e883862b457c38a6e0bfd74c30e16256 100644 (file)
@@ -108,6 +108,7 @@ TS_CMD_SCRIPT=${TS_CMD_SCRIPT-"${ts_commandsdir}script"}
 TS_CMD_SCRIPTREPLAY=${TS_CMD_SCRIPTREPLAY-"${ts_commandsdir}scriptreplay"}
 TS_CMD_SCRIPTLIVE=${TS_CMD_SCRIPTLIVE-"${ts_commandsdir}scriptlive"}
 TS_CMD_SETARCH=${TS_CMD_SETARCH-"${ts_commandsdir}setarch"}
+TS_CMD_SETPGID=${TS_CMD_SETPGID-"${ts_commandsdir}setpgid"}
 TS_CMD_SETSID=${TS_CMD_SETSID-"${ts_commandsdir}setsid"}
 TS_CMD_SWAPLABEL=${TS_CMD_SWAPLABEL:-"${ts_commandsdir}swaplabel"}
 TS_CMD_SWAPOFF=${TS_CMD_SWAPOFF:-"${ts_commandsdir}swapoff"}
index 0574f23d1dd97a703bb3b20d2a50dd6cbc847546..87e5f624f6d4d0db4fa8c51a91c4ba1a8fea7093 100644 (file)
@@ -110,6 +110,7 @@ scriptlive:
 scriptreplay:  
 setarch:  
 setpriv:  libcap-ng 
+setpgid:  
 setsid:  
 setterm:  libtinfo 
 su:  libpam libpam_misc 
index 3e9a72072ddd3302d8078740bd8f6ef849a040b6..da9afc1bef2d9db2257008d78546b3dd4e007bbf 100644 (file)
@@ -89,6 +89,7 @@ readprofile:
 renice:  
 rtcwake:  
 setarch:  
+setpgid:  
 setsid:  
 switch_root:  
 tunelp:  
index 0574f23d1dd97a703bb3b20d2a50dd6cbc847546..87e5f624f6d4d0db4fa8c51a91c4ba1a8fea7093 100644 (file)
@@ -110,6 +110,7 @@ scriptlive:
 scriptreplay:  
 setarch:  
 setpriv:  libcap-ng 
+setpgid:  
 setsid:  
 setterm:  libtinfo 
 su:  libpam libpam_misc 
index bdcd0872dfee4a285fb4a3efee812ff9515f3628..6dab91fb0187e5c445a78ed03abaa202e7354d28 100644 (file)
@@ -106,6 +106,7 @@ scriptlive:
 scriptreplay:  
 setarch:  
 setpriv:  libcap-ng 
+setpgid:  
 setsid:  
 setterm:  libtinfo 
 su:  libpam libpam_misc 
index 97e1ae605603fe4c3444c7f99984c5d0d48bb28e..61e4e622167ea27205c70cad9d0f0e42e94e16d5 100644 (file)
@@ -106,6 +106,7 @@ scriptlive:
 scriptreplay:  
 setarch:  
 setpriv:  libcap-ng 
+setpgid:  
 setsid:  
 setterm:  libtinfo 
 su:  libpam libpam_misc 
index 734113e3aa2aaf46bdafc59b94c621dd3f6cf4b1..441e6c56b65f03b41596d8b25cfac99ad9a0f90a 100644 (file)
@@ -106,6 +106,7 @@ scriptlive:
 scriptreplay:  
 setarch:  
 setpriv:  libcap-ng 
+setpgid:  
 setsid:  
 setterm:  libtinfo 
 su:  libpam libpam_misc 
index 532013c993a7cf7e0d88348fddb04c29bb82c3f8..d247f8db295a87d2688544ebdd713faef9d2cf35 100644 (file)
@@ -106,6 +106,7 @@ scriptlive:
 scriptreplay:  
 setarch:  
 setpriv:  libcap-ng 
+setpgid:  
 setsid:  
 setterm:  libtinfo 
 su:  libpam libpam_misc 
index a20517cc26a7625f047d05be96c03d20a0abcf6f..132ee2418befc81778e6b7d9bd76bf04d404ffea 100644 (file)
@@ -106,6 +106,7 @@ scriptlive:
 scriptreplay:  
 setarch:  
 setpriv:  libcap-ng 
+setpgid:  
 setsid:  
 setterm:  libtinfo 
 su:  libpam libpam_misc 
index c4aae454902a13e8ca6584210e62ecf3611872a8..2a16c393f93ec90b69a141c52253efc3a54b6607 100644 (file)
@@ -110,6 +110,7 @@ scriptlive:
 scriptreplay:  
 setarch:  
 setpriv:  libcap-ng 
+setpgid:  
 setsid:  
 setterm:  libtinfo 
 su:  libpam libpam_misc 
index 568241341e25f6f1aab235817f90e14b4c56d875..c4c4b372234ba4202482a4d38f427181cd8e6baf 100644 (file)
@@ -110,6 +110,7 @@ scriptlive:
 scriptreplay:  
 setarch:  
 setpriv:  libcap-ng 
+setpgid:  
 setsid:  
 setterm:  libtinfo 
 su:  libpam libpam_misc 
index 9ac82725767f89e0c3a5116ca8272496d620bf08..8befe4ccc18392945d541746d9511a8060dbd54d 100644 (file)
@@ -94,6 +94,7 @@ readprofile:
 renice:  
 rtcwake:  
 setarch:  
+setpgid:  
 setsid:  
 switch_root:  
 tunelp:  
index 568241341e25f6f1aab235817f90e14b4c56d875..c4c4b372234ba4202482a4d38f427181cd8e6baf 100644 (file)
@@ -110,6 +110,7 @@ scriptlive:
 scriptreplay:  
 setarch:  
 setpriv:  libcap-ng 
+setpgid:  
 setsid:  
 setterm:  libtinfo 
 su:  libpam libpam_misc 
index 568241341e25f6f1aab235817f90e14b4c56d875..c4c4b372234ba4202482a4d38f427181cd8e6baf 100644 (file)
@@ -110,6 +110,7 @@ scriptlive:
 scriptreplay:  
 setarch:  
 setpriv:  libcap-ng 
+setpgid:  
 setsid:  
 setterm:  libtinfo 
 su:  libpam libpam_misc 
index ed38eb5c5604e123ed6891fb1f9e518697da3558..191dd3a0692345b00a55899d0afc07c6945737db 100644 (file)
@@ -83,6 +83,7 @@ scriptlive:
 scriptreplay:  
 setarch:  
 setpriv:  libcap-ng 
+setpgid:  
 setsid:  
 setterm:  libtinfo 
 su:  libpam libpam_misc 
index 45ff7117598f299f083569beaf589f355e58fc1c..23931e34679f635823acb450b76a84565d89e383 100644 (file)
@@ -92,6 +92,7 @@ scriptlive:
 scriptreplay:  
 setarch:  
 setpriv:  libcap-ng 
+setpgid:  
 setsid:  
 setterm:  libtinfo 
 su:  libpam libpam_misc 
index b88a6a1833dc120a319691902879a32d7f3e7869..17556267e7565f72dbeb016b12ada5f223ce2e57 100644 (file)
@@ -60,6 +60,7 @@ scriptlive:
 scriptreplay:  
 setarch:  
 setpriv:  libcap-ng 
+setpgid:  
 setsid:  
 setterm:  libtinfo 
 su:  libpam libpam_misc 
index 28ff817b2bf2f7a7c5d5fef37e3d5df8a42ee2c9..f5cb24d7effe5a894bbc78c82271a11d4af7568a 100644 (file)
@@ -76,6 +76,7 @@ scriptlive:
 scriptreplay:  
 setarch:  
 setpriv:  libcap-ng 
+setpgid:  
 setsid:  
 setterm:  libtinfo 
 su:  libpam libpam_misc 
index 3e5a295e4c2c54f4c366db128eca8260c7886b4b..37626d88eea9d19c0c2b48f26a418d01ec334363 100644 (file)
@@ -98,6 +98,7 @@ scriptlive:
 scriptreplay:  
 setarch:  
 setpriv:  libcap-ng 
+setpgid:  
 setsid:  
 setterm:  libtinfo 
 su:  libpam libpam_misc 
index a20517cc26a7625f047d05be96c03d20a0abcf6f..132ee2418befc81778e6b7d9bd76bf04d404ffea 100644 (file)
@@ -106,6 +106,7 @@ scriptlive:
 scriptreplay:  
 setarch:  
 setpriv:  libcap-ng 
+setpgid:  
 setsid:  
 setterm:  libtinfo 
 su:  libpam libpam_misc 
index 4d2e5f84b40cf8cde7e16045bdd77791e7833c6b..c3950b24018874f9e695c5306cd4b482af49006f 100644 (file)
@@ -106,6 +106,7 @@ scriptlive:
 scriptreplay:  
 setarch:  
 setpriv:  libcap-ng 
+setpgid:  
 setsid:  
 setterm:  libtinfo 
 su:  libpam libpam_misc 
index 5a3ac996c11916ac818f6be212cc18606c50e7e2..735f119b9021d3032a07e0d5b434701721475093 100644 (file)
@@ -106,6 +106,7 @@ scriptlive:
 scriptreplay:  
 setarch:  
 setpriv:  libcap-ng 
+setpgid:  
 setsid:  
 setterm:  libtinfo 
 su:  libpam libpam_misc 
index 92de9d78f5c5b7b8d25881c216a194a344d95167..5ba0433b0582ac8fedd039a4912bb4fb22d812c3 100644 (file)
@@ -111,6 +111,7 @@ scriptlive:
 scriptreplay:  
 setarch:  
 setpriv:  libcap-ng 
+setpgid:  
 setsid:  
 setterm:  libtinfo 
 sfdisk.static: STATIC
diff --git a/tests/expected/misc/setpgid b/tests/expected/misc/setpgid
new file mode 100644 (file)
index 0000000..3e4e055
--- /dev/null
@@ -0,0 +1,2 @@
+success
+not equal
diff --git a/tests/ts/misc/setpgid b/tests/ts/misc/setpgid
new file mode 100755 (executable)
index 0000000..40c4754
--- /dev/null
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+# This file is part of util-linux.
+#
+# This file is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This file 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 General Public License for more details.
+
+TS_TOPDIR="${0%/*}/../.."
+TS_DESC="setpgid"
+
+. "$TS_TOPDIR"/functions.sh
+ts_init "$*"
+
+ts_check_test_command "$TS_CMD_SETPGID"
+
+$TS_CMD_SETPGID echo "success" >> $TS_OUTPUT 2>> $TS_ERRLOG
+
+# qemu-user always reports '0' for the pgid field which prevents the test from
+# working so we skip it.
+ts_skip_qemu_user
+
+PGID1="$(awk '{print $5}' /proc/self/stat)"
+PGID2="$($TS_CMD_SETPGID awk '{print $5}' /proc/self/stat)"
+test ! "$PGID1" = "$PGID2" && echo "not equal" >> $TS_OUTPUT
+
+ts_finalize