/**
 * rpmostree_prepare_rootfs_for_commit:
 *
 * Walk over the root filesystem and perform some core conversions
 * from RPM conventions to OSTree conventions.  For example:
 *
 *  * Move /etc to /usr/etc
 *  * Checksum the kernel in /boot
 *  * Migrate content in /var to systemd-tmpfiles
 */
gboolean
rpmostree_prepare_rootfs_for_commit (GFile         *rootfs,
                                     JsonObject    *treefile,
                                     GCancellable  *cancellable,
                                     GError       **error)
{
  gboolean ret = FALSE;
  gs_unref_object GFile *rootfs_tmp = NULL;
  gs_free char *rootfs_tmp_path = NULL;

  rootfs_tmp_path = g_strconcat (gs_file_get_path_cached (rootfs), ".tmp", NULL);
  rootfs_tmp = g_file_new_for_path (rootfs_tmp_path);

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

  if (!create_rootfs_from_yumroot_content (rootfs_tmp, rootfs, treefile,
                                           cancellable, error))
    goto out;

  if (!gs_shutil_rm_rf (rootfs, cancellable, error))
    goto out;
  if (!gs_file_rename (rootfs_tmp, rootfs, cancellable, error))
    goto out;

  ret = TRUE;
 out:
  return ret;
}
static gboolean
cleanup_other_bootversions (OstreeSysroot       *self,
                            GCancellable        *cancellable,
                            GError             **error)
{
    gboolean ret = FALSE;
    int cleanup_bootversion;
    int cleanup_subbootversion;
    gs_unref_object GFile *cleanup_boot_dir = NULL;

    cleanup_bootversion = self->bootversion == 0 ? 1 : 0;
    cleanup_subbootversion = self->subbootversion == 0 ? 1 : 0;

    cleanup_boot_dir = ot_gfile_resolve_path_printf (self->path, "boot/loader.%d", cleanup_bootversion);
    if (!gs_shutil_rm_rf (cleanup_boot_dir, cancellable, error))
        goto out;
    g_clear_object (&cleanup_boot_dir);

    cleanup_boot_dir = ot_gfile_resolve_path_printf (self->path, "ostree/boot.%d", cleanup_bootversion);
    if (!gs_shutil_rm_rf (cleanup_boot_dir, cancellable, error))
        goto out;
    g_clear_object (&cleanup_boot_dir);

    cleanup_boot_dir = ot_gfile_resolve_path_printf (self->path, "ostree/boot.%d.0", cleanup_bootversion);
    if (!gs_shutil_rm_rf (cleanup_boot_dir, cancellable, error))
        goto out;
    g_clear_object (&cleanup_boot_dir);

    cleanup_boot_dir = ot_gfile_resolve_path_printf (self->path, "ostree/boot.%d.1", cleanup_bootversion);
    if (!gs_shutil_rm_rf (cleanup_boot_dir, cancellable, error))
        goto out;
    g_clear_object (&cleanup_boot_dir);

    cleanup_boot_dir = ot_gfile_resolve_path_printf (self->path, "ostree/boot.%d.%d", self->bootversion,
                       cleanup_subbootversion);
    if (!gs_shutil_rm_rf (cleanup_boot_dir, cancellable, error))
        goto out;
    g_clear_object (&cleanup_boot_dir);

    ret = TRUE;
out:
    return ret;
}
static gboolean
do_kernel_prep (GFile         *yumroot,
                JsonObject    *treefile,
                GCancellable  *cancellable,
                GError       **error)
{
    gboolean ret = FALSE;
    gs_unref_object GFile *bootdir =
        g_file_get_child (yumroot, "boot");
    gs_unref_object GFile *kernel_path = NULL;
    gs_unref_object GFile *initramfs_path = NULL;
    const char *boot_checksum_str = NULL;
    GChecksum *boot_checksum = NULL;
    g_autofree char *kver = NULL;

    if (!find_kernel_and_initramfs_in_bootdir (bootdir, &kernel_path,
            &initramfs_path,
            cancellable, error))
        goto out;

    if (kernel_path == NULL)
    {
        gs_unref_object GFile *mod_dir = g_file_resolve_relative_path (yumroot, "usr/lib/modules");
        gs_unref_object GFile *modversion_dir = NULL;

        if (!find_ensure_one_subdirectory (mod_dir, &modversion_dir, cancellable, error))
            goto out;

        if (modversion_dir)
        {
            kver = g_file_get_basename (modversion_dir);
            if (!find_kernel_and_initramfs_in_bootdir (modversion_dir, &kernel_path,
                    &initramfs_path,
                    cancellable, error))
                goto out;
        }
    }

    if (kernel_path == NULL)
    {
        g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                     "Unable to find kernel (vmlinuz) in /boot or /usr/lib/modules");
        goto out;
    }

    if (initramfs_path)
    {
        g_print ("Removing RPM-generated '%s'\n",
                 gs_file_get_path_cached (initramfs_path));
        if (!gs_shutil_rm_rf (initramfs_path, cancellable, error))
            goto out;
    }

    if (!kver)
    {
        const char *kname = gs_file_get_basename_cached (kernel_path);
        const char *kver_p;

        kver_p = strchr (kname, '-');
        g_assert (kver_p);
        kver = g_strdup (kver_p + 1);
    }

    /* OSTree needs to own this */
    {
        gs_unref_object GFile *loaderdir = g_file_get_child (bootdir, "loader");
        if (!gs_shutil_rm_rf (loaderdir, cancellable, error))
            goto out;
    }

    {
        char *child_argv[] = { "depmod", (char*)kver, NULL };
        if (!run_sync_in_root (yumroot, "depmod", child_argv, error))
            goto out;
    }

    /* Ensure the /etc/machine-id file is present and empty. Apparently systemd
       doesn't work when the file is missing (as of systemd-219-9.fc22) but it is
       correctly populated if the file is there.  */
    g_print ("Creating empty machine-id\n");
    {
        const char *hardcoded_machine_id = "";
        gs_unref_object GFile *machineid_path =
            g_file_resolve_relative_path (yumroot, "etc/machine-id");
        if (!g_file_replace_contents (machineid_path, hardcoded_machine_id,
                                      strlen (hardcoded_machine_id),
                                      NULL, FALSE, 0, NULL,
                                      cancellable, error))
            goto out;
    }

    {
        gboolean reproducible;
        gs_unref_ptrarray GPtrArray *dracut_argv = g_ptr_array_new ();

        if (!dracut_supports_reproducible (yumroot, &reproducible, cancellable, error))
            goto out;

        g_ptr_array_add (dracut_argv, "dracut");
        g_ptr_array_add (dracut_argv, "-v");
        if (reproducible)
        {
            g_ptr_array_add (dracut_argv, "--reproducible");
            g_ptr_array_add (dracut_argv, "--gzip");
        }
        g_ptr_array_add (dracut_argv, "--tmpdir=/tmp");
        g_ptr_array_add (dracut_argv, "-f");
        g_ptr_array_add (dracut_argv, "/var/tmp/initramfs.img");
        g_ptr_array_add (dracut_argv, (char*)kver);

        if (json_object_has_member (treefile, "initramfs-args"))
        {
            guint i, len;
            JsonArray *initramfs_args;

            initramfs_args = json_object_get_array_member (treefile, "initramfs-args");
            len = json_array_get_length (initramfs_args);

            for (i = 0; i < len; i++)
            {
                const char *arg = _rpmostree_jsonutil_array_require_string_element (initramfs_args, i, error);
                if (!arg)
                    goto out;
                g_ptr_array_add (dracut_argv, (char*)arg);
            }
        }

        g_ptr_array_add (dracut_argv, NULL);

        if (!run_sync_in_root (yumroot, "dracut", (char**)dracut_argv->pdata, error))
            goto out;
    }

    initramfs_path = g_file_resolve_relative_path (yumroot, "var/tmp/initramfs.img");
    if (!g_file_query_exists (initramfs_path, NULL))
    {
        g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                     "Dracut failed to generate '%s'",
                     gs_file_get_path_cached (initramfs_path));
        goto out;
    }

    {
        gs_free char *initramfs_name = g_strconcat ("initramfs-", kver, ".img", NULL);
        gs_unref_object GFile *initramfs_dest =
            g_file_get_child (bootdir, initramfs_name);

        if (!gs_file_rename (initramfs_path, initramfs_dest,
                             cancellable, error))
            goto out;

        /* Transfer ownership */
        g_object_unref (initramfs_path);
        initramfs_path = initramfs_dest;
        initramfs_dest = NULL;
    }

    boot_checksum = g_checksum_new (G_CHECKSUM_SHA256);
    if (!_rpmostree_util_update_checksum_from_file (boot_checksum, kernel_path,
            cancellable, error))
        goto out;
    if (!_rpmostree_util_update_checksum_from_file (boot_checksum, initramfs_path,
            cancellable, error))
        goto out;

    boot_checksum_str = g_checksum_get_string (boot_checksum);

    {
        gs_free char *new_kernel_name =
            g_strconcat (gs_file_get_basename_cached (kernel_path), "-",
                         boot_checksum_str, NULL);
        gs_unref_object GFile *new_kernel_path =
            g_file_get_child (bootdir, new_kernel_name);
        gs_free char *new_initramfs_name =
            g_strconcat (gs_file_get_basename_cached (initramfs_path), "-",
                         boot_checksum_str, NULL);
        gs_unref_object GFile *new_initramfs_path =
            g_file_get_child (bootdir, new_initramfs_name);

        if (!gs_file_rename (kernel_path, new_kernel_path,
                             cancellable, error))
            goto out;
        if (!gs_file_rename (initramfs_path, new_initramfs_path,
                             cancellable, error))
            goto out;
    }

    ret = TRUE;
