-
Simon McVittie authored
This avoids duplicating it in the command-line tools. The test is still in tests/pressure-vessel/ for now, because it needs testutils.py, which uses its own location to find G_TEST_SRCDIR. Signed-off-by:
Simon McVittie <smcv@collabora.com>
Simon McVittie authoredThis avoids duplicating it in the command-line tools. The test is still in tests/pressure-vessel/ for now, because it needs testutils.py, which uses its own location to find G_TEST_SRCDIR. Signed-off-by:
Simon McVittie <smcv@collabora.com>
adverb.c 10.17 KiB
/*
* adverb — Run a child process with various environmental adjustments.
*
* Copyright © 2020 Collabora Ltd.
*
* SPDX-License-Identifier: MIT
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <sysexits.h>
#include <glib.h>
#include <gio/gio.h>
#include "steam-runtime-tools/glib-backports-internal.h"
#include "steam-runtime-tools/utils-internal.h"
static gboolean opt_print_version = FALSE;
static gboolean opt_ignore_sigchld = FALSE;
static gboolean opt_unignore_sigchld = FALSE;
static gboolean opt_block_sigchld = FALSE;
static gboolean opt_unblock_sigchld = FALSE;
static gboolean opt_show_signal_dispositions = FALSE;
static GOptionEntry options[] =
{
{ "show-signals", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
&opt_show_signal_dispositions,
"Show signal dispositions", NULL },
{ "ignore-sigchld", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
&opt_ignore_sigchld,
"Ignore SIGCHLD with SIG_IGN", NULL },
{ "unignore-sigchld", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
&opt_unignore_sigchld,
"Don't ignore SIGCHLD (restore default disposition SIG_DFL)", NULL },
{ "block-sigchld", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
&opt_block_sigchld,
"Block SIGCHLD with sigprocmask()", NULL },
{ "unblock-sigchld", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
&opt_unblock_sigchld,
"Unblock SIGCHLD with sigprocmask()", NULL },
{ "version", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_print_version,
"Print version number and exit", NULL },
{ NULL }
};
static gboolean
put_back_original_stdout (FILE *original_stdout,
GError **error)
{
int original_stdout_fd = fileno (original_stdout);
if (original_stdout_fd < 0)
return glnx_throw_errno_prefix (error,
"Unable to get fileno of original stdout");
if (dup2 (original_stdout_fd, STDOUT_FILENO) != STDOUT_FILENO)
return glnx_throw_errno_prefix (error,
"Unable to make fd %d a copy of fd %d",
STDOUT_FILENO, original_stdout_fd);
return TRUE;
}
int
main (int argc,
char *argv[])
{
GError *local_error = NULL;
FILE *original_stdout = NULL;
int ret = EX_USAGE;
char **command_and_args;
int saved_errno;
GOptionContext *context;
context = g_option_context_new (
"[COMMAND [ARG...]]\n"
"Run COMMAND [ARG...] with environmental adjustments.\n"
"If no COMMAND is given, just print current status.\n");
g_option_context_add_main_entries (context, options, NULL);
if (!g_option_context_parse (context, &argc, &argv, &local_error))
{
if (local_error->domain == G_OPTION_ERROR)
ret = EX_USAGE;
else
ret = EX_UNAVAILABLE;
goto out;
}
if (opt_print_version)
{
g_print (
"%s:\n"
" Package: steam-runtime-tools\n"
" Version: %s\n",
argv[0], VERSION);
ret = 0;
goto out;
}
original_stdout = _srt_divert_stdout_to_stderr (&local_error);
if (original_stdout == NULL)
{
ret = EX_OSERR;
goto out;
}
if (argc >= 2 && strcmp (argv[1], "--") == 0)
{
argv++;
argc--;
}
if (argc < 2)
command_and_args = NULL;
else
command_and_args = argv + 1;
if (opt_block_sigchld && opt_unblock_sigchld)
{
g_printerr ("%s: Cannot both block and unblock SIGCHLD",
g_get_prgname ());
goto out;
}
if (opt_ignore_sigchld && opt_unignore_sigchld)
{
g_printerr ("%s: Cannot both ignore and unignore SIGCHLD",
g_get_prgname ());
goto out;
}
ret = EX_OSERR;
if (opt_block_sigchld || opt_unblock_sigchld)
{
int sig;
int how;
sigset_t old_set;
sigset_t new_set;
sigemptyset (&old_set);
sigemptyset (&new_set);
sigaddset (&new_set, SIGCHLD);
if (opt_block_sigchld)
{
how = SIG_BLOCK;
g_message ("Blocking SIGCHLD");
}
else
{
how = SIG_UNBLOCK;
g_message ("Unblocking SIGCHLD");
}
if (sigprocmask (how, NULL, &old_set) != 0)
{
saved_errno = errno;
g_set_error (&local_error, G_IO_ERROR,
g_io_error_from_errno (saved_errno),
"get sigprocmask: %s", g_strerror (saved_errno));
goto out;
}
for (sig = 1; sig < 64; sig++)
{
/* sigismember returns -1 for non-signals, which we ignore */
if (sigismember (&old_set, sig) == 1)
g_message ("Before: signal %d (%s) blocked", sig, g_strsignal (sig));
}
if (sigprocmask (how, &new_set, &old_set) != 0)
{
saved_errno = errno;
g_set_error (&local_error, G_IO_ERROR,
g_io_error_from_errno (saved_errno),
"set sigprocmask: %s", g_strerror (saved_errno));
goto out;
}
if (sigprocmask (how, NULL, &new_set) != 0)
{
saved_errno = errno;
g_set_error (&local_error, G_IO_ERROR,
g_io_error_from_errno (saved_errno),
"get sigprocmask: %s", g_strerror (saved_errno));
goto out;
}
for (sig = 1; sig < 64; sig++)
{
if (sigismember (&new_set, sig) == 1)
g_message ("After: signal %d (%s) blocked", sig, g_strsignal (sig));
}
}
if (opt_ignore_sigchld || opt_unignore_sigchld)
{
struct sigaction action = { .sa_handler = SIG_DFL };
struct sigaction old_action = { .sa_handler = SIG_DFL };
if (opt_ignore_sigchld)
{
g_message ("Ignoring SIGCHLD");
action.sa_handler = SIG_IGN;
}
else
{
g_message ("Unignoring SIGCHLD");
action.sa_handler = SIG_DFL;
}
if (sigaction (SIGCHLD, &action, &old_action) != 0)
{
saved_errno = errno;
g_set_error (&local_error, G_IO_ERROR,
g_io_error_from_errno (saved_errno),
"sigaction: %s", g_strerror (saved_errno));
goto out;
}
g_message ("SIG_DFL: %p", (void *) SIG_DFL);
g_message ("SIG_IGN: %p", (void *) SIG_IGN);
g_message ("Old signal handler: %p", old_action.sa_handler);
g_message ("Old flags: 0x%x", old_action.sa_flags);
g_message ("New signal handler: %p", action.sa_handler);
g_message ("New flags: 0x%x", action.sa_flags);
}
if (opt_show_signal_dispositions)
{
int sig;
sigset_t set;
sigemptyset (&set);
if (sigprocmask (SIG_UNBLOCK, NULL, &set) != 0)
{
saved_errno = errno;
g_set_error (&local_error, G_IO_ERROR,
g_io_error_from_errno (saved_errno),
"get sigprocmask: %s", g_strerror (saved_errno));
goto out;
}
for (sig = 1; sig < 64; sig++)
{
if (sigismember (&set, sig) == 1)
g_message ("Signal %s (%d) is blocked", g_strsignal (sig), sig);
}
sigfillset (&set);
for (sig = 1; sig < 64; sig++)
{
if (sigismember (&set, sig) == 1)
{
int masked;
struct sigaction action = { .sa_handler = SIG_DFL };
if (sigaction (sig, NULL, &action) != 0)
{
saved_errno = errno;
g_set_error (&local_error, G_IO_ERROR,
g_io_error_from_errno (saved_errno),
"get sigaction: %s", g_strerror (saved_errno));
goto out;
}
if (action.sa_handler != SIG_DFL || action.sa_flags != 0)
{
g_message ("Signal %s (%d) handler: %p",
g_strsignal (sig), sig,
action.sa_handler);
g_message ("Signal %s (%d) flags: 0x%x",
g_strsignal (sig), sig,
action.sa_flags);
}
for (masked = 1; masked < 64; masked++)
{
if (sigismember (&action.sa_mask, masked) == 1)
{
g_message ("Signal %s (%d) blocks signal %s (%d)",
g_strsignal (sig), sig,
g_strsignal (masked), masked);
}
}
}
}
}
if (!put_back_original_stdout (original_stdout, &local_error))
goto out;
if (command_and_args == NULL)
{
ret = EX_OK;
}
else
{
/* Doesn't return if successful */
execvp (command_and_args[0], command_and_args);
saved_errno = errno;
g_set_error (&local_error, G_IO_ERROR,
g_io_error_from_errno (saved_errno),
"execvp %s: %s",
command_and_args[0], g_strerror (saved_errno));
}
out:
g_option_context_free (context);
if (local_error != NULL)
g_warning ("%s", local_error->message);
g_clear_error (&local_error);
return ret;
}