/* SELinux uses PCRE pre-compiled regexps for binary caches, which can
 * fail if the version of PCRE on the host differs from the version
 * which generated the cache (in the target root).
 *
 * Note also this function is probably already broken in Fedora
 * 23+ from https://bugzilla.redhat.com/show_bug.cgi?id=1265406
 */
static gboolean
workaround_selinux_cross_labeling_recurse (int            dfd,
        const char    *path,
        GCancellable  *cancellable,
        GError       **error)
{
    gboolean ret = FALSE;
    g_auto(GLnxDirFdIterator) dfd_iter = { 0, };

    if (!glnx_dirfd_iterator_init_at (dfd, path, TRUE, &dfd_iter, error))
        goto out;

    while (TRUE)
    {
        struct dirent *dent = NULL;
        const char *name;

        if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, cancellable, error))
            goto out;

        if (!dent)
            break;

        name = dent->d_name;

        if (dent->d_type == DT_DIR)
        {
            if (!workaround_selinux_cross_labeling_recurse (dfd_iter.fd, name, cancellable, error))
                goto out;
        }
        else if (g_str_has_suffix (name, ".bin"))
        {
            struct stat stbuf;
            const char *lastdot;
            gs_free char *nonbin_name = NULL;

            if (TEMP_FAILURE_RETRY (fstatat (dfd_iter.fd, name, &stbuf, AT_SYMLINK_NOFOLLOW)) != 0)
            {
                glnx_set_error_from_errno (error);
                goto out;
            }

            lastdot = strrchr (name, '.');
            g_assert (lastdot);

            nonbin_name = g_strndup (name, lastdot - name);

            g_print ("Setting mtime of '%s' to newer than '%s'\n", nonbin_name, name);
            if (TEMP_FAILURE_RETRY (utimensat (dfd_iter.fd, nonbin_name, NULL, 0)) == -1)
            {
                glnx_set_error_from_errno (error);
                goto out;
            }
        }
    }

    ret = TRUE;
out:
    return ret;
}
Exemple #2
0
static gpointer
xattr_thread (gpointer data)
{
  g_autoptr(GError) local_error = NULL;
  GError **error = &local_error;
  struct XattrWorker *worker = data;
  guint64 end_time = g_get_monotonic_time () + XATTR_THREAD_RUN_TIME_USECS;
  guint n_read = 0;

  while (g_get_monotonic_time () < end_time)
    {
      g_auto(GLnxDirFdIterator) dfd_iter = { 0, };

      if (!glnx_dirfd_iterator_init_at (worker->dfd, ".", TRUE, &dfd_iter, error))
        goto out;

      if (worker->is_writer)
        {
          if (!do_write_run (&dfd_iter, error))
            goto out;
        }
      else
        {
          if (!do_read_run (&dfd_iter, &n_read, error))
            goto out;
        }
    }

 out:
  g_assert_no_error (local_error);

  return GINT_TO_POINTER (n_read);
}
/**
 * ostree_repo_checkout_gc:
 * @self: Repo
 * @cancellable: Cancellable
 * @error: Error
 *
 * Call this after finishing a succession of checkout operations; it
 * will delete any currently-unused uncompressed objects from the
 * cache.
 */
