Esempio n. 1
0
static gboolean
write_regular_file_content (OstreeRepo            *self,
                            OstreeRepoCheckoutOptions *options,
                            GOutputStream         *output,
                            GFileInfo             *file_info,
                            GVariant              *xattrs,
                            GInputStream          *input,
                            GCancellable          *cancellable,
                            GError               **error)
{
  gboolean ret = FALSE;
  const OstreeRepoCheckoutMode mode = options->mode;
  int fd;
  int res;

  if (g_output_stream_splice (output, input, 0,
                              cancellable, error) < 0)
    goto out;

  if (!g_output_stream_flush (output, cancellable, error))
    goto out;

  fd = g_file_descriptor_based_get_fd ((GFileDescriptorBased*)output);

  if (mode != OSTREE_REPO_CHECKOUT_MODE_USER)
    {
      do
        res = fchown (fd,
                      g_file_info_get_attribute_uint32 (file_info, "unix::uid"),
                      g_file_info_get_attribute_uint32 (file_info, "unix::gid"));
      while (G_UNLIKELY (res == -1 && errno == EINTR));
      if (G_UNLIKELY (res == -1))
        {
          glnx_set_error_from_errno (error);
          goto out;
        }

      do
        res = fchmod (fd, g_file_info_get_attribute_uint32 (file_info, "unix::mode"));
      while (G_UNLIKELY (res == -1 && errno == EINTR));
      if (G_UNLIKELY (res == -1))
        {
          glnx_set_error_from_errno (error);
          goto out;
        }
              
      if (xattrs)
        {
          if (!glnx_fd_set_all_xattrs (fd, xattrs, cancellable, error))
            goto out;
        }
    }
          
  if (fsync_is_enabled (self, options))
    {
      if (fsync (fd) == -1)
        {
          glnx_set_error_from_errno (error);
          goto out;
        }
    }
          
  if (!g_output_stream_close (output, cancellable, error))
    goto out;

  ret = TRUE;
 out:
  return ret;
}
Esempio n. 2
0
static gboolean
checkout_object_for_uncompressed_cache (OstreeRepo      *self,
                                        const char      *loose_path,
                                        GFileInfo       *src_info,
                                        GInputStream    *content,
                                        GCancellable    *cancellable,
                                        GError         **error)
{
  gboolean ret = FALSE;
  g_autofree char *temp_filename = NULL;
  g_autoptr(GOutputStream) temp_out = NULL;
  glnx_fd_close int fd = -1;
  int res;
  guint32 file_mode;

  /* Don't make setuid files in uncompressed cache */
  file_mode = g_file_info_get_attribute_uint32 (src_info, "unix::mode");
  file_mode &= ~(S_ISUID|S_ISGID);

  if (!glnx_open_tmpfile_linkable_at (self->tmp_dir_fd, ".", O_WRONLY | O_CLOEXEC,
                                      &fd, &temp_filename,
                                      error))
    goto out;
  temp_out = g_unix_output_stream_new (fd, FALSE);

  if (g_output_stream_splice (temp_out, content, 0, cancellable, error) < 0)
    goto out;

  if (!g_output_stream_flush (temp_out, cancellable, error))
    goto out;

  if (!self->disable_fsync)
    {
      do
        res = fsync (fd);
      while (G_UNLIKELY (res == -1 && errno == EINTR));
      if (G_UNLIKELY (res == -1))
        {
          glnx_set_error_from_errno (error);
          goto out;
        }
    }

  if (!g_output_stream_close (temp_out, cancellable, error))
    goto out;

  if (fchmod (fd, file_mode) < 0)
    {
      glnx_set_error_from_errno (error);
      goto out;
    }

  if (!_ostree_repo_ensure_loose_objdir_at (self->uncompressed_objects_dir_fd,
                                            loose_path,
                                            cancellable, error))
    goto out;

  if (!glnx_link_tmpfile_at (self->tmp_dir_fd, GLNX_LINK_TMPFILE_NOREPLACE_IGNORE_EXIST,
                             fd, temp_filename,
                             self->uncompressed_objects_dir_fd, loose_path,
                             error))
    goto out;

  ret = TRUE;
 out:
  return ret;
}
Esempio n. 3
0
static gboolean
run_script_in_bwrap_container (int rootfs_fd,
                               const char *name,
                               const char *scriptdesc,
                               const char *script,
                               GCancellable  *cancellable,
                               GError       **error)
{
  gboolean ret = FALSE;
  int i;
  const char *usr_links[] = {"lib", "lib32", "lib64", "bin", "sbin"};
  int estatus;
  char *rofiles_mnt = strdupa ("/tmp/rofiles-fuse.XXXXXX");
  const char *rofiles_argv[] = { "rofiles-fuse", "./usr", rofiles_mnt, NULL};
  const char *pkg_script = glnx_strjoina (name, ".", scriptdesc+1);
  const char *postscript_name = glnx_strjoina ("/", pkg_script);
  const char *postscript_path_container = glnx_strjoina ("/usr/", postscript_name);
  const char *postscript_path_host;
  gboolean mntpoint_created = FALSE;
  gboolean fuse_mounted = FALSE;
  g_autoptr(GPtrArray) bwrap_argv = g_ptr_array_new ();
  g_autoptr(GPtrArray) bwrap_argv_mallocd = g_ptr_array_new_with_free_func (g_free);
  GSpawnFlags bwrap_spawnflags = G_SPAWN_SEARCH_PATH;
  gboolean created_var_tmp = FALSE;

  if (!glnx_mkdtempat (AT_FDCWD, rofiles_mnt, 0700, error))
    goto out;

  mntpoint_created = TRUE;

  if (!g_spawn_sync (NULL, (char**)rofiles_argv, NULL, G_SPAWN_SEARCH_PATH,
                     child_setup_fchdir, GINT_TO_POINTER (rootfs_fd),
                     NULL, NULL, &estatus, error))
    goto out;
  if (!g_spawn_check_exit_status (estatus, error))
    {
      g_prefix_error (error, "Executing rofiles-fuse: ");
      goto out;
    }

  fuse_mounted = TRUE;

  postscript_path_host = glnx_strjoina (rofiles_mnt, "/", postscript_name);

  /* TODO - Create a pipe and send this to bwrap so it's inside the
   * tmpfs
   */
  if (!g_file_set_contents (postscript_path_host, script, -1, error))
    {
      g_prefix_error (error, "Writing script to %s: ", postscript_path_host);
      goto out;
    }
  if (chmod (postscript_path_host, 0755) != 0)
    {
      g_prefix_error (error, "chmod %s: ", postscript_path_host);
      goto out;
    }

  /* We need to make the mount point in the case where we're doing
   * package layering, since the host `/var` tree is empty.  We
   * *could* point at the real `/var`...but that seems
   * unnecessary/dangerous to me.  Daemons that need to perform data
   * migrations should do them as part of their systemd units and not
   * in %post.
   *
   * Another alternative would be to make a tmpfs with the compat
   * symlinks.
   */
  if (mkdirat (rootfs_fd, "var/tmp", 0755) < 0)
    {
      if (errno == EEXIST)
        ;
      else
        {
          glnx_set_error_from_errno (error);
          goto out;
        }
    }
  else
    created_var_tmp = TRUE;

  add_const_args (bwrap_argv,
                  WITH_BUBBLEWRAP_PATH,
                  "--bind", rofiles_mnt, "/usr",
                  "--dev", "/dev",
                  "--proc", "/proc",
                  "--dir", "/tmp",
                  "--chdir", "/",
                  /* Scripts can see a /var with compat links like alternatives */
                  "--ro-bind", "./var", "/var",
                  /* But no need to access persistent /tmp, so make it /tmp */
                  "--bind", "/tmp", "/var/tmp",
                  /* Allow RPM scripts to change the /etc defaults */
                  "--symlink", "usr/etc", "/etc",
                  "--ro-bind", "/sys/block", "/sys/block",
                  "--ro-bind", "/sys/bus", "/sys/bus",
                  "--ro-bind", "/sys/class", "/sys/class",
                  "--ro-bind", "/sys/dev", "/sys/dev",
                  "--ro-bind", "/sys/devices", "/sys/devices",
                  NULL);

  for (i = 0; i < G_N_ELEMENTS (usr_links); i++)
    {
      const char *subdir = usr_links[i];
      struct stat stbuf;
      char *path;

      if (!(fstatat (rootfs_fd, subdir, &stbuf, AT_SYMLINK_NOFOLLOW) == 0 && S_ISLNK (stbuf.st_mode)))
        continue;

      g_ptr_array_add (bwrap_argv, "--symlink");

      path = g_strconcat ("usr/", subdir, NULL);
      g_ptr_array_add (bwrap_argv_mallocd, path);
      g_ptr_array_add (bwrap_argv, path);

      path = g_strconcat ("/", subdir, NULL);
      g_ptr_array_add (bwrap_argv_mallocd, path);
      g_ptr_array_add (bwrap_argv, path);
    }

  { const char *debugscript = getenv ("RPMOSTREE_DEBUG_SCRIPT");
    if (g_strcmp0 (debugscript, pkg_script) == 0)
      {
        g_ptr_array_add (bwrap_argv, (char*)"/bin/bash");
        bwrap_spawnflags |= G_SPAWN_CHILD_INHERITS_STDIN;
      }
    else
      g_ptr_array_add (bwrap_argv, (char*)postscript_path_container);
  }
  g_ptr_array_add (bwrap_argv, NULL);

  if (!g_spawn_sync (NULL, (char**)bwrap_argv->pdata, NULL, bwrap_spawnflags,
                     child_setup_fchdir, GINT_TO_POINTER (rootfs_fd),
                     NULL, NULL, &estatus, error))
    {
      g_prefix_error (error, "Executing bwrap: ");
      goto out;
    }
  if (!g_spawn_check_exit_status (estatus, error))
    {
      g_prefix_error (error, "Executing bwrap: ");
      goto out;
    }

  ret = TRUE;
 out:
  if (fuse_mounted)
    {
      (void) unlink (postscript_path_host);
      fusermount_cleanup (rofiles_mnt);
    }
  if (mntpoint_created)
    (void) unlinkat (AT_FDCWD, rofiles_mnt, AT_REMOVEDIR);
  if (created_var_tmp)
    (void) unlinkat (rootfs_fd, "var/tmp", AT_REMOVEDIR);
  return ret;
}
int
rpmostree_container_builtin_assemble (int             argc,
                                      char          **argv,
                                      GCancellable   *cancellable,
                                      GError        **error)
{
  int exit_status = EXIT_FAILURE;
  GOptionContext *context = g_option_context_new ("NAME [PKGNAME PKGNAME...]");
  g_auto(ROContainerContext) rocctx_data = RO_CONTAINER_CONTEXT_INIT;
  ROContainerContext *rocctx = &rocctx_data;
  g_autoptr(RpmOstreeInstall) install = {0,};
  const char *specpath;
  struct stat stbuf;
  const char *name;
  g_autofree char *commit = NULL;
  const char *target_rootdir;
  g_autoptr(RpmOstreeTreespec) treespec = NULL;
  
  if (!rpmostree_option_context_parse (context,
                                       assemble_option_entries,
                                       &argc, &argv,
                                       RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD,
                                       cancellable,
                                       NULL,
                                       error))
    goto out;

  if (argc < 1)
    {
      rpmostree_usage_error (context, "SPEC must be specified", error);
      goto out;
    }

  specpath = argv[1];
  treespec = rpmostree_treespec_new_from_path (specpath, error);
  if (!treespec)
    goto out;

  name = rpmostree_treespec_get_ref (treespec);

  if (!roc_context_init (rocctx, error))
    goto out;

  target_rootdir = glnx_strjoina (name, ".0");

  if (fstatat (rocctx->roots_dfd, target_rootdir, &stbuf, AT_SYMLINK_NOFOLLOW) < 0)
    {
      if (errno != ENOENT)
        {
          glnx_set_error_from_errno (error);
          goto out;
        }
    }
  else
    {
      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                   "Tree %s already exists", target_rootdir);
      goto out;
    }

  if (!roc_context_prepare_for_root (rocctx, target_rootdir, treespec, cancellable, error))
    goto out;

  /* --- Downloading metadata --- */
  if (!rpmostree_context_download_metadata (rocctx->ctx, cancellable, error))
    goto out;

  /* --- Resolving dependencies --- */
  if (!rpmostree_context_prepare_install (rocctx->ctx, &install, cancellable, error))
    goto out;

  /* --- Download and import as necessary --- */
  if (!rpmostree_context_download_import (rocctx->ctx, install,
                                          cancellable, error))
    goto out;

  { glnx_fd_close int tmpdir_dfd = -1;

    if (!glnx_opendirat (rocctx->userroot_dfd, "tmp", TRUE, &tmpdir_dfd, error))
      goto out;
    
    if (!rpmostree_context_assemble_commit (rocctx->ctx, tmpdir_dfd,
                                            name,
                                            install,
                                            &commit,
                                            cancellable, error))
      goto out;
  }

  g_print ("Checking out %s @ %s...\n", name, commit);

  { OstreeRepoCheckoutOptions opts = { OSTREE_REPO_CHECKOUT_MODE_USER,
                                       OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES, };

    /* For now... to be crash safe we'd need to duplicate some of the
     * boot-uuid/fsync gating at a higher level.
     */
    opts.disable_fsync = TRUE;

    /* Also, what we really want here is some sort of sane lifecycle
     * management with whatever is running in the root.
     */
    if (!glnx_shutil_rm_rf_at (rocctx->roots_dfd, target_rootdir, cancellable, error))
      goto out;

    if (!ostree_repo_checkout_tree_at (rocctx->repo, &opts, rocctx->roots_dfd, target_rootdir,
                                       commit, cancellable, error))
      goto out;
  }

  g_print ("Checking out %s @ %s...done\n", name, commit);

  if (!symlink_at_replace (target_rootdir, rocctx->roots_dfd, name,
                           cancellable, error))
    goto out;

  g_print ("Creating current symlink...done\n");

  exit_status = EXIT_SUCCESS;
 out:
  return exit_status;
}
Esempio n. 5
0
/**
 * ot_file_replace_contents_at:
 * 
 * Like g_file_replace_contents(), except using a fd-relative
 * directory, and optionally enforces use of fdatasync().
 */
