diff --git a/build-relocatable-install.py b/build-relocatable-install.py index 8bd52e776b120efcec360df4fefa9be7fe1a4a5f..9b645a2448f53372e401a9ce50242c92e42a4d82 100755 --- a/build-relocatable-install.py +++ b/build-relocatable-install.py @@ -108,6 +108,7 @@ SCRIPTS = [ ] EXECUTABLES = [ 'pressure-vessel-try-setlocale', + 'pressure-vessel-with-lock', 'pressure-vessel-wrap', ] LIBCAPSULE_TOOLS = [ diff --git a/meson.build b/meson.build index ba209631e207da76ddd8409786bc045d63653f70..217500ef0156641262042d29fb8bee29e7d599e6 100644 --- a/meson.build +++ b/meson.build @@ -164,11 +164,39 @@ add_project_arguments( language : 'c', ) +executable( + 'pressure-vessel-with-lock', + sources : [ + 'src/bwrap-lock.c', + 'src/bwrap-lock.h', + 'src/glib-backports.c', + 'src/glib-backports.h', + 'src/flatpak-utils.c', + 'src/flatpak-utils-private.h', + 'src/utils.c', + 'src/utils.h', + 'src/with-lock.c', + ], + c_args : [ + '-Wno-unused-local-typedefs', + ], + dependencies : [ + dependency('gio-unix-2.0', required : true), + subproject('libglnx').get_variable('libglnx_dep'), + ], + install : true, + install_dir : get_option('bindir'), + build_rpath : '${ORIGIN}/../' + get_option('libdir'), + install_rpath : '${ORIGIN}/../' + get_option('libdir'), +) + executable( 'pressure-vessel-wrap', sources : [ 'src/bwrap.c', 'src/bwrap.h', + 'src/bwrap-lock.c', + 'src/bwrap-lock.h', 'src/glib-backports.c', 'src/glib-backports.h', 'src/flatpak-bwrap.c', diff --git a/src/bwrap-lock.c b/src/bwrap-lock.c new file mode 100644 index 0000000000000000000000000000000000000000..bb82887c93df4666976958408cbc7877bf4ad9c6 --- /dev/null +++ b/src/bwrap-lock.c @@ -0,0 +1,161 @@ +/* + * Copyright © 2019 Collabora Ltd. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This 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. + * + * This 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 this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" +#include "subprojects/libglnx/config.h" + +#include <gio/gio.h> + +#include "bwrap-lock.h" + +/** + * PvBwrapLock: + * + * A read/write lock compatible with the locks taken out by + * `bwrap --lock-file FILENAME` and Flatpak. + */ +struct _PvBwrapLock +{ + int fd; +}; + +/** + * pv_bwrap_lock_new: + * @path: Runtime directory to lock; the actual lock file will be `$path/.ref` + * @flags: Flags affecting how we lock the directory + * @error: Used to raise an error on failure + * + * Take out a lock on a directory. + * + * If %PV_BWRAP_LOCK_FLAGS_WRITE is in @flags, the lock is a write-lock, + * which can be held by at most one process at a time. This is appropriate + * when about to modify or delete the runtime. Otherwise it is a read-lock, + * which excludes writers but does not exclude other readers. This is + * appropriate when running an app or game using the runtime. + * + * If %PV_BWRAP_LOCK_FLAGS_WAIT is not in @flags, raise %G_IO_ERROR_BUSY + * if the lock cannot be obtained immediately. + * + * Returns: (nullable): A lock (release and free with pv_bwrap_lock_free()) + * or %NULL. + */ +PvBwrapLock * +pv_bwrap_lock_new (const gchar *path, + PvBwrapLockFlags flags, + GError **error) +{ + glnx_autofd int fd = -1; + struct flock l = { + .l_type = F_RDLCK, + .l_whence = SEEK_SET, + .l_start = 0, + .l_len = 0 + }; + const char *type_str = "reading"; + int open_flags = O_CLOEXEC | O_NOCTTY; + int cmd; + + g_return_val_if_fail (path != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + if (flags & PV_BWRAP_LOCK_FLAGS_CREATE) + open_flags |= O_RDWR | O_CREAT; + else if (flags & PV_BWRAP_LOCK_FLAGS_WRITE) + open_flags |= O_RDWR; + else + open_flags |= O_RDONLY; + + fd = TEMP_FAILURE_RETRY (openat (AT_FDCWD, path, open_flags, 0644)); + + if (fd < 0) + { + glnx_throw_errno_prefix (error, "openat(%s)", path); + return NULL; + } + + if (flags & PV_BWRAP_LOCK_FLAGS_WAIT) + cmd = F_SETLKW; + else + cmd = F_SETLK; + + if (flags & PV_BWRAP_LOCK_FLAGS_WRITE) + { + l.l_type = F_WRLCK; + type_str = "writing"; + } + + if (TEMP_FAILURE_RETRY (fcntl (fd, cmd, &l)) < 0) + { + int saved_errno = errno; + + if (saved_errno == EACCES || saved_errno == EAGAIN) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_BUSY, + "Unable to lock %s for %s: file is busy", + path, type_str); + return NULL; + } + + glnx_throw_errno_prefix (error, "Unable to lock %s for %s", + path, type_str); + return NULL; + } + + return pv_bwrap_lock_new_take (glnx_steal_fd (&fd)); +} + +/** + * pv_bwrap_lock_new_take: + * @fd: A file descriptor, already locked + * + * Convert a simple file descriptor into a #PvBwrapLock. + * + * Returns: (not nullable): A lock (release and free + * with pv_bwrap_lock_free()) + */ +PvBwrapLock * +pv_bwrap_lock_new_take (int fd) +{ + PvBwrapLock *self = NULL; + + g_return_val_if_fail (fd >= 0, NULL); + + self = g_slice_new0 (PvBwrapLock); + self->fd = glnx_steal_fd (&fd); + return self; +} + +void +pv_bwrap_lock_free (PvBwrapLock *self) +{ + glnx_autofd int fd = -1; + + g_return_if_fail (self != NULL); + + fd = glnx_steal_fd (&self->fd); + g_slice_free (PvBwrapLock, self); + /* fd is closed by glnx_autofd if necessary, and that releases the lock */ +} + +int +pv_bwrap_lock_steal_fd (PvBwrapLock *self) +{ + g_return_val_if_fail (self != NULL, -1); + return glnx_steal_fd (&self->fd); +} diff --git a/src/bwrap-lock.h b/src/bwrap-lock.h new file mode 100644 index 0000000000000000000000000000000000000000..3c85bf78f0d3ae4a26c90ab7031a660a9e02db4a --- /dev/null +++ b/src/bwrap-lock.h @@ -0,0 +1,57 @@ +/* + * Copyright © 2019 Collabora Ltd. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This 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. + * + * This 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 this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <glib.h> +#include <glib-object.h> + +#include "glib-backports.h" +#include "libglnx.h" + +/** + * PvBwrapLockFlags: + * @PV_BWRAP_LOCK_FLAGS_CREATE: If the lock file doesn't exist, create it + * @PV_BWRAP_LOCK_FLAGS_WAIT: If another process holds an incompatible lock, + * wait for it to be released; by default pv_bwrap_lock_new() + * raises %G_IO_ERROR_BUSY immediately + * @PV_BWRAP_LOCK_FLAGS_WRITE: Take a write-lock instead of a read-lock; + * by default pv_bwrap_lock_new() takes a read-lock + * @PV_BWRAP_LOCK_FLAGS_NONE: None of the above + * + * Flags affecting how we take a lock on a runtime directory. + */ +typedef enum +{ + PV_BWRAP_LOCK_FLAGS_CREATE = (1 << 0), + PV_BWRAP_LOCK_FLAGS_WAIT = (1 << 1), + PV_BWRAP_LOCK_FLAGS_WRITE = (1 << 2), + PV_BWRAP_LOCK_FLAGS_NONE = 0 +} PvBwrapLockFlags; + +typedef struct _PvBwrapLock PvBwrapLock; + +PvBwrapLock *pv_bwrap_lock_new (const gchar *path, + PvBwrapLockFlags flags, + GError **error); +PvBwrapLock *pv_bwrap_lock_new_take (int fd); +void pv_bwrap_lock_free (PvBwrapLock *self); +int pv_bwrap_lock_steal_fd (PvBwrapLock *self); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (PvBwrapLock, pv_bwrap_lock_free) diff --git a/src/glib-backports.h b/src/glib-backports.h index 925b30c6e65caf4471739f899ab922c4763837fd..70aba601342eb13e2b49f5215ec4cb1eb9c51ab3 100644 --- a/src/glib-backports.h +++ b/src/glib-backports.h @@ -67,3 +67,7 @@ void my_g_ptr_array_insert (GPtrArray *arr, gint index_, gpointer data); #endif + +#if !GLIB_CHECK_VERSION (2, 42, 0) +#define G_OPTION_FLAG_NONE 0 +#endif diff --git a/src/with-lock.c b/src/with-lock.c new file mode 100644 index 0000000000000000000000000000000000000000..4015086b5e376bab691d3d216a1406ba2b7ad1cb --- /dev/null +++ b/src/with-lock.c @@ -0,0 +1,347 @@ +/* pressure-vessel-with-lock — run a command with a lock held. + * Basically flock(1), but using fcntl locks compatible with those used + * by bubblewrap and Flatpak. + * + * Copyright © 2019 Collabora Ltd. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This program 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. + * + * This 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 this library. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" +#include "subprojects/libglnx/config.h" + +#include <fcntl.h> +#include <locale.h> +#include <sysexits.h> +#include <sys/prctl.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include <glib.h> +#include <glib/gstdio.h> +#include <gio/gio.h> + +#include "glib-backports.h" +#include "libglnx.h" + +#include "bwrap-lock.h" +#include "utils.h" + +#ifndef PR_SET_CHILD_SUBREAPER +#define PR_SET_CHILD_SUBREAPER 36 +#endif + +static GPtrArray *global_locks = NULL; +static gboolean opt_create = FALSE; +static gboolean opt_subreaper = FALSE; +static gboolean opt_verbose = FALSE; +static gboolean opt_version = FALSE; +static gboolean opt_wait = FALSE; +static gboolean opt_write = FALSE; + +static gboolean +opt_fd_cb (const char *name, + const char *value, + gpointer data, + GError **error) +{ + char *endptr; + gint64 i64 = g_ascii_strtoll (value, &endptr, 10); + int fd; + int fd_flags; + + g_return_val_if_fail (global_locks != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + if (i64 < 0 || i64 > G_MAXINT || endptr == value || *endptr != '\0') + { + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Integer out of range or invalid: %s", value); + return FALSE; + } + + fd = (int) i64; + + fd_flags = fcntl (fd, F_GETFD); + + if (fd_flags < 0) + return glnx_throw_errno_prefix (error, "Unable to receive --fd %d", fd); + + if ((fd_flags & FD_CLOEXEC) == 0 + && fcntl (fd, F_SETFD, fd_flags | FD_CLOEXEC) != 0) + return glnx_throw_errno_prefix (error, + "Unable to configure --fd %d for " + "close-on-exec", + fd); + + g_ptr_array_add (global_locks, pv_bwrap_lock_new_take (fd)); + return TRUE; +} + +static gboolean +opt_lock_file_cb (const char *name, + const char *value, + gpointer data, + GError **error) +{ + PvBwrapLock *lock; + PvBwrapLockFlags flags = PV_BWRAP_LOCK_FLAGS_NONE; + + g_return_val_if_fail (global_locks != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + if (opt_create) + flags |= PV_BWRAP_LOCK_FLAGS_CREATE; + + if (opt_write) + flags |= PV_BWRAP_LOCK_FLAGS_WRITE; + + if (opt_wait) + flags |= PV_BWRAP_LOCK_FLAGS_WAIT; + + lock = pv_bwrap_lock_new (value, flags, error); + + if (lock == NULL) + return FALSE; + + g_ptr_array_add (global_locks, lock); + return TRUE; +} + +static GOptionEntry options[] = +{ + { "fd", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, opt_fd_cb, + "Take a file descriptor, already locked if desired, and keep it " + "open. May be repeated.", + NULL }, + + { "create", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_create, + "Create each subsequent lock file if it doesn't exist.", + NULL }, + { "no-create", '\0', + G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &opt_create, + "Don't create subsequent nonexistent lock files [default].", + NULL }, + + { "write", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_write, + "Lock each subsequent lock file for write access.", + NULL }, + { "no-write", '\0', + G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &opt_write, + "Lock each subsequent lock file for read-only access [default].", + NULL }, + + { "wait", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_wait, + "Wait for each subsequent lock file.", + NULL }, + { "no-wait", '\0', + G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &opt_wait, + "Exit unsuccessfully if a lock-file is busy [default].", + NULL }, + + { "lock-file", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, opt_lock_file_cb, + "Open the given file and lock it, affected by options appearing " + "earlier on the command-line. May be repeated.", + NULL }, + + { "subreaper", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_subreaper, + "Do not exit until all child processes have exited.", + NULL }, + { "no-subreaper", '\0', + G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &opt_subreaper, + "Only wait for a direct child process [default].", + NULL }, + + { "verbose", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_verbose, + "Be more verbose.", NULL }, + { "version", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_version, + "Print version number and exit.", NULL }, + { NULL } +}; + +static void +cli_log_func (const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, + gpointer user_data) +{ + g_printerr ("%s: %s\n", (const char *) user_data, message); +} + +int +main (int argc, + char *argv[]) +{ + g_autoptr(GPtrArray) locks = NULL; + g_autoptr(GOptionContext) context = NULL; + g_autoptr(GError) local_error = NULL; + GError **error = &local_error; + int ret = EX_USAGE; + char **command_and_args; + GPid child_pid; + int wait_status = -1; + + setlocale (LC_ALL, ""); + pv_avoid_gvfs (); + + locks = g_ptr_array_new_with_free_func ((GDestroyNotify) pv_bwrap_lock_free); + global_locks = locks; + + g_set_prgname ("pressure-vessel-with-lock"); + + g_log_set_handler (G_LOG_DOMAIN, + G_LOG_LEVEL_WARNING | G_LOG_LEVEL_MESSAGE, + cli_log_func, (void *) g_get_prgname ()); + + context = g_option_context_new ( + "COMMAND [ARG...]\n" + "Run COMMAND [ARG...] with a lock held, a subreaper, or similar.\n"); + + g_option_context_add_main_entries (context, options, NULL); + + if (!g_option_context_parse (context, &argc, &argv, error)) + { + if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_BUSY)) + ret = EX_TEMPFAIL; + else if (local_error->domain == G_OPTION_ERROR) + ret = EX_USAGE; + else + ret = EX_UNAVAILABLE; + + goto out; + } + + if (opt_version) + { + g_print ("%s:\n" + " Package: pressure-vessel\n" + " Version: %s\n", + argv[0], VERSION); + ret = 0; + goto out; + } + + if (opt_verbose) + g_log_set_handler (G_LOG_DOMAIN, + G_LOG_LEVEL_DEBUG | G_LOG_LEVEL_INFO, + cli_log_func, (void *) g_get_prgname ()); + + if (argc >= 2 && strcmp (argv[1], "--") == 0) + { + argv++; + argc--; + } + + if (argc < 2) + { + g_printerr ("%s: Usage: %s [OPTIONS] COMMAND [ARG...]\n", + g_get_prgname (), + g_get_prgname ()); + goto out; + } + + ret = EX_UNAVAILABLE; + + command_and_args = argv + 1; + + if (opt_subreaper + && prctl (PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0) != 0) + { + glnx_throw_errno_prefix (error, + "Unable to manage background processes"); + goto out; + } + + g_debug ("Launching child process..."); + + if (!g_spawn_async (NULL, /* working directory */ + command_and_args, + NULL, /* environment */ + G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, + NULL, NULL, /* child setup + user_data */ + &child_pid, + &local_error)) + { + ret = 127; + goto out; + } + + while (1) + { + pid_t died = wait (&wait_status); + + if (died < 0) + { + if (errno == EINTR) + { + continue; + } + else if (errno == ECHILD) + { + g_debug ("No more child processes, exiting"); + break; + } + else + { + glnx_throw_errno_prefix (error, "wait"); + goto out; + } + } + else if (died == child_pid) + { + if (WIFEXITED (wait_status)) + { + ret = WEXITSTATUS (wait_status); + g_debug ("Command exited with status %d", ret); + } + else if (WIFSIGNALED (wait_status)) + { + ret = 128 + WTERMSIG (wait_status); + g_debug ("Command killed by signal %d", ret - 128); + } + else + { + ret = EX_SOFTWARE; + g_debug ("Command terminated in an unknown way (wait status %d)", + wait_status); + } + } + else + { + g_debug ("Indirect child %lld exited with wait status %d", + (long long) died, wait_status); + g_warn_if_fail (opt_subreaper); + } + } + +out: + global_locks = NULL; + + if (local_error != NULL) + g_warning ("%s", local_error->message); + + return ret; +} diff --git a/src/wrap.c b/src/wrap.c index 4bbf8470a1c62d7c8c3d4a02327cd0ece236526c..4cfcb48239fe878f4cd249f71807a374af6e0093 100644 --- a/src/wrap.c +++ b/src/wrap.c @@ -38,6 +38,7 @@ #include <steam-runtime-tools/steam-runtime-tools.h> #include "bwrap.h" +#include "bwrap-lock.h" #include "flatpak-bwrap-private.h" #include "flatpak-run-private.h" #include "flatpak-utils-private.h" @@ -1809,61 +1810,76 @@ opt_share_home_cb (const gchar *option_name, static GOptionEntry options[] = { - { "env-if-host", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_env_if_host, + { "env-if-host", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING_ARRAY, &opt_env_if_host, "Set VAR=VAL if COMMAND is run with /usr from the host system, " "but not if it is run with /usr from RUNTIME.", "VAR=VAL" }, - { "freedesktop-app-id", 0, 0, G_OPTION_ARG_STRING, &opt_freedesktop_app_id, + { "freedesktop-app-id", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &opt_freedesktop_app_id, "Make --unshare-home use ~/.var/app/ID as home directory, where ID " "is com.example.MyApp or similar. This interoperates with Flatpak. " "[Default: $PRESSURE_VESSEL_FDO_APP_ID if set]", "ID" }, - { "steam-app-id", 0, 0, G_OPTION_ARG_STRING, &opt_steam_app_id, + { "steam-app-id", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &opt_steam_app_id, "Make --unshare-home use ~/.var/app/com.steampowered.AppN " "as home directory. [Default: $SteamAppId]", "N" }, - { "home", 0, 0, G_OPTION_ARG_FILENAME, &opt_home, + { "home", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &opt_home, "Use HOME as home directory. Implies --unshare-home. " "[Default: $PRESSURE_VESSEL_HOME if set]", "HOME" }, - { "host-fallback", 0, 0, G_OPTION_ARG_NONE, &opt_host_fallback, + { "host-fallback", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_host_fallback, "Run COMMAND on the host system if we cannot run it in a container.", NULL }, - { "host-ld-preload", 0, 0, G_OPTION_ARG_CALLBACK, &opt_host_ld_preload_cb, + { "host-ld-preload", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, &opt_host_ld_preload_cb, "Add MODULE from the host system to LD_PRELOAD when executing COMMAND.", "MODULE" }, - { "runtime", 0, 0, G_OPTION_ARG_FILENAME, &opt_runtime, + { "runtime", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &opt_runtime, "Mount the given sysroot or merged /usr in the container, and augment " "it with the host system's graphics stack. The empty string " "means don't use a runtime. [Default: $PRESSURE_VESSEL_RUNTIME or '']", "RUNTIME" }, - { "runtime-base", 0, 0, G_OPTION_ARG_FILENAME, &opt_runtime_base, + { "runtime-base", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &opt_runtime_base, "If a --runtime is a relative path, look for it relative to BASE. " "[Default: $PRESSURE_VESSEL_RUNTIME_BASE or '.']", "BASE" }, - { "share-home", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, opt_share_home_cb, + { "share-home", '\0', + G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, opt_share_home_cb, "Use the real home directory. " "[Default unless $PRESSURE_VESSEL_HOME is set or " "$PRESSURE_VESSEL_SHARE_HOME is 0]", NULL }, - { "unshare-home", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, opt_share_home_cb, + { "unshare-home", '\0', + G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, opt_share_home_cb, "Use an app-specific home directory chosen according to --home, " "--freedesktop-app-id, --steam-app-id or $SteamAppId. " "[Default if $PRESSURE_VESSEL_HOME is set or " "$PRESSURE_VESSEL_SHARE_HOME is 0]", NULL }, - { "shell", 0, 0, G_OPTION_ARG_CALLBACK, opt_shell_cb, + { "shell", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, opt_shell_cb, "--shell=after is equivalent to --shell-after, and so on. " "[Default: $PRESSURE_VESSEL_SHELL or 'none']", "{none|after|fail|instead}" }, - { "shell-after", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, opt_shell_cb, + { "shell-after", '\0', + G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, opt_shell_cb, "Run an interactive shell after COMMAND. Executing \"$@\" in that " "shell will re-run COMMAND [ARGS].", NULL }, - { "shell-fail", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, opt_shell_cb, + { "shell-fail", '\0', + G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, opt_shell_cb, "Run an interactive shell after COMMAND, but only if it fails.", NULL }, - { "shell-instead", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, opt_shell_cb, + { "shell-instead", '\0', + G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, opt_shell_cb, "Run an interactive shell instead of COMMAND. Executing \"$@\" in that " "shell will run COMMAND [ARGS].", NULL }, - { "terminal", 0, 0, G_OPTION_ARG_CALLBACK, opt_terminal_cb, + { "terminal", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, opt_terminal_cb, "none: disable features that would use a terminal; " "auto: equivalent to xterm if a --shell option is used, or none; " "xterm: put game output (and --shell if used) in an xterm; " @@ -1871,13 +1887,17 @@ static GOptionEntry options[] = "controlling tty " "[Default: $PRESSURE_VESSEL_TERMINAL or 'auto']", "{none|auto|xterm|tty}" }, - { "tty", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, opt_terminal_cb, + { "tty", '\0', + G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, opt_terminal_cb, "Equivalent to --terminal=tty", NULL }, - { "xterm", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, opt_terminal_cb, + { "xterm", '\0', + G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, opt_terminal_cb, "Equivalent to --terminal=xterm", NULL }, - { "verbose", 0, 0, G_OPTION_ARG_NONE, &opt_verbose, + { "verbose", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_verbose, "Be more verbose.", NULL }, - { "version", 0, 0, G_OPTION_ARG_NONE, &opt_version, + { "version", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_version, "Print version number and exit.", NULL }, { NULL } }; @@ -1949,6 +1969,7 @@ main (int argc, g_autoptr(GString) adjusted_ld_preload = g_string_new (""); g_autofree gchar *cwd_p = NULL; g_autofree gchar *cwd_l = NULL; + g_autoptr(PvBwrapLock) runtime_lock = NULL; const gchar *home; g_autofree gchar *bwrap_help = NULL; g_autofree gchar *tools_dir = NULL; @@ -2027,7 +2048,10 @@ main (int argc, if (opt_version) { - g_print ("pressure-vessel version %s\n", VERSION); + g_print ("%s:\n" + " Package: pressure-vessel\n" + " Version: %s\n", + argv[0], VERSION); ret = 0; goto out; } @@ -2307,8 +2331,37 @@ main (int argc, if (opt_runtime != NULL && opt_runtime[0] != '\0') { + g_autofree gchar *usr = NULL; + g_autofree gchar *files_ref = NULL; + g_debug ("Configuring runtime..."); + /* Take a lock on the runtime until we're finished with setup, + * to make sure it doesn't get deleted. */ + files_ref = g_build_filename (opt_runtime, ".ref", NULL); + runtime_lock = pv_bwrap_lock_new (files_ref, + PV_BWRAP_LOCK_FLAGS_CREATE, + error); + + /* If the runtime is being deleted, ... don't use it, I suppose? */ + if (runtime_lock == NULL) + goto out; + + usr = g_build_filename (opt_runtime, "usr", NULL); + + /* Tell bwrap to hold a reference to files/.ref until it exits. + * If files is a merged-/usr, it's mounted as /usr (as if for + * Flatpak); otherwise it's mounted at /, which we need to cope + * with too. */ + if (g_file_test (usr, G_FILE_TEST_IS_DIR)) + flatpak_bwrap_add_args (bwrap, + "--lock-file", "/.ref", + NULL); + else + flatpak_bwrap_add_args (bwrap, + "--lock-file", "/usr/.ref", + NULL); + search_path_append (bin_path, "/overrides/bin"); search_path_append (bin_path, g_getenv ("PATH")); flatpak_bwrap_add_args (bwrap, @@ -2513,6 +2566,32 @@ main (int argc, if (!flatpak_bwrap_bundle_args (bwrap, 1, -1, FALSE, error)) goto out; + /* If we are using a runtime, pass the lock fd to the executed process, + * and make it act as a subreaper for the game itself. + * + * If we were using --unshare-pid then we could use bwrap --sync-fd + * and rely on bubblewrap's init process for this, but we currently + * can't do that without breaking gameoverlayrender.so's assumptions. */ + if (runtime_lock != NULL) + { + g_autofree gchar *with_lock = NULL; + g_autofree gchar *fd_str = NULL; + int fd = pv_bwrap_lock_steal_fd (runtime_lock); + + with_lock = g_build_filename (tools_dir, + "pressure-vessel-with-lock", + NULL); + fd_str = g_strdup_printf ("%d", fd); + + flatpak_bwrap_add_fd (bwrap, fd); + flatpak_bwrap_add_args (bwrap, + with_lock, + "--subreaper", + "--fd", fd_str, + "--", + NULL); + } + g_debug ("Adding wrapped command..."); flatpak_bwrap_append_args (bwrap, wrapped_command->argv); diff --git a/tests/relocatable-install.py b/tests/relocatable-install.py index ef1d224c8aeeeaecc03d7129d833ce4f3259eb88..0110424bae3e54b8ea43d4c18f073c3c30a439b3 100755 --- a/tests/relocatable-install.py +++ b/tests/relocatable-install.py @@ -78,6 +78,7 @@ class TapTest: EXES = [ + 'pressure-vessel-with-lock', 'pressure-vessel-wrap', ] WRAPPED = [