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.
This commit is contained in:
Lennart Poettering
2021-04-19 23:16:02 +02:00
parent 78eee6ce4d
commit d83d804863
2 changed files with 105 additions and 6 deletions
+27
View File
@@ -493,6 +493,33 @@
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>
+78 -6
View 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);