gboolean
ot_file_replace_contents_at (int             dfd,
                             const char     *path,
                             GBytes         *contents,
                             gboolean        datasync,
                             GCancellable   *cancellable,
                             GError        **error)
{
  gboolean ret = FALSE;
  int fd;
  g_autofree char *tmpname = NULL;
  g_autoptr(GOutputStream) stream = NULL;
  g_autoptr(GInputStream) instream = NULL;

  if (!gs_file_open_in_tmpdir_at (dfd, 0644,
                                  &tmpname, &stream,
                                  cancellable, error))
    goto out;

  g_assert (G_IS_FILE_DESCRIPTOR_BASED (stream));
  fd = g_file_descriptor_based_get_fd (G_FILE_DESCRIPTOR_BASED (stream));

  instream = g_memory_input_stream_new_from_bytes (contents);

  if (g_bytes_get_size (contents) > 0)
    {
      int r = posix_fallocate (fd, 0, g_bytes_get_size (contents));
      if (r != 0)
        {
          /* posix_fallocate is a weird deviation from errno standards */
          errno = r;
          glnx_set_error_from_errno (error);
          goto out;
        }
    }

  if (g_output_stream_splice (stream, instream, 0,
                              cancellable, error) < 0)
    goto out;

  if (datasync && fdatasync (fd) != 0)
    {
      glnx_set_error_from_errno (error);
      goto out;
    }

  if (!g_output_stream_close (stream, cancellable, error))
    goto out;

  if (renameat (dfd, tmpname, dfd, path) == -1)
    {
      glnx_set_error_from_errno (error);
      goto out;
    }

  g_clear_pointer (&tmpname, g_free);

  ret = TRUE;
 out:
  if (tmpname)
    (void) unlinkat (dfd, tmpname, 0);
  return ret;
}
Esempio n. 6
0
static gboolean
checkout_object_for_uncompressed_cache (OstreeRepo      *self,
                                        const char      *loose_path,
                                        GFileInfo       *src_info,
                                        GInputStream    *content,
                                        GCancellable    *cancellable,
                                        GError         **error)
{
  gboolean ret = FALSE;
  g_autofree char *temp_filename = NULL;
  g_autoptr(GOutputStream) temp_out = NULL;
  int fd;
  int res;
  guint32 file_mode;

  /* Don't make setuid files in uncompressed cache */
  file_mode = g_file_info_get_attribute_uint32 (src_info, "unix::mode");
  file_mode &= ~(S_ISUID|S_ISGID);

  if (!gs_file_open_in_tmpdir_at (self->tmp_dir_fd, file_mode,
                                  &temp_filename, &temp_out,
                                  cancellable, error))
    goto out;

  if (g_output_stream_splice (temp_out, content, 0, cancellable, error) < 0)
    goto out;

  if (!g_output_stream_flush (temp_out, cancellable, error))
    goto out;

  fd = g_file_descriptor_based_get_fd ((GFileDescriptorBased*)temp_out);

  if (!self->disable_fsync)
    {
      do
        res = fsync (fd);
      while (G_UNLIKELY (res == -1 && errno == EINTR));
      if (G_UNLIKELY (res == -1))
        {
          glnx_set_error_from_errno (error);
          goto out;
        }
    }

  if (!g_output_stream_close (temp_out, cancellable, error))
    goto out;

  if (!_ostree_repo_ensure_loose_objdir_at (self->uncompressed_objects_dir_fd,
                                            loose_path,
                                            cancellable, error))
    goto out;

  if (G_UNLIKELY (renameat (self->tmp_dir_fd, temp_filename,
                            self->uncompressed_objects_dir_fd, loose_path) == -1))
    {
      if (errno != EEXIST)
        {
          glnx_set_error_from_errno (error);
          g_prefix_error (error, "Storing file '%s': ", temp_filename);
          goto out;
        }
      else
        (void) unlinkat (self->tmp_dir_fd, temp_filename, 0);
    }

  ret = TRUE;
 out:
  return ret;
}
Esempio n. 7
0
/*
 * checkout_tree_at:
 * @self: Repo
 * @mode: Options controlling all files
 * @overwrite_mode: Whether or not to overwrite files
 * @destination_parent_fd: Place tree here
 * @destination_name: Use this name for tree
 * @source: Source tree
 * @source_info: Source info
 * @cancellable: Cancellable
 * @error: Error
 *
 * Like ostree_repo_checkout_tree(), but check out @source into the
 * relative @destination_name, located by @destination_parent_fd.
 */
