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:
@@ -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
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user