const char *
_rpmostree_jsonutil_object_require_string_member (JsonObject     *object,
						  const char     *member_name,
						  GError        **error)
{
  const char *ret;
  if (!_rpmostree_jsonutil_object_get_optional_string_member (object, member_name, &ret, error))
    return NULL;
  if (!ret)
    {
      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                   "Member '%s' not found", member_name);
      return NULL;
    }
  return ret;
}
Esempio n. 2
0
/* Prepare a root filesystem, taking mainly the contents of /usr from yumroot */
static gboolean
create_rootfs_from_yumroot_content (GFile         *targetroot,
                                    GFile         *yumroot,
                                    JsonObject    *treefile,
                                    GCancellable  *cancellable,
                                    GError       **error)
{
    gboolean ret = FALSE;
    glnx_fd_close int src_rootfs_fd = -1;
    glnx_fd_close int target_root_dfd = -1;
    gs_unref_object GFile *kernel_path = NULL;
    gs_unref_object GFile *initramfs_path = NULL;
    gs_unref_hashtable GHashTable *preserve_groups_set = NULL;
    gboolean container = FALSE;

    if (!glnx_opendirat (AT_FDCWD, gs_file_get_path_cached (yumroot), TRUE,
                         &src_rootfs_fd, error))
        goto out;

    if (!_rpmostree_jsonutil_object_get_optional_boolean_member (treefile,
            "container",
            &container,
            error))
        goto out;

    g_print ("Preparing kernel\n");
    if (!container && !do_kernel_prep (yumroot, treefile, cancellable, error))
        goto out;

    g_print ("Initializing rootfs\n");
    if (!init_rootfs (targetroot, cancellable, error))
        goto out;

    if (!glnx_opendirat (AT_FDCWD, gs_file_get_path_cached (targetroot), TRUE, &target_root_dfd, error))
        goto out;

    g_print ("Migrating /etc/passwd to /usr/lib/\n");
    if (!rpmostree_passwd_migrate_except_root (yumroot, RPM_OSTREE_PASSWD_MIGRATE_PASSWD, NULL,
            cancellable, error))
        goto out;

    if (json_object_has_member (treefile, "etc-group-members"))
    {
        JsonArray *etc_group_members = json_object_get_array_member (treefile, "etc-group-members");
        preserve_groups_set = _rpmostree_jsonutil_jsarray_strings_to_set (etc_group_members);
    }

    g_print ("Migrating /etc/group to /usr/lib/\n");
    if (!rpmostree_passwd_migrate_except_root (yumroot, RPM_OSTREE_PASSWD_MIGRATE_GROUP,
            preserve_groups_set,
            cancellable, error))
        goto out;

    /* NSS configuration to look at the new files */
    {
        gs_unref_object GFile *yumroot_etc =
            g_file_resolve_relative_path (yumroot, "etc");

        if (!replace_nsswitch (yumroot_etc, cancellable, error))
            goto out;
    }

    /* We take /usr from the yum content */
    g_print ("Moving /usr to target\n");
    {
        gs_unref_object GFile *usr = g_file_get_child (yumroot, "usr");
        if (!move_to_dir (usr, targetroot, cancellable, error))
            goto out;
    }

    /* Except /usr/local -> ../var/usrlocal */
    g_print ("Linking /usr/local -> ../var/usrlocal\n");
    {
        gs_unref_object GFile *target_usrlocal =
            g_file_resolve_relative_path (targetroot, "usr/local");

        if (!gs_shutil_rm_rf (target_usrlocal, cancellable, error))
            goto out;

        if (!g_file_make_symbolic_link (target_usrlocal, "../var/usrlocal",
                                        cancellable, error))
            goto out;
    }

    /* And now we take the contents of /etc and put them in /usr/etc */
    g_print ("Moving /etc to /usr/etc\n");
    {
        gs_unref_object GFile *yumroot_etc =
            g_file_get_child (yumroot, "etc");
        gs_unref_object GFile *target_usretc =
            g_file_resolve_relative_path (targetroot, "usr/etc");

        if (!gs_file_rename (yumroot_etc, target_usretc,
                             cancellable, error))
            goto out;
    }

    if (!migrate_rpm_and_yumdb (targetroot, yumroot, cancellable, error))
        goto out;

    if (!convert_var_to_tmpfiles_d (src_rootfs_fd, target_root_dfd, cancellable, error))
        goto out;

    /* Move boot, but rename the kernel/initramfs to have a checksum */
    if (!container)
    {
        gs_unref_object GFile *yumroot_boot =
            g_file_get_child (yumroot, "boot");
        gs_unref_object GFile *target_boot =
            g_file_get_child (targetroot, "boot");
        gs_unref_object GFile *target_usrlib =
            g_file_resolve_relative_path (targetroot, "usr/lib");
        gs_unref_object GFile *target_usrlib_ostree_boot =
            g_file_resolve_relative_path (target_usrlib, "ostree-boot");
        RpmOstreePostprocessBootLocation boot_location =
            RPMOSTREE_POSTPROCESS_BOOT_LOCATION_BOTH;
        const char *boot_location_str = NULL;

        g_print ("Moving /boot\n");

        if (!_rpmostree_jsonutil_object_get_optional_string_member (treefile,
                "boot_location",
                &boot_location_str, error))
            goto out;

        if (boot_location_str != NULL)
        {
            if (strcmp (boot_location_str, "legacy") == 0)
                boot_location = RPMOSTREE_POSTPROCESS_BOOT_LOCATION_LEGACY;
            else if (strcmp (boot_location_str, "both") == 0)
                boot_location = RPMOSTREE_POSTPROCESS_BOOT_LOCATION_BOTH;
            else if (strcmp (boot_location_str, "new") == 0)
                boot_location = RPMOSTREE_POSTPROCESS_BOOT_LOCATION_NEW;
            else
            {
                g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                             "Invalid boot location '%s'", boot_location_str);
                goto out;
            }
        }

        if (!gs_file_ensure_directory (target_usrlib, TRUE, cancellable, error))
            goto out;

        switch (boot_location)
        {
        case RPMOSTREE_POSTPROCESS_BOOT_LOCATION_LEGACY:
        {
            g_print ("Using boot location: legacy\n");
            if (!gs_file_rename (yumroot_boot, target_boot, cancellable, error))
                goto out;
        }
        break;
        case RPMOSTREE_POSTPROCESS_BOOT_LOCATION_BOTH:
        {
            g_print ("Using boot location: both\n");
            if (!gs_file_rename (yumroot_boot, target_boot, cancellable, error))
                goto out;
            /* Hardlink the existing content, only a little ugly as
             * we'll end up sha256'ing it twice, but oh well. */
            if (!gs_shutil_cp_al_or_fallback (target_boot, target_usrlib_ostree_boot, cancellable, error))
                goto out;
        }
        break;
        case RPMOSTREE_POSTPROCESS_BOOT_LOCATION_NEW:
        {
            g_print ("Using boot location: new\n");
            if (!gs_file_rename (yumroot_boot, target_usrlib_ostree_boot, cancellable, error))
                goto out;
        }
        break;
        }
    }

    /* Also carry along toplevel compat links */
    g_print ("Copying toplevel compat symlinks\n");
    {
        guint i;
        const char *toplevel_links[] = { "lib", "lib64", "lib32",
                                         "bin", "sbin"
                                       };
        for (i = 0; i < G_N_ELEMENTS (toplevel_links); i++)
        {
            gs_unref_object GFile *srcpath =
                g_file_get_child (yumroot, toplevel_links[i]);

            if (g_file_query_file_type (srcpath, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL) == G_FILE_TYPE_SYMBOLIC_LINK)
            {
                if (!move_to_dir (srcpath, targetroot, cancellable, error))
                    goto out;
            }
        }
    }

    g_print ("Adding tmpfiles-ostree-integration.conf\n");
    {
        gs_unref_object GFile *src_pkglibdir = g_file_new_for_path (PKGLIBDIR);
        gs_unref_object GFile *src_tmpfilesd =
            g_file_get_child (src_pkglibdir, "tmpfiles-ostree-integration.conf");
        gs_unref_object GFile *target_tmpfilesd =
            g_file_resolve_relative_path (targetroot, "usr/lib/tmpfiles.d/tmpfiles-ostree-integration.conf");
        gs_unref_object GFile *target_tmpfilesd_parent = g_file_get_parent (target_tmpfilesd);

        if (!gs_file_ensure_directory (target_tmpfilesd_parent, TRUE, cancellable, error))
            goto out;

        if (!g_file_copy (src_tmpfilesd, target_tmpfilesd, 0,
                          cancellable, NULL, NULL, error))
            goto out;
    }

    ret = TRUE;