static gboolean
checkout_tree_at (OstreeRepo                        *self,
                  OstreeRepoCheckoutOptions         *options,
                  int                                destination_parent_fd,
                  const char                        *destination_name,
                  OstreeRepoFile                    *source,
                  GFileInfo                         *source_info,
                  GCancellable                      *cancellable,
                  GError                           **error)
{
  gboolean ret = FALSE;
  gboolean did_exist = FALSE;
  glnx_fd_close int destination_dfd = -1;
  int res;
  g_autoptr(GVariant) xattrs = NULL;
  g_autoptr(GFileEnumerator) dir_enum = NULL;

  /* Create initially with mode 0700, then chown/chmod only when we're
   * done.  This avoids anyone else being able to operate on partially
   * constructed dirs.
   */
  do
    res = mkdirat (destination_parent_fd, destination_name, 0700);
  while (G_UNLIKELY (res == -1 && errno == EINTR));
  if (res == -1)
    {
      if (errno == EEXIST && options->overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES)
        did_exist = TRUE;
      else
        {
          glnx_set_error_from_errno (error);
          goto out;
        }
    }

  if (!glnx_opendirat (destination_parent_fd, destination_name, TRUE,
                       &destination_dfd, error))
    goto out;

  /* Set the xattrs now, so any derived labeling works */
  if (!did_exist && options->mode != OSTREE_REPO_CHECKOUT_MODE_USER)
    {
      if (!ostree_repo_file_get_xattrs (source, &xattrs, NULL, error))
        goto out;

      if (xattrs)
        {
          if (!glnx_fd_set_all_xattrs (destination_dfd, xattrs, cancellable, error))
            goto out;
        }
    }

  if (g_file_info_get_file_type (source_info) != G_FILE_TYPE_DIRECTORY)
    {
      ret = checkout_one_file_at (self, options,
                                  (GFile *) source,
                                  source_info,
                                  destination_dfd,
                                  g_file_info_get_name (source_info),
                                  cancellable, error);
      goto out;
    }
  dir_enum = g_file_enumerate_children ((GFile*)source,
                                        OSTREE_GIO_FAST_QUERYINFO, 
                                        G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
                                        cancellable, 
                                        error);
  if (!dir_enum)
    goto out;

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

      if (!g_file_enumerator_iterate (dir_enum, &file_info, &src_child,
                                      cancellable, error))
        goto out;
      if (file_info == NULL)
        break;

      name = g_file_info_get_name (file_info);

      if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY)
        {
          if (!checkout_tree_at (self, options,
                                 destination_dfd, name,
                                 (OstreeRepoFile*)src_child, file_info,
                                 cancellable, error))
            goto out;
        }
      else
        {
          if (!checkout_one_file_at (self, options,
                                     src_child, file_info,
                                     destination_dfd, name,
                                     cancellable, error))
            goto out;
        }
    }

  /* We do fchmod/fchown last so that no one else could access the
   * partially created directory and change content we're laying out.
   */
  if (!did_exist)
    {
      do
        res = fchmod (destination_dfd,
                      g_file_info_get_attribute_uint32 (source_info, "unix::mode"));
      while (G_UNLIKELY (res == -1 && errno == EINTR));
      if (G_UNLIKELY (res == -1))
        {
          glnx_set_error_from_errno (error);
          goto out;
        }
    }

  if (!did_exist && options->mode != OSTREE_REPO_CHECKOUT_MODE_USER)
    {
      do
        res = fchown (destination_dfd,
                      g_file_info_get_attribute_uint32 (source_info, "unix::uid"),
                      g_file_info_get_attribute_uint32 (source_info, "unix::gid"));
      while (G_UNLIKELY (res == -1 && errno == EINTR));
      if (G_UNLIKELY (res == -1))
        {
          glnx_set_error_from_errno (error);
          goto out;
        }
    }

  /* Set directory mtime to OSTREE_TIMESTAMP, so that it is constant for all checkouts.
   * Must be done after setting permissions and creating all children.
   */
  if (!did_exist)
    {
      const struct timespec times[2] = { { OSTREE_TIMESTAMP, UTIME_OMIT }, { OSTREE_TIMESTAMP, 0} };
      do
        res = futimens (destination_dfd, times);
      while (G_UNLIKELY (res == -1 && errno == EINTR));
      if (G_UNLIKELY (res == -1))
        {
          glnx_set_error_from_errno (error);
          goto out;
        }
    }

  if (fsync_is_enabled (self, options))
    {
      if (fsync (destination_dfd) == -1)
        {
          glnx_set_error_from_errno (error);
          goto out;
        }
    }

  ret = TRUE;
 out:
  return ret;
}
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;
}
static gboolean
dispatch_write (OstreeRepo                 *repo,
               StaticDeltaExecutionState  *state,
               GCancellable               *cancellable,  
               GError                    **error)
{
  gboolean ret = FALSE;
  guint64 content_size;
  guint64 content_offset;
      
  if (!read_varuint64 (state, &content_size, error))
    goto out;
  if (!read_varuint64 (state, &content_offset, error))
    goto out;

  if (state->stats_only)
    {
      ret = TRUE;
      goto out;
    }

  if (!state->have_obj)
    {
      if (state->read_source_fd != -1)
        {
          if (lseek (state->read_source_fd, content_offset, SEEK_SET) == -1)
            {
              glnx_set_error_from_errno (error);
              goto out;
            }
          while (content_size > 0)
            {
              char buf[4096];
              gssize bytes_read;

              do
                bytes_read = read (state->read_source_fd, buf, MIN(sizeof(buf), content_size));
              while (G_UNLIKELY (bytes_read == -1 && errno == EINTR));
              if (bytes_read == -1)
                {
                  glnx_set_error_from_errno (error);
                  goto out;
                }
              if (G_UNLIKELY (bytes_read == 0))
                {
                  g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                               "Unexpected EOF reading object %s", state->read_source_object);
                  goto out;
                }
              
              if (!content_out_write (repo, state, (guint8*)buf, bytes_read,
                                      cancellable, error))
                goto out;
              
              content_size -= bytes_read;
            }
        }
      else
        {
          if (!validate_ofs (state, content_offset, content_size, error))
            goto out;

          if (!content_out_write (repo, state, state->payload_data + content_offset, content_size,
                                  cancellable, error))
            goto out;
        }
    }
  
  ret = TRUE;
 out:
  if (!ret)
    g_prefix_error (error, "opcode open-splice-and-close: ");
  return ret;
}
gboolean
_ostree_static_delta_part_open (GInputStream   *part_in,
                                GBytes         *inline_part_bytes,
                                OstreeStaticDeltaOpenFlags flags,
                                const char     *expected_checksum,
                                GVariant    **out_part,
                                GCancellable *cancellable,
                                GError      **error)
{
    gboolean ret = FALSE;
    const gboolean trusted = (flags & OSTREE_STATIC_DELTA_OPEN_FLAGS_VARIANT_TRUSTED) > 0;
    const gboolean skip_checksum = (flags & OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM) > 0;
    gsize bytes_read;
    guint8 comptype;
    g_autoptr(GChecksum) checksum = NULL;
    g_autoptr(GInputStream) checksum_in = NULL;
    g_autoptr(GVariant) ret_part = NULL;
    GInputStream *source_in;

    /* We either take a fd or a GBytes reference */
    g_return_val_if_fail (G_IS_FILE_DESCRIPTOR_BASED (part_in) || inline_part_bytes != NULL, FALSE);
    g_return_val_if_fail (skip_checksum || expected_checksum != NULL, FALSE);

    if (!skip_checksum)
    {
        checksum = g_checksum_new (G_CHECKSUM_SHA256);
        checksum_in = (GInputStream*)ostree_checksum_input_stream_new (part_in, checksum);
        source_in = checksum_in;
    }
    else
    {
        source_in = part_in;
    }

    {   guint8 buf[1];
        /* First byte is compression type */
        if (!g_input_stream_read_all (source_in, buf, sizeof(buf), &bytes_read,
                                      cancellable, error))
        {
            g_prefix_error (error, "Reading initial compression flag byte: ");
            goto out;
        }
        comptype = buf[0];
    }

    switch (comptype)
    {
    case 0:
        if (!inline_part_bytes)
        {
            int part_fd = g_file_descriptor_based_get_fd ((GFileDescriptorBased*)part_in);

            /* No compression, no checksums - a fast path */
            if (!ot_util_variant_map_fd (part_fd, 1, G_VARIANT_TYPE (OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT_V0),
                                         trusted, &ret_part, error))
                goto out;
        }
        else
        {
            g_autoptr(GBytes) content_bytes = g_bytes_new_from_bytes (inline_part_bytes, 1,
                                              g_bytes_get_size (inline_part_bytes) - 1);
            ret_part = g_variant_new_from_bytes (G_VARIANT_TYPE (OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT_V0),
                                                 content_bytes, trusted);
            g_variant_ref_sink (ret_part);
        }

        if (!skip_checksum)
            g_checksum_update (checksum, g_variant_get_data (ret_part),
                               g_variant_get_size (ret_part));

        break;
    case 'x':
    {
        g_autofree char *tmppath = g_strdup ("/var/tmp/ostree-delta-XXXXXX");
        g_autoptr(GConverter) decomp = (GConverter*) _ostree_lzma_decompressor_new ();
        g_autoptr(GInputStream) convin = g_converter_input_stream_new (source_in, decomp);
        g_autoptr(GOutputStream) unpacked_out = NULL;
        glnx_fd_close int unpacked_fd = -1;
        gssize n_bytes_written;

        unpacked_fd = g_mkstemp_full (tmppath, O_RDWR | O_CLOEXEC, 0640);
        if (unpacked_fd < 0)
        {
            glnx_set_error_from_errno (error);
            goto out;
        }

        /* Now make it autocleanup on process exit - in the future, we
         * should consider caching unpacked deltas as well.
         */
        if (unlink (tmppath) < 0)
        {
            glnx_set_error_from_errno (error);
            goto out;
        }

        unpacked_out = g_unix_output_stream_new (unpacked_fd, FALSE);

        n_bytes_written = g_output_stream_splice (unpacked_out, convin,
                          G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
                          G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
                          cancellable, error);
        if (n_bytes_written < 0)
            goto out;

        if (!ot_util_variant_map_fd (unpacked_fd, 0, G_VARIANT_TYPE (OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT_V0),
                                     trusted, &ret_part, error))
            goto out;
    }
    break;
    default:
        g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                     "Invalid compression type '%u'", comptype);
        goto out;
    }

    if (checksum)
    {
        const char *actual_checksum = g_checksum_get_string (checksum);
        g_assert (expected_checksum != NULL);
        if (strcmp (actual_checksum, expected_checksum) != 0)
        {
            g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                         "Checksum mismatch in static delta part; expected=%s actual=%s",
                         expected_checksum, actual_checksum);
            goto out;
        }
    }

    ret = TRUE;
    *out_part = g_steal_pointer (&ret_part);
