blob: fadf0721f844bc1057c8d1dc140a3d0a9f37ff1d [file] [log] [blame]
/*
* create_inode_libarchive.c --- create an inode from libarchive input
*
* Copyright (C) 2023 Johannes Schauer Marin Rodrigues <josch@debian.org>
*
* %Begin-Header%
* This file may be redistributed under the terms of the GNU library
* General Public License, version 2.
* %End-Header%
*/
#define _LARGEFILE64_SOURCE 1
#define _GNU_SOURCE 1
#include "config.h"
#include "create_inode.h"
#include "create_inode_libarchive.h"
#include "support/nls-enable.h"
extern int link_append_flag;
#if (!(defined(CONFIG_DLOPEN_LIBARCHIVE) || defined(HAVE_ARCHIVE_H)) || \
defined(CONFIG_DISABLE_LIBARCHIVE))
/* If ./configure was run with --without-libarchive, then only
* __populate_fs_from_tar() remains in this file and will return an error. */
errcode_t __populate_fs_from_tar(ext2_filsys fs EXT2FS_ATTR((unused)),
ext2_ino_t root_ino EXT2FS_ATTR((unused)),
const char *source_tar EXT2FS_ATTR((unused)),
ext2_ino_t root EXT2FS_ATTR((unused)),
struct hdlinks_s *hdlinks EXT2FS_ATTR((unused)),
struct file_info *target EXT2FS_ATTR((unused)),
int flags EXT2FS_ATTR((unused)),
struct fs_ops_callbacks *fs_callbacks EXT2FS_ATTR((unused))) {
com_err(__func__, 0,
_("you need to compile e2fsprogs without --without-libarchive"
"be able to process tarballs"));
return ENOTSUP;
}
#else
/* If ./configure was NOT run with --without-libarchive, then build with
* support for dlopen()-ing libarchive at runtime. This will also work even
* if archive.h is not available at compile-time. See the comment below. */
/* 64KiB is the minimum blksize to best minimize system call overhead. */
//#define COPY_FILE_BUFLEN 65536
//#define COPY_FILE_BUFLEN 1048576
#define COPY_FILE_BUFLEN 16777216
/* If archive.h was found, include it as usual. To support easier
* bootstrapping, also allow compilation without archive.h present by
* declaring the necessary opaque structs and preprocessor definitions. */
#ifdef HAVE_ARCHIVE_H
#include <archive.h>
#include <archive_entry.h>
#else
struct archive;
struct archive_entry;
#define ARCHIVE_EOF 1 /* Found end of archive. */
#define ARCHIVE_OK 0 /* Operation was successful. */
#include <unistd.h> /* ssize_t */
typedef ssize_t la_ssize_t;
#endif /* HAVE_ARCHIVE_H */
#include <libgen.h>
static const char *(*dl_archive_entry_hardlink)(struct archive_entry *);
static const char *(*dl_archive_entry_pathname)(struct archive_entry *);
static const struct stat *(*dl_archive_entry_stat)(struct archive_entry *);
static const char *(*dl_archive_entry_symlink)(struct archive_entry *);
static int (*dl_archive_entry_xattr_count)(struct archive_entry *);
static int (*dl_archive_entry_xattr_next)(struct archive_entry *, const char **,
const void **, size_t *);
static int (*dl_archive_entry_xattr_reset)(struct archive_entry *);
static const char *(*dl_archive_error_string)(struct archive *);
static int (*dl_archive_read_close)(struct archive *);
static la_ssize_t (*dl_archive_read_data)(struct archive *, void *, size_t);
static int (*dl_archive_read_free)(struct archive *);
static struct archive *(*dl_archive_read_new)(void);
static int (*dl_archive_read_next_header)(struct archive *,
struct archive_entry **);
static int (*dl_archive_read_open_filename)(struct archive *,
const char *filename, size_t);
static int (*dl_archive_read_support_filter_all)(struct archive *);
static int (*dl_archive_read_support_format_all)(struct archive *);
#ifdef CONFIG_DLOPEN_LIBARCHIVE
#include <dlfcn.h>
static void *libarchive_handle;
#if defined(__FreeBSD__)
#define LIBARCHIVE_SO "libarchive.so.7"
#elif defined(__APPLE__)
#define LIBARCHIVE_SO "libarchive.13.dylib"
#else
#define LIBARCHIVE_SO "libarchive.so.13"
#endif
static int libarchive_available(void)
{
if (!libarchive_handle) {
libarchive_handle = dlopen(LIBARCHIVE_SO, RTLD_NOW);
if (!libarchive_handle)
return 0;
dl_archive_entry_hardlink =
(const char *(*)(struct archive_entry *))dlsym(
libarchive_handle, "archive_entry_hardlink");
if (!dl_archive_entry_hardlink)
return 0;
dl_archive_entry_pathname =
(const char *(*)(struct archive_entry *))dlsym(
libarchive_handle, "archive_entry_pathname");
if (!dl_archive_entry_pathname)
return 0;
dl_archive_entry_stat =
(const struct stat *(*)(struct archive_entry *))dlsym(
libarchive_handle, "archive_entry_stat");
if (!dl_archive_entry_stat)
return 0;
dl_archive_entry_symlink =
(const char *(*)(struct archive_entry *))dlsym(
libarchive_handle, "archive_entry_symlink");
if (!dl_archive_entry_symlink)
return 0;
dl_archive_entry_xattr_count =
(int (*)(struct archive_entry *))dlsym(
libarchive_handle, "archive_entry_xattr_count");
if (!dl_archive_entry_xattr_count)
return 0;
dl_archive_entry_xattr_next = (int (*)(
struct archive_entry *, const char **, const void **,
size_t *))dlsym(libarchive_handle,
"archive_entry_xattr_next");
if (!dl_archive_entry_xattr_next)
return 0;
dl_archive_entry_xattr_reset =
(int (*)(struct archive_entry *))dlsym(
libarchive_handle, "archive_entry_xattr_reset");
if (!dl_archive_entry_xattr_reset)
return 0;
dl_archive_error_string =
(const char *(*)(struct archive *))dlsym(
libarchive_handle, "archive_error_string");
if (!dl_archive_error_string)
return 0;
dl_archive_read_close = (int (*)(struct archive *))dlsym(
libarchive_handle, "archive_read_close");
if (!dl_archive_read_close)
return 0;
dl_archive_read_data =
(la_ssize_t(*)(struct archive *, void *, size_t))dlsym(
libarchive_handle, "archive_read_data");
if (!dl_archive_read_data)
return 0;
dl_archive_read_free = (int (*)(struct archive *))dlsym(
libarchive_handle, "archive_read_free");
if (!dl_archive_read_free)
return 0;
dl_archive_read_new = (struct archive * (*)(void))
dlsym(libarchive_handle, "archive_read_new");
if (!dl_archive_read_new)
return 0;
dl_archive_read_next_header = (int (*)(struct archive *,
struct archive_entry **))
dlsym(libarchive_handle, "archive_read_next_header");
if (!dl_archive_read_next_header)
return 0;
dl_archive_read_open_filename =
(int (*)(struct archive *, const char *filename,
size_t))dlsym(libarchive_handle,
"archive_read_open_filename");
if (!dl_archive_read_open_filename)
return 0;
dl_archive_read_support_filter_all =
(int (*)(struct archive *))dlsym(
libarchive_handle,
"archive_read_support_filter_all");
if (!dl_archive_read_support_filter_all)
return 0;
dl_archive_read_support_format_all =
(int (*)(struct archive *))dlsym(
libarchive_handle,
"archive_read_support_format_all");
if (!dl_archive_read_support_format_all)
return 0;
}
return 1;
}
#else
static int libarchive_available(void)
{
dl_archive_entry_hardlink = archive_entry_hardlink;
dl_archive_entry_pathname = archive_entry_pathname;
dl_archive_entry_stat = archive_entry_stat;
dl_archive_entry_symlink = archive_entry_symlink;
dl_archive_entry_xattr_count = archive_entry_xattr_count;
dl_archive_entry_xattr_next = archive_entry_xattr_next;
dl_archive_entry_xattr_reset = archive_entry_xattr_reset;
dl_archive_error_string = archive_error_string;
dl_archive_read_close = archive_read_close;
dl_archive_read_data = archive_read_data;
dl_archive_read_free = archive_read_free;
dl_archive_read_new = archive_read_new;
dl_archive_read_next_header = archive_read_next_header;
dl_archive_read_open_filename = archive_read_open_filename;
dl_archive_read_support_filter_all = archive_read_support_filter_all;
dl_archive_read_support_format_all = archive_read_support_format_all;
return 1;
}
#endif /* CONFIG_DLOPEN_LIBARCHIVE */
static errcode_t __find_path(ext2_filsys fs, ext2_ino_t root, const char *name,
ext2_ino_t *inode)
{
errcode_t retval = 0;
ext2_ino_t tmpino;
char *p, *n, *n2 = strdup(name);
if (n2 == NULL) {
retval = errno;
goto out;
}
n = n2;
*inode = root;
/* any number of leading slashes */
while (*n == '/')
n++;
while (*n) {
/* replace the next slash by a NULL, if any */
if ((p = strchr(n, '/')))
(*p) = 0;
/* find the inode of the next component */
retval = ext2fs_lookup(fs, *inode, n, strlen(n), 0, &tmpino);
if (retval)
goto out;
*inode = tmpino;
/* continue the search at the character after the slash */
if (p)
n = p + 1;
else
break;
}
out:
free(n2);
return retval;
}
/* Rounds quantity up to a multiple of size. size should be a power of 2 */
static inline unsigned int __round_up(unsigned int quantity, unsigned int size)
{
return (quantity + (size - 1)) & ~(size - 1);
}
static int remove_inode(ext2_filsys fs, ext2_ino_t ino)
{
errcode_t ret = 0;
struct ext2_inode_large inode;
memset(&inode, 0, sizeof(inode));
ret = ext2fs_read_inode_full(fs, ino, (struct ext2_inode *)&inode,
sizeof(inode));
if (ret)
goto out;
switch (inode.i_links_count) {
case 0:
return 0; /* XXX: already done? */
case 1:
inode.i_links_count--;
ext2fs_set_dtime(fs, EXT2_INODE(&inode));
break;
default:
inode.i_links_count--;
}
if (inode.i_links_count)
goto write_out;
/* Nobody holds this file; free its blocks! */
ret = ext2fs_free_ext_attr(fs, ino, &inode);
if (ret)
goto write_out;
if (ext2fs_inode_has_valid_blocks2(fs, (struct ext2_inode *)&inode)) {
ret = ext2fs_punch(fs, ino, (struct ext2_inode *)&inode, NULL,
0, ~0ULL);
if (ret)
goto write_out;
}
ext2fs_inode_alloc_stats2(fs, ino, -1, LINUX_S_ISDIR(inode.i_mode));
write_out:
ret = ext2fs_write_inode_full(fs, ino, (struct ext2_inode *)&inode,
sizeof(inode));
out:
return ret;
}
static errcode_t copy_file_chunk_tar(ext2_filsys fs, struct archive *archive,
ext2_file_t e2_file, off_t start,
off_t end, char *buf, char *zerobuf)
{
off_t off, bpos;
ssize_t got, blen;
unsigned int written;
char *ptr;
errcode_t err = 0;
for (off = start; off < end; off += COPY_FILE_BUFLEN) {
got = dl_archive_read_data(archive, buf, COPY_FILE_BUFLEN);
if (got < 0) {
err = errno;
goto fail;
}
for (bpos = 0, ptr = buf; bpos < got; bpos += fs->blocksize) {
blen = fs->blocksize;
if (blen > got - bpos)
blen = got - bpos;
if (memcmp(ptr, zerobuf, blen) == 0) {
ptr += blen;
continue;
}
err = ext2fs_file_llseek(e2_file, off + bpos,
EXT2_SEEK_SET, NULL);
if (err)
goto fail;
while (blen > 0) {
err = ext2fs_file_write(e2_file, ptr, blen,
&written);
if (err)
goto fail;
if (written == 0) {
err = EIO;
goto fail;
}
blen -= written;
ptr += written;
}
}
}
fail:
return err;
}
static errcode_t copy_file_tar(ext2_filsys fs, struct archive *archive,
const struct stat *statbuf, ext2_ino_t ino)
{
ext2_file_t e2_file;
char *buf = NULL, *zerobuf = NULL;
errcode_t err, close_err;
err = ext2fs_file_open(fs, ino, EXT2_FILE_WRITE, &e2_file);
if (err)
return err;
err = ext2fs_get_mem(COPY_FILE_BUFLEN, &buf);
if (err)
goto out;
err = ext2fs_get_memzero(fs->blocksize, &zerobuf);
if (err)
goto out;
err = copy_file_chunk_tar(fs, archive, e2_file, 0, statbuf->st_size,
buf, zerobuf);
out:
ext2fs_free_mem(&zerobuf);
ext2fs_free_mem(&buf);
close_err = ext2fs_file_close(e2_file);
if (err == 0)
err = close_err;
return err;
}
static errcode_t do_write_internal_tar(ext2_filsys fs, ext2_ino_t cwd,
struct archive *archive,
const char *dest,
const struct stat *statbuf)
{
ext2_ino_t newfile;
errcode_t retval;
struct ext2_inode inode;
retval = ext2fs_new_inode(fs, cwd, 010755, 0, &newfile);
if (retval)
goto out;
#ifdef DEBUGFS
printf("Allocated inode: %u\n", newfile);
#endif
retval = ext2fs_link(fs, cwd, dest, newfile,
EXT2_FT_REG_FILE | link_append_flag);
if (retval)
goto out;
if (ext2fs_test_inode_bitmap2(fs->inode_map, newfile))
com_err(__func__, 0, "Warning: inode already set");
ext2fs_inode_alloc_stats2(fs, newfile, 1, 0);
memset(&inode, 0, sizeof(inode));
inode.i_mode = (statbuf->st_mode & ~S_IFMT) | LINUX_S_IFREG;
inode.i_atime = inode.i_ctime = inode.i_mtime = fs->now ? fs->now :
time(0);
inode.i_links_count = 1;
retval = ext2fs_inode_size_set(fs, &inode, statbuf->st_size);
if (retval)
goto out;
if (ext2fs_has_feature_inline_data(fs->super)) {
inode.i_flags |= EXT4_INLINE_DATA_FL;
} else if (ext2fs_has_feature_extents(fs->super)) {
ext2_extent_handle_t handle;
inode.i_flags &= ~EXT4_EXTENTS_FL;
retval = ext2fs_extent_open2(fs, newfile, &inode, &handle);
if (retval)
goto out;
ext2fs_extent_free(handle);
}
retval = ext2fs_write_new_inode(fs, newfile, &inode);
if (retval)
goto out;
if (inode.i_flags & EXT4_INLINE_DATA_FL) {
retval = ext2fs_inline_data_init(fs, newfile);
if (retval)
goto out;
}
if (LINUX_S_ISREG(inode.i_mode)) {
retval = copy_file_tar(fs, archive, statbuf, newfile);
if (retval)
goto out;
}
out:
return retval;
}
static errcode_t set_inode_xattr_tar(ext2_filsys fs, ext2_ino_t ino,
struct archive_entry *entry)
{
errcode_t retval, close_retval;
struct ext2_xattr_handle *handle;
ssize_t size;
const char *name;
const void *value;
size_t value_size;
size = dl_archive_entry_xattr_count(entry);
if (size == 0)
return 0;
retval = ext2fs_xattrs_open(fs, ino, &handle);
if (retval) {
if (retval == EXT2_ET_MISSING_EA_FEATURE)
return 0;
com_err(__func__, retval, _("while opening inode %u"), ino);
return retval;
}
retval = ext2fs_xattrs_read(handle);
if (retval) {
com_err(__func__, retval,
_("while reading xattrs for inode %u"), ino);
goto out;
}
dl_archive_entry_xattr_reset(entry);
while (dl_archive_entry_xattr_next(entry, &name, &value, &value_size) ==
ARCHIVE_OK) {
if (strcmp(name, "security.capability") != 0 && strcmp(name, "gnu.translator"))
continue;
retval = ext2fs_xattr_set(handle, name, value, value_size);
if (retval) {
com_err(__func__, retval,
_("while writing attribute \"%s\" to inode %u"),
name, ino);
break;
}
}
out:
close_retval = ext2fs_xattrs_close(&handle);
if (close_retval) {
com_err(__func__, retval, _("while closing inode %u"), ino);
retval = retval ? retval : close_retval;
}
return retval;
}
static errcode_t handle_entry(ext2_filsys fs, ext2_ino_t root_ino,
ext2_ino_t root, ext2_ino_t dirinode, char *name,
struct archive *a, struct archive_entry *entry,
const struct stat *st)
{
errcode_t retval = 0;
char *ln_target;
ext2_ino_t tmpino;
switch (st->st_mode & S_IFMT) {
case S_IFCHR:
case S_IFBLK:
case S_IFIFO:
case S_IFSOCK:
retval = do_mknod_internal(fs, dirinode, name, st->st_mode,
st->st_rdev);
if (retval) {
com_err(__func__, retval,
_("while creating special file "
"\"%s\""),
name);
return 1;
}
break;
case S_IFLNK:
ln_target = calloc(
1, __round_up(strlen(dl_archive_entry_symlink(entry)),
1024));
strcpy(ln_target, dl_archive_entry_symlink(entry));
retval = do_symlink_internal(fs, dirinode, name, ln_target,
root);
free(ln_target);
if (retval) {
com_err(__func__, retval,
_("while writing symlink\"%s\""), name);
return 1;
}
break;
case S_IFREG:
retval = do_write_internal_tar(fs, dirinode, a, name, st);
if (retval) {
com_err(__func__, retval,
_("while writing file \"%s\""), name);
return 1;
}
break;
case S_IFDIR:
retval = do_mkdir_internal(fs, dirinode, name, 0, root);
if (retval) {
com_err(__func__, retval, _("while making dir \"%s\""),
name);
return 1;
}
break;
default:
if (dl_archive_entry_hardlink(entry) != NULL) {
if ((retval = __find_path(
fs, root_ino,
dl_archive_entry_hardlink(entry),
&tmpino))) {
com_err(__func__, retval,
_("cannot find hardlink destination \"%s\" "
"to create \"%s\""),
dl_archive_entry_hardlink(entry), name);
return 1;
}
retval = add_link(fs, dirinode, tmpino, name);
if (retval) {
com_err(__func__, retval, "while linking %s",
name);
return 1;
}
} else {
com_err(__func__, 0, _("ignoring entry \"%s\""),
dl_archive_entry_pathname(entry));
}
}
return 0;
}
errcode_t __populate_fs_from_tar(ext2_filsys fs, ext2_ino_t root_ino,
const char *source_tar, ext2_ino_t root,
struct hdlinks_s *hdlinks EXT2FS_ATTR((unused)),
struct file_info *target,
int flags,
struct fs_ops_callbacks *fs_callbacks)
{
char *path2=NULL, *path3=NULL, *dir, *name;
unsigned int dir_exists;
struct archive *a;
struct archive_entry *entry;
errcode_t retval = 0;
ext2_ino_t dirinode, tmpino;
const struct stat *st;
if (!libarchive_available()) {
com_err(__func__, 0,
_("you need libarchive to be able to process tarballs"));
return 1;
}
a = dl_archive_read_new();
if (a == NULL) {
retval = 1;
com_err(__func__, retval, _("while creating archive reader"));
goto out;
}
if (dl_archive_read_support_filter_all(a) != ARCHIVE_OK) {
retval = 1;
com_err(__func__, retval, _("while enabling decompression"));
goto out;
}
if (dl_archive_read_support_format_all(a) != ARCHIVE_OK) {
retval = 1;
com_err(__func__, retval, _("while enabling reader formats"));
goto out;
}
if ((retval = dl_archive_read_open_filename(a, source_tar, 4096))) {
com_err(__func__, retval, _("while opening \"%s\""),
dl_archive_error_string(a));
goto out;
}
for (;;) {
retval = dl_archive_read_next_header(a, &entry);
if (retval == ARCHIVE_EOF) {
retval = 0;
break;
}
if (retval != ARCHIVE_OK) {
com_err(__func__, retval,
_("cannot read archive header: \"%s\""),
dl_archive_error_string(a));
goto out;
}
path2 = strdup(dl_archive_entry_pathname(entry));
path3 = strdup(dl_archive_entry_pathname(entry));
if (!path2 || !path3) {
retval = ENOMEM;
goto out;
}
name = basename(path2);
dir = dirname(path3);
if ((retval = __find_path(fs, root_ino, dir, &dirinode))) {
com_err(__func__, retval,
_("cannot find directory \"%s\" to create \"%s\""),
dir, name);
goto out;
}
/*
* Did we already create this file as the result of a repeated entry
* in the tarball? Delete the existing one (except if it is a
* directory) so that it can be re-created by handle_entry().
*/
dir_exists = 0;
st = dl_archive_entry_stat(entry);
retval = ext2fs_namei(fs, root, dirinode, name, &tmpino);
if (!retval) {
if ((st->st_mode & S_IFMT) == S_IFDIR) {
dir_exists = 1;
} else {
retval = ext2fs_unlink(fs, dirinode, name,
tmpino, 0);
if (retval) {
com_err(__func__, retval,
_("failed to unlink \"%s/%s\""),
dir, name);
goto out;
}
retval = remove_inode(fs, tmpino);
if (retval) {
com_err(__func__, retval,
_("failed to remove inode of \"%s/%s\""),
dir, name);
goto out;
}
}
}
/*
* Create files, directories, symlinks etc referenced by this archive
* entry unless this is an already existing directory
*/
if (!dir_exists) {
retval = handle_entry(fs, root_ino, root, dirinode,
name, a, entry, st);
if (retval)
goto out;
retval =
ext2fs_namei(fs, root, dirinode, name, &tmpino);
if (retval) {
com_err(__func__, retval,
_("while looking up \"%s\""), name);
goto out;
}
}
/* set uid, gid, mode and time for the new (or re-created) inode */
retval = set_inode_extra(fs, tmpino, st);
if (retval) {
com_err(__func__, retval,
_("while setting inode for \"%s\""), name);
goto out;
}
if ((flags & POPULATE_FS_NO_COPY_XATTRS) == 0) {
retval = set_inode_xattr_tar(fs, tmpino, entry);
if (retval) {
com_err(__func__, retval,
_("while setting xattrs for \"%s\""),
name);
goto out;
}
}
if (fs_callbacks && fs_callbacks->end_create_new_inode) {
retval = fs_callbacks->end_create_new_inode(
fs, target->path, name, dirinode, root,
st->st_mode & S_IFMT);
if (retval)
goto out;
}
free(path2); path2 = NULL;
free(path3); path3 = NULL;
}
out:
free(path2);
free(path3);
dl_archive_read_close(a);
dl_archive_read_free(a);
return retval;
}
#endif /* CONFIG_DISABLE_LIBARCHIVE */