out:
    if (boot_checksum) g_checksum_free (boot_checksum);
    return ret;
}
gboolean
rpmostree_commit (GFile         *rootfs,
                  OstreeRepo    *repo,
                  const char    *refname,
                  GVariant      *metadata,
                  const char    *gpg_keyid,
                  gboolean       enable_selinux,
                  GCancellable  *cancellable,
                  GError       **error)
{
    gboolean ret = FALSE;
    gs_unref_object OstreeMutableTree *mtree = NULL;
    OstreeRepoCommitModifier *commit_modifier = NULL;
    gs_free char *parent_revision = NULL;
    gs_free char *new_revision = NULL;
    gs_unref_object GFile *root_tree = NULL;
    gs_unref_object OstreeSePolicy *sepolicy = NULL;
    gs_fd_close int rootfs_fd = -1;

    if (!gs_file_open_dir_fd (rootfs, &rootfs_fd, cancellable, error))
        goto out;

    /* hardcode targeted policy for now */
    if (enable_selinux)
    {
        if (!rpmostree_prepare_rootfs_get_sepolicy (rootfs_fd, ".", &sepolicy, cancellable, error))
            goto out;
    }

    g_print ("Committing '%s' ...\n", gs_file_get_path_cached (rootfs));
    if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error))
        goto out;

    mtree = ostree_mutable_tree_new ();
    commit_modifier = ostree_repo_commit_modifier_new (0, NULL, NULL, NULL);
    ostree_repo_commit_modifier_set_xattr_callback (commit_modifier,
            read_xattrs_cb, NULL,
            GINT_TO_POINTER (rootfs_fd));
    if (sepolicy)
    {
        const char *policy_name = ostree_sepolicy_get_name (sepolicy);
        g_print ("Labeling with SELinux policy '%s'\n", policy_name);
        ostree_repo_commit_modifier_set_sepolicy (commit_modifier, sepolicy);
    }

    if (!ostree_repo_write_directory_to_mtree (repo, rootfs, mtree, commit_modifier, cancellable, error))
        goto out;
    if (!ostree_repo_write_mtree (repo, mtree, &root_tree, cancellable, error))
        goto out;

    if (!ostree_repo_resolve_rev (repo, refname, TRUE, &parent_revision, error))
        goto out;

    if (!ostree_repo_write_commit (repo, parent_revision, "", "", metadata,
                                   (OstreeRepoFile*)root_tree, &new_revision,
                                   cancellable, error))
        goto out;

    if (gpg_keyid)
    {
        g_print ("Signing commit %s with key %s\n", new_revision, gpg_keyid);
        if (!ostree_repo_sign_commit (repo, new_revision, gpg_keyid, NULL,
                                      cancellable, error))
            goto out;
    }

    ostree_repo_transaction_set_ref (repo, NULL, refname, new_revision);

    if (!ostree_repo_commit_transaction (repo, NULL, cancellable, error))
        goto out;

    g_print ("%s => %s\n", refname, new_revision);

    if (!g_getenv ("RPM_OSTREE_PRESERVE_ROOTFS"))
        (void) gs_shutil_rm_rf (rootfs, NULL, NULL);
    else
        g_print ("Preserved %s\n", gs_file_get_path_cached (rootfs));

    ret = TRUE;