out:
    return ret;
}
static gboolean
show_one_part (OstreeRepo                    *self,
               gboolean                       swap_endian,
               const char                    *from,
               const char                    *to,
               GVariant                      *meta_entries,
               guint                          i,
               guint64                       *total_size_ref,
               guint64                       *total_usize_ref,
               GCancellable                  *cancellable,
               GError                      **error)
{
    gboolean ret = FALSE;
    guint32 version;
    guint64 size, usize;
    g_autoptr(GVariant) objects = NULL;
    g_autoptr(GInputStream) part_in = NULL;
    g_autoptr(GVariant) part = NULL;
    g_autofree char *part_path = _ostree_get_relative_static_delta_part_path (from, to, i);
    gint part_fd = -1;

    g_variant_get_child (meta_entries, i, "(u@aytt@ay)", &version, NULL, &size, &usize, &objects);
    size = maybe_swap_endian_u64 (swap_endian, size);
    usize = maybe_swap_endian_u64 (swap_endian, usize);
    *total_size_ref += size;
    *total_usize_ref += usize;
    g_print ("PartMeta%u: nobjects=%u size=%" G_GUINT64_FORMAT " usize=%" G_GUINT64_FORMAT "\n",
             i, (guint)(g_variant_get_size (objects) / OSTREE_STATIC_DELTA_OBJTYPE_CSUM_LEN), size, usize);

    part_fd = openat (self->repo_dir_fd, part_path, O_RDONLY | O_CLOEXEC);
    if (part_fd < 0)
    {
        glnx_set_error_from_errno (error);
        goto out;
    }

    part_in = g_unix_input_stream_new (part_fd, FALSE);

    if (!_ostree_static_delta_part_open (part_in, NULL,
                                         OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM,
                                         NULL,
                                         &part,
                                         cancellable, error))
        goto out;

    {   g_autoptr(GVariant) modes = NULL;
        g_autoptr(GVariant) xattrs = NULL;
        g_autoptr(GVariant) blob = NULL;
        g_autoptr(GVariant) ops = NULL;
        OstreeDeltaExecuteStats stats = { { 0, }, };

        g_variant_get (part, "(@a(uuu)@aa(ayay)@ay@ay)",
                       &modes, &xattrs, &blob, &ops);

        g_print ("PartPayload%u: nmodes=%" G_GUINT64_FORMAT
                 " nxattrs=%" G_GUINT64_FORMAT
                 " blobsize=%" G_GUINT64_FORMAT
                 " opsize=%" G_GUINT64_FORMAT
                 "\n",
                 i,
                 (guint64)g_variant_n_children (modes),
                 (guint64)g_variant_n_children (xattrs),
                 (guint64)g_variant_n_children (blob),
                 (guint64)g_variant_n_children (ops));

        if (!_ostree_static_delta_part_execute (self, objects,
                                                part, TRUE, TRUE,
                                                &stats, cancellable, error))
            goto out;

        {   const guint *n_ops = stats.n_ops_executed;
            g_print ("PartPayloadOps%u: openspliceclose=%u open=%u write=%u setread=%u "
                     "unsetread=%u close=%u bspatch=%u\n",
                     i, n_ops[0], n_ops[1], n_ops[2], n_ops[3], n_ops[4], n_ops[5], n_ops[6]);
        }
    }

    ret = TRUE;
out:
    return ret;
}
/**
 * ostree_repo_static_delta_execute_offline:
 * @self: Repo
 * @dir_or_file: Path to a directory containing static delta data, or directly to the superblock
 * @skip_validation: If %TRUE, assume data integrity
 * @cancellable: Cancellable
 * @error: Error
 *
 * Given a directory representing an already-downloaded static delta
 * on disk, apply it, generating a new commit.  The directory must be
 * named with the form "FROM-TO", where both are checksums, and it
 * must contain a file named "superblock", along with at least one part.
 */
