diff --git a/TODO b/TODO index deed465ca6..d6ae200a34 100644 --- a/TODO +++ b/TODO @@ -120,9 +120,9 @@ Features: this, it's useful to have one that can dump contents of them, too. * All tools that support --root= should also learn --image= so that they can - operate on disk images directly. Specifically: bootctl, tmpfiles, sysusers, - systemctl, repart, journalctl, coredumpctl. (Already done: systemd-nspawn, - systemd-firstboot) + operate on disk images directly. Specifically: bootctl, systemctl, + coredumpctl. (Already done: systemd-nspawn, systemd-firstboot, + systemd-repart, systemd-tmpfiles, systemd-sysusers, journalctl) * seccomp: by default mask x32 ABI system wide on x86-64. it's on its way out @@ -337,9 +337,6 @@ Features: right) become genuine first class citizens, and we gain automatic, sane JSON output for them. -* systemd-firstboot: teach it dissector magic, so that you can point it to some - disk image and it will just set everything in it all behind the scenes. - * We should probably replace /var/log/README, /etc/rc.d/README with symlinks that are linked to these places instead of copied. After all they are constant vendor data. diff --git a/man/journalctl.xml b/man/journalctl.xml index 07310d90a1..a03493fc35 100644 --- a/man/journalctl.xml +++ b/man/journalctl.xml @@ -756,16 +756,29 @@ - Takes a directory path as an argument. If - specified, journalctl will operate on journal directories and catalog file hierarchy - underneath the specified directory instead of the root - directory (e.g. will create - ROOT/var/lib/systemd/catalog/database, - and journal files under ROOT/run/journal/ - or ROOT/var/log/journal/ will be displayed). + Takes a directory path as an argument. If specified, journalctl + will operate on journal directories and catalog file hierarchy underneath the specified directory + instead of the root directory (e.g. will create + ROOT/var/lib/systemd/catalog/database, and journal + files under ROOT/run/journal/ or + ROOT/var/log/journal/ will be displayed). + + + + Takes a path to a disk image file or block device node. If specified, + journalctl will operate on the file system in the indicated disk image. This is + similar to but operates on file systems stored in disk images or block + devices, thus providing an easy way to extract log data from disk images. The disk image should + either contain just a file system or a set of file systems within a GPT partition table, following + the Discoverable Partitions + Specification. For further information on supported disk images, see + systemd-nspawn1's + switch of the same name. + + diff --git a/man/systemd-sysusers.xml b/man/systemd-sysusers.xml index cc02625699..1e5853a55b 100644 --- a/man/systemd-sysusers.xml +++ b/man/systemd-sysusers.xml @@ -68,6 +68,19 @@ paths. + + + + Takes a path to a disk image file or block device node. If specified all operations + are applied to file system in the indicated disk image. This is similar to + but operates on file systems stored in disk images or block devices. The disk image should either + contain just a file system or a set of file systems within a GPT partition table, following the + Discoverable Partitions + Specification. For further information on supported disk images, see + systemd-nspawn1's + switch of the same name. + + When this option is given, one ore more positional arguments diff --git a/man/systemd-tmpfiles.xml b/man/systemd-tmpfiles.xml index 998fd0911b..4510fb120a 100644 --- a/man/systemd-tmpfiles.xml +++ b/man/systemd-tmpfiles.xml @@ -149,6 +149,7 @@ the specified prefix. This option can be specified multiple times. + Ignore rules with paths that start with the @@ -156,6 +157,16 @@ times. + + + A shortcut for --exclude-prefix=/dev --exclude-prefix=/proc + --exclude-prefix=/run --exclude-prefix=/sys, i.e. exclude the hierarchies typically backed + by virtual or memory file systems. This is useful in combination with , if + the specified directory tree contains an OS tree without these virtual/memory file systems mounted + in, as it is typically not desirable to create any files and directories below these subdirectories + if they are supposed to be overmounted during runtime. + + Takes a directory path as an argument. All paths will be prefixed with the given alternate @@ -164,7 +175,26 @@ When this option is used, the libc Name Service Switch (NSS) is bypassed for resolving users and groups. Instead the files /etc/passwd and /etc/group inside the alternate root are read directly. This means that users/groups not listed in these files - will not be resolved, i.e. LDAP NIS and other complex databases are not considered. + will not be resolved, i.e. LDAP NIS and other complex databases are not considered. + + Consider combining this with to ensure the invocation does not create files + or directories below mount points in the OS image operated on that are typically overmounted during + runtime. + + + + + + Takes a path to a disk image file or block device node. If specified all operations + are applied to file system in the indicated disk image. This is similar to + but operates on file systems stored in disk images or block devices. The disk image should either + contain just a file system or a set of file systems within a GPT partition table, following the + Discoverable Partitions + Specification. For further information on supported disk images, see + systemd-nspawn1's + switch of the same name. + + Implies . diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 78abcbeff6..d56de0bb25 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -19,7 +19,6 @@ #include "kbd-util.h" #include "libcrypt-util.h" #include "locale-util.h" -#include "loop-util.h" #include "main-func.h" #include "memory-util.h" #include "mkdir.h" @@ -907,75 +906,6 @@ static int process_kernel_cmdline(void) { return 0; } -static int setup_image(char **ret_mount_dir, LoopDevice **ret_loop_device, DecryptedImage **ret_decrypted_image) { - DissectImageFlags f = DISSECT_IMAGE_REQUIRE_ROOT|DISSECT_IMAGE_VALIDATE_OS|DISSECT_IMAGE_RELAX_VAR_CHECK|DISSECT_IMAGE_FSCK; - _cleanup_(loop_device_unrefp) LoopDevice *d = NULL; - _cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL; - _cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL; - _cleanup_(rmdir_and_freep) char *mount_dir = NULL; - _cleanup_free_ char *temp = NULL; - int r; - - if (!arg_image) { - *ret_mount_dir = NULL; - *ret_decrypted_image = NULL; - *ret_loop_device = NULL; - return 0; - } - - assert(!arg_root); - - r = tempfn_random_child(NULL, "firstboot", &temp); - if (r < 0) - return log_error_errno(r, "Failed to generate temporary mount directory: %m"); - - r = loop_device_make_by_path(arg_image, O_RDWR, LO_FLAGS_PARTSCAN, &d); - if (r < 0) - return log_error_errno(r, "Failed to set up loopback device: %m"); - - r = dissect_image_and_warn(d->fd, arg_image, NULL, 0, NULL, NULL, f, &dissected_image); - if (r < 0) - return r; - - r = dissected_image_decrypt_interactively(dissected_image, NULL, NULL, 0, NULL, NULL, NULL, 0, f, &decrypted_image); - if (r < 0) - return r; - - r = detach_mount_namespace(); - if (r < 0) - return log_error_errno(r, "Failed to detach mount namespace: %m"); - - mount_dir = strdup(temp); - if (!mount_dir) - return log_oom(); - - r = mkdir_p(mount_dir, 0700); - if (r < 0) { - mount_dir = mfree(mount_dir); - return log_error_errno(r, "Failed to create mount point: %m"); - } - - r = dissected_image_mount(dissected_image, mount_dir, UID_INVALID, f); - if (r < 0) - return log_error_errno(r, "Failed to mount image: %m"); - - if (decrypted_image) { - r = decrypted_image_relinquish(decrypted_image); - if (r < 0) - return log_error_errno(r, "Failed to relinquish DM devices: %m"); - } - - loop_device_relinquish(d); - - arg_root = TAKE_PTR(temp); - - *ret_mount_dir = TAKE_PTR(mount_dir); - *ret_decrypted_image = TAKE_PTR(decrypted_image); - *ret_loop_device = TAKE_PTR(d); - - return 1; -} - static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -1353,9 +1283,22 @@ static int run(int argc, char *argv[]) { return 0; /* disabled */ } - r = setup_image(&unlink_dir, &loop_device, &decrypted_image); - if (r < 0) - return r; + if (arg_image) { + assert(!arg_root); + + r = mount_image_privately_interactively( + arg_image, + DISSECT_IMAGE_REQUIRE_ROOT|DISSECT_IMAGE_VALIDATE_OS|DISSECT_IMAGE_RELAX_VAR_CHECK|DISSECT_IMAGE_FSCK, + &unlink_dir, + &loop_device, + &decrypted_image); + if (r < 0) + return r; + + arg_root = strdup(unlink_dir); + if (!arg_root) + return log_oom(); + } r = process_locale(); if (r < 0) diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index 79daa43494..53d2c60f91 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -31,6 +31,7 @@ #include "chattr-util.h" #include "def.h" #include "device-private.h" +#include "dissect-image.h" #include "fd-util.h" #include "fileio.h" #include "format-util.h" @@ -51,6 +52,7 @@ #include "logs-show.h" #include "memory-util.h" #include "mkdir.h" +#include "mount-util.h" #include "mountpoint-util.h" #include "nulstr-util.h" #include "pager.h" @@ -121,6 +123,7 @@ static bool arg_reverse = false; static int arg_journal_type = 0; static int arg_namespace_flags = 0; static char *arg_root = NULL; +static char *arg_image = NULL; static const char *arg_machine = NULL; static const char *arg_namespace = NULL; static uint64_t arg_vacuum_size = 0; @@ -375,6 +378,7 @@ static int help(void) { " -D --directory=PATH Show journal files from directory\n" " --file=PATH Show journal file\n" " --root=ROOT Operate on files below a root directory\n" + " --image=IMAGE Operate on files in filesystem image\n" " --namespace=NAMESPACE Show journal data from specified namespace\n" " --interval=TIME Time interval for changing the FSS sealing key\n" " --verify-key=KEY Specify FSS verification key\n" @@ -422,6 +426,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_USER, ARG_SYSTEM, ARG_ROOT, + ARG_IMAGE, ARG_HEADER, ARG_FACILITY, ARG_SETUP_KEYS, @@ -478,6 +483,7 @@ static int parse_argv(int argc, char *argv[]) { { "directory", required_argument, NULL, 'D' }, { "file", required_argument, NULL, ARG_FILE }, { "root", required_argument, NULL, ARG_ROOT }, + { "image", required_argument, NULL, ARG_IMAGE }, { "header", no_argument, NULL, ARG_HEADER }, { "identifier", required_argument, NULL, 't' }, { "priority", required_argument, NULL, 'p' }, @@ -713,7 +719,13 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_ROOT: - r = parse_path_argument_and_warn(optarg, true, &arg_root); + r = parse_path_argument_and_warn(optarg, /* suppress_root= */ true, &arg_root); + if (r < 0) + return r; + break; + + case ARG_IMAGE: + r = parse_path_argument_and_warn(optarg, /* suppress_root= */ false, &arg_image); if (r < 0) return r; break; @@ -1043,8 +1055,8 @@ static int parse_argv(int argc, char *argv[]) { if (arg_follow && !arg_no_tail && !arg_since && arg_lines == ARG_LINES_DEFAULT) arg_lines = 10; - if (!!arg_directory + !!arg_file + !!arg_machine + !!arg_root > 1) { - log_error("Please specify at most one of -D/--directory=, --file=, -M/--machine=, --root."); + if (!!arg_directory + !!arg_file + !!arg_machine + !!arg_root + !!arg_image > 1) { + log_error("Please specify at most one of -D/--directory=, --file=, -M/--machine=, --root=, --image=."); return -EINVAL; } @@ -2084,6 +2096,9 @@ static int wait_for_change(sd_journal *j, int poll_fd) { } int main(int argc, char *argv[]) { + _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; + _cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL; + _cleanup_(umount_and_rmdir_and_freep) char *unlink_dir = NULL; bool previous_boot_id_valid = false, first_line = true, ellipsized = false, need_seek = false; bool use_cursor = false, after_cursor = false; _cleanup_(sd_journal_closep) sd_journal *j = NULL; @@ -2101,6 +2116,24 @@ int main(int argc, char *argv[]) { if (r <= 0) goto finish; + if (arg_image) { + assert(!arg_root); + + r = mount_image_privately_interactively( + arg_image, + DISSECT_IMAGE_REQUIRE_ROOT|DISSECT_IMAGE_VALIDATE_OS|DISSECT_IMAGE_RELAX_VAR_CHECK| + (arg_action == ACTION_UPDATE_CATALOG ? DISSECT_IMAGE_FSCK : DISSECT_IMAGE_READ_ONLY), + &unlink_dir, + &loop_device, + &decrypted_image); + if (r < 0) + return r; + + arg_root = strdup(unlink_dir); + if (!arg_root) + return log_oom(); + } + signal(SIGWINCH, columns_lines_cache_reset); sigbus_install(); diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index e96658ca66..c98c84993e 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -33,8 +33,10 @@ #include "hexdecoct.h" #include "hostname-util.h" #include "id128-util.h" +#include "mkdir.h" #include "mount-util.h" #include "mountpoint-util.h" +#include "namespace-util.h" #include "nulstr-util.h" #include "os-util.h" #include "path-util.h" @@ -1940,9 +1942,84 @@ const char* mount_options_from_part(const MountOptions *options, unsigned int pa LIST_FOREACH(mount_options, m, (MountOptions *)options) if (partition_number == m->partition_number && !isempty(m->options)) return m->options; + return NULL; } +int mount_image_privately_interactively( + const char *image, + DissectImageFlags flags, + char **ret_directory, + LoopDevice **ret_loop_device, + DecryptedImage **ret_decrypted_image) { + + _cleanup_(loop_device_unrefp) LoopDevice *d = NULL; + _cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL; + _cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL; + _cleanup_(rmdir_and_freep) char *created_dir = NULL; + _cleanup_free_ char *temp = NULL; + int r; + + /* Mounts an OS image at a temporary place, inside a newly created mount namespace of our own. This + * is used by tools such as systemd-tmpfiles or systemd-firstboot to operate on some disk image + * easily. */ + + assert(image); + assert(ret_directory); + assert(ret_loop_device); + assert(ret_decrypted_image); + + r = tempfn_random_child(NULL, program_invocation_short_name, &temp); + if (r < 0) + return log_error_errno(r, "Failed to generate temporary mount directory: %m"); + + r = loop_device_make_by_path( + image, + FLAGS_SET(flags, DISSECT_IMAGE_READ_ONLY) ? O_RDONLY : O_RDWR, + FLAGS_SET(flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN, + &d); + if (r < 0) + return log_error_errno(r, "Failed to set up loopback device: %m"); + + r = dissect_image_and_warn(d->fd, image, NULL, 0, NULL, NULL, flags, &dissected_image); + if (r < 0) + return r; + + r = dissected_image_decrypt_interactively(dissected_image, NULL, NULL, 0, NULL, NULL, NULL, 0, flags, &decrypted_image); + if (r < 0) + return r; + + r = detach_mount_namespace(); + if (r < 0) + return log_error_errno(r, "Failed to detach mount namespace: %m"); + + r = mkdir_p(temp, 0700); + if (r < 0) + return log_error_errno(r, "Failed to create mount point: %m"); + + created_dir = TAKE_PTR(temp); + + r = dissected_image_mount(dissected_image, created_dir, UID_INVALID, flags); + if (r == -EUCLEAN) + return log_error_errno(r, "File system check on image failed: %m"); + if (r < 0) + return log_error_errno(r, "Failed to mount image: %m"); + + if (decrypted_image) { + r = decrypted_image_relinquish(decrypted_image); + if (r < 0) + return log_error_errno(r, "Failed to relinquish DM devices: %m"); + } + + loop_device_relinquish(d); + + *ret_directory = TAKE_PTR(created_dir); + *ret_loop_device = TAKE_PTR(d); + *ret_decrypted_image = TAKE_PTR(decrypted_image); + + return 0; +} + static const char *const partition_designator_table[] = { [PARTITION_ROOT] = "root", [PARTITION_ROOT_SECONDARY] = "root-secondary", diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index 52aa377a67..7f67c8745e 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -6,6 +6,7 @@ #include "sd-id128.h" #include "list.h" +#include "loop-util.h" #include "macro.h" typedef struct DissectedImage DissectedImage; @@ -117,3 +118,5 @@ int partition_designator_from_string(const char *name) _pure_; int verity_metadata_load(const char *image, const char *root_hash_path, void **ret_roothash, size_t *ret_roothash_size, char **ret_verity_data, char **ret_roothashsig); bool dissected_image_can_do_verity(const DissectedImage *image, unsigned partition_designator); bool dissected_image_has_verity(const DissectedImage *image, unsigned partition_designator); + +int mount_image_privately_interactively(const char *path, DissectImageFlags flags, char **ret_directory, LoopDevice **ret_loop_device, DecryptedImage **ret_decrypted_image); diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c index b5e7e08eee..383a62c598 100644 --- a/src/sysusers/sysusers.c +++ b/src/sysusers/sysusers.c @@ -7,17 +7,19 @@ #include "conf-files.h" #include "copy.h" #include "def.h" +#include "dissect-image.h" #include "fd-util.h" #include "fileio.h" #include "format-util.h" #include "fs-util.h" #include "hashmap.h" #include "main-func.h" +#include "mount-util.h" #include "pager.h" #include "path-util.h" #include "pretty-print.h" -#include "set.h" #include "selinux-util.h" +#include "set.h" #include "smack-util.h" #include "specifier.h" #include "string-util.h" @@ -63,6 +65,7 @@ typedef struct Item { } Item; static char *arg_root = NULL; +static char *arg_image = NULL; static bool arg_cat_config = false; static const char *arg_replace = NULL; static bool arg_inline = false; @@ -93,6 +96,7 @@ STATIC_DESTRUCTOR_REGISTER(database_by_groupname, hashmap_freep); STATIC_DESTRUCTOR_REGISTER(database_groups, set_free_freep); STATIC_DESTRUCTOR_REGISTER(uid_range, freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); +STATIC_DESTRUCTOR_REGISTER(arg_image, freep); static int errno_is_not_exists(int code) { /* See getpwnam(3) and getgrnam(3): those codes and others can be returned if the user or group are @@ -1739,6 +1743,7 @@ static int help(void) { " --version Show package version\n" " --cat-config Show configuration files\n" " --root=PATH Operate on an alternate filesystem root\n" + " --image=PATH Operate on disk image as filesystem root\n" " --replace=PATH Treat arguments as replacement for PATH\n" " --inline Treat arguments as configuration lines\n" " --no-pager Do not pipe output into a pager\n" @@ -1756,6 +1761,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_VERSION = 0x100, ARG_CAT_CONFIG, ARG_ROOT, + ARG_IMAGE, ARG_REPLACE, ARG_INLINE, ARG_NO_PAGER, @@ -1766,6 +1772,7 @@ static int parse_argv(int argc, char *argv[]) { { "version", no_argument, NULL, ARG_VERSION }, { "cat-config", no_argument, NULL, ARG_CAT_CONFIG }, { "root", required_argument, NULL, ARG_ROOT }, + { "image", required_argument, NULL, ARG_IMAGE }, { "replace", required_argument, NULL, ARG_REPLACE }, { "inline", no_argument, NULL, ARG_INLINE }, { "no-pager", no_argument, NULL, ARG_NO_PAGER }, @@ -1797,6 +1804,12 @@ static int parse_argv(int argc, char *argv[]) { return r; break; + case ARG_IMAGE: + r = parse_path_argument_and_warn(optarg, /* suppress_root= */ false, &arg_image); + if (r < 0) + return r; + break; + case ARG_REPLACE: if (!path_is_absolute(optarg) || !endswith(optarg, ".conf")) @@ -1829,6 +1842,9 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "When --replace= is given, some configuration items must be specified"); + if (arg_image && arg_root) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported."); + return 1; } @@ -1880,6 +1896,9 @@ static int read_config_files(char **args) { } static int run(int argc, char *argv[]) { + _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; + _cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL; + _cleanup_(umount_and_rmdir_and_freep) char *unlink_dir = NULL; _cleanup_close_ int lock = -1; Iterator iterator; Item *i; @@ -1900,6 +1919,23 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; + if (arg_image) { + assert(!arg_root); + + r = mount_image_privately_interactively( + arg_image, + DISSECT_IMAGE_REQUIRE_ROOT|DISSECT_IMAGE_VALIDATE_OS|DISSECT_IMAGE_RELAX_VAR_CHECK|DISSECT_IMAGE_FSCK, + &unlink_dir, + &loop_device, + &decrypted_image); + if (r < 0) + return r; + + arg_root = strdup(unlink_dir); + if (!arg_root) + return log_oom(); + } + /* If command line arguments are specified along with --replace, read all * configuration files and insert the positional arguments at the specified * place. Otherwise, if command line arguments are specified, execute just diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 2404e36bf2..616a54b3c3 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -26,6 +26,7 @@ #include "copy.h" #include "def.h" #include "dirent-util.h" +#include "dissect-image.h" #include "escape.h" #include "fd-util.h" #include "fileio.h" @@ -38,6 +39,7 @@ #include "macro.h" #include "main-func.h" #include "mkdir.h" +#include "mount-util.h" #include "mountpoint-util.h" #include "offline-passwd.h" #include "pager.h" @@ -164,6 +166,7 @@ static PagerFlags arg_pager_flags = 0; static char **arg_include_prefixes = NULL; static char **arg_exclude_prefixes = NULL; static char *arg_root = NULL; +static char *arg_image = NULL; static char *arg_replace = NULL; #define MAX_DEPTH 256 @@ -177,6 +180,7 @@ STATIC_DESTRUCTOR_REGISTER(unix_sockets, set_free_freep); STATIC_DESTRUCTOR_REGISTER(arg_include_prefixes, freep); STATIC_DESTRUCTOR_REGISTER(arg_exclude_prefixes, freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); +STATIC_DESTRUCTOR_REGISTER(arg_image, freep); static int specifier_machine_id_safe(char specifier, const void *data, const void *userdata, char **ret); static int specifier_directory(char specifier, const void *data, const void *userdata, char **ret); @@ -2884,6 +2888,27 @@ static int cat_config(char **config_dirs, char **args) { return cat_files(NULL, files, 0); } +static int exclude_default_prefixes(void) { + int r; + + /* Provide an easy way to exclude virtual/memory file systems from what we do here. Useful in + * combination with --root= where we probably don't want to apply stuff to these dirs as they are + * likely over-mounted if the root directory is actually used, and it wouldbe less than ideal to have + * all kinds of files created/adjusted underneath these mount points. */ + + r = strv_extend_strv( + &arg_exclude_prefixes, + STRV_MAKE("/dev", + "/proc", + "/run", + "/sys"), + true); + if (r < 0) + return log_oom(); + + return 0; +} + static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -2904,7 +2929,9 @@ static int help(void) { " --boot Execute actions only safe at boot\n" " --prefix=PATH Only apply rules with the specified prefix\n" " --exclude-prefix=PATH Ignore rules with the specified prefix\n" + " -E Ignore rules prefixed with /dev, /proc, /run, /sys\n" " --root=PATH Operate on an alternate filesystem root\n" + " --image=PATH Operate on disk image as filesystem root\n" " --replace=PATH Treat arguments as replacement for PATH\n" " --no-pager Do not pipe output into a pager\n" "\nSee the %s for details.\n" @@ -2928,6 +2955,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_PREFIX, ARG_EXCLUDE_PREFIX, ARG_ROOT, + ARG_IMAGE, ARG_REPLACE, ARG_NO_PAGER, }; @@ -2944,6 +2972,7 @@ static int parse_argv(int argc, char *argv[]) { { "prefix", required_argument, NULL, ARG_PREFIX }, { "exclude-prefix", required_argument, NULL, ARG_EXCLUDE_PREFIX }, { "root", required_argument, NULL, ARG_ROOT }, + { "image", required_argument, NULL, ARG_IMAGE }, { "replace", required_argument, NULL, ARG_REPLACE }, { "no-pager", no_argument, NULL, ARG_NO_PAGER }, {} @@ -2954,7 +2983,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + while ((c = getopt_long(argc, argv, "hE", options, NULL)) >= 0) switch (c) { @@ -3004,6 +3033,21 @@ static int parse_argv(int argc, char *argv[]) { return r; break; + case ARG_IMAGE: + r = parse_path_argument_and_warn(optarg, /* suppress_root= */ false, &arg_image); + if (r < 0) + return r; + + /* Imply -E here since it makes little sense to create files persistently in the /run mointpoint of a disk image */ + _fallthrough_; + + case 'E': + r = exclude_default_prefixes(); + if (r < 0) + return r; + + break; + case ARG_REPLACE: if (!path_is_absolute(optarg) || !endswith(optarg, ".conf")) @@ -3036,6 +3080,13 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "When --replace= is given, some configuration items must be specified"); + if (arg_root && arg_user) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Combination of --user and --root= is not supported."); + + if (arg_image && arg_root) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported."); + return 1; } @@ -3211,6 +3262,9 @@ DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(item_array_hash_ops, char, string_ ItemArray, item_array_free); static int run(int argc, char *argv[]) { + _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; + _cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL; + _cleanup_(umount_and_rmdir_and_freep) char *unlink_dir = NULL; _cleanup_strv_free_ char **config_dirs = NULL; bool invalid_config = false; Iterator iterator; @@ -3243,10 +3297,20 @@ static int run(int argc, char *argv[]) { if (DEBUG_LOGGING) { _cleanup_free_ char *t = NULL; + char **i; - t = strv_join(config_dirs, "\n\t"); - if (t) - log_debug("Looking for configuration files in (higher priority first):\n\t%s", t); + STRV_FOREACH(i, config_dirs) { + _cleanup_free_ char *j = NULL; + + j = path_join(arg_root, *i); + if (!j) + return log_oom(); + + if (!strextend(&t, "\n\t", j, NULL)) + return log_oom(); + } + + log_debug("Looking for configuration files in (higher priority first):%s", t); } if (arg_cat_config) { @@ -3261,6 +3325,23 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; + if (arg_image) { + assert(!arg_root); + + r = mount_image_privately_interactively( + arg_image, + DISSECT_IMAGE_REQUIRE_ROOT|DISSECT_IMAGE_VALIDATE_OS|DISSECT_IMAGE_RELAX_VAR_CHECK|DISSECT_IMAGE_FSCK, + &unlink_dir, + &loop_device, + &decrypted_image); + if (r < 0) + return r; + + arg_root = strdup(unlink_dir); + if (!arg_root) + return log_oom(); + } + items = ordered_hashmap_new(&item_array_hash_ops); globs = ordered_hashmap_new(&item_array_hash_ops); if (!items || !globs)