]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
repart: Add --generate-fstab= and --generate-crypttab= options
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Mon, 25 Dec 2023 22:11:22 +0000 (23:11 +0100)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Sat, 10 Feb 2024 12:07:02 +0000 (13:07 +0100)
These can be used along with two new settings MountPoint= and
EncryptedVolume= to write fstab and crypttab entries to the given
paths respectively in the root directory that repart is operating on.

This is useful to cover scenarios that aren't covered by the
Discoverable Partitions Spec. For example when one wants to mount
/home as a separate btrfs subvolume. Because multiple btrfs subvolumes
can be mounted from the same partition, we allow specifying MountPoint=
multiple times to add multiple entries for the same partition.

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

index f297933ccec2cb1dd4bac22321185b91f7a1c621..50a2de7c33d6500901e539ebe17f1dcaa507e179 100644 (file)
 
         <xi:include href="version-info.xml" xpointer="v253"/></listitem>
       </varlistentry>
+
+      <varlistentry>
+        <term><varname>MountPoint=</varname></term>
+
+        <listitem><para>Specifies where and how the partition should be mounted. Takes at least one and at
+        most two fields separated with a colon (<literal>:</literal>). The first field specifies where the
+        partition should be mounted. The second field specifies extra mount options to append to the default
+        mount options. These fields correspond to the second and fourth column of the
+        <citerefentry project='man-pages'><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        format. This setting may be specified multiple times to mount the partition multiple times. This can
+        be used to add mounts for different btrfs subvolumes located on the same btrfs partition.</para>
+
+        <para>Note that this setting is only taken into account when <option>--generate-fstab=</option> is
+        specified on the <command>systemd-repart</command> command line.</para>
+
+        <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><varname>EncryptedVolume=</varname></term>
+
+        <listitem><para>Specify how the encrypted partition should be set up. Takes at least one and at most
+        three fields separated with a colon (<literal>:</literal>). The first field specifies the encrypted
+        volume name under <filename>/dev/mapper/</filename>. If not specified, <literal>luks-UUID</literal>
+        will be used where <literal>UUID</literal> is the LUKS UUID. The second field specifies the keyfile
+        to use following the same format as specified in crypttab. The third field specifies a
+        comma-delimited list of crypttab options. These fields correspond to the first, third and fourth
+        column of the
+        <citerefentry><refentrytitle>crypttab</refentrytitle><manvolnum>5</manvolnum></citerefentry> format.
+        </para>
+
+        <para>Note that this setting is only taken into account when <option>--generate-crypttab=</option>
+        is specified on the <command>systemd-repart</command> command line.</para>
+
+        <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+      </varlistentry>
     </variablelist>
   </refsect1>
 
index da5d5858459b3a638b333b4f0dd5484829a036e2..7dfcfc90b31c80ebf23aa0065fe4d9725d171e02 100644 (file)
         <xi:include href="version-info.xml" xpointer="v255"/></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--generate-fstab=</option><replaceable>PATH</replaceable></term>
+
+        <listitem><para>Specifies a path where to write fstab entries for the mountpoints configured with
+        <option>MountPoint=</option> in the root directory specified with <option>--copy-source=</option> or
+        <option>--root=</option> or in the host's root directory if neither is specified. Disabled by
+        default.</para>
+
+        <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--generate-crypttab=</option><replaceable>PATH</replaceable></term>
+
+        <listitem><para>Specifies a path where to write crypttab entries for the encrypted volumes configured
+        with <option>EncryptedVolume=</option> in the root directory specified with
+        <option>--copy-source=</option> or <option>--root=</option> or in the host's root directory if
+        neither is specified. Disabled by default.</para>
+
+        <xi:include href="version-info.xml" xpointer="v256"/></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 6f347a6e1faaada10ddebf6d4a5a980018f0f9b9..63e9dcfbd163247146257706d1c01efd0deb4c6d 100644 (file)
@@ -168,6 +168,8 @@ static int arg_offline = -1;
 static char **arg_copy_from = NULL;
 static char *arg_copy_source = NULL;
 static char *arg_make_ddi = NULL;