gboolean
ostree_repo_static_delta_execute_offline (OstreeRepo                    *self,
        GFile                         *dir_or_file,
        gboolean                       skip_validation,
        GCancellable                  *cancellable,
        GError                      **error)
{
    gboolean ret = FALSE;
    guint i, n;
    const char *dir_or_file_path = NULL;
    glnx_fd_close int meta_fd = -1;
    glnx_fd_close int dfd = -1;
    g_autoptr(GVariant) meta = NULL;
    g_autoptr(GVariant) headers = NULL;
    g_autoptr(GVariant) metadata = NULL;
    g_autoptr(GVariant) fallback = NULL;
    g_autofree char *to_checksum = NULL;
    g_autofree char *from_checksum = NULL;
    g_autofree char *basename = NULL;

    dir_or_file_path = gs_file_get_path_cached (dir_or_file);

    /* First, try opening it as a directory */
    dfd = glnx_opendirat_with_errno (AT_FDCWD, dir_or_file_path, TRUE);
    if (dfd < 0)
    {
        if (errno != ENOTDIR)
        {
            glnx_set_error_from_errno (error);
            goto out;
        }
        else
        {
            g_autofree char *dir = dirname (g_strdup (dir_or_file_path));
            basename = g_path_get_basename (dir_or_file_path);

            if (!glnx_opendirat (AT_FDCWD, dir, TRUE, &dfd, error))
                goto out;
        }
    }
    else
        basename = g_strdup ("superblock");

    meta_fd = openat (dfd, basename, O_RDONLY | O_CLOEXEC);
    if (meta_fd < 0)
    {
        glnx_set_error_from_errno (error);
        goto out;
    }

    if (!ot_util_variant_map_fd (meta_fd, 0, G_VARIANT_TYPE (OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT),
                                 FALSE, &meta, error))
        goto out;

    /* Parsing OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT */

    metadata = g_variant_get_child_value (meta, 0);

    /* Write the to-commit object */
    {
        g_autoptr(GVariant) to_csum_v = NULL;
        g_autoptr(GVariant) from_csum_v = NULL;
        g_autoptr(GVariant) to_commit = NULL;
        gboolean have_to_commit;
        gboolean have_from_commit;

        to_csum_v = g_variant_get_child_value (meta, 3);
        if (!ostree_validate_structureof_csum_v (to_csum_v, error))
            goto out;
        to_checksum = ostree_checksum_from_bytes_v (to_csum_v);

        from_csum_v = g_variant_get_child_value (meta, 2);
        if (g_variant_n_children (from_csum_v) > 0)
        {
            if (!ostree_validate_structureof_csum_v (from_csum_v, error))
                goto out;
            from_checksum = ostree_checksum_from_bytes_v (from_csum_v);

            if (!ostree_repo_has_object (self, OSTREE_OBJECT_TYPE_COMMIT, from_checksum,
                                         &have_from_commit, cancellable, error))
                goto out;

            if (!have_from_commit)
            {
                g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                             "Commit %s, which is the delta source, is not in repository", from_checksum);
                goto out;
            }
        }

        if (!ostree_repo_has_object (self, OSTREE_OBJECT_TYPE_COMMIT, to_checksum,
                                     &have_to_commit, cancellable, error))
            goto out;

        if (!have_to_commit)
        {
            g_autofree char *detached_path = _ostree_get_relative_static_delta_path (from_checksum, to_checksum, "commitmeta");
            g_autoptr(GVariant) detached_data = NULL;

            detached_data = g_variant_lookup_value (metadata, detached_path, G_VARIANT_TYPE("a{sv}"));
            if (detached_data && !ostree_repo_write_commit_detached_metadata (self,
                    to_checksum,
                    detached_data,
                    cancellable,
                    error))
                goto out;

            to_commit = g_variant_get_child_value (meta, 4);
            if (!ostree_repo_write_metadata (self, OSTREE_OBJECT_TYPE_COMMIT,
                                             to_checksum, to_commit, NULL,
                                             cancellable, error))
                goto out;
        }
    }

    fallback = g_variant_get_child_value (meta, 7);
    if (g_variant_n_children (fallback) > 0)
    {
        g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                     "Cannot execute delta offline: contains nonempty http fallback entries");
        goto out;
    }

    headers = g_variant_get_child_value (meta, 6);
    n = g_variant_n_children (headers);
    for (i = 0; i < n; i++)
    {
        guint32 version;
        guint64 size;
        guint64 usize;
        const guchar *csum;
        char checksum[OSTREE_SHA256_STRING_LEN+1];
        gboolean have_all;
        g_autoptr(GInputStream) part_in = NULL;
        g_autoptr(GBytes) delta_data = NULL;
        g_autoptr(GVariant) inline_part_data = NULL;
        g_autoptr(GVariant) header = NULL;
        g_autoptr(GVariant) csum_v = NULL;
        g_autoptr(GVariant) objects = NULL;
        g_autoptr(GVariant) part = NULL;
        g_autofree char *deltapart_path = NULL;
        OstreeStaticDeltaOpenFlags delta_open_flags =
            skip_validation ? OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM : 0;

        header = g_variant_get_child_value (headers, i);
        g_variant_get (header, "(u@aytt@ay)", &version, &csum_v, &size, &usize, &objects);

        if (version > OSTREE_DELTAPART_VERSION)
        {
            g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                         "Delta part has too new version %u", version);
            goto out;
        }

        if (!_ostree_repo_static_delta_part_have_all_objects (self, objects, &have_all,
                cancellable, error))
            goto out;

        /* If we already have these objects, don't bother executing the
         * static delta.
         */
        if (have_all)
            continue;

        csum = ostree_checksum_bytes_peek_validate (csum_v, error);
        if (!csum)
            goto out;
        ostree_checksum_inplace_from_bytes (csum, checksum);

        deltapart_path =
            _ostree_get_relative_static_delta_part_path (from_checksum, to_checksum, i);

        inline_part_data = g_variant_lookup_value (metadata, deltapart_path, G_VARIANT_TYPE("(yay)"));
        if (inline_part_data)
        {
            g_autoptr(GBytes) inline_part_bytes = g_variant_get_data_as_bytes (inline_part_data);
            part_in = g_memory_input_stream_new_from_bytes (inline_part_bytes);

            /* For inline parts, we don't checksum, because it's
             * included with the metadata, so we're not trying to
             * protect against MITM or such.  Non-security related
             * checksums should be done at the underlying storage layer.
             */
            delta_open_flags |= OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM;

            if (!_ostree_static_delta_part_open (part_in, inline_part_bytes,
                                                 delta_open_flags,
                                                 NULL,
                                                 &part,
                                                 cancellable, error))
                goto out;
        }
        else
        {
            g_autofree char *relpath = g_strdup_printf ("%u", i); /* TODO avoid malloc here */
            glnx_fd_close int part_fd = openat (dfd, relpath, O_RDONLY | O_CLOEXEC);
            if (part_fd < 0)
            {
                glnx_set_error_from_errno (error);
                g_prefix_error (error, "Opening deltapart '%s': ", deltapart_path);
                goto out;
            }

            part_in = g_unix_input_stream_new (part_fd, FALSE);

            if (!_ostree_static_delta_part_open (part_in, NULL,
                                                 delta_open_flags,
                                                 checksum,
                                                 &part,
                                                 cancellable, error))
                goto out;
        }

        if (!_ostree_static_delta_part_execute (self, objects, part, skip_validation,
                                                FALSE, NULL,
                                                cancellable, error))
        {
            g_prefix_error (error, "Executing delta part %i: ", i);
            goto out;
        }
    }

    ret = TRUE;
out:
    return ret;
}
Esempio n. 13
0
gboolean
ostree_builtin_trivial_httpd (int argc, char **argv, GCancellable *cancellable, GError **error)
{
  gboolean ret = FALSE;
  GOptionContext *context;
  const char *dirpath;
  OtTrivialHttpd appstruct = { 0, };
  OtTrivialHttpd *app = &appstruct;
  glnx_unref_object SoupServer *server = NULL;
  g_autoptr(GFileMonitor) dirmon = NULL;

  context = g_option_context_new ("[DIR] - Simple webserver");

  if (!ostree_option_context_parse (context, options, &argc, &argv, OSTREE_BUILTIN_FLAG_NO_REPO, NULL, cancellable, error))
    goto out;

  if (argc > 1)
    dirpath = argv[1];
  else
    dirpath = ".";

  app->root = g_file_new_for_path (dirpath);

  if (!(opt_random_500s_percentage >= 0 && opt_random_500s_percentage <= 99))
    {
      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                   "Invalid --random-500s=%u", opt_random_500s_percentage);
      goto out;
    }

  if (opt_log)
    {
      GOutputStream *stream = NULL;

      if (g_strcmp0 (opt_log, "-") == 0)
        {
          if (opt_daemonize)
            {
              ot_util_usage_error (context, "Cannot use --log-file=- and --daemonize at the same time", error);
              goto out;
            }
          stream = G_OUTPUT_STREAM (g_unix_output_stream_new (STDOUT_FILENO, FALSE));
        }
      else
        {
          g_autoptr(GFile) log_file;
          GFileOutputStream* log_stream;

          log_file = g_file_new_for_path (opt_log);
          log_stream = g_file_create (log_file,
                                      G_FILE_CREATE_PRIVATE,
                                      cancellable,
                                      error);
          if (!log_stream)
            goto out;
          stream = G_OUTPUT_STREAM (log_stream);
        }

      app->log = stream;
    }

#if SOUP_CHECK_VERSION(2, 48, 0)
  server = soup_server_new (SOUP_SERVER_SERVER_HEADER, "ostree-httpd ", NULL);
  if (!soup_server_listen_all (server, opt_port, 0, error))
    goto out;
#else
  server = soup_server_new (SOUP_SERVER_PORT, opt_port,
                            SOUP_SERVER_SERVER_HEADER, "ostree-httpd ",
                            NULL);
#endif

  soup_server_add_handler (server, NULL, httpd_callback, app, NULL);
  if (opt_port_file)
    {
      g_autofree char *portstr = NULL;
#if SOUP_CHECK_VERSION(2, 48, 0)
      GSList *listeners = soup_server_get_listeners (server);
      g_autoptr(GSocket) listener = NULL;
      g_autoptr(GSocketAddress) addr = NULL;
      
      g_assert (listeners);
      listener = g_object_ref (listeners->data);
      g_slist_free (listeners);
      listeners = NULL;
      addr = g_socket_get_local_address (listener, error);
      if (!addr)
        goto out;

      g_assert (G_IS_INET_SOCKET_ADDRESS (addr));
      
      portstr = g_strdup_printf ("%u\n", g_inet_socket_address_get_port ((GInetSocketAddress*)addr));
#else
      portstr = g_strdup_printf ("%u\n", soup_server_get_port (server));
#endif

      if (g_strcmp0 ("-", opt_port_file) == 0)
        {
          fputs (portstr, stdout); // not g_print - this must go to stdout, not a handler
          fflush (stdout);
        }
      else if (!g_file_set_contents (opt_port_file, portstr, strlen (portstr), error))
        goto out;
    }
#if !SOUP_CHECK_VERSION(2, 48, 0)
  soup_server_run_async (server);