out:
    return ret;
}
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;
}
static gboolean
handle_remove_files_from_package (GFile         *yumroot,
                                  RpmOstreeRefSack *refsack,
                                  JsonArray     *removespec,
                                  GCancellable  *cancellable,
                                  GError       **error)
{
    gboolean ret = FALSE;
    const char *pkg = json_array_get_string_element (removespec, 0);
    guint i, j, npackages;
    guint len = json_array_get_length (removespec);
    HyPackage hypkg;
    _cleanup_hyquery_ HyQuery query = NULL;
    _cleanup_hypackagelist_ HyPackageList pkglist = NULL;

    query = hy_query_create (refsack->sack);
    hy_query_filter (query, HY_PKG_NAME, HY_EQ, pkg);
    pkglist = hy_query_run (query);
    npackages = hy_packagelist_count (pkglist);
    if (npackages == 0)
    {
        g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                     "Unable to find package '%s' specified in remove-files-from", pkg);
        goto out;
    }

    for (j = 0; j < npackages; j++)
    {
        _cleanup_hystringarray_ HyStringArray pkg_files = NULL;

        hypkg = hy_packagelist_get (pkglist, 0);
        pkg_files = hy_package_get_files (hypkg);

        for (i = 1; i < len; i++)
        {
            const char *remove_regex_pattern = json_array_get_string_element (removespec, i);
            GRegex *regex;
            char **strviter;

            regex = g_regex_new (remove_regex_pattern, G_REGEX_JAVASCRIPT_COMPAT, 0, error);

            if (!regex)
                goto out;

            for (strviter = pkg_files; strviter && strviter[0]; strviter++)
            {
                const char *file = *strviter;

                if (g_regex_match (regex, file, 0, NULL))
                {
                    gs_unref_object GFile *child = NULL;

                    if (file[0] == '/')
                        file++;

                    child = g_file_resolve_relative_path (yumroot, file);
                    g_print ("Deleting: %s\n", file);
                    if (!gs_shutil_rm_rf (child, cancellable, error))
                        goto out;
                }
            }
        }
    }

    ret = TRUE;
out:
    return ret;
}
static gboolean
cleanup_old_deployments (OstreeSysroot       *self,
                         GCancellable        *cancellable,
                         GError             **error)
{
    gboolean ret = FALSE;
    guint32 root_device;
    guint64 root_inode;
    guint i;
    gs_unref_object GFile *active_root = g_file_new_for_path ("/");
    gs_unref_hashtable GHashTable *active_deployment_dirs = NULL;
    gs_unref_hashtable GHashTable *active_boot_checksums = NULL;
    gs_unref_ptrarray GPtrArray *all_deployment_dirs = NULL;
    gs_unref_ptrarray GPtrArray *all_boot_dirs = NULL;

    if (!_ostree_sysroot_get_devino (active_root, &root_device, &root_inode,
                                     cancellable, error))
        goto out;

    active_deployment_dirs = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, NULL);
    active_boot_checksums = g_hash_table_new_full (g_str_hash, (GEqualFunc)g_str_equal, g_free, NULL);

    for (i = 0; i < self->deployments->len; i++)
    {
        OstreeDeployment *deployment = self->deployments->pdata[i];
        GFile *deployment_path = ostree_sysroot_get_deployment_directory (self, deployment);
        char *bootcsum = g_strdup (ostree_deployment_get_bootcsum (deployment));
        /* Transfer ownership */
        g_hash_table_replace (active_deployment_dirs, deployment_path, deployment_path);
        g_hash_table_replace (active_boot_checksums, bootcsum, bootcsum);
    }

    if (!list_all_deployment_directories (self, &all_deployment_dirs,
                                          cancellable, error))
        goto out;

    for (i = 0; i < all_deployment_dirs->len; i++)
    {
        OstreeDeployment *deployment = all_deployment_dirs->pdata[i];
        gs_unref_object GFile *deployment_path = ostree_sysroot_get_deployment_directory (self, deployment);
        gs_unref_object GFile *origin_path = ostree_sysroot_get_deployment_origin_path (deployment_path);
        if (!g_hash_table_lookup (active_deployment_dirs, deployment_path))
        {
            guint32 device;
            guint64 inode;

            if (!_ostree_sysroot_get_devino (deployment_path, &device, &inode,
                                             cancellable, error))
                goto out;

            /* This shouldn't happen, because higher levels should
             * disallow having the booted deployment not in the active
             * deployment list, but let's be extra safe. */
            if (device == root_device && inode == root_inode)
                continue;

            if (!gs_shutil_rm_rf (deployment_path, cancellable, error))
                goto out;
            if (!gs_shutil_rm_rf (origin_path, cancellable, error))
                goto out;
        }
    }

    if (!list_all_boot_directories (self, &all_boot_dirs,
                                    cancellable, error))
        goto out;

    for (i = 0; i < all_boot_dirs->len; i++)
    {
        GFile *bootdir = all_boot_dirs->pdata[i];
        gs_free char *osname = NULL;
        gs_free char *bootcsum = NULL;

        if (!parse_bootdir_name (gs_file_get_basename_cached (bootdir),
                                 &osname, &bootcsum))
            g_assert_not_reached ();

        if (g_hash_table_lookup (active_boot_checksums, bootcsum))
            continue;

        if (!gs_shutil_rm_rf (bootdir, cancellable, error))
            goto out;
    }

    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
