machined/import: allow running in per-user mode (#38728)

This commit is contained in:
Daan De Meyer
2025-11-04 17:27:43 +01:00
committed by GitHub
9 changed files with 835 additions and 45 deletions

25
TODO
View File

@@ -63,6 +63,12 @@ Regularly:
Janitorial Clean-ups:
* machined: make remaining machine bus calls compatible with unpriv machined +
unpriv npsawn: GetAddresses(), GetSSHInfo(), GetOSRelease(), OpenPTY(),
OpenLogin(), OpenShell(), BindMount(), CopyFrom(), CopyTo(),
OpenRootDirectory(). Similar for images: GetHostname(), GetMachineID(),
GetMachineInfo(), GetOSRelease().
* rework mount.c and swap.c to follow proper state enumeration/deserialization
semantics, like we do for device.c now
@@ -469,18 +475,10 @@ Features:
* allow dynamic modifications of ConcurrencyHardMax= and ConcurrencySoftMax=
via DBus (and with that also by daemon-reload)
* importd: introduce a per-user instance, that downloads into per-user DDI dirs
* sysupdated: similar
* sysupdated: introduce per-user version that can update per-user installed dDIs
* portabled: similar
* machined: implement a per-user instance, that manages per-user DDI dirs for
images. systemd-nspawn/systemd-vmspawn should probably register with both the
system and the user scoped machined instance. The former to get the machine
name registered as hostname, and the latter so that the image stuff is nicely
per-user managed.
* resolved: make resolved process DNR DHCP info
* maybe introduce an OSC sequence that signals when we ask for a password, so
@@ -490,14 +488,8 @@ Features:
* start using STATX_SUBVOL in btrfs_is_subvol(). Also, make use of it
generically, so that image discovery recognizes bcachefs subvols too.
* "systemd-export tar" should reuse the libarchive export code from systemd-dissect
--archive.
* "systemd-import tar" should be moved to libarchive
* foreign uid:
- add support to export-fs, import-fs, import-tar, export-tar
- add tool for deleting foreign UID held container images
- add support to export-fs, import-fs
- systemd-dissect should learn mappings, too, when doing mtree and such
* format-table: introduce new cell type for strings with ansi sequences in
@@ -720,7 +712,6 @@ Features:
from, tracing through overlayfs, DM, loopback block device.
* importd/importctl
- port tar handling to libarchive
- complete varlink interface
- download images into .v/ dirs

View File

@@ -35,7 +35,9 @@ int import_fork_tar_x(int tree_fd, int userns_fd, PidRef *ret_pid) {
if (r < 0)
return r;
TarFlags flags = mac_selinux_use() ? TAR_SELINUX : 0;
TarFlags flags =
(userns_fd >= 0 ? TAR_SQUASH_UIDS_ABOVE_64K : 0) |
(mac_selinux_use() ? TAR_SELINUX : 0);
_cleanup_close_pair_ int pipefd[2] = EBADF_PAIR;
if (pipe2(pipefd, O_CLOEXEC) < 0)
@@ -100,7 +102,9 @@ int import_fork_tar_c(int tree_fd, int userns_fd, PidRef *ret_pid) {
if (r < 0)
return r;
TarFlags flags = mac_selinux_use() ? TAR_SELINUX : 0;
TarFlags flags =
(userns_fd >= 0 ? TAR_SQUASH_UIDS_ABOVE_64K : 0) |
(mac_selinux_use() ? TAR_SELINUX : 0);
_cleanup_close_pair_ int pipefd[2] = EBADF_PAIR;
if (pipe2(pipefd, O_CLOEXEC) < 0)

View File

@@ -23,6 +23,7 @@ DLSYM_PROTOTYPE(acl_delete_entry);
DLSYM_PROTOTYPE(acl_delete_perm);
DLSYM_PROTOTYPE(acl_dup);
DLSYM_PROTOTYPE(acl_entries);
DLSYM_PROTOTYPE(acl_extended_file);
DLSYM_PROTOTYPE(acl_free);
DLSYM_PROTOTYPE(acl_from_mode);
DLSYM_PROTOTYPE(acl_from_text);
@@ -36,6 +37,7 @@ DLSYM_PROTOTYPE(acl_get_tag_type);
DLSYM_PROTOTYPE(acl_init);
DLSYM_PROTOTYPE(acl_set_fd);
DLSYM_PROTOTYPE(acl_set_file);
DLSYM_PROTOTYPE(acl_set_permset);
DLSYM_PROTOTYPE(acl_set_qualifier);
DLSYM_PROTOTYPE(acl_set_tag_type);
DLSYM_PROTOTYPE(acl_to_any_text);
@@ -58,6 +60,7 @@ int dlopen_libacl(void) {
DLSYM_ARG(acl_delete_perm),
DLSYM_ARG(acl_dup),
DLSYM_ARG(acl_entries),
DLSYM_ARG(acl_extended_file),
DLSYM_ARG(acl_free),
DLSYM_ARG(acl_from_mode),
DLSYM_ARG(acl_from_text),
@@ -71,6 +74,7 @@ int dlopen_libacl(void) {
DLSYM_ARG(acl_init),
DLSYM_ARG(acl_set_fd),
DLSYM_ARG(acl_set_file),
DLSYM_ARG(acl_set_permset),
DLSYM_ARG(acl_set_qualifier),
DLSYM_ARG(acl_set_tag_type),
DLSYM_ARG(acl_to_any_text));

View File

@@ -20,6 +20,7 @@ extern DLSYM_PROTOTYPE(acl_delete_entry);
extern DLSYM_PROTOTYPE(acl_delete_perm);
extern DLSYM_PROTOTYPE(acl_dup);
extern DLSYM_PROTOTYPE(acl_entries);
extern DLSYM_PROTOTYPE(acl_extended_file);
extern DLSYM_PROTOTYPE(acl_free);
extern DLSYM_PROTOTYPE(acl_from_mode);
extern DLSYM_PROTOTYPE(acl_from_text);
@@ -33,6 +34,7 @@ extern DLSYM_PROTOTYPE(acl_get_tag_type);
extern DLSYM_PROTOTYPE(acl_init);
extern DLSYM_PROTOTYPE(acl_set_fd);
extern DLSYM_PROTOTYPE(acl_set_file);
extern DLSYM_PROTOTYPE(acl_set_permset);
extern DLSYM_PROTOTYPE(acl_set_qualifier);
extern DLSYM_PROTOTYPE(acl_set_tag_type);
extern DLSYM_PROTOTYPE(acl_to_any_text);
@@ -62,6 +64,10 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(char*, sym_acl_free, acl_free_charpp, NU
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(uid_t*, sym_acl_free, acl_free_uid_tpp, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(gid_t*, sym_acl_free, acl_free_gid_tpp, NULL);
static inline int acl_set_perm(acl_permset_t ps, acl_perm_t p, bool b) {
return (b ? sym_acl_add_perm : sym_acl_delete_perm)(ps, p);
}
#else
#define ACL_READ 0x04

View File

@@ -7,6 +7,10 @@
#if HAVE_LIBARCHIVE
static void *libarchive_dl = NULL;
DLSYM_PROTOTYPE(archive_entry_acl_add_entry) = NULL;
DLSYM_PROTOTYPE(archive_entry_acl_next) = NULL;
DLSYM_PROTOTYPE(archive_entry_acl_reset) = NULL;
DLSYM_PROTOTYPE(archive_entry_fflags) = NULL;
DLSYM_PROTOTYPE(archive_entry_filetype) = NULL;
DLSYM_PROTOTYPE(archive_entry_free) = NULL;
DLSYM_PROTOTYPE(archive_entry_gid) = NULL;
@@ -26,8 +30,10 @@ DLSYM_PROTOTYPE(archive_entry_pathname) = NULL;
DLSYM_PROTOTYPE(archive_entry_rdevmajor) = NULL;
DLSYM_PROTOTYPE(archive_entry_rdevminor) = NULL;
DLSYM_PROTOTYPE(archive_entry_set_ctime) = NULL;
DLSYM_PROTOTYPE(archive_entry_set_fflags) = NULL;
DLSYM_PROTOTYPE(archive_entry_set_filetype) = NULL;
DLSYM_PROTOTYPE(archive_entry_set_gid) = NULL;
DLSYM_PROTOTYPE(archive_entry_set_hardlink) = NULL;
DLSYM_PROTOTYPE(archive_entry_set_mtime) = NULL;
DLSYM_PROTOTYPE(archive_entry_set_pathname) = NULL;
DLSYM_PROTOTYPE(archive_entry_set_perm) = NULL;
@@ -36,11 +42,13 @@ DLSYM_PROTOTYPE(archive_entry_set_rdevminor) = NULL;
DLSYM_PROTOTYPE(archive_entry_set_size) = NULL;
DLSYM_PROTOTYPE(archive_entry_set_symlink) = NULL;
DLSYM_PROTOTYPE(archive_entry_set_uid) = NULL;
DLSYM_PROTOTYPE(archive_entry_sparse_add_entry) = NULL;
DLSYM_PROTOTYPE(archive_entry_symlink) = NULL;
DLSYM_PROTOTYPE(archive_entry_uid) = NULL;
#if HAVE_LIBARCHIVE_UID_IS_SET
DLSYM_PROTOTYPE(archive_entry_uid_is_set) = NULL;
#endif
DLSYM_PROTOTYPE(archive_entry_xattr_add_entry) = NULL;
DLSYM_PROTOTYPE(archive_entry_xattr_next) = NULL;
DLSYM_PROTOTYPE(archive_entry_xattr_reset) = NULL;
DLSYM_PROTOTYPE(archive_error_string) = NULL;
@@ -59,7 +67,7 @@ DLSYM_PROTOTYPE(archive_write_new) = NULL;
DLSYM_PROTOTYPE(archive_write_open_FILE) = NULL;
DLSYM_PROTOTYPE(archive_write_open_fd) = NULL;
DLSYM_PROTOTYPE(archive_write_set_format_filter_by_ext) = NULL;
DLSYM_PROTOTYPE(archive_write_set_format_gnutar) = NULL;
DLSYM_PROTOTYPE(archive_write_set_format_pax) = NULL;
int dlopen_libarchive(void) {
ELF_NOTE_DLOPEN("archive",
@@ -71,6 +79,10 @@ int dlopen_libarchive(void) {
&libarchive_dl,
"libarchive.so.13",
LOG_DEBUG,
DLSYM_ARG(archive_entry_acl_add_entry),
DLSYM_ARG(archive_entry_acl_next),
DLSYM_ARG(archive_entry_acl_reset),
DLSYM_ARG(archive_entry_fflags),
DLSYM_ARG(archive_entry_filetype),
DLSYM_ARG(archive_entry_free),
DLSYM_ARG(archive_entry_gid),
@@ -90,8 +102,10 @@ int dlopen_libarchive(void) {
DLSYM_ARG(archive_entry_rdevmajor),
DLSYM_ARG(archive_entry_rdevminor),
DLSYM_ARG(archive_entry_set_ctime),
DLSYM_ARG(archive_entry_set_fflags),
DLSYM_ARG(archive_entry_set_filetype),
DLSYM_ARG(archive_entry_set_gid),
DLSYM_ARG(archive_entry_set_hardlink),
DLSYM_ARG(archive_entry_set_mtime),
DLSYM_ARG(archive_entry_set_pathname),
DLSYM_ARG(archive_entry_set_perm),
@@ -100,11 +114,13 @@ int dlopen_libarchive(void) {
DLSYM_ARG(archive_entry_set_size),
DLSYM_ARG(archive_entry_set_symlink),
DLSYM_ARG(archive_entry_set_uid),
DLSYM_ARG(archive_entry_sparse_add_entry),
DLSYM_ARG(archive_entry_symlink),
DLSYM_ARG(archive_entry_uid),
#if HAVE_LIBARCHIVE_UID_IS_SET
DLSYM_ARG(archive_entry_uid_is_set),
#endif
DLSYM_ARG(archive_entry_xattr_add_entry),
DLSYM_ARG(archive_entry_xattr_next),
DLSYM_ARG(archive_entry_xattr_reset),
DLSYM_ARG(archive_error_string),
@@ -123,8 +139,7 @@ int dlopen_libarchive(void) {
DLSYM_ARG(archive_write_open_FILE),
DLSYM_ARG(archive_write_open_fd),
DLSYM_ARG(archive_write_set_format_filter_by_ext),
DLSYM_ARG(archive_write_set_format_gnutar)
);
DLSYM_ARG(archive_write_set_format_pax));
}
/* libarchive uses its own file type macros. They happen to be defined the same way as the Linux ones, and

View File

@@ -9,6 +9,10 @@
#include "dlfcn-util.h"
extern DLSYM_PROTOTYPE(archive_entry_acl_add_entry);
extern DLSYM_PROTOTYPE(archive_entry_acl_next);
extern DLSYM_PROTOTYPE(archive_entry_acl_reset);
extern DLSYM_PROTOTYPE(archive_entry_fflags);
extern DLSYM_PROTOTYPE(archive_entry_filetype);
extern DLSYM_PROTOTYPE(archive_entry_free);
extern DLSYM_PROTOTYPE(archive_entry_gid);
@@ -22,8 +26,10 @@ extern DLSYM_PROTOTYPE(archive_entry_pathname);
extern DLSYM_PROTOTYPE(archive_entry_rdevmajor);
extern DLSYM_PROTOTYPE(archive_entry_rdevminor);
extern DLSYM_PROTOTYPE(archive_entry_set_ctime);
extern DLSYM_PROTOTYPE(archive_entry_set_fflags);
extern DLSYM_PROTOTYPE(archive_entry_set_filetype);
extern DLSYM_PROTOTYPE(archive_entry_set_gid);
extern DLSYM_PROTOTYPE(archive_entry_set_hardlink);
extern DLSYM_PROTOTYPE(archive_entry_set_mtime);
extern DLSYM_PROTOTYPE(archive_entry_set_pathname);
extern DLSYM_PROTOTYPE(archive_entry_set_perm);
@@ -32,8 +38,10 @@ extern DLSYM_PROTOTYPE(archive_entry_set_rdevminor);
extern DLSYM_PROTOTYPE(archive_entry_set_size);
extern DLSYM_PROTOTYPE(archive_entry_set_symlink);
extern DLSYM_PROTOTYPE(archive_entry_set_uid);
extern DLSYM_PROTOTYPE(archive_entry_sparse_add_entry);
extern DLSYM_PROTOTYPE(archive_entry_symlink);
extern DLSYM_PROTOTYPE(archive_entry_uid);
extern DLSYM_PROTOTYPE(archive_entry_xattr_add_entry);
extern DLSYM_PROTOTYPE(archive_entry_xattr_next);
extern DLSYM_PROTOTYPE(archive_entry_xattr_reset);
extern DLSYM_PROTOTYPE(archive_error_string);
@@ -52,7 +60,7 @@ extern DLSYM_PROTOTYPE(archive_write_new);
extern DLSYM_PROTOTYPE(archive_write_open_FILE);
extern DLSYM_PROTOTYPE(archive_write_open_fd);
extern DLSYM_PROTOTYPE(archive_write_set_format_filter_by_ext);
extern DLSYM_PROTOTYPE(archive_write_set_format_gnutar);
extern DLSYM_PROTOTYPE(archive_write_set_format_pax);
#if HAVE_LIBARCHIVE_UID_IS_SET
extern DLSYM_PROTOTYPE(archive_entry_gid_is_set);

View File

@@ -5,16 +5,25 @@
#include "tar-util.h"
#if HAVE_LIBARCHIVE
#include <sys/mount.h>
#include <sys/sysmacros.h>
#include "acl-util.h"
#include "alloc-util.h"
#include "chase.h"
#include "chattr-util.h"
#include "fd-util.h"
#include "fs-util.h"
#include "hexdecoct.h"
#include "iovec-util.h"
#include "libarchive-util.h"
#include "mountpoint-util.h"
#include "nsresource.h"
#include "nulstr-util.h"
#include "path-util.h"
#include "recurse-dir.h"
#include "rm-rf.h"
#include "sha256.h"
#include "stat-util.h"
#include "string-util.h"
#include "tmpfile-util.h"
@@ -23,6 +32,15 @@
#define DEPTH_MAX 128U
/* We are a bit conservative with the flags we save/restore in tar files */
#define CHATTR_TAR_FL \
(FS_NOATIME_FL | \
FS_NOCOW_FL | \
FS_PROJINHERIT_FL | \
FS_NODUMP_FL | \
FS_SYNC_FL | \
FS_DIRSYNC_FL)
typedef struct XAttr {
char *name;
struct iovec data;
@@ -38,8 +56,10 @@ typedef struct OpenInode {
struct timespec mtime;
uid_t uid;
gid_t gid;
unsigned fflags;
XAttr *xattr;
size_t n_xattr;
acl_t acl_access, acl_default;
} OpenInode;
static void xattr_done(XAttr *xa) {
@@ -68,6 +88,10 @@ static void open_inode_done(OpenInode *of) {
of->path = mfree(of->path);
}
xattr_done_many(of->xattr, of->n_xattr);
if (of->acl_access)
sym_acl_free(of->acl_access);
if (of->acl_default)
sym_acl_free(of->acl_default);
}
static void open_inode_done_many(OpenInode *array, size_t n) {
@@ -79,6 +103,29 @@ static void open_inode_done_many(OpenInode *array, size_t n) {
free(array);
}
static int open_inode_apply_acl(OpenInode *of) {
int r = 0;
assert(of);
assert(of->fd >= 0);
if (!inode_type_can_acl(of->filetype))
return 0;
if (of->acl_access) {
if (sym_acl_set_fd(of->fd, of->acl_access) < 0)
RET_GATHER(r, log_error_errno(errno, "Failed to adjust ACLs of '%s': %m", of->path));
}
if (of->filetype == S_IFDIR && of->acl_default) {
/* There's no API to set default ACLs by fd, hence go by /proc/self/fd/ path */
if (sym_acl_set_file(FORMAT_PROC_FD_PATH(of->fd), ACL_TYPE_DEFAULT, of->acl_default) < 0)
RET_GATHER(r, log_error_errno(errno, "Failed to adjust default ACLs of '%s': %m", of->path));
}
return r;
}
static int open_inode_finalize(OpenInode *of) {
int r = 0;
@@ -99,6 +146,24 @@ static int open_inode_finalize(OpenInode *of) {
RET_GATHER(r, log_error_errno(k, "Failed to adjust ownership/mode of '%s': %m", of->path));
}
k = open_inode_apply_acl(of);
if (k < 0)
RET_GATHER(r, log_error_errno(k, "Failed to adjust ACL of '%s': %m", of->path));
if ((of->fflags & ~CHATTR_EARLY_FL) != 0 && inode_type_can_chattr(of->filetype)) {
k = chattr_full(of->fd,
/* path= */ NULL,
/* value= */ of->fflags,
/* mask= */ of->fflags & ~CHATTR_EARLY_FL,
/* ret_previous= */ NULL,
/* ret_final= */ NULL,
CHATTR_FALLBACK_BITWISE);
if (ERRNO_IS_NEG_NOT_SUPPORTED(k))
log_warning_errno(k, "Failed to apply chattr of '%s', ignoring: %m", of->path);
else if (k < 0)
RET_GATHER(r, log_error_errno(k, "Failed to adjust chattr of '%s': %m", of->path));
}
/* We also adjust the mtime only after leaving a dir, since it might otherwise change again
* because we make modifications inside it */
if (of->mtime.tv_nsec != UTIME_OMIT) {
@@ -149,7 +214,8 @@ static int archive_unpack_regular(
struct archive_entry *entry,
int parent_fd,
const char *filename,
const char *path) {
const char *path,
unsigned fflags) {
int r;
@@ -164,6 +230,22 @@ static int archive_unpack_regular(
if (fd < 0)
return log_error_errno(fd, "Failed to create regular file '%s': %m", path);
if ((fflags & CHATTR_EARLY_FL) != 0) {
r = chattr_full(fd,
/* path= */ NULL,
/* value= */ fflags,
/* mask= */ fflags & CHATTR_EARLY_FL,
/* ret_previous= */ NULL,
/* ret_final= */ NULL,
CHATTR_FALLBACK_BITWISE);
if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
log_warning_errno(r, "Failed to apply chattr of '%s', ignoring: %m", path);
else if (r < 0) {
log_error_errno(r, "Failed to adjust chattr of '%s': %m", path);
goto fail;
}
}
r = sym_archive_read_data_into_fd(a, fd);
if (r != ARCHIVE_OK) {
r = log_error_errno(
@@ -204,7 +286,10 @@ static int archive_unpack_directory(
struct archive_entry *entry,
int parent_fd,
const char *filename,
const char *path) {
const char *path,
unsigned fflags) {
int r;
assert(a);
assert(entry);
@@ -220,6 +305,20 @@ static int archive_unpack_directory(
if (fd < 0)
return log_error_errno(fd, "Failed to create directory '%s': %m", path);
if ((fflags & CHATTR_EARLY_FL) != 0) {
r = chattr_full(fd,
/* path= */ NULL,
/* value= */ fflags,
/* mask= */ fflags & CHATTR_EARLY_FL,
/* ret_previous= */ NULL,
/* ret_final= */ NULL,
CHATTR_FALLBACK_BITWISE);
if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
log_warning_errno(r, "Failed to apply chattr of '%s', ignoring: %m", path);
else if (r < 0)
return log_error_errno(r, "Failed to adjust chattr of '%s': %m", path);
}
return TAKE_FD(fd);
}
@@ -321,6 +420,145 @@ static int archive_entry_pathname_safe(struct archive_entry *entry, const char *
return 0;
}
static int archive_entry_read_acl(
struct archive_entry *entry,
acl_type_t ntype,
acl_t *acl,
TarFlags flags) {
int r;
assert(entry);
assert(acl);
int type;
if (ntype == ACL_TYPE_ACCESS)
type = ARCHIVE_ENTRY_ACL_TYPE_ACCESS;
else if (ntype == ACL_TYPE_DEFAULT)
type = ARCHIVE_ENTRY_ACL_TYPE_DEFAULT;
else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unexpected ACL type");
int c = sym_archive_entry_acl_reset(entry, type);
if (c == 0)
return 0;
assert(c > 0);
r = dlopen_libacl();
if (r < 0) {
log_debug_errno(r, "Not restoring ACL data on inode as libacl is not available: %m");
return 0;
}
_cleanup_(acl_freep) acl_t a = NULL;
a = sym_acl_init(c);
if (!a)
return log_oom();
for (;;) {
int rtype, permset, tag, qual;
const char *name;
r = sym_archive_entry_acl_next(
entry,
type,
&rtype,
&permset,
&tag,
&qual,
&name);
if (r == ARCHIVE_EOF)
break;
if (r != ARCHIVE_OK)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unexpected error while iterating through ACLs.");
assert(rtype == type);
acl_entry_t e;
if (sym_acl_create_entry(&a, &e) < 0)
return log_error_errno(errno, "Failed to create ACL entry: %m");
static const struct {
int libarchive;
acl_tag_t libacl;
} tag_map[] = {
{ ARCHIVE_ENTRY_ACL_USER, ACL_USER },
{ ARCHIVE_ENTRY_ACL_GROUP, ACL_GROUP },
{ ARCHIVE_ENTRY_ACL_USER_OBJ, ACL_USER_OBJ },
{ ARCHIVE_ENTRY_ACL_GROUP_OBJ, ACL_GROUP_OBJ },
{ ARCHIVE_ENTRY_ACL_MASK, ACL_MASK },
{ ARCHIVE_ENTRY_ACL_OTHER, ACL_OTHER },
};
acl_tag_t ntag = ACL_UNDEFINED_TAG;
FOREACH_ELEMENT(t, tag_map)
if (t->libarchive == tag) {
ntag = t->libacl;
break;
}
if (ntag == ACL_UNDEFINED_TAG)
continue;
if (sym_acl_set_tag_type(e, ntag) < 0)
return log_error_errno(errno, "Failed to set ACL entry tag: %m");
if (IN_SET(ntag, ACL_USER, ACL_GROUP)) {
id_t id = qual;
/* Suppress ACL entries for invalid UIDs/GIDS */
if (!uid_is_valid(id))
continue;
/* Suppress ACL entries for UIDs/GIDs to squash */
if (FLAGS_SET(flags, TAR_SQUASH_UIDS_ABOVE_64K) && id >= NSRESOURCE_UIDS_64K)
continue;
if (sym_acl_set_qualifier(e, &id) < 0)
return log_error_errno(errno, "Failed to set ACL entry qualifier: %m");
}
acl_permset_t p;
if (sym_acl_get_permset(e, &p) < 0)
return log_error_errno(errno, "Failed to get ACL entry permission set: %m");
r = acl_set_perm(p, ACL_READ, permset & ARCHIVE_ENTRY_ACL_READ);
if (r < 0)
return log_error_errno(r, "Failed to set ACL entry read bit: %m");
r = acl_set_perm(p, ACL_WRITE, permset & ARCHIVE_ENTRY_ACL_WRITE);
if (r < 0)
return log_error_errno(r, "Failed to set ACL entry write bit: %m");
r = acl_set_perm(p, ACL_EXECUTE, permset & ARCHIVE_ENTRY_ACL_EXECUTE);
if (r < 0)
return log_error_errno(r, "Failed to set ACL entry excute bit: %m");
if (sym_acl_set_permset(e, p) < 0)
return log_error_errno(errno, "Failed to set ACL entry permission set: %m");
}
if (*acl)
sym_acl_free(*acl);
*acl = TAKE_PTR(a);
return 0;
}
static uid_t maybe_squash_uid(uid_t uid, TarFlags flags) {
if (FLAGS_SET(flags, TAR_SQUASH_UIDS_ABOVE_64K) &&
uid_is_valid(uid) &&
uid >= NSRESOURCE_UIDS_64K)
return UID_NOBODY;
return uid;
}
static uid_t maybe_squash_gid(uid_t gid, TarFlags flags) {
if (FLAGS_SET(flags, TAR_SQUASH_UIDS_ABOVE_64K) &&
gid_is_valid(gid) &&
gid >= NSRESOURCE_UIDS_64K)
return GID_NOBODY;
return gid;
}
static int archive_entry_read_stat(
struct archive_entry *entry,
mode_t *filetype,
@@ -328,10 +566,15 @@ static int archive_entry_read_stat(
struct timespec *mtime,
uid_t *uid,
gid_t *gid,
unsigned *fflags,
acl_t *acl_access,
acl_t *acl_default,
XAttr **xa,
size_t *n_xa,
TarFlags flags) {
int r;
assert(entry);
/* Fills in all fields that are present in the archive entry. Doesn't change the fields if the entry
@@ -349,24 +592,40 @@ static int archive_entry_read_stat(
sym_archive_entry_mtime_nsec(entry),
};
if (uid && sym_archive_entry_uid_is_set(entry))
*uid = sym_archive_entry_uid(entry);
*uid = maybe_squash_uid(sym_archive_entry_uid(entry), flags);
if (gid && sym_archive_entry_gid_is_set(entry))
*gid = sym_archive_entry_gid(entry);
*gid = maybe_squash_gid(sym_archive_entry_gid(entry), flags);
if (fflags) {
unsigned long fs = 0, fc = 0;
sym_archive_entry_fflags(entry, &fs, &fc);
*fflags = (fs & ~fc) & CHATTR_TAR_FL;
}
(void) sym_archive_entry_xattr_reset(entry);
for (;;) {
const char *name = NULL;
struct iovec data;
(void) sym_archive_entry_xattr_next(entry, &name, (const void**) &data.iov_base, &data.iov_len);
if (!name)
r = sym_archive_entry_xattr_next(entry, &name, (const void**) &data.iov_base, &data.iov_len);
if (r != ARCHIVE_OK)
break;
assert(name);
if (xattr_is_acl(name))
continue;
if (!FLAGS_SET(flags, TAR_SELINUX) && xattr_is_selinux(name))
continue;
bool duplicate = false;
FOREACH_ARRAY(i, *xa, *n_xa)
if (streq(i->name, name)) {
duplicate = true;
break;
}
if (duplicate)
continue;
_cleanup_free_ char *n = strdup(name);
if (!n)
return log_oom();
@@ -384,6 +643,18 @@ static int archive_entry_read_stat(
};
}
if (acl_access) {
r = archive_entry_read_acl(entry, ACL_TYPE_ACCESS, acl_access, flags);
if (r < 0)
return r;
}
if (acl_default) {
r = archive_entry_read_acl(entry, ACL_TYPE_DEFAULT, acl_default, flags);
if (r < 0)
return r;
}
return 0;
}
@@ -458,6 +729,9 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) {
&open_inodes[0].mtime,
&open_inodes[0].uid,
&open_inodes[0].gid,
&open_inodes[0].fflags,
&open_inodes[0].acl_access,
&open_inodes[0].acl_default,
&open_inodes[0].xattr,
&open_inodes[0].n_xattr,
flags);
@@ -528,6 +802,8 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) {
uid_t uid = UID_INVALID;
gid_t gid = GID_INVALID;
struct timespec mtime = { .tv_nsec = UTIME_OMIT };
unsigned fflags = 0;
_cleanup_(acl_freep) acl_t acl_access = NULL, acl_default = NULL;
XAttr *xa = NULL;
size_t n_xa = 0;
CLEANUP_ARRAY(xa, n_xa, xattr_done_many);
@@ -602,6 +878,9 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) {
&mtime,
&uid,
&gid,
&fflags,
&acl_access,
&acl_default,
&xa,
&n_xa,
flags);
@@ -611,11 +890,11 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) {
switch (filetype) {
case S_IFREG:
fd = archive_unpack_regular(a, entry, parent_fd, e, j);
fd = archive_unpack_regular(a, entry, parent_fd, e, j, fflags);
break;
case S_IFDIR:
fd = archive_unpack_directory(a, entry, parent_fd, e, j);
fd = archive_unpack_directory(a, entry, parent_fd, e, j, fflags);
break;
case S_IFLNK:
@@ -660,6 +939,9 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) {
.mtime = mtime,
.uid = uid,
.gid = gid,
.fflags = fflags,
.acl_access = TAKE_PTR(acl_access),
.acl_default = TAKE_PTR(acl_default),
.xattr = TAKE_PTR(xa),
.n_xattr = n_xa,
};
@@ -675,6 +957,258 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) {
return 0;
}
static int make_tmpfs(void) {
/* Creates a tmpfs superblock to store our hardlink db in. We can do this if we run in our own
* userns, or if we are privileged. This is preferable, since it means the db is cleaned up
* automatically once we are done. Moreover, since this is a new superblock owned by us, we do not
* need to set up any uid mapping shenanigans */
_cleanup_close_ int superblock_fd = fsopen("tmpfs", FSOPEN_CLOEXEC);
if (superblock_fd < 0)
return log_debug_errno(errno, "Failed to allocate tmpfs superblock: %m");
(void) fsconfig(superblock_fd, FSCONFIG_SET_STRING, "source", "hardlink", /* aux= */ 0);
(void) fsconfig(superblock_fd, FSCONFIG_SET_STRING, "mode", "0700", /* aux= */ 0);
if (fsconfig(superblock_fd, FSCONFIG_CMD_CREATE, /* key= */ NULL, /* value= */ NULL, /* aux= */ 0) < 0)
return log_debug_errno(errno, "Failed to finalize superblock: %m");
_cleanup_close_ int mount_fd = fsmount(superblock_fd, FSMOUNT_CLOEXEC, MS_NODEV|MS_NOEXEC|MS_NOSUID);
if (mount_fd < 0)
return log_debug_errno(errno, "Failed to turn tmpfs superblock into mount: %m");
return TAKE_FD(mount_fd);
}
struct make_archive_data {
struct archive *archive;
TarFlags flags;
int hardlink_db_fd;
char *hardlink_db_path;
};
static int hardlink_lookup(
struct make_archive_data *d,
int inode_fd,
const struct statx *sx,
const char *path,
char **ret) {
_cleanup_free_ struct file_handle *handle = NULL;
_cleanup_free_ char *m = NULL, *n = NULL;
int r;
assert(d);
assert(inode_fd >= 0);
assert(sx);
/* If we know the hardlink count, and it's 1, then don't bother */
if (FLAGS_SET(sx->stx_mask, STATX_NLINK) && sx->stx_nlink == 1)
goto bypass;
/* If this is a directory, then don't bother */
if (FLAGS_SET(sx->stx_mask, STATX_TYPE) && !inode_type_can_hardlink(sx->stx_mode))
goto bypass;
int mnt_id;
r = name_to_handle_at_try_fid(inode_fd, /* path= */ NULL, &handle, &mnt_id, /* flags= */ AT_EMPTY_PATH);
if (r < 0)
return log_error_errno(r, "Failed to get file handle of file: %m");
m = hexmem(SHA256_DIRECT(handle->f_handle, handle->handle_bytes), SHA256_DIGEST_SIZE);
if (!m)
return log_oom();
if (asprintf(&n, "%i:%i:%s", mnt_id, handle->handle_type, m) < 0)
return log_oom();
if (d->hardlink_db_fd < 0) {
assert(!d->hardlink_db_path);
/* We first try to create our own superblock, which works if we are in a userns, and which
* doesn't require explicit clean-up */
d->hardlink_db_fd = make_tmpfs();
if (d->hardlink_db_fd < 0) {
log_debug_errno(d->hardlink_db_fd, "Failed to allocate tmpfs superblock for hardlink db, falling back to temporary directory: %m");
const char *vt;
r = var_tmp_dir(&vt);
if (r < 0)
return log_error_errno(r, "Failed to determine /var/tmp/ directory: %m");
_cleanup_free_ char *j = path_join(vt, "make-tar-XXXXXX");
if (!j)
return log_oom();
d->hardlink_db_fd = mkdtemp_open(j, /* flags= */ 0, &d->hardlink_db_path);
if (d->hardlink_db_fd < 0)
return log_error_errno(d->hardlink_db_fd, "Failed to make hardlink database directory: %m");
}
} else {
_cleanup_free_ char *p = NULL;
r = readlinkat_malloc(d->hardlink_db_fd, n, &p);
if (r >= 0) {
/* Found previous hit! */
log_debug("hardlinkdb: found %s → %s", n, p);
*ret = TAKE_PTR(p);
return 1;
}
if (r != -ENOENT)
return log_error_errno(r, "Failed to read symlink '%s': %m", n);
}
/* Store information about this inode */
if (symlinkat(path, d->hardlink_db_fd, n) < 0)
return log_error_errno(errno, "Failed to create symlink '%s' → '%s': %m", n, path);
log_debug("hardlinkdb: created %s → %s", n, path);
bypass:
*ret = NULL;
return 0;
}
static int archive_generate_sparse(struct archive_entry *entry, int fd) {
assert(entry);
assert(fd);
off_t c = 0;
for (;;) {
/* Look for the next hole */
off_t h = lseek(fd, c, SEEK_HOLE);
if (h < 0) {
if (errno != ENXIO)
return log_error_errno(errno, "Failed to issue SEEK_HOLE: %m");
/* If errno == ENXIO, that means we've reached the final data of the file and
* that data isn't followed by anything more */
/* Figure out where the end of the file is */
off_t e = lseek(fd, 0, SEEK_END);
if (e < 0)
return log_error_errno(errno, "Failed to issue SEEK_END: %m");
/* Generate sparse entry for final block */
if (e > c && c != 0) {
log_debug("final sparse block %" PRIu64 "…%" PRIu64, (uint64_t) c, (uint64_t) e);
sym_archive_entry_sparse_add_entry(entry, c, e - c);
}
break;
}
if (h > c) {
log_debug("inner sparse block %" PRIu64 "…%" PRIu64 " (%" PRIu64 ")", (uint64_t) c, (uint64_t) h, (uint64_t) h - (uint64_t) c);
sym_archive_entry_sparse_add_entry(entry, c, h - c);
}
/* Now look for the next data after the hole */
c = lseek(fd, h, SEEK_DATA);
if (c < 0) {
if (errno != ENXIO)
return log_error_errno(errno, "Failed to issue SEEK_DATA: %m");
/* No data anymore */
break;
}
}
if (lseek(fd, 0, SEEK_SET) < 0)
return log_error_errno(errno, "Failed to reset seek offset: %m");
return 0;
}
static int archive_write_acl(
struct archive_entry *entry,
acl_type_t ntype,
acl_t acl,
TarFlags flags) {
int r;
assert(entry);
assert(acl);
int type;
if (ntype == ACL_TYPE_ACCESS)
type = ARCHIVE_ENTRY_ACL_TYPE_ACCESS;
else if (ntype == ACL_TYPE_DEFAULT)
type = ARCHIVE_ENTRY_ACL_TYPE_DEFAULT;
else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unexpected ACL type");
acl_entry_t e;
r = sym_acl_get_entry(acl, ACL_FIRST_ENTRY, &e);
for (;;) {
if (r < 0)
return log_error_errno(errno, "Failed to get ACL entry: %m");
if (r == 0)
break;
acl_tag_t ntag;
if (sym_acl_get_tag_type(e, &ntag) < 0)
return log_error_errno(errno, "Failed to get ACL entry tag: %m");
static const int tag_map[] = {
[ACL_USER] = ARCHIVE_ENTRY_ACL_USER,
[ACL_GROUP] = ARCHIVE_ENTRY_ACL_GROUP,
[ACL_USER_OBJ] = ARCHIVE_ENTRY_ACL_USER_OBJ,
[ACL_GROUP_OBJ] = ARCHIVE_ENTRY_ACL_GROUP_OBJ,
[ACL_MASK] = ARCHIVE_ENTRY_ACL_MASK,
[ACL_OTHER] = ARCHIVE_ENTRY_ACL_OTHER,
};
assert_cc(ACL_UNDEFINED_TAG == 0); /* safety check, we assume that holes are filled with ACL_UNDEFINED_TAG */
assert_cc(ELEMENTSOF(tag_map) <= 64); /* safety check, we assume that the tag ids are all packed and low */
int tag = ntag >= 0 && ntag <= (acl_tag_t) ELEMENTSOF(tag_map) ? tag_map[ntag] : ACL_UNDEFINED_TAG;
bool skip = false;
id_t qualifier = UID_INVALID;
if (IN_SET(ntag, ACL_USER, ACL_GROUP)) {
id_t *q = sym_acl_get_qualifier(e);
if (!q)
return log_error_errno(errno, "Failed to get ACL entry qualifier: %m");
qualifier = *q;
sym_acl_free(q);
/* Suppress invalid UIDs or those that shall be squashed */
skip = !(uid_is_valid(qualifier) &&
(!FLAGS_SET(flags, TAR_SQUASH_UIDS_ABOVE_64K) || qualifier < NSRESOURCE_UIDS_64K));
}
if (!skip) {
acl_permset_t p;
if (sym_acl_get_permset(e, &p) < 0)
return log_error_errno(errno, "Failed to get ACL entry permission set: %m");
int permset = 0;
r = sym_acl_get_perm(p, ACL_READ);
if (r < 0)
return log_error_errno(r, "Failed to get ACL entry read bit: %m");
SET_FLAG(permset, ARCHIVE_ENTRY_ACL_READ, r);
r = sym_acl_get_perm(p, ACL_WRITE);
if (r < 0)
return log_error_errno(r, "Failed to get ACL entry write bit: %m");
SET_FLAG(permset, ARCHIVE_ENTRY_ACL_WRITE, r);
r = sym_acl_get_perm(p, ACL_EXECUTE);
if (r < 0)
return log_error_errno(r, "Failed to get ACL entry execute bit: %m");
SET_FLAG(permset, ARCHIVE_ENTRY_ACL_EXECUTE, r);
r = sym_archive_entry_acl_add_entry(entry, type, permset, tag, qualifier, /* name= */ NULL);
if (r != ARCHIVE_OK)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to add ACL entry.");
}
r = sym_acl_get_entry(acl, ACL_NEXT_ENTRY, &e);
}
return 0;
}
static int archive_item(
RecurseDirEvent event,
const char *path,
@@ -684,7 +1218,7 @@ static int archive_item(
const struct statx *sx,
void *userdata) {
struct archive *a = ASSERT_PTR(userdata);
struct make_archive_data *d = ASSERT_PTR(userdata);
int r;
assert(path);
@@ -702,17 +1236,31 @@ static int archive_item(
if (!entry)
return log_oom();
assert(FLAGS_SET(sx->stx_mask, STATX_TYPE|STATX_MODE));
sym_archive_entry_set_pathname(entry, path);
_cleanup_free_ char *hardlink = NULL;
r = hardlink_lookup(d, inode_fd, sx, path, &hardlink);
if (r < 0)
return r;
if (r > 0) {
sym_archive_entry_set_hardlink(entry, hardlink);
if (sym_archive_write_header(d->archive, entry) != ARCHIVE_OK)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to write archive entry header: %s", sym_archive_error_string(d->archive));
return RECURSE_DIR_CONTINUE;
}
assert(FLAGS_SET(sx->stx_mask, STATX_TYPE|STATX_MODE));
sym_archive_entry_set_filetype(entry, sx->stx_mode);
if (!S_ISLNK(sx->stx_mode))
sym_archive_entry_set_perm(entry, sx->stx_mode);
if (FLAGS_SET(sx->stx_mask, STATX_UID))
sym_archive_entry_set_uid(entry, sx->stx_uid);
sym_archive_entry_set_uid(entry, maybe_squash_uid(sx->stx_uid, d->flags));
if (FLAGS_SET(sx->stx_mask, STATX_GID))
sym_archive_entry_set_gid(entry, sx->stx_gid);
sym_archive_entry_set_gid(entry, maybe_squash_gid(sx->stx_gid, d->flags));
if (S_ISREG(sx->stx_mode)) {
if (!FLAGS_SET(sx->stx_mask, STATX_SIZE))
@@ -745,17 +1293,90 @@ static int archive_item(
sym_archive_entry_set_symlink(entry, s);
}
if (sym_archive_write_header(a, entry) != ARCHIVE_OK)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to write archive entry header: %s", sym_archive_error_string(a));
if (inode_type_can_acl(sx->stx_mode)) {
r = dlopen_libacl();
if (r < 0)
log_debug_errno(r, "No trying to read ACL off inode, as libacl support is not available: %m");
else {
r = sym_acl_extended_file(FORMAT_PROC_FD_PATH(inode_fd));
if (r < 0 && !ERRNO_IS_NOT_SUPPORTED(errno))
return log_error_errno(errno, "Failed check if '%s' has ACLs: %m", path);
if (r > 0) {
_cleanup_(acl_freep) acl_t acl = NULL;
acl = sym_acl_get_file(FORMAT_PROC_FD_PATH(inode_fd), ACL_TYPE_ACCESS);
if (!acl)
return log_error_errno(errno, "Failed read access ACLs of '%s': %m", path);
archive_write_acl(entry, ACL_TYPE_ACCESS, acl, d->flags);
if (S_ISDIR(sx->stx_mode)) {
sym_acl_free(acl);
acl = sym_acl_get_file(FORMAT_PROC_FD_PATH(inode_fd), ACL_TYPE_DEFAULT);
if (!acl)
return log_error_errno(errno, "Failed to read default ACLs of '%s': %m", path);
archive_write_acl(entry, ACL_TYPE_DEFAULT, acl, d->flags);
}
}
}
}
_cleanup_free_ char *xattrs = NULL;
r = flistxattr_malloc(inode_fd, &xattrs);
if (r < 0 && !ERRNO_IS_NEG_NOT_SUPPORTED(r) && r != -ENODATA)
return log_error_errno(r, "Failed to read xattr list of '%s': %m", path);
NULSTR_FOREACH(xa, xattrs) {
_cleanup_free_ char *buf = NULL;
size_t size;
if (xattr_is_acl(xa))
continue;
if (!FLAGS_SET(d->flags, TAR_SELINUX) && xattr_is_selinux(xa))
continue;
r = fgetxattr_malloc(inode_fd, xa, &buf, &size);
if (r == -ENODATA) /* deleted by now? ignore... */
continue;
if (r < 0)
return log_error_errno(r, "Failed to read xattr '%s' of '%s': %m", xa, path);
sym_archive_entry_xattr_add_entry(entry, xa, buf, size);
}
_cleanup_close_ int data_fd = -EBADF;
if (S_ISREG(sx->stx_mode)) {
_cleanup_close_ int data_fd = -EBADF;
/* Convert the O_PATH fd in a proper fd */
/* Convert the O_PATH fd into a proper fd */
data_fd = fd_reopen(inode_fd, O_RDONLY|O_CLOEXEC);
if (data_fd < 0)
return log_error_errno(data_fd, "Failed to open '%s': %m", path);
r = archive_generate_sparse(entry, data_fd);
if (r < 0)
return r;
}
if (inode_type_can_chattr(sx->stx_mode)) {
unsigned f = 0;
r = read_attr_fd(data_fd >= 0 ? data_fd : inode_fd, &f);
if (r < 0 && !ERRNO_IS_NEG_NOT_SUPPORTED(r))
return log_error_errno(r, "Failed to read file flags of '%s': %m", path);
f &= CHATTR_TAR_FL;
if (f != 0)
sym_archive_entry_set_fflags(entry, f, /* clear= */ 0);
}
if (sym_archive_write_header(d->archive, entry) != ARCHIVE_OK)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to write archive entry header: %s", sym_archive_error_string(d->archive));
if (S_ISREG(sx->stx_mode)) {
assert(data_fd >= 0);
for (;;) {
char buffer[64*1024];
ssize_t l;
@@ -767,15 +1388,24 @@ static int archive_item(
break;
la_ssize_t k;
k = sym_archive_write_data(a, buffer, l);
k = sym_archive_write_data(d->archive, buffer, l);
if (k < 0)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to write archive data: %s", sym_archive_error_string(a));
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to write archive data: %s", sym_archive_error_string(d->archive));
}
}
return RECURSE_DIR_CONTINUE;
}
static void make_archive_data_done(struct make_archive_data *d) {
assert(d);
if (d->hardlink_db_fd >= 0)
(void) rm_rf_children(d->hardlink_db_fd, REMOVE_PHYSICAL, /* root_dev= */ NULL);
unlink_and_free(d->hardlink_db_path);
}
int tar_c(int tree_fd, int output_fd, const char *filename, TarFlags flags) {
int r;
@@ -789,7 +1419,7 @@ int tar_c(int tree_fd, int output_fd, const char *filename, TarFlags flags) {
if (filename)
r = sym_archive_write_set_format_filter_by_ext(a, filename);
else
r = sym_archive_write_set_format_gnutar(a);
r = sym_archive_write_set_format_pax(a);
if (r != ARCHIVE_OK)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to set libarchive output format: %s", sym_archive_error_string(a));
@@ -797,13 +1427,19 @@ int tar_c(int tree_fd, int output_fd, const char *filename, TarFlags flags) {
if (r != ARCHIVE_OK)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to set libarchive output file: %s", sym_archive_error_string(a));
_cleanup_(make_archive_data_done) struct make_archive_data data = {
.archive = a,
.flags = flags,
.hardlink_db_fd = -EBADF,
};
r = recurse_dir(tree_fd,
".",
STATX_TYPE|STATX_MODE|STATX_UID|STATX_GID|STATX_SIZE|STATX_ATIME|STATX_CTIME,
UINT_MAX,
RECURSE_DIR_SORT|RECURSE_DIR_INODE_FD|RECURSE_DIR_TOPLEVEL,
archive_item,
a);
&data);
if (r < 0)
return log_error_errno(r, "Failed to make archive: %m");

View File

@@ -2,7 +2,8 @@
#pragma once
typedef enum TarFlags {
TAR_SELINUX = 1 << 0,
TAR_SELINUX = 1 << 0, /* Include SELinux xattr in tarball, or unpack it */
TAR_SQUASH_UIDS_ABOVE_64K = 1 << 1, /* Squash UIDs/GIDs above 64K when packing/unpacking to the nobody user */
} TarFlags;
int tar_x(int input_fd, int tree_fd, TarFlags flags);

View File

@@ -14,8 +14,21 @@ fi
at_exit() {
rm -rf /home/testuser/.local/state/machines/zurps ||:
rm -rf /home/testuser/.local/state/machines/nurps ||:
rm -rf /home/testuser/.local/state/machines/kurps ||:
rm -rf /home/testuser/.local/state/machines/wumms ||:
rm -rf /home/testuser/.local/state/machines/wamms ||:
rm -rf /home/testuser/.local/state/machines/inodetest ||:
rm -rf /home/testuser/.local/state/machines/inodetest2 ||:
machinectl terminate zurps ||:
rm -f /etc/polkit-1/rules.d/registermachinetest.rules
machinectl terminate nurps ||:
machinectl terminate kurps ||:
machinectl terminate wumms ||:
machinectl terminate wamms ||:
rm -f /usr/share/polkit-1/rules.d/registermachinetest.rules
rm -rf /var/tmp/mangletest
rm -f /var/tmp/mangletest.tar.gz
}
trap at_exit EXIT
@@ -45,6 +58,8 @@ EOF
loginctl enable-linger testuser
run0 -u testuser mkdir -p .config/systemd/nspawn/
run0 -u testuser -i "echo -e \"[Exec]\nKillSignal=SIGKILL\n\" > .config/systemd/nspawn/zurps.nspawn"
run0 -u testuser systemctl start --user systemd-nspawn@zurps.service
machinectl status zurps
@@ -87,4 +102,114 @@ run0 -u testuser \
(! run0 -u testuser machinectl shell 0@shouldnotwork2 /usr/bin/id -u)
(! run0 -u testuser machinectl shell testuser@shouldnotwork2 /usr/bin/id -u)
run0 -u testuser mkdir /var/tmp/image-tar
run0 -u testuser importctl --user export-tar zurps /var/tmp/image-tar/kurps.tar.gz -m
run0 -u testuser importctl --user import-tar /var/tmp/image-tar/kurps.tar.gz -m
run0 -u testuser -i "echo -e \"[Exec]\nKillSignal=SIGKILL\n\" > .config/systemd/nspawn/kurps.nspawn"
run0 -u testuser systemctl start --user systemd-nspawn@kurps.service
machinectl terminate kurps
run0 -u testuser -D /var/tmp/image-tar/ bash -c 'sha256sum kurps.tar.gz > SHA256SUMS'
run0 -u testuser importctl --user pull-tar file:///var/tmp/image-tar/kurps.tar.gz nurps --verify=checksum -m
run0 -u testuser -i "echo -e \"[Exec]\nKillSignal=SIGKILL\n\" > .config/systemd/nspawn/nurps.nspawn"
run0 -u testuser systemctl start --user systemd-nspawn@nurps.service
machinectl terminate nurps
run0 -u testuser rm -r /var/tmp/image-tar
run0 -u testuser importctl --user list-images
run0 -u testuser machinectl --user list-images
assert_in 'zurps' "$(run0 --pipe -u testuser machinectl --user list-images)"
assert_in 'nurps' "$(run0 --pipe -u testuser machinectl --user list-images)"
assert_in 'kurps' "$(run0 --pipe -u testuser machinectl --user list-images)"
run0 -u testuser machinectl --user image-status zurps
run0 -u testuser machinectl --user image-status nurps
run0 -u testuser machinectl --user image-status kurps
run0 -u testuser machinectl --user show-image zurps
run0 -u testuser machinectl --user show-image nurps
run0 -u testuser machinectl --user show-image kurps
run0 -u testuser machinectl --user clone zurps wumms
assert_in 'wumms' "$(run0 -u testuser machinectl --user list-images)"
run0 -u testuser machinectl --user image-status wumms
run0 -u testuser machinectl --user show-image wumms
run0 -u testuser machinectl --user rename wumms wamms
assert_not_in 'wumms' "$(run0 -u testuser machinectl --user list-images)"
assert_in 'wamms' "$(run0 -u testuser machinectl --user list-images)"
run0 -u testuser machinectl --user image-status wamms
run0 -u testuser machinectl --user show-image wamms
run0 -u testuser -i "echo -e \"[Exec]\nKillSignal=SIGKILL\n\" > .config/systemd/nspawn/wamms.nspawn"
run0 -u testuser systemctl start --user systemd-nspawn@wamms.service
run0 -u testuser systemctl stop --user systemd-nspawn@zurps.service
run0 -u testuser systemctl stop --user systemd-nspawn@nurps.service
run0 -u testuser systemctl stop --user systemd-nspawn@kurps.service
run0 -u testuser systemctl stop --user systemd-nspawn@wamms.service
run0 -u testuser machinectl --user remove zurps
run0 -u testuser machinectl --user remove kurps
run0 -u testuser machinectl --user remove nurps
run0 -u testuser machinectl --user remove wamms
assert_not_in 'zurps' "$(run0 --pipe -u testuser machinectl --user list-images)"
assert_not_in 'nurps' "$(run0 --pipe -u testuser machinectl --user list-images)"
assert_not_in 'kurps' "$(run0 --pipe -u testuser machinectl --user list-images)"
mkdir /home/testuser/.local/state/machines/inodetest
echo hallo > /home/testuser/.local/state/machines/inodetest/testfile
# Make the file sparse, set an xattr, set an ACL, set a chattr flag, and make it hardlink
ln /home/testuser/.local/state/machines/inodetest/testfile /home/testuser/.local/state/machines/inodetest/testfile.hard
truncate -s 1M /home/testuser/.local/state/machines/inodetest/testfile
setfattr -n "user.piff" -v "paff" /home/testuser/.local/state/machines/inodetest/testfile
setfacl -m g:foreign-47:rwx /home/testuser/.local/state/machines/inodetest/testfile
chattr +A /home/testuser/.local/state/machines/inodetest/testfile
chown foreign-0:foreign-0 /home/testuser/.local/state/machines/inodetest/testfile.hard /home/testuser/.local/state/machines/inodetest
ls -al /home/testuser/.local/state/machines/inodetest
echo gaga > /home/testuser/.local/state/machines/inodetest/squashtest
chown 1000:1000 /home/testuser/.local/state/machines/inodetest/squashtest
run0 --pipe -u testuser importctl -m --user export-tar inodetest |
run0 --pipe -u testuser importctl -m --user import-tar - inodetest2
ls -al /home/testuser/.local/state/machines/inodetest2
cmp /home/testuser/.local/state/machines/inodetest/testfile /home/testuser/.local/state/machines/inodetest2/testfile.hard
cmp <(stat -c"%s %b %B" /home/testuser/.local/state/machines/inodetest/testfile) <(stat -c"%s %b %B" /home/testuser/.local/state/machines/inodetest2/testfile)
cmp <(stat -c"%i" /home/testuser/.local/state/machines/inodetest2/testfile) <(stat -c"%i" /home/testuser/.local/state/machines/inodetest2/testfile.hard)
getfattr -d /home/testuser/.local/state/machines/inodetest/testfile
getfattr -d /home/testuser/.local/state/machines/inodetest2/testfile
getfacl /home/testuser/.local/state/machines/inodetest/testfile
getfacl /home/testuser/.local/state/machines/inodetest2/testfile
lsattr /home/testuser/.local/state/machines/inodetest/testfile
lsattr /home/testuser/.local/state/machines/inodetest2/testfile
cmp <(getfattr -d /home/testuser/.local/state/machines/inodetest/testfile | grep -v ^#) <(getfattr -d /home/testuser/.local/state/machines/inodetest2/testfile | grep -v ^#)
cmp <(getfacl /home/testuser/.local/state/machines/inodetest/testfile | grep -v ^#) <(getfacl /home/testuser/.local/state/machines/inodetest2/testfile | grep -v ^#)
cmp <(lsattr /home/testuser/.local/state/machines/inodetest/testfile | cut -d " " -f1) <(lsattr /home/testuser/.local/state/machines/inodetest2/testfile | cut -d " " -f1)
# verify that squashing outside of 64K works
test "$(stat -c'%U:%G' /home/testuser/.local/state/machines/inodetest2/squashtest)" = "foreign-65534:foreign-65534"
# chown to foreing UID range, so that removal works
chown foreign-4711:foreign-4711 /home/testuser/.local/state/machines/inodetest/squashtest
run0 -u testuser machinectl --user remove inodetest
run0 -u testuser machinectl --user remove inodetest2
# Test tree mangling (i.e. moving the root dir one level up on extract)
mkdir -p /var/tmp/mangletest/mangletest-0.1/usr/lib
echo "ID=brumm" > /var/tmp/mangletest/mangletest-0.1/usr/lib/os-release
tar -C /var/tmp/mangletest/ -cvzf /var/tmp/mangletest.tar.gz mangletest-0.1
run0 --pipe -u testuser importctl -m --user import-tar /var/tmp/mangletest.tar.gz
cmp /var/tmp/mangletest/mangletest-0.1/usr/lib/os-release /home/testuser/.local/state/machines/mangletest/usr/lib/os-release
loginctl disable-linger testuser