gboolean
ot_remote_builtin_delete_cookie (int argc, char **argv, GCancellable *cancellable, GError **error)
{
  g_autoptr(OstreeRepo) repo = NULL;
  g_autoptr(GOptionContext) context = g_option_context_new ("NAME DOMAIN PATH COOKIE_NAME- Remote one cookie from remote");

  if (!ostree_option_context_parse (context, option_entries, &argc, &argv,
                                    OSTREE_BUILTIN_FLAG_NONE, &repo, cancellable, error))
    return FALSE;

  if (argc < 5)
    {
      ot_util_usage_error (context, "NAME, DOMAIN, PATH and COOKIE_NAME must be specified", error);
      return FALSE;
    }

  const char *remote_name = argv[1];
  const char *domain = argv[2];
  const char *path = argv[3];
  const char *cookie_name = argv[4];
  g_autofree char *cookie_file = g_strdup_printf ("%s.cookies.txt", remote_name);
  if (!ot_delete_cookie_at (ostree_repo_get_dfd (repo), cookie_file, domain, path, cookie_name, error))
    return FALSE;

  return TRUE;
}
static gboolean
_ostree_repo_static_delta_delete (OstreeRepo   *self,
                                  const char   *delta_id,
                                  GCancellable *cancellable,
                                  GError      **error)
{
  gboolean ret = FALSE;
  g_autofree char *from = NULL;
  g_autofree char *to = NULL;
  g_autofree char *deltadir = NULL;
  struct stat buf;
  int repo_dir_fd = ostree_repo_get_dfd (self);

  _ostree_parse_delta_name (delta_id, &from, &to);
  deltadir = _ostree_get_relative_static_delta_path (from, to, NULL);

  if (fstatat (repo_dir_fd, deltadir, &buf, 0) != 0)
    {
      if (errno == ENOENT)
        g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                     "Can't find delta %s", delta_id);
      else
        glnx_set_error_from_errno (error);

      goto out;
    }

  if (!glnx_shutil_rm_rf_at (repo_dir_fd, deltadir,
                             cancellable, error))
    goto out;

  ret = TRUE;
