/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- * * Copyright (C) 2013,2014 Colin Walters * * 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 of the licence 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, write to the * Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, MA 02111-1307, USA. */ #include "config.h" #include #include #include #include #include #include "rpmostree-compose-builtins.h" #include "rpmostree-util.h" #include "rpmostree-json-parsing.h" #include "rpmostree-cleanup.h" #include "rpmostree-treepkgdiff.h" #include "rpmostree-libcontainer.h" #include "rpmostree-postprocess.h" #include "rpmostree-passwd-util.h" #include "libgsystem.h" static char *opt_workdir; static gboolean opt_workdir_tmpfs; static char *opt_cachedir; static char *opt_proxy; static char *opt_output_repodata_dir; static char **opt_metadata_strings; static char *opt_repo; static char **opt_override_pkg_repos; static gboolean opt_print_only; static GOptionEntry option_entries[] = { { "add-metadata-string", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_metadata_strings, "Append given key and value (in string format) to metadata", "KEY=VALUE" }, { "workdir", 0, 0, G_OPTION_ARG_STRING, &opt_workdir, "Working directory", "WORKDIR" }, { "workdir-tmpfs", 0, 0, G_OPTION_ARG_NONE, &opt_workdir_tmpfs, "Use tmpfs for working state", NULL }, { "output-repodata-dir", 0, 0, G_OPTION_ARG_STRING, &opt_output_repodata_dir, "Save downloaded repodata in DIR", "DIR" }, { "cachedir", 0, 0, G_OPTION_ARG_STRING, &opt_cachedir, "Cached state", "CACHEDIR" }, { "repo", 'r', 0, G_OPTION_ARG_STRING, &opt_repo, "Path to OSTree repository", "REPO" }, { "add-override-pkg-repo", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_override_pkg_repos, "Include an additional package repository from DIRECTORY", "DIRECTORY" }, { "proxy", 0, 0, G_OPTION_ARG_STRING, &opt_proxy, "HTTP proxy", "PROXY" }, { "print-only", 0, 0, G_OPTION_ARG_NONE, &opt_print_only, "Just expand any includes and print treefile", NULL }, { NULL } }; /* FIXME: This is a copy of ot_admin_checksum_version */ static char * checksum_version (GVariant *checksum) { gs_unref_variant GVariant *metadata = NULL; const char *ret = NULL; metadata = g_variant_get_child_value (checksum, 0); if (!g_variant_lookup (metadata, "version", "&s", &ret)) return NULL; return g_strdup (ret); } typedef struct { GPtrArray *treefile_context_dirs; GFile *workdir; GBytes *serialized_treefile; } RpmOstreeTreeComposeContext; static char * strv_join_shell_quote (char **argv) { GString *ret = g_string_new (""); char **strviter; for (strviter = argv; strviter && *strviter; strviter++) { gs_free char *quoted = g_shell_quote (*strviter); g_string_append_c (ret, ' '); g_string_append (ret, quoted); } return g_string_free (ret, FALSE); } typedef struct { gboolean running; pid_t pid; GFile *tmp_reposdir_path; GDataOutputStream *stdin; } YumContext; static gboolean yum_context_close (YumContext *yumctx, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; if (!yumctx) return TRUE; if (yumctx->running) { if (yumctx->stdin) { if (!g_output_stream_close ((GOutputStream*)yumctx->stdin, cancellable, error)) goto out; g_clear_object (&yumctx->stdin); } g_print ("Waiting for yum...\n"); if (!_rpmostree_sync_wait_on_pid (yumctx->pid, error)) goto out; g_print ("Waiting for yum [OK]\n"); } ret = TRUE; out: return ret; } static void yum_context_free (YumContext *yumctx) { if (!yumctx) return; (void) yum_context_close (yumctx, NULL, NULL); g_free (yumctx); } static gboolean append_repo_and_cache_opts (RpmOstreeTreeComposeContext *self, JsonObject *treedata, GPtrArray *args, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; JsonArray *enable_repos = NULL; guint i; char **iter; gs_unref_object GFile *yumcache_lookaside = NULL; gs_unref_object GFile *repos_tmpdir = NULL; gs_unref_ptrarray GPtrArray *reposdir_args = g_ptr_array_new_with_free_func (g_free); if (opt_output_repodata_dir) yumcache_lookaside = g_file_new_for_path (opt_output_repodata_dir); else { yumcache_lookaside = g_file_resolve_relative_path (self->workdir, "yum-cache"); if (!gs_file_ensure_directory (yumcache_lookaside, TRUE, cancellable, error)) goto out; } repos_tmpdir = g_file_resolve_relative_path (self->workdir, "tmp-repos"); if (!gs_shutil_rm_rf (repos_tmpdir, cancellable, error)) goto out; if (!gs_file_ensure_directory (repos_tmpdir, TRUE, cancellable, error)) goto out; if (g_getenv ("RPM_OSTREE_OFFLINE")) g_ptr_array_add (args, g_strdup ("-C")); { const char *proxy; if (opt_proxy) proxy = opt_proxy; else proxy = g_getenv ("http_proxy"); if (proxy) g_ptr_array_add (args, g_strconcat ("--setopt=proxy=", proxy, NULL)); } g_ptr_array_add (args, g_strdup ("--disablerepo=*")); /* Add the directory for each treefile to the reposdir argument */ for (i = 0; i < self->treefile_context_dirs->len; i++) { GFile *contextdir = self->treefile_context_dirs->pdata[i]; g_ptr_array_add (reposdir_args, g_file_get_path (contextdir)); } /* Process local overrides */ for (iter = opt_override_pkg_repos; iter && *iter; iter++) { const char *repodir = *iter; gs_free char *bn = g_path_get_basename (repodir); gs_free char *reponame = g_strconcat ("rpm-ostree-override-", repodir, NULL); gs_free char *baseurl = g_strconcat ("file://", repodir, NULL); gs_free char *tmprepo_filename = g_strconcat (reponame, ".repo", NULL); gs_unref_object GFile *tmprepo_path = g_file_get_child (repos_tmpdir, tmprepo_filename); gs_unref_keyfile GKeyFile *keyfile = NULL; gs_free char *data = NULL; gsize len; keyfile = g_key_file_new (); g_key_file_set_string (keyfile, reponame, "name", reponame); g_key_file_set_string (keyfile, reponame, "baseurl", baseurl); data = g_key_file_to_data (keyfile, &len, NULL); if (!g_file_replace_contents (tmprepo_path, data, len, NULL, FALSE, 0, NULL, cancellable, error)) goto out; g_ptr_array_add (args, g_strconcat ("--enablerepo=", reponame, NULL)); } if (opt_override_pkg_repos) g_ptr_array_add (reposdir_args, g_file_get_path (repos_tmpdir)); { gboolean first = TRUE; GString *reposdir_value = g_string_new ("--setopt=reposdir="); for (i = 0; i < reposdir_args->len; i++) { const char *reponame = reposdir_args->pdata[i]; if (first) first = FALSE; else g_string_append_c (reposdir_value, ','); g_string_append (reposdir_value, reponame); } g_ptr_array_add (args, g_string_free (reposdir_value, FALSE)); } if (json_object_has_member (treedata, "repos")) enable_repos = json_object_get_array_member (treedata, "repos"); if (enable_repos) { guint i; guint n = json_array_get_length (enable_repos); for (i = 0; i < n; i++) { const char *reponame = _rpmostree_jsonutil_array_require_string_element (enable_repos, i, error); if (!reponame) goto out; g_ptr_array_add (args, g_strconcat ("--enablerepo=", reponame, NULL)); } } g_ptr_array_add (args, g_strdup ("--setopt=keepcache=0")); g_ptr_array_add (args, g_strconcat ("--setopt=cachedir=", gs_file_get_path_cached (yumcache_lookaside), NULL)); ret = TRUE; out: return ret; } static YumContext * yum_context_new (RpmOstreeTreeComposeContext *self, JsonObject *treedata, GFile *yumroot, GCancellable *cancellable, GError **error) { gboolean success = FALSE; YumContext *yumctx = NULL; JsonNode *install_langs_n; GPtrArray *yum_argv = g_ptr_array_new_with_free_func (g_free); pid_t child; int clone_flags = SIGCHLD | CLONE_NEWNS | CLONE_NEWPID; int pipefds[2]; g_ptr_array_add (yum_argv, g_strdup ("yum")); g_ptr_array_add (yum_argv, g_strdup ("-y")); if (!append_repo_and_cache_opts (self, treedata, yum_argv, cancellable, error)) goto out; install_langs_n = json_object_get_member (treedata, "install-langs"); if (install_langs_n != NULL) { JsonArray *instlangs_a = json_node_get_array (install_langs_n); guint len = json_array_get_length (instlangs_a); guint i; GString *opt = g_string_new ("--setopt=override_install_langs="); for (i = 0; i < len; i++) { g_string_append (opt, json_array_get_string_element (instlangs_a, i)); if (i < len - 1) g_string_append_c (opt, ','); } g_ptr_array_add (yum_argv, opt->str); g_string_free (opt, FALSE); } g_ptr_array_add (yum_argv, g_strconcat ("--installroot=", gs_file_get_path_cached (yumroot), NULL)); g_ptr_array_add (yum_argv, g_strdup ("shell")); g_ptr_array_add (yum_argv, NULL); if (!g_unix_open_pipe (pipefds, FD_CLOEXEC, error)) goto out; if ((child = syscall (__NR_clone, clone_flags, NULL)) < 0) { _rpmostree_set_error_from_errno (error, errno); goto out; } if (child == 0) { if (dup2 (pipefds[0], 0) != 0) _rpmostree_perror_fatal ("dup2()"); /* This is used at the moment, but eventually I'd like to teach * Fedora's kernel.spec to e.g. skip making an initramfs, * because we're going to be making one. */ setenv ("OSTREE_KERNEL_INSTALL_NOOP", "1", TRUE); /* See fedora's kernel.spec; we don't need this because ostree * itself takes care of dedup-via-hardlink. */ setenv ("HARDLINK", "no", TRUE); /* Turn off setuid binaries, we shouldn't need them */ if (_rpmostree_libcontainer_get_available ()) { if (mount (NULL, "/", "none", MS_PRIVATE | MS_REMOUNT | MS_NOSUID, NULL) < 0) _rpmostree_perror_fatal ("mount(/, MS_PRIVATE | MS_NOSUID)"); } if (execvp ("yum", (char**)yum_argv->pdata) < 0) _rpmostree_perror_fatal ("execvp"); } (void) close (pipefds[0]); { gs_free char *cmdline = strv_join_shell_quote ((char**)yum_argv->pdata); g_print ("Starting %s\n", cmdline); } yumctx = g_new0 (YumContext, 1); yumctx->running = TRUE; yumctx->pid = child; { gs_unref_object GOutputStream *yumproc_stdin = g_unix_output_stream_new (pipefds[1], TRUE); yumctx->stdin = (GDataOutputStream*)g_data_output_stream_new (yumproc_stdin); } success = TRUE; out: if (!success) { yum_context_free (yumctx); return NULL; } return yumctx; } static gboolean yum_context_command (YumContext *yumctx, const char *cmd, GPtrArray **out_lines, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; gsize bytes_written; gs_unref_ptrarray GPtrArray *lines = g_ptr_array_new_with_free_func (g_free); gs_free char *cmd_nl = g_strconcat (cmd, "\n", NULL); g_print ("yum> %s", cmd_nl); if (!g_output_stream_write_all ((GOutputStream*)yumctx->stdin, cmd_nl, strlen (cmd_nl), &bytes_written, cancellable, error)) goto out; ret = TRUE; gs_transfer_out_value (out_lines, &lines); out: return ret; } static gboolean yuminstall (RpmOstreeTreeComposeContext *self, JsonObject *treedata, GFile *yumroot, char **packages, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; char **strviter; YumContext *yumctx; yumctx = yum_context_new (self, treedata, yumroot, cancellable, error); if (!yumctx) goto out; for (strviter = packages; strviter && *strviter; strviter++) { gs_free char *cmd = NULL; const char *package = *strviter; gs_unref_ptrarray GPtrArray *lines = NULL; if (g_str_has_prefix (package, "@")) cmd = g_strconcat ("group install ", package, NULL); else cmd = g_strconcat ("install ", package, NULL); if (!yum_context_command (yumctx, cmd, &lines, cancellable, error)) goto out; } { gs_unref_ptrarray GPtrArray *lines = NULL; if (!yum_context_command (yumctx, "run", &lines, cancellable, error)) goto out; } if (!yum_context_close (yumctx, cancellable, error)) goto out; ret = TRUE; out: return ret; } static gboolean process_includes (RpmOstreeTreeComposeContext *self, GFile *treefile_path, guint depth, JsonObject *root, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; const char *include_path; const guint maxdepth = 50; if (depth > maxdepth) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Exceeded maximum include depth of %u", maxdepth); goto out; } { gs_unref_object GFile *parent = g_file_get_parent (treefile_path); gboolean existed = FALSE; if (self->treefile_context_dirs->len > 0) { GFile *prev = self->treefile_context_dirs->pdata[self->treefile_context_dirs->len-1]; if (g_file_equal (parent, prev)) existed = TRUE; } if (!existed) { g_ptr_array_add (self->treefile_context_dirs, parent); parent = NULL; /* Transfer ownership */ } } if (!_rpmostree_jsonutil_object_get_optional_string_member (root, "include", &include_path, error)) goto out; if (include_path) { gs_unref_object GFile *treefile_dirpath = g_file_get_parent (treefile_path); gs_unref_object GFile *parent_path = g_file_resolve_relative_path (treefile_dirpath, include_path); gs_unref_object JsonParser *parent_parser = json_parser_new (); JsonNode *parent_rootval; JsonObject *parent_root; GList *members; GList *iter; if (!json_parser_load_from_file (parent_parser, gs_file_get_path_cached (parent_path), error)) goto out; parent_rootval = json_parser_get_root (parent_parser); if (!JSON_NODE_HOLDS_OBJECT (parent_rootval)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Treefile root is not an object"); goto out; } parent_root = json_node_get_object (parent_rootval); if (!process_includes (self, parent_path, depth + 1, parent_root, cancellable, error)) goto out; members = json_object_get_members (parent_root); for (iter = members; iter; iter = iter->next) { const char *name = iter->data; JsonNode *parent_val = json_object_get_member (parent_root, name); JsonNode *val = json_object_get_member (root, name); g_assert (parent_val); if (!val) json_object_set_member (root, name, json_node_copy (parent_val)); else { JsonNodeType parent_type = json_node_get_node_type (parent_val); JsonNodeType child_type = json_node_get_node_type (val); if (parent_type != child_type) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Conflicting element type of '%s'", name); goto out; } if (child_type == JSON_NODE_ARRAY) { JsonArray *parent_array = json_node_get_array (parent_val); JsonArray *child_array = json_node_get_array (val); JsonArray *new_child = json_array_new (); guint i, len; len = json_array_get_length (parent_array); for (i = 0; i < len; i++) json_array_add_element (new_child, json_node_copy (json_array_get_element (parent_array, i))); len = json_array_get_length (child_array); for (i = 0; i < len; i++) json_array_add_element (new_child, json_node_copy (json_array_get_element (child_array, i))); json_object_set_array_member (root, name, new_child); } } } json_object_remove_member (root, "include"); } ret = TRUE; out: return ret; } static char * cachedir_fssafe_key (const char *primary_key) { GString *ret = g_string_new (""); for (; *primary_key; primary_key++) { const char c = *primary_key; if (!g_ascii_isprint (c) || c == '-') g_string_append_printf (ret, "\\%02x", c); else if (c == '/') g_string_append_c (ret, '-'); else g_string_append_c (ret, c); } return g_string_free (ret, FALSE); } static GFile * cachedir_keypath (GFile *cachedir, const char *primary_key) { gs_free char *fssafe_key = cachedir_fssafe_key (primary_key); return g_file_get_child (cachedir, fssafe_key); } static gboolean cachedir_lookup_string (GFile *cachedir, const char *key, char **out_value, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; gs_free char *ret_value = NULL; if (cachedir) { gs_unref_object GFile *keypath = cachedir_keypath (cachedir, key); if (!_rpmostree_file_load_contents_utf8_allow_noent (keypath, &ret_value, cancellable, error)) goto out; } ret = TRUE; gs_transfer_out_value (out_value, &ret_value); out: return ret; } static gboolean cachedir_set_string (GFile *cachedir, const char *key, const char *value, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; gs_unref_object GFile *keypath = NULL; if (!cachedir) return TRUE; keypath = cachedir_keypath (cachedir, key); if (!g_file_replace_contents (keypath, value, strlen (value), NULL, FALSE, 0, NULL, cancellable, error)) goto out; ret = TRUE; out: return ret; } static gboolean compute_checksum_for_compose (RpmOstreeTreeComposeContext *self, JsonObject *treefile_rootval, GFile *yumroot, char **out_checksum, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; gs_free char *ret_checksum = NULL; GChecksum *checksum = g_checksum_new (G_CHECKSUM_SHA256); { gsize len; const guint8* buf = g_bytes_get_data (self->serialized_treefile, &len); g_checksum_update (checksum, buf, len); } /* Query the generated rpmdb, to see if anything has changed. */ { _cleanup_hysack_ HySack sack = NULL; _cleanup_hypackagelist_ HyPackageList pkglist = NULL; HyPackage pkg; guint i; if (!rpmostree_get_pkglist_for_root (yumroot, &sack, &pkglist, cancellable, error)) { g_prefix_error (error, "Reading package set: "); goto out; } FOR_PACKAGELIST(pkg, pkglist, i) { gs_free char *nevra = hy_package_get_nevra (pkg); g_checksum_update (checksum, (guint8*)nevra, strlen (nevra)); } } ret_checksum = g_strdup (g_checksum_get_string (checksum)); ret = TRUE; gs_transfer_out_value (out_checksum, &ret_checksum); out: if (checksum) g_checksum_free (checksum); return ret; } static gboolean parse_keyvalue_strings (char **strings, GVariantBuilder *builder, GError **error) { gboolean ret = FALSE; char **iter; for (iter = strings; *iter; iter++) { const char *s; const char *eq; gs_free char *key = NULL; s = *iter; eq = strchr (s, '='); if (!eq) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Missing '=' in KEY=VALUE metadata '%s'", s); goto out; } key = g_strndup (s, eq - s); g_variant_builder_add (builder, "{sv}", key, g_variant_new_string (eq + 1)); } ret = TRUE; out: return ret; } static gboolean compose_strv_contains_prefix (gchar **strv, const gchar *prefix) { if (!strv) return FALSE; while (*strv) { if (g_str_has_prefix (*strv, prefix)) return TRUE; ++strv; } return FALSE; } gboolean rpmostree_compose_builtin_tree (int argc, char **argv, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; GError *temp_error = NULL; GOptionContext *context = g_option_context_new ("- Run yum and commit the result to an OSTree repository"); const char *ref; RpmOstreeTreeComposeContext selfdata = { NULL, }; RpmOstreeTreeComposeContext *self = &selfdata; JsonNode *treefile_rootval = NULL; JsonObject *treefile = NULL; gs_free char *ref_unix = NULL; gs_free char *cachekey = NULL; gs_free char *cached_compose_checksum = NULL; gs_free char *new_compose_checksum = NULL; gs_unref_object GFile *cachedir = NULL; gs_unref_object GFile *previous_root = NULL; gs_free char *previous_checksum = NULL; gs_unref_object GFile *yumroot = NULL; gs_unref_object GFile *targetroot = NULL; gs_unref_object GFile *yumroot_varcache = NULL; gs_unref_object OstreeRepo *repo = NULL; gs_unref_ptrarray GPtrArray *bootstrap_packages = NULL; gs_unref_ptrarray GPtrArray *packages = NULL; gs_unref_object GFile *treefile_path = NULL; gs_unref_object GFile *repo_path = NULL; gs_unref_object JsonParser *treefile_parser = NULL; gs_unref_variant_builder GVariantBuilder *metadata_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); gboolean workdir_is_tmp = FALSE; self->treefile_context_dirs = g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref); if (!rpmostree_option_context_parse (context, option_entries, &argc, &argv, error)) goto out; if (argc < 2) { g_printerr ("usage: rpm-ostree compose tree TREEFILE\n"); g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Option processing failed"); goto out; } if (!opt_repo) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "--repo must be specified"); goto out; } /* Use a private mount namespace to avoid polluting the global * namespace, and to ensure any tmpfs mounts get cleaned up if we * exit unexpectedly. * * We also rely on this for the yum confinement. */ if (unshare (CLONE_NEWNS) != 0) { _rpmostree_set_prefix_error_from_errno (error, errno, "unshare(CLONE_NEWNS): "); goto out; } if (mount (NULL, "/", "none", MS_PRIVATE | MS_REC, NULL) == -1) { /* This happens on RHEL6, not going to debug it further right now... */ if (errno == EINVAL) _rpmostree_libcontainer_set_not_available (); else { _rpmostree_set_prefix_error_from_errno (error, errno, "mount(/, MS_PRIVATE): "); goto out; } } /* Mount several directories read only for protection from librpm * and any stray code in yum/hawkey. */ if (_rpmostree_libcontainer_get_available ()) { struct stat stbuf; /* Protect /var/lib/rpm if (and only if) it's a regular directory. This happens when you're running compose-tree from inside a "mainline" system. On an rpm-ostree based system, /var/lib/rpm -> /usr/share/rpm, which is already protected by a read-only bind mount. */ if (lstat ("/var/lib/rpm", &stbuf) == 0 && S_ISDIR (stbuf.st_mode)) { if (!_rpmostree_libcontainer_bind_mount_readonly ("/var/lib/rpm", error)) goto out; } /* Protect the system's /etc and /usr */ if (!_rpmostree_libcontainer_bind_mount_readonly ("/etc", error)) goto out; if (!_rpmostree_libcontainer_bind_mount_readonly ("/usr", error)) goto out; } repo_path = g_file_new_for_path (opt_repo); repo = ostree_repo_new (repo_path); if (!ostree_repo_open (repo, cancellable, error)) goto out; treefile_path = g_file_new_for_path (argv[1]); if (opt_workdir) { self->workdir = g_file_new_for_path (opt_workdir); } else { gs_free char *tmpd = g_mkdtemp (g_strdup ("/var/tmp/rpm-ostree.XXXXXX")); self->workdir = g_file_new_for_path (tmpd); workdir_is_tmp = TRUE; if (opt_workdir_tmpfs) { if (mount ("tmpfs", tmpd, "tmpfs", 0, (const void*)"mode=755") != 0) { _rpmostree_set_prefix_error_from_errno (error, errno, "mount(tmpfs): "); goto out; } } } if (opt_cachedir) { cachedir = g_file_new_for_path (opt_cachedir); if (!gs_file_ensure_directory (cachedir, FALSE, cancellable, error)) goto out; } if (opt_metadata_strings) { if (!parse_keyvalue_strings (opt_metadata_strings, metadata_builder, error)) goto out; } if (chdir (gs_file_get_path_cached (self->workdir)) != 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to chdir to '%s': %s", gs_file_get_path_cached (self->workdir), strerror (errno)); goto out; } treefile_parser = json_parser_new (); if (!json_parser_load_from_file (treefile_parser, gs_file_get_path_cached (treefile_path), error)) goto out; treefile_rootval = json_parser_get_root (treefile_parser); if (!JSON_NODE_HOLDS_OBJECT (treefile_rootval)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Treefile root is not an object"); goto out; } treefile = json_node_get_object (treefile_rootval); if (!process_includes (self, treefile_path, 0, treefile, cancellable, error)) goto out; if (opt_print_only) { gs_unref_object JsonGenerator *generator = json_generator_new (); gs_unref_object GOutputStream *stdout = g_unix_output_stream_new (1, FALSE); json_generator_set_pretty (generator, TRUE); json_generator_set_root (generator, treefile_rootval); (void) json_generator_to_stream (generator, stdout, NULL, NULL); ret = TRUE; goto out; } ref = _rpmostree_jsonutil_object_require_string_member (treefile, "ref", error); if (!ref) goto out; ref_unix = g_strdelimit (g_strdup (ref), "/", '_'); if (!ostree_repo_read_commit (repo, ref, &previous_root, &previous_checksum, cancellable, &temp_error)) { if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { g_clear_error (&temp_error); g_print ("No previous commit for %s\n", ref); } else { g_propagate_error (error, temp_error); goto out; } } else g_print ("Previous commit: %s\n", previous_checksum); yumroot = g_file_get_child (self->workdir, "rootfs.tmp"); if (!gs_shutil_rm_rf (yumroot, cancellable, error)) goto out; targetroot = g_file_get_child (self->workdir, "rootfs"); if (json_object_has_member (treefile, "automatic_version_prefix") && !compose_strv_contains_prefix (opt_metadata_strings, "version=")) { gs_unref_variant GVariant *variant = NULL; gs_free char *last_version = NULL; gs_free char *next_version = NULL; const char *ver_prefix; ver_prefix = _rpmostree_jsonutil_object_require_string_member (treefile, "automatic_version_prefix", error); if (!ver_prefix) goto out; if (previous_checksum) { if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, previous_checksum, &variant, error)) goto out; last_version = checksum_version (variant); } if (!last_version || !g_str_has_prefix (last_version, ver_prefix)) next_version = g_strdup (ver_prefix); else if (g_str_equal (last_version, ver_prefix)) next_version = g_strdup_printf ("version=%s.1", ver_prefix); else { unsigned long long num; const char *end = last_version + strlen(ver_prefix); if (*end == '.') ++end; num = g_ascii_strtoull (end, NULL, 10); next_version = g_strdup_printf ("%s.%llu", ver_prefix, num + 1); } g_variant_builder_add (metadata_builder, "{sv}", "version", g_variant_new_string (next_version)); } bootstrap_packages = g_ptr_array_new (); packages = g_ptr_array_new (); if (json_object_has_member (treefile, "bootstrap_packages")) { if (!_rpmostree_jsonutil_append_string_array_to (treefile, "bootstrap_packages", packages, error)) goto out; } if (!_rpmostree_jsonutil_append_string_array_to (treefile, "packages", packages, error)) goto out; g_ptr_array_add (packages, NULL); { gs_unref_object JsonGenerator *generator = json_generator_new (); char *treefile_buf = NULL; gsize len; json_generator_set_root (generator, treefile_rootval); json_generator_set_pretty (generator, TRUE); treefile_buf = json_generator_to_data (generator, &len); self->serialized_treefile = g_bytes_new_take (treefile_buf, len); } if (previous_root != NULL) { gboolean generate_from_previous = TRUE; if (!_rpmostree_jsonutil_object_get_optional_boolean_member (treefile, "preserve-passwd", &generate_from_previous, error)) goto out; if (generate_from_previous) { if (!rpmostree_generate_passwd_from_previous (repo, yumroot, previous_root, cancellable, error)) goto out; } } if (!yuminstall (self, treefile, yumroot, (char**)packages->pdata, cancellable, error)) goto out; cachekey = g_strconcat ("treecompose/", ref, NULL); if (!cachedir_lookup_string (cachedir, cachekey, &cached_compose_checksum, cancellable, error)) goto out; if (!compute_checksum_for_compose (self, treefile, yumroot, &new_compose_checksum, cancellable, error)) goto out; if (g_strcmp0 (cached_compose_checksum, new_compose_checksum) == 0) { g_print ("No changes to input, reusing cached commit\n"); ret = TRUE; goto out; } ref_unix = g_strdelimit (g_strdup (ref), "/", '_'); if (g_strcmp0 (g_getenv ("RPM_OSTREE_BREAK"), "post-yum") == 0) goto out; if (!rpmostree_treefile_postprocessing (yumroot, self->treefile_context_dirs->pdata[0], self->serialized_treefile, treefile, cancellable, error)) goto out; if (!rpmostree_prepare_rootfs_for_commit (yumroot, treefile, cancellable, error)) goto out; if (!rpmostree_check_passwd (repo, yumroot, treefile_path, treefile, cancellable, error)) goto out; if (!rpmostree_check_groups (repo, yumroot, treefile_path, treefile, cancellable, error)) goto out; { const char *gpgkey; gs_unref_variant GVariant *metadata = g_variant_ref_sink (g_variant_builder_end (metadata_builder)); if (!_rpmostree_jsonutil_object_get_optional_string_member (treefile, "gpg_key", &gpgkey, error)) goto out; if (!rpmostree_commit (yumroot, repo, ref, metadata, gpgkey, json_object_get_boolean_member (treefile, "selinux"), cancellable, error)) goto out; } if (!cachedir_set_string (cachedir, cachekey, new_compose_checksum, cancellable, error)) goto out; g_print ("Complete\n"); out: if (workdir_is_tmp) { if (opt_workdir_tmpfs) (void) umount (gs_file_get_path_cached (self->workdir)); (void) gs_shutil_rm_rf (self->workdir, NULL, NULL); } if (self) { g_clear_object (&self->workdir); g_clear_pointer (&self->serialized_treefile, g_bytes_unref); g_ptr_array_unref (self->treefile_context_dirs); } return ret; }