diff --git a/src/runtime.c b/src/runtime.c index c221a4d59e83f1988f713c61a61b40a98a1b1a38..6a90102578dc64013f057145b8dfdcb5be408250 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 8666668a0a0d9e6bbcbfdb332107b50af132cacd..81160366e8880ac9b96ca180d1d2e5802abea110 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 b15e13fad6822aa04fae27bc42d54486e0b12595..473005148fea484ac9d8195f45ef6f55e0a74448 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 912da297eec5f89b4bfc0fbf0ef04faf0a92c481..f9fc6b3d2457cf4a2a6603ebdbc88df224fe05da 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;