-
Simon McVittie authored
Initial support for custom graphics provider path and Flatpak environments See merge request steam/pressure-vessel!60
Simon McVittie authoredInitial support for custom graphics provider path and Flatpak environments See merge request steam/pressure-vessel!60
bwrap.c 15.02 KiB
/*
* Copyright © 2017-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 <ftw.h>
#include <gio/gio.h>
#include "bwrap.h"
#include "resolve-in-sysroot.h"
/**
* pv_bwrap_run_sync:
* @bwrap: A #FlatpakBwrap on which flatpak_bwrap_finish() has been called
* @exit_status_out: (out) (optional): Used to return the exit status,
* or -1 if it could not be launched or was killed by a signal
* @error: Used to raise an error on failure
*
* Try to run a command. Its standard output and standard error go to
* pressure-vessel's own stdout and stderr.
*
* Returns: %TRUE if the subprocess runs to completion
*/
gboolean
pv_bwrap_run_sync (FlatpakBwrap *bwrap,
int *exit_status_out,
GError **error)
{
gint exit_status;
g_autofree gchar *output = NULL;
g_autofree gchar *errors = NULL;
guint i;
g_autoptr(GString) command = g_string_new ("");
GArray *child_setup_data = NULL;
g_return_val_if_fail (bwrap != NULL, FALSE);
g_return_val_if_fail (bwrap->argv->len >= 2, FALSE);
g_return_val_if_fail (pv_bwrap_was_finished (bwrap), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
if (exit_status_out != NULL)
*exit_status_out = -1;
for (i = 0; i < bwrap->argv->len; i++)
{
g_autofree gchar *quoted = NULL;
char *unquoted = g_ptr_array_index (bwrap->argv, i);
if (unquoted == NULL)
break;
quoted = g_shell_quote (unquoted);
g_string_append_printf (command, " %s", quoted);
}
if (bwrap->fds != NULL && bwrap->fds->len > 0)
child_setup_data = bwrap->fds;
g_debug ("run:%s", command->str);
/* We use LEAVE_DESCRIPTORS_OPEN to work around a deadlock in older GLib,
* see flatpak_close_fds_workaround */
if (!g_spawn_sync (NULL, /* cwd */
(char **) bwrap->argv->pdata,
bwrap->envp,
(G_SPAWN_SEARCH_PATH |
G_SPAWN_LEAVE_DESCRIPTORS_OPEN),
flatpak_bwrap_child_setup_cb,
child_setup_data,
&output,
&errors,
&exit_status,
error))
return FALSE;
g_print ("%s", output);
g_printerr ("%s", errors);
if (exit_status_out != NULL)
{
if (WIFEXITED (exit_status))
*exit_status_out = WEXITSTATUS (exit_status);
}
if (!g_spawn_check_exit_status (exit_status, error))
return FALSE;
return TRUE;
}
/**
* pv_bwrap_execve:
* @bwrap: A #FlatpakBwrap on which flatpak_bwrap_finish() has been called
* @original_stdout: If > 0, `dup2()` this file descriptor onto stdout
* @error: Used to raise an error on failure
*
* Attempt to replace the current process with the given bwrap command.
* If unable to do so, raise an error.
*
* Returns: %FALSE
*/
gboolean
pv_bwrap_execve (FlatpakBwrap *bwrap,
int original_stdout,
GError **error)
{
int saved_errno;
g_return_val_if_fail (bwrap != NULL, FALSE);
g_return_val_if_fail (bwrap->argv->len >= 2, FALSE);
g_return_val_if_fail (pv_bwrap_was_finished (bwrap), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
g_debug ("Replacing self with bwrap...");
if (bwrap->fds != NULL && bwrap->fds->len > 0)
flatpak_bwrap_child_setup_cb (bwrap->fds);
fflush (stdout);
fflush (stderr);
if (original_stdout > 0 &&
dup2 (original_stdout, STDOUT_FILENO) != STDOUT_FILENO)
return glnx_throw_errno_prefix (error,
"Unable to make fd %d a copy of fd %d",
STDOUT_FILENO, original_stdout);
execve (bwrap->argv->pdata[0],
(char * const *) bwrap->argv->pdata,
bwrap->envp);
saved_errno = errno;
/* If we are still here then execve failed */
g_set_error (error,
G_IO_ERROR,
g_io_error_from_errno (saved_errno),
"Error replacing self with bwrap: %s",
g_strerror (saved_errno));
return FALSE;
}
/**
* pv_bwrap_bind_usr:
* @bwrap: The #FlatpakBwrap
* @provider_in_host_namespace: The directory we will
* use to provide the container's `/usr`, `/lib*` etc.,
* in the form of an absolute path that can be resolved
* on the host system
* @provider_in_current_namespace: The same directory, this time
* in the form of an absolute path that can be resolved
* in the namespace where pressure-vessel-wrap is running.
* For example, if we run in a Flatpak container and we intend
* to mount the host system's `/usr`, then
* @provider_in_host_namespace would be `/`
* but @provider_in_current_namespace would be `/run/host`.
* @provider_in_container_namespace: Absolute path of the location
* at which we will mount @provider_in_host_namespace in the
* final container
* @error: Used to raise an error on failure
*
* Append arguments to @bwrap that will bind-mount `/usr` and associated
* directories from @host_path into @mount_point.
*
* If @host_path contains a `usr` directory, it is assumed to be a
* system root. Its `usr` directory is mounted on `${mount_point}/usr`
* in the container. Its `lib*`, `bin` and `sbin` directories are
* created as symbolic links in @mount_point, or mounted on subdirectories
* of @mount_point, as appropriate.
*
* If @host_path does not contain a `usr` directory, it is assumed to be
* a merged `/usr`. It is mounted on `${mount_point}/usr`, and `lib*`,
* `bin` and `sbin` symbolic links are created in @mount_point.
*
* In either case, if @host_path` contains `etc/alternatives` and/or
* `etc/ld.so.cache`, they are mounted on corresponding paths under
* @mount_point.
*
* Returns: %TRUE on success
*/
gboolean
pv_bwrap_bind_usr (FlatpakBwrap *bwrap,
const char *provider_in_host_namespace,
const char *provider_in_current_namespace,
const char *provider_in_container_namespace,
GError **error)
{
g_autofree gchar *usr = NULL;
g_autofree gchar *usr_in_current_namespace = NULL;
g_autofree gchar *dest = NULL;
gboolean host_path_is_usr = FALSE;
g_autoptr(GDir) dir = NULL;
const gchar *member = NULL;
static const char * const bind_etc[] =
{
"alternatives",
"ld.so.cache",
"ld.so.conf",
"ld.so.conf.d",
};
gsize i;
g_return_val_if_fail (bwrap != NULL, FALSE);
g_return_val_if_fail (!pv_bwrap_was_finished (bwrap), FALSE);
g_return_val_if_fail (provider_in_host_namespace != NULL, FALSE);
g_return_val_if_fail (provider_in_host_namespace[0] == '/', FALSE);
g_return_val_if_fail (provider_in_current_namespace != NULL, FALSE);
g_return_val_if_fail (provider_in_current_namespace[0] == '/', FALSE);
g_return_val_if_fail (provider_in_container_namespace != NULL, FALSE);
g_return_val_if_fail (provider_in_container_namespace[0] == '/', FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
usr = g_build_filename (provider_in_host_namespace, "usr", NULL);
usr_in_current_namespace = g_build_filename (provider_in_current_namespace,
"usr", NULL);
dest = g_build_filename (provider_in_container_namespace, "usr", NULL);
if (g_file_test (usr_in_current_namespace, G_FILE_TEST_IS_DIR))
{
flatpak_bwrap_add_args (bwrap,
"--ro-bind", usr, dest,
NULL);
}
else
{
/* host_path is assumed to be a merged /usr */
host_path_is_usr = TRUE;
flatpak_bwrap_add_args (bwrap,
"--ro-bind", provider_in_host_namespace, dest,
NULL);
}
g_clear_pointer (&dest, g_free);
dir = g_dir_open (provider_in_current_namespace, 0, error);
if (dir == NULL)
return FALSE;
for (member = g_dir_read_name (dir);
member != NULL;
member = g_dir_read_name (dir))
{
if ((g_str_has_prefix (member, "lib")
&& !g_str_equal (member, "libexec"))
|| g_str_equal (member, "bin")
|| g_str_equal (member, "sbin")
|| g_str_equal (member, ".ref"))
{
dest = g_build_filename (provider_in_container_namespace, member, NULL);
if (host_path_is_usr)
{
g_autofree gchar *target = g_build_filename ("usr",
member, NULL);
flatpak_bwrap_add_args (bwrap,
"--symlink", target, dest,
NULL);
}
else
{
g_autofree gchar *path_in_host = g_build_filename (provider_in_host_namespace,
member, NULL);
g_autofree gchar *path_in_current_namespace =
g_build_filename (provider_in_current_namespace, member, NULL);
g_autofree gchar *target = glnx_readlinkat_malloc (-1,
path_in_current_namespace,
NULL, NULL);
if (target != NULL)
{
flatpak_bwrap_add_args (bwrap,
"--symlink", target, dest,
NULL);
}
else
{
flatpak_bwrap_add_args (bwrap,
"--ro-bind", path_in_host, dest,
NULL);
}
}
g_clear_pointer (&dest, g_free);
}
}
for (i = 0; i < G_N_ELEMENTS (bind_etc); i++)
{
g_autofree gchar *path = g_build_filename (provider_in_host_namespace, "etc",
bind_etc[i], NULL);
g_autofree gchar *path_in_current_namespace = g_build_filename (provider_in_current_namespace,
"etc", bind_etc[i], NULL);
if (g_file_test (path_in_current_namespace, G_FILE_TEST_EXISTS))
{
dest = g_build_filename (provider_in_container_namespace, "etc", bind_etc[i], NULL);
flatpak_bwrap_add_args (bwrap,
"--ro-bind", path, dest,
NULL);
g_clear_pointer (&dest, g_free);
}
}
return TRUE;
}
/* nftw() doesn't have a user_data argument so we need to use a global
* variable :-( */
static struct
{
FlatpakBwrap *bwrap;
const char *source;
const char *dest;
} nftw_data;
static int
copy_tree_helper (const char *fpath,
const struct stat *sb,
int typeflag,
struct FTW *ftwbuf)
{
const char *path_in_container;
g_autofree gchar *target = NULL;
gsize prefix_len;
int fd = -1;
GError *error = NULL;
g_return_val_if_fail (g_str_has_prefix (fpath, nftw_data.source), 1);
g_return_val_if_fail (g_str_has_suffix (nftw_data.source, nftw_data.dest), 1);
prefix_len = strlen (nftw_data.source) - strlen (nftw_data.dest);
path_in_container = fpath + prefix_len;
switch (typeflag)
{
case FTW_D:
flatpak_bwrap_add_args (nftw_data.bwrap,
"--dir", path_in_container,
NULL);
break;
case FTW_SL:
target = glnx_readlinkat_malloc (-1, fpath, NULL, NULL);
flatpak_bwrap_add_args (nftw_data.bwrap,
"--symlink", target, path_in_container,
NULL);
break;
case FTW_F:
if (!glnx_openat_rdonly (AT_FDCWD, fpath, FALSE, &fd, &error))
{
g_warning ("Unable to copy file into container: %s",
error->message);
g_clear_error (&error);
}
flatpak_bwrap_add_args_data_fd (nftw_data.bwrap,
"--ro-bind-data", glnx_steal_fd (&fd),
path_in_container);
break;
default:
g_warning ("Don't know how to handle ftw type flag %d at %s",
typeflag, fpath);
}
return 0;
}
/**
* pv_bwrap_copy_tree:
* @bwrap: The #FlatpakBwrap
* @source: A copy of the desired @dest in a temporary directory,
* for example `/tmp/tmp12345678/overrides/lib`. The path must end
* with @dest.
* @dest: The destination path in the container, which must be absolute.
*
* For every file, directory or symbolic link in @source, add a
* corresponding read-only file, directory or symbolic link via the bwrap
* command-line, so that the files, directories and symbolic links in the
* container will persist even after @source has been deleted.
*/
void
pv_bwrap_copy_tree (FlatpakBwrap *bwrap,
const char *source,
const char *dest)
{
g_return_if_fail (nftw_data.bwrap == NULL);
g_return_if_fail (dest[0] == '/');
g_return_if_fail (g_str_has_suffix (source, dest));
nftw_data.bwrap = bwrap;
nftw_data.source = source;
nftw_data.dest = dest;
nftw (source, copy_tree_helper, 100, FTW_PHYS);
nftw_data.bwrap = NULL;
nftw_data.source = NULL;
nftw_data.dest = NULL;
}
/**
* pv_bwrap_add_api_filesystems:
* @bwrap: The #FlatpakBwrap
*
* Make basic API filesystems available.
*/
void
pv_bwrap_add_api_filesystems (FlatpakBwrap *bwrap)
{
flatpak_bwrap_add_args (bwrap,
"--dev-bind", "/dev", "/dev",
"--proc", "/proc",
"--ro-bind", "/sys", "/sys",
NULL);
if (g_file_test ("/dev/pts", G_FILE_TEST_EXISTS))
flatpak_bwrap_add_args (bwrap,
"--dev-bind", "/dev/pts", "/dev/pts",
NULL);
if (g_file_test ("/dev/shm", G_FILE_TEST_EXISTS))
flatpak_bwrap_add_args (bwrap,
"--dev-bind", "/dev/shm", "/dev/shm",
NULL);
}
FlatpakBwrap *
pv_bwrap_copy (FlatpakBwrap *bwrap)
{
FlatpakBwrap *ret;
g_return_val_if_fail (bwrap != NULL, NULL);
g_return_val_if_fail (!pv_bwrap_was_finished (bwrap), NULL);
/* bwrap can't own any fds, because if it did,
* flatpak_bwrap_append_bwrap() would steal them. */
g_return_val_if_fail (bwrap->fds == NULL || bwrap->fds->len == 0, NULL);
ret = flatpak_bwrap_new (flatpak_bwrap_empty_env);
flatpak_bwrap_append_bwrap (ret, bwrap);
return ret;
}