]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
repart: Add --split option to generate split artifacts 24746/head
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Mon, 19 Sep 2022 14:58:20 +0000 (16:58 +0200)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Thu, 22 Sep 2022 13:10:03 +0000 (15:10 +0200)
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.

man/repart.d.xml
man/systemd-repart.xml
src/partition/repart.c

index df5338c92af12af70d768b7af9b8d409ab6bd203..280ce6b8eaf1c5eb2903cd69f7ffef3f735cca15 100644 (file)
         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>
 
     <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'>
           </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>
index 236058b74c3f5f682c072ac8dc9103f5f3cee7d0..6a7aa21c66e52c987f1b7b651e919550ef54cf7e 100644 (file)
         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" />
index 413d136fe9cc4c380265f34d85c11ff93c0ed395..5b9d142e4082dfd8a7767102965b314f6a50c364 100644 (file)
@@ -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;