+static char *arg_generate_fstab = NULL;
+static char *arg_generate_crypttab = NULL;
 
 STATIC_DESTRUCTOR_REGISTER(arg_node, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
@@ -186,6 +188,8 @@ STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_copy_from, strv_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_copy_source, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_make_ddi, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_generate_fstab, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_generate_crypttab, freep);
 
 typedef struct FreeArea FreeArea;
 
@@ -215,6 +219,39 @@ typedef enum MinimizeMode {
         _MINIMIZE_MODE_INVALID = -EINVAL,
 } MinimizeMode;
 
+typedef struct PartitionMountPoint {
+        char *where;
+        char *options;
+} PartitionMountPoint;
+
+static void partition_mountpoint_free_many(PartitionMountPoint *f, size_t n) {
+        assert(f || n == 0);
+
+        FOREACH_ARRAY(i, f, n) {
+                free(i->where);
+                free(i->options);
+        }
+
+        free(f);
+}
+
+typedef struct PartitionEncryptedVolume {
+        char *name;
+        char *keyfile;
+        char *options;
+} PartitionEncryptedVolume;
+
+static PartitionEncryptedVolume* partition_encrypted_volume_free(PartitionEncryptedVolume *c) {
+        if (!c)
+                return NULL;
+
+        free(c->name);
+        free(c->keyfile);
+        free(c->options);
+
+        return mfree(c);
+}
+
 typedef struct Partition {
         char *definition_path;
         char **drop_in_files;
@@ -277,6 +314,11 @@ typedef struct Partition {
         char *split_name_format;
         char *split_path;
 
+        PartitionMountPoint *mountpoints;
+        size_t n_mountpoints;
+
+        PartitionEncryptedVolume *encrypted_volume;
+
         struct Partition *siblings[_VERITY_MODE_MAX];
 
         LIST_FIELDS(struct Partition, partitions);
@@ -426,6 +468,12 @@ static Partition* partition_free(Partition *p) {
         free(p->split_name_format);
         unlink_and_free(p->split_path);
 
+        partition_mountpoint_free_many(p->mountpoints, p->n_mountpoints);
+        p->mountpoints = NULL;
+        p->n_mountpoints = 0;
+
+        partition_encrypted_volume_free(p->encrypted_volume);
+
         return mfree(p);
 }
 
@@ -461,6 +509,12 @@ static void partition_foreignize(Partition *p) {
         p->read_only = -1;
         p->growfs = -1;
         p->verity = VERITY_OFF;
+
+        partition_mountpoint_free_many(p->mountpoints, p->n_mountpoints);
+        p->mountpoints = NULL;
+        p->n_mountpoints = 0;
+
+        p->encrypted_volume = partition_encrypted_volume_free(p->encrypted_volume);
 }
 
 static bool partition_type_exclude(const GptPartitionType *type) {
@@ -1678,41 +1732,163 @@ static int config_parse_uuid(
         return 0;
 }
 
+static int config_parse_mountpoint(
+                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) {
+
+        _cleanup_free_ char *where = NULL, *options = NULL;
+        Partition *p = ASSERT_PTR(data);
+        int r;
+
+        if (isempty(rvalue)) {
+                partition_mountpoint_free_many(p->mountpoints, p->n_mountpoints);
+                return 0;
+        }
+
+        const char *q = rvalue;
+        r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_DONT_COALESCE_SEPARATORS|EXTRACT_UNQUOTE,
+                               &where, &options, NULL);
+        if (r == -ENOMEM)
+                return log_oom();
+        if (r < 0) {
+                log_syntax(unit, LOG_WARNING, filename, line, r,
+                           "Invalid syntax in %s=, ignoring: %s", lvalue, rvalue);
+                return 0;
+        }
+        if (r < 1) {
+                log_syntax(unit, LOG_WARNING, filename, line, SYNTHETIC_ERRNO(EINVAL),
+                           "Too few arguments in %s=, ignoring: %s", lvalue, rvalue);
+                return 0;
+        }
+        if (!isempty(q)) {
+                log_syntax(unit, LOG_WARNING, filename, line, SYNTHETIC_ERRNO(EINVAL),
+                           "Too many arguments in %s=, ignoring: %s", lvalue, rvalue);
+                return 0;
+        }
+
+        r = path_simplify_and_warn(where, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue);
+        if (r < 0)
+                return 0;
+
+        if (!GREEDY_REALLOC(p->mountpoints, p->n_mountpoints + 1))
+                return log_oom();
+
+        p->mountpoints[p->n_mountpoints++] = (PartitionMountPoint) {
+                .where = TAKE_PTR(where),
+                .options = TAKE_PTR(options),
+        };
+
+        return 0;
+}
+
+static int config_parse_encrypted_volume(
+                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) {
+
+        _cleanup_free_ char *volume = NULL, *keyfile = NULL, *options = NULL;
+        Partition *p = ASSERT_PTR(data);
+        int r;
+
+        if (isempty(rvalue)) {
+                p->encrypted_volume = mfree(p->encrypted_volume);
+                return 0;
+        }
+
+        const char *q = rvalue;
+        r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_DONT_COALESCE_SEPARATORS|EXTRACT_UNQUOTE,
+                               &volume, &keyfile, &options, NULL);
+        if (r == -ENOMEM)
+                return log_oom();
+        if (r < 0) {
+                log_syntax(unit, LOG_WARNING, filename, line, r,
+                           "Invalid syntax in %s=, ignoring: %s", lvalue, rvalue);
+                return 0;
+        }
+        if (r < 1) {
+                log_syntax(unit, LOG_WARNING, filename, line, SYNTHETIC_ERRNO(EINVAL),
+                           "Too few arguments in %s=, ignoring: %s", lvalue, rvalue);
+                return 0;
+        }
+        if (!isempty(q)) {
+                log_syntax(unit, LOG_WARNING, filename, line, SYNTHETIC_ERRNO(EINVAL),
+                           "Too many arguments in %s=, ignoring: %s", lvalue, rvalue);
+                return 0;
+        }
+
+        if (!filename_is_valid(volume)) {
+                log_syntax(unit, LOG_WARNING, filename, line, SYNTHETIC_ERRNO(EINVAL),
+                           "Volume name %s is not valid, ignoring", volume);
+                return 0;
+        }
+
+        partition_encrypted_volume_free(p->encrypted_volume);
+
+        p->encrypted_volume = new(PartitionEncryptedVolume, 1);
+        if (!p->encrypted_volume)
+                return log_oom();
+
+        *p->encrypted_volume = (PartitionEncryptedVolume) {
+                .name = TAKE_PTR(volume),
+                .keyfile = TAKE_PTR(keyfile),
+                .options = TAKE_PTR(options),
+        };
+
+        return 0;
+}
+
 static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_verity, verity_mode, VerityMode, VERITY_OFF, "Invalid verity mode");
 static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_minimize, minimize_mode, MinimizeMode, MINIMIZE_OFF, "Invalid minimize mode");
 
 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                    },