out:
  return ret;
}
gboolean
ot_remote_builtin_list_cookies (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error)
{
  g_autoptr(OstreeRepo) repo = NULL;
  g_autoptr(GOptionContext) context = g_option_context_new ("NAME");

  if (!ostree_option_context_parse (context, option_entries, &argc, &argv,
                                    invocation, &repo, cancellable, error))
    return FALSE;

  if (argc < 2)
    {
      ot_util_usage_error (context, "NAME must be specified", error);
      return FALSE;
    }

  const char *remote_name = argv[1];
  g_autofree char *cookie_file = g_strdup_printf ("%s.cookies.txt", remote_name);
  if (!ot_list_cookies_at (ostree_repo_get_dfd (repo), cookie_file, error))
    return FALSE;

  return TRUE;
}
static gboolean
sysroot_populate_deployments_unlocked (RpmostreedSysroot *self,
				       gboolean *out_changed,
				       GError **error)
{
  gboolean ret = FALSE;
  OstreeDeployment *booted = NULL; /* owned by sysroot */
  g_autofree gchar *booted_id = NULL;
  g_autoptr(GPtrArray) deployments = NULL;
  g_autoptr(GHashTable) seen_osnames = NULL;
  GHashTableIter iter;
  gpointer hashkey;
  gpointer value;
  GVariantBuilder builder;
  guint i;
  gboolean sysroot_changed;
  gboolean repo_changed;
  struct stat repo_new_stat;

  if (!ostree_sysroot_load_if_changed (self->ot_sysroot, &sysroot_changed, self->cancellable, error))
    goto out;

  if (fstat (ostree_repo_get_dfd (self->repo), &repo_new_stat) < 0)
    {
      glnx_set_error_from_errno (error);
      goto out;
    }

  repo_changed = !(self->repo_last_stat.st_mtim.tv_sec == repo_new_stat.st_mtim.tv_sec
		   && self->repo_last_stat.st_mtim.tv_nsec == repo_new_stat.st_mtim.tv_nsec);
  if (repo_changed)
    self->repo_last_stat = repo_new_stat;

  if (!(sysroot_changed || repo_changed))
    {
      ret = TRUE;
      if (out_changed)
	*out_changed = FALSE;
      goto out;
    }

  g_debug ("loading deployments");
  g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));

  seen_osnames = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);

  /* Updated booted property */
  booted = ostree_sysroot_get_booted_deployment (self->ot_sysroot);
  if (booted)
    {
      const gchar *os = ostree_deployment_get_osname (booted);
      g_autofree gchar *path = rpmostreed_generate_object_path (BASE_DBUS_PATH,
                                                                os, NULL);
      rpmostree_sysroot_set_booted (RPMOSTREE_SYSROOT (self), path);
      booted_id = rpmostreed_deployment_generate_id (booted);
    }
  else
    {
      rpmostree_sysroot_set_booted (RPMOSTREE_SYSROOT (self), "/");
    }

  /* Add deployment interfaces */
  deployments = ostree_sysroot_get_deployments (self->ot_sysroot);

  for (i = 0; deployments != NULL && i < deployments->len; i++)
    {
      GVariant *variant;
      OstreeDeployment *deployment = deployments->pdata[i];
      const char *deployment_os;
      
      variant = rpmostreed_deployment_generate_variant (deployment, booted_id, self->repo, error);
      if (!variant)
	goto out;
      g_variant_builder_add_value (&builder, variant);

      deployment_os = ostree_deployment_get_osname (deployment);

      /* Have we not seen this osname instance before?  If so, add it
       * now.
       */
      if (!g_hash_table_contains (self->os_interfaces, deployment_os))
	{
	  RPMOSTreeOS *obj = rpmostreed_os_new (self->ot_sysroot, self->repo,
						deployment_os,
                                                self->transaction_monitor);
          g_hash_table_insert (self->os_interfaces, g_strdup (deployment_os), obj);
	}
      /* Owned by deployment, hash lifetime is smaller */
      g_hash_table_add (seen_osnames, (char*)deployment_os);
    }

  /* Remove dead os paths */
  g_hash_table_iter_init (&iter, self->os_interfaces);
  while (g_hash_table_iter_next (&iter, &hashkey, &value))
    {
      if (!g_hash_table_contains (seen_osnames, hashkey))
        {
          g_object_run_dispose (G_OBJECT (value));
          g_hash_table_iter_remove (&iter);
        }
    }

  rpmostree_sysroot_set_deployments (RPMOSTREE_SYSROOT (self),
                                     g_variant_builder_end (&builder));
  g_debug ("finished deployments");

  ret = TRUE;
  if (out_changed)
    *out_changed = TRUE;
 out:
  return ret;
}
static gboolean
rewrite_delta (OstreeRepo *src_repo,
               const char *src_commit,
               OstreeRepo *dst_repo,
               const char *dst_commit,
               GVariant   *dst_commitv,
               const char *from,
               GError    **error)
{
  g_autoptr(GFile) src_delta_file = NULL;
  g_autoptr(GFile) dst_delta_file = NULL;
  g_autofree char *src_detached_key = _ostree_get_relative_static_delta_path (from, src_commit, "commitmeta");
  g_autofree char *dst_detached_key = _ostree_get_relative_static_delta_path (from, dst_commit, "commitmeta");
  g_autofree char *src_delta_dir = _ostree_get_relative_static_delta_path (from, src_commit, NULL);
  g_autofree char *dst_delta_dir = _ostree_get_relative_static_delta_path (from, dst_commit, NULL);
  g_autofree char *src_superblock_path = _ostree_get_relative_static_delta_path (from, src_commit, "superblock");
  g_autofree char *dst_superblock_path = _ostree_get_relative_static_delta_path (from, dst_commit, "superblock");
  GMappedFile *mfile = NULL;
  g_auto(GVariantBuilder) superblock_builder = FLATPAK_VARIANT_BUILDER_INITIALIZER;
  g_autoptr(GVariant) src_superblock = NULL;
  g_autoptr(GVariant) dst_superblock = NULL;
  g_autoptr(GBytes) bytes = NULL;
  g_autoptr(GVariant) dst_detached = NULL;
  g_autoptr(GVariant) src_metadata = NULL;
  g_autoptr(GVariant) src_recurse = NULL;
  g_autoptr(GVariant) src_parts = NULL;
  g_auto(GVariantDict) dst_metadata_dict = FLATPAK_VARIANT_DICT_INITIALIZER;
  int i;

  src_delta_file = g_file_resolve_relative_path (ostree_repo_get_path (src_repo), src_superblock_path);
  mfile = g_mapped_file_new (flatpak_file_get_path_cached (src_delta_file), FALSE, NULL);
  if (mfile == NULL)
    return TRUE; /* No superblock, not an error */

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

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

  src_metadata = g_variant_get_child_value (src_superblock, 0);
  src_recurse = g_variant_get_child_value (src_superblock, 5);
  src_parts = g_variant_get_child_value (src_superblock, 6);

  if (g_variant_n_children (src_recurse) != 0)
    return flatpak_fail (error, "Recursive deltas not supported, ignoring");

  g_variant_builder_init (&superblock_builder, G_VARIANT_TYPE (OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT));

  g_variant_dict_init (&dst_metadata_dict, src_metadata);
  g_variant_dict_remove (&dst_metadata_dict, src_detached_key);
  if (ostree_repo_read_commit_detached_metadata (dst_repo, dst_commit, &dst_detached, NULL, NULL) &&
      dst_detached != NULL)
    g_variant_dict_insert_value (&dst_metadata_dict, dst_detached_key, dst_detached);

  g_variant_builder_add_value (&superblock_builder, g_variant_dict_end (&dst_metadata_dict));
  g_variant_builder_add_value (&superblock_builder, g_variant_get_child_value (src_superblock, 1)); /* timestamp */
  g_variant_builder_add_value (&superblock_builder, from ? ostree_checksum_to_bytes_v (from) : new_bytearray ((guchar *) "", 0));
  g_variant_builder_add_value (&superblock_builder, ostree_checksum_to_bytes_v (dst_commit));
  g_variant_builder_add_value (&superblock_builder, dst_commitv);
  g_variant_builder_add_value (&superblock_builder, src_recurse);
  g_variant_builder_add_value (&superblock_builder, src_parts);
  g_variant_builder_add_value (&superblock_builder, g_variant_get_child_value (src_superblock, 7)); /* fallback */

  dst_superblock = g_variant_ref_sink (g_variant_builder_end (&superblock_builder));

  if (!glnx_shutil_mkdir_p_at (ostree_repo_get_dfd (dst_repo), dst_delta_dir, 0755, NULL, error))
    return FALSE;

  for (i = 0; i < g_variant_n_children (src_parts); i++)
    {
      g_autofree char *src_part_path = g_strdup_printf ("%s/%d", src_delta_dir, i);
      g_autofree char *dst_part_path = g_strdup_printf ("%s/%d", dst_delta_dir, i);

      if (!glnx_file_copy_at (ostree_repo_get_dfd (src_repo),
                              src_part_path,
                              NULL,
                              ostree_repo_get_dfd (dst_repo),
                              dst_part_path,
                              GLNX_FILE_COPY_OVERWRITE | GLNX_FILE_COPY_NOXATTRS,
                              NULL, error))
        return FALSE;
    }

  dst_delta_file = g_file_resolve_relative_path (ostree_repo_get_path (dst_repo), dst_superblock_path);
  if (!flatpak_variant_save (dst_delta_file, dst_superblock, NULL, error))
    return FALSE;

  return TRUE;
}
/* TODO: Add a man page. */
gboolean
ostree_builtin_create_usb (int            argc,
                           char         **argv,
                           OstreeCommandInvocation *invocation,
                           GCancellable  *cancellable,
                           GError       **error)
{
  g_autoptr(GOptionContext) context = NULL;
  g_autoptr(OstreeAsyncProgress) progress = NULL;
  g_auto(GLnxConsoleRef) console = { 0, };

  context = g_option_context_new ("MOUNT-PATH COLLECTION-ID REF [COLLECTION-ID REF...]");

  /* Parse options. */
  g_autoptr(OstreeRepo) src_repo = NULL;

  if (!ostree_option_context_parse (context, options, &argc, &argv, invocation, &src_repo, cancellable, error))
    return FALSE;

  if (argc < 2)
    {
      ot_util_usage_error (context, "A MOUNT-PATH must be specified", error);
      return FALSE;
    }

  if (argc < 4)
    {
      ot_util_usage_error (context, "At least one COLLECTION-ID REF pair must be specified", error);
      return FALSE;
    }

  if (argc % 2 == 1)
    {
      ot_util_usage_error (context, "Only complete COLLECTION-ID REF pairs may be specified", error);
      return FALSE;
    }

  /* Open the USB stick, which must exist. Allow automounting and following symlinks. */
  const char *mount_root_path = argv[1];
  struct stat mount_root_stbuf;

  glnx_autofd int mount_root_dfd = -1;
  if (!glnx_opendirat (AT_FDCWD, mount_root_path, TRUE, &mount_root_dfd, error))
    return FALSE;
  if (!glnx_fstat (mount_root_dfd, &mount_root_stbuf, error))
    return FALSE;

  /* Read in the refs to add to the USB stick. */
  g_autoptr(GPtrArray) refs = g_ptr_array_new_full (argc, (GDestroyNotify) ostree_collection_ref_free);

  for (gsize i = 2; i < argc; i += 2)
    {
      if (!ostree_validate_collection_id (argv[i], error) ||
          !ostree_validate_rev (argv[i + 1], error))
        return FALSE;

      g_ptr_array_add (refs, ostree_collection_ref_new (argv[i], argv[i + 1]));
    }

  /* Open the destination repository on the USB stick or create it if it doesn’t exist.
   * Check it’s below @mount_root_path, and that it’s not the same as the source
   * repository.
   *
   * If the destination file system supports xattrs (for example, ext4), we use
   * a BARE_USER repository; if it doesn’t (for example, FAT), we use ARCHIVE.
   * In either case, we want a lossless repository. */
  const char *dest_repo_path = (opt_destination_repo != NULL) ? opt_destination_repo : ".ostree/repo";

  if (!glnx_shutil_mkdir_p_at (mount_root_dfd, dest_repo_path, 0755, cancellable, error))
    return FALSE;

  OstreeRepoMode mode = OSTREE_REPO_MODE_BARE_USER;

  if (TEMP_FAILURE_RETRY (fgetxattr (mount_root_dfd, "user.test", NULL, 0)) < 0 &&
      (errno == ENOTSUP || errno == EOPNOTSUPP))
    mode = OSTREE_REPO_MODE_ARCHIVE;

  g_debug ("%s: Creating repository in mode %u", G_STRFUNC, mode);

  g_autoptr(OstreeRepo) dest_repo = ostree_repo_create_at (mount_root_dfd, dest_repo_path,
                                                           mode, NULL, cancellable, error);

  if (dest_repo == NULL)
    return FALSE;

  struct stat dest_repo_stbuf;

  if (!glnx_fstat (ostree_repo_get_dfd (dest_repo), &dest_repo_stbuf, error))
    return FALSE;

  if (dest_repo_stbuf.st_dev != mount_root_stbuf.st_dev)
    {
      ot_util_usage_error (context, "--destination-repo must be a descendent of MOUNT-PATH", error);
      return FALSE;
    }

  if (ostree_repo_equal (src_repo, dest_repo))
    {
      ot_util_usage_error (context, "--destination-repo must not be the source repository", error);
      return FALSE;
    }

  if (!ostree_ensure_repo_writable (dest_repo, error))
    return FALSE;

  if (opt_disable_fsync)
    ostree_repo_set_disable_fsync (dest_repo, TRUE);

  /* Copy across all of the collection–refs to the destination repo. */
  GVariantBuilder refs_builder;
  g_variant_builder_init (&refs_builder, G_VARIANT_TYPE ("a(sss)"));

  for (gsize i = 0; i < refs->len; i++)
    {
      const OstreeCollectionRef *ref = g_ptr_array_index (refs, i);

      g_variant_builder_add (&refs_builder, "(sss)",
                             ref->collection_id, ref->ref_name, "");
    }

  {
    GVariantBuilder builder;
    g_autoptr(GVariant) opts = NULL;
    OstreeRepoPullFlags flags = OSTREE_REPO_PULL_FLAGS_MIRROR;

    glnx_console_lock (&console);

    if (console.is_tty)
      progress = ostree_async_progress_new_and_connect (ostree_repo_pull_default_console_progress_changed, &console);

    g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));

    g_variant_builder_add (&builder, "{s@v}", "collection-refs",
                           g_variant_new_variant (g_variant_builder_end (&refs_builder)));
    g_variant_builder_add (&builder, "{s@v}", "flags",
                           g_variant_new_variant (g_variant_new_int32 (flags)));
    g_variant_builder_add (&builder, "{s@v}", "depth",
                           g_variant_new_variant (g_variant_new_int32 (0)));
    opts = g_variant_ref_sink (g_variant_builder_end (&builder));

    g_autofree char *src_repo_uri = g_file_get_uri (ostree_repo_get_path (src_repo));

    if (!ostree_repo_pull_with_options (dest_repo, src_repo_uri,
                                        opts,
                                        progress,
                                        cancellable, error))
      {
        ostree_repo_abort_transaction (dest_repo, cancellable, NULL);
        return FALSE;
      }

    if (progress != NULL)
      ostree_async_progress_finish (progress);
  }

  /* Ensure a summary file is present to make it easier to look up commit checksums. */
  /* FIXME: It should be possible to work without this, but find_remotes_cb() in
   * ostree-repo-pull.c currently assumes a summary file (signed or unsigned) is
   * present. */
  struct stat stbuf;
  if (!glnx_fstatat_allow_noent (ostree_repo_get_dfd (dest_repo), "summary", &stbuf, 0, error))
    return FALSE;
  if (errno == ENOENT &&
      !ostree_repo_regenerate_summary (dest_repo, NULL, cancellable, error))
    return FALSE;

  /* Add the symlinks .ostree/repos.d/@symlink_name → @dest_repo_path, unless
   * the @dest_repo_path is a well-known one like ostree/repo, in which case no
   * symlink is necessary; #OstreeRepoFinderMount always looks there. */
  if (!g_str_equal (dest_repo_path, "ostree/repo") &&
      !g_str_equal (dest_repo_path, ".ostree/repo"))
    {
      if (!glnx_shutil_mkdir_p_at (mount_root_dfd, ".ostree/repos.d", 0755, cancellable, error))
        return FALSE;

      /* Find a unique name for the symlink. If a symlink already targets
       * @dest_repo_path, use that and don’t create a new one. */
      GLnxDirFdIterator repos_iter;
      gboolean need_symlink = TRUE;

      if (!glnx_dirfd_iterator_init_at (mount_root_dfd, ".ostree/repos.d", TRUE, &repos_iter, error))
        return FALSE;

      while (TRUE)
        {
          struct dirent *repo_dent;

          if (!glnx_dirfd_iterator_next_dent (&repos_iter, &repo_dent, cancellable, error))
            return FALSE;

          if (repo_dent == NULL)
            break;

          /* Does the symlink already point to this repository? (Or is the
           * repository itself present in repos.d?) We already guarantee that
           * they’re on the same device. */
          if (repo_dent->d_ino == dest_repo_stbuf.st_ino)
            {
              need_symlink = FALSE;
              break;
            }
        }

      /* If we need a symlink, find a unique name for it and create it. */
      if (need_symlink)
        {
          /* Relative to .ostree/repos.d. */
          g_autofree char *relative_dest_repo_path = g_build_filename ("..", "..", dest_repo_path, NULL);
          guint i;
          const guint max_attempts = 100;

          for (i = 0; i < max_attempts; i++)
            {
              g_autofree char *symlink_path = g_strdup_printf (".ostree/repos.d/%02u-generated", i);

              int ret = TEMP_FAILURE_RETRY (symlinkat (relative_dest_repo_path, mount_root_dfd, symlink_path));
              if (ret < 0 && errno != EEXIST)
                return glnx_throw_errno_prefix (error, "symlinkat(%s → %s)", symlink_path, relative_dest_repo_path);
              else if (ret >= 0)
                break;
            }

          if (i == max_attempts)
            return glnx_throw (error, "Could not find an unused symlink name for the repository");
        }
    }

  /* Report success to the user. */
  g_autofree char *src_repo_path = g_file_get_path (ostree_repo_get_path (src_repo));

  g_print ("Copied %u/%u refs successfully from ‘%s’ to ‘%s’ repository in ‘%s’.\n", refs->len, refs->len,
           src_repo_path, dest_repo_path, mount_root_path);

  return TRUE;
}