summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaan De Meyer <daan.j.demeyer@gmail.com>2022-09-19 16:58:20 +0200
committerDaan De Meyer <daan.j.demeyer@gmail.com>2022-09-22 15:10:03 +0200
commit4cee83331c3071925b1b8d70dea8d365275a2c8e (patch)
treeb9b76e731b5990c4251ca1be01b72ca2cbaddd2b
parent1e58a0a82ca309112016dfa8793f3c37a31d93e9 (diff)
downloadsystemd-4cee83331c3071925b1b8d70dea8d365275a2c8e.tar.gz
repart: Add --split option to generate split artifacts
For use with sysupdate or other systemd tooling, it's useful to be able to generate split artifacts from disk images, where each partition is written to a separate file. Let's support this with a --split switch for repart and a SplitName= configuration option. --split enables split artifacts generation, and SplitName= configures for which partition to generate split artifacts, and which suffix to add to the split artifact name. For SplitName=, we add support for some extra specifiers, more specifically the partition Type UUID and the partition UUID.
-rw-r--r--man/repart.d.xml53
-rw-r--r--man/systemd-repart.xml15
-rw-r--r--src/partition/repart.c224
3 files changed, 268 insertions, 24 deletions
diff --git a/man/repart.d.xml b/man/repart.d.xml
index df5338c92a..280ce6b8ea 100644
--- a/man/repart.d.xml
+++ b/man/repart.d.xml
@@ -669,6 +669,15 @@
all partition types that support it, except if the partition is marked read-only (and thus
effectively, defaults to off for Verity partitions).</para></listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><varname>SplitName=</varname></term>
+
+ <listitem><para>Configures the suffix to append to split artifacts when the <option>--split</option>
+ option of <command>systemd-repart</command> is used. Simple specifier expansion is supported, see
+ below. Defaults to <literal>%t</literal>. To disable split artifact generation for a partition, set
+ <varname>SplitName=</varname> to <literal>-</literal>.</para></listitem>
+ </varlistentry>
</variablelist>
</refsect1>
@@ -676,8 +685,8 @@
<title>Specifiers</title>
<para>Specifiers may be used in the <varname>Label=</varname>, <varname>CopyBlocks=</varname>,
- <varname>CopyFiles=</varname>, <varname>MakeDirectories=</varname> settings. The following expansions are
- understood:</para>
+ <varname>CopyFiles=</varname>, <varname>MakeDirectories=</varname>, <varname>SplitName=</varname>
+ settings. The following expansions are understood:</para>
<table class='specifiers'>
<title>Specifiers available</title>
<tgroup cols='3' align='left' colsep='1' rowsep='1'>
@@ -710,6 +719,46 @@
</tbody>
</tgroup>
</table>
+
+ <para>Additionally, for the <varname>SplitName=</varname> setting, the following specifiers are also
+ understood:</para>
+ <table class='specifiers'>
+ <title>Specifiers available</title>
+ <tgroup cols='3' align='left' colsep='1' rowsep='1'>
+ <colspec colname="spec" />
+ <colspec colname="mean" />
+ <colspec colname="detail" />
+ <thead>
+ <row>
+ <entry>Specifier</entry>
+ <entry>Meaning</entry>
+ <entry>Details</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row id='T'>
+ <entry><literal>%T</literal></entry>
+ <entry>Partition Type UUID</entry>
+ <entry>The partition type UUID, as configured with <varname>Type=</varname></entry>
+ </row>
+ <row id='t'>
+ <entry><literal>%t</literal></entry>
+ <entry>Partition Type Identifier</entry>
+ <entry>The partition type identifier corresponding to the partition type UUID</entry>
+ </row>
+ <row id='U'>
+ <entry><literal>%U</literal></entry>
+ <entry>Partition UUID</entry>
+ <entry>The partition UUID, as configured with <varname>UUID=</varname></entry>
+ </row>
+ <row id='n'>
+ <entry><literal>%n</literal></entry>
+ <entry>Partition Number</entry>
+ <entry>The partition number assigned to the partition</entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
</refsect1>
<refsect1>
diff --git a/man/systemd-repart.xml b/man/systemd-repart.xml
index 236058b74c..6a7aa21c66 100644
--- a/man/systemd-repart.xml
+++ b/man/systemd-repart.xml
@@ -332,6 +332,21 @@
for details on these two options.</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--split=</option><arg>BOOL</arg></term>
+
+ <listitem><para>Enables generation of split artifacts from partitions configured with
+ <varname>SplitName=</varname>. If enabled, for each partition with <varname>SplitName=</varname> set,
+ a separate output file containing just the contents of that partition is generated. The output
+ filename consists of the loopback filename suffixed with the name configured with
+ <varname>SplitName=</varname>. If the loopback filename ends with <literal>.raw</literal>, the suffix
+ is inserted before the <literal>.raw</literal> extension instead.</para>
+
+ <para>Note that <option>--split</option> is independent from <option>--dry-run</option>. Even if
+ <option>--dry-run</option> is enabled, split artifacts will still be generated from an existing image
+ if <option>--split</option> is enabled.</para></listitem>
+ </varlistentry>
+
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
<xi:include href="standard-options.xml" xpointer="no-pager" />
diff --git a/src/partition/repart.c b/src/partition/repart.c
index 413d136fe9..5b9d142e40 100644
--- a/src/partition/repart.c
+++ b/src/partition/repart.c
@@ -117,6 +117,7 @@ static char *arg_tpm2_device = NULL;
static uint32_t arg_tpm2_pcr_mask = UINT32_MAX;
static char *arg_tpm2_public_key = NULL;
static uint32_t arg_tpm2_public_key_pcr_mask = UINT32_MAX;
+static bool arg_split = false;
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
@@ -195,6 +196,9 @@ struct Partition {
uint8_t *roothash;
size_t roothash_size;
+ char *split_name_format;
+ char *split_name_resolved;
+
Partition *siblings[_VERITY_MODE_MAX];
LIST_FIELDS(Partition, partitions);
@@ -315,6 +319,9 @@ static Partition* partition_free(Partition *p) {
free(p->roothash);
+ free(p->split_name_format);
+ free(p->split_name_resolved);
+
return mfree(p);
}
@@ -1449,28 +1456,29 @@ static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_verity, verity_mode, V
static int partition_read_definition(Partition *p, const char *path, const char *const *conf_file_dirs) {
ConfigTableItem table[] = {
- { "Partition", "Type", config_parse_type, 0, &p->type_uuid },
- { "Partition", "Label", config_parse_label, 0, &p->new_label },
- { "Partition", "UUID", config_parse_uuid, 0, p },
- { "Partition", "Priority", config_parse_int32, 0, &p->priority },
- { "Partition", "Weight", config_parse_weight, 0, &p->weight },
- { "Partition", "PaddingWeight", config_parse_weight, 0, &p->padding_weight },
- { "Partition", "SizeMinBytes", config_parse_size4096, 1, &p->size_min },
- { "Partition", "SizeMaxBytes", config_parse_size4096, -1, &p->size_max },
- { "Partition", "PaddingMinBytes", config_parse_size4096, 1, &p->padding_min },
- { "Partition", "PaddingMaxBytes", config_parse_size4096, -1, &p->padding_max },
- { "Partition", "FactoryReset", config_parse_bool, 0, &p->factory_reset },
- { "Partition", "CopyBlocks", config_parse_copy_blocks, 0, p },
- { "Partition", "Format", config_parse_fstype, 0, &p->format },
- { "Partition", "CopyFiles", config_parse_copy_files, 0, p },
- { "Partition", "MakeDirectories", config_parse_make_dirs, 0, p },
- { "Partition", "Encrypt", config_parse_encrypt, 0, &p->encrypt },
- { "Partition", "Verity", config_parse_verity, 0, &p->verity },
- { "Partition", "VerityMatchKey", config_parse_string, 0, &p->verity_match_key },
- { "Partition", "Flags", config_parse_gpt_flags, 0, &p->gpt_flags },
- { "Partition", "ReadOnly", config_parse_tristate, 0, &p->read_only },
- { "Partition", "NoAuto", config_parse_tristate, 0, &p->no_auto },
- { "Partition", "GrowFileSystem", config_parse_tristate, 0, &p->growfs },
+ { "Partition", "Type", config_parse_type, 0, &p->type_uuid },
+ { "Partition", "Label", config_parse_label, 0, &p->new_label },
+ { "Partition", "UUID", config_parse_uuid, 0, p },
+ { "Partition", "Priority", config_parse_int32, 0, &p->priority },
+ { "Partition", "Weight", config_parse_weight, 0, &p->weight },
+ { "Partition", "PaddingWeight", config_parse_weight, 0, &p->padding_weight },
+ { "Partition", "SizeMinBytes", config_parse_size4096, 1, &p->size_min },
+ { "Partition", "SizeMaxBytes", config_parse_size4096, -1, &p->size_max },
+ { "Partition", "PaddingMinBytes", config_parse_size4096, 1, &p->padding_min },
+ { "Partition", "PaddingMaxBytes", config_parse_size4096, -1, &p->padding_max },
+ { "Partition", "FactoryReset", config_parse_bool, 0, &p->factory_reset },
+ { "Partition", "CopyBlocks", config_parse_copy_blocks, 0, p },
+ { "Partition", "Format", config_parse_fstype, 0, &p->format },
+ { "Partition", "CopyFiles", config_parse_copy_files, 0, p },
+ { "Partition", "MakeDirectories", config_parse_make_dirs, 0, p },
+ { "Partition", "Encrypt", config_parse_encrypt, 0, &p->encrypt },
+ { "Partition", "Verity", config_parse_verity, 0, &p->verity },
+ { "Partition", "VerityMatchKey", config_parse_string, 0, &p->verity_match_key },
+ { "Partition", "Flags", config_parse_gpt_flags, 0, &p->gpt_flags },
+ { "Partition", "ReadOnly", config_parse_tristate, 0, &p->read_only },
+ { "Partition", "NoAuto", config_parse_tristate, 0, &p->no_auto },
+ { "Partition", "GrowFileSystem", config_parse_tristate, 0, &p->growfs },
+ { "Partition", "SplitName", config_parse_string, 0, &p->split_name_format },
{}
};
int r;
@@ -1560,6 +1568,15 @@ static int partition_read_definition(Partition *p, const char *path, const char
p->read_only <= 0)
p->growfs = true;
+ if (!p->split_name_format) {
+ char *s = strdup("%t");
+ if (!s)
+ return log_oom();
+
+ p->split_name_format = s;
+ } else if (streq(p->split_name_format, "-"))
+ p->split_name_format = mfree(p->split_name_format);
+
return 0;
}
@@ -3984,6 +4001,150 @@ static int context_mangle_partitions(Context *context) {
return 0;
}
+static int split_name_printf(Partition *p) {
+ assert(p);
+
+ const Specifier table[] = {
+ { 't', specifier_string, GPT_PARTITION_TYPE_UUID_TO_STRING_HARDER(p->type_uuid) },
+ { 'T', specifier_id128, &p->type_uuid },
+ { 'U', specifier_id128, &p->new_uuid },
+ { 'n', specifier_uint64, &p->partno },
+
+ COMMON_SYSTEM_SPECIFIERS,
+ {}
+ };
+
+ return specifier_printf(p->split_name_format, NAME_MAX, table, arg_root, p, &p->split_name_resolved);
+}
+
+static int split_name_resolve(Context *context) {
+ int r;
+
+ LIST_FOREACH(partitions, p, context->partitions) {
+ if (p->dropped)
+ continue;
+
+ if (!p->split_name_format)
+ continue;
+
+ r = split_name_printf(p);
+ if (r < 0)
+ return log_error_errno(r, "Failed to resolve specifiers in %s: %m", p->split_name_format);
+ }
+
+ LIST_FOREACH(partitions, p, context->partitions) {
+ if (!p->split_name_resolved)
+ continue;
+
+ LIST_FOREACH(partitions, q, context->partitions) {
+ if (p == q)
+ continue;
+
+ if (!q->split_name_resolved)
+ continue;
+
+ if (!streq(p->split_name_resolved, q->split_name_resolved))
+ continue;
+
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ),
+ "%s and %s have the same resolved split name \"%s\", refusing",
+ p->definition_path, q->definition_path, p->split_name_resolved);
+ }
+ }
+
+ return 0;
+}
+
+static int split_node(const char *node, char **ret_base, char **ret_ext) {
+ _cleanup_free_ char *base = NULL, *ext = NULL;
+ char *e;
+ int r;
+
+ assert(node);
+ assert(ret_base);
+ assert(ret_ext);
+
+ r = path_extract_filename(node, &base);
+ if (r == O_DIRECTORY || r == -EADDRNOTAVAIL)
+ return log_error_errno(r, "Device node %s cannot be a directory", arg_node);
+ if (r < 0)
+ return log_error_errno(r, "Failed to extract filename from %s: %m", arg_node);
+
+ e = endswith(base, ".raw");
+ if (e) {
+ ext = strdup(e);
+ if (!ext)
+ return log_oom();
+
+ *e = 0;
+ }
+
+ *ret_base = TAKE_PTR(base);
+ *ret_ext = TAKE_PTR(ext);
+
+ return 0;
+}
+
+static int context_split(Context *context) {
+ _cleanup_free_ char *base = NULL, *ext = NULL;
+ _cleanup_close_ int dir_fd = -1;
+ int fd = -1, r;
+
+ if (!arg_split)
+ return 0;
+
+ assert(context);
+ assert(arg_node);
+
+ /* We can't do resolution earlier because the partition UUIDs for verity partitions are only filled
+ * in after they've been generated. */
+
+ r = split_name_resolve(context);
+ if (r < 0)
+ return r;
+
+ r = split_node(arg_node, &base, &ext);
+ if (r < 0)
+ return r;
+
+ dir_fd = r = open_parent(arg_node, O_PATH|O_CLOEXEC, 0);
+ if (r == -EDESTADDRREQ)
+ dir_fd = AT_FDCWD;
+ else if (r < 0)
+ return log_error_errno(r, "Failed to open parent directory of %s: %m", arg_node);
+
+ LIST_FOREACH(partitions, p, context->partitions) {
+ _cleanup_free_ char *fname = NULL;
+ _cleanup_close_ int fdt = -1;
+
+ if (p->dropped)
+ continue;
+
+ if (!p->split_name_resolved)
+ continue;
+
+ fname = strjoin(base, ".", p->split_name_resolved, ext);
+ if (!fname)
+ return log_oom();
+
+ fdt = openat(dir_fd, fname, O_WRONLY|O_NOCTTY|O_CLOEXEC|O_NOFOLLOW|O_CREAT|O_EXCL, 0666);
+ if (fdt < 0)
+ return log_error_errno(errno, "Failed to open %s: %m", fname);
+
+ if (fd < 0)
+ assert_se((fd = fdisk_get_devfd(context->fdisk_context)) >= 0);
+
+ if (lseek(fd, p->offset, SEEK_SET) < 0)
+ return log_error_errno(errno, "Failed to seek to partition offset: %m");
+
+ r = copy_bytes_full(fd, fdt, p->new_size, COPY_REFLINK|COPY_HOLES, NULL, NULL, NULL, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to copy to split partition %s: %m", fname);
+ }
+
+ return 0;
+}
+
static int context_write_partition_table(
Context *context,
const char *node,
@@ -4597,6 +4758,7 @@ static int help(void) {
" --size=BYTES Grow loopback file to specified size\n"
" --json=pretty|short|off\n"
" Generate JSON output\n"
+ " --split=BOOL Whether to generate split artifacts\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
ansi_highlight(),
@@ -4629,6 +4791,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_TPM2_PCRS,
ARG_TPM2_PUBLIC_KEY,
ARG_TPM2_PUBLIC_KEY_PCRS,
+ ARG_SPLIT,
};
static const struct option options[] = {
@@ -4653,6 +4816,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS },
{ "tpm2-public-key", required_argument, NULL, ARG_TPM2_PUBLIC_KEY },
{ "tpm2-public-key-pcrs", required_argument, NULL, ARG_TPM2_PUBLIC_KEY_PCRS },
+ { "split", required_argument, NULL, ARG_SPLIT },
{}
};
@@ -4859,6 +5023,14 @@ static int parse_argv(int argc, char *argv[]) {
break;
+ case ARG_SPLIT:
+ r = parse_boolean_argument("--split=", optarg, NULL);
+ if (r < 0)
+ return r;
+
+ arg_split = r;
+ break;
+
case '?':
return -EINVAL;
@@ -4910,6 +5082,10 @@ static int parse_argv(int argc, char *argv[]) {
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"A path to a device node or loopback file must be specified when --empty=force, --empty=require or --empty=create are used.");
+ if (arg_split && !arg_node)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "A path to a loopback file must be specified when --split is used.");
+
if (arg_tpm2_pcr_mask == UINT32_MAX)
arg_tpm2_pcr_mask = TPM2_PCR_MASK_DEFAULT;
if (arg_tpm2_public_key_pcr_mask == UINT32_MAX)
@@ -5524,6 +5700,10 @@ static int run(int argc, char *argv[]) {
if (r < 0)
return r;
+ r = context_split(context);
+ if (r < 0)
+ return r;
+
(void) context_dump(context, node, /*late=*/ true);
return 0;