From: Lennart Poettering Date: Sat, 20 Mar 2021 13:05:28 +0000 (+0100) Subject: repart: add high-level setting for creating dirs in formatted file systems X-Git-Tag: v249-rc1~388^2~7 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=d83d80486326607ed3b6e3c73f1f18e9baabf3ba;p=thirdparty%2Fsystemd.git repart: add high-level setting for creating dirs in formatted file systems 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. --- diff --git a/man/repart.d.xml b/man/repart.d.xml index 67f687947e4..43540234a7c 100644 --- a/man/repart.d.xml +++ b/man/repart.d.xml @@ -493,6 +493,33 @@ specified root directory or disk image root. + + MakeDirectories= + + akes one or more absolute paths, separated by whitespace, each declaring a directory + to create within the new file system. Behaviour is similar to CopyFiles=, 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 + CopyFiles= 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 CopyFiles= and MakeDirectories= 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). + + 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 + /usr/ in it this way, so that the usr partition may + over-mount it. + + Consider using + systemd-tmpfiles8 + with its 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. + + Encrypt= diff --git a/src/partition/repart.c b/src/partition/repart.c index bbf4fbbc83b..b011eb88954 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -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);