diff --git a/src/glib-backports.c b/src/glib-backports.c index c7bfc1f9c8f460ac343a5e423f4c63a43cdb6984..ce4990ee15acbd95fbe213285a2e28c35370ea39 100644 --- a/src/glib-backports.c +++ b/src/glib-backports.c @@ -5,6 +5,8 @@ * Copyright 1997-2000 GLib team * Copyright 2000 Red Hat, Inc. * Copyright 2013-2019 Collabora Ltd. + * Copyright 2018 Georges Basile Stavracas Neto + * Copyright 2018 Philip Withnall * g_execvpe implementation based on GNU libc execvp: * Copyright 1991, 92, 95, 96, 97, 98, 99 Free Software Foundation, Inc. * @@ -351,3 +353,140 @@ my_g_unix_fd_add_full (int priority, return ret; } #endif + +#if !GLIB_CHECK_VERSION(2, 58, 0) +/** + * g_canonicalize_filename: + * @filename: (type filename): the name of the file + * @relative_to: (type filename) (nullable): the relative directory, or %NULL + * to use the current working directory + * + * Gets the canonical file name from @filename. All triple slashes are turned into + * single slashes, and all `..` and `.`s resolved against @relative_to. + * + * Symlinks are not followed, and the returned path is guaranteed to be absolute. + * + * If @filename is an absolute path, @relative_to is ignored. Otherwise, + * @relative_to will be prepended to @filename to make it absolute. @relative_to + * must be an absolute path, or %NULL. If @relative_to is %NULL, it'll fallback + * to g_get_current_dir(). + * + * This function never fails, and will canonicalize file paths even if they don't + * exist. + * + * No file system I/O is done. + * + * Returns: (type filename) (transfer full): a newly allocated string with the + * canonical file path + * Since: 2.58 + */ +gchar * +my_g_canonicalize_filename (const gchar *filename, + const gchar *relative_to) +{ + gchar *canon, *start, *p, *q; + guint i; + + g_return_val_if_fail (relative_to == NULL || g_path_is_absolute (relative_to), NULL); + + if (!g_path_is_absolute (filename)) + { + gchar *cwd_allocated = NULL; + const gchar *cwd; + + if (relative_to != NULL) + cwd = relative_to; + else + cwd = cwd_allocated = g_get_current_dir (); + + canon = g_build_filename (cwd, filename, NULL); + g_free (cwd_allocated); + } + else + { + canon = g_strdup (filename); + } + + start = (char *)g_path_skip_root (canon); + + if (start == NULL) + { + /* This shouldn't really happen, as g_get_current_dir() should + return an absolute pathname, but bug 573843 shows this is + not always happening */ + g_free (canon); + return g_build_filename (G_DIR_SEPARATOR_S, filename, NULL); + } + + /* POSIX allows double slashes at the start to + * mean something special (as does windows too). + * So, "//" != "/", but more than two slashes + * is treated as "/". + */ + i = 0; + for (p = start - 1; + (p >= canon) && + G_IS_DIR_SEPARATOR (*p); + p--) + i++; + if (i > 2) + { + i -= 1; + start -= i; + memmove (start, start+i, strlen (start+i) + 1); + } + + /* Make sure we're using the canonical dir separator */ + p++; + while (p < start && G_IS_DIR_SEPARATOR (*p)) + *p++ = G_DIR_SEPARATOR; + + p = start; + while (*p != 0) + { + if (p[0] == '.' && (p[1] == 0 || G_IS_DIR_SEPARATOR (p[1]))) + { + memmove (p, p+1, strlen (p+1)+1); + } + else if (p[0] == '.' && p[1] == '.' && (p[2] == 0 || G_IS_DIR_SEPARATOR (p[2]))) + { + q = p + 2; + /* Skip previous separator */ + p = p - 2; + if (p < start) + p = start; + while (p > start && !G_IS_DIR_SEPARATOR (*p)) + p--; + if (G_IS_DIR_SEPARATOR (*p)) + *p++ = G_DIR_SEPARATOR; + memmove (p, q, strlen (q)+1); + } + else + { + /* Skip until next separator */ + while (*p != 0 && !G_IS_DIR_SEPARATOR (*p)) + p++; + + if (*p != 0) + { + /* Canonicalize one separator */ + *p++ = G_DIR_SEPARATOR; + } + } + + /* Remove additional separators */ + q = p; + while (*q && G_IS_DIR_SEPARATOR (*q)) + q++; + + if (p != q) + memmove (p, q, strlen (q) + 1); + } + + /* Remove trailing slashes */ + if (p > start && G_IS_DIR_SEPARATOR (*(p-1))) + *(p-1) = 0; + + return canon; +} +#endif diff --git a/src/glib-backports.h b/src/glib-backports.h index 0e047412531ef71e032762e4e044c300657aeb10..78f7fe164f4b3566c4a3b69ee79255144234b766 100644 --- a/src/glib-backports.h +++ b/src/glib-backports.h @@ -94,3 +94,9 @@ guint my_g_unix_fd_add_full (int priority, GSource *my_g_unix_fd_source_new (int fd, GIOCondition condition); #endif + +#if !GLIB_CHECK_VERSION(2, 58, 0) +#define g_canonicalize_filename(f, r) my_g_canonicalize_filename (f, r) +gchar *my_g_canonicalize_filename (const gchar *filename, + const gchar *relative_to); +#endif diff --git a/src/meson.build b/src/meson.build index 0ac2f0a4f7d7db605188bb57fb6474967f4880c3..27242f101364c88817a4708058217a7ca2f4bd72 100644 --- a/src/meson.build +++ b/src/meson.build @@ -54,6 +54,8 @@ pressure_vessel_utils = static_library( 'glib-backports.h', 'resolve-in-sysroot.c', 'resolve-in-sysroot.h', + 'tree-copy.c', + 'tree-copy.h', 'utils.c', 'utils.h', ], diff --git a/src/runtime.c b/src/runtime.c index c7dd569f74ef3ac08d9d19c6415fb93976c4694b..6a90102578dc64013f057145b8dfdcb5be408250 100644 --- a/src/runtime.c +++ b/src/runtime.c @@ -36,6 +36,7 @@ #include "enumtypes.h" #include "flatpak-run-private.h" #include "resolve-in-sysroot.h" +#include "tree-copy.h" #include "utils.h" /* @@ -550,16 +551,19 @@ pv_runtime_init_mutable (PvRuntime *self, * ${temp_dir}/usr/bin, etc. */ source_usr = self->source_files; - if (!pv_cheap_tree_copy (self->source_files, dest_usr, error)) + if (!pv_cheap_tree_copy (self->source_files, dest_usr, + PV_COPY_FLAGS_NONE, error)) return FALSE; } else { /* ${source_files}/usr exists, so assume it's a complete sysroot. - * Copy ${source_files}/bin to ${temp_dir}/bin, etc. */ + * Merge ${source_files}/bin and ${source_files}/usr/bin into + * ${temp_dir}/usr/bin, etc. */ source_usr = source_usr_subdir; - if (!pv_cheap_tree_copy (self->source_files, temp_dir, error)) + if (!pv_cheap_tree_copy (self->source_files, temp_dir, + PV_COPY_FLAGS_USRMERGE, error)) return FALSE; } @@ -2962,7 +2966,8 @@ pv_runtime_bind (PvRuntime *self, dest = glnx_fdrel_abspath (parent_dirfd, "from-host"); - if (!pv_cheap_tree_copy (pressure_vessel_prefix, dest, error)) + if (!pv_cheap_tree_copy (pressure_vessel_prefix, dest, + PV_COPY_FLAGS_NONE, error)) return FALSE; flatpak_bwrap_add_args (bwrap, diff --git a/src/tree-copy.c b/src/tree-copy.c new file mode 100644 index 0000000000000000000000000000000000000000..81160366e8880ac9b96ca180d1d2e5802abea110 --- /dev/null +++ b/src/tree-copy.c @@ -0,0 +1,332 @@ +/* + * Contains code taken from Flatpak. + * + * Copyright © 2014-2019 Red Hat, Inc + * Copyright © 2017-2020 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 "tree-copy.h" + +#include <ftw.h> + +#include <glib.h> +#include <glib/gstdio.h> +#include <gio/gio.h> + +#include "libglnx/libglnx.h" + +#include "glib-backports.h" +#include "flatpak-bwrap-private.h" +#include "flatpak-utils-base-private.h" +#include "flatpak-utils-private.h" + +/* Enabling debug logging for this is rather too verbose, so only + * enable it when actively debugging this module */ +#if 0 +#define trace(...) g_debug (__VA_ARGS__) +#else +#define trace(...) do { } while (0) +#endif + +static inline gboolean +gets_usrmerged (const char *path) +{ + while (path[0] == '/') + path++; + + if (strcmp (path, "bin") == 0 || + strcmp (path, "sbin") == 0 || + g_str_has_prefix (path, "bin/") || + g_str_has_prefix (path, "sbin/") || + (g_str_has_prefix (path, "lib") && + strcmp (path, "libexec") != 0 && + !g_str_has_prefix (path, "libexec/"))) + return TRUE; + + return FALSE; +} + +/* nftw() doesn't have a user_data argument so we need to use a global + * variable :-( */ +static struct +{ + gchar *source_root; + gchar *dest_root; + PvCopyFlags flags; + GError *error; +} nftw_data; + +static int +copy_tree_helper (const char *fpath, + const struct stat *sb, + int typeflag, + struct FTW *ftwbuf) +{ + size_t len; + const char *suffix; + g_autofree gchar *dest = NULL; + g_autofree gchar *target = NULL; + GError **error = &nftw_data.error; + gboolean usrmerge; + + g_return_val_if_fail (g_str_has_prefix (fpath, nftw_data.source_root), 1); + + if (strcmp (fpath, nftw_data.source_root) == 0) + { + if (typeflag != FTW_D) + { + glnx_throw (error, "\"%s\" is not a directory", fpath); + return 1; + } + + if (!glnx_shutil_mkdir_p_at (-1, nftw_data.dest_root, + sb->st_mode & 07777, NULL, error)) + return 1; + + return 0; + } + + len = strlen (nftw_data.source_root); + g_return_val_if_fail (fpath[len] == '/', 1); + suffix = &fpath[len + 1]; + + while (suffix[0] == '/') + suffix++; + + trace ("\"%s\": suffix=\"%s\"", fpath, suffix); + + /* If source_root was /path/to/source and fpath was /path/to/source/foo/bar, + * then suffix is now foo/bar. */ + + if ((nftw_data.flags & PV_COPY_FLAGS_USRMERGE) != 0 && + gets_usrmerged (suffix)) + { + trace ("Transforming to \"usr/%s\" for /usr merge", suffix); + usrmerge = TRUE; + /* /path/to/dest/usr/foo/bar */ + dest = g_build_filename (nftw_data.dest_root, "usr", suffix, NULL); + } + else + { + usrmerge = FALSE; + /* /path/to/dest/foo/bar */ + dest = g_build_filename (nftw_data.dest_root, suffix, NULL); + } + + switch (typeflag) + { + case FTW_D: + trace ("Is a directory"); + + /* If merging /usr, replace /bin, /sbin, /lib* with symlinks like + * /bin -> usr/bin */ + if (usrmerge && strchr (suffix, '/') == NULL) + { + /* /path/to/dest/bin or similar */ + g_autofree gchar *in_root = g_build_filename (nftw_data.dest_root, + suffix, NULL); + + target = g_build_filename ("usr", suffix, NULL); + + if (TEMP_FAILURE_RETRY (symlink (target, in_root)) != 0) + { + glnx_throw_errno_prefix (error, + "Unable to create symlink \"%s\" -> \"%s\"", + dest, target); + return 1; + } + + /* Fall through to create usr/bin or similar too */ + } + + if (!glnx_shutil_mkdir_p_at (-1, dest, sb->st_mode & 07777, + NULL, error)) + return 1; + break; + + case FTW_SL: + target = glnx_readlinkat_malloc (-1, fpath, NULL, error); + + if (target == NULL) + return 1; + + trace ("Is a symlink to \"%s\"", target); + + if (usrmerge) + { + trace ("Checking for compat symlinks into /usr"); + + /* Ignore absolute compat symlinks /lib/foo -> /usr/lib/foo. + * In this case suffix would be lib/foo. (In a Debian-based + * source root, Debian Policy §10.5 says this is the only + * form of compat symlink that should exist in this + * direction.) */ + if (g_str_has_prefix (target, "/usr/") && + strcmp (target + 5, suffix) == 0) + { + trace ("Ignoring compat symlink \"%s\" -> \"%s\"", + fpath, target); + return 0; + } + + /* Ignore relative compat symlinks /lib/foo -> ../usr/lib/foo. */ + if (target[0] != '/') + { + g_autofree gchar *dir = g_path_get_dirname (suffix); + g_autofree gchar *joined = NULL; + g_autofree gchar *canon = NULL; + + joined = g_build_filename (dir, target, NULL); + trace ("Joined: \"%s\"", joined); + canon = g_canonicalize_filename (joined, "/"); + trace ("Canonicalized: \"%s\"", canon); + + if (g_str_has_prefix (canon, "/usr/") && + strcmp (canon + 5, suffix) == 0) + { + trace ("Ignoring compat symlink \"%s\" -> \"%s\"", + fpath, target); + return 0; + } + } + } + + if ((nftw_data.flags & PV_COPY_FLAGS_USRMERGE) != 0 && + g_str_has_prefix (suffix, "usr/") && + gets_usrmerged (suffix + 4)) + { + trace ("Checking for compat symlinks out of /usr"); + + /* Ignore absolute compat symlinks /usr/lib/foo -> /lib/foo. + * In this case suffix would be usr/lib/foo. (In a Debian-based + * source root, Debian Policy §10.5 says this is the only + * form of compat symlink that should exist in this + * direction.) */ + if (strcmp (suffix + 3, target) == 0) + { + trace ("Ignoring compat symlink \"%s\" -> \"%s\"", + fpath, target); + return 0; + } + + /* Ignore relative compat symlinks + * /usr/lib/foo -> ../../lib/foo. */ + if (target[0] != '/') + { + g_autofree gchar *dir = g_path_get_dirname (suffix); + g_autofree gchar *joined = NULL; + g_autofree gchar *canon = NULL; + + joined = g_build_filename (dir, target, NULL); + trace ("Joined: \"%s\"", joined); + canon = g_canonicalize_filename (joined, "/"); + trace ("Canonicalized: \"%s\"", canon); + g_assert (canon[0] == '/'); + + if (strcmp (suffix + 3, canon) == 0) + { + trace ("Ignoring compat symlink \"%s\" -> \"%s\"", + fpath, target); + return 0; + } + } + } + + if (TEMP_FAILURE_RETRY (symlink (target, dest)) != 0) + { + glnx_throw_errno_prefix (error, + "Unable to create symlink \"%s\" -> \"%s\"", + dest, target); + return 1; + } + break; + + case FTW_F: + trace ("Is a regular file"); + + /* Fast path: try to make a hard link. */ + if (link (fpath, dest) == 0) + break; + + /* Slow path: fall back to copying. + * + * This does a FICLONE or copy_file_range to get btrfs reflinks + * if possible, making the copy as cheap as cp --reflink=auto. + * + * Rather than second-guessing which errno values would result + * in link() failing but a copy succeeding, we just try it + * unconditionally - the worst that can happen is that this + * fails too. */ + if (!glnx_file_copy_at (AT_FDCWD, fpath, sb, + AT_FDCWD, dest, + GLNX_FILE_COPY_OVERWRITE, + NULL, error)) + { + glnx_prefix_error (error, "Unable to copy \"%s\" to \"%s\"", + fpath, dest); + return 1; + } + break; + + default: + glnx_throw (&nftw_data.error, + "Don't know how to handle ftw type flag %d at %s", + typeflag, fpath); + return 1; + } + + return 0; +} + +gboolean +pv_cheap_tree_copy (const char *source_root, + const char *dest_root, + PvCopyFlags flags, + GError **error) +{ + int res; + + g_return_val_if_fail (source_root != NULL, FALSE); + g_return_val_if_fail (dest_root != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + /* Can't run concurrently */ + g_return_val_if_fail (nftw_data.source_root == NULL, FALSE); + + nftw_data.source_root = flatpak_canonicalize_filename (source_root); + nftw_data.dest_root = flatpak_canonicalize_filename (dest_root); + nftw_data.flags = flags; + nftw_data.error = NULL; + + res = nftw (nftw_data.source_root, copy_tree_helper, 100, FTW_PHYS); + + if (res == -1) + { + g_assert (nftw_data.error == NULL); + glnx_throw_errno_prefix (error, "Unable to copy \"%s\" to \"%s\"", + source_root, dest_root); + } + else if (res != 0) + { + g_propagate_error (error, g_steal_pointer (&nftw_data.error)); + } + + g_clear_pointer (&nftw_data.source_root, g_free); + g_clear_pointer (&nftw_data.dest_root, g_free); + g_assert (nftw_data.error == NULL); + return (res == 0); +} diff --git a/src/tree-copy.h b/src/tree-copy.h new file mode 100644 index 0000000000000000000000000000000000000000..473005148fea484ac9d8195f45ef6f55e0a74448 --- /dev/null +++ b/src/tree-copy.h @@ -0,0 +1,45 @@ +/* + * Contains code taken from Flatpak. + * + * Copyright © 2014-2019 Red Hat, Inc + * Copyright © 2017-2020 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> + +/* + * PvCopyFlags: + * @PV_COPY_FLAGS_USRMERGE: Transform the copied tree by merging + * /bin, /sbin, /lib* into /usr, and replacing them with symbolic + * links /bin -> usr/bin and so on. + * @PV_RESOLVE_FLAGS_NONE: No special behaviour. + * + * Flags affecting how pv_cheap_tree_copy() behaves. + */ +typedef enum +{ + PV_COPY_FLAGS_USRMERGE = (1 << 0), + PV_COPY_FLAGS_NONE = 0 +} PvCopyFlags; + +gboolean pv_cheap_tree_copy (const char *source_root, + const char *dest_root, + PvCopyFlags flags, + GError **error); diff --git a/src/utils.c b/src/utils.c index 95f3bf80358d4362ecbbe29a5a88230f15409f3d..e646399bdd7eb9112a79d4438e6f554d4c74e9cf 100644 --- a/src/utils.c +++ b/src/utils.c @@ -286,143 +286,6 @@ pv_hash_table_get_arbitrary_key (GHashTable *table) return NULL; } -/* nftw() doesn't have a user_data argument so we need to use a global - * variable :-( */ -static struct -{ - gchar *source_root; - gchar *dest_root; - GError *error; -} nftw_data; - -static int -copy_tree_helper (const char *fpath, - const struct stat *sb, - int typeflag, - struct FTW *ftwbuf) -{ - size_t len; - const char *suffix; - g_autofree gchar *dest = NULL; - g_autofree gchar *target = NULL; - GError **error = &nftw_data.error; - - g_return_val_if_fail (g_str_has_prefix (fpath, nftw_data.source_root), 1); - - if (strcmp (fpath, nftw_data.source_root) == 0) - { - if (typeflag != FTW_D) - { - glnx_throw (error, "\"%s\" is not a directory", fpath); - return 1; - } - - if (!glnx_shutil_mkdir_p_at (-1, nftw_data.dest_root, - sb->st_mode & 07777, NULL, error)) - return 1; - - return 0; - } - - len = strlen (nftw_data.source_root); - g_return_val_if_fail (fpath[len] == '/', 1); - suffix = &fpath[len + 1]; - dest = g_build_filename (nftw_data.dest_root, suffix, NULL); - - switch (typeflag) - { - case FTW_D: - if (!glnx_shutil_mkdir_p_at (-1, dest, sb->st_mode & 07777, - NULL, error)) - return 1; - break; - - case FTW_SL: - target = glnx_readlinkat_malloc (-1, fpath, NULL, error); - - if (target == NULL) - return 1; - - if (symlink (target, dest) != 0) - { - glnx_throw_errno_prefix (error, - "Unable to create symlink at \"%s\"", - dest); - return 1; - } - break; - - case FTW_F: - /* Fast path: try to make a hard link. */ - if (link (fpath, dest) == 0) - break; - - /* Slow path: fall back to copying. - * - * This does a FICLONE or copy_file_range to get btrfs reflinks - * if possible, making the copy as cheap as cp --reflink=auto. - * - * Rather than second-guessing which errno values would result - * in link() failing but a copy succeeding, we just try it - * unconditionally - the worst that can happen is that this - * fails too. */ - if (!glnx_file_copy_at (AT_FDCWD, fpath, sb, - AT_FDCWD, dest, - GLNX_FILE_COPY_OVERWRITE, - NULL, error)) - { - glnx_prefix_error (error, "Unable to copy \"%s\" to \"%s\"", - fpath, dest); - return 1; - } - break; - - default: - glnx_throw (&nftw_data.error, - "Don't know how to handle ftw type flag %d at %s", - typeflag, fpath); - return 1; - } - - return 0; -} - -gboolean -pv_cheap_tree_copy (const char *source_root, - const char *dest_root, - GError **error) -{ - int res; - - g_return_val_if_fail (source_root != NULL, FALSE); - g_return_val_if_fail (dest_root != NULL, FALSE); - g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - /* Can't run concurrently */ - g_return_val_if_fail (nftw_data.source_root == NULL, FALSE); - - nftw_data.source_root = flatpak_canonicalize_filename (source_root); - nftw_data.dest_root = flatpak_canonicalize_filename (dest_root); - nftw_data.error = NULL; - - res = nftw (nftw_data.source_root, copy_tree_helper, 100, FTW_PHYS); - - if (res == -1) - { - g_assert (nftw_data.error == NULL); - glnx_throw_errno_prefix (error, "Unable to copy \"%s\" to \"%s\"", - source_root, dest_root); - } - else if (res != 0) - { - g_propagate_error (error, g_steal_pointer (&nftw_data.error)); - } - - g_clear_pointer (&nftw_data.source_root, g_free); - g_clear_pointer (&nftw_data.dest_root, g_free); - g_assert (nftw_data.error == NULL); - return (res == 0); -} - static gint ftw_remove (const gchar *path, const struct stat *sb, diff --git a/src/utils.h b/src/utils.h index 3d89b7cedce569deca2ad6d61b2053e38a08e7d0..0966efedb88a1af8abd8e32dfbe45796941cacde 100644 --- a/src/utils.h +++ b/src/utils.h @@ -54,10 +54,6 @@ gchar *pv_capture_output (const char * const * argv, gpointer pv_hash_table_get_arbitrary_key (GHashTable *table); -gboolean pv_cheap_tree_copy (const char *source_root, - const char *dest_root, - GError **error); - gboolean pv_rm_rf (const char *directory); gboolean pv_boolean_environment (const gchar *name, diff --git a/tests/cheap-copy.c b/tests/cheap-copy.c index 9a5bf686c8a1183b48a58cbd2ad761c3942bc73d..f9fc6b3d2457cf4a2a6603ebdbc88df224fe05da 100644 --- a/tests/cheap-copy.c +++ b/tests/cheap-copy.c @@ -26,10 +26,18 @@ #include "libglnx/libglnx.h" #include "glib-backports.h" +#include "tree-copy.h" #include "utils.h" +static gboolean opt_usrmerge = FALSE; + static GOptionEntry options[] = { + { "usrmerge", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_usrmerge, + "Assume SOURCE is a sysroot, and carry out the /usr merge in DEST.", + NULL }, + { NULL } }; @@ -40,6 +48,7 @@ main (int argc, g_autoptr(GOptionContext) context = NULL; g_autoptr(GError) local_error = NULL; GError **error = &local_error; + PvCopyFlags flags = PV_COPY_FLAGS_NONE; int ret = EX_USAGE; setlocale (LC_ALL, ""); @@ -65,7 +74,10 @@ main (int argc, ret = EX_UNAVAILABLE; - if (!pv_cheap_tree_copy (argv[1], argv[2], error)) + if (opt_usrmerge) + flags |= PV_COPY_FLAGS_USRMERGE; + + if (!pv_cheap_tree_copy (argv[1], argv[2], flags, error)) goto out; ret = 0; diff --git a/tests/cheap-copy.py b/tests/cheap-copy.py index e14082ebd679b7b13f0c7b6e56ce77cb8fbe86c3..f98d7d94eafa8c53c747149fed99e5d6fbcc6ddb 100755 --- a/tests/cheap-copy.py +++ b/tests/cheap-copy.py @@ -7,6 +7,7 @@ import os import subprocess import sys import tempfile +from pathlib import Path try: @@ -24,6 +25,7 @@ from testutils import ( class TestCheapCopy(BaseTest): def setUp(self) -> None: super().setUp() + os.environ['G_MESSAGES_DEBUG'] = 'all' self.cheap_copy = os.path.join(self.G_TEST_BUILDDIR, 'test-cheap-copy') def assert_tree_is_superset( @@ -156,6 +158,79 @@ class TestCheapCopy(BaseTest): """ self.test_populated('/tmp', '/var/tmp', require_hard_links=False) + def test_usrmerge(self): + with tempfile.TemporaryDirectory( + ) as source, tempfile.TemporaryDirectory( + ) as parent, tempfile.TemporaryDirectory( + ) as expected: + (Path(source) / 'bin').mkdir(parents=True) + (Path(source) / 'lib').mkdir(parents=True) + (Path(source) / 'lib/x86_64-linux-gnu').mkdir(parents=True) + (Path(source) / 'lib32').mkdir(parents=True) + (Path(source) / 'usr/bin').mkdir(parents=True) + (Path(source) / 'usr/bin/which').touch() + (Path(source) / 'bin/which').symlink_to('/usr/bin/which') + (Path(source) / 'bin/less').touch() + (Path(source) / 'usr/bin/less').symlink_to('/bin/less') + (Path(source) / 'bin/more').touch() + (Path(source) / 'usr/bin/more').symlink_to('../../bin/more') + (Path(source) / 'usr/bin/env').touch() + (Path(source) / 'bin/env').symlink_to('../usr/bin/env') + (Path(source) / 'usr/bin/gcc').symlink_to('gcc-9') + (Path(source) / 'usr/bin/foo').symlink_to('/bin/foo-1') + (Path(source) / 'usr/bin/bar').symlink_to('../../bin/bar-2') + (Path(source) / 'usr/lib/x86_64-linux-gnu').mkdir(parents=True) + (Path(source) / 'bin/x').symlink_to('/usr/bin/x-1') + (Path(source) / 'bin/y').symlink_to('../usr/bin/x-2') + (Path(source) / 'lib/x86_64-linux-gnu/libpng12.so.0').symlink_to( + 'libpng12.so.0.46.0') + (Path(source) / 'lib/x86_64-linux-gnu/libpng12.so.0.46.0').touch() + ( + Path(source) / 'usr/lib/x86_64-linux-gnu/libpng12.so.0' + ).symlink_to('/lib/x86_64-linux-gnu/libpng12.so.0') + ( + Path(source) / 'usr/lib/x86_64-linux-gnu/libpng12.so' + ).symlink_to('libpng12.so.0') + + (Path(expected) / 'bin').symlink_to('usr/bin') + (Path(expected) / 'lib').symlink_to('usr/lib') + (Path(expected) / 'lib32').symlink_to('usr/lib32') + (Path(expected) / 'usr/lib').mkdir(parents=True) + (Path(expected) / 'usr/lib/x86_64-linux-gnu').mkdir(parents=True) + (Path(expected) / 'usr/lib32').mkdir(parents=True) + (Path(expected) / 'usr/bin').mkdir(parents=True) + (Path(expected) / 'usr/bin/which').touch() + (Path(expected) / 'usr/bin/less').touch() + (Path(expected) / 'usr/bin/more').touch() + (Path(expected) / 'usr/bin/env').touch() + (Path(expected) / 'usr/bin/gcc').symlink_to('gcc-9') + (Path(expected) / 'usr/bin/foo').symlink_to('/bin/foo-1') + (Path(expected) / 'usr/bin/bar').symlink_to('../../bin/bar-2') + (Path(expected) / 'bin/x').symlink_to('/usr/bin/x-1') + (Path(expected) / 'bin/y').symlink_to('../usr/bin/x-2') + ( + Path(expected) / 'usr/lib/x86_64-linux-gnu/libpng12.so.0' + ).symlink_to('libpng12.so.0.46.0') + ( + Path(expected) / 'usr/lib/x86_64-linux-gnu/libpng12.so.0.46.0' + ).touch() + ( + Path(expected) / 'usr/lib/x86_64-linux-gnu/libpng12.so' + ).symlink_to('libpng12.so.0') + + dest = os.path.join(parent, 'dest') + subprocess.run( + [ + self.cheap_copy, + '--usrmerge', + source, + dest, + ], + check=True, + stdout=2, + ) + self.assert_tree_is_same(expected, dest, require_hard_links=False) + def tearDown(self) -> None: super().tearDown() diff --git a/tests/containers.py b/tests/containers.py index 77a5c760055065eeea5a1abb179709cb4ad336ea..9e1721e063d3307f0f9eb8e984ed8153a218a84c 100755 --- a/tests/containers.py +++ b/tests/containers.py @@ -25,6 +25,8 @@ ideally contains at least: The Platform merged-/usr from the SteamLinuxRuntime depot * scout_sysroot, soldier_sysroot: An SDK sysroot like the one recommended for the Docker container +* scout_sysroot_usrmerge: + The same, but /usr-merged and run (for example) 'meson test -v -C _build' as usual. @@ -906,6 +908,27 @@ class TestContainers(BaseTest): with self.subTest('transient'): self._test_scout('scout_sysroot', scout, locales=True) + def test_scout_sysroot_usrmerge(self) -> None: + scout = os.path.join(self.containers_dir, 'scout_sysroot_usrmerge') + + if os.path.isdir(os.path.join(scout, 'files')): + scout = os.path.join(scout, 'files') + + with self.subTest('only-prepare'): + self._test_scout( + 'scout_sysroot_prep_usrmerge', scout, + copy=True, only_prepare=True, + ) + + with self.subTest('copy'): + self._test_scout( + 'scout_sysroot_copy_usrmerge', scout, + copy=True, gc=False, + ) + + with self.subTest('transient'): + self._test_scout('scout_sysroot_usrmerge', scout, locales=True) + def test_scout_usr(self) -> None: scout = os.path.join(self.containers_dir, 'scout', 'files') diff --git a/tests/meson.build b/tests/meson.build index 34d42008754fe57f8e93dca0fe7a19961288a657..0e9a3eb2a5e665572cfa5c6eff4afecd608e96ba 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -92,6 +92,10 @@ foreach test_name : tests test_args += ['-e', python.path()] endif + if test_name.endswith('launcher.py') + timeout = 60 + endif + if test_name.endswith('containers.py') timeout = 300 endif