From 909ba9db7fcfe6116beb0b19889a5cfcf72b79c9 Mon Sep 17 00:00:00 2001 From: Simon McVittie <smcv@collabora.com> Date: Wed, 19 Aug 2020 17:44:33 +0100 Subject: [PATCH] runtime: Carry out the /usr merge when copying sysroots This doesn't work for current scout because libpng has some weird symlinks, which are not shipped in the .deb but are created by ldconfig (see also Debian #706181, #713270), but a new version of libpng will fix that (T23209). Resolves: #3, T22126 Signed-off-by: Simon McVittie <smcv@collabora.com> --- src/runtime.c | 12 ++-- src/tree-copy.c | 167 +++++++++++++++++++++++++++++++++++++++++++-- src/tree-copy.h | 16 +++++ tests/cheap-copy.c | 13 +++- 4 files changed, 199 insertions(+), 9 deletions(-) diff --git a/src/runtime.c b/src/runtime.c index c221a4d59..6a9010257 100644 --- a/src/runtime.c +++ b/src/runtime.c @@ -551,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; } @@ -2963,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 index 8666668a0..81160366e 100644 --- a/src/tree-copy.c +++ b/src/tree-copy.c @@ -35,12 +35,39 @@ #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; @@ -55,6 +82,7 @@ copy_tree_helper (const char *fpath, 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); @@ -76,11 +104,56 @@ copy_tree_helper (const char *fpath, 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); + + 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; @@ -92,16 +165,100 @@ copy_tree_helper (const char *fpath, if (target == NULL) return 1; - if (symlink (target, dest) != 0) + 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 at \"%s\"", - dest); + "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; @@ -139,6 +296,7 @@ copy_tree_helper (const char *fpath, gboolean pv_cheap_tree_copy (const char *source_root, const char *dest_root, + PvCopyFlags flags, GError **error) { int res; @@ -151,6 +309,7 @@ pv_cheap_tree_copy (const char *source_root, 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); diff --git a/src/tree-copy.h b/src/tree-copy.h index b15e13fad..473005148 100644 --- a/src/tree-copy.h +++ b/src/tree-copy.h @@ -24,6 +24,22 @@ #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/tests/cheap-copy.c b/tests/cheap-copy.c index 912da297e..f9fc6b3d2 100644 --- a/tests/cheap-copy.c +++ b/tests/cheap-copy.c @@ -29,8 +29,15 @@ #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 } }; @@ -41,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, ""); @@ -66,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; -- GitLab