gboolean
ostree_repo_checkout_gc (OstreeRepo        *self,
                         GCancellable      *cancellable,
                         GError           **error)
{
  gboolean ret = FALSE;
  g_autoptr(GHashTable) to_clean_dirs = NULL;
  GHashTableIter iter;
  gpointer key, value;

  g_mutex_lock (&self->cache_lock);
  to_clean_dirs = self->updated_uncompressed_dirs;
  self->updated_uncompressed_dirs = g_hash_table_new (NULL, NULL);
  g_mutex_unlock (&self->cache_lock);

  if (to_clean_dirs)
    g_hash_table_iter_init (&iter, to_clean_dirs);
  while (to_clean_dirs && g_hash_table_iter_next (&iter, &key, &value))
    {
      g_autofree char *objdir_name = g_strdup_printf ("%02x", GPOINTER_TO_UINT (key));
      g_auto(GLnxDirFdIterator) dfd_iter = { 0, };

      if (!glnx_dirfd_iterator_init_at (self->uncompressed_objects_dir_fd, objdir_name, FALSE,
                                        &dfd_iter, error))
        goto out;

      while (TRUE)
        {
          struct dirent *dent;
          struct stat stbuf;

          if (!glnx_dirfd_iterator_next_dent (&dfd_iter, &dent, cancellable, error))
            goto out;
          if (dent == NULL)
            break;

          if (fstatat (dfd_iter.fd, dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW) != 0)
            {
              glnx_set_error_from_errno (error);
              goto out;
            }
          
          if (stbuf.st_nlink == 1)
            {
              if (unlinkat (dfd_iter.fd, dent->d_name, 0) != 0)
                {
                  glnx_set_error_from_errno (error);
                  goto out;
                }
            }
        }
    }

  ret = TRUE;
 out:
  return ret;
}
static GString *
get_directory_listing (int dfd,
                       const char *path)
{
  g_autoptr(GPtrArray) entries = g_ptr_array_new_with_free_func (g_free);
  g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
  g_autoptr(GError) local_error = NULL;
  GError **error = &local_error;
  guint i;
  char *escaped;
  GString *listing;

  listing = g_string_new ("<html>\r\n");

  if (!glnx_dirfd_iterator_init_at (dfd, path, FALSE, &dfd_iter, error))
    goto out;

  while (TRUE)
    {
      struct dirent *dent;

      if (!glnx_dirfd_iterator_next_dent (&dfd_iter, &dent, NULL, error))
        goto out;

      if (dent == NULL)
        break;

      escaped = g_markup_escape_text (dent->d_name, -1);
      g_ptr_array_add (entries, escaped);
    }

  g_ptr_array_sort (entries, (GCompareFunc)compare_strings);

  escaped = g_markup_escape_text (strchr (path, '/'), -1);
  g_string_append_printf (listing, "<head><title>Index of %s</title></head>\r\n", escaped);
  g_string_append_printf (listing, "<body><h1>Index of %s</h1>\r\n<p>\r\n", escaped);
  g_free (escaped);
  for (i = 0; i < entries->len; i++)
    {
      g_string_append_printf (listing, "<a href=\"%s\">%s</a><br>\r\n",
                              (char *)entries->pdata[i], 
                              (char *)entries->pdata[i]);
      g_free (g_steal_pointer (&entries->pdata[i]));
    }
  g_string_append (listing, "</body>\r\n</html>\r\n");
 out:
  if (local_error)
    g_printerr ("%s\n", local_error->message);
  return listing;
}
static gboolean
enumerate_refs_recurse (OstreeRepo    *repo,
                        const char    *remote,
                        int            base_dfd,
                        GString       *base_path,
                        int            child_dfd,
                        const char    *path,
                        GHashTable    *refs,
                        GCancellable  *cancellable,
                        GError       **error)
{
  g_auto(GLnxDirFdIterator) dfd_iter = { 0, };

  if (!glnx_dirfd_iterator_init_at (child_dfd, path, FALSE, &dfd_iter, error))
    return FALSE;

  while (TRUE)
    {
      guint len = base_path->len;
      struct dirent *dent = NULL;

      if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, cancellable, error))
        return FALSE;
      if (dent == NULL)
        break;

      g_string_append (base_path, dent->d_name);

      if (dent->d_type == DT_DIR)
        {
          g_string_append_c (base_path, '/');

          if (!enumerate_refs_recurse (repo, remote, base_dfd, base_path,
                                       dfd_iter.fd, dent->d_name,
                                       refs, cancellable, error))
            return FALSE;
        }
      else if (dent->d_type == DT_REG)
        {
          if (!add_ref_to_set (remote, base_dfd, base_path->str, refs,
                               cancellable, error))
            return FALSE;
        }

      g_string_truncate (base_path, len);
    }

  return TRUE;
}
static gboolean
find_ref_in_remotes (OstreeRepo         *self,
                     const char         *rev,
                     int                *out_fd,
                     GError            **error)
{
  g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
  glnx_fd_close int ret_fd = -1;

  if (!glnx_dirfd_iterator_init_at (self->repo_dir_fd, "refs/remotes", TRUE, &dfd_iter, error))
    return FALSE;

  while (TRUE)
    {
      struct dirent *dent = NULL;
      glnx_fd_close int remote_dfd = -1;

      if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, NULL, error))
        return FALSE;
      if (dent == NULL)
        break;

      if (dent->d_type != DT_DIR)
        continue;

      if (!glnx_opendirat (dfd_iter.fd, dent->d_name, TRUE, &remote_dfd, error))
        return FALSE;

      if (!ot_openat_ignore_enoent (remote_dfd, rev, &ret_fd, error))
        return FALSE;

      if (ret_fd != -1)
        break;
    }

  *out_fd = ret_fd; ret_fd = -1;
  return TRUE;
}
static gboolean
convert_var_to_tmpfiles_d_recurse (GOutputStream *tmpfiles_out,
                                   int            dfd,
                                   GString       *prefix,
                                   GCancellable  *cancellable,
                                   GError       **error)
{
    gboolean ret = FALSE;
    g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
    gsize bytes_written;

    if (!glnx_dirfd_iterator_init_at (dfd, prefix->str + 1, TRUE, &dfd_iter, error))
        goto out;

    while (TRUE)
    {
        struct dirent *dent = NULL;
        GString *tmpfiles_d_buf;
        gs_free char *tmpfiles_d_line = NULL;
        char filetype_c;
        gs_free char *relpath = NULL;

        if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, cancellable, error))
            goto out;

        if (!dent)
            break;

        switch (dent->d_type)
        {
        case DT_DIR:
            filetype_c = 'd';
            break;
        case DT_LNK:
            filetype_c = 'L';
            break;
        default:
            g_print ("Ignoring non-directory/non-symlink '%s'\n",
                     dent->d_name);
            continue;
        }

        tmpfiles_d_buf = g_string_new ("");
        g_string_append_c (tmpfiles_d_buf, filetype_c);
        g_string_append_c (tmpfiles_d_buf, ' ');
        g_string_append (tmpfiles_d_buf, prefix->str);
        g_string_append_c (tmpfiles_d_buf, '/');
        g_string_append (tmpfiles_d_buf, dent->d_name);

        if (filetype_c == 'd')
        {
            struct stat stbuf;

            if (TEMP_FAILURE_RETRY (fstatat (dfd_iter.fd, dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW)) != 0)
            {
                glnx_set_error_from_errno (error);
                goto out;
            }

            g_string_append_printf (tmpfiles_d_buf, " 0%02o", stbuf.st_mode & ~S_IFMT);
            g_string_append_printf (tmpfiles_d_buf, " %d %d - -", stbuf.st_uid, stbuf.st_gid);

            /* Push prefix */
            g_string_append_c (prefix, '/');
            g_string_append (prefix, dent->d_name);

            if (!convert_var_to_tmpfiles_d_recurse (tmpfiles_out, dfd, prefix,
                                                    cancellable, error))
                goto out;

            /* Pop prefix */
            {
                char *r = memrchr (prefix->str, '/', prefix->len);
                g_assert (r != NULL);
                g_string_truncate (prefix, r - prefix->str);
            }
        }
        else
        {
            g_autofree char *link = glnx_readlinkat_malloc (dfd_iter.fd, dent->d_name, cancellable, error);
            if (!link)
                goto out;
            g_string_append (tmpfiles_d_buf, " - - - - ");
            g_string_append (tmpfiles_d_buf, link);
        }

        g_string_append_c (tmpfiles_d_buf, '\n');

        tmpfiles_d_line = g_string_free (tmpfiles_d_buf, FALSE);

        if (!g_output_stream_write_all (tmpfiles_out, tmpfiles_d_line,
                                        strlen (tmpfiles_d_line), &bytes_written,
                                        cancellable, error))
            goto out;
    }

    ret = TRUE;