out:
    return ret;
}
Esempio n. 3
0
gboolean
rpmostree_treefile_postprocessing (GFile         *yumroot,
                                   GFile         *context_directory,
                                   GBytes        *serialized_treefile,
                                   JsonObject    *treefile,
                                   GCancellable  *cancellable,
                                   GError       **error)
{
    gboolean ret = FALSE;
    guint i, len;
    JsonArray *units = NULL;
    JsonArray *remove = NULL;
    const char *default_target = NULL;
    const char *postprocess_script = NULL;

    if (json_object_has_member (treefile, "units"))
        units = json_object_get_array_member (treefile, "units");

    if (units)
        len = json_array_get_length (units);
    else
        len = 0;

    {
        gs_unref_object GFile *multiuser_wants_dir =
            g_file_resolve_relative_path (yumroot, "etc/systemd/system/multi-user.target.wants");

        if (!gs_file_ensure_directory (multiuser_wants_dir, TRUE, cancellable, error))
            goto out;

        for (i = 0; i < len; i++)
        {
            const char *unitname = _rpmostree_jsonutil_array_require_string_element (units, i, error);
            gs_unref_object GFile *unit_link_target = NULL;
            gs_free char *symlink_target = NULL;

            if (!unitname)
                goto out;

            symlink_target = g_strconcat ("/usr/lib/systemd/system/", unitname, NULL);
            unit_link_target = g_file_get_child (multiuser_wants_dir, unitname);

            if (g_file_query_file_type (unit_link_target, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL) == G_FILE_TYPE_SYMBOLIC_LINK)
                continue;

            g_print ("Adding %s to multi-user.target.wants\n", unitname);

            if (!g_file_make_symbolic_link (unit_link_target, symlink_target,
                                            cancellable, error))
                goto out;
        }
    }

    {
        gs_unref_object GFile *target_treefile_dir_path =
            g_file_resolve_relative_path (yumroot, "usr/share/rpm-ostree");
        gs_unref_object GFile *target_treefile_path =
            g_file_get_child (target_treefile_dir_path, "treefile.json");
        const guint8 *buf;
        gsize len;

        if (!gs_file_ensure_directory (target_treefile_dir_path, TRUE,
                                       cancellable, error))
            goto out;

        g_print ("Writing '%s'\n", gs_file_get_path_cached (target_treefile_path));
        buf = g_bytes_get_data (serialized_treefile, &len);

        if (!g_file_replace_contents (target_treefile_path, (char*)buf, len,
                                      NULL, FALSE, G_FILE_CREATE_REPLACE_DESTINATION,
                                      NULL, cancellable, error))
            goto out;
    }

    if (!_rpmostree_jsonutil_object_get_optional_string_member (treefile, "default_target",
            &default_target, error))
        goto out;

    if (default_target != NULL)
    {
        gs_unref_object GFile *default_target_path =
            g_file_resolve_relative_path (yumroot, "etc/systemd/system/default.target");
        gs_free char *dest_default_target_path =
            g_strconcat ("/usr/lib/systemd/system/", default_target, NULL);

        (void) gs_file_unlink (default_target_path, NULL, NULL);

        if (!g_file_make_symbolic_link (default_target_path, dest_default_target_path,
                                        cancellable, error))
            goto out;
    }

    if (json_object_has_member (treefile, "remove-files"))
    {
        remove = json_object_get_array_member (treefile, "remove-files");
        len = json_array_get_length (remove);
    }
    else
        len = 0;

    for (i = 0; i < len; i++)
    {
        const char *val = _rpmostree_jsonutil_array_require_string_element (remove, i, error);
        gs_unref_object GFile *child = NULL;

        if (!val)
            return FALSE;
        if (g_path_is_absolute (val))
        {
            g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                         "'remove' elements must be relative");
            goto out;
        }

        child = g_file_resolve_relative_path (yumroot, val);

        if (g_file_query_exists (child, NULL))
        {
            g_print ("Removing '%s'\n", val);
            if (!gs_shutil_rm_rf (child, cancellable, error))
                goto out;
        }
        else
        {
            g_printerr ("warning: Targeted path for remove-files does not exist: %s\n",
                        gs_file_get_path_cached (child));
        }
    }

    if (json_object_has_member (treefile, "remove-from-packages"))
    {
        g_autoptr(RpmOstreeRefSack) refsack = NULL;
        _cleanup_hypackagelist_ HyPackageList pkglist = NULL;
        guint i;

        remove = json_object_get_array_member (treefile, "remove-from-packages");
        len = json_array_get_length (remove);

        if (!rpmostree_get_pkglist_for_root (AT_FDCWD, gs_file_get_path_cached (yumroot),
                                             &refsack, &pkglist,
                                             cancellable, error))
        {
            g_prefix_error (error, "Reading package set: ");
            goto out;
        }

        for (i = 0; i < len; i++)
        {
            JsonArray *elt = json_array_get_array_element (remove, i);
            if (!handle_remove_files_from_package (yumroot, refsack, elt, cancellable, error))
                goto out;
        }
    }

    if (!_rpmostree_jsonutil_object_get_optional_string_member (treefile, "postprocess-script",
            &postprocess_script, error))
        goto out;

    if (postprocess_script)
    {
        const char *yumroot_path = gs_file_get_path_cached (yumroot);
        gs_unref_object GFile *src = g_file_resolve_relative_path (context_directory, postprocess_script);
        const char *bn = gs_file_get_basename_cached (src);
        gs_free char *binpath = g_strconcat ("/usr/bin/rpmostree-postprocess-", bn, NULL);
        gs_free char *destpath = g_strconcat (yumroot_path, binpath, NULL);
        gs_unref_object GFile *dest = g_file_new_for_path (destpath);
        /* Clone all the things */

        if (!g_file_copy (src, dest, 0, cancellable, NULL, NULL, error))
        {
            g_prefix_error (error, "Copying postprocess-script '%s' into target: ", bn);
            goto out;
        }

        g_print ("Executing postprocessing script '%s'\n", bn);

        {
            char *child_argv[] = { binpath, NULL };
            if (!run_sync_in_root (yumroot, binpath, child_argv, error))
            {
                g_prefix_error (error, "While executing postprocessing script '%s': ", bn);
                goto out;
            }
        }

        g_print ("Finished postprocessing script '%s'\n", bn);
    }

    ret = TRUE;