#endif
  
  if (opt_daemonize)
    {
      pid_t pid = fork();
      if (pid == -1)
        {
          int errsv = errno;
          g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
                               g_strerror (errsv));
          goto out;
        }
      else if (pid > 0)
        {
          /* Parent */
          _exit (0);
        }
      /* Child, continue */
      /* Daemonising: close stdout/stderr so $() et al work on us */
      fclose (stdout);
      fclose (stdin);
    }
  else
    {
      /* Since we're used for testing purposes, let's just do this by
       * default.  This ensures we exit when our parent does.
       */
      if (prctl (PR_SET_PDEATHSIG, SIGTERM) != 0)
        {
          if (errno != ENOSYS)
            {
              glnx_set_error_from_errno (error);
              goto out;
            }
        }
    }

  app->running = TRUE;
  if (opt_autoexit)
    {
      gboolean is_symlink = FALSE;
      g_autoptr(GFileInfo) info = NULL;

      info = g_file_query_info (app->root,
                                G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK,
                                G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
                                cancellable, error);
      if (!info)
        goto out;

      is_symlink = g_file_info_get_is_symlink (info);

      if (is_symlink)
        dirmon = g_file_monitor_file (app->root, 0, cancellable, error);
      else
        dirmon = g_file_monitor_directory (app->root, 0, cancellable, error);

      if (!dirmon)
        goto out;
      g_signal_connect (dirmon, "changed", G_CALLBACK (on_dir_changed), app);
    }
  {
    g_autofree gchar *path = g_file_get_path (app->root);
    httpd_log (app, "serving at root %s\n", path);
  }
  while (app->running)
    g_main_context_iteration (NULL, TRUE);

  ret = TRUE;
 out:
  g_clear_object (&app->root);
  g_clear_object (&app->log);
  if (context)
    g_option_context_free (context);
  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;
}
Esempio n. 15
0
static gboolean
checkout_file_from_input_at (OstreeRepo     *self,
                             OstreeRepoCheckoutOptions *options,
                             GFileInfo      *file_info,
                             GVariant       *xattrs,
                             GInputStream   *input,
                             int             destination_dfd,
                             const char     *destination_name,
                             GCancellable   *cancellable,
                             GError        **error)
{
  gboolean ret = FALSE;
  int res;

  if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_SYMBOLIC_LINK)
    {
      do
        res = symlinkat (g_file_info_get_symlink_target (file_info),
                         destination_dfd, destination_name);
      while (G_UNLIKELY (res == -1 && errno == EINTR));
      if (res == -1)
        {
          glnx_set_error_from_errno (error);
          goto out;
        }

      if (options->mode != OSTREE_REPO_CHECKOUT_MODE_USER)
        {
          if (G_UNLIKELY (fchownat (destination_dfd, destination_name,
                                    g_file_info_get_attribute_uint32 (file_info, "unix::uid"),
                                    g_file_info_get_attribute_uint32 (file_info, "unix::gid"),
                                    AT_SYMLINK_NOFOLLOW) == -1))
            {
              glnx_set_error_from_errno (error);
              goto out;
            }

          if (xattrs)
            {
              if (!glnx_dfd_name_set_all_xattrs (destination_dfd, destination_name,
                                                   xattrs, cancellable, error))
                goto out;
            }
        }
    }
  else if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR)
    {
      g_autoptr(GOutputStream) temp_out = NULL;
      int fd;
      guint32 file_mode;

      file_mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode");
      /* Don't make setuid files on checkout when we're doing --user */
      if (options->mode == OSTREE_REPO_CHECKOUT_MODE_USER)
        file_mode &= ~(S_ISUID|S_ISGID);

      do
        fd = openat (destination_dfd, destination_name, O_WRONLY | O_CREAT | O_EXCL, file_mode);
      while (G_UNLIKELY (fd == -1 && errno == EINTR));
      if (fd == -1)
        {
          glnx_set_error_from_errno (error);
          goto out;
        }
      temp_out = g_unix_output_stream_new (fd, TRUE);
      fd = -1; /* Transfer ownership */

      if (!write_regular_file_content (self, options, temp_out, file_info, xattrs, input,
                                       cancellable, error))
        goto out;
    }
  else
    g_assert_not_reached ();
  
  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;
}
Esempio n. 17
0
/*
 * This function creates a file under a temporary name, then rename()s
 * it into place.  This implements union-like behavior.
 */
