summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--TODO11
-rw-r--r--man/rules/meson.build1
-rw-r--r--man/systemd-dissect.xml253
-rw-r--r--meson.build3
-rw-r--r--src/basic/copy.c15
-rw-r--r--src/basic/copy.h1
-rw-r--r--src/basic/mkdir.c2
-rw-r--r--src/dissect/dissect.c625
-rw-r--r--src/firstboot/firstboot.c1
-rw-r--r--src/nspawn/nspawn.c13
-rw-r--r--src/partition/repart.c2
-rw-r--r--src/shared/dissect-image.c96
-rw-r--r--src/shared/dissect-image.h2
-rw-r--r--src/shared/json.c45
-rw-r--r--src/shared/json.h6
-rwxr-xr-xtest/units/testsuite-50.sh30
16 files changed, 973 insertions, 133 deletions
diff --git a/TODO b/TODO
index 07f21fc06e..a8d0ca290f 100644
--- a/TODO
+++ b/TODO
@@ -42,9 +42,6 @@ Features:
mounting a subdir of the root fs as actual root. This can be used as
fstype-agnostic version of btrfs' rootflags=subvol=foobar.
-* add --copy-from and --copy-to command to systemd-dissect which copies stuff
- in and out of a disk image
-
* Support ProtectProc= or so, using: https://patchwork.kernel.org/cover/11310197/
* if /usr/bin/swapoff fails due to OOM, log a friendly explanatory message about it
@@ -112,10 +109,6 @@ Features:
* systemd-path: add ESP and XBOOTLDR path. Add "private" runtime/state/cache dir enum,
mapping to $RUNTIME_DIRECTORY, $STATE_DIRECTORY and such
-* make "systemd-dissect" an official supported tool, i.e. move to /usr/bin/ and
- provide man page. Given that we now have a tool that can generate images like
- 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, systemctl,
coredumpctl. (Already done: systemd-nspawn, systemd-firstboot,
@@ -1153,10 +1146,6 @@ Features:
- optionally automatically add FORWARD rules to iptables whenever nspawn is
running, remove them when shut down.
-* dissect
- - refuse mounting over a mount point
- - automatically discover .roothash files in dissect, similarly to nspawn
-
* machined:
- add an API so that libvirt-lxc can inform us about network interfaces being
removed or added to an existing machine
diff --git a/man/rules/meson.build b/man/rules/meson.build
index 3fb454faa9..d545f032a2 100644
--- a/man/rules/meson.build
+++ b/man/rules/meson.build
@@ -818,6 +818,7 @@ manpages = [
['systemd-debug-generator', '8', [], ''],
['systemd-delta', '1', [], ''],
['systemd-detect-virt', '1', [], ''],
+ ['systemd-dissect', '1', [], ''],
['systemd-environment-d-generator',
'8',
['30-systemd-environment-d-generator'],
diff --git a/man/systemd-dissect.xml b/man/systemd-dissect.xml
new file mode 100644
index 0000000000..fd70e1bfc1
--- /dev/null
+++ b/man/systemd-dissect.xml
@@ -0,0 +1,253 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<!-- SPDX-License-Identifier: LGPL-2.1+ -->
+
+<refentry id="systemd-dissect"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+ <refentryinfo>
+ <title>systemd-dissect</title>
+ <productname>systemd</productname>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>systemd-dissect</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>systemd-dissect</refname>
+ <refpurpose>Dissect file system OS images</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>systemd-dissect <arg choice="opt" rep="repeat">OPTIONS</arg> <arg choice="plain"><replaceable>IMAGE</replaceable></arg></command>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>systemd-dissect <arg choice="opt" rep="repeat">OPTIONS</arg> <option>--mount</option> <arg choice="plain"><replaceable>IMAGE</replaceable></arg> <arg choice="plain"><replaceable>PATH</replaceable></arg></command>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>systemd-dissect <arg choice="opt" rep="repeat">OPTIONS</arg> <option>--copy-from</option> <arg choice="plain"><replaceable>IMAGE</replaceable></arg> <arg choice="plain"><replaceable>PATH</replaceable></arg> <arg choice="opt"><replaceable>TARGET</replaceable></arg></command>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>systemd-dissect <arg choice="opt" rep="repeat">OPTIONS</arg> <option>--copy-to</option> <arg choice="plain"><replaceable>IMAGE</replaceable></arg> <arg choice="opt"><replaceable>SOURCE</replaceable></arg> <arg choice="plain"><replaceable>PATH</replaceable></arg></command>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para><command>systemd-dissect</command> is a tool for introspecting and interacting with file system OS
+ disk images. It supports four different operations:</para>
+
+ <orderedlist>
+ <listitem><para>Show general OS image information, including the image's
+ <citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry> data,
+ machine ID, partition information and more.</para></listitem>
+
+ <listitem><para>Mount an OS image to a local directory. In this mode it will dissect the OS image and
+ mount the included partitions according to their designation onto a directory and possibly
+ sub-directories.</para></listitem>
+
+ <listitem><para>Copy files and directories in and out of an OS image.</para></listitem>
+ </orderedlist>
+
+ <para>The tool may operate on three types of OS images:</para>
+
+ <orderedlist>
+ <listitem><para>OS disk images containing a GPT partition table envelope, with partitions marked
+ according to the <ulink url="https://systemd.io/DISCOVERABLE_PARTITIONS">Discoverable Partitions
+ Specification</ulink>.</para></listitem>
+
+ <listitem><para>OS disk images containing just a plain file-system without an enveloping partition
+ table. (This file system is assumed to be the root file system of the OS.)</para></listitem>
+
+ <listitem><para>OS disk images containing a GPT or MBR partition table, with a single
+ partition only. (This partition is assumed to contain the root file system of the OS.)</para></listitem>
+ </orderedlist>
+
+ <para>OS images may use any kind of Linux-supported file systems. In addition they may make use of LUKS
+ disk encryption, and contain Verity integrity information. Note that qualifying OS images may be booted
+ with <citerefentry><refentrytitle>system-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry>'s
+ <option>--image=</option> switch, and be used as root file system for system service using the
+ <varname>RootImage=</varname> unit file setting, see
+ <citerefentry><refentrytitle>system.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Commands</title>
+
+ <para>If neither of the command switches listed below are passed the specified disk image is opened and
+ general information about the image and the contained partitions and their use is shown.</para>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>--mount</option></term>
+ <term><option>-m</option></term>
+
+ <listitem><para>Mount the specified OS image to the specified directory. This will dissect the image,
+ determine the OS root file system — as well as possibly other partitions — and mount them to the
+ specified directory. If the OS image contains multiple partitions marked with the <ulink
+ url="https://systemd.io/DISCOVERABLE_PARTITIONS">Discoverable Partitions Specification</ulink>
+ multiple nested mounts are established. This command expects two arguments: a path to an image file
+ and a path to a directory where to mount the image.</para>
+
+ <para>To unmount an OS image mounted like this use <citerefentry
+ project='man-pages'><refentrytitle>umount</refentrytitle><manvolnum>8</manvolnum></citerefentry>'s
+ <option>-R</option> switch (for recursive operation), so that the OS image and all nested partition
+ mounts are unmounted.</para>
+
+ <para>When the OS image contains LUKS encrypted or Verity integrity protected file systems
+ appropriate volumes are automatically set up and marked for automatic disassembly when the image is
+ unmounted.</para>
+
+ <para>The OS image may either be specified as path to an OS image stored in a regular file or may
+ refer to block device node (in the latter case the block device must be the "whole" device, i.e. not
+ a partition device). (The other supported commands described here support this, too.)</para>
+
+ <para>All mounted file systems are checked with the appropriate <citerefentry
+ project='man-pages'><refentrytitle>fsck</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ implementation in automatic fixing mode, unless explicitly turned off (<option>--fsck=no</option>) or
+ read-only operation is requested (<option>--read-only</option>).</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-M</option></term>
+
+ <listitem><para>This is a shortcut for <option>--mount --mkdir</option>.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--copy-from</option></term>
+ <term><option>-x</option></term>
+
+ <listitem><para>Copies a file or directory from the specified OS image into the specified location on
+ the host file system. Expects three arguments: a path to an image file, a source path (relative to
+ the image's root directory) and a destination path (relative to the current working directory, or an
+ absolute path, both outside of the image). If the destination path is omitted or specified as dash
+ (<literal>-</literal>), the specified file is written to standard output. If the source path in the
+ image file system refers to a regular file it is copied to the destination path. In this case access
+ mode, extended attributes and timestamps are copied as well, but file ownership is not. If the source
+ path in the image refers to a directory, it is copied to the destination path, recursively with all
+ containing files and directories. In this case the file ownership is copied too.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--copy-to</option></term>
+ <term><option>-a</option></term>
+
+ <listitem><para>Copies a file or directory from the specified location in the host file system into
+ the specified OS image. Expects three arguments: a path to an image file, a source path (relative to
+ the current working directory, or an absolute path, both outside of the image) and a destination path
+ (relative to the image's root directory). If the source path is omitted or specified as dash
+ (<literal>-</literal>), the data to write is read from standard input. If the source path in the host
+ file system refers to a regular file, it is copied to the destination path. In this case access mode,
+ extended attributes and timestamps are copied as well, but file ownership is not. If the source path
+ in the host file system refers to a directory it is copied to the destination path, recursively with
+ all containing files and directories. In this case the file ownership is copied
+ too.</para>
+
+ <para>As with <option>--mount</option> file system checks are implicitly run before the copy
+ operation begins.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--json=</option><replaceable>MODE</replaceable></term>
+
+ <listitem><para>Shows output formatted as JSON. Expects one of <literal>short</literal> (for the
+ shortest possible output without any redundant whitespace or line breaks), <literal>pretty</literal>
+ (for a pretty version of the same, with indentation and line breaks) or <literal>off</literal> (to turn
+ off json output).</para></listitem>
+ </varlistentry>
+
+ <xi:include href="standard-options.xml" xpointer="help" />
+ <xi:include href="standard-options.xml" xpointer="version" />
+ </variablelist>
+
+ </refsect1>
+
+ <refsect1>
+ <title>Options</title>
+
+ <para>The following options are understood:</para>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>--read-only</option></term>
+ <term><option>-r</option></term>
+
+ <listitem><para>Operate in read-only mode. By default <option>--mount</option> will establish
+ writable mount points. If this option is specified they are established in read-only mode
+ instead.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--fsck=no</option></term>
+
+ <listitem><para>Turn off automatic file system checking. By default when an image is accessed for
+ writing (by <option>--mount</option> or <option>--add</option>) the file systems contained in the OS
+ image are automatically checked using the appropriate <citerefentry
+ project='man-pages'><refentrytitle>fsck</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ command, in automatic fixing mode. This behavior may be switched off using
+ <option>--fsck=no</option>.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--mkdir</option></term>
+
+ <listitem><para>If combined with <option>--mount</option> the directory to mount the OS image to is
+ created if it is missing. Note that the directory is not automatically removed when the disk image is
+ unmounted again.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--discard=</option></term>
+
+ <listitem><para>Takes one of <literal>disabled</literal>, <literal>loop</literal>,
+ <literal>all</literal>, <literal>crypto</literal>. If <literal>disabled</literal> the image is
+ accessed with empty block discarding turned off. if <literal>loop</literal> discarding is enabled if
+ operating on a regular file. If <literal>crypt</literal> discarding is enabled even on encrypted file
+ systems. If <literal>all</literal> discarding is unconditionally enabled.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--root-hash=</option></term>
+ <term><option>--root-hash-sig=</option></term>
+ <term><option>--verity-data=</option></term>
+
+ <listitem><para>Configure various aspects of Verity data integrity for the OS
+ image. <option>--root-hash=</option> expects a hex-encoding top-level Verity hash to use for setting
+ up the Verity integrity protection. <option>--root-hash-sig=</option> expects the path to a file
+ containing a PKCS#7 signature file for the hash. This signature is passed to the kernel during
+ activation, which will match it against signature keys available in the kernel
+ keyring. <option>--verity-data=</option> expects the path to a file with the Verity data to use for
+ the OS image, in case it is stored in a detached file. It is recommended to embed the Verity data
+ directly in the image, using the Verity mechanisms in the <ulink
+ url="https://systemd.io/DISCOVERABLE_PARTITIONS">Discoverable Partitions Specification</ulink>.</para></listitem>
+ </varlistentry>
+
+ </variablelist>
+
+ </refsect1>
+
+ <refsect1>
+ <title>Exit status</title>
+
+ <para>On success, 0 is returned, a non-zero failure code
+ otherwise.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>system-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>system.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+ <ulink url="https://systemd.io/DISCOVERABLE_PARTITIONS">Discoverable Partitions Specification</ulink>,
+ <citerefentry project='man-pages'><refentrytitle>umount</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ </para>
+ </refsect1>
+
+</refentry>
diff --git a/meson.build b/meson.build
index 134e95d475..227fe097a8 100644
--- a/meson.build
+++ b/meson.build
@@ -1898,8 +1898,7 @@ if conf.get('HAVE_BLKID') == 1
include_directories : includes,
link_with : [libshared],
install_rpath : rootlibexecdir,
- install : true,
- install_dir : rootlibexecdir)
+ install : true)
endif
if conf.get('ENABLE_RESOLVE') == 1
diff --git a/src/basic/copy.c b/src/basic/copy.c
index b384010ae3..54f7235b16 100644
--- a/src/basic/copy.c
+++ b/src/basic/copy.c
@@ -969,6 +969,21 @@ int copy_times(int fdf, int fdt, CopyFlags flags) {
return 0;
}
+int copy_access(int fdf, int fdt) {
+ struct stat st;
+
+ assert(fdf >= 0);
+ assert(fdt >= 0);
+
+ if (fstat(fdf, &st) < 0)
+ return -errno;
+
+ if (fchmod(fdt, st.st_mode & 07777) < 0)
+ return -errno;
+
+ return 0;
+}
+
int copy_xattr(int fdf, int fdt) {
_cleanup_free_ char *names = NULL;
int ret = 0, r;
diff --git a/src/basic/copy.h b/src/basic/copy.h
index af8e88af04..ab9031038e 100644
--- a/src/basic/copy.h
+++ b/src/basic/copy.h
@@ -61,4 +61,5 @@ static inline int copy_bytes(int fdf, int fdt, uint64_t max_bytes, CopyFlags cop
}
int copy_times(int fdf, int fdt, CopyFlags flags);
+int copy_access(int fdf, int fdt);
int copy_xattr(int fdf, int fdt);
diff --git a/src/basic/mkdir.c b/src/basic/mkdir.c
index 6ebc2b95fd..d591e65e41 100644
--- a/src/basic/mkdir.c
+++ b/src/basic/mkdir.c
@@ -106,7 +106,7 @@ int mkdir_parents_internal(const char *prefix, const char *path, mode_t mode, ui
/* return immediately if directory exists */
e = strrchr(path, '/');
if (!e)
- return -EINVAL;
+ return 0;
if (e == path)
return 0;
diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c
index 318cd37c6f..375c5739b2 100644
--- a/src/dissect/dissect.c
+++ b/src/dissect/dissect.c
@@ -4,26 +4,44 @@
#include <getopt.h>
#include <linux/loop.h>
#include <stdio.h>
+#include <sys/ioctl.h>
+#include <sys/mount.h>
#include "architecture.h"
+#include "copy.h"
#include "dissect-image.h"
+#include "fd-util.h"
+#include "format-table.h"
+#include "format-util.h"
+#include "fs-util.h"
#include "hexdecoct.h"
#include "log.h"
#include "loop-util.h"
#include "main-func.h"
+#include "mkdir.h"
+#include "mount-util.h"
+#include "namespace-util.h"
#include "parse-util.h"
#include "path-util.h"
+#include "pretty-print.h"
+#include "stat-util.h"
#include "string-util.h"
#include "strv.h"
+#include "terminal-util.h"
+#include "tmpfile-util.h"
#include "user-util.h"
#include "util.h"
static enum {
ACTION_DISSECT,
ACTION_MOUNT,
+ ACTION_COPY_FROM,
+ ACTION_COPY_TO,
} arg_action = ACTION_DISSECT;
static const char *arg_image = NULL;
static const char *arg_path = NULL;
+static const char *arg_source = NULL;
+static const char *arg_target = NULL;
static DissectImageFlags arg_flags = DISSECT_IMAGE_REQUIRE_ROOT|DISSECT_IMAGE_DISCARD_ON_LOOP|DISSECT_IMAGE_RELAX_VAR_CHECK|DISSECT_IMAGE_FSCK;
static void *arg_root_hash = NULL;
static char *arg_verity_data = NULL;
@@ -31,21 +49,31 @@ static size_t arg_root_hash_size = 0;
static char *arg_root_hash_sig_path = NULL;
static void *arg_root_hash_sig = NULL;
static size_t arg_root_hash_sig_size = 0;
+static bool arg_json = false;
+static JsonFormatFlags arg_json_format_flags = 0;
STATIC_DESTRUCTOR_REGISTER(arg_root_hash, freep);
STATIC_DESTRUCTOR_REGISTER(arg_verity_data, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root_hash_sig_path, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root_hash_sig, freep);
-static void help(void) {
- printf("%s [OPTIONS...] IMAGE\n"
- "%s [OPTIONS...] --mount IMAGE PATH\n"
- "Dissect a file system OS image.\n\n"
- " -h --help Show this help\n"
- " --version Show package version\n"
- " -m --mount Mount the image to the specified directory\n"
+static int help(void) {
+ _cleanup_free_ char *link = NULL;
+ int r;
+
+ r = terminal_urlify_man("systemd-dissect", "1", &link);
+ if (r < 0)
+ return log_oom();
+
+ printf("%1$s [OPTIONS...] IMAGE\n"
+ "%1$s [OPTIONS...] --mount IMAGE PATH\n"
+ "%1$s [OPTIONS...] --copy-from IMAGE PATH [TARGET]\n"
+ "%1$s [OPTIONS...] --copy-to IMAGE [SOURCE] PATH\n\n"
+ "%5$sDissect a file system OS image.%6$s\n\n"
+ "%3$sOptions:%4$s\n"
" -r --read-only Mount read-only\n"
" --fsck=BOOL Run fsck before mounting\n"
+ " --mkdir Make mount directory before mounting, if missing\n"
" --discard=MODE Choose 'discard' mode (disabled, loop, all, crypto)\n"
" --root-hash=HASH Specify root hash for verity\n"
" --root-hash-sig=SIG Specify pkcs7 signature of root hash for verity\n"
@@ -53,9 +81,23 @@ static void help(void) {
" or as an ASCII base64 encoded string prefixed by\n"
" 'base64:'\n"
" --verity-data=PATH Specify data file with hash tree for verity if it is\n"
- " not embedded in IMAGE\n",
- program_invocation_short_name,
- program_invocation_short_name);
+ " not embedded in IMAGE\n"
+ " --json=pretty|short|off\n"
+ " Generate JSON output\n"
+ "\n%3$sCommands:%4$s\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " -m --mount Mount the image to the specified directory\n"
+ " -M Shortcut for --mount --mkdir\n"
+ " -x --copy-from Copy files from image to host\n"
+ " -a --copy-to Copy files from host to image\n"
+ "\nSee the %2$s for details.\n"
+ , program_invocation_short_name
+ , link
+ , ansi_underline(), ansi_normal()
+ , ansi_highlight(), ansi_normal());
+
+ return 0;
}
static int parse_argv(int argc, char *argv[]) {
@@ -67,6 +109,8 @@ static int parse_argv(int argc, char *argv[]) {
ARG_FSCK,
ARG_VERITY_DATA,
ARG_ROOT_HASH_SIG,
+ ARG_MKDIR,
+ ARG_JSON,
};
static const struct option options[] = {
@@ -79,6 +123,10 @@ static int parse_argv(int argc, char *argv[]) {
{ "fsck", required_argument, NULL, ARG_FSCK },
{ "verity-data", required_argument, NULL, ARG_VERITY_DATA },
{ "root-hash-sig", required_argument, NULL, ARG_ROOT_HASH_SIG },
+ { "mkdir", no_argument, NULL, ARG_MKDIR },
+ { "copy-from", no_argument, NULL, 'x' },
+ { "copy-to", no_argument, NULL, 'a' },
+ { "json", required_argument, NULL, ARG_JSON },
{}
};
@@ -87,13 +135,12 @@ static int parse_argv(int argc, char *argv[]) {
assert(argc >= 0);
assert(argv);
- while ((c = getopt_long(argc, argv, "hmr", options, NULL)) >= 0) {
+ while ((c = getopt_long(argc, argv, "hmrMxa", options, NULL)) >= 0) {
switch (c) {
case 'h':
- help();
- return 0;
+ return help();
case ARG_VERSION:
return version();
@@ -102,6 +149,25 @@ static int parse_argv(int argc, char *argv[]) {
arg_action = ACTION_MOUNT;
break;
+ case ARG_MKDIR:
+ arg_flags |= DISSECT_IMAGE_MKDIR;
+ break;
+
+ case 'M':
+ /* Shortcut combination of the above two */
+ arg_action = ACTION_MOUNT;
+ arg_flags |= DISSECT_IMAGE_MKDIR;
+ break;
+
+ case 'x':
+ arg_action = ACTION_COPY_FROM;
+ arg_flags |= DISSECT_IMAGE_READ_ONLY;
+ break;
+
+ case 'a':
+ arg_action = ACTION_COPY_TO;
+ break;
+
case 'r':
arg_flags |= DISSECT_IMAGE_READ_ONLY;
break;
@@ -117,7 +183,13 @@ static int parse_argv(int argc, char *argv[]) {
flags = DISSECT_IMAGE_DISCARD_ON_LOOP | DISSECT_IMAGE_DISCARD;
else if (streq(optarg, "crypt"))
flags = DISSECT_IMAGE_DISCARD_ANY;
- else
+ else if (streq(optarg, "list")) {
+ puts("disabled\n"
+ "all\n"
+ "crypt\n"
+ "loop");
+ return 0;
+ } else
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Unknown --discard= parameter: %s",
optarg);
@@ -184,6 +256,26 @@ static int parse_argv(int argc, char *argv[]) {
SET_FLAG(arg_flags, DISSECT_IMAGE_FSCK, r);
break;
+ case ARG_JSON:
+ if (streq(optarg, "pretty")) {
+ arg_json = true;
+ arg_json_format_flags = JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR_AUTO;
+ } else if (streq(optarg, "short")) {
+ arg_json = true;
+ arg_json_format_flags = JSON_FORMAT_NEWLINE;
+ } else if (streq(optarg, "off")) {
+ arg_json = false;
+ arg_json_format_flags = 0;
+ } else if (streq(optarg, "help")) {
+ puts("pretty\n"
+ "short\n"
+ "off");
+ return 0;
+ } else
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown argument to --json=: %s", optarg);
+
+ break;
+
case '?':
return -EINVAL;
@@ -198,7 +290,7 @@ static int parse_argv(int argc, char *argv[]) {
case ACTION_DISSECT:
if (optind + 1 != argc)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Expected a file path as only argument.");
+ "Expected an image file path as only argument.");
arg_image = argv[optind];
arg_flags |= DISSECT_IMAGE_READ_ONLY;
@@ -207,87 +299,102 @@ static int parse_argv(int argc, char *argv[]) {
case ACTION_MOUNT:
if (optind + 2 != argc)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Expected a file path and mount point path as only arguments.");
+ "Expected an image file path and mount point path as only arguments.");
arg_image = argv[optind];
arg_path = argv[optind + 1];
break;
- default:
- assert_not_reached("Unknown action.");
- }
-
- return 1;
-}
-
-static int run(int argc, char *argv[]) {
- _cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
- _cleanup_(decrypted_image_unrefp) DecryptedImage *di = NULL;
- _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
- int r;
-
- log_parse_environment();
- log_open();
+ case ACTION_COPY_FROM:
+ if (argc < optind + 2 || argc > optind + 3)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Expected an image file path, a source path and an optional destination path as only arguments.");
- r = parse_argv(argc, argv);
- if (r <= 0)
- return r;
+ arg_image = argv[optind];
+ arg_source = argv[optind + 1];
+ arg_target = argc > optind + 2 ? argv[optind + 2] : "-" /* this means stdout */ ;
- r = loop_device_make_by_path(arg_image, (arg_flags & DISSECT_IMAGE_READ_ONLY) ? O_RDONLY : O_RDWR, LO_FLAGS_PARTSCAN, &d);
- if (r < 0)
- return log_error_errno(r, "Failed to set up loopback device: %m");
+ arg_flags |= DISSECT_IMAGE_READ_ONLY;
+ break;
- r = verity_metadata_load(arg_image, NULL, arg_root_hash ? NULL : &arg_root_hash, &arg_root_hash_size,
- arg_verity_data ? NULL : &arg_verity_data,
- arg_root_hash_sig_path || arg_root_hash_sig ? NULL : &arg_root_hash_sig_path);
- if (r < 0)
- return log_error_errno(r, "Failed to read verity artefacts for %s: %m", arg_image);
- arg_flags |= arg_verity_data ? DISSECT_IMAGE_NO_PARTITION_TABLE : 0;
+ case ACTION_COPY_TO:
+ if (argc < optind + 2 || argc > optind + 3)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Expected an image file path, an optional source path and a destination path as only arguments.");
- r = dissect_image_and_warn(d->fd, arg_image, arg_root_hash, arg_root_hash_size, arg_verity_data, NULL, arg_flags, &m);
- if (r < 0)
- return r;
+ arg_image = argv[optind];
- switch (arg_action) {
+ if (argc > optind + 2) {
+ arg_source = argv[optind + 1];
+ arg_target = argv[optind + 2];
+ } else {
+ arg_source = "-"; /* this means stdin */
+ arg_target = argv[optind + 1];
+ }
- case ACTION_DISSECT: {
- unsigned i;
+ break;
- for (i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) {
- DissectedPartition *p = m->partitions + i;
+ default:
+ assert_not_reached("Unknown action.");
+ }
- if (!p->found)
- continue;
+ return 1;
+}
- printf("Found %s '%s' partition",
- p->rw ? "writable" : "read-only",
- partition_designator_to_string(i));
+static int strv_pair_to_json(char **l, JsonVariant **ret) {
+ _cleanup_strv_free_ char **jl = NULL;
+ char **a, **b;
- if (!sd_id128_is_null(p->uuid))
- printf(" (UUID " SD_ID128_FORMAT_STR ")", SD_ID128_FORMAT_VAL(p->uuid));
+ STRV_FOREACH_PAIR(a, b, l) {
+ char *j;
- if (p->fstype)
- printf(" of type %s", p->fstype);
+ j = strjoin(*a, "=", *b);
+ if (!j)
+ return log_oom();
- if (p->architecture != _ARCHITECTURE_INVALID)
- printf(" for %s", architecture_to_string(p->architecture));
+ if (strv_consume(&jl, j) < 0)
+ return log_oom();
+ }
- if (dissected_image_can_do_verity(m, i))
- printf(" %s verity", dissected_image_has_verity(m, i) ? "with" : "without");
+ return json_variant_new_array_strv(ret, jl);
+}
- if (p->partno >= 0)
- printf(" on partition #%i", p->partno);
+static int action_dissect(DissectedImage *m, LoopDevice *d) {
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ _cleanup_(table_unrefp) Table *t = NULL;
+ uint64_t size = UINT64_MAX;
+ int r;
- if (p->node)
- printf(" (%s)", p->node);
+ assert(m);
+ assert(d);
- putchar('\n');
- }
+ if (!arg_json)
+ printf(" Name: %s\n", basename(arg_image));
- r = dissected_image_acquire_metadata(m);
- if (r < 0)
- return log_error_errno(r, "Failed to acquire image metadata: %m");
+ if (ioctl(d->fd, BLKGETSIZE64, &size) < 0)
+ log_debug_errno(errno, "Failed to query size of loopback device: %m");
+ else if (!arg_json) {
+ char s[FORMAT_BYTES_MAX];
+ printf(" Size: %s\n", format_bytes(s, sizeof(s), size));
+ }
+ if (!arg_json)
+ putc('\n', stdout);
+
+ r = dissected_image_acquire_metadata(m);
+ if (r == -ENXIO)
+ return log_error_errno(r, "No root partition discovered.");
+ if (r == -EMEDIUMTYPE)
+ return log_error_errno(r, "Not a valid OS image, no os-release file included.");
+ if (r == -EUCLEAN)
+ return log_error_errno(r, "File system check of image failed.");
+ if (r == -EUNATCH)
+ log_warning_errno(r, "OS image is encrypted, proceeding without showing OS image metadata.");
+ else if (r == -EBUSY)
+ log_warning_errno(r, "OS image is currently in use, proceeding without showing OS image metadata.");
+ else if (r < 0)
+ return log_error_errno(r, "Failed to acquire image metadata: %m");
+ else if (!arg_json) {
if (m->hostname)
printf(" Hostname: %s\n", m->hostname);
@@ -311,35 +418,381 @@ static int run(int argc, char *argv[]) {
p == m->os_release ? "OS Release:" : " ",
*p, *q);
}
+ }
- break;
+ if (arg_json) {
+ _cleanup_(json_variant_unrefp) JsonVariant *mi = NULL, *osr = NULL;
+
+ if (!strv_isempty(m->machine_info)) {
+ r = strv_pair_to_json(m->machine_info, &mi);
+ if (r < 0)
+ return log_oom();
+ }
+
+ if (!strv_isempty(m->os_release)) {
+ r = strv_pair_to_json(m->os_release, &osr);
+ if (r < 0)
+ return log_oom();
+ }
+
+ r = json_build(&v, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("name", JSON_BUILD_STRING(basename(arg_image))),
+ JSON_BUILD_PAIR("size", JSON_BUILD_INTEGER(size)),
+ JSON_BUILD_PAIR_CONDITION(m->hostname, "hostname", JSON_BUILD_STRING(m->hostname)),
+ JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(m->machine_id), "machineId", JSON_BUILD_ID128(m->machine_id)),
+ JSON_BUILD_PAIR_CONDITION(mi, "machineInfo", JSON_BUILD_VARIANT(mi)),
+ JSON_BUILD_PAIR_CONDITION(osr, "osRelease", JSON_BUILD_VARIANT(osr))));
+ if (r < 0)
+ return log_oom();
}
- case ACTION_MOUNT:
- r = dissected_image_decrypt_interactively(m, NULL, arg_root_hash, arg_root_hash_size, arg_verity_data, arg_root_hash_sig_path, arg_root_hash_sig, arg_root_hash_sig_size, arg_flags, &di);
+ if (!arg_json)
+ putc('\n', stdout);
+
+ t = table_new("rw", "designator", "partition uuid", "fstype", "architecture", "verity", "node", "partno");
+ if (!t)
+ return log_oom();
+
+ (void) table_set_empty_string(t, "-");
+ (void) table_set_align_percent(t, table_get_cell(t, 0, 7), 100);
+
+ for (unsigned i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) {
+ DissectedPartition *p = m->partitions + i;
+
+ if (!p->found)
+ continue;
+
+ r = table_add_many(
+ t,
+ TABLE_STRING, p->rw ? "rw" : "ro",
+ TABLE_STRING, partition_designator_to_string(i));
if (r < 0)
- return r;
+ return table_log_add_error(r);
- r = dissected_image_mount(m, arg_path, UID_INVALID, arg_flags);
- if (r == -EUCLEAN)
- return log_error_errno(r, "File system check on image failed: %m");
+ if (sd_id128_is_null(p->uuid))
+ r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
+ else
+ r = table_add_cell(t, NULL, TABLE_UUID, &p->uuid);
+ if (r < 0)
+ return table_log_add_error(r);
+
+ r = table_add_many(
+ t,
+ TABLE_STRING, p->fstype,
+ TABLE_STRING, architecture_to_string(p->architecture));
+ if (r < 0)
+ return table_log_add_error(r);
+
+ if (arg_verity_data)
+ r = table_add_cell(t, NULL, TABLE_STRING, "external");
+ else if (dissected_image_can_do_verity(m, i))
+ r = table_add_cell(t, NULL, TABLE_STRING, yes_no(dissected_image_has_verity(m, i)));
+ else
+ r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
if (r < 0)
- return log_error_errno(r, "Failed to mount image: %m");
+ return table_log_add_error(r);
- if (di) {
- r = decrypted_image_relinquish(di);
+ if (p->partno < 0) /* no partition table, naked file system */ {
+ r = table_add_cell(t, NULL, TABLE_STRING, arg_image);
if (r < 0)
- return log_error_errno(r, "Failed to relinquish DM devices: %m");
+ return table_log_add_error(r);
+
+ r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
+ } else {
+ r = table_add_cell(t, NULL, TABLE_STRING, p->node);
+ if (r < 0)
+ return table_log_add_error(r);
+
+ r = table_add_cell(t, NULL, TABLE_INT, &p->partno);
}
+ if (r < 0)
+ return table_log_add_error(r);
+ }
+
+ if (arg_json) {
+ _cleanup_(json_variant_unrefp) JsonVariant *jt = NULL;
+
+ r = table_to_json(t, &jt);
+ if (r < 0)
+ return log_error_errno(r, "Failed to convert table to JSON: %m");
+
+ r = json_variant_set_field(&v, "mounts", jt);
+ if (r < 0)
+ return log_oom();
+
+ json_variant_dump(v, arg_json_format_flags, stdout, NULL);
+ } else {
+ r = table_print(t, stdout);
+ if (r < 0)
+ return log_error_errno(r, "Failed to dump table: %m");
+ }
+
+ return 0;
+}
+
+static int action_mount(DissectedImage *m, LoopDevice *d) {
+ _cleanup_(decrypted_image_unrefp) DecryptedImage *di = NULL;
+ int r;
+
+ assert(m);
+ assert(d);
- loop_device_relinquish(d);
+ r = dissected_image_decrypt_interactively(
+ m, NULL,
+ arg_root_hash, arg_root_hash_size,
+ arg_verity_data,
+ arg_root_hash_sig_path, arg_root_hash_sig, arg_root_hash_sig_size,
+ arg_flags,
+ &di);
+ if (r < 0)
+ return r;
+
+ r = dissected_image_mount_and_warn(m, arg_path, UID_INVALID, arg_flags);
+ if (r < 0)
+ return r;
+
+ if (di) {
+ r = decrypted_image_relinquish(di);
+ if (r < 0)
+ return log_error_errno(r, "Failed to relinquish DM devices: %m");
+ }
+
+ loop_device_relinquish(d);
+ return 0;
+}
+
+static int action_copy(DissectedImage *m, LoopDevice *d) {
+ _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
+ _cleanup_(decrypted_image_unrefp) DecryptedImage *di = NULL;
+ _cleanup_(rmdir_and_freep) char *created_dir = NULL;
+ _cleanup_free_ char *temp = NULL;
+ int r;
+
+ assert(m);
+ assert(d);
+
+ r = dissected_image_decrypt_interactively(
+ m, NULL,
+ arg_root_hash, arg_root_hash_size,
+ arg_verity_data,
+ arg_root_hash_sig_path, arg_root_hash_sig, arg_root_hash_sig_size,
+ arg_flags,
+ &di);
+ if (r < 0)
+ return r;
+
+ r = detach_mount_namespace();
+ if (r < 0)
+ return log_error_errno(r, "Failed to detach mount namespace: %m");
+
+ 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 = 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_and_warn(m, created_dir, UID_INVALID, arg_flags);
+ if (r < 0)
+ return r;
+
+ mounted_dir = TAKE_PTR(created_dir);
+
+ if (di) {
+ r = decrypted_image_relinquish(di);
+ if (r < 0)
+ return log_error_errno(r, "Failed to relinquish DM devices: %m");
+ }
+
+ loop_device_relinquish(d);
+
+ if (arg_action == ACTION_COPY_FROM) {
+ _cleanup_close_ int source_fd = -1, target_fd = -1;
+
+ source_fd = chase_symlinks_and_open(arg_source, mounted_dir, CHASE_PREFIX_ROOT|CHASE_WARN, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
+ if (source_fd < 0)
+ return log_error_errno(source_fd, "Failed to open source path '%s' in image '%s': %m", arg_source, arg_image);
+
+ /* Copying to stdout? */
+ if (streq(arg_target, "-")) {
+ r = copy_bytes(source_fd, STDOUT_FILENO, (uint64_t) -1, COPY_REFLINK);
+ if (r < 0)
+ return log_error_errno(r, "Failed to copy bytes from %s in mage '%s' to stdout: %m", arg_source, arg_image);
+
+ /* When we copy to stdou we don't copy any attributes (i.e. no access mode, no ownership, no xattr, no times) */
+ return 0;
+ }
+
+ /* Try to copy as directory? */
+ r = copy_directory_fd(source_fd, arg_target, COPY_REFLINK|COPY_MERGE_EMPTY|COPY_SIGINT);
+ if (r >= 0)
+ return 0;
+ if (r != -ENOTDIR)
+ return log_error_errno(r, "Failed to copy %s in image '%s' to '%s': %m", arg_source, arg_image, arg_target);
+
+ r = fd_verify_regular(source_fd);
+ if (r == -EISDIR)
+ return log_error_errno(r, "Target '%s' exists already and is not a directory.", arg_target);
+ if (r < 0)
+ return log_error_errno(r, "Source path %s in image '%s' is neither regular file nor directory, refusing: %m", arg_source, arg_image);
+
+ /* Nah, it's a plain file! */
+ target_fd = open(arg_target, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600);
+ if (target_fd < 0)
+ return log_error_errno(errno, "Failed to create regular file at target path '%s': %m", arg_target);
+
+ r = copy_bytes(source_fd, target_fd, (uint64_t) -1, COPY_REFLINK);
+ if (r < 0)
+ return log_error_errno(r, "Failed to copy bytes from %s in mage '%s' to '%s': %m", arg_source, arg_image, arg_target);
+
+ (void) copy_xattr(source_fd, target_fd);
+ (void) copy_access(source_fd, target_fd);
+ (void) copy_times(source_fd, target_fd, 0);
+
+ /* When this is a regular file we don't copy ownership! */
+
+ } else {
+ _cleanup_close_ int source_fd = -1, target_fd = -1;
+ _cleanup_close_ int dfd = -1;
+ _cleanup_free_ char *dn = NULL;
+
+ assert(arg_action == ACTION_COPY_TO);
+
+ dn = dirname_malloc(arg_target);
+ if (!dn)
+ return log_oom();
+
+ r = chase_symlinks(dn, mounted_dir, CHASE_PREFIX_ROOT|CHASE_WARN, NULL, &dfd);
+ if (r < 0)
+ return log_error_errno(r, "Failed to open '%s': %m", dn);
+
+ /* Are we reading from stdin? */
+ if (streq(arg_source, "-")) {
+ target_fd = openat(dfd, basename(arg_target), O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_EXCL, 0644);
+ if (target_fd < 0)
+ return log_error_errno(errno, "Failed to open target file '%s': %m", arg_target);
+
+ r = copy_bytes(STDIN_FILENO, target_fd, (uint64_t) -1, COPY_REFLINK);
+ if (r < 0)
+ return log_error_errno(r, "Failed to copy bytes from stdin to '%s' in image '%s': %m", arg_target, arg_image);
+
+ /* When we copy from stdin we don't copy any attributes (i.e. no access mode, no ownership, no xattr, no times) */
+ return 0;
+ }
+
+ source_fd = open(arg_source, O_RDONLY|O_CLOEXEC|O_NOCTTY);
+ if (source_fd < 0)
+ return log_error_errno(source_fd, "Failed to open source path '%s': %m", arg_source);
+
+ r = fd_verify_regular(source_fd);
+ if (r < 0) {
+ if (r != -EISDIR)
+ return log_error_errno(r, "Source '%s' is neither regular file nor directory: %m", arg_source);
+
+ /* We are looking at a directory. */
+
+ target_fd = openat(dfd, basename(arg_target), O_RDONLY|O_DIRECTORY|O_CLOEXEC);
+ if (target_fd < 0) {
+ if (errno != ENOENT)
+ return log_error_errno(errno, "Failed to open destination '%s': %m", arg_target);
+
+ r = copy_tree_at(source_fd, ".", dfd, basename(arg_target), UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_SIGINT);
+ } else
+ r = copy_tree_at(source_fd, ".", target_fd, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_SIGINT);
+ if (r < 0)
+ return log_error_errno(r, "Failed to copy '%s' to '%s' in image '%s': %m", arg_source, arg_target, arg_image);
+
+ return 0;
+ }
+
+ /* We area looking at a regular file */
+ target_fd = openat(dfd, basename(arg_target), O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_EXCL, 0600);
+ if (target_fd < 0)
+ return log_error_errno(errno, "Failed to open target file '%s': %m", arg_target);
+
+ r = copy_bytes(source_fd, target_fd, (uint64_t) -1, COPY_REFLINK);
+ if (r < 0)
+ return log_error_errno(r, "Failed to copy bytes from '%s' to '%s' in image '%s': %m", arg_source, arg_target, arg_image);
+
+ (void) copy_xattr(source_fd, target_fd);
+ (void) copy_access(source_fd, target_fd);
+ (void) copy_times(source_fd, target_fd, 0);
+
+ /* When this is a regular file we don't copy ownership! */
+ }
+
+ return 0;
+}
+
+static int run(int argc, char *argv[]) {
+ _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
+ _cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
+ int r;
+
+ log_parse_environment();
+ log_open();
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r;
+
+ r = verity_metadata_load(
+ arg_image, NULL,
+ arg_root_hash ? NULL : &arg_root_hash,
+ &arg_root_hash_size,
+ arg_verity_data ? NULL : &arg_verity_data,
+ arg_root_hash_sig_path || arg_root_hash_sig ? NULL : &arg_root_hash_sig_path);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read verity artifacts for %s: %m", arg_image);
+
+ r = loop_device_make_by_path(
+ arg_image,
+ (arg_flags & DISSECT_IMAGE_READ_ONLY) ? O_RDONLY : O_RDWR,
+ arg_verity_data ? 0 : LO_FLAGS_PARTSCAN,
+ &d);
+ if (r < 0)
+ return log_error_errno(r, "Failed to set up loopback device: %m");
+
+ if (arg_verity_data)
+ arg_flags |= DISSECT_IMAGE_NO_PARTITION_TABLE; /* We only support Verity per file system,
+ * hence if there's external Verity data
+ * available we turn off partition table
+ * support */
+ r = dissect_image_and_warn(
+ d->fd,
+ arg_image,
+ arg_root_hash,
+ arg_root_hash_size,
+ arg_verity_data,
+ NULL,
+ arg_flags,
+ &m);
+ if (r < 0)
+ return r;
+
+ switch (arg_action) {
+
+ case ACTION_DISSECT:
+ r = action_dissect(m, d);
+ break;
+
+ case ACTION_MOUNT:
+ r = action_mount(m, d);
+ break;
+
+ case ACTION_COPY_FROM:
+ case ACTION_COPY_TO:
+ r = action_copy(m, d);
break;
default:
assert_not_reached("Unknown action.");
}
- return 0;
+ return r;
}
DEFINE_MAIN_FUNCTION(run);
diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c
index d56de0bb25..e4c7a2d374 100644
--- a/src/firstboot/firstboot.c
+++ b/src/firstboot/firstboot.c
@@ -23,7 +23,6 @@
#include "memory-util.h"
#include "mkdir.h"
#include "mount-util.h"
-#include "namespace-util.h"
#include "os-util.h"
#include "parse-util.h"
#include "path-util.h"
diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c
index 6d6fe87ed1..1b83f5ad58 100644
--- a/src/nspawn/nspawn.c
+++ b/src/nspawn/nspawn.c
@@ -3369,14 +3369,13 @@ static int outer_child(
* uid shift known. That way we can mount VFAT file systems shifted to the right place right away. This
* makes sure ESP partitions and userns are compatible. */
- r = dissected_image_mount(dissected_image, directory, arg_uid_shift,
- DISSECT_IMAGE_MOUNT_ROOT_ONLY|DISSECT_IMAGE_DISCARD_ON_LOOP|
- (arg_read_only ? DISSECT_IMAGE_READ_ONLY : DISSECT_IMAGE_FSCK)|
- (arg_start_mode == START_BOOT ? DISSECT_IMAGE_VALIDATE_OS : 0));
- if (r == -EUCLEAN)
- return log_error_errno(r, "File system check for image failed: %m");
+ r = dissected_image_mount_and_warn(
+ dissected_image, directory, arg_uid_shift,
+ DISSECT_IMAGE_MOUNT_ROOT_ONLY|DISSECT_IMAGE_DISCARD_ON_LOOP|
+ (arg_read_only ? DISSECT_IMAGE_READ_ONLY : DISSECT_IMAGE_FSCK)|
+ (arg_start_mode == START_BOOT ? DISSECT_IMAGE_VALIDATE_OS : 0));
if (r < 0)
- return log_error_errno(r, "Failed to mount image root file system: %m");
+ return r;
}
r = determine_uid_shift(directory);
diff --git a/src/partition/repart.c b/src/partition/repart.c
index 9a9fd6dff3..572db3bdc4 100644
--- a/src/partition/repart.c
+++ b/src/partition/repart.c
@@ -2826,7 +2826,7 @@ static int help(void) {
" --seed=UUID 128bit seed UUID to derive all UUIDs from\n"
" --size=BYTES Grow loopback file to specified size\n"
" --json=pretty|short|off\n"
- " Generate json output\n"
+ " Generate JSON output\n"
"\nSee the %s for details.\n"
, program_invocation_short_name
, ansi_highlight(), ansi_normal()
diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c
index d3f183b50c..8cbf4828b8 100644
--- a/src/shared/dissect-image.c
+++ b/src/shared/dissect-image.c
@@ -9,6 +9,7 @@
#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/wait.h>
+#include <sysexits.h>
#include "sd-device.h"
#include "sd-id128.h"
@@ -1003,9 +1004,9 @@ static int mount_partition(
if (!m->found || !node || !fstype)
return 0;
- /* Stacked encryption? Yuck */
+ /* We are looking at an encrypted partition? This either means stacked encryption, or the caller didn't call dissected_image_decrypt() beforehand. Let's return a recognizable error for this case. */
if (streq_ptr(fstype, "crypto_LUKS"))
- return -ELOOP;
+ return -EUNATCH;
rw = m->rw && !(flags & DISSECT_IMAGE_READ_ONLY);
@@ -1047,6 +1048,12 @@ static int mount_partition(
if (!strextend_with_separator(&options, ",", m->mount_options, NULL))
return -ENOMEM;
+ if (FLAGS_SET(flags, DISSECT_IMAGE_MKDIR)) {
+ r = mkdir_p(p, 0755);
+ if (r < 0)
+ return r;
+ }
+
r = mount_verbose(LOG_DEBUG, node, p, fstype, MS_NODEV|(rw ? 0 : MS_RDONLY), options);
if (r < 0)
return r;
@@ -1060,6 +1067,15 @@ int dissected_image_mount(DissectedImage *m, const char *where, uid_t uid_shift,
assert(m);
assert(where);
+ /* Returns:
+ *
+ * -ENXIO → No root partition found
+ * -EMEDIUMTYPE → DISSECT_IMAGE_VALIDATE_OS set but no os-release file found
+ * -EUNATCH → Encrypted partition found for which no dm-crypt was set up yet
+ * -EUCLEAN → fsck for file system failed
+ * -EBUSY → File system already mounted/used elsewhere (kernel)
+ */
+
if (!m->partitions[PARTITION_ROOT].found)
return -ENXIO;
@@ -1080,6 +1096,10 @@ int dissected_image_mount(DissectedImage *m, const char *where, uid_t uid_shift,
if (flags & DISSECT_IMAGE_MOUNT_ROOT_ONLY)
return 0;
+ /* Mask DISSECT_IMAGE_MKDIR for all subdirs: the idea is that only the top-level mount point is
+ * created if needed, but the image itself not modified. */
+ flags &= ~DISSECT_IMAGE_MKDIR;
+
r = mount_partition(m->partitions + PARTITION_HOME, where, "/home", uid_shift, flags);
if (r < 0)
return r;
@@ -1125,6 +1145,29 @@ int dissected_image_mount(DissectedImage *m, const char *where, uid_t uid_shift,
return 0;
}
+int dissected_image_mount_and_warn(DissectedImage *m, const char *where, uid_t uid_shift, DissectImageFlags flags) {
+ int r;
+
+ assert(m);
+ assert(where);
+
+ r = dissected_image_mount(m, where, uid_shift, flags);
+ if (r == -ENXIO)
+ return log_error_errno(r, "Not root file system found in image.");
+ if (r == -EMEDIUMTYPE)
+ return log_error_errno(r, "No suitable os-release file in image found.");
+ if (r == -EUNATCH)
+ return log_error_errno(r, "Encrypted file system discovered, but decryption not requested.");
+ if (r == -EUCLEAN)
+ return log_error_errno(r, "File system check on image failed.");
+ if (r == -EBUSY)
+ return log_error_errno(r, "File system already mounted elsewhere.");
+ if (r < 0)
+ return log_error_errno(r, "Failed to mount image: %m");
+
+ return r;
+}
+
#if HAVE_LIBCRYPTSETUP
typedef struct DecryptedPartition {
struct crypt_device *device;
@@ -1586,7 +1629,14 @@ int decrypted_image_relinquish(DecryptedImage *d) {
return 0;
}
-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) {
+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) {
+
_cleanup_free_ char *verity_filename = NULL, *roothashsig_filename = NULL;
_cleanup_free_ void *roothash_decoded = NULL;
size_t roothash_decoded_size = 0;
@@ -1722,12 +1772,14 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
};
_cleanup_strv_free_ char **machine_info = NULL, **os_release = NULL;
+ _cleanup_close_pair_ int error_pipe[2] = { -1, -1 };
_cleanup_(rmdir_and_freep) char *t = NULL;
_cleanup_(sigkill_waitp) pid_t child = 0;
sd_id128_t machine_id = SD_ID128_NULL;
_cleanup_free_ char *hostname = NULL;
unsigned n_meta_initialized = 0, k;
- int fds[2 * _META_MAX], r;
+ int fds[2 * _META_MAX], r, v;
+ ssize_t n;
BLOCK_SIGNALS(SIGCHLD);
@@ -1743,18 +1795,28 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
if (r < 0)
goto finish;
+ if (pipe2(error_pipe, O_CLOEXEC) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
r = safe_fork("(sd-dissect)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE, &child);
if (r < 0)
goto finish;
if (r == 0) {
+ error_pipe[0] = safe_close(error_pipe[0]);
+
r = dissected_image_mount(m, t, UID_INVALID, DISSECT_IMAGE_READ_ONLY|DISSECT_IMAGE_MOUNT_ROOT_ONLY|DISSECT_IMAGE_VALIDATE_OS);
if (r < 0) {
+ /* Let parent know the error */
+ (void) write(error_pipe[1], &r, sizeof(r));
+
log_debug_errno(r, "Failed to mount dissected image: %m");
_exit(EXIT_FAILURE);
}
for (k = 0; k < _META_MAX; k++) {
- _cleanup_close_ int fd = -1;
+ _cleanup_close_ int fd = -ENOENT;
const char *p;
fds[2*k] = safe_close(fds[2*k]);
@@ -1766,12 +1828,15 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
}
if (fd < 0) {
log_debug_errno(fd, "Failed to read %s file of image, ignoring: %m", paths[k]);
+ fds[2*k+1] = safe_close(fds[2*k+1]);
continue;
}
r = copy_bytes(fd, fds[2*k+1], (uint64_t) -1, 0);
- if (r < 0)
+ if (r < 0) {
+ (void) write(error_pipe[1], &r, sizeof(r));
_exit(EXIT_FAILURE);
+ }
fds[2*k+1] = safe_close(fds[2*k+1]);
}
@@ -1779,6 +1844,8 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
_exit(EXIT_SUCCESS);
}
+ error_pipe[1] = safe_close(error_pipe[1]);
+
for (k = 0; k < _META_MAX; k++) {
_cleanup_fclose_ FILE *f = NULL;
@@ -1836,7 +1903,16 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
r = wait_for_terminate_and_check("(sd-dissect)", child, 0);
child = 0;
if (r < 0)
- goto finish;
+ return r;
+
+ n = read(error_pipe[0], &v, sizeof(v));
+ if (n < 0)
+ return -errno;
+ if (n == sizeof(v))
+ return v; /* propagate error sent to us from child */
+ if (n != 0)
+ return -EIO;
+
if (r != EXIT_SUCCESS)
return -EPROTO;
@@ -1995,11 +2071,9 @@ int mount_image_privately_interactively(
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");
+ r = dissected_image_mount_and_warn(dissected_image, created_dir, UID_INVALID, flags);
if (r < 0)
- return log_error_errno(r, "Failed to mount image: %m");
+ return r;
if (decrypted_image) {
r = decrypted_image_relinquish(decrypted_image);
diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h
index 7f67c8745e..4d21789e18 100644
--- a/src/shared/dissect-image.h
+++ b/src/shared/dissect-image.h
@@ -69,6 +69,7 @@ typedef enum DissectImageFlags {
DISSECT_IMAGE_FSCK = 1 << 11, /* File system check the partition before mounting (no effect when combined with DISSECT_IMAGE_READ_ONLY) */
DISSECT_IMAGE_NO_PARTITION_TABLE = 1 << 12, /* Only recognize single file system images */
DISSECT_IMAGE_VERITY_SHARE = 1 << 13, /* When activating a verity device, reuse existing one if already open */
+ DISSECT_IMAGE_MKDIR = 1 << 14, /* Make directory to mount right before mounting, if missing */
} DissectImageFlags;
struct DissectedImage {
@@ -105,6 +106,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(DissectedImage*, dissected_image_unref);
int dissected_image_decrypt(DissectedImage *m, const char *passphrase, const void *root_hash, size_t root_hash_size, const char *verity_data, const char *root_hash_sig_path, const void *root_hash_sig, size_t root_hash_sig_size, DissectImageFlags flags, DecryptedImage **ret);
int dissected_image_decrypt_interactively(DissectedImage *m, const char *passphrase, const void *root_hash, size_t root_hash_size, const char *verity_data, const char *root_hash_sig_path, const void *root_hash_sig, size_t root_hash_sig_size, DissectImageFlags flags, DecryptedImage **ret);
int dissected_image_mount(DissectedImage *m, const char *dest, uid_t uid_shift, DissectImageFlags flags);
+int dissected_image_mount_and_warn(DissectedImage *m, const char *where, uid_t uid_shift, DissectImageFlags flags);
int dissected_image_acquire_metadata(DissectedImage *m);
diff --git a/src/shared/json.c b/src/shared/json.c
index 27a3a518fe..2e151578ff 100644
--- a/src/shared/json.c
+++ b/src/shared/json.c
@@ -430,6 +430,12 @@ int json_variant_new_base64(JsonVariant **ret, const void *p, size_t n) {
return json_variant_new_stringn(ret, s, k);
}
+int json_variant_new_id128(JsonVariant **ret, sd_id128_t id) {
+ char s[SD_ID128_STRING_MAX];
+
+ return json_variant_new_string(ret, sd_id128_to_string(id, s));
+}
+
static void json_variant_set(JsonVariant *a, JsonVariant *b) {
assert(a);
@@ -1964,6 +1970,17 @@ int json_variant_set_field_boolean(JsonVariant **v, const char *field, bool b) {
return json_variant_set_field(v, field, m);
}
+int json_variant_set_field_strv(JsonVariant **v, const char *field, char **l) {
+ _cleanup_(json_variant_unrefp) JsonVariant *m = NULL;
+ int r;
+
+ r = json_variant_new_array_strv(&m, l);
+ if (r < 0)
+ return r;
+
+ return json_variant_set_field(v, field, m);
+}
+
int json_variant_merge(JsonVariant **v, JsonVariant *m) {
_cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
_cleanup_free_ JsonVariant **array = NULL;
@@ -3579,6 +3596,34 @@ int json_buildv(JsonVariant **ret, va_list ap) {
break;
}
+ case _JSON_BUILD_ID128: {
+ sd_id128_t id;
+
+ if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ id = va_arg(ap, sd_id128_t);
+
+ if (current->n_suppress == 0) {
+ r = json_variant_new_id128(&add, id);
+ if (r < 0)
+ goto finish;
+ }
+
+ n_subtract = 1;
+
+ if (current->expect == EXPECT_TOPLEVEL)
+ current->expect = EXPECT_END;
+ else if (current->expect == EXPECT_OBJECT_VALUE)
+ current->expect = EXPECT_OBJECT_KEY;
+ else
+ assert(current->expect == EXPECT_ARRAY_ELEMENT);
+
+ break;
+ }
+
case _JSON_BUILD_OBJECT_BEGIN:
if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
diff --git a/src/shared/json.h b/src/shared/json.h
index ceb01a2028..ae71593d7b 100644
--- a/src/shared/json.h
+++ b/src/shared/json.h
@@ -7,6 +7,8 @@
#include <stdint.h>
#include <stdio.h>
+#include "sd-id128.h"
+
#include "macro.h"
#include "string-util.h"
#include "log.h"
@@ -65,6 +67,7 @@ int json_variant_new_array_bytes(JsonVariant **ret, const void *p, size_t n);
int json_variant_new_array_strv(JsonVariant **ret, char **l);
int json_variant_new_object(JsonVariant **ret, JsonVariant **array, size_t n);
int json_variant_new_null(JsonVariant **ret);
+int json_variant_new_id128(JsonVariant **ret, sd_id128_t id);
static inline int json_variant_new_string(JsonVariant **ret, const char *s) {
return json_variant_new_stringn(ret, s, (size_t) -1);
@@ -183,6 +186,7 @@ int json_variant_set_field_string(JsonVariant **v, const char *field, const char
int json_variant_set_field_integer(JsonVariant **v, const char *field, intmax_t value);
int json_variant_set_field_unsigned(JsonVariant **v, const char *field, uintmax_t value);
int json_variant_set_field_boolean(JsonVariant **v, const char *field, bool b);
+int json_variant_set_field_strv(JsonVariant **v, const char *field, char **l);
int json_variant_append_array(JsonVariant **v, JsonVariant *element);
@@ -223,6 +227,7 @@ enum {
_JSON_BUILD_LITERAL,
_JSON_BUILD_STRV,
_JSON_BUILD_BASE64,
+ _JSON_BUILD_ID128,
_JSON_BUILD_MAX,
};
@@ -243,6 +248,7 @@ enum {
#define JSON_BUILD_LITERAL(l) _JSON_BUILD_LITERAL, ({ const char *_x = l; _x; })
#define JSON_BUILD_STRV(l) _JSON_BUILD_STRV, ({ char **_x = l; _x; })
#define JSON_BUILD_BASE64(p, n) _JSON_BUILD_BASE64, ({ const void *_x = p; _x; }), ({ size_t _y = n; _y; })
+#define JSON_BUILD_ID128(id) _JSON_BUILD_ID128, ({ sd_id128_t _x = id; _x; })
int json_build(JsonVariant **ret, ...);
int json_buildv(JsonVariant **ret, va_list ap);
diff --git a/test/units/testsuite-50.sh b/test/units/testsuite-50.sh
index 587184e854..bb3b20da3e 100755
--- a/test/units/testsuite-50.sh
+++ b/test/units/testsuite-50.sh
@@ -28,25 +28,25 @@ cp /usr/share/minimal.* "${image_dir}/"
image="${image_dir}/minimal"
roothash="$(cat ${image}.roothash)"
-/usr/lib/systemd/systemd-dissect ${image}.raw | grep -q -F "Found read-only 'root' partition of type squashfs with verity"
-/usr/lib/systemd/systemd-dissect ${image}.raw | grep -q -F "MARKER=1"
-/usr/lib/systemd/systemd-dissect ${image}.raw | grep -q -F -f /usr/lib/os-release
+systemd-dissect --json=short ${image}.raw | grep -q -F '{"rw":"ro","designator":"root","partition_uuid":null,"fstype":"squashfs","architecture":null,"verity":"external"'
+systemd-dissect ${image}.raw | grep -q -F "MARKER=1"
+systemd-dissect ${image}.raw | grep -q -F -f /usr/lib/os-release
mv ${image}.verity ${image}.fooverity
mv ${image}.roothash ${image}.foohash
-/usr/lib/systemd/systemd-dissect ${image}.raw --root-hash=${roothash} --verity-data=${image}.fooverity | grep -q -F "Found read-only 'root' partition of type squashfs with verity"
-/usr/lib/systemd/systemd-dissect ${image}.raw --root-hash=${roothash} --verity-data=${image}.fooverity | grep -q -F "MARKER=1"
-/usr/lib/systemd/systemd-dissect ${image}.raw --root-hash=${roothash} --verity-data=${image}.fooverity | grep -q -F -f /usr/lib/os-release
+systemd-dissect --json=short ${image}.raw --root-hash=${roothash} --verity-data=${image}.fooverity | grep -q -F '{"rw":"ro","designator":"root","partition_uuid":null,"fstype":"squashfs","architecture":null,"verity":"external"'
+systemd-dissect ${image}.raw --root-hash=${roothash} --verity-data=${image}.fooverity | grep -q -F "MARKER=1"
+systemd-dissect ${image}.raw --root-hash=${roothash} --verity-data=${image}.fooverity | grep -q -F -f /usr/lib/os-release
mv ${image}.fooverity ${image}.verity
mv ${image}.foohash ${image}.roothash
mkdir -p ${image_dir}/mount ${image_dir}/mount2
-/usr/lib/systemd/systemd-dissect --mount ${image}.raw ${image_dir}/mount
+systemd-dissect --mount ${image}.raw ${image_dir}/mount
cat ${image_dir}/mount/usr/lib/os-release | grep -q -F -f /usr/lib/os-release
cat ${image_dir}/mount/etc/os-release | grep -q -F -f /usr/lib/os-release
cat ${image_dir}/mount/usr/lib/os-release | grep -q -F "MARKER=1"
# Verity volume should be shared (opened only once)
-/usr/lib/systemd/systemd-dissect --mount ${image}.raw ${image_dir}/mount2
+systemd-dissect --mount ${image}.raw ${image_dir}/mount2
verity_count=$(ls -1 /dev/mapper/ | grep -c verity)
# In theory we should check that count is exactly one. In practice, libdevmapper
# randomly and unpredictably fails with an unhelpful EINVAL when a device is open
@@ -111,12 +111,16 @@ dd if=${image}.raw of=${loop}p1
dd if=${image}.verity of=${loop}p2
losetup -d ${loop}
-/usr/lib/systemd/systemd-dissect --root-hash ${roothash} ${image}.gpt | grep -q "Found read-only 'root' partition (UUID $(head -c 32 ${image}.roothash)) of type squashfs for .* with verity on partition #1"
-/usr/lib/systemd/systemd-dissect --root-hash ${roothash} ${image}.gpt | grep -q "Found read-only 'root-verity' partition (UUID $(tail -c 32 ${image}.roothash)) of type DM_verity_hash for .* on partition #2"
-/usr/lib/systemd/systemd-dissect --root-hash ${roothash} ${image}.gpt | grep -q -F "MARKER=1"
-/usr/lib/systemd/systemd-dissect --root-hash ${roothash} ${image}.gpt | grep -q -F -f /usr/lib/os-release
+# Derive partition UUIDs from root hash, in UUID syntax
+ROOT_UUID=$(systemd-id128 -u show $(head -c 32 ${image}.roothash) -u | tail -n 1 | cut -b 6-)
+VERITY_UUID=$(systemd-id128 -u show $(tail -c 32 ${image}.roothash) -u | tail -n 1 | cut -b 6-)
-/usr/lib/systemd/systemd-dissect --root-hash ${roothash} --mount ${image}.gpt ${image_dir}/mount
+systemd-dissect --json=short --root-hash ${roothash} ${image}.gpt | grep -q '{"rw":"ro","designator":"root","partition_uuid":"'$ROOT_UUID'","fstype":"squashfs","architecture":"x86-64","verity":"yes","node":'
+systemd-dissect --json=short --root-hash ${roothash} ${image}.gpt | grep -q '{"rw":"ro","designator":"root-verity","partition_uuid":"'$VERITY_UUID'","fstype":"DM_verity_hash","architecture":"x86-64","verity":null,"node":'
+systemd-dissect --root-hash ${roothash} ${image}.gpt | grep -q -F "MARKER=1"
+systemd-dissect --root-hash ${roothash} ${image}.gpt | grep -q -F -f /usr/lib/os-release
+
+systemd-dissect --root-hash ${roothash} --mount ${image}.gpt ${image_dir}/mount
cat ${image_dir}/mount/usr/lib/os-release | grep -q -F -f /usr/lib/os-release
cat ${image_dir}/mount/etc/os-release | grep -q -F -f /usr/lib/os-release
cat ${image_dir}/mount/usr/lib/os-release | grep -q -F "MARKER=1"