out:
    return ret;
}
/**
 * ostree_repo_list_static_delta_names:
 * @self: Repo
 * @out_deltas: (out) (element-type utf8) (transfer container): String name of deltas (checksum-checksum.delta)
 * @cancellable: Cancellable
 * @error: Error
 *
 * This function synchronously enumerates all static deltas in the
 * repository, returning its result in @out_deltas.
 */ 
gboolean
ostree_repo_list_static_delta_names (OstreeRepo                  *self,
                                     GPtrArray                  **out_deltas,
                                     GCancellable                *cancellable,
                                     GError                     **error)
{
  g_autoptr(GPtrArray) ret_deltas = g_ptr_array_new_with_free_func (g_free);

  g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
  gboolean exists;
  if (!ot_dfd_iter_init_allow_noent (self->repo_dir_fd, "deltas", &dfd_iter,
                                     &exists, error))
    return FALSE;
  if (!exists)
    {
      /* Note early return */
      ot_transfer_out_value (out_deltas, &ret_deltas);
      return TRUE;
    }

  while (TRUE)
    {
      g_auto(GLnxDirFdIterator) sub_dfd_iter = { 0, };
      struct dirent *dent;

      if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, cancellable, error))
        return FALSE;
      if (dent == NULL)
        break;
      if (dent->d_type != DT_DIR)
        continue;

      if (!glnx_dirfd_iterator_init_at (dfd_iter.fd, dent->d_name, FALSE,
                                        &sub_dfd_iter, error))
        return FALSE;

      while (TRUE)
        {
          struct dirent *sub_dent;
          const char *name1;
          const char *name2;
          g_autofree char *superblock_subpath = NULL;
          struct stat stbuf;

          if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&sub_dfd_iter, &sub_dent,
                                                           cancellable, error))
            return FALSE;
          if (sub_dent == NULL)
            break;
          if (dent->d_type != DT_DIR)
            continue;

          name1 = dent->d_name;
          name2 = sub_dent->d_name;

          superblock_subpath = g_strconcat (name2, "/superblock", NULL);
          if (fstatat (sub_dfd_iter.fd, superblock_subpath, &stbuf, 0) < 0)
            {
              if (errno != ENOENT)
                {
                  glnx_set_error_from_errno (error);
                  return FALSE;
                }
            }
          else
            {
              g_autofree char *buf = g_strconcat (name1, name2, NULL);
              GString *out = g_string_new ("");
              char checksum[OSTREE_SHA256_STRING_LEN+1];
              guchar csum[OSTREE_SHA256_DIGEST_LEN];
              const char *dash = strchr (buf, '-');

              ostree_checksum_b64_inplace_to_bytes (buf, csum);
              ostree_checksum_inplace_from_bytes (csum, checksum);
              g_string_append (out, checksum);
              if (dash)
                {
                  g_string_append_c (out, '-');
                  ostree_checksum_b64_inplace_to_bytes (dash+1, csum);
                  ostree_checksum_inplace_from_bytes (csum, checksum);
                  g_string_append (out, checksum);
                }

              g_ptr_array_add (ret_deltas, g_string_free (out, FALSE));
            }
        }
    }

  ot_transfer_out_value (out_deltas, &ret_deltas);
  return TRUE;
}
static gboolean
export_dir (int           source_parent_fd,
            const char   *source_name,
            const char   *source_relpath,
            int           destination_parent_fd,
            const char   *destination_name,
            const char   *required_prefix,
            GCancellable *cancellable,
            GError      **error)
{
  int res;

  g_auto(GLnxDirFdIterator) source_iter = {0};
  glnx_fd_close int destination_dfd = -1;
  struct dirent *dent;

  if (!glnx_dirfd_iterator_init_at (source_parent_fd, source_name, FALSE, &source_iter, error))
    return FALSE;

  do
    res = mkdirat (destination_parent_fd, destination_name, 0755);
  while (G_UNLIKELY (res == -1 && errno == EINTR));
  if (res == -1)
    {
      if (errno != EEXIST)
        {
          glnx_set_error_from_errno (error);
          return FALSE;
        }
    }

  if (!glnx_opendirat (destination_parent_fd, destination_name, TRUE,
                       &destination_dfd, error))
    return FALSE;

  while (TRUE)
    {
      struct stat stbuf;
      g_autofree char *source_printable = NULL;

      if (!glnx_dirfd_iterator_next_dent (&source_iter, &dent, cancellable, error))
        return FALSE;

      if (dent == NULL)
        break;

      if (fstatat (source_iter.fd, dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW) == -1)
        {
          if (errno == ENOENT)
            {
              continue;
            }
          else
            {
              glnx_set_error_from_errno (error);
              return FALSE;
            }
        }

      /* Don't export any hidden files or backups */
      if (g_str_has_prefix (dent->d_name, ".") ||
          g_str_has_suffix (dent->d_name, "~"))
        continue;

      if (S_ISDIR (stbuf.st_mode))
        {
          g_autofree gchar *child_relpath = g_build_filename (source_relpath, dent->d_name, NULL);

          if (!export_dir (source_iter.fd, dent->d_name, child_relpath, destination_dfd, dent->d_name,
                           required_prefix, cancellable, error))
            return FALSE;
        }
      else if (S_ISREG (stbuf.st_mode))
        {
          source_printable = g_build_filename (source_relpath, dent->d_name, NULL);


          if (!flatpak_has_name_prefix (dent->d_name, required_prefix))
            {
              g_print ("Not exporting %s, wrong prefix\n", source_printable);
              continue;
            }

          g_print ("Exporting %s\n", source_printable);

          if (!glnx_file_copy_at (source_iter.fd, dent->d_name, &stbuf,
                                  destination_dfd, dent->d_name,
                                  GLNX_FILE_COPY_NOXATTRS,
                                  cancellable,
                                  error))
            return FALSE;
        }
      else
        {
          source_printable = g_build_filename (source_relpath, dent->d_name, NULL);
          g_debug ("Not exporting non-regular file %s\n", source_printable);
        }
    }

  /* Try to remove the directory, as we don't want to export empty directories.
   * However, don't fail if the unlink fails due to the directory not being empty */
  do
    res = unlinkat (destination_parent_fd, destination_name, AT_REMOVEDIR);
  while (G_UNLIKELY (res == -1 && errno == EINTR));
  if (res == -1)
    {
      if (errno != ENOTEMPTY)
        {
          glnx_set_error_from_errno (error);
          return FALSE;
        }
    }

  return TRUE;
}
static gboolean
export_dir (int            source_parent_fd,
            const char    *source_name,
            const char    *source_relpath,
            int            destination_parent_fd,
            const char    *destination_name,
            const char    *required_prefix,
            GCancellable  *cancellable,
            GError       **error)
{
  gboolean ret = FALSE;
  int res;
  g_auto(GLnxDirFdIterator) source_iter = {0};
  glnx_fd_close int destination_dfd = -1;
  struct dirent *dent;

  if (!glnx_dirfd_iterator_init_at (source_parent_fd, source_name, FALSE, &source_iter, error))
    goto out;

  do
    res = mkdirat (destination_parent_fd, destination_name, 0777);
  while (G_UNLIKELY (res == -1 && errno == EINTR));
  if (res == -1)
    {
      if (errno != EEXIST)
        {
          glnx_set_error_from_errno (error);
          goto out;
        }
    }

  if (!gs_file_open_dir_fd_at (destination_parent_fd, destination_name,
                               &destination_dfd,
                               cancellable, error))
    goto out;

  while (TRUE)
    {
      struct stat stbuf;
      g_autofree char *source_printable = NULL;

      if (!glnx_dirfd_iterator_next_dent (&source_iter, &dent, cancellable, error))
        goto out;

      if (dent == NULL)
        break;

      if (fstatat (source_iter.fd, dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW) == -1)
        {
          if (errno == ENOENT)
            continue;
          else
            {
              glnx_set_error_from_errno (error);
              goto out;
            }
        }

      if (S_ISDIR (stbuf.st_mode))
        {
          g_autofree gchar *child_relpath = g_build_filename(source_relpath, dent->d_name, NULL);

          if (!export_dir (source_iter.fd, dent->d_name, child_relpath, destination_dfd, dent->d_name,
                           required_prefix, cancellable, error))
            goto out;
        }
      else if (S_ISREG (stbuf.st_mode))
        {
          source_printable = g_build_filename (source_relpath, dent->d_name, NULL);


          if (!xdg_app_has_name_prefix (dent->d_name, required_prefix))
            {
              g_print ("Not exporting %s, wrong prefix\n", source_printable);
              continue;
            }

          g_print ("Exporting %s\n", source_printable);

          if (!glnx_file_copy_at (source_iter.fd, dent->d_name, &stbuf,
                                  destination_dfd, dent->d_name,
                                  GLNX_FILE_COPY_NOXATTRS,
                                  cancellable,
                                  error))
            goto out;
        }
      else
        {
          source_printable = g_build_filename (source_relpath, dent->d_name, NULL);
          g_print ("Not exporting non-regular file %s\n", source_printable);
        }
    }

  ret = TRUE;
 out:

  return ret;
}
/* 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;
}
Exemple #12
0
static gboolean
_ostree_repo_list_refs_internal (OstreeRepo       *self,
                                 gboolean         cut_prefix,
                                 const char       *refspec_prefix,
                                 GHashTable      **out_all_refs,
                                 GCancellable     *cancellable,
                                 GError          **error)
{
  g_autoptr(GHashTable) ret_all_refs = NULL;
  g_autofree char *remote = NULL;
  g_autofree char *ref_prefix = NULL;

  ret_all_refs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);

  if (refspec_prefix)
    {
      struct stat stbuf;
      const char *prefix_path;
      const char *path;

      if (!ostree_parse_refspec (refspec_prefix, &remote, &ref_prefix, error))
        return FALSE;

      if (remote)
        {
          prefix_path = glnx_strjoina ("refs/remotes/", remote, "/");
          path = glnx_strjoina (prefix_path, ref_prefix);
        }
      else
        {
          prefix_path = "refs/heads/";
          path = glnx_strjoina (prefix_path, ref_prefix);
        }

      if (fstatat (self->repo_dir_fd, path, &stbuf, 0) < 0)
        {
          if (errno != ENOENT)
            return glnx_throw_errno (error);
        }
      else
        {
          if (S_ISDIR (stbuf.st_mode))
            {
              glnx_fd_close int base_fd = -1;
              g_autoptr(GString) base_path = g_string_new ("");
              if (!cut_prefix)
                g_string_printf (base_path, "%s/", ref_prefix);

              if (!glnx_opendirat (self->repo_dir_fd, cut_prefix ? path : prefix_path, TRUE, &base_fd, error))
                return FALSE;

              if (!enumerate_refs_recurse (self, remote, base_fd, base_path,
                                           base_fd, cut_prefix ? "." : ref_prefix,
                                           ret_all_refs, cancellable, error))
                return FALSE;
            }
          else
            {
              glnx_fd_close int prefix_dfd = -1;

              if (!glnx_opendirat (self->repo_dir_fd, prefix_path, TRUE, &prefix_dfd, error))
                return FALSE;

              if (!add_ref_to_set (remote, prefix_dfd, ref_prefix, ret_all_refs,
                                   cancellable, error))
                return FALSE;
            }
        }
    }
  else
    {
      g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
      g_autoptr(GString) base_path = g_string_new ("");
      glnx_fd_close int refs_heads_dfd = -1;

      if (!glnx_opendirat (self->repo_dir_fd, "refs/heads", TRUE, &refs_heads_dfd, error))
        return FALSE;

      if (!enumerate_refs_recurse (self, NULL, refs_heads_dfd, base_path,
                                   refs_heads_dfd, ".",
                                   ret_all_refs, cancellable, error))
        return FALSE;

      g_string_truncate (base_path, 0);

      if (!glnx_dirfd_iterator_init_at (self->repo_dir_fd, "refs/remotes", TRUE, &dfd_iter, error))
        return FALSE;

      while (TRUE)
        {
          struct dirent *dent;
          glnx_fd_close int remote_dfd = -1;

          if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, cancellable, error))
            return FALSE;
          if (!dent)
            break;

          if (dent->d_type != DT_DIR)
            continue;

          if (!glnx_opendirat (dfd_iter.fd, dent->d_name, TRUE, &remote_dfd, error))
            return FALSE;

          if (!enumerate_refs_recurse (self, dent->d_name, remote_dfd, base_path,
                                       remote_dfd, ".",
                                       ret_all_refs,
                                       cancellable, error))
            return FALSE;
        }
    }

  ot_transfer_out_value (out_all_refs, &ret_all_refs);
  return TRUE;
}