/*
* Copyright (C) 2011,2013 Colin Walters <walters@verbum.org>
*
* SPDX-License-Identifier: LGPL-2.0+
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
* Author: Colin Walters <walters@verbum.org>
*/
#include "config.h"
#include "otutil.h"
#include "ostree.h"
#include "ostree-core-private.h"
#include "ostree-repo-private.h"
#ifdef HAVE_LIBARCHIVE
#include <archive.h>
#include <archive_entry.h>
#include "ostree-libarchive-input-stream.h"
#endif
#include "otutil.h"
#ifdef HAVE_LIBARCHIVE
#define DEFAULT_DIRMODE (0755 | S_IFDIR)
static void
propagate_libarchive_error (GError **error,
struct archive *a)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"%s", archive_error_string (a));
}
static const char *
path_relative (const char *src,
GError **error)
{
/* One issue here is that some archives almost record the pathname as just a
* string and don't need to actually encode parent/child relationships in the
* archive. For us however, this will be important. So we do our best to deal
* with non-conventional paths. We also validate the path at the end to make
* sure there are no illegal components. Also important, we relativize the
* path. */
/* relativize first (and make /../../ --> /) */
while (src[0] == '/')
{
src += 1;
if (src[0] == '.' && src[1] == '.' && src[2] == '/')
src += 2; /* keep trailing / so we continue */
}
/* now let's skip . and empty components */
while (TRUE)
{
if (src[0] == '.' && src[1] == '/')
src += 2;
else if (src[0] == '/')
src += 1;
else
break;
}
/* assume a single '.' means the root dir itself, which we handle as the empty
* string in our code */
if (src[0] == '.' && src[1] == '\0')
src += 1;
/* make sure that the final path is valid (no . or ..) */
if (!ot_util_path_split_validate (src, NULL, error))
{
g_prefix_error (error, "While making relative path \"%s\":", src);
return NULL;
}
return src;
}
static char *
path_relative_ostree (const char *path,
GError **error)
{
path = path_relative (path, error);
if (path == NULL)
return NULL;
if (g_str_has_prefix (path, "etc/"))
return g_strconcat ("usr/", path, NULL);
else if (strcmp (path, "etc") == 0)
return g_strdup ("usr/etc");
return g_strdup (path);
}
static void
append_path_component (char **path_builder,
const char *component)
{
g_autofree char *s = g_steal_pointer (path_builder);
*path_builder = g_build_filename (s ?: "/", component, NULL);
}
/* inplace trailing slash squashing */
static void
squash_trailing_slashes (char *path)
{
char *endp = path + strlen (path) - 1;
for (; endp > path && *endp == '/'; endp--)
*endp = '\0';
}
/* Like archive_entry_stat(), but since some archives only store the permission
* mode bits in hardlink entries, so let's just make it into a regular file.
* Yes, this hack will work even if it's a hardlink to a symlink.
*/
static void
read_archive_entry_stat (struct archive_entry *entry,
struct stat *stbuf)
{
const struct stat *st = archive_entry_stat (entry);
*stbuf = *st;
if (archive_entry_hardlink (entry))
stbuf->st_mode |= S_IFREG;
}
/* Create a GFileInfo from archive_entry_stat() */
static GFileInfo *
file_info_from_archive_entry (struct archive_entry *entry)
{
struct stat stbuf;
read_archive_entry_stat (entry, &stbuf);
g_autoptr(GFileInfo) info = _ostree_stbuf_to_gfileinfo (&stbuf);
if (S_ISLNK (stbuf.st_mode))
g_file_info_set_attribute_byte_string (info, "standard::symlink-target",
archive_entry_symlink (entry));
return g_steal_pointer (&info);
}
static gboolean
builder_add_label (GVariantBuilder *builder,
OstreeSePolicy *sepolicy,
const char *path,
mode_t mode,
GCancellable *cancellable,
GError **error)
{
g_autofree char *label = NULL;
if (!sepolicy)
return TRUE;
if (!ostree_sepolicy_get_label (sepolicy, path, mode, &label,
cancellable, error))
return FALSE;
if (label)
g_variant_builder_add (builder, "(@ay@ay)",
g_variant_new_bytestring ("security.selinux"),
g_variant_new_bytestring (label));
return TRUE;
}
/* Like ostree_mutable_tree_ensure_dir(), but also creates and sets dirmeta if
* the dir has to be created. */
static gboolean
mtree_ensure_dir_with_meta (OstreeRepo *repo,
OstreeMutableTree *parent,
const char *name,
GFileInfo *file_info,
GVariant *xattrs,
gboolean error_if_exist, /* XXX: remove if not needed */
OstreeMutableTree **out_dir,
GCancellable *cancellable,
GError **error)
{
g_autoptr(OstreeMutableTree) dir = NULL;
g_autofree guchar *csum_raw = NULL;
g_autofree char *csum = NULL;
if (name[0] == '\0') /* root? */
dir = g_object_ref (parent);
else if (ostree_mutable_tree_lookup (parent, name, NULL, &dir, error))
{
if (error_if_exist)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Directory \"%s\" already exists", name);
return FALSE;
}
}
if (dir == NULL)
{
if (!g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
return FALSE;
g_clear_error (error);
if (!ostree_mutable_tree_ensure_dir (parent, name, &dir, error))
return FALSE;
}
if (!_ostree_repo_write_directory_meta (repo, file_info, xattrs,
&csum_raw, cancellable, error))
return FALSE;
csum = ostree_checksum_from_bytes (csum_raw);
ostree_mutable_tree_set_metadata_checksum (dir, csum);
if (out_dir)
*out_dir = g_steal_pointer (&dir);
return TRUE;
}
typedef struct {
OstreeRepo *repo;
OstreeRepoImportArchiveOptions *opts;
OstreeMutableTree *root;
struct archive *archive;
struct archive_entry *entry;
GHashTable *deferred_hardlinks;
OstreeRepoCommitModifier *modifier;
} OstreeRepoArchiveImportContext;
typedef struct {
OstreeMutableTree *parent;
char *path;
guint64 size;
} DeferredHardlink;
static inline char*
aic_get_final_path (OstreeRepoArchiveImportContext *ctx,
const char *path,
GError **error)
{
if (ctx->opts->translate_pathname)
{
struct stat stbuf;
path = path_relative (path, error);
read_archive_entry_stat (ctx->entry, &stbuf);
char *ret = ctx->opts->translate_pathname (ctx->repo, &stbuf, path,
ctx->opts->translate_pathname_user_data);
if (ret)
return ret;
/* Fall through */
}
else if (ctx->opts->use_ostree_convention)
return path_relative_ostree (path, error);
return g_strdup (path_relative (path, error));
}
static inline char*
aic_get_final_entry_pathname (OstreeRepoArchiveImportContext *ctx,
GError **error)
{
const char *pathname = archive_entry_pathname (ctx->entry);
g_autofree char *final = aic_get_final_path (ctx, pathname, error);
if (final == NULL)
return NULL;
/* get rid of trailing slashes some archives put on dirs */
squash_trailing_slashes (final);
return g_steal_pointer (&final);
}
static inline char*
aic_get_final_entry_hardlink (OstreeRepoArchiveImportContext *ctx)
{
GError *local_error = NULL;
const char *hardlink = archive_entry_hardlink (ctx->entry);
g_autofree char *final = NULL;
if (hardlink != NULL)
{
final = aic_get_final_path (ctx, hardlink, &local_error);
/* hardlinks always point to a preceding entry, so if there were an error
* it would have failed then */
g_assert_no_error (local_error);
}
return g_steal_pointer (&final);
}
static OstreeRepoCommitFilterResult
aic_apply_modifier_filter (OstreeRepoArchiveImportContext *ctx,
const char *relpath,
GFileInfo **out_file_info)
{
g_autoptr(GFileInfo) file_info = NULL;
g_autofree char *abspath = NULL;
const char *cb_path = NULL;
if (ctx->opts->callback_with_entry_pathname)
cb_path = archive_entry_pathname (ctx->entry);
else
{
/* the user expects an abspath (where the dir to commit represents /) */
abspath = g_build_filename ("/", relpath, NULL);
cb_path = abspath;
}
file_info = file_info_from_archive_entry (ctx->entry);
return _ostree_repo_commit_modifier_apply (ctx->repo, ctx->modifier, cb_path,
file_info, out_file_info);
}
static gboolean
aic_ensure_parent_dir_with_file_info (OstreeRepoArchiveImportContext *ctx,
OstreeMutableTree *parent,
const char *fullpath,
GFileInfo *file_info,
OstreeMutableTree **out_dir,
GCancellable *cancellable,
GError **error)
{
const char *name = glnx_basename (fullpath);
g_auto(GVariantBuilder) xattrs_builder;
g_autoptr(GVariant) xattrs = NULL;
/* is this the root directory itself? transform into empty string */
if (name[0] == '/' && name[1] == '\0')
name++;
g_variant_builder_init (&xattrs_builder, (GVariantType*)"a(ayay)");
if (ctx->modifier && ctx->modifier->sepolicy)
if (!builder_add_label (&xattrs_builder, ctx->modifier->sepolicy, fullpath,
DEFAULT_DIRMODE, cancellable, error))
return FALSE;
xattrs = g_variant_ref_sink (g_variant_builder_end (&xattrs_builder));
return mtree_ensure_dir_with_meta (ctx->repo, parent, name, file_info,
xattrs,
FALSE /* error_if_exist */, out_dir,
cancellable, error);
}
static gboolean
aic_ensure_parent_dir (OstreeRepoArchiveImportContext *ctx,
OstreeMutableTree *parent,
const char *fullpath,
OstreeMutableTree **out_dir,
GCancellable *cancellable,
GError **error)
{
/* Who should own the parent dir? Since it's not in the archive, it's up to
* us. Here, we use the heuristic of simply creating it as the same user as
* the owner of the archive entry for which we're creating the dir. This is OK
* since any nontrivial dir perms should have explicit archive entries. */
guint32 uid = archive_entry_uid (ctx->entry);
guint32 gid = archive_entry_gid (ctx->entry);
glnx_unref_object GFileInfo *file_info = g_file_info_new ();
g_file_info_set_attribute_uint32 (file_info, "unix::uid", uid);
g_file_info_set_attribute_uint32 (file_info, "unix::gid", gid);
g_file_info_set_attribute_uint32 (file_info, "unix::mode", DEFAULT_DIRMODE);
return aic_ensure_parent_dir_with_file_info (ctx, parent, fullpath, file_info,
out_dir, cancellable, error);
}
static gboolean
aic_create_parent_dirs (OstreeRepoArchiveImportContext *ctx,
GPtrArray *components,
OstreeMutableTree **out_subdir,
GCancellable *cancellable,
GError **error)
{
g_autofree char *fullpath = NULL;
g_autoptr(OstreeMutableTree) dir = NULL;
/* start with the root itself */
if (!aic_ensure_parent_dir (ctx, ctx->root, "/", &dir, cancellable, error))
return FALSE;
for (guint i = 0; i < components->len-1; i++)
{
glnx_unref_object OstreeMutableTree *subdir = NULL;
append_path_component (&fullpath, components->pdata[i]);
if (!aic_ensure_parent_dir (ctx, dir, fullpath, &subdir,
cancellable, error))
return FALSE;
g_set_object (&dir, subdir);
}
*out_subdir = g_steal_pointer (&dir);
return TRUE;
}
static gboolean
aic_get_parent_dir (OstreeRepoArchiveImportContext *ctx,
const char *path,
OstreeMutableTree **out_dir,
GCancellable *cancellable,
GError **error)
{
g_autoptr(GPtrArray) components = NULL;
if (!ot_util_path_split_validate (path, &components, error))
return FALSE;
if (components->len == 0) /* root dir? */
{
*out_dir = g_object_ref (ctx->root);
return TRUE;
}
if (ostree_mutable_tree_walk (ctx->root, components, 0, out_dir, error))
return TRUE; /* already exists, nice! */
if (!g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
return FALSE; /* some other error occurred */
if (ctx->opts->autocreate_parents)
{
g_clear_error (error);
return aic_create_parent_dirs (ctx, components, out_dir,
cancellable, error);
}
return FALSE;
}
static gboolean
aic_get_xattrs (OstreeRepoArchiveImportContext *ctx,
const char *path,
GFileInfo *file_info,
GVariant **out_xattrs,
GCancellable *cancellable,
GError **error)
{
g_autofree char *abspath = g_build_filename ("/", path, NULL);
g_autoptr(GVariant) xattrs = NULL;
const char *cb_path = abspath;
if (ctx->opts->callback_with_entry_pathname)
cb_path = archive_entry_pathname (ctx->entry);
if (ctx->modifier && ctx->modifier->xattr_callback)
xattrs = ctx->modifier->xattr_callback (ctx->repo, cb_path, file_info,
ctx->modifier->xattr_user_data);
if (ctx->modifier && ctx->modifier->sepolicy)
{
mode_t mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode");
g_autoptr(GVariantBuilder) builder =
ot_util_variant_builder_from_variant (xattrs, G_VARIANT_TYPE
("a(ayay)"));
if (!builder_add_label (builder, ctx->modifier->sepolicy, abspath, mode,
cancellable, error))
return FALSE;
if (xattrs)
g_variant_unref (xattrs);
xattrs = g_variant_builder_end (builder);
g_variant_ref_sink (xattrs);
}
*out_xattrs = g_steal_pointer (&xattrs);
return TRUE;
}
/* XXX: add option in ctx->opts to disallow already existing dirs? see
* error_if_exist */
static gboolean
aic_handle_dir (OstreeRepoArchiveImportContext *ctx,
OstreeMutableTree *parent,
const char *path,
GFileInfo *fi,
GCancellable *cancellable,
GError **error)
{
const char *name = glnx_basename (path);
g_autoptr(GVariant) xattrs = NULL;
if (!aic_get_xattrs (ctx, path, fi, &xattrs, cancellable, error))
return FALSE;
return mtree_ensure_dir_with_meta (ctx->repo, parent, name, fi, xattrs,
FALSE /* error_if_exist */, NULL,
cancellable, error);
}
static gboolean
aic_write_file (OstreeRepoArchiveImportContext *ctx,
GFileInfo *fi,
GVariant *xattrs,
char **out_csum,
GCancellable *cancellable,
GError **error)
{
g_autoptr(GInputStream) archive_stream = NULL;
g_autoptr(GInputStream) file_object_input = NULL;
guint64 length;
g_autofree guchar *csum_raw = NULL;
if (g_file_info_get_file_type (fi) == G_FILE_TYPE_REGULAR)
archive_stream = _ostree_libarchive_input_stream_new (ctx->archive);
if (!ostree_raw_file_to_content_stream (archive_stream, fi, xattrs,
&file_object_input, &length,
cancellable, error))
return FALSE;
if (!ostree_repo_write_content (ctx->repo, NULL, file_object_input, length,
&csum_raw, cancellable, error))
return FALSE;
*out_csum = ostree_checksum_from_bytes (csum_raw);
return TRUE;
}
static gboolean
aic_import_file (OstreeRepoArchiveImportContext *ctx,
OstreeMutableTree *parent,
const char *path,
GFileInfo *fi,
GCancellable *cancellable,
GError **error)
{
const char *name = glnx_basename (path);
g_autoptr(GVariant) xattrs = NULL;
g_autofree char *csum = NULL;
if (!aic_get_xattrs (ctx, path, fi, &xattrs, cancellable, error))
return FALSE;
if (!aic_write_file (ctx, fi, xattrs, &csum, cancellable, error))
return FALSE;
if (!ostree_mutable_tree_replace_file (parent, name, csum, error))
return FALSE;
return TRUE;
}
static void
aic_add_deferred_hardlink (OstreeRepoArchiveImportContext *ctx,
const char *hardlink,
DeferredHardlink *dh)
{
gboolean new_slist;
GSList *slist;
slist = g_hash_table_lookup (ctx->deferred_hardlinks, hardlink);
new_slist = (slist == NULL);
slist = g_slist_append (slist, dh);
if (new_slist)
g_hash_table_insert (ctx->deferred_hardlinks, g_strdup (hardlink), slist);
}
static void
aic_defer_hardlink (OstreeRepoArchiveImportContext *ctx,
OstreeMutableTree *parent,
const char *path,
guint64 size,
const char *hardlink)
{
DeferredHardlink *dh = g_slice_new (DeferredHardlink);
dh->parent = g_object_ref (parent);
dh->path = g_strdup (path);
dh->size = size;
aic_add_deferred_hardlink (ctx, hardlink, dh);
}
static gboolean
aic_handle_file (OstreeRepoArchiveImportContext *ctx,
OstreeMutableTree *parent,
const char *path,
GFileInfo *fi,
GCancellable *cancellable,
GError **error)
{
/* The wonderful world of hardlinks and archives. We have to be very careful
* here. Do not assume that if a file is a hardlink, it will have size 0 (e.g.
* cpio). Do not assume that if a file will have hardlinks to it, it will have
* size > 0. Also do not assume that its nlink param is present (tar) or even
* accurate (cpio). Also do not assume that hardlinks follow each other in
* order of entries.
*
* These archives were made to be extracted onto a filesystem, not directly
* hashed into an object store. So to be careful, we defer all hardlink
* imports until the very end. Nonzero files have to be imported, hardlink or
* not, since we can't easily seek back to this position later on.
* */
g_autofree char *hardlink = aic_get_final_entry_hardlink (ctx);
guint64 size = g_file_info_get_attribute_uint64 (fi, "standard::size");
if (hardlink == NULL || size > 0)
if (!aic_import_file (ctx, parent, path, fi, cancellable, error))
return FALSE;
if (hardlink)
aic_defer_hardlink (ctx, parent, path, size, hardlink);
return TRUE;
}
static gboolean
aic_handle_entry (OstreeRepoArchiveImportContext *ctx,
OstreeMutableTree *parent,
const char *path,
GFileInfo *fi,
GCancellable *cancellable,
GError **error)
{
switch (g_file_info_get_file_type (fi))
{
case G_FILE_TYPE_DIRECTORY:
return aic_handle_dir (ctx, parent, path, fi, cancellable, error);
case G_FILE_TYPE_REGULAR:
case G_FILE_TYPE_SYMBOLIC_LINK:
return aic_handle_file (ctx, parent, path, fi, cancellable, error);
default:
if (ctx->opts->ignore_unsupported_content)
return TRUE;
else
{
return glnx_throw (error, "Unsupported file type for path \"%s\"",
path);
}
}
}
static gboolean
aic_import_entry (OstreeRepoArchiveImportContext *ctx,
GCancellable *cancellable,
GError **error)
{
g_autofree char *path = aic_get_final_entry_pathname (ctx, error);
if (path == NULL)
return FALSE;
g_autoptr(GFileInfo) fi = NULL;
if (aic_apply_modifier_filter (ctx, path, &fi)
== OSTREE_REPO_COMMIT_FILTER_SKIP)
return TRUE;
g_autoptr(OstreeMutableTree) parent = NULL;
if (!aic_get_parent_dir (ctx, path, &parent, cancellable, error))
return FALSE;
return aic_handle_entry (ctx, parent, path, fi, cancellable, error);
}
static gboolean
aic_import_from_hardlink (OstreeRepoArchiveImportContext *ctx,
const char *target,
DeferredHardlink *dh,
GError **error)
{
g_autofree char *csum = NULL;
const char *name = glnx_basename (target);
const char *name_dh = glnx_basename (dh->path);
g_autoptr(GPtrArray) components = NULL;
g_autoptr(OstreeMutableTree) parent = NULL;
if (!ostree_mutable_tree_lookup (dh->parent, name_dh, &csum, NULL, error))
return FALSE;
g_assert (csum);
if (!ot_util_path_split_validate (target, &components, error))
return FALSE;
if (!ostree_mutable_tree_walk (ctx->root, components, 0, &parent, error))
return FALSE;
if (!ostree_mutable_tree_replace_file (parent, name, csum, error))
return FALSE;
return TRUE;
}
static gboolean
aic_lookup_file_csum (OstreeRepoArchiveImportContext *ctx,
const char *target,
char **out_csum,
GError **error)
{
g_autofree char *csum = NULL;
const char *name = glnx_basename (target);
g_autoptr(OstreeMutableTree) parent = NULL;
g_autoptr(OstreeMutableTree) subdir = NULL;
g_autoptr(GPtrArray) components = NULL;
if (!ot_util_path_split_validate (target, &components, error))
return FALSE;
if (!ostree_mutable_tree_walk (ctx->root, components, 0, &parent, error))
return FALSE;
if (!ostree_mutable_tree_lookup (parent, name, &csum, &subdir, error))
return FALSE;
if (subdir != NULL)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Expected hardlink file target at \"%s\" but found a "
"directory", target);
return FALSE;
}
*out_csum = g_steal_pointer (&csum);
return TRUE;
}
static gboolean
aic_import_deferred_hardlinks_for (OstreeRepoArchiveImportContext *ctx,
const char *target,
GSList *hardlinks,
GError **error)
{
GSList *payload = hardlinks;
g_autofree char *csum = NULL;
/* find node with the payload, if any (if none, then they're all hardlinks to
* a zero sized target, and there's no rewrite required) */
while (payload && ((DeferredHardlink*)payload->data)->size == 0)
payload = g_slist_next (payload);
/* rewrite the target so it points to the csum of the payload hardlink */
if (payload)
if (!aic_import_from_hardlink (ctx, target, payload->data, error))
return FALSE;
if (!aic_lookup_file_csum (ctx, target, &csum, error))
return FALSE;
/* import all the hardlinks */
for (GSList *hl = hardlinks; hl != NULL; hl = g_slist_next (hl))
{
DeferredHardlink *df = hl->data;
const char *name = glnx_basename (df->path);
if (hl == payload)
continue; /* small optimization; no need to redo this one */
if (!ostree_mutable_tree_replace_file (df->parent, name, csum, error))
return FALSE;
}
return TRUE;
}
static gboolean
aic_import_deferred_hardlinks (OstreeRepoArchiveImportContext *ctx,
GCancellable *cancellable,
GError **error)
{
GLNX_HASH_TABLE_FOREACH_KV (ctx->deferred_hardlinks, const char*, target, GSList*, links)
{
if (!aic_import_deferred_hardlinks_for (ctx, target, links, error))
return FALSE;
}
return TRUE;
}
static void
deferred_hardlink_free (void *data)
{
DeferredHardlink *dh = data;
g_object_unref (dh->parent);
g_free (dh->path);
g_slice_free (DeferredHardlink, dh);
}
static void
deferred_hardlinks_list_free (void *data)
{
GSList *slist = data;
g_slist_free_full (slist, deferred_hardlink_free);
}
#endif /* HAVE_LIBARCHIVE */
/**
* ostree_repo_import_archive_to_mtree: (skip)
* @self: An #OstreeRepo
* @opts: Options structure, ensure this is zeroed, then set specific variables
* @archive: Really this is "struct archive*"
* @mtree: The #OstreeMutableTree to write to
* @modifier: (allow-none): Optional commit modifier
* @cancellable: Cancellable
* @error: Error
*
* Import an archive file @archive into the repository, and write its
* file structure to @mtree.
*/
gboolean
ostree_repo_import_archive_to_mtree (OstreeRepo *self,
OstreeRepoImportArchiveOptions *opts,
void *archive,
OstreeMutableTree *mtree,
OstreeRepoCommitModifier *modifier,
GCancellable *cancellable,
GError **error)
{
#ifdef HAVE_LIBARCHIVE
gboolean ret = FALSE;
struct archive *a = archive;
g_autoptr(GHashTable) deferred_hardlinks =
g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
deferred_hardlinks_list_free);
OstreeRepoArchiveImportContext aictx = {
.repo = self,
.opts = opts,
.root = mtree,
.archive = archive,
.deferred_hardlinks = deferred_hardlinks,
.modifier = modifier
};
_ostree_repo_setup_generate_sizes (self, modifier);
while (TRUE)
{
int r = archive_read_next_header (a, &aictx.entry);
if (r == ARCHIVE_EOF)
break;
if (r != ARCHIVE_OK)
{
propagate_libarchive_error (error, a);
goto out;
}
if (g_cancellable_set_error_if_cancelled (cancellable, error))
goto out;
if (!aic_import_entry (&aictx, cancellable, error))
goto out;
}
if (!aic_import_deferred_hardlinks (&aictx, cancellable, error))
goto out;
/* If we didn't import anything at all, and autocreation of parents
* is enabled, automatically create a root directory. This is
* useful primarily when importing Docker image layers, which can
* just be metadata.
*/
if (opts->autocreate_parents &&
ostree_mutable_tree_get_metadata_checksum (mtree) == NULL)
{
/* _ostree_stbuf_to_gfileinfo() only looks at these fields,
* but we use it to ensure it sets all of the relevant GFileInfo
* properties.
*/
struct stat stbuf = { .st_mode = DEFAULT_DIRMODE,
.st_uid = 0,
.st_gid = 0 };
g_autoptr(GFileInfo) fi = _ostree_stbuf_to_gfileinfo (&stbuf);
g_autoptr(GFileInfo) mfi = NULL;
(void)_ostree_repo_commit_modifier_apply (self, modifier, "/",
fi, &mfi);
if (!aic_ensure_parent_dir_with_file_info (&aictx, mtree, "/", mfi, NULL,
cancellable, error))
goto out;
}
ret = TRUE;
out:
return ret;
#else
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"This version of ostree is not compiled with libarchive support");
return FALSE;
#endif
}
#ifdef HAVE_LIBARCHIVE
static gboolean
write_archive_to_mtree (OstreeRepo *self,
OtAutoArchiveRead *archive,
OstreeMutableTree *mtree,
OstreeRepoCommitModifier *modifier,
gboolean autocreate_parents,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
OstreeRepoImportArchiveOptions opts = { 0, };
opts.autocreate_parents = !!autocreate_parents;
if (!ostree_repo_import_archive_to_mtree (self, &opts, archive, mtree, modifier, cancellable, error))
goto out;
if (archive_read_close (archive) != ARCHIVE_OK)
{
propagate_libarchive_error (error, archive);
goto out;
}
ret = TRUE;
out:
(void)archive_read_close (archive);
return ret;
}
#endif
/**
* ostree_repo_write_archive_to_mtree:
* @self: An #OstreeRepo
* @archive: A path to an archive file
* @mtree: The #OstreeMutableTree to write to
* @modifier: (allow-none): Optional commit modifier
* @autocreate_parents: Autocreate parent directories
* @cancellable: Cancellable
* @error: Error
*
* Import an archive file @archive into the repository, and write its
* file structure to @mtree.
*/
gboolean
ostree_repo_write_archive_to_mtree (OstreeRepo *self,
GFile *archive,
OstreeMutableTree *mtree,
OstreeRepoCommitModifier *modifier,
gboolean autocreate_parents,
GCancellable *cancellable,
GError **error)
{
#ifdef HAVE_LIBARCHIVE
g_autoptr(OtAutoArchiveRead) a = ot_open_archive_read (gs_file_get_path_cached (archive), error);
if (a)
return write_archive_to_mtree (self, a, mtree, modifier, autocreate_parents, cancellable, error);
return FALSE;
#else
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"This version of ostree is not compiled with libarchive support");
return FALSE;
#endif
}
/**
* ostree_repo_write_archive_to_mtree_from_fd:
* @self: An #OstreeRepo
* @fd: A file descriptor to read the archive from
* @mtree: The #OstreeMutableTree to write to
* @modifier: (allow-none): Optional commit modifier
* @autocreate_parents: Autocreate parent directories
* @cancellable: Cancellable
* @error: Error
*
* Read an archive from @fd and import it into the repository, writing
* its file structure to @mtree.
*/
gboolean
ostree_repo_write_archive_to_mtree_from_fd (OstreeRepo *self,
int fd,
OstreeMutableTree *mtree,
OstreeRepoCommitModifier *modifier,
gboolean autocreate_parents,
GCancellable *cancellable,
GError **error)
{
#ifdef HAVE_LIBARCHIVE
g_autoptr(OtAutoArchiveRead) a = ot_open_archive_read_fd (fd, error);
if (a)
return write_archive_to_mtree (self, a, mtree, modifier, autocreate_parents, cancellable, error);
return FALSE;
#else
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"This version of ostree is not compiled with libarchive support");
return FALSE;
#endif
}
#ifdef HAVE_LIBARCHIVE
static gboolean
file_to_archive_entry_common (GFile *root,
OstreeRepoExportArchiveOptions *opts,
GFile *path,
GFileInfo *file_info,
struct archive_entry *entry,
GError **error)
{
gboolean ret = FALSE;
g_autofree char *pathstr = g_file_get_relative_path (root, path);
g_autoptr(GVariant) xattrs = NULL;
time_t ts = (time_t) opts->timestamp_secs;
if (opts->path_prefix && opts->path_prefix[0])
{
g_autofree char *old_pathstr = pathstr;
pathstr = g_strconcat (opts->path_prefix, old_pathstr, NULL);
}
if (pathstr == NULL || !pathstr[0])
{
g_free (pathstr);
pathstr = g_strdup (".");
}
archive_entry_update_pathname_utf8 (entry, pathstr);
archive_entry_set_ctime (entry, ts, OSTREE_TIMESTAMP);
archive_entry_set_mtime (entry, ts, OSTREE_TIMESTAMP);
archive_entry_set_atime (entry, ts, OSTREE_TIMESTAMP);
archive_entry_set_uid (entry, g_file_info_get_attribute_uint32 (file_info, "unix::uid"));
archive_entry_set_gid (entry, g_file_info_get_attribute_uint32 (file_info, "unix::gid"));
archive_entry_set_mode (entry, g_file_info_get_attribute_uint32 (file_info, "unix::mode"));
if (!ostree_repo_file_get_xattrs ((OstreeRepoFile*)path, &xattrs, NULL, error))
goto out;
if (!opts->disable_xattrs)
{
int i, n;
n = g_variant_n_children (xattrs);
for (i = 0; i < n; i++)
{
const guint8* name;
g_autoptr(GVariant) value = NULL;
const guint8* value_data;
gsize value_len;
g_variant_get_child (xattrs, i, "(^&ay@ay)", &name, &value);
value_data = g_variant_get_fixed_array (value, &value_len, 1);
archive_entry_xattr_add_entry (entry, (char*)name,
(char*) value_data, value_len);
}
}
ret = TRUE;
out:
return ret;
}
static gboolean
write_header_free_entry (struct archive *a,
struct archive_entry **entryp,
GError **error)
{
struct archive_entry *entry = *entryp;
gboolean ret = FALSE;
if (archive_write_header (a, entry) != ARCHIVE_OK)
{
propagate_libarchive_error (error, a);
goto out;
}
ret = TRUE;
out:
archive_entry_free (entry);
*entryp = NULL;
return ret;
}
static gboolean
write_directory_to_libarchive_recurse (OstreeRepo *self,
OstreeRepoExportArchiveOptions *opts,
GFile *root,
GFile *dir,
struct archive *a,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
g_autoptr(GFileInfo) dir_info = NULL;
g_autoptr(GFileEnumerator) dir_enum = NULL;
struct archive_entry *entry = NULL;
dir_info = g_file_query_info (dir, OSTREE_GIO_FAST_QUERYINFO,
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
cancellable, error);
if (!dir_info)
goto out;
entry = archive_entry_new2 (a);
if (!file_to_archive_entry_common (root, opts, dir, dir_info, entry, error))
goto out;
if (!write_header_free_entry (a, &entry, error))
goto out;
dir_enum = g_file_enumerate_children (dir, OSTREE_GIO_FAST_QUERYINFO,
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
cancellable, error);
if (!dir_enum)
goto out;
while (TRUE)
{
GFileInfo *file_info;
GFile *path;
if (!g_file_enumerator_iterate (dir_enum, &file_info, &path,
cancellable, error))
goto out;
if (file_info == NULL)
break;
/* First, handle directories recursively */
if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY)
{
if (!write_directory_to_libarchive_recurse (self, opts, root, path, a,
cancellable, error))
goto out;
/* Go to the next entry */
continue;
}
/* Past here, should be a regular file or a symlink */
entry = archive_entry_new2 (a);
if (!file_to_archive_entry_common (root, opts, path, file_info, entry, error))
goto out;
switch (g_file_info_get_file_type (file_info))
{
case G_FILE_TYPE_SYMBOLIC_LINK:
{
archive_entry_set_symlink (entry, g_file_info_get_symlink_target (file_info));
if (!write_header_free_entry (a, &entry, error))
goto out;
}
break;
case G_FILE_TYPE_REGULAR:
{
guint8 buf[8192];
g_autoptr(GInputStream) file_in = NULL;
g_autoptr(GFileInfo) regular_file_info = NULL;
const char *checksum;
checksum = ostree_repo_file_get_checksum ((OstreeRepoFile*)path);
if (!ostree_repo_load_file (self, checksum, &file_in, ®ular_file_info, NULL,
cancellable, error))
goto out;
archive_entry_set_size (entry, g_file_info_get_size (regular_file_info));
if (archive_write_header (a, entry) != ARCHIVE_OK)
{
propagate_libarchive_error (error, a);
goto out;
}
while (TRUE)
{
gssize bytes_read = g_input_stream_read (file_in, buf, sizeof (buf),
cancellable, error);
if (bytes_read < 0)
goto out;
if (bytes_read == 0)
break;
{ ssize_t r = archive_write_data (a, buf, bytes_read);
if (r != bytes_read)
{
propagate_libarchive_error (error, a);
g_prefix_error (error, "Failed to write %" G_GUINT64_FORMAT " bytes (code %" G_GUINT64_FORMAT"): ", (guint64)bytes_read, (guint64)r);
goto out;
}
}
}
if (archive_write_finish_entry (a) != ARCHIVE_OK)
{
propagate_libarchive_error (error, a);
goto out;
}
archive_entry_free (entry);
entry = NULL;
}
break;
default:
g_assert_not_reached ();
}
}
ret = TRUE;
out:
if (entry)
archive_entry_free (entry);
return ret;
}
#endif
/**
* ostree_repo_export_tree_to_archive: (skip)
* @self: An #OstreeRepo
* @opts: Options controlling conversion
* @root: An #OstreeRepoFile for the base directory
* @archive: A `struct archive`, but specified as void to avoid a dependency on the libarchive headers
* @cancellable: Cancellable
* @error: Error
*
* Import an archive file @archive into the repository, and write its
* file structure to @mtree.
*/
gboolean
ostree_repo_export_tree_to_archive (OstreeRepo *self,
OstreeRepoExportArchiveOptions *opts,
OstreeRepoFile *root,
void *archive,
GCancellable *cancellable,
GError **error)
{
#ifdef HAVE_LIBARCHIVE
gboolean ret = FALSE;
struct archive *a = archive;
if (!write_directory_to_libarchive_recurse (self, opts, (GFile*)root, (GFile*)root,
a, cancellable, error))
goto out;
ret = TRUE;
out:
return ret;
#else
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"This version of ostree is not compiled with libarchive support");
return FALSE;
#endif
}