libarchive: Add support for translating paths during commit
For rpm-ostree, I want to move RPM files in `/boot` to `/usr/lib/ostree-boot`. This is currently impossible without forking the libarchive code. Supporting this is pretty straightforward; we already had pathname translation in the libarchive code, we just need to expose it as an option. On the command line side, I chose to wrap this as a regexp. That should be good enough for a lot of use cases; sophisticated users should as always be making use of the API. Note that this required some new `#ifdef LIBARCHIVE` bits to use the new API. Following previous patterns here, we use the new API only if a relevant option is enabled, ensuring unit test coverage of both paths. For the test cases, I ended up changing the accounting to avoid having to multiply the test count. Closes: #1105 Approved by: jlebon
This commit is contained in:
1 parent
355e8516b0
commit
138c4d7aae
5 files changed
+229
-53
No files matched your search
@@ -38,6 +38,28 @@ typedef struct archive OtAutoArchiveWrite;
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(OtAutoArchiveWrite, archive_write_free)
|
||||
typedef struct archive OtAutoArchiveRead;
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(OtAutoArchiveRead, archive_read_free)
|
||||
|
||||
static inline OtAutoArchiveRead *
|
||||
ot_open_archive_read (const char *path, GError **error)
|
||||
{
|
||||
g_autoptr(OtAutoArchiveRead) a = archive_read_new ();
|
||||
|
||||
#ifdef HAVE_ARCHIVE_READ_SUPPORT_FILTER_ALL
|
||||
archive_read_support_filter_all (a);
|
||||
#else
|
||||
archive_read_support_compression_all (a);
|
||||
#endif
|
||||
archive_read_support_format_all (a);
|
||||
if (archive_read_open_filename (a, path, 8192) != ARCHIVE_OK)
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"%s", archive_error_string (a));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return g_steal_pointer (&a);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
G_END_DECLS
|
||||
@@ -123,24 +123,30 @@ squash_trailing_slashes (char *path)
|
||||
*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)
|
||||
{
|
||||
const struct stat *st = archive_entry_stat (entry);
|
||||
struct stat st_copy;
|
||||
struct stat stbuf;
|
||||
read_archive_entry_stat (entry, &stbuf);
|
||||
|
||||
/* 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. */
|
||||
if (archive_entry_hardlink (entry))
|
||||
{
|
||||
st_copy = *st;
|
||||
st_copy.st_mode |= S_IFREG;
|
||||
st = &st_copy;
|
||||
}
|
||||
|
||||
g_autoptr(GFileInfo) info = _ostree_stbuf_to_gfileinfo (st);
|
||||
if (S_ISLNK (st->st_mode))
|
||||
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));
|
||||
|
||||
@@ -247,7 +253,18 @@ aic_get_final_path (OstreeRepoArchiveImportContext *ctx,
|
||||
const char *path,
|
||||
GError **error)
|
||||
{
|
||||
if (ctx->opts->use_ostree_convention)
|
||||
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));
|
||||
}
|
||||
@@ -258,7 +275,6 @@ aic_get_final_entry_pathname (OstreeRepoArchiveImportContext *ctx,
|
||||
{
|
||||
const char *pathname = archive_entry_pathname (ctx->entry);
|
||||
g_autofree char *final = aic_get_final_path (ctx, pathname, error);
|
||||
|
||||
if (final == NULL)
|
||||
return NULL;
|
||||
|
||||
@@ -642,17 +658,17 @@ aic_import_entry (OstreeRepoArchiveImportContext *ctx,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
g_autoptr(GFileInfo) fi = NULL;
|
||||
g_autoptr(OstreeMutableTree) parent = NULL;
|
||||
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;
|
||||
|
||||
@@ -907,18 +923,9 @@ ostree_repo_write_archive_to_mtree (OstreeRepo *self,
|
||||
g_autoptr(OtAutoArchiveRead) a = archive_read_new ();
|
||||
OstreeRepoImportArchiveOptions opts = { 0, };
|
||||
|
||||
#ifdef HAVE_ARCHIVE_READ_SUPPORT_FILTER_ALL
|
||||
archive_read_support_filter_all (a);
|
||||
#else
|
||||
archive_read_support_compression_all (a);
|
||||
#endif
|
||||
archive_read_support_format_all (a);
|
||||
if (archive_read_open_filename (a, gs_file_get_path_cached (archive), 8192) != ARCHIVE_OK)
|
||||
{
|
||||
propagate_libarchive_error (error, a);
|
||||
goto out;
|
||||
}
|
||||
|
||||
a = ot_open_archive_read (gs_file_get_path_cached (archive), error);
|
||||
if (!a)
|
||||
goto out;
|
||||
opts.autocreate_parents = !!autocreate_parents;
|
||||
|
||||
if (!ostree_repo_import_archive_to_mtree (self, &opts, a, mtree, modifier, cancellable, error))
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "ostree-core.h"
|
||||
#include "ostree-types.h"
|
||||
#include "ostree-async-progress.h"
|
||||
@@ -688,6 +690,31 @@ gboolean ostree_repo_write_archive_to_mtree (OstreeRepo *
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
/**
|
||||
* OstreeRepoImportArchiveTranslatePathname:
|
||||
* @repo: Repo
|
||||
* @stbuf: Stat buffer
|
||||
* @src_path: Path in the archive
|
||||
* @user_data: User data
|
||||
*
|
||||
* Possibly change a pathname while importing an archive. If %NULL is returned,
|
||||
* then @src_path will be used unchanged. Otherwise, return a new pathname which
|
||||
* will be freed via `g_free()`.
|
||||
*
|
||||
* This pathname translation will be performed *before* any processing from an
|
||||
* active `OstreeRepoCommitModifier`. Will be invoked for all directory and file
|
||||
* types, first with outer directories, then their sub-files and directories.
|
||||
*
|
||||
* Note that enabling pathname translation will always override the setting for
|
||||
* `use_ostree_convention`.
|
||||
*
|
||||
* Since: 2017.11
|
||||
*/
|
||||
typedef char *(*OstreeRepoImportArchiveTranslatePathname) (OstreeRepo *repo,
|
||||
const struct stat *stbuf,
|
||||
const char *src_path,
|
||||
gpointer user_data);
|
||||
|
||||
/**
|
||||
* OstreeRepoImportArchiveOptions: (skip)
|
||||
*
|
||||
@@ -703,7 +730,9 @@ typedef struct {
|
||||
guint reserved : 28;
|
||||
|
||||
guint unused_uint[8];
|
||||
gpointer unused_ptrs[8];
|
||||
OstreeRepoImportArchiveTranslatePathname translate_pathname;
|
||||
gpointer translate_pathname_user_data;
|
||||
gpointer unused_ptrs[6];
|
||||
} OstreeRepoImportArchiveOptions;
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
|
||||
Reference in new issue
Block a user