-                { "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->copy_files              },
-                { "Partition", "ExcludeFiles",             config_parse_exclude_files, 0, &p->exclude_files_source    },
-                { "Partition", "ExcludeFilesTarget",       config_parse_exclude_files, 0, &p->exclude_files_target    },
-                { "Partition", "MakeDirectories",          config_parse_make_dirs,     0, &p->make_directories        },
-                { "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       },
-                { "Partition", "Minimize",                 config_parse_minimize,      0, &p->minimize                },
-                { "Partition", "Subvolumes",               config_parse_make_dirs,     0, &p->subvolumes              },
-                { "Partition", "VerityDataBlockSizeBytes", config_parse_block_size,    0, &p->verity_data_block_size  },
-                { "Partition", "VerityHashBlockSizeBytes", config_parse_block_size,    0, &p->verity_hash_block_size  },
+                { "Partition", "Type",                     config_parse_type,             0, &p->type                    },
+                { "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->copy_files              },
+                { "Partition", "ExcludeFiles",             config_parse_exclude_files,    0, &p->exclude_files_source    },
+                { "Partition", "ExcludeFilesTarget",       config_parse_exclude_files,    0, &p->exclude_files_target    },
+                { "Partition", "MakeDirectories",          config_parse_make_dirs,        0, &p->make_directories        },
+                { "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       },
+                { "Partition", "Minimize",                 config_parse_minimize,         0, &p->minimize                },
+                { "Partition", "Subvolumes",               config_parse_make_dirs,        0, &p->subvolumes              },
+                { "Partition", "VerityDataBlockSizeBytes", config_parse_block_size,       0, &p->verity_data_block_size  },
+                { "Partition", "VerityHashBlockSizeBytes", config_parse_block_size,       0, &p->verity_hash_block_size  },
+                { "Partition", "MountPoint",               config_parse_mountpoint,       0, p                           },
+                { "Partition", "EncryptedVolume",          config_parse_encrypted_volume, 0, p                           },
                 {}
         };
         int r;
@@ -6130,6 +6306,177 @@ static int fd_apparent_size(int fd, uint64_t *ret) {
         return 0;
 }
 
+static bool need_fstab_one(const Partition *p) {
+        assert(p);
+
+        if (p->dropped)
+                return false;
+
+        if (!p->format)
+                return false;
+
+        if (p->n_mountpoints == 0)
+                return false;
+
+        return true;
+}
+
+static bool need_fstab(const Context *context) {
+        assert(context);
+
+        LIST_FOREACH(partitions, p, context->partitions)
+                if (need_fstab_one(p))
+                        return true;
+
+        return false;
+}
+
+static int context_fstab(Context *context) {
+        _cleanup_(unlink_and_freep) char *t = NULL;
+        _cleanup_fclose_ FILE *f = NULL;
+        _cleanup_free_ char *path = NULL;
+        int r;
+
+        assert(context);
+
+        if (!arg_generate_fstab)
+                return false;
+
+        if (!need_fstab(context)) {
+                log_notice("MountPoint= is not specified for any elligible partitions, not generating %s",
+                           arg_generate_fstab);
+                return 0;
+        }
+
+        path = path_join(arg_copy_source, arg_generate_fstab);
+        if (!path)
+                return log_oom();
+
+        r = fopen_tmpfile_linkable(path, O_WRONLY|O_CLOEXEC, &t, &f);
+        if (r < 0)
+                return log_error_errno(r, "Failed to open temporary file for %s: %m", path);
+
+        fprintf(f, "# Automatically generated by systemd-repart\n\n");
+
+        LIST_FOREACH(partitions, p, context->partitions) {
+                _cleanup_free_ char *what = NULL, *options = NULL;
+
+                if (!need_fstab_one(p))
+                        continue;
+
+                what = strjoin("UUID=", SD_ID128_TO_UUID_STRING(p->fs_uuid));
+                if (!what)
+                        return log_oom();
+
+                FOREACH_ARRAY(mountpoint, p->mountpoints, p->n_mountpoints) {
+                        r = partition_pick_mount_options(
+                                        p->type.designator,
+                                        p->format,
+                                        /* rw= */ true,
+                                        /* discard= */ !IN_SET(p->type.designator, PARTITION_ESP, PARTITION_XBOOTLDR),
+                                        &options,
+                                        NULL);
+                        if (r < 0)
+                                return r;
+
+                        if (!strextend_with_separator(&options, ",", mountpoint->options))
+                                return log_oom();
+
+                        fprintf(f, "%s %s %s %s 0 %i\n",
+                                what,
+                                mountpoint->where,
+                                p->format,
+                                options,
+                                p->type.designator == PARTITION_ROOT ? 1 : 2);
+                }
+        }
+
+        r = flink_tmpfile(f, t, path, 0);
+        if (r < 0)
+                return log_error_errno(r, "Failed to link temporary file to %s: %m", path);
+
+        log_info("%s written.", path);
+
+        return 0;
+}
+
+static bool need_crypttab_one(const Partition *p) {
+        assert(p);
+
+        if (p->dropped)
+                return false;
+
+        if (p->encrypt == ENCRYPT_OFF)
+                return false;
+
+        if (!p->encrypted_volume)
+                return false;
+
+        return true;
+}
+
+static bool need_crypttab(Context *context) {
+        assert(context);
+
+        LIST_FOREACH(partitions, p, context->partitions)
+                if (need_crypttab_one(p))
+                        return true;
+
+        return false;
+}
+
+static int context_crypttab(Context *context) {
+        _cleanup_(unlink_and_freep) char *t = NULL;
+        _cleanup_fclose_ FILE *f = NULL;
+        _cleanup_free_ char *path = NULL;
+        int r;
+
+        assert(context);
+
+        if (!arg_generate_crypttab)
+                return false;
+
+        if (!need_crypttab(context)) {
+                log_notice("EncryptedVolume= is not specified for any elligible partitions, not generating %s",
+                           arg_generate_crypttab);
+                return 0;
+        }
+
+        path = path_join(arg_copy_source, arg_generate_crypttab);
+        if (!path)
+                return log_oom();
+
+        r = fopen_tmpfile_linkable(path, O_WRONLY|O_CLOEXEC, &t, &f);
+        if (r < 0)
+                return log_error_errno(r, "Failed to open temporary file for %s: %m", path);
+
+        fprintf(f, "# Automatically generated by systemd-repart\n\n");
+
+        LIST_FOREACH(partitions, p, context->partitions) {
+                _cleanup_free_ char *volume = NULL;
+
+                if (!need_crypttab_one(p))
+                        continue;
+
+                if (!p->encrypted_volume->name && asprintf(&volume, "luks-%s", SD_ID128_TO_UUID_STRING(p->luks_uuid)) < 0)
+                        return log_oom();
+
+                fprintf(f, "%s UUID=%s %s %s\n",
+                        p->encrypted_volume->name ?: volume,
+                        SD_ID128_TO_UUID_STRING(p->luks_uuid),
+                        isempty(p->encrypted_volume->keyfile) ? "-" : p->encrypted_volume->keyfile,
+                        strempty(p->encrypted_volume->options));
+        }
+
+        r = flink_tmpfile(f, t, path, 0);
+        if (r < 0)
+                return log_error_errno(r, "Failed to link temporary file to %s: %m", path);
+
+        log_info("%s written.", path);
+
+        return 0;
+}
+
 static int context_minimize(Context *context) {
         const char *vt = NULL;
         int r;
@@ -6496,6 +6843,10 @@ static int help(void) {
                "  -S --make-ddi=sysext    Make a system extension DDI\n"
                "  -C --make-ddi=confext   Make a configuration extension DDI\n"
                "  -P --make-ddi=portable  Make a portable service DDI\n"
+               "     --generate-fstab=PATH\n"
+               "                          Write fstab configuration to the given path\n"
+               "     --generate-crypttab=PATH\n"
+               "                          Write crypttab configuration to the given path\n"
                "\nSee the %s for details.\n",
                program_invocation_short_name,
                ansi_highlight(),
@@ -6546,6 +6897,8 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_OFFLINE,
                 ARG_COPY_FROM,
                 ARG_MAKE_DDI,
+                ARG_GENERATE_FSTAB,
+                ARG_GENERATE_CRYPTTAB,
         };
 
         static const struct option options[] = {
@@ -6587,6 +6940,8 @@ static int parse_argv(int argc, char *argv[]) {
                 { "copy-from",            required_argument, NULL, ARG_COPY_FROM            },
                 { "copy-source",          required_argument, NULL, 's'                      },
                 { "make-ddi",             required_argument, NULL, ARG_MAKE_DDI             },
+                { "generate-fstab",       required_argument, NULL, ARG_GENERATE_FSTAB       },
+                { "generate-crypttab",    required_argument, NULL, ARG_GENERATE_CRYPTTAB    },
                 {}
         };
 
@@ -6964,6 +7319,18 @@ static int parse_argv(int argc, char *argv[]) {
                                 return r;
                         break;
 
+                case ARG_GENERATE_FSTAB:
+                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_generate_fstab);
+                        if (r < 0)
+                                return r;
+                        break;
+
+                case ARG_GENERATE_CRYPTTAB:
+                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_generate_crypttab);
+                        if (r < 0)
+                                return r;
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -7713,6 +8080,14 @@ static int run(int argc, char *argv[]) {
         if (r < 0)
                 return r;
 
+        r = context_fstab(context);
+        if (r < 0)
+                return r;
+
+        r = context_crypttab(context);
+        if (r < 0)
+                return r;
+
         r = context_minimize(context);
         if (r < 0)
                 return r;