]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
repart: add high-level setting for creating dirs in formatted file systems
authorLennart Poettering <lennart@poettering.net>
Sat, 20 Mar 2021 13:05:28 +0000 (14:05 +0100)
committerLennart Poettering <lennart@poettering.net>
Mon, 19 Apr 2021 21:16:02 +0000 (23:16 +0200)
So far we already had the CopyFiles= option in systemd-repart drop-in
files, as a mechanism for populating freshly formatted file systems with
files and directories. This adds MakeDirectories= in similar style, and
creates simple directories as listed. The option is of course entirely
redundant, since the same can be done with CopyFiles= simply by copying
in a directory. It's kinda nice to encode the dirs to create directly in
the drop-in files however, instead of providing a directory subtree to
copy in somehere, to make the files more self-contained — since often
just creating dirs is entirely sufficient.

The main usecase for this are GPT OS images that carry only a /usr/
tree, and for which a root file system is only formatted on first boot
via repart.  Without any additional CopyFiles=/MakeDirectories=
configuration these root file systems are entirely empty of course
initially. To mount in the /usr/ tree, a directory inode for /usr/ to
mount over needs to be created.  systemd-nspawn will do so automatically
when booting up the image, as will the initrd during boot. However, this
requires the image to be writable – which is OK for npawn and
initrd-based boots, but there are plenty tools where read-only operation
is desirable after repart ran, before the image was booted for the first
time. Specifically, "systemd-dissect" opens the image in read-only to
inspect its contents, and this will only work of /usr/ can be properly
mounted. Moreover systemd-dissect --mount --read-only won't succeed
either if the fs is read-only.

Via MakeDirectories= we now provide a way that ensures that the image
can be mounted/inspected in a fully read-only way immediately after
systemd-repart completed. Specifically, let's consider a GPT disk image
shipping with a file usr/lib/repart.d/50-root.conf:

       [Partition]
       Type=root
       Format=btrfs
       MakeDirectories=/usr
       MakeDirectories=/efi

With this in place systemd-repart will create a root partition when run,
and add /usr and /efi into it as directory inods. This ensures that the
whole image can then be mounted truly read-only anf /usr and /efi can be
overmounted by the /usr partition and the ESP.

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

index 67f687947e4185e2a7ebc413e36136f12754be9a..43540234a7cba8b61995a14f08c929799f88a33b 100644 (file)
         specified root directory or disk image root.</para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>MakeDirectories=</varname></term>
+
+        <listitem><para>akes one or more absolute paths, separated by whitespace, each declaring a directory
+        to create within the new file system. Behaviour is similar to <varname>CopyFiles=</varname>, but
+        instead of copying in a set of files this just creates the specified directories with the default
+        mode of 0755 owned by the root user and group, plus all their parent directories (with the same
+        ownership and access mode). To configure directories with different ownership or access mode, use
+        <varname>CopyFiles=</varname> and specify a source tree to copy containing appropriately
+        owned/configured directories. This option may be used more than once to create multiple
+        directories. When <varname>CopyFiles=</varname> and <varname>MakeDirectories=</varname> are used
+        together the former is applied first. If a directory listed already exists no operation is executed
+        (in particular, the ownership/access mode of the directories is left as is).</para>
+
+        <para>The primary usecase for this option is to create a minimal set of directories that may be
+        mounted over by other partitions contained in the same disk image. For example, a disk image where
+        the root file system is formatted at first boot might want to automatically pre-create
+        <filename>/usr/</filename> in it this way, so that the <literal>usr</literal> partition may
+        over-mount it.</para>
+
+        <para>Consider using
+        <citerefentry><refentrytitle>systemd-tmpfiles</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+        with its <option>--image=</option> option to pre-create other, more complex directory hierarchies (as
+        well as other inodes) with fine-grained control of ownership, access modes and other file
+        attributes.</para></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>Encrypt=</varname></term>
 