static gboolean
checkout_file_unioning_from_input_at (OstreeRepo     *repo,
                                      OstreeRepoCheckoutOptions  *options,
                                      GFileInfo      *file_info,
                                      GVariant       *xattrs,
                                      GInputStream   *input,
                                      int             destination_dfd,
                                      const char     *destination_name,
                                      GCancellable   *cancellable,
                                      GError        **error)
{
  gboolean ret = FALSE;
  g_autofree char *temp_filename = NULL;

  if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_SYMBOLIC_LINK)
    {
      if (!_ostree_make_temporary_symlink_at (destination_dfd,
                                              g_file_info_get_symlink_target (file_info),
                                              &temp_filename,
                                              cancellable, error))
        goto out;
          
      if (xattrs)
        {
          if (!glnx_dfd_name_set_all_xattrs (destination_dfd, temp_filename,
                                               xattrs, cancellable, error))
            goto out;
        }
    }
  else if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR)
    {
      g_autoptr(GOutputStream) temp_out = NULL;
      guint32 file_mode;

      file_mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode");
      /* Don't make setuid files on checkout when we're doing --user */
      if (options->mode == OSTREE_REPO_CHECKOUT_MODE_USER)
        file_mode &= ~(S_ISUID|S_ISGID);

      if (!gs_file_open_in_tmpdir_at (destination_dfd, file_mode,
                                      &temp_filename, &temp_out,
                                      cancellable, error))
        goto out;

      if (!write_regular_file_content (repo, options, temp_out, file_info, xattrs, input,
                                       cancellable, error))
        goto out;
    }
  else
    g_assert_not_reached ();

  if (G_UNLIKELY (renameat (destination_dfd, temp_filename,
                            destination_dfd, destination_name) == -1))
    {
      glnx_set_error_from_errno (error);
      goto out;
    }

  ret = TRUE;
 out:
  return ret;
}
Esempio n. 18
0
static gboolean
find_booted_deployment (OstreeSysroot       *self,
                        GPtrArray           *deployments,
                        OstreeDeployment   **out_deployment,
                        GCancellable        *cancellable,
                        GError             **error)
{
  gboolean ret = FALSE;
  struct stat root_stbuf;
  struct stat self_stbuf;
  glnx_unref_object OstreeDeployment *ret_deployment = NULL;

  if (stat ("/", &root_stbuf) != 0)
    {
      glnx_set_error_from_errno (error);
      goto out;
    }

  if (!ensure_sysroot_fd (self, error))
    goto out;

  if (fstat (self->sysroot_fd, &self_stbuf) != 0)
    {
      glnx_set_error_from_errno (error);
      goto out;
    }

  if (root_stbuf.st_dev == self_stbuf.st_dev &&
      root_stbuf.st_ino == self_stbuf.st_ino)
    { 
      guint i;
      const char *bootlink_arg;
      __attribute__((cleanup(_ostree_kernel_args_cleanup))) OstreeKernelArgs *kernel_args = NULL;
      
      if (!parse_kernel_commandline (&kernel_args, cancellable, error))
        goto out;
      
      bootlink_arg = _ostree_kernel_args_get_last_value (kernel_args, "ostree");
      if (bootlink_arg)
        {
          for (i = 0; i < deployments->len; i++)
            {
              OstreeDeployment *deployment = deployments->pdata[i];
              g_autofree char *deployment_path = ostree_sysroot_get_deployment_dirpath (self, deployment);
              struct stat stbuf;

              if (fstatat (self->sysroot_fd, deployment_path, &stbuf, 0) != 0)
                {
                  glnx_set_error_from_errno (error);
                  goto out;
                }

              if (stbuf.st_dev == root_stbuf.st_dev &&
                  stbuf.st_ino == root_stbuf.st_ino)
                {
                  ret_deployment = g_object_ref (deployment);
                  break;
                }
            }

          if (ret_deployment == NULL)
            {
              g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                           "Unexpected state: ostree= kernel argument found, but / is not a deployment root");
              goto out;
            }
        }
      else
        {
          /* Not an ostree system */
        }
    }

  ret = TRUE;
  ot_transfer_out_value (out_deployment, &ret_deployment);
 out:
  return ret;
}
Esempio n. 19
0
static gboolean
checkout_one_file_at (OstreeRepo                        *repo,
                      OstreeRepoCheckoutOptions         *options,
                      GFile                             *source,
                      GFileInfo                         *source_info,
                      int                                destination_dfd,
                      const char                        *destination_name,
                      GCancellable                      *cancellable,
                      GError                           **error)
{
  gboolean ret = FALSE;
  const char *checksum;
  gboolean is_symlink;
  gboolean can_cache;
  gboolean need_copy = TRUE;
  char loose_path_buf[_OSTREE_LOOSE_PATH_MAX];
  g_autoptr(GInputStream) input = NULL;
  g_autoptr(GVariant) xattrs = NULL;
  gboolean is_whiteout;

  is_symlink = g_file_info_get_file_type (source_info) == G_FILE_TYPE_SYMBOLIC_LINK;

  checksum = ostree_repo_file_get_checksum ((OstreeRepoFile*)source);

  is_whiteout = !is_symlink && options->process_whiteouts &&
    g_str_has_prefix (destination_name, WHITEOUT_PREFIX);

  /* First, see if it's a Docker whiteout,
   * https://github.com/docker/docker/blob/1a714e76a2cb9008cd19609059e9988ff1660b78/pkg/archive/whiteouts.go
   */
  if (is_whiteout)
    {
      const char *name = destination_name + (sizeof (WHITEOUT_PREFIX) - 1);

      if (!name[0])
        {
          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                       "Invalid empty whiteout '%s'", name);
          goto out;
        }

      g_assert (name[0] != '/'); /* Sanity */

      if (!glnx_shutil_rm_rf_at (destination_dfd, name, cancellable, error))
        goto out;

      need_copy = FALSE;
    }
  else if (!is_symlink)
    {
      gboolean did_hardlink = FALSE;
      /* Try to do a hardlink first, if it's a regular file.  This also
       * traverses all parent repos.
       */
      OstreeRepo *current_repo = repo;

      while (current_repo)
        {
          gboolean is_bare = ((current_repo->mode == OSTREE_REPO_MODE_BARE
                               && options->mode == OSTREE_REPO_CHECKOUT_MODE_NONE) ||
                              (current_repo->mode == OSTREE_REPO_MODE_BARE_USER
                               && options->mode == OSTREE_REPO_CHECKOUT_MODE_USER));
          gboolean current_can_cache = (options->enable_uncompressed_cache
                                        && current_repo->enable_uncompressed_cache);
          gboolean is_archive_z2_with_cache = (current_repo->mode == OSTREE_REPO_MODE_ARCHIVE_Z2
                                               && options->mode == OSTREE_REPO_CHECKOUT_MODE_USER
                                               && current_can_cache);

          /* But only under these conditions */
          if (is_bare || is_archive_z2_with_cache)
            {
              /* Override repo mode; for archive-z2 we're looking in
                 the cache, which is in "bare" form */
              _ostree_loose_path (loose_path_buf, checksum, OSTREE_OBJECT_TYPE_FILE, OSTREE_REPO_MODE_BARE);
              if (!checkout_file_hardlink (current_repo,
                                           options,
                                           loose_path_buf,
                                           destination_dfd, destination_name,
                                           TRUE, &did_hardlink,
                                           cancellable, error))
                goto out;

              if (did_hardlink && options->devino_to_csum_cache)
                {
                  struct stat stbuf;
                  OstreeDevIno *key;
                  
                  if (TEMP_FAILURE_RETRY (fstatat (destination_dfd, destination_name, &stbuf, AT_SYMLINK_NOFOLLOW)) != 0)
                    {
                      glnx_set_error_from_errno (error);
                      goto out;
                    }
                  
                  key = g_new (OstreeDevIno, 1);
                  key->dev = stbuf.st_dev;
                  key->ino = stbuf.st_ino;
                  memcpy (key->checksum, checksum, OSTREE_SHA256_STRING_LEN+1);
                  
                  g_hash_table_add ((GHashTable*)options->devino_to_csum_cache, key);
                }

              if (did_hardlink)
                break;
            }
          current_repo = current_repo->parent_repo;
        }

      need_copy = !did_hardlink;
    }

  can_cache = (options->enable_uncompressed_cache
               && repo->enable_uncompressed_cache);

  /* Ok, if we're archive-z2 and we didn't find an object, uncompress
   * it now, stick it in the cache, and then hardlink to that.
   */
  if (can_cache
      && !is_whiteout
      && !is_symlink
      && need_copy
      && repo->mode == OSTREE_REPO_MODE_ARCHIVE_Z2
      && options->mode == OSTREE_REPO_CHECKOUT_MODE_USER)
    {
      gboolean did_hardlink;
      
      if (!ostree_repo_load_file (repo, checksum, &input, NULL, NULL,
                                  cancellable, error))
        goto out;

      /* Overwrite any parent repo from earlier */
      _ostree_loose_path (loose_path_buf, checksum, OSTREE_OBJECT_TYPE_FILE, OSTREE_REPO_MODE_BARE);

      if (!checkout_object_for_uncompressed_cache (repo, loose_path_buf,
                                                   source_info, input,
                                                   cancellable, error))
        {
          g_prefix_error (error, "Unpacking loose object %s: ", checksum);
          goto out;
        }
      
      g_clear_object (&input);

      /* Store the 2-byte objdir prefix (e.g. e3) in a set.  The basic
       * idea here is that if we had to unpack an object, it's very
       * likely we're replacing some other object, so we may need a GC.
       *
       * This model ensures that we do work roughly proportional to
       * the size of the changes.  For example, we don't scan any
       * directories if we didn't modify anything, meaning you can
       * checkout the same tree multiple times very quickly.
       *
       * This is also scale independent; we don't hardcode e.g. looking
       * at 1000 objects.
       *
       * The downside is that if we're unlucky, we may not free
       * an object for quite some time.
       */
      g_mutex_lock (&repo->cache_lock);
      {
        gpointer key = GUINT_TO_POINTER ((g_ascii_xdigit_value (checksum[0]) << 4) + 
                                         g_ascii_xdigit_value (checksum[1]));
        if (repo->updated_uncompressed_dirs == NULL)
          repo->updated_uncompressed_dirs = g_hash_table_new (NULL, NULL);
        g_hash_table_insert (repo->updated_uncompressed_dirs, key, key);
      }
      g_mutex_unlock (&repo->cache_lock);

      if (!checkout_file_hardlink (repo, options, loose_path_buf,
                                   destination_dfd, destination_name,
                                   FALSE, &did_hardlink,
                                   cancellable, error))
        {
          g_prefix_error (error, "Using new cached uncompressed hardlink of %s to %s: ", checksum, destination_name);
          goto out;
        }

      need_copy = !did_hardlink;
    }

  /* Fall back to copy if we couldn't hardlink */
  if (need_copy)
    {
      if (!ostree_repo_load_file (repo, checksum, &input, NULL, &xattrs,
                                  cancellable, error))
        goto out;

      if (options->overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES)
        {
          if (!checkout_file_unioning_from_input_at (repo, options, source_info, xattrs, input,
                                                     destination_dfd,
                                                     destination_name,
                                                     cancellable, error)) 
            {
              g_prefix_error (error, "Union checkout of %s to %s: ", checksum, destination_name);
              goto out;
            }
        }
      else
        {
          if (!checkout_file_from_input_at (repo, options, source_info, xattrs, input,
                                            destination_dfd,
                                            destination_name,
                                            cancellable, error))
            {
              g_prefix_error (error, "Checkout of %s to %s: ", checksum, destination_name);
              goto out;
            }
        }

      if (input)
        {
          if (!g_input_stream_close (input, cancellable, error))
            goto out;
        }
    }

  ret = TRUE;
 out:
  return ret;
}
Esempio n. 20
0
gboolean
_ostree_sysroot_read_boot_loader_configs (OstreeSysroot *self,
                                          int            bootversion,
                                          GPtrArray    **out_loader_configs,
                                          GCancellable  *cancellable,
                                          GError       **error)
{
  gboolean ret = FALSE;
  int fd; /* Temporary owned by iterator */
  g_autofree char *entries_path = g_strdup_printf ("boot/loader.%d/entries", bootversion);
  g_autoptr(GPtrArray) ret_loader_configs = NULL;
  g_auto(GLnxDirFdIterator) dfd_iter = { 0, };

  if (!ensure_sysroot_fd (self, error))
    goto out;

  ret_loader_configs = g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref);

  fd = glnx_opendirat_with_errno (self->sysroot_fd, entries_path, TRUE);
  if (fd == -1)
    {
      if (errno == ENOENT)
        goto done;
      else
        {
          glnx_set_error_from_errno (error);
          goto out;
        }
    }

  if (!glnx_dirfd_iterator_init_take_fd (fd, &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, 0) != 0)
        {
          glnx_set_error_from_errno (error);
          goto out;
        }

      if (g_str_has_prefix (dent->d_name, "ostree-") &&
          g_str_has_suffix (dent->d_name, ".conf") &&
          S_ISREG (stbuf.st_mode))
        {
          glnx_unref_object OstreeBootconfigParser *config = ostree_bootconfig_parser_new ();
  
          if (!ostree_bootconfig_parser_parse_at (config, dfd_iter.fd, dent->d_name, cancellable, error))
            {
              g_prefix_error (error, "Parsing %s: ", dent->d_name);
              goto out;
            }

          g_ptr_array_add (ret_loader_configs, g_object_ref (config));
        }
    }

 done:
  gs_transfer_out_value (out_loader_configs, &ret_loader_configs);
  ret = TRUE;
 out:
  return ret;
}
Esempio n. 21
0
static gboolean
do_write_run (GLnxDirFdIterator *dfd_iter, GError **error)
{
  WriteType wtype = g_random_int () % 2;

  if (wtype == WRITE_RUN_CREATE)
    {
      guint32 randname_v = g_random_int ();
      g_autofree char *randname = g_strdup_printf ("file%u", randname_v);
      glnx_autofd int fd = -1;

    again:
      fd = openat (dfd_iter->fd, randname, O_CREAT | O_EXCL, 0644);
      if (fd < 0)
        {
          if (errno == EEXIST)
            {
              g_printerr ("Congratulations!  I suggest purchasing a lottery ticket today!\n");
              goto again;
            }
          else
            {
              glnx_set_error_from_errno (error);
              return FALSE;
            }
        }

      if (!add_random_xattrs (fd, error))
        return FALSE;
      }
  else if (wtype == WRITE_RUN_MUTATE)
    {
      while (TRUE)
        {
          struct dirent *dent;
          if (!glnx_dirfd_iterator_next_dent (dfd_iter, &dent, NULL, error))
            return FALSE;
          if (!dent)
            break;

          glnx_autofd int fd = -1;
          if (!glnx_openat_rdonly (dfd_iter->fd, dent->d_name, FALSE, &fd, error))
            return FALSE;

          g_autoptr(GVariant) current_xattrs = NULL;
          if (!glnx_fd_get_all_xattrs (fd, &current_xattrs, NULL, error))
            return FALSE;

          for (int i = 0; i < g_variant_n_children (current_xattrs); i++)
            {
              const char *name, *value;
              g_variant_get_child (current_xattrs, i, "(^&ay^&ay)", &name, &value);

              /* We don't want to potentially test/change xattrs like security.selinux
               * that were injected by the system.
               */
              if (!g_str_has_prefix (name, "user.test"))
                continue;

              if (!set_random_xattr_value (fd, name, error))
                return FALSE;
            }
        }
    }
  else
    g_assert_not_reached ();

  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;
}
Esempio n. 23
0
gboolean
ot_gpgme_ctx_tmp_home_dir (gpgme_ctx_t     gpgme_ctx,
                           const char     *tmp_dir,
                           char          **out_tmp_home_dir,
                           GOutputStream **out_pubring_stream,
                           GCancellable   *cancellable,
                           GError        **error)
{
  g_autoptr(GFile) pubring_file = NULL;
  g_autoptr(GOutputStream) target_stream = NULL;
  g_autofree char *pubring_path = NULL;
  g_autofree char *tmp_home_dir = NULL;
  gpgme_error_t gpg_error;
  gboolean ret = FALSE;

  g_return_val_if_fail (gpgme_ctx != NULL, FALSE);

  /* GPGME has no API for using multiple keyrings (aka, gpg --keyring),
   * so we create a temporary directory and tell GPGME to use it as the
   * home directory.  Then (optionally) create a pubring.gpg file there
   * and hand the caller an open output stream to concatenate necessary
   * keyring files. */

  if (tmp_dir == NULL)
    tmp_dir = g_get_tmp_dir ();

  tmp_home_dir = g_build_filename (tmp_dir, "ostree-gpg-XXXXXX", NULL);

  if (mkdtemp (tmp_home_dir) == NULL)
    {
      glnx_set_error_from_errno (error);
      goto out;
    }

  /* Not documented, but gpgme_ctx_set_engine_info() accepts NULL for
   * the executable file name, which leaves the old setting unchanged. */
  gpg_error = gpgme_ctx_set_engine_info (gpgme_ctx,
                                         GPGME_PROTOCOL_OpenPGP,
                                         NULL, tmp_home_dir);
  if (gpg_error != GPG_ERR_NO_ERROR)
    {
      ot_gpgme_error_to_gio_error (gpg_error, error);
      goto out;
    }

  if (out_pubring_stream != NULL)
    {
      GFileOutputStream *pubring_stream;
      glnx_unref_object GFile *pubring_file = NULL;
      g_autofree char *pubring_path = NULL;

      pubring_path = g_build_filename (tmp_home_dir, "pubring.gpg", NULL);
      pubring_file = g_file_new_for_path (pubring_path);

      pubring_stream = g_file_create (pubring_file,
                                      G_FILE_CREATE_NONE,
                                      cancellable, error);
      if (pubring_stream == NULL)
        goto out;

      /* Sneaky cast from GFileOutputStream to GOutputStream. */
      *out_pubring_stream = g_steal_pointer (&pubring_stream);
    }

  if (out_tmp_home_dir != NULL)
    *out_tmp_home_dir = g_steal_pointer (&tmp_home_dir);

  ret = TRUE;

out:
  if (!ret)
    {
      /* Clean up our mess on error. */
      (void) glnx_shutil_rm_rf_at (AT_FDCWD, tmp_home_dir, NULL, NULL);
    }

  return ret;
}
Esempio n. 24
0
static gboolean
run_script_in_bwrap_container (int rootfs_fd,
                               const char *name,
                               const char *scriptdesc,
                               const char *script,
                               GCancellable  *cancellable,
                               GError       **error)
{
  gboolean ret = FALSE;
  char *rofiles_mnt = strdupa ("/tmp/rofiles-fuse.XXXXXX");
  const char *rofiles_argv[] = { "rofiles-fuse", "./usr", rofiles_mnt, NULL};
  const char *pkg_script = glnx_strjoina (name, ".", scriptdesc+1);
  const char *postscript_name = glnx_strjoina ("/", pkg_script);
  const char *postscript_path_container = glnx_strjoina ("/usr/", postscript_name);
  const char *postscript_path_host;
  gboolean mntpoint_created = FALSE;
  gboolean fuse_mounted = FALSE;
  g_autoptr(GPtrArray) bwrap_argv = g_ptr_array_new ();
  gboolean created_var_tmp = FALSE;

  if (!glnx_mkdtempat (AT_FDCWD, rofiles_mnt, 0700, error))
    goto out;

  mntpoint_created = TRUE;

  if (!rpmostree_run_sync_fchdir_setup ((char**)rofiles_argv, G_SPAWN_SEARCH_PATH,
                                        rootfs_fd, error))
    {
      g_prefix_error (error, "Executing rofiles-fuse: ");
      goto out;
    }

  fuse_mounted = TRUE;

  postscript_path_host = glnx_strjoina (rofiles_mnt, "/", postscript_name);

  /* TODO - Create a pipe and send this to bwrap so it's inside the
   * tmpfs
   */
  if (!g_file_set_contents (postscript_path_host, script, -1, error))
    {
      g_prefix_error (error, "Writing script to %s: ", postscript_path_host);
      goto out;
    }
  if (chmod (postscript_path_host, 0755) != 0)
    {
      g_prefix_error (error, "chmod %s: ", postscript_path_host);
      goto out;
    }

  /* We need to make the mount point in the case where we're doing
   * package layering, since the host `/var` tree is empty.  We
   * *could* point at the real `/var`...but that seems
   * unnecessary/dangerous to me.  Daemons that need to perform data
   * migrations should do them as part of their systemd units and not
   * in %post.
   *
   * Another alternative would be to make a tmpfs with the compat
   * symlinks.
   */
  if (mkdirat (rootfs_fd, "var/tmp", 0755) < 0)
    {
      if (errno == EEXIST)
        ;
      else
        {
          glnx_set_error_from_errno (error);
          goto out;
        }
    }
  else
    created_var_tmp = TRUE;

  bwrap_argv = rpmostree_bwrap_base_argv_new_for_rootfs (rootfs_fd, error);
  if (!bwrap_argv)
    goto out;

  rpmostree_ptrarray_append_strdup (bwrap_argv,
                  "--bind", rofiles_mnt, "/usr",
                  /* Scripts can see a /var with compat links like alternatives */
                  "--ro-bind", "./var", "/var",
                  /* But no need to access persistent /tmp, so make it /tmp */
                  "--bind", "/tmp", "/var/tmp",
                  /* Allow RPM scripts to change the /etc defaults */
                  "--symlink", "usr/etc", "/etc",
                  NULL);

  g_ptr_array_add (bwrap_argv, g_strdup (postscript_path_container));
  /* http://www.rpm.org/max-rpm/s1-rpm-inside-scripts.html#S3-RPM-INSIDE-PRE-SCRIPT */
  g_ptr_array_add (bwrap_argv, g_strdup ("1"));
  g_ptr_array_add (bwrap_argv, NULL);

  if (!rpmostree_run_bwrap_sync ((char**)bwrap_argv->pdata, rootfs_fd, error))
    {
      g_prefix_error (error, "Executing bwrap: ");
      goto out;
    }

  ret = TRUE;
 out:
  if (fuse_mounted)
    {
      (void) unlink (postscript_path_host);
      fusermount_cleanup (rofiles_mnt);
    }
  if (mntpoint_created)
    (void) unlinkat (AT_FDCWD, rofiles_mnt, AT_REMOVEDIR);
  if (created_var_tmp)
    (void) unlinkat (rootfs_fd, "var/tmp", AT_REMOVEDIR);
  return ret;
}