out:
    return ret;
}
int
rpmostree_compose_builtin_tree (int             argc,
                                char          **argv,
                                GCancellable   *cancellable,
                                GError        **error)
{
  int exit_status = EXIT_FAILURE;
  GError *temp_error = NULL;
  GOptionContext *context = g_option_context_new ("TREEFILE - Run yum and commit the result to an OSTree repository");
  RpmOstreeTreeComposeContext selfdata = { NULL, };
  RpmOstreeTreeComposeContext *self = &selfdata;
  JsonNode *treefile_rootval = NULL;
  JsonObject *treefile = NULL;
  g_autofree char *cachekey = NULL;
  g_autofree char *new_inputhash = NULL;
  g_autoptr(GFile) previous_root = NULL;
  g_autofree char *previous_checksum = NULL;
  g_autoptr(GFile) yumroot = NULL;
  g_autoptr(GFile) yumroot_varcache = NULL;
  glnx_fd_close int rootfs_fd = -1;
  glnx_unref_object OstreeRepo *repo = NULL;
  g_autoptr(GPtrArray) bootstrap_packages = NULL;
  g_autoptr(GPtrArray) packages = NULL;
  g_autoptr(GFile) treefile_path = NULL;
  g_autoptr(GFile) treefile_dirpath = NULL;
  g_autoptr(GFile) repo_path = NULL;
  glnx_unref_object JsonParser *treefile_parser = NULL;
  gs_unref_variant_builder GVariantBuilder *metadata_builder = 
    g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
  g_autoptr(RpmOstreeContext) corectx = NULL;
  g_autoptr(GHashTable) varsubsts = NULL;
  gboolean workdir_is_tmp = FALSE;
  g_autofree char *next_version = NULL;

  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,
                                       RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD,
                                       cancellable,
                                       NULL,
                                       error))
    goto out;

  if (argc < 2)
    {
      rpmostree_usage_error (context, "TREEFILE must be specified", error);
      goto out;
    }
  
  if (!opt_repo)
    {
      rpmostree_usage_error (context, "--repo must be specified", error);
      goto out;
    }

  if (getuid () != 0)
    {
      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                   "compose tree must presently be run as uid 0 (root)");
      goto out;
    }

  /* Test whether or not bwrap is going to work - we will fail inside e.g. a Docker
   * container without --privileged or userns exposed.
   */
  if (!rpmostree_bwrap_selftest (error))
    goto out;

  repo_path = g_file_new_for_path (opt_repo);
  repo = self->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
    {
      g_autofree char *tmpd = NULL;

      if (!rpmostree_mkdtemp ("/var/tmp/rpm-ostree.XXXXXX", &tmpd, NULL, error))
        goto out;

      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 (!glnx_opendirat (AT_FDCWD, gs_file_get_path_cached (self->workdir),
                       FALSE, &self->workdir_dfd, error))
    goto out;

  if (opt_cachedir)
    {
      if (!glnx_opendirat (AT_FDCWD, opt_cachedir, TRUE, &self->cachedir_dfd, error))
        {
          g_prefix_error (error, "Opening cachedir '%s': ", opt_cachedir);
          goto out;
        }
    }
  else
    {
      self->cachedir_dfd = fcntl (self->workdir_dfd, F_DUPFD_CLOEXEC, 3);
      if (self->cachedir_dfd < 0)
        {
          glnx_set_error_from_errno (error);
          goto out;
        }
    }

  if (opt_metadata_strings)
    {
      if (!parse_keyvalue_strings (opt_metadata_strings,
                                   metadata_builder, error))
        goto out;
    }

  if (fchdir (self->workdir_dfd) != 0)
    {
      glnx_set_error_from_errno (error);
      goto out;
    }

  corectx = rpmostree_context_new_compose (self->cachedir_dfd, cancellable, error);
  if (!corectx)
    goto out;

  varsubsts = rpmostree_context_get_varsubsts (corectx);

  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)
    {
      glnx_unref_object JsonGenerator *generator = json_generator_new ();
      g_autoptr(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);

      exit_status = EXIT_SUCCESS;
      goto out;
    }

  { const char *input_ref = _rpmostree_jsonutil_object_require_string_member (treefile, "ref", error);
    if (!input_ref)
      goto out;
    self->ref = _rpmostree_varsubst_string (input_ref, varsubsts, error);
    if (!self->ref)
      goto out;
  }

  if (!ostree_repo_read_commit (repo, self->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", self->ref);
        }
      else
        {
          g_propagate_error (error, temp_error);
          goto out;
        }
    }
  else
    g_print ("Previous commit: %s\n", previous_checksum);

  self->previous_checksum = previous_checksum;

  yumroot = g_file_get_child (self->workdir, "rootfs.tmp");
  if (!glnx_shutil_rm_rf_at (self->workdir_dfd, "rootfs.tmp", cancellable, error))
    goto out;

  if (json_object_has_member (treefile, "automatic_version_prefix") &&
      !compose_strv_contains_prefix (opt_metadata_strings, "version="))
    {
      g_autoptr(GVariant) variant = NULL;
      g_autofree char *last_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);
        }

      next_version = _rpmostree_util_next_version (ver_prefix, last_version);
      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_autofree char *thisarch_packages = g_strconcat ("packages-", dnf_context_get_base_arch (rpmostree_context_get_hif (corectx)), NULL);

    if (json_object_has_member (treefile, thisarch_packages))
      {
        if (!_rpmostree_jsonutil_append_string_array_to (treefile, thisarch_packages, packages, error))
          goto out;
      }
  }
  g_ptr_array_add (packages, NULL);

  { glnx_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);
  }

  treefile_dirpath = g_file_get_parent (treefile_path);
  if (TRUE)
    {
      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,
                                                        treefile_dirpath,
                                                        previous_root, treefile,
                                                        cancellable, error))
            goto out;
        }
    }

  { gboolean unmodified = FALSE;

    if (!install_packages_in_root (self, corectx, treefile, yumroot,
                                   (char**)packages->pdata,
                                   opt_force_nocache ? NULL : &unmodified,
                                   &new_inputhash,
                                   cancellable, error))
      goto out;

    if (unmodified)
      {
        g_print ("No apparent changes since previous commit; use --force-nocache to override\n");
        exit_status = EXIT_SUCCESS;
        goto out;
      }
    else if (opt_dry_run)
      {
        g_print ("--dry-run complete, exiting\n");
        exit_status = EXIT_SUCCESS;
        goto out;
      }
  }

  if (g_strcmp0 (g_getenv ("RPM_OSTREE_BREAK"), "post-yum") == 0)
    goto out;

  if (!glnx_opendirat (AT_FDCWD, gs_file_get_path_cached (yumroot), TRUE,
                       &rootfs_fd, error))
    goto out;

  if (!rpmostree_treefile_postprocessing (rootfs_fd, self->treefile_context_dirs->pdata[0],
                                          self->serialized_treefile, treefile,
                                          next_version, cancellable, error))
    goto out;

  if (!rpmostree_prepare_rootfs_for_commit (yumroot, treefile, cancellable, error))
    goto out;

  /* Reopen since the prepare renamed */
  (void) close (rootfs_fd);
  if (!glnx_opendirat (AT_FDCWD, gs_file_get_path_cached (yumroot), TRUE,
                       &rootfs_fd, error))
    goto out;

  if (!rpmostree_copy_additional_files (yumroot, self->treefile_context_dirs->pdata[0], treefile, cancellable, error))
    goto out;

  if (!rpmostree_check_passwd (repo, yumroot, treefile_dirpath, treefile,
                               previous_checksum,
                               cancellable, error))
    goto out;

  if (!rpmostree_check_groups (repo, yumroot, treefile_dirpath, treefile,
                               previous_checksum,
                               cancellable, error))
    goto out;

  {
    const char *gpgkey;
    gboolean selinux = TRUE;
    g_autoptr(GVariant) metadata = NULL;

    g_variant_builder_add (metadata_builder, "{sv}",
                           "rpmostree.inputhash",
                           g_variant_new_string (new_inputhash));

    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_jsonutil_object_get_optional_boolean_member (treefile,
                                                                 "selinux",
                                                                 &selinux,
                                                                 error))
      goto out;

    { g_autofree char *new_revision = NULL;

      if (!rpmostree_commit (rootfs_fd, repo, self->ref, metadata, gpgkey, selinux, NULL,
                             &new_revision,
                             cancellable, error))
        goto out;

      g_print ("%s => %s\n", self->ref, new_revision);

    }
  }

  if (opt_touch_if_changed)
    {
      gs_fd_close int fd = open (opt_touch_if_changed, O_CREAT|O_WRONLY|O_NOCTTY, 0644);
      if (fd == -1)
        {
          gs_set_error_from_errno (error, errno);
          g_prefix_error (error, "Updating '%s': ", opt_touch_if_changed);
          goto out;
        }
      if (futimens (fd, NULL) == -1)
        {
          gs_set_error_from_errno (error, errno);
          goto out;
        }
    }

  exit_status = EXIT_SUCCESS;

 out:
  /* Explicitly close this one now as it may have references to files
   * we delete below.
   */
  g_clear_object (&corectx);
  
  /* Move back out of the workding directory to ensure unmount works */
  (void )chdir ("/");

  if (self->workdir_dfd != -1)
    (void) close (self->workdir_dfd);

  if (workdir_is_tmp)
    {
      if (opt_workdir_tmpfs)
        if (umount (gs_file_get_path_cached (self->workdir)) != 0)
          {
            fprintf (stderr, "warning: umount failed: %m\n");
          }
      (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 exit_status;
}
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;
    }

  {
    g_autoptr(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)
    {
      g_autoptr(GFile) treefile_dirpath = g_file_get_parent (treefile_path);
      g_autoptr(GFile) parent_path = g_file_resolve_relative_path (treefile_dirpath, include_path);
      glnx_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;
}