index bbf4fbbc83b0e9ed4abb0c22b9118a1cc66e780b..b011eb889547ce0a3c4b7a782d747cd57c57ef4e 100644 (file)
@@ -162,6 +162,7 @@ struct Partition {
 
         char *format;
         char **copy_files;
+        char **make_directories;
         EncryptMode encrypt;
 
         LIST_FIELDS(Partition, partitions);
@@ -258,6 +259,7 @@ static Partition* partition_free(Partition *p) {
 
         free(p->format);
         strv_free(p->copy_files);
+        strv_free(p->make_directories);
 
         return mfree(p);
 }
@@ -1160,6 +1162,55 @@ static int config_parse_copy_files(
         return 0;
 }
 
+static int config_parse_make_dirs(
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *section,
+                unsigned section_line,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        Partition *partition = data;
+        const char *p = rvalue;
+        int r;
+
+        assert(rvalue);
+        assert(partition);
+
+        for (;;) {
+                _cleanup_free_ char *word = NULL, *d = NULL;
+
+                r = extract_first_word(&p, &word, NULL, EXTRACT_UNQUOTE);
+                if (r == -ENOMEM)
+                        return log_oom();
+                if (r < 0) {
+                        log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid syntax, ignoring: %s", rvalue);
+                        return 0;
+                }
+                if (r == 0)
+                        return 0;
+
+                r = specifier_printf(word, specifier_table, NULL, &d);
+                if (r < 0) {
+                        log_syntax(unit, LOG_WARNING, filename, line, r,
+                                   "Failed to expand specifiers in MakeDirectories= parameter, ignoring: %s", word);
+                        continue;
+                }
+
+                r = path_simplify_and_warn(d, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue);
+                if (r < 0)
+                        continue;
+
+                r = strv_consume(&partition->make_directories, TAKE_PTR(d));
+                if (r < 0)
+                        return log_oom();
+        }
+}
+
 static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_encrypt, encrypt_mode, EncryptMode, ENCRYPT_OFF, "Invalid encryption mode");
 
 static int partition_read_definition(Partition *p, const char *path) {
@@ -1179,6 +1230,7 @@ static int partition_read_definition(Partition *p, const char *path) {
                 { "Partition", "CopyBlocks",      config_parse_path,       0, &p->copy_blocks_path },
                 { "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          },
                 {}
         };
@@ -1205,15 +1257,15 @@ static int partition_read_definition(Partition *p, const char *path) {
                 return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
                                   "Type= not defined, refusing.");
 
-        if (p->copy_blocks_path && (p->format || !strv_isempty(p->copy_files)))
+        if (p->copy_blocks_path && (p->format || !strv_isempty(p->copy_files) || !strv_isempty(p->make_directories)))
                 return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
                                   "Format= and CopyBlocks= cannot be combined, refusing.");
 
-        if (!strv_isempty(p->copy_files) && streq_ptr(p->format, "swap"))
+        if ((!strv_isempty(p->copy_files) || !strv_isempty(p->make_directories)) && streq_ptr(p->format, "swap"))
                 return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
                                   "Format=swap and CopyFiles= cannot be combined, refusing.");
 
-        if (!p->format && (!strv_isempty(p->copy_files) || (p->encrypt != ENCRYPT_OFF && !p->copy_blocks_path))) {
+        if (!p->format && (!strv_isempty(p->copy_files) || !strv_isempty(p->make_directories) || (p->encrypt != ENCRYPT_OFF && !p->copy_blocks_path))) {
                 /* Pick "ext4" as file system if we are configured to copy files or encrypt the device */
                 p->format = strdup("ext4");
                 if (!p->format)
@@ -2732,13 +2784,30 @@ static int do_copy_files(Partition *p, const char *fs) {
         return 0;
 }
 
-static int partition_copy_files(Partition *p, const char *node) {
+static int do_make_directories(Partition *p, const char *fs) {
+        char **d;
+        int r;
+
+        assert(p);
+        assert(fs);
+
+        STRV_FOREACH(d, p->make_directories) {
+
+                r = mkdir_p_root(fs, *d, UID_INVALID, GID_INVALID, 0755);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to create directory '%s' in file system: %m", *d);
+        }
+
+        return 0;
+}
+
+static int partition_populate(Partition *p, const char *node) {
         int r;
 
         assert(p);
         assert(node);
 
-        if (strv_isempty(p->copy_files))
+        if (strv_isempty(p->copy_files) && strv_isempty(p->make_directories))
                 return 0;
 
         log_info("Populating partition %" PRIu64 " with files.", p->partno);
@@ -2766,6 +2835,9 @@ static int partition_copy_files(Partition *p, const char *node) {
                 if (do_copy_files(p, fs) < 0)
                         _exit(EXIT_FAILURE);
 
+                if (do_make_directories(p, fs) < 0)
+                        _exit(EXIT_FAILURE);
+
                 r = syncfs_path(AT_FDCWD, fs);
                 if (r < 0) {
                         log_error_errno(r, "Failed to synchronize written files: %m");
@@ -2855,7 +2927,7 @@ static int context_mkfs(Context *context) {
                         if (flock(encrypted_dev_fd, LOCK_UN) < 0)
                                 return log_error_errno(errno, "Failed to unlock LUKS device: %m");
 
-                r = partition_copy_files(p, fsdev);
+                r = partition_populate(p, fsdev);
                 if (r < 0) {
                         encrypted_dev_fd = safe_close(encrypted_dev_fd);
                         (void) deactivate_luks(cd, encrypted);