do_kernel_prep (GFile         *yumroot,
                JsonObject    *treefile,
                GCancellable  *cancellable,
                GError       **error)
{
  gboolean ret = FALSE;
  gs_unref_object GFile *bootdir = 
    g_file_get_child (yumroot, "boot");
  gs_unref_object GFile *kernel_path = NULL;
  gs_unref_object GFile *initramfs_path = NULL;
  const char *boot_checksum_str = NULL;
  GChecksum *boot_checksum = NULL;
  const char *kname;
  const char *kver;

  if (!find_kernel_and_initramfs_in_bootdir (bootdir, &kernel_path,
                                             &initramfs_path,
                                             cancellable, error))
    goto out;

  if (initramfs_path)
    {
      g_print ("Removing RPM-generated '%s'\n",
               gs_file_get_path_cached (initramfs_path));
      if (!gs_shutil_rm_rf (initramfs_path, cancellable, error))
        goto out;
    }

  kname = gs_file_get_basename_cached (kernel_path);
  kver = strchr (kname, '-');
  g_assert (kver);
  kver += 1;

  /* OSTree needs to own this */
  {
    gs_unref_object GFile *loaderdir = g_file_get_child (bootdir, "loader");
    if (!gs_shutil_rm_rf (loaderdir, cancellable, error))
      goto out;
  }

  {
    char *child_argv[] = { "depmod", (char*)kver, NULL };
    if (!run_sync_in_root (yumroot, "/usr/sbin/depmod", child_argv, error))
      goto out;
  }

  /* Copy of code from gnome-continuous; yes, we hardcode
     the machine id for now, because distributing pre-generated
     initramfs images with dracut/systemd at the moment
     effectively requires this.
     http://lists.freedesktop.org/archives/systemd-devel/2013-July/011770.html
  */
  g_print ("Hardcoding machine-id\n");
  {
    const char *hardcoded_machine_id = "45bb3b96146aa94f299b9eb43646eb35\n";
    gs_unref_object GFile *machineid_path =
      g_file_resolve_relative_path (yumroot, "etc/machine-id");
    if (!g_file_replace_contents (machineid_path, hardcoded_machine_id,
                                  strlen (hardcoded_machine_id),
                                  NULL, FALSE, 0, NULL,
                                  cancellable, error))
      goto out;
  }

  {
    gs_unref_ptrarray GPtrArray *dracut_argv = g_ptr_array_new ();

    g_ptr_array_add (dracut_argv, "dracut");
    g_ptr_array_add (dracut_argv, "-v");
    g_ptr_array_add (dracut_argv, "--tmpdir=/tmp");
    g_ptr_array_add (dracut_argv, "-f");
    g_ptr_array_add (dracut_argv, "/var/tmp/initramfs.img");
    g_ptr_array_add (dracut_argv, (char*)kver);

    if (json_object_has_member (treefile, "initramfs-args"))
      {
        guint i, len;
        JsonArray *initramfs_args;

        initramfs_args = json_object_get_array_member (treefile, "initramfs-args");
        len = json_array_get_length (initramfs_args);

        for (i = 0; i < len; i++)
          {
            const char *arg = _rpmostree_jsonutil_array_require_string_element (initramfs_args, i, error);
            if (!arg)
              goto out;
            g_ptr_array_add (dracut_argv, (char*)arg);
          }
      }

    g_ptr_array_add (dracut_argv, NULL);

    if (!run_sync_in_root (yumroot, "/usr/sbin/dracut", (char**)dracut_argv->pdata, error))
      goto out;
  }

  initramfs_path = g_file_resolve_relative_path (yumroot, "var/tmp/initramfs.img");
  if (!g_file_query_exists (initramfs_path, NULL))
    {
      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                   "Dracut failed to generate '%s'",
                   gs_file_get_path_cached (initramfs_path));
      goto out;
    }

  {
    gs_free char *initramfs_name = g_strconcat ("initramfs-", kver, ".img", NULL);
    gs_unref_object GFile *initramfs_dest =
      g_file_get_child (bootdir, initramfs_name);

    if (!gs_file_rename (initramfs_path, initramfs_dest,
                         cancellable, error))
      goto out;

    /* Transfer ownership */
    g_object_unref (initramfs_path);
    initramfs_path = initramfs_dest;
    initramfs_dest = NULL;
  }

  boot_checksum = g_checksum_new (G_CHECKSUM_SHA256);
  if (!_rpmostree_util_update_checksum_from_file (boot_checksum, kernel_path,
                                                  cancellable, error))
    goto out;
  if (!_rpmostree_util_update_checksum_from_file (boot_checksum, initramfs_path,
                                                  cancellable, error))
    goto out;

  boot_checksum_str = g_checksum_get_string (boot_checksum);
  
  {
    gs_free char *new_kernel_name =
      g_strconcat (gs_file_get_basename_cached (kernel_path), "-",
                   boot_checksum_str, NULL);
    gs_unref_object GFile *new_kernel_path =
      g_file_get_child (bootdir, new_kernel_name);
    gs_free char *new_initramfs_name =
      g_strconcat (gs_file_get_basename_cached (initramfs_path), "-",
                   boot_checksum_str, NULL);
    gs_unref_object GFile *new_initramfs_path =
      g_file_get_child (bootdir, new_initramfs_name);

    if (!gs_file_rename (kernel_path, new_kernel_path,
                         cancellable, error))
      goto out;
    if (!gs_file_rename (initramfs_path, new_initramfs_path,
                         cancellable, error))
      goto out;
  }

  ret = TRUE;
 out:
  if (boot_checksum) g_checksum_free (boot_checksum);
  return ret;
}
Beispiel #10
0
static gboolean
git_mirror_submodules (const char     *repo_location,
                       gboolean        update,
                       GFile          *mirror_dir,
                       const char     *revision,
                       BuilderContext *context,
                       GError        **error)
{
  g_autofree char *mirror_dir_path = NULL;

  g_autoptr(GFile) checkout_dir_template = NULL;
  g_autoptr(GFile) checkout_dir = NULL;
  g_autofree char *checkout_dir_path = NULL;
  g_autofree char *submodule_status = NULL;

  mirror_dir_path = g_file_get_path (mirror_dir);

  checkout_dir_template = g_file_get_child (builder_context_get_state_dir (context),
                                            "tmp-checkout-XXXXXX");
  checkout_dir_path = g_file_get_path (checkout_dir_template);

  if (g_mkdtemp (checkout_dir_path) == NULL)
    {
      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Can't create temporary checkout directory");
      return FALSE;
    }

  checkout_dir = g_file_new_for_path (checkout_dir_path);

  if (!git (NULL, NULL, error,
            "clone", "-q", "--no-checkout", mirror_dir_path, checkout_dir_path, NULL))
    return FALSE;

  if (!git (checkout_dir, NULL, error, "checkout", "-q", "-f", revision, NULL))
    return FALSE;

  if (!git (checkout_dir, &submodule_status, error,
            "submodule", "status", NULL))
    return FALSE;

  if (submodule_status)
    {
      int i;
      g_auto(GStrv) lines = g_strsplit (submodule_status, "\n", -1);
      for (i = 0; lines[i] != NULL; i++)
        {
          g_autofree char *url = NULL;
          g_autofree char *option = NULL;
          g_autofree char *old = NULL;
          g_auto(GStrv) words = NULL;
          if (*lines[i] == 0)
            continue;
          words = g_strsplit (lines[i] + 1, " ", 3);

          option = g_strdup_printf ("submodule.%s.url", words[1]);
          if (!git (checkout_dir, &url, error,
                    "config", "-f", ".gitmodules", option, NULL))
            return FALSE;
          /* Trim trailing whitespace */
          g_strchomp (url);

          old = url;
          url = make_absolute (repo_location, old, error);
          if (url == NULL)
            return FALSE;

          if (!git_mirror_repo (url, update, words[0], context, error))
            return FALSE;
        }
    }

  if (!gs_shutil_rm_rf (checkout_dir, NULL, error))
    return FALSE;

  return TRUE;
}
gboolean
flatpak_builtin_build_init (int argc, char **argv, GCancellable *cancellable, GError **error)
{
  g_autoptr(GOptionContext) context = NULL;
  g_autoptr(GFile) var_deploy_base = NULL;
  g_autoptr(GFile) var_deploy_files = NULL;
  g_autoptr(GFile) base = NULL;
  g_autoptr(GFile) files_dir = NULL;
  g_autoptr(GFile) usr_dir = NULL;
  g_autoptr(GFile) var_dir = NULL;
  g_autoptr(GFile) var_tmp_dir = NULL;
  g_autoptr(GFile) var_run_dir = NULL;
  g_autoptr(GFile) metadata_file = NULL;
  g_autoptr(GString) metadata_contents = NULL;
  const char *app_id;
  const char *directory;
  const char *sdk;
  const char *runtime;
  const char *branch = "master";
  g_autofree char *runtime_ref = NULL;
  g_autofree char *var_ref = NULL;
  g_autofree char *sdk_ref = NULL;
  int i;

  context = g_option_context_new ("DIRECTORY APPNAME SDK RUNTIME [BRANCH] - Initialize a directory for building");

  if (!flatpak_option_context_parse (context, options, &argc, &argv, FLATPAK_BUILTIN_FLAG_NO_DIR, NULL, cancellable, error))
    return FALSE;

  if (argc < 5)
    return usage_error (context, "RUNTIME must be specified", error);

  directory = argv[1];
  app_id = argv[2];
  sdk = argv[3];
  runtime = argv[4];
  if (argc >= 6)
    branch = argv[5];

  if (!flatpak_is_valid_name (app_id))
    return flatpak_fail (error, "'%s' is not a valid application name", app_id);

  if (!flatpak_is_valid_name (runtime))
    return flatpak_fail (error, "'%s' is not a valid runtime name", runtime);

  if (!flatpak_is_valid_name (sdk))
    return flatpak_fail (error, "'%s' is not a valid sdk name", sdk);

  if (!flatpak_is_valid_branch (branch))
    return flatpak_fail (error, "'%s' is not a valid branch name", branch);

  runtime_ref = flatpak_build_untyped_ref (runtime, branch, opt_arch);
  sdk_ref = flatpak_build_untyped_ref (sdk, branch, opt_arch);

  base = g_file_new_for_commandline_arg (directory);

  if (!gs_file_ensure_directory (base, TRUE, cancellable, error))
    return FALSE;

  files_dir = g_file_get_child (base, "files");
  if (opt_sdk_dir)
    usr_dir = g_file_get_child (base, opt_sdk_dir);
  else
    usr_dir = g_file_get_child (base, "usr");
  var_dir = g_file_get_child (base, "var");
  var_tmp_dir = g_file_get_child (var_dir, "tmp");
  var_run_dir = g_file_get_child (var_dir, "run");
  metadata_file = g_file_get_child (base, "metadata");

  if (!opt_update &&
      g_file_query_exists (files_dir, cancellable))
    return flatpak_fail (error, "Build directory %s already initialized", directory);

  if (opt_writable_sdk)
    {
      g_autofree char *full_sdk_ref = g_strconcat ("runtime/", sdk_ref, NULL);
      g_autoptr(GError) my_error = NULL;
      g_autoptr(GFile) sdk_deploy_files = NULL;
      g_autoptr(FlatpakDeploy) sdk_deploy = NULL;

      sdk_deploy = flatpak_find_deploy_for_ref (full_sdk_ref, cancellable, error);
      if (sdk_deploy == NULL)
        return FALSE;

      if (!gs_shutil_rm_rf (usr_dir, NULL, &my_error))
        {
          if (!g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
            {
              g_propagate_error (error, g_steal_pointer (&my_error));
              return FALSE;
            }

          g_clear_error (&my_error);
        }

      sdk_deploy_files = flatpak_deploy_get_files (sdk_deploy);
      if (!flatpak_cp_a (sdk_deploy_files, usr_dir, FLATPAK_CP_FLAGS_NO_CHOWN, cancellable, error))
        return FALSE;

      if (opt_sdk_extensions)
        {
          g_autoptr(GKeyFile) metakey = flatpak_deploy_get_metadata (sdk_deploy);
          GList *extensions = NULL, *l;

          /* We leak this on failure, as we have no autoptr for deep lists.. */
          extensions = flatpak_list_extensions (metakey,
                                                opt_arch,
                                                branch);

          for (i = 0; opt_sdk_extensions[i] != NULL; i++)
            {
              const char *requested_extension = opt_sdk_extensions[i];
              gboolean found = FALSE;

              for (l = extensions; l != NULL; l = l->next)
                {
                  FlatpakExtension *ext = l->data;

                  if (strcmp (ext->installed_id, requested_extension) == 0 ||
                      strcmp (ext->id, requested_extension) == 0)
                    {
                      g_autoptr(GFile) ext_deploy_dir = flatpak_find_deploy_dir_for_ref (ext->ref, cancellable, NULL);
                      if (ext_deploy_dir != NULL)
                        {
                          g_autoptr(GFile) ext_deploy_files = g_file_get_child (ext_deploy_dir, "files");
                          g_autoptr(GFile) target = g_file_resolve_relative_path (usr_dir, ext->directory);
                          g_autoptr(GFile) target_parent = g_file_get_parent (target);

                          if (!gs_file_ensure_directory (target_parent, TRUE, cancellable, error))
                            return FALSE;

                          /* An extension overrides whatever is there before, so we clean up first */
                          if (!gs_shutil_rm_rf (target, cancellable, error))
                            return FALSE;

                          if (!flatpak_cp_a (ext_deploy_files, target, FLATPAK_CP_FLAGS_NO_CHOWN, cancellable, error))
                            return FALSE;

                          found = TRUE;
                        }
                      else
                        {
                          g_list_free_full (extensions, (GDestroyNotify) flatpak_extension_free);
                          return flatpak_fail (error, "Requested extension %s not installed\n", requested_extension);
                        }
                    }
                }

              if (!found)
                return flatpak_fail (error, "No extension %s in sdk\n", requested_extension);
            }
          g_list_free_full (extensions, (GDestroyNotify) flatpak_extension_free);
        }
    }

  if (opt_var)
    {
      var_ref = flatpak_build_runtime_ref (opt_var, branch, opt_arch);

      var_deploy_base = flatpak_find_deploy_dir_for_ref (var_ref, cancellable, error);
      if (var_deploy_base == NULL)
        return FALSE;

      var_deploy_files = g_file_get_child (var_deploy_base, "files");
    }

  if (opt_update)
    return TRUE;

  if (!g_file_make_directory (files_dir, cancellable, error))
    return FALSE;

  if (var_deploy_files)
    {
      if (!gs_shutil_cp_a (var_deploy_files, var_dir, cancellable, error))
        return FALSE;
    }
  else
    {
      if (!g_file_make_directory (var_dir, cancellable, error))
        return FALSE;
    }

  if (!gs_file_ensure_directory (var_tmp_dir, FALSE, cancellable, error))
    return FALSE;

  if (!g_file_query_exists (var_run_dir, cancellable) &&
      !g_file_make_symbolic_link (var_run_dir, "/run", cancellable, error))
    return FALSE;


  metadata_contents = g_string_new ("[Application]\n");
  g_string_append_printf (metadata_contents,
                          "name=%s\n"
                          "runtime=%s\n"
                          "sdk=%s\n",
                          app_id, runtime_ref, sdk_ref);
  if (opt_tags != NULL)
    {
      g_string_append (metadata_contents, "tags=");
      for (i = 0; opt_tags[i] != NULL; i++)
        {
          g_string_append (metadata_contents, opt_tags[i]);
          g_string_append_c (metadata_contents, ';');
        }
      g_string_append_c (metadata_contents, '\n');
    }

  if (!g_file_replace_contents (metadata_file,
                                metadata_contents->str, metadata_contents->len, NULL, FALSE,
                                G_FILE_CREATE_REPLACE_DESTINATION,
                                NULL, cancellable, error))
    return FALSE;

  return TRUE;
}
gboolean
install_bundle (XdgAppDir *dir,
                GOptionContext *context,
                int argc, char **argv,
                GCancellable *cancellable,
                GError **error)
{
  gboolean ret = FALSE;
  g_autoptr(GFile) deploy_base = NULL;
  g_autoptr(GFile) file = NULL;
  const char *filename;
  g_autofree char *ref = NULL;
  g_autofree char *origin = NULL;
  gboolean created_deploy_base = FALSE;
  gboolean added_remote = FALSE;
  g_autofree char *to_checksum = NULL;
  g_auto(GStrv) parts = NULL;
  g_autoptr(GBytes) gpg_data = NULL;
  g_autofree char *remote = NULL;
  OstreeRepo *repo;
  g_auto(GLnxLockFile) lock = GLNX_LOCK_FILE_INIT;
  g_autoptr(GVariant) metadata = NULL;
  g_autofree char *basename = NULL;

  if (argc < 2)
    return usage_error (context, "bundle filename must be specified", error);

  filename = argv[1];

  repo = xdg_app_dir_get_repo (dir);

  file = g_file_new_for_commandline_arg (filename);

  metadata = xdg_app_bundle_load (file, &to_checksum,
                                  &ref,
                                  &origin,
                                  NULL,
                                  &gpg_data,
                                  error);
  if (metadata == NULL)
    return FALSE;

  if (opt_gpg_file != NULL)
    {
      /* Override gpg_data from file */
      gpg_data = read_gpg_data (cancellable, error);
      if (gpg_data == NULL)
        return FALSE;
    }

  parts = xdg_app_decompose_ref (ref, error);
  if (parts == NULL)
    return FALSE;

  deploy_base = xdg_app_dir_get_deploy_dir (dir, ref);

  if (g_file_query_exists (deploy_base, cancellable))
    return xdg_app_fail (error, "%s branch %s already installed", parts[1], parts[3]);

  /* Add a remote for later updates */
  basename = g_file_get_basename (file);
  remote = xdg_app_dir_create_origin_remote (dir,
                                             origin,
                                             parts[1],
                                             basename,
                                             gpg_data,
                                             cancellable,
                                             error);
  if (remote == NULL)
    return FALSE;

  /* From here we need to goto out on error, to clean up */
  added_remote = TRUE;

  if (!xdg_app_dir_pull_from_bundle (dir,
                                     file,
                                     remote,
                                     ref,
                                     gpg_data != NULL,
                                     cancellable,
                                     error))
    goto out;

  if (!xdg_app_dir_lock (dir, &lock,
                         cancellable, error))
    return FALSE;

  if (!g_file_make_directory_with_parents (deploy_base, cancellable, error))
    goto out;

  created_deploy_base = TRUE;

  if (!xdg_app_dir_set_origin (dir, ref, remote, cancellable, error))
    goto out;

  if (!xdg_app_dir_deploy (dir, ref, to_checksum, cancellable, error))
    goto out;

  if (strcmp (parts[0], "app") == 0)
    {
      if (!xdg_app_dir_make_current_ref (dir, ref, cancellable, error))
        goto out;

      if (!xdg_app_dir_update_exports (dir, parts[1], cancellable, error))
        goto out;
    }

  glnx_release_lock_file (&lock);

  xdg_app_dir_cleanup_removed (dir, cancellable, NULL);

  if (!xdg_app_dir_mark_changed (dir, error))
    goto out;

  ret = TRUE;

 out:
  if (created_deploy_base && !ret)
    gs_shutil_rm_rf (deploy_base, cancellable, NULL);

  if (added_remote && !ret)
    ostree_repo_remote_delete (repo, remote, NULL, NULL);

  return ret;
}
gboolean
xdg_app_builtin_install (int argc, char **argv, GCancellable *cancellable, GError **error)
{
  gboolean ret = FALSE;
  g_autoptr(GOptionContext) context = NULL;
  g_autoptr(XdgAppDir) dir = NULL;
  g_autoptr(GFile) deploy_base = NULL;
  const char *repository;
  const char *name;
  const char *branch = NULL;
  g_autofree char *ref = NULL;
  g_autofree char *installed_ref = NULL;
  gboolean is_app;
  gboolean created_deploy_base = FALSE;
  g_auto(GLnxLockFile) lock = GLNX_LOCK_FILE_INIT;
  g_autoptr(GError) my_error = NULL;

  context = g_option_context_new ("REPOSITORY NAME [BRANCH] - Install an application or runtime");

  if (!xdg_app_option_context_parse (context, options, &argc, &argv, 0, &dir, cancellable, error))
    return FALSE;

  if (opt_bundle)
    return install_bundle (dir, context, argc, argv, cancellable, error);

  if (argc < 3)
    return usage_error (context, "REPOSITORY and NAME must be specified", error);

  repository = argv[1];
  name  = argv[2];
  if (argc >= 4)
    branch = argv[3];

  if (!opt_app && !opt_runtime)
    opt_app = opt_runtime = TRUE;

  installed_ref = xdg_app_dir_find_installed_ref (dir,
                                                  name,
                                                  branch,
                                                  opt_arch,
                                                  opt_app, opt_runtime, &is_app,
                                                  &my_error);
  if (installed_ref != NULL)
    {
      return xdg_app_fail (error, "%s %s, branch %s is already installed",
                           is_app ? "App" : "Runtime", name, branch ? branch : "master");
    }

  if (!g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
    {
      g_propagate_error (error, g_steal_pointer (&my_error));
      return FALSE;
    }

  ref = xdg_app_dir_find_remote_ref (dir, repository, name, branch, opt_arch,
                                     opt_app, opt_runtime, &is_app, cancellable, error);
  if (ref == NULL)
    return FALSE;

  deploy_base = xdg_app_dir_get_deploy_dir (dir, ref);
  if (g_file_query_exists (deploy_base, cancellable))
    return xdg_app_fail (error, "Ref %s already deployed", ref);

  if (!opt_no_pull)
    {
      if (!xdg_app_dir_pull (dir, repository, ref, NULL,
                             cancellable, error))
        return FALSE;
    }

  /* After we create the deploy base we must goto out on errors */

  if (!opt_no_deploy)
    {
      if (!xdg_app_dir_lock (dir, &lock,
                             cancellable, error))
        goto out;

      if (!g_file_make_directory_with_parents (deploy_base, cancellable, error))
        goto out;
      created_deploy_base = TRUE;

      if (!xdg_app_dir_set_origin (dir, ref, repository, cancellable, error))
        goto out;

      if (!xdg_app_dir_deploy (dir, ref, NULL, cancellable, error))
        goto out;

      if (is_app)
        {
          if (!xdg_app_dir_make_current_ref (dir, ref, cancellable, error))
            goto out;

          if (!xdg_app_dir_update_exports (dir, name, cancellable, error))
            goto out;
        }

      glnx_release_lock_file (&lock);
    }

  xdg_app_dir_cleanup_removed (dir, cancellable, NULL);

  if (!xdg_app_dir_mark_changed (dir, error))
    goto out;

  ret = TRUE;

 out:
  if (created_deploy_base && !ret)
    gs_shutil_rm_rf (deploy_base, cancellable, NULL);

  return ret;
}
static gboolean
migrate_rpm_and_yumdb (GFile          *targetroot,
                       GFile          *yumroot,
                       GCancellable   *cancellable,
                       GError        **error)

{
    gboolean ret = FALSE;
    gs_unref_object GFile *usrbin_rpm =
        g_file_resolve_relative_path (targetroot, "usr/bin/rpm");
    gs_unref_object GFile *legacyrpm_path =
        g_file_resolve_relative_path (yumroot, "var/lib/rpm");
    gs_unref_object GFile *newrpm_path =
        g_file_resolve_relative_path (targetroot, "usr/share/rpm");
    gs_unref_object GFile *src_yum_rpmdb_indexes =
        g_file_resolve_relative_path (yumroot, "var/lib/yum");
    gs_unref_object GFile *target_yum_rpmdb_indexes =
        g_file_resolve_relative_path (targetroot, "usr/share/yumdb");
    gs_unref_object GFile *yumroot_yumlib =
        g_file_get_child (yumroot, "var/lib/yum");
    gs_unref_object GFileEnumerator *direnum = NULL;

    direnum = g_file_enumerate_children (legacyrpm_path, "standard::name",
                                         G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
                                         cancellable, error);
    if (!direnum)
        goto out;

    while (TRUE)
    {
        const char *name;
        GFileInfo *file_info;
        GFile *child;

        if (!gs_file_enumerator_iterate (direnum, &file_info, &child,
                                         cancellable, error))
            goto out;
        if (!file_info)
            break;

        name = g_file_info_get_name (file_info);

        if (g_str_has_prefix (name, "__db.") ||
                strcmp (name, ".dbenv.lock") == 0 ||
                strcmp (name, ".rpm.lock") == 0)
        {
            if (!gs_file_unlink (child, cancellable, error))
                goto out;
        }
    }

    (void) g_file_enumerator_close (direnum, cancellable, error);

    g_print ("Placing RPM db in /usr/share/rpm\n");
    if (!gs_file_rename (legacyrpm_path, newrpm_path, cancellable, error))
        goto out;

    /* Remove /var/lib/yum; we don't want it here. */
    if (!gs_shutil_rm_rf (yumroot_yumlib, cancellable, error))
        goto out;

    ret = TRUE;
out:
    return ret;
}
static gboolean
migrate_rpm_and_yumdb (GFile          *targetroot,
                       GFile          *yumroot,
                       GCancellable   *cancellable,
                       GError        **error)

{
  gboolean ret = FALSE;
  gs_unref_object GFile *usrbin_rpm =
    g_file_resolve_relative_path (targetroot, "usr/bin/rpm");
  gs_unref_object GFile *legacyrpm_path =
    g_file_resolve_relative_path (yumroot, "var/lib/rpm");
  gs_unref_object GFile *newrpm_path =
    g_file_resolve_relative_path (targetroot, "usr/share/rpm");
  gs_unref_object GFile *src_yum_rpmdb_indexes =
    g_file_resolve_relative_path (yumroot, "var/lib/yum");
  gs_unref_object GFile *target_yum_rpmdb_indexes =
    g_file_resolve_relative_path (targetroot, "usr/share/yumdb");
  gs_unref_object GFile *yumroot_yumlib =
    g_file_get_child (yumroot, "var/lib/yum");
  gs_unref_object GFileEnumerator *direnum = NULL;

  direnum = g_file_enumerate_children (legacyrpm_path, "standard::name",
                                       G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
                                       cancellable, error);
  if (!direnum)
    goto out;

  while (TRUE)
    {
      const char *name;
      GFileInfo *file_info;
      GFile *child;

      if (!gs_file_enumerator_iterate (direnum, &file_info, &child,
                                       cancellable, error))
        goto out;
      if (!file_info)
        break;

      name = g_file_info_get_name (file_info);

      if (g_str_has_prefix (name, "__db.") ||
          strcmp (name, ".dbenv.lock") == 0 ||
          strcmp (name, ".rpm.lock") == 0)
        {
          if (!gs_file_unlink (child, cancellable, error))
            goto out;
        }
    }

  (void) g_file_enumerator_close (direnum, cancellable, error);
    
  g_print ("Placing RPM db in /usr/share/rpm\n");
  if (!gs_file_rename (legacyrpm_path, newrpm_path, cancellable, error))
    goto out;
    
  /* Move the yum database to usr/share/yumdb; disabled for now due
   * to bad conflict with OSTree's current
   * one-http-request-per-file.
   */
#if 0
  if (g_file_query_exists (src_yum_rpmdb_indexes, NULL))
    {
      g_print ("Moving %s to %s\n", gs_file_get_path_cached (src_yum_rpmdb_indexes),
               gs_file_get_path_cached (target_yum_rpmdb_indexes));
      if (!gs_file_rename (src_yum_rpmdb_indexes, target_yum_rpmdb_indexes,
                           cancellable, error))
        goto out;
        
      if (!clean_yumdb_extraneous_files (target_yum_rpmdb_indexes, cancellable, error))
        goto out;
    }
#endif

  /* Remove /var/lib/yum; we don't want it here. */
  if (!gs_shutil_rm_rf (yumroot_yumlib, cancellable, error))
    goto out;

  ret = TRUE;
 out:
  return ret;
}
/* 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;
}
gboolean
install_bundle (XdgAppDir *dir,
                GOptionContext *context,
                int argc, char **argv,
                GCancellable *cancellable,
                GError **error)
{
  gboolean ret = FALSE;
  g_autoptr(GFile) deploy_base = NULL;
  g_autoptr(GFile) file = NULL;
  g_autoptr(GFile) gpg_tmp_file = NULL;
  const char *filename;
  g_autofree char *ref = NULL;
  g_autofree char *origin = NULL;
  gboolean created_deploy_base = FALSE;
  gboolean added_remote = FALSE;
  g_autofree char *to_checksum = NULL;
  g_auto(GStrv) parts = NULL;
  g_autoptr(GBytes) gpg_data = NULL;
  g_autofree char *remote = NULL;
  OstreeRepo *repo;
  g_autoptr(OstreeGpgVerifyResult) gpg_result = NULL;
  g_autoptr(GError) my_error = NULL;
  g_auto(GLnxLockFile) lock = GLNX_LOCK_FILE_INIT;

  if (argc < 2)
    return usage_error (context, "bundle filename must be specified", error);

  filename = argv[1];

  repo = xdg_app_dir_get_repo (dir);

  if (!xdg_app_supports_bundles (repo))
    return xdg_app_fail (error, "Your version of ostree is too old to support single-file bundles");

  if (!xdg_app_dir_lock (dir, &lock,
                         cancellable, error))
    goto out;

  file = g_file_new_for_commandline_arg (filename);

  {
    g_autoptr(GVariant) delta = NULL;
    g_autoptr(GVariant) metadata = NULL;
    g_autoptr(GBytes) bytes = NULL;
    g_autoptr(GVariant) to_csum_v = NULL;
    g_autoptr(GVariant) gpg_value = NULL;

    GMappedFile *mfile = g_mapped_file_new (gs_file_get_path_cached (file), FALSE, error);

    if (mfile == NULL)
      return FALSE;

    bytes = g_mapped_file_get_bytes (mfile);
    g_mapped_file_unref (mfile);

    delta = g_variant_new_from_bytes (G_VARIANT_TYPE (OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT), bytes, FALSE);
    g_variant_ref_sink (delta);

    to_csum_v = g_variant_get_child_value (delta, 3);
    if (!ostree_validate_structureof_csum_v (to_csum_v, error))
      return FALSE;

    to_checksum = ostree_checksum_from_bytes_v (to_csum_v);

    metadata = g_variant_get_child_value (delta, 0);

    if (!g_variant_lookup (metadata, "ref", "s", &ref))
      return xdg_app_fail (error, "Invalid bundle, no ref in metadata");

    if (!g_variant_lookup (metadata, "origin", "s", &origin))
      origin = NULL;

    gpg_value = g_variant_lookup_value (metadata, "gpg-keys", G_VARIANT_TYPE("ay"));
    if (gpg_value)
      {
        gsize n_elements;
        const char *data = g_variant_get_fixed_array (gpg_value, &n_elements, 1);

        gpg_data = g_bytes_new (data, n_elements);
      }
  }

  parts = xdg_app_decompose_ref (ref, error);
  if (parts == NULL)
    return FALSE;

  deploy_base = xdg_app_dir_get_deploy_dir (dir, ref);
  if (g_file_query_exists (deploy_base, cancellable))
    return xdg_app_fail (error, "%s branch %s already installed", parts[1], parts[3]);

  if (opt_gpg_file != NULL)
    {
      /* Override gpg_data from file */
      gpg_data = read_gpg_data (cancellable, error);
      if (gpg_data == NULL)
        return FALSE;
    }

  /* Add a remote for later updates */
  if (origin != NULL)
    {
      g_auto(GStrv) remotes = ostree_repo_remote_list (repo, NULL);
      int version = 0;

      do
        {
          g_autofree char *name = NULL;
          if (version == 0)
            name = g_strdup_printf ("%s-origin", parts[1]);
          else
            name = g_strdup_printf ("%s-%d-origin", parts[1], version);
          version++;

          if (remotes == NULL ||
              !g_strv_contains ((const char * const *) remotes, name))
            remote = g_steal_pointer (&name);
        }
      while (remote == NULL);
    }

  if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error))
    return FALSE;

  ostree_repo_transaction_set_ref (repo, remote, ref, to_checksum);

  if (!ostree_repo_static_delta_execute_offline (repo,
                                                 file,
                                                 FALSE,
                                                 cancellable,
                                                 error))
    return FALSE;

  if (gpg_data)
    {
      g_autoptr(GFileIOStream) stream;
      GOutputStream *o;

      gpg_tmp_file = g_file_new_tmp (".xdg-app-XXXXXX", &stream, error);
      if (gpg_tmp_file == NULL)
        return FALSE;
      o = g_io_stream_get_output_stream (G_IO_STREAM (stream));
      if (!g_output_stream_write_all (o, g_bytes_get_data (gpg_data, NULL), g_bytes_get_size (gpg_data), NULL, cancellable, error))
        return FALSE;
    }

  gpg_result = ostree_repo_verify_commit_ext (repo,
                                              to_checksum,
                                              NULL, gpg_tmp_file, cancellable, &my_error);

  if (gpg_tmp_file)
    g_file_delete (gpg_tmp_file, cancellable, NULL);

  if (gpg_result == NULL)
    {
      /* NOT_FOUND means no gpg signature, we ignore this *if* there
       * is no gpg key specified in the bundle or by the user */
      if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) &&
          gpg_data == NULL)
        g_clear_error (&my_error);
      else
        {
          g_propagate_error (error, g_steal_pointer (&my_error));
          return FALSE;
        }
    }
  else
    {
      /* If there is no valid gpg signature we fail, unless there is no gpg
         key specified (on the command line or in the file) because then we
         trust the source bundle. */
      if (ostree_gpg_verify_result_count_valid (gpg_result) == 0  &&
          gpg_data != NULL)
        return xdg_app_fail (error, "GPG signatures found, but none are in trusted keyring");
    }

  if (!ostree_repo_commit_transaction (repo, NULL, cancellable, error))
    return FALSE;

  if (!g_file_make_directory_with_parents (deploy_base, cancellable, error))
    return FALSE;

  /* From here we need to goto out on error, to clean up */
  created_deploy_base = TRUE;

  if (remote)
    {
      g_autoptr(GVariantBuilder) optbuilder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
      g_autofree char *basename = g_file_get_basename (file);

      g_variant_builder_add (optbuilder, "{s@v}",
                             "xa.title",
                             g_variant_new_variant (g_variant_new_string (basename)));

      g_variant_builder_add (optbuilder, "{s@v}",
                             "xa.noenumerate",
                             g_variant_new_variant (g_variant_new_boolean (TRUE)));

      g_variant_builder_add (optbuilder, "{s@v}",
                             "xa.prio",
                             g_variant_new_variant (g_variant_new_string ("0")));

      if (!ostree_repo_remote_add (repo,
                                   remote, origin, g_variant_builder_end (optbuilder), cancellable, error))
        goto out;

      added_remote = TRUE;

      if (gpg_data)
        {
          g_autoptr(GInputStream) gpg_data_as_stream = g_memory_input_stream_new_from_bytes (gpg_data);

          if (!ostree_repo_remote_gpg_import (repo, remote, gpg_data_as_stream,
                                              NULL, NULL, cancellable, error))
            goto out;
        }

      if (!xdg_app_dir_set_origin (dir, ref, remote, cancellable, error))
        goto out;
    }

  if (!xdg_app_dir_deploy (dir, ref, to_checksum, cancellable, error))
    goto out;

  if (!xdg_app_dir_make_current_ref (dir, ref, cancellable, error))
    goto out;

  if (strcmp (parts[0], "app") == 0)
    {
      if (!xdg_app_dir_update_exports (dir, parts[1], cancellable, error))
        goto out;
    }

  glnx_release_lock_file (&lock);

  xdg_app_dir_cleanup_removed (dir, cancellable, NULL);

  if (!xdg_app_dir_mark_changed (dir, error))
    goto out;

  ret = TRUE;

 out:
  if (created_deploy_base && !ret)
    gs_shutil_rm_rf (deploy_base, cancellable, NULL);

  if (added_remote && !ret)
    ostree_repo_remote_delete (repo, remote, NULL, NULL);

  return ret;
}