diff --git a/TODO b/TODO
index 17fe6e8b2a..b63b436e29 100644
--- a/TODO
+++ b/TODO
@@ -234,9 +234,6 @@ Features:
* systemd-repart: allow sizing partitions as factor of available RAM, so that
we can reasonably size swap partitions for hibernation.
-* systemd-repart: allow running mkfs before making partitions pop up +
- encryption via LUKS to allow booting into an empty root with only /usr mounted in
-
* systemd-repart: allow managing the gpt read-only partition flag + auto-mount flag
* systemd-repart: allow boolean option that ensures that if existing partition
@@ -252,10 +249,6 @@ Features:
* systemd-repart: add per-partition option to fail if partition already exist,
i.e. is not added new. Similar, add option to fail if partition does not exist yet.
-* systemd-repart: add --size=auto for generating/resizing images of minimal
- size, i.e. where the image file is sized exactly as large as necessary taking
- SizeMin= into account, but not a single byte larger.
-
* systemd-repart: allow disabling growing of specific partitions, or making
them (think ESP: we don't ever want to grow it, since we cannot resize vfat)
diff --git a/man/repart.d.xml b/man/repart.d.xml
index 1b104e76d0..9714a3602a 100644
--- a/man/repart.d.xml
+++ b/man/repart.d.xml
@@ -55,11 +55,11 @@
partition slot greater than the highest slot number currently in use. Any existing partitions that have
no matching partition file are left as they are.
- Note that these partition definition files do not describe the contents of the partitions, such as
- the file system used. Separate mechanisms, such as
- systemd-growfs8 and
- systemd-makefs maybe be used to initialize or grow the file systems inside of these
- partitions.
+ Note that these definitions may only be used to created and initialize new partitions or grow
+ existing ones. In the latter case it will not grow the contained files systems however; separate
+ mechanisms, such as
+ systemd-growfs8 may be
+ used to grow the file systems inside of these partitions.
@@ -327,7 +327,72 @@
data is never overwritten. Note that the data is copied in before the partition table is updated,
i.e. before the partition actually is persistently created. This provides robustness: it is
guaranteed that the partition either doesn't exist or exists fully populated; it is not possible that
- the partition exists but is not or only partially populated.
+ the partition exists but is not or only partially populated.
+
+ This option cannot be combined with Format= or
+ CopyFiles=.
+
+
+
+ Format=
+
+ Takes a file system name, such as ext4, btrfs,
+ xfs or vfat, or the special value swap. If
+ specified and the partition is newly created it is formatted with the specified file system (or as
+ swap device). The file system UUID and label are automatically derived from the partition UUID and
+ label. If this option is used, the size allocation algorithm is slightly altered: the partition is
+ created as least as big as required for the minimal file system of the specified type (or 4KiB if the
+ minimal size is not known).
+
+ This option has no effect if the partition already exists.
+
+ Similar to the behaviour of CopyBlocks= the file system is formatted before
+ the partition is created, ensuring that the partition only ever exists with a fully initialized
+ file system.
+
+ This option cannot be combined with CopyBlocks=.
+
+
+
+ CopyFiles=
+
+ Takes a pair of colon separated absolute file system paths. The first path refers to
+ a source file or directory on the host, the second path refers to a target in the file system of the
+ newly created partition and formatted file system. This setting may be used to copy files or
+ directories from the host into the file system that is created due to the Format=
+ option. If CopyFiles= is used without Format= specified
+ explicitly, Format= with a suitable default is implied (currently
+ ext4, but this may change in the future). This option may be used multiple times
+ to copy multiple files or directories from host into the newly formatted file system. The colon and
+ second path may be omitted in which case the source path is also used as the target path (relative to
+ the root of the newly created file system). If the source path refers to a directory it is copied
+ recursively.
+
+ This option has no effect if the partition already exists: it cannot be used to copy additional
+ files into an existing partition, it may only be used to populate a file system created anew.
+
+ The copy operation is executed before the file system is registered in the partition table,
+ thus ensuring that a file system populated this way only ever exists fully initialized.
+
+ This option cannot be combined with CopyBlocks=.
+
+
+
+ Encrypt=
+
+ Takes a boolean parameter, defaulting to false. If true the partition will be
+ formatted with a LUKS2 superblock, before the blocks configured with CopyBlocks=
+ are copied in or the file system configured with Format= is created.
+
+ The LUKS2 UUID is automatically derived from the partition UUID in a stable fashion. A single
+ key is added to the LUKS2 superblock, configurable with the switch to
+ systemd-repart.
+
+ When used this slightly alters the size allocation logic as the implicit, minimal size limits
+ of Format= and CopyBlocks= are increased by the space necessary
+ for the LUKS2 superblock (see above).
+
+ This option has no effect if the partition already exists.
diff --git a/man/systemd-repart.xml b/man/systemd-repart.xml
index 6905e99b11..fb0d4c3496 100644
--- a/man/systemd-repart.xml
+++ b/man/systemd-repart.xml
@@ -202,13 +202,26 @@
- Takes a size in bytes, using the usual K, M, G, T suffixes. If used the specified
- device node path must refer to a regular file, which is then grown to the specified size if smaller,
- before any change is made to the partition table. This is not supported if the specified node is a
- block device. This switch has no effect if the file is already as large as the specified size or
- larger. The specified size is implicitly rounded up to multiples of 4096. When used with
- this specifies the initial size of the loopback file to
- create.
+ Takes a size in bytes, using the usual K, M, G, T suffixes, or the special value
+ auto. If used the specified device node path must refer to a regular file, which
+ is then grown to the specified size if smaller, before any change is made to the partition table. If
+ specified as auto the minimal size for the disk image is automatically determined
+ (i.e. the minimal sizes of all partitions are summed up, taking space for additional metadata into
+ account). This switch is not supported if the specified node is a block device. This switch has no
+ effect if the file is already as large as the specified size or larger. The specified size is
+ implicitly rounded up to multiples of 4096. When used with this
+ specifies the initial size of the loopback file to create.
+
+ The option takes the sizes of pre-existing partitions into
+ account. However, it does not accomodate for partition tables that are not tightly packed: the
+ configured partitions might still not fit into the backing device if empty space exists between
+ pre-existing partitions (or before the first partition) that cannot be fully filled by partitions to
+ grow or create.
+
+ Also note that the automatic size determination does not take files or directories specified
+ with into account: operation might fail if the specified files or
+ directories require more disk space then the configured per-partition minimal size
+ limit.
@@ -283,6 +296,18 @@
/run/repart.d/*.conf.
+
+
+
+ Takes a file system path. Configures the encryption key to use when setting up LUKS2
+ volumes configured with the Encrypt= setting in partition files. Should refer to a
+ regular file containing the key, or an AF_UNIX stream socket in the file
+ system. In the latter case a connection is made to it and the key read from it. If this switch is not
+ specified the empty key (i.e. zero length key) is used. This behaviour is useful for setting up encrypted
+ partitions during early first boot that receive their user-supplied password only in a later setup
+ step.
+
+
diff --git a/src/basic/mkdir.c b/src/basic/mkdir.c
index d591e65e41..eea8f1c9aa 100644
--- a/src/basic/mkdir.c
+++ b/src/basic/mkdir.c
@@ -5,6 +5,7 @@
#include
#include "alloc-util.h"
+#include "fd-util.h"
#include "format-util.h"
#include "fs-util.h"
#include "macro.h"
@@ -187,3 +188,54 @@ int mkdir_p(const char *path, mode_t mode) {
int mkdir_p_safe(const char *prefix, const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags) {
return mkdir_p_internal(prefix, path, mode, uid, gid, flags, mkdir_errno_wrapper);
}
+
+int mkdir_p_root(const char *root, const char *p, uid_t uid, gid_t gid, mode_t m) {
+ _cleanup_free_ char *pp = NULL;
+ _cleanup_close_ int dfd = -1;
+ const char *bn;
+ int r;
+
+ pp = dirname_malloc(p);
+ if (!pp)
+ return -ENOMEM;
+
+ /* Not top-level? */
+ if (!(path_equal(pp, "/") || isempty(pp) || path_equal(pp, "."))) {
+
+ /* Recurse up */
+ r = mkdir_p_root(root, pp, uid, gid, m);
+ if (r < 0)
+ return r;
+ }
+
+ bn = basename(p);
+ if (path_equal(bn, "/") || isempty(bn) || path_equal(bn, "."))
+ return 0;
+
+ if (!filename_is_valid(bn))
+ return -EINVAL;
+
+ dfd = chase_symlinks_and_open(pp, root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_DIRECTORY, NULL);
+ if (dfd < 0)
+ return dfd;
+
+ if (mkdirat(dfd, bn, m) < 0) {
+ if (errno == EEXIST)
+ return 0;
+
+ return -errno;
+ }
+
+ if (uid_is_valid(uid) || gid_is_valid(gid)) {
+ _cleanup_close_ int nfd = -1;
+
+ nfd = openat(dfd, bn, O_RDONLY|O_CLOEXEC|O_DIRECTORY);
+ if (nfd < 0)
+ return -errno;
+
+ if (fchown(nfd, uid, gid) < 0)
+ return -errno;
+ }
+
+ return 1;
+}
diff --git a/src/basic/mkdir.h b/src/basic/mkdir.h
index 8bfaaf405b..fc66a7d365 100644
--- a/src/basic/mkdir.h
+++ b/src/basic/mkdir.h
@@ -26,3 +26,5 @@ typedef int (*mkdir_func_t)(const char *pathname, mode_t mode);
int mkdir_safe_internal(const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags, mkdir_func_t _mkdir);
int mkdir_parents_internal(const char *prefix, const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags, mkdir_func_t _mkdir);
int mkdir_p_internal(const char *prefix, const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags, mkdir_func_t _mkdir);
+
+int mkdir_p_root(const char *root, const char *p, uid_t uid, gid_t gid, mode_t m);
diff --git a/src/basic/path-util.c b/src/basic/path-util.c
index c4e022b3a1..c463ae23ab 100644
--- a/src/basic/path-util.c
+++ b/src/basic/path-util.c
@@ -726,18 +726,6 @@ int fsck_exists(const char *fstype) {
return binary_is_good(checker);
}
-int mkfs_exists(const char *fstype) {
- const char *mkfs;
-
- assert(fstype);
-
- if (streq(fstype, "auto"))
- return -EINVAL;
-
- mkfs = strjoina("mkfs.", fstype);
- return binary_is_good(mkfs);
-}
-
int parse_path_argument_and_warn(const char *path, bool suppress_root, char **arg) {
char *p;
int r;
diff --git a/src/basic/path-util.h b/src/basic/path-util.h
index 30031fca8e..99c4e35782 100644
--- a/src/basic/path-util.h
+++ b/src/basic/path-util.h
@@ -85,7 +85,6 @@ int find_binary(const char *name, char **filename);
bool paths_check_timestamp(const char* const* paths, usec_t *paths_ts_usec, bool update);
int fsck_exists(const char *fstype);
-int mkfs_exists(const char *fstype);
/* Iterates through the path prefixes of the specified path, going up
* the tree, to root. Also returns "" (and not "/"!) for the root
diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c
index 1fcf17806e..1fe0465cfb 100644
--- a/src/home/homework-luks.c
+++ b/src/home/homework-luks.c
@@ -24,6 +24,7 @@
#include "memory-util.h"
#include "missing_magic.h"
#include "mkdir.h"
+#include "mkfs-util.h"
#include "mount-util.h"
#include "openssl-util.h"
#include "parse-util.h"
@@ -1371,71 +1372,6 @@ int home_trim_luks(UserRecord *h) {
return 0;
}
-static int run_mkfs(
- const char *node,
- const char *fstype,
- const char *label,
- sd_id128_t uuid,
- bool discard) {
-
- int r;
-
- assert(node);
- assert(fstype);
- assert(label);
-
- r = mkfs_exists(fstype);
- if (r < 0)
- return log_error_errno(r, "Failed to check if mkfs for file system %s exists: %m", fstype);
- if (r == 0)
- return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "No mkfs for file system %s installed.", fstype);
-
- r = safe_fork("(mkfs)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_LOG|FORK_WAIT|FORK_STDOUT_TO_STDERR, NULL);
- if (r < 0)
- return r;
- if (r == 0) {
- const char *mkfs;
- char suuid[37];
-
- /* Child */
-
- mkfs = strjoina("mkfs.", fstype);
- id128_to_uuid_string(uuid, suuid);
-
- if (streq(fstype, "ext4"))
- execlp(mkfs, mkfs,
- "-L", label,
- "-U", suuid,
- "-I", "256",
- "-O", "has_journal",
- "-m", "0",
- "-E", discard ? "lazy_itable_init=1,discard" : "lazy_itable_init=1,nodiscard",
- node, NULL);
- else if (streq(fstype, "btrfs")) {
- if (discard)
- execlp(mkfs, mkfs, "-L", label, "-U", suuid, node, NULL);
- else
- execlp(mkfs, mkfs, "-L", label, "-U", suuid, "--nodiscard", node, NULL);
- } else if (streq(fstype, "xfs")) {
- const char *j;
-
- j = strjoina("uuid=", suuid);
- if (discard)
- execlp(mkfs, mkfs, "-L", label, "-m", j, "-m", "reflink=1", node, NULL);
- else
- execlp(mkfs, mkfs, "-L", label, "-m", j, "-m", "reflink=1", "-K", node, NULL);
- } else {
- log_error("Cannot make file system: %s", fstype);
- _exit(EXIT_FAILURE);
- }
-
- log_error_errno(errno, "Failed to execute %s: %m", mkfs);
- _exit(EXIT_FAILURE);
- }
-
- return 0;
-}
-
static struct crypt_pbkdf_type* build_good_pbkdf(struct crypt_pbkdf_type *buffer, UserRecord *hr) {
assert(buffer);
assert(hr);
@@ -2083,7 +2019,7 @@ int home_create_luks(
log_info("Setting up LUKS device %s completed.", dm_node);
- r = run_mkfs(dm_node, fstype, user_record_user_name_and_realm(h), fs_uuid, user_record_luks_discard(h));
+ r = make_filesystem(dm_node, fstype, user_record_user_name_and_realm(h), fs_uuid, user_record_luks_discard(h));
if (r < 0)
goto fail;
diff --git a/src/partition/makefs.c b/src/partition/makefs.c
index 97f50c9033..1ce6acd01e 100644
--- a/src/partition/makefs.c
+++ b/src/partition/makefs.c
@@ -11,40 +11,15 @@
#include "dissect-image.h"
#include "fd-util.h"
#include "main-func.h"
+#include "mkfs-util.h"
#include "process-util.h"
#include "signal-util.h"
#include "string-util.h"
-static int makefs(const char *type, const char *device) {
- const char *mkfs;
- pid_t pid;
- int r;
-
- if (streq(type, "swap"))
- mkfs = "/sbin/mkswap";
- else
- mkfs = strjoina("/sbin/mkfs.", type);
- if (access(mkfs, X_OK) != 0)
- return log_error_errno(errno, "%s is not executable: %m", mkfs);
-
- r = safe_fork("(mkfs)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pid);
- if (r < 0)
- return r;
- if (r == 0) {
- const char *cmdline[3] = { mkfs, device, NULL };
-
- /* Child */
-
- execv(cmdline[0], (char**) cmdline);
- _exit(EXIT_FAILURE);
- }
-
- return wait_for_terminate_and_check(mkfs, pid, WAIT_LOG);
-}
-
static int run(int argc, char *argv[]) {
- _cleanup_free_ char *device = NULL, *type = NULL, *detected = NULL;
+ _cleanup_free_ char *device = NULL, *fstype = NULL, *detected = NULL;
_cleanup_close_ int lock_fd = -1;
+ sd_id128_t uuid;
struct stat st;
int r;
@@ -55,8 +30,8 @@ static int run(int argc, char *argv[]) {
"This program expects two arguments.");
/* type and device must be copied because makefs calls safe_fork, which clears argv[] */
- type = strdup(argv[1]);
- if (!type)
+ fstype = strdup(argv[1]);
+ if (!fstype)
return log_oom();
device = strdup(argv[2]);
@@ -85,7 +60,11 @@ static int run(int argc, char *argv[]) {
return 0;
}
- return makefs(type, device);
+ r = sd_id128_randomize(&uuid);
+ if (r < 0)
+ return log_error_errno(r, "Failed to generate UUID for file system: %m");
+
+ return make_filesystem(device, fstype, basename(device), uuid, true);
}
DEFINE_MAIN_FUNCTION(run);
diff --git a/src/partition/repart.c b/src/partition/repart.c
index 572db3bdc4..96c4e809ae 100644
--- a/src/partition/repart.c
+++ b/src/partition/repart.c
@@ -24,10 +24,12 @@
#include "btrfs-util.h"
#include "conf-files.h"
#include "conf-parser.h"
+#include "crypt-util.h"
#include "def.h"
#include "efivars.h"
#include "errno-util.h"
#include "fd-util.h"
+#include "fileio.h"
#include "format-table.h"
#include "format-util.h"
#include "fs-util.h"
@@ -36,11 +38,18 @@
#include "json.h"
#include "list.h"
#include "locale-util.h"
+#include "loop-util.h"
#include "main-func.h"
+#include "mkdir.h"
+#include "mkfs-util.h"
+#include "mount-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "pretty-print.h"
#include "proc-cmdline.h"
+#include "process-util.h"
+#include "random-util.h"
+#include "resize-fs.h"
#include "sort-util.h"
#include "specifier.h"
#include "stat-util.h"
@@ -48,6 +57,7 @@
#include "string-util.h"
#include "strv.h"
#include "terminal-util.h"
+#include "user-util.h"
#include "utf8.h"
/* If not configured otherwise use a minimal partition size of 10M */
@@ -56,6 +66,12 @@
/* Hard lower limit for new partition sizes */
#define HARD_MIN_SIZE 4096
+/* libfdisk takes off sightly more than 1M of the disk size when creating a GPT disk label */
+#define GPT_METADATA_SIZE (1044*1024)
+
+/* LUKS2 takes off 16M of the partition size with its metadata by default */
+#define LUKS2_METADATA_SIZE (16*1024*1024)
+
/* Note: When growing and placing new partitions we always align to 4K sector size. It's how newer hard disks
* are designed, and if everything is aligned to that performance is best. And for older hard disks with 512B
* sector size devices were generally assumed to have an even number of sectors, hence at the worst we'll
@@ -80,11 +96,15 @@ static sd_id128_t arg_seed = SD_ID128_NULL;
static bool arg_randomize = false;
static int arg_pretty = -1;
static uint64_t arg_size = UINT64_MAX;
+static bool arg_size_auto = false;
static bool arg_json = false;
static JsonFormatFlags arg_json_format_flags = 0;
+static void *arg_key = NULL;
+static size_t arg_key_size = 0;
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_definitions, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_key, erase_and_freep);
typedef struct Partition Partition;
typedef struct FreeArea FreeArea;
@@ -121,6 +141,10 @@ struct Partition {
int copy_blocks_fd;
uint64_t copy_blocks_size;
+ char *format;
+ char **copy_files;
+ bool encrypt;
+
LIST_FIELDS(Partition, partitions);
};
@@ -204,6 +228,9 @@ static Partition* partition_free(Partition *p) {
free(p->copy_blocks_path);
safe_close(p->copy_blocks_fd);
+ free(p->format);
+ strv_free(p->copy_files);
+
return mfree(p);
}
@@ -352,8 +379,25 @@ static uint64_t partition_min_size(const Partition *p) {
sz = p->current_size != UINT64_MAX ? p->current_size : HARD_MIN_SIZE;
- if (p->copy_blocks_size != UINT64_MAX)
- sz = MAX(p->copy_blocks_size, sz);
+ if (!PARTITION_EXISTS(p)) {
+ uint64_t d = 0;
+
+ if (p->encrypt)
+ d += round_up_size(LUKS2_METADATA_SIZE, 4096);
+
+ if (p->copy_blocks_size != UINT64_MAX)
+ d += round_up_size(p->copy_blocks_size, 4096);
+ else if (p->format || p->encrypt) {
+ uint64_t f;
+
+ /* If we shall synthesize a file system, take minimal fs size into account (assumed to be 4K if not known) */
+ f = p->format ? minimal_size_by_fs_name(p->format) : UINT64_MAX;
+ d += f == UINT64_MAX ? 4096 : f;
+ }
+
+ if (d > sz)
+ sz = d;
+ }
return MAX(p->size_min != UINT64_MAX ? p->size_min : DEFAULT_MIN_SIZE, sz);
}
@@ -867,6 +911,20 @@ static int config_parse_type(
return 0;
}
+static const Specifier specifier_table[] = {
+ { 'm', specifier_machine_id, NULL },
+ { 'b', specifier_boot_id, NULL },
+ { 'H', specifier_host_name, NULL },
+ { 'l', specifier_short_host_name, NULL },
+ { 'v', specifier_kernel_release, NULL },
+ { 'a', specifier_architecture, NULL },
+ { 'o', specifier_os_id, NULL },
+ { 'w', specifier_os_version_id, NULL },
+ { 'B', specifier_os_build_id, NULL },
+ { 'W', specifier_os_variant_id, NULL },
+ {}
+};
+
static int config_parse_label(
const char *unit,
const char *filename,
@@ -879,20 +937,6 @@ static int config_parse_label(
void *data,
void *userdata) {
- static const Specifier specifier_table[] = {
- { 'm', specifier_machine_id, NULL },
- { 'b', specifier_boot_id, NULL },
- { 'H', specifier_host_name, NULL },
- { 'l', specifier_short_host_name, NULL },
- { 'v', specifier_kernel_release, NULL },
- { 'a', specifier_architecture, NULL },
- { 'o', specifier_os_id, NULL },
- { 'w', specifier_os_version_id, NULL },
- { 'B', specifier_os_build_id, NULL },
- { 'W', specifier_os_variant_id, NULL },
- {}
- };
-
_cleanup_free_ char16_t *recoded = NULL;
_cleanup_free_ char *resolved = NULL;
char **label = data;
@@ -1000,21 +1044,120 @@ static int config_parse_size4096(
return 0;
}
+static int config_parse_fstype(
+ 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) {
+
+ char **fstype = data;
+
+ assert(rvalue);
+ assert(data);
+
+ if (!filename_is_valid(rvalue))
+ return log_syntax(unit, LOG_ERR, filename, line, 0,
+ "File system type is not valid, refusing: %s", rvalue);
+
+ return free_and_strdup_warn(fstype, rvalue);
+}
+
+static int config_parse_copy_files(
+ 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 *source = NULL, *buffer = NULL, *resolved_source = NULL, *resolved_target = NULL;
+ const char *p = rvalue, *target;
+ Partition *partition = data;
+ int r;
+
+ assert(rvalue);
+ assert(partition);
+
+ r = extract_first_word(&p, &source, ":", EXTRACT_CUNESCAPE|EXTRACT_DONT_COALESCE_SEPARATORS);
+ if (r < 0)
+ return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to extract source path: %s", rvalue);
+ if (r == 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0, "No argument specified: %s", rvalue);
+ return 0;
+ }
+
+ r = extract_first_word(&p, &buffer, ":", EXTRACT_CUNESCAPE|EXTRACT_DONT_COALESCE_SEPARATORS);
+ if (r < 0)
+ return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to extract target path: %s", rvalue);
+ if (r == 0)
+ target = source; /* No target, then it's the same as the source */
+ else
+ target = buffer;
+
+ if (!isempty(p))
+ return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL), "Too many arguments: %s", rvalue);
+
+ r = specifier_printf(source, specifier_table, NULL, &resolved_source);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to expand specifiers in CopyFiles= source, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ if (!path_is_absolute(resolved_source) || !path_is_normalized(resolved_source)) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid path name in CopyFiles= source, ignoring: %s", resolved_source);
+ return 0;
+ }
+
+ r = specifier_printf(target, specifier_table, NULL, &resolved_target);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to expand specifiers in CopyFiles= target, ignoring: %s", resolved_target);
+ return 0;
+ }
+
+ if (!path_is_absolute(resolved_target) || !path_is_normalized(resolved_target)) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid path name in CopyFiles= source, ignoring: %s", resolved_target);
+ return 0;
+ }
+
+ r = strv_consume_pair(&partition->copy_files, TAKE_PTR(resolved_source), TAKE_PTR(resolved_target));
+ if (r < 0)
+ return log_oom();
+
+ return 0;
+}
+
static int partition_read_definition(Partition *p, const char *path) {
ConfigTableItem table[] = {
- { "Partition", "Type", config_parse_type, 0, &p->type_uuid },
- { "Partition", "Label", config_parse_label, 0, &p->new_label },
- { "Partition", "UUID", config_parse_id128, 0, &p->new_uuid },
- { "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_path, 0, &p->copy_blocks_path },
+ { "Partition", "Type", config_parse_type, 0, &p->type_uuid },
+ { "Partition", "Label", config_parse_label, 0, &p->new_label },
+ { "Partition", "UUID", config_parse_id128, 0, &p->new_uuid },
+ { "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_path, 0, &p->copy_blocks_path },
+ { "Partition", "Format", config_parse_fstype, 0, &p->format },
+ { "Partition", "CopyFiles", config_parse_copy_files, 0, p },
+ { "Partition", "Encrypt", config_parse_bool, 0, &p->encrypt },
{}
};
int r;
@@ -1040,6 +1183,21 @@ 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)))
+ 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"))
+ 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 && !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)
+ return log_oom();
+ }
+
return 0;
}
@@ -1191,23 +1349,23 @@ static int fdisk_set_disklabel_id_by_uuid(struct fdisk_context *c, sd_id128_t id
return fdisk_set_ask(c, NULL, NULL);
}
-#define DISK_UUID_TOKEN "disk-uuid"
-
-static int disk_acquire_uuid(Context *context, sd_id128_t *ret) {
+static int derive_uuid(sd_id128_t base, const char *token, sd_id128_t *ret) {
union {
unsigned char md[SHA256_DIGEST_LENGTH];
sd_id128_t id;
} result;
- assert(context);
+ assert(token);
assert(ret);
- /* Calculate the HMAC-SHA256 of the string "disk-uuid", keyed off the machine ID. We use the machine
- * ID as key (and not as cleartext!) since it's the machine ID we don't want to leak. */
+ /* Derive a new UUID from the specified UUID in a stable and reasonably safe way. Specifically, we
+ * calculate the HMAC-SHA256 of the specified token string, keyed by the supplied base (typically the
+ * machine ID). We use the machine ID as key (and not as cleartext!) of the HMAC operation since it's
+ * the machine ID we don't want to leak. */
if (!HMAC(EVP_sha256(),
- &context->seed, sizeof(context->seed),
- (const unsigned char*) DISK_UUID_TOKEN, strlen(DISK_UUID_TOKEN),
+ &base, sizeof(base),
+ (const unsigned char*) token, strlen(token),
result.md, NULL))
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "HMAC-SHA256 calculation failed.");
@@ -1234,6 +1392,11 @@ static int context_load_partition_table(
assert(context);
assert(node);
assert(backing_fd);
+ assert(!context->fdisk_context);
+ assert(!context->free_areas);
+ assert(context->start == UINT64_MAX);
+ assert(context->end == UINT64_MAX);
+ assert(context->total == UINT64_MAX);
c = fdisk_new_context();
if (!c)
@@ -1249,6 +1412,24 @@ static int context_load_partition_table(
r = fdisk_assign_device(c, procfs_path, arg_dry_run);
}
+ if (r == -EINVAL && arg_size_auto) {
+ struct stat st;
+
+ /* libfdisk returns EINVAL if opening a file of size zero. Let's check for that, and accept
+ * it if automatic sizing is requested. */
+
+ if (*backing_fd < 0)
+ r = stat(node, &st);
+ else
+ r = fstat(*backing_fd, &st);
+ if (r < 0)
+ return log_error_errno(errno, "Failed to stat block device '%s': %m", node);
+
+ if (S_ISREG(st.st_mode) && st.st_size == 0)
+ return /* from_scratch = */ true;
+
+ r = -EINVAL;
+ }
if (r < 0)
return log_error_errno(r, "Failed to open device '%s': %m", node);
@@ -1304,15 +1485,11 @@ static int context_load_partition_table(
}
if (from_scratch) {
- r = fdisk_enable_wipe(c, true);
- if (r < 0)
- return log_error_errno(r, "Failed to enable wiping of disk signature: %m");
-
r = fdisk_create_disklabel(c, "gpt");
if (r < 0)
return log_error_errno(r, "Failed to create GPT disk label: %m");
- r = disk_acquire_uuid(context, &disk_uuid);
+ r = derive_uuid(context->seed, "disk-uuid", &disk_uuid);
if (r < 0)
return log_error_errno(r, "Failed to acquire disk GPT uuid: %m");
@@ -1332,7 +1509,7 @@ static int context_load_partition_table(
return log_error_errno(r, "Failed to parse current GPT disk label UUID: %m");
if (sd_id128_is_null(disk_uuid)) {
- r = disk_acquire_uuid(context, &disk_uuid);
+ r = derive_uuid(context->seed, "disk-uuid", &disk_uuid);
if (r < 0)
return log_error_errno(r, "Failed to acquire disk GPT uuid: %m");
@@ -1563,7 +1740,8 @@ static void context_unload_partition_table(Context *context) {
p->padding_area = NULL;
p->allocated_to_area = NULL;
- p->current_uuid = p->new_uuid = SD_ID128_NULL;
+ p->current_uuid = SD_ID128_NULL;
+ p->current_label = mfree(p->current_label);
}
context->start = UINT64_MAX;
@@ -1943,32 +2121,29 @@ static bool context_changed(const Context *context) {
return false;
}
-static int context_wipe_partition(Context *context, Partition *p) {
+static int context_wipe_range(Context *context, uint64_t offset, uint64_t size) {
_cleanup_(blkid_free_probep) blkid_probe probe = NULL;
int r;
assert(context);
- assert(p);
- assert(!PARTITION_EXISTS(p)); /* Safety check: never wipe existing partitions */
+ assert(offset != UINT64_MAX);
+ assert(size != UINT64_MAX);
probe = blkid_new_probe();
if (!probe)
return log_oom();
- assert(p->offset != UINT64_MAX);
- assert(p->new_size != UINT64_MAX);
-
errno = 0;
- r = blkid_probe_set_device(probe, fdisk_get_devfd(context->fdisk_context), p->offset, p->new_size);
+ r = blkid_probe_set_device(probe, fdisk_get_devfd(context->fdisk_context), offset, size);
if (r < 0)
- return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to allocate device probe for partition %" PRIu64 ".", p->partno);
+ return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to allocate device probe for wiping.");
errno = 0;
if (blkid_probe_enable_superblocks(probe, true) < 0 ||
blkid_probe_set_superblocks_flags(probe, BLKID_SUBLKS_MAGIC|BLKID_SUBLKS_BADCSUM) < 0 ||
blkid_probe_enable_partitions(probe, true) < 0 ||
blkid_probe_set_partitions_flags(probe, BLKID_PARTS_MAGIC) < 0)
- return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to enable superblock and partition probing for partition %" PRIu64 ".", p->partno);
+ return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to enable superblock and partition probing.");
for (;;) {
errno = 0;
@@ -1983,11 +2158,32 @@ static int context_wipe_partition(Context *context, Partition *p) {
return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to wipe file system signature.");
}
- log_info("Successfully wiped file system signatures from partition %" PRIu64 ".", p->partno);
return 0;
}
-static int context_discard_range(Context *context, uint64_t offset, uint64_t size) {
+static int context_wipe_partition(Context *context, Partition *p) {
+ int r;
+
+ assert(context);
+ assert(p);
+ assert(!PARTITION_EXISTS(p)); /* Safety check: never wipe existing partitions */
+
+ assert(p->offset != UINT64_MAX);
+ assert(p->new_size != UINT64_MAX);
+
+ r = context_wipe_range(context, p->offset, p->new_size);
+ if (r < 0)
+ return r;
+
+ log_info("Successfully wiped file system signatures from future partition %" PRIu64 ".", p->partno);
+ return 0;
+}
+
+static int context_discard_range(
+ Context *context,
+ uint64_t offset,
+ uint64_t size) {
+
struct stat st;
int fd;
@@ -2055,7 +2251,7 @@ static int context_discard_partition(Context *context, Partition *p) {
r = context_discard_range(context, p->offset, p->new_size);
if (r == -EOPNOTSUPP) {
- log_info("Storage does not support discarding, not discarding data in new partition %" PRIu64 ".", p->partno);
+ log_info("Storage does not support discard, not discarding data in future partition %" PRIu64 ".", p->partno);
return 0;
}
if (r == 0) {
@@ -2063,9 +2259,9 @@ static int context_discard_partition(Context *context, Partition *p) {
return 0;
}
if (r < 0)
- return log_error_errno(r, "Failed to discard data for new partition %" PRIu64 ".", p->partno);
+ return log_error_errno(r, "Failed to discard data for future partition %" PRIu64 ".", p->partno);
- log_info("Successfully discarded data from partition %" PRIu64 ".", p->partno);
+ log_info("Successfully discarded data from future partition %" PRIu64 ".", p->partno);
return 1;
}
@@ -2106,9 +2302,9 @@ static int context_discard_gap_after(Context *context, Partition *p) {
r = context_discard_range(context, gap, next - gap);
if (r == -EOPNOTSUPP) {
if (p)
- log_info("Storage does not support discarding, not discarding gap after partition %" PRIu64 ".", p->partno);
+ log_info("Storage does not support discard, not discarding gap after partition %" PRIu64 ".", p->partno);
else
- log_info("Storage does not support discarding, not discarding gap at beginning of disk.");
+ log_info("Storage does not support discard, not discarding gap at beginning of disk.");
return 0;
}
if (r == 0) /* Too short */
@@ -2143,17 +2339,15 @@ static int context_wipe_and_discard(Context *context, bool from_scratch) {
if (!p->allocated_to_area)
continue;
- if (!from_scratch) {
- r = context_discard_partition(context, p);
- if (r < 0)
- return r;
- }
-
r = context_wipe_partition(context, p);
if (r < 0)
return r;
if (!from_scratch) {
+ r = context_discard_partition(context, p);
+ if (r < 0)
+ return r;
+
r = context_discard_gap_after(context, p);
if (r < 0)
return r;
@@ -2169,16 +2363,139 @@ static int context_wipe_and_discard(Context *context, bool from_scratch) {
return 0;
}
+static int partition_encrypt(
+ Partition *p,
+ const char *node,
+ struct crypt_device **ret_cd,
+ char **ret_volume,
+ int *ret_fd) {
+
+ _cleanup_(crypt_freep) struct crypt_device *cd = NULL;
+ _cleanup_(erase_and_freep) void *volume_key = NULL;
+ _cleanup_free_ char *dm_name = NULL, *vol = NULL;
+ char suuid[ID128_UUID_STRING_MAX];
+ size_t volume_key_size = 256 / 8;
+ sd_id128_t uuid;
+ int r;
+
+ assert(p);
+ assert(p->encrypt);
+
+ if (asprintf(&dm_name, "luks-repart-%08" PRIx64, random_u64()) < 0)
+ return log_oom();
+
+ if (ret_volume) {
+ vol = path_join("/dev/mapper/", dm_name);
+ if (!vol)
+ return log_oom();
+ }
+
+ r = derive_uuid(p->new_uuid, "luks-uuid", &uuid);
+ if (r < 0)
+ return r;
+
+ log_info("Encrypting future partition %" PRIu64 "...", p->partno);
+
+ volume_key = malloc(volume_key_size);
+ if (!volume_key)
+ return log_oom();
+
+ r = genuine_random_bytes(volume_key, volume_key_size, RANDOM_BLOCK);
+ if (r < 0)
+ return log_error_errno(r, "Failed to generate volume key: %m");
+
+ r = crypt_init(&cd, node);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate libcryptsetup context: %m");
+
+ cryptsetup_enable_logging(cd);
+
+ r = crypt_format(cd,
+ CRYPT_LUKS2,
+ "aes",
+ "xts-plain64",
+ id128_to_uuid_string(uuid, suuid),
+ volume_key,
+ volume_key_size,
+ &(struct crypt_params_luks2) {
+ .label = p->new_label,
+ .sector_size = 512U,
+ });
+ if (r < 0)
+ return log_error_errno(r, "Failed to LUKS2 format future partition: %m");
+
+ r = crypt_keyslot_add_by_volume_key(
+ cd,
+ CRYPT_ANY_SLOT,
+ volume_key,
+ volume_key_size,
+ strempty(arg_key),
+ arg_key_size);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add LUKS2 key: %m");
+
+ r = crypt_activate_by_volume_key(
+ cd,
+ dm_name,
+ volume_key,
+ volume_key_size,
+ arg_discard ? CRYPT_ACTIVATE_ALLOW_DISCARDS : 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to activate LUKS superblock: %m");
+
+ log_info("Successfully encrypted future partition %" PRIu64 ".", p->partno);
+
+ if (ret_fd) {
+ _cleanup_close_ int dev_fd = -1;
+
+ dev_fd = open(vol, O_RDWR|O_CLOEXEC|O_NOCTTY);
+ if (dev_fd < 0)
+ return log_error_errno(errno, "Failed to open LUKS volume '%s': %m", vol);
+
+ *ret_fd = TAKE_FD(dev_fd);
+ }
+
+ if (ret_cd)
+ *ret_cd = TAKE_PTR(cd);
+ if (ret_volume)
+ *ret_volume = TAKE_PTR(vol);
+
+ return 0;
+}
+
+static int deactivate_luks(struct crypt_device *cd, const char *node) {
+ int r;
+
+ if (!cd)
+ return 0;
+
+ assert(node);
+
+ /* udev or so might access out block device in the background while we are done. Let's hence force
+ * detach the volume. We sync'ed before, hence this should be safe. */
+
+ r = crypt_deactivate_by_name(cd, basename(node), CRYPT_DEACTIVATE_FORCE);
+ if (r < 0)
+ return log_error_errno(r, "Failed to deactivate LUKS device: %m");
+
+ return 1;
+}
+
static int context_copy_blocks(Context *context) {
Partition *p;
- int fd = -1, r;
+ int whole_fd = -1, r;
assert(context);
/* Copy in file systems on the block level */
LIST_FOREACH(partitions, p, context->partitions) {
+ _cleanup_(crypt_freep) struct crypt_device *cd = NULL;
+ _cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
+ _cleanup_free_ char *encrypted = NULL;
+ _cleanup_close_ int encrypted_dev_fd = -1;
char buf[FORMAT_BYTES_MAX];
+ int target_fd;
if (p->copy_blocks_fd < 0)
continue;
@@ -2193,24 +2510,289 @@ static int context_copy_blocks(Context *context) {
assert(p->copy_blocks_size != UINT64_MAX);
assert(p->new_size >= p->copy_blocks_size);
- if (fd < 0)
- assert_se((fd = fdisk_get_devfd(context->fdisk_context)) >= 0);
+ if (whole_fd < 0)
+ assert_se((whole_fd = fdisk_get_devfd(context->fdisk_context)) >= 0);
- if (lseek(fd, p->offset, SEEK_SET) == (off_t) -1)
- return log_error_errno(errno, "Failed to seek to partition offset: %m");
+ if (p->encrypt) {
+ r = loop_device_make(whole_fd, O_RDWR, p->offset, p->new_size, 0, &d);
+ if (r < 0)
+ return log_error_errno(r, "Failed to make loopback device of future partition %" PRIu64 ": %m", p->partno);
- log_info("Copying in '%s' (%s) on block level into partition %" PRIu64 ".", p->copy_blocks_path, format_bytes(buf, sizeof(buf), p->copy_blocks_size), p->partno);
+ r = loop_device_flock(d, LOCK_EX);
+ if (r < 0)
+ return log_error_errno(r, "Failed to lock loopback device: %m");
- r = copy_bytes_full(p->copy_blocks_fd, fd, p->copy_blocks_size, 0, NULL, NULL, NULL, NULL);
+ r = partition_encrypt(p, d->node, &cd, &encrypted, &encrypted_dev_fd);
+ if (r < 0)
+ return log_error_errno(r, "Failed to encrypt device: %m");
+
+ if (flock(encrypted_dev_fd, LOCK_EX) < 0)
+ return log_error_errno(errno, "Failed to lock LUKS device: %m");
+
+ target_fd = encrypted_dev_fd;
+ } else {
+ if (lseek(whole_fd, p->offset, SEEK_SET) == (off_t) -1)
+ return log_error_errno(errno, "Failed to seek to partition offset: %m");
+
+ target_fd = whole_fd;
+ }
+
+ log_info("Copying in '%s' (%s) on block level into future partition %" PRIu64 ".", p->copy_blocks_path, format_bytes(buf, sizeof(buf), p->copy_blocks_size), p->partno);
+
+ r = copy_bytes_full(p->copy_blocks_fd, target_fd, p->copy_blocks_size, 0, NULL, NULL, NULL, NULL);
if (r < 0)
return log_error_errno(r, "Failed to copy in data from '%s': %m", p->copy_blocks_path);
+ if (fsync(target_fd) < 0)
+ return log_error_errno(r, "Failed to synchronize copied data blocks: %m");
+
+ if (p->encrypt) {
+ encrypted_dev_fd = safe_close(encrypted_dev_fd);
+
+ r = deactivate_luks(cd, encrypted);
+ if (r < 0)
+ return r;
+
+ crypt_free(cd);
+ cd = NULL;
+
+ r = loop_device_sync(d);
+ if (r < 0)
+ return log_error_errno(r, "Failed to sync loopback device: %m");
+ }
+
log_info("Copying in of '%s' on block level completed.", p->copy_blocks_path);
}
return 0;
}
+static int do_copy_files(Partition *p, const char *fs) {
+ char **source, **target;
+ int r;
+
+ assert(p);
+ assert(fs);
+
+ STRV_FOREACH_PAIR(source, target, p->copy_files) {
+ _cleanup_close_ int sfd = -1, pfd = -1, tfd = -1;
+ _cleanup_free_ char *dn = NULL;
+
+ dn = dirname_malloc(*target);
+ if (!dn)
+ return log_oom();
+
+ sfd = chase_symlinks_and_open(*source, arg_root, CHASE_PREFIX_ROOT|CHASE_WARN, O_CLOEXEC|O_NOCTTY, NULL);
+ if (sfd < 0)
+ return log_error_errno(sfd, "Failed to open source file '%s%s': %m", strempty(arg_root), *source);
+
+ r = fd_verify_regular(sfd);
+ if (r < 0) {
+ if (r != -EISDIR)
+ return log_error_errno(r, "Failed to check type of source file '%s': %m", *source);
+
+ /* We are looking at a directory */
+ tfd = chase_symlinks_and_open(*target, fs, CHASE_PREFIX_ROOT|CHASE_WARN, O_RDONLY|O_DIRECTORY|O_CLOEXEC, NULL);
+ if (tfd < 0) {
+ if (tfd != -ENOENT)
+ return log_error_errno(tfd, "Failed to open target directory '%s': %m", *target);
+
+ r = mkdir_p_root(fs, dn, UID_INVALID, GID_INVALID, 0755);
+ if (r < 0)
+ return log_error_errno(r, "Failed to create parent directory '%s': %m", dn);
+
+ pfd = chase_symlinks_and_open(dn, fs, CHASE_PREFIX_ROOT|CHASE_WARN, O_RDONLY|O_DIRECTORY|O_CLOEXEC, NULL);
+ if (pfd < 0)
+ return log_error_errno(pfd, "Failed to open parent directory of target: %m");
+
+ r = copy_tree_at(sfd, ".", pfd, basename(*target), UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_MERGE|COPY_REPLACE|COPY_SIGINT);
+ } else
+ r = copy_tree_at(sfd, ".", tfd, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_MERGE|COPY_REPLACE|COPY_SIGINT);
+ if (r < 0)
+ return log_error_errno(r, "Failed to copy %s%s to %s: %m", strempty(arg_root), *source, *target);
+ } else {
+ /* We are looking at a regular file */
+
+ r = mkdir_p_root(fs, dn, UID_INVALID, GID_INVALID, 0755);
+ if (r < 0)
+ return log_error_errno(r, "Failed to create parent directory: %m");
+
+ pfd = chase_symlinks_and_open(dn, fs, CHASE_PREFIX_ROOT|CHASE_WARN, O_RDONLY|O_DIRECTORY|O_CLOEXEC, NULL);
+ if (pfd < 0)
+ return log_error_errno(tfd, "Failed to open parent directory of target: %m");
+
+ tfd = openat(pfd, basename(*target), O_CREAT|O_EXCL|O_WRONLY|O_CLOEXEC, 0700);
+ if (tfd < 0)
+ return log_error_errno(errno, "Failed to create target file '%s': %m", *target);
+
+ r = copy_bytes(sfd, tfd, UINT64_MAX, COPY_REFLINK|COPY_SIGINT);
+ if (r < 0)
+ return log_error_errno(r, "Failed to copy '%s%s' to '%s': %m", strempty(arg_root), *source, *target);
+
+ (void) copy_xattr(sfd, tfd);
+ (void) copy_access(sfd, tfd);
+ (void) copy_times(sfd, tfd, 0);
+ }
+ }
+
+ return 0;
+}
+
+static int partition_copy_files(Partition *p, const char *node) {
+ int r;
+
+ assert(p);
+ assert(node);
+
+ if (strv_isempty(p->copy_files))
+ return 0;
+
+ log_info("Populating partition %" PRIu64 " with files.", p->partno);
+
+ /* We copy in a child process, since we have to mount the fs for that, and we don't want that fs to
+ * appear in the host namespace. Hence we fork a child that has its own file system namespace and
+ * detached mount propagation. */
+
+ r = safe_fork("(sd-copy)", FORK_DEATHSIG|FORK_LOG|FORK_WAIT|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE, NULL);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ static const char fs[] = "/run/systemd/mount-root";
+ /* This is a child process with its own mount namespace and propagation to host turned off */
+
+ r = mkdir_p(fs, 0700);
+ if (r < 0) {
+ log_error_errno(r, "Failed to create mount point: %m");
+ _exit(EXIT_FAILURE);
+ }
+
+ if (mount_verbose(LOG_ERR, node, fs, p->format, MS_NOATIME|MS_NODEV|MS_NOEXEC|MS_NOSUID, NULL) < 0)
+ _exit(EXIT_FAILURE);
+
+ if (do_copy_files(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");
+ _exit(EXIT_FAILURE);
+ }
+
+ _exit(EXIT_SUCCESS);
+ }
+
+ log_info("Successfully populated partition %" PRIu64 " with files.", p->partno);
+ return 0;
+}
+
+static int context_mkfs(Context *context) {
+ Partition *p;
+ int fd = -1, r;
+
+ assert(context);
+
+ /* Make a file system */
+
+ LIST_FOREACH(partitions, p, context->partitions) {
+ _cleanup_(crypt_freep) struct crypt_device *cd = NULL;
+ _cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
+ _cleanup_free_ char *encrypted = NULL;
+ _cleanup_close_ int encrypted_dev_fd = -1;
+ const char *fsdev;
+ sd_id128_t fs_uuid;
+
+ if (p->dropped)
+ continue;
+
+ if (PARTITION_EXISTS(p)) /* Never format existing partitions */
+ continue;
+
+ if (!p->format)
+ continue;
+
+ assert(p->offset != UINT64_MAX);
+ assert(p->new_size != UINT64_MAX);
+
+ if (fd < 0)
+ assert_se((fd = fdisk_get_devfd(context->fdisk_context)) >= 0);
+
+ /* Loopback block devices are not only useful to turn regular files into block devices, but
+ * also to cut out sections of block devices into new block devices. */
+
+ r = loop_device_make(fd, O_RDWR, p->offset, p->new_size, 0, &d);
+ if (r < 0)
+ return log_error_errno(r, "Failed to make loopback device of future partition %" PRIu64 ": %m", p->partno);
+
+ r = loop_device_flock(d, LOCK_EX);
+ if (r < 0)
+ return log_error_errno(r, "Failed to lock loopback device: %m");
+
+ if (p->encrypt) {
+ r = partition_encrypt(p, d->node, &cd, &encrypted, &encrypted_dev_fd);
+ if (r < 0)
+ return log_error_errno(r, "Failed to encrypt device: %m");
+
+ if (flock(encrypted_dev_fd, LOCK_EX) < 0)
+ return log_error_errno(errno, "Failed to lock LUKS device: %m");
+
+ fsdev = encrypted;
+ } else
+ fsdev = d->node;
+
+ log_info("Formatting future partition %" PRIu64 ".", p->partno);
+
+ /* Calculate the UUID for the file system as HMAC-SHA256 of the string "file-system-uuid",
+ * keyed off the partition UUID. */
+ r = derive_uuid(p->new_uuid, "file-system-uuid", &fs_uuid);
+ if (r < 0)
+ return r;
+
+ r = make_filesystem(fsdev, p->format, p->new_label, fs_uuid, arg_discard);
+ if (r < 0) {
+ encrypted_dev_fd = safe_close(encrypted_dev_fd);
+ (void) deactivate_luks(cd, encrypted);
+ return r;
+ }
+
+ log_info("Successfully formatted future partition %" PRIu64 ".", p->partno);
+
+ /* The file system is now created, no need to delay udev further */
+ if (p->encrypt)
+ 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);
+ if (r < 0) {
+ encrypted_dev_fd = safe_close(encrypted_dev_fd);
+ (void) deactivate_luks(cd, encrypted);
+ return r;
+ }
+
+ /* Note that we always sync explicitly here, since mkfs.fat doesn't do that on its own, and
+ * if we don't sync before detaching a block device the in-flight sectors possibly won't hit
+ * the disk. */
+
+ if (p->encrypt) {
+ if (fsync(encrypted_dev_fd) < 0)
+ return log_error_errno(r, "Failed to synchronize LUKS volume: %m");
+ encrypted_dev_fd = safe_close(encrypted_dev_fd);
+
+ r = deactivate_luks(cd, encrypted);
+ if (r < 0)
+ return r;
+
+ crypt_free(cd);
+ cd = NULL;
+ }
+
+ r = loop_device_sync(d);
+ if (r < 0)
+ return log_error_errno(r, "Failed to sync loopback device: %m");
+ }
+
+ return 0;
+}
+
static int partition_acquire_uuid(Context *context, Partition *p, sd_id128_t *ret) {
struct {
sd_id128_t type_uuid;
@@ -2415,66 +2997,12 @@ static int device_kernel_partitions_supported(int fd) {
return FLAGS_SET(info.lo_flags, LO_FLAGS_PARTSCAN);
}
-static int context_write_partition_table(
- Context *context,
- const char *node,
- bool from_scratch) {
-
- _cleanup_(fdisk_unref_tablep) struct fdisk_table *original_table = NULL;
- int capable, r;
+static int context_mangle_partitions(Context *context) {
Partition *p;
+ int r;
assert(context);
- if (arg_pretty > 0 ||
- (arg_pretty < 0 && isatty(STDOUT_FILENO) > 0) || arg_json) {
-
- (void) context_dump_partitions(context, node);
-
- putc('\n', stdout);
-
- if (!arg_json)
- (void) context_dump_partition_bar(context, node);
- putc('\n', stdout);
- fflush(stdout);
- }
-
- if (!from_scratch && !context_changed(context)) {
- log_info("No changes.");
- return 0;
- }
-
- if (arg_dry_run) {
- log_notice("Refusing to repartition, please re-run with --dry-run=no.");
- return 0;
- }
-
- log_info("Applying changes.");
-
- if (from_scratch) {
- r = context_discard_range(context, 0, context->total);
- if (r == -EOPNOTSUPP)
- log_info("Storage does not support discarding, not discarding entire block device data.");
- else if (r < 0)
- return log_error_errno(r, "Failed to discard entire block device: %m");
- else if (r > 0)
- log_info("Discarded entire block device.");
- }
-
- r = fdisk_get_partitions(context->fdisk_context, &original_table);
- if (r < 0)
- return log_error_errno(r, "Failed to acquire partition table: %m");
-
- /* Wipe fs signatures and discard sectors where the new partitions are going to be placed and in the
- * gaps between partitions, just to be sure. */
- r = context_wipe_and_discard(context, from_scratch);
- if (r < 0)
- return r;
-
- r = context_copy_blocks(context);
- if (r < 0)
- return r;
-
LIST_FOREACH(partitions, p, context->partitions) {
if (p->dropped)
continue;
@@ -2586,7 +3114,7 @@ static int context_write_partition_table(
if (r < 0)
return log_error_errno(r, "Failed to set partition label: %m");
- log_info("Creating new partition %" PRIu64 ".", p->partno);
+ log_info("Adding new partition %" PRIu64 " to partition table.", p->partno);
r = fdisk_add_partition(context->fdisk_context, q, NULL);
if (r < 0)
@@ -2597,6 +3125,83 @@ static int context_write_partition_table(
}
}
+ return 0;
+}
+
+static int context_write_partition_table(
+ Context *context,
+ const char *node,
+ bool from_scratch) {
+
+ _cleanup_(fdisk_unref_tablep) struct fdisk_table *original_table = NULL;
+ int capable, r;
+
+ assert(context);
+
+ if (arg_pretty > 0 ||
+ (arg_pretty < 0 && isatty(STDOUT_FILENO) > 0) ||
+ arg_json) {
+
+ (void) context_dump_partitions(context, node);
+
+ putc('\n', stdout);
+
+ if (!arg_json)
+ (void) context_dump_partition_bar(context, node);
+ putc('\n', stdout);
+ fflush(stdout);
+ }
+
+ if (!from_scratch && !context_changed(context)) {
+ log_info("No changes.");
+ return 0;
+ }
+
+ if (arg_dry_run) {
+ log_notice("Refusing to repartition, please re-run with --dry-run=no.");
+ return 0;
+ }
+
+ log_info("Applying changes.");
+
+ if (from_scratch) {
+ r = context_wipe_range(context, 0, context->total);
+ if (r < 0)
+ return r;
+
+ log_info("Wiped block device.");
+
+ r = context_discard_range(context, 0, context->total);
+ if (r == -EOPNOTSUPP)
+ log_info("Storage does not support discard, not discarding entire block device data.");
+ else if (r < 0)
+ return log_error_errno(r, "Failed to discard entire block device: %m");
+ else if (r > 0)
+ log_info("Discarded entire block device.");
+ }
+
+ r = fdisk_get_partitions(context->fdisk_context, &original_table);
+ if (r < 0)
+ return log_error_errno(r, "Failed to acquire partition table: %m");
+
+ /* Wipe fs signatures and discard sectors where the new partitions are going to be placed and in the
+ * gaps between partitions, just to be sure. */
+ r = context_wipe_and_discard(context, from_scratch);
+ if (r < 0)
+ return r;
+
+ r = context_copy_blocks(context);
+ if (r < 0)
+ return r;
+
+ r = context_mkfs(context);
+ if (r < 0)
+ return r;
+
+ r = context_mangle_partitions(context);
+ if (r < 0)
+ return r;
+
log_info("Writing new partition table.");
r = fdisk_write_disklabel(context->fdisk_context);
@@ -2823,6 +3428,7 @@ static int help(void) {
" --can-factory-reset Test whether factory reset is defined\n"
" --root=PATH Operate relative to root path\n"
" --definitions=DIR Find partitions in specified directory\n"
+ " --key-file=PATH Key to use when encrypting partitions\n"
" --seed=UUID 128bit seed UUID to derive all UUIDs from\n"
" --size=BYTES Grow loopback file to specified size\n"
" --json=pretty|short|off\n"
@@ -2851,6 +3457,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_DEFINITIONS,
ARG_SIZE,
ARG_JSON,
+ ARG_KEY_FILE,
};
static const struct option options[] = {
@@ -2867,6 +3474,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "definitions", required_argument, NULL, ARG_DEFINITIONS },
{ "size", required_argument, NULL, ARG_SIZE },
{ "json", required_argument, NULL, ARG_JSON },
+ { "key-file", required_argument, NULL, ARG_KEY_FILE },
{}
};
@@ -2973,6 +3581,12 @@ static int parse_argv(int argc, char *argv[]) {
case ARG_SIZE: {
uint64_t parsed, rounded;
+ if (streq(optarg, "auto")) {
+ arg_size = UINT64_MAX;
+ arg_size_auto = true;
+ break;
+ }
+
r = parse_size(optarg, 1024, &parsed);
if (r < 0)
return log_error_errno(r, "Failed to parse --size= parameter: %s", optarg);
@@ -2988,8 +3602,10 @@ static int parse_argv(int argc, char *argv[]) {
parsed, rounded);
arg_size = rounded;
+ arg_size_auto = false;
break;
}
+
case ARG_JSON:
if (streq(optarg, "pretty")) {
arg_json = true;
@@ -3010,6 +3626,19 @@ static int parse_argv(int argc, char *argv[]) {
break;
+ case ARG_KEY_FILE: {
+ _cleanup_(erase_and_freep) char *k = NULL;
+ size_t n = 0;
+
+ r = read_full_file_full(AT_FDCWD, optarg, READ_FULL_FILE_SECURE|READ_FULL_FILE_CONNECT_SOCKET, &k, &n);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read key file '%s': %m", optarg);
+
+ erase_and_free(arg_key);
+ arg_key = TAKE_PTR(k);
+ arg_key_size = n;
+ break;
+ }
case '?':
return -EINVAL;
@@ -3033,7 +3662,7 @@ static int parse_argv(int argc, char *argv[]) {
else if (dry_run >= 0)
arg_dry_run = dry_run;
- if (arg_empty == EMPTY_CREATE && arg_size == UINT64_MAX)
+ if (arg_empty == EMPTY_CREATE && (arg_size == UINT64_MAX && !arg_size_auto))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"If --empty=create is specified, --size= must be specified, too.");
@@ -3201,7 +3830,7 @@ static int find_root(char **ret, int *ret_fd) {
r = acquire_root_devno(arg_node, O_RDONLY|O_CLOEXEC, ret, ret_fd);
if (r < 0)
- return log_error_errno(r, "Failed to determine backing device of %s: %m", arg_node);
+ return log_error_errno(r, "Failed to open file or determine backing device of %s: %m", arg_node);
return 0;
}
@@ -3309,6 +3938,35 @@ static int resize_backing_fd(const char *node, int *fd) {
return 1;
}
+static int determine_auto_size(Context *c) {
+ uint64_t sum = round_up_size(GPT_METADATA_SIZE, 4096);
+ char buf[FORMAT_BYTES_MAX];
+ Partition *p;
+
+ assert_se(c);
+ assert_se(arg_size == UINT64_MAX);
+ assert_se(arg_size_auto);
+
+ LIST_FOREACH(partitions, p, c->partitions) {
+ uint64_t m;
+
+ if (p->dropped)
+ continue;
+
+ m = partition_min_size_with_padding(p);
+ if (m > UINT64_MAX - sum)
+ return log_error_errno(SYNTHETIC_ERRNO(EOVERFLOW), "Image would grow too large, refusing.");
+
+ sum += m;
+ }
+
+ assert_se(format_bytes(buf, sizeof(buf), sum));
+ log_info("Automatically determined minimal disk image size as %s.", buf);
+
+ arg_size = sum;
+ return 0;
+}
+
static int run(int argc, char *argv[]) {
_cleanup_(context_freep) Context* context = NULL;
_cleanup_free_ char *node = NULL;
@@ -3410,6 +4068,24 @@ static int run(int argc, char *argv[]) {
if (r < 0)
return r;
+ if (arg_size_auto) {
+ r = determine_auto_size(context);
+ if (r < 0)
+ return r;
+
+ /* Flush out everything again, and let's grow the file first, then start fresh */
+ context_unload_partition_table(context);
+
+ assert_se(arg_size != UINT64_MAX);
+ r = resize_backing_fd(node, &backing_fd);
+ if (r < 0)
+ return r;
+
+ r = context_load_partition_table(context, node, &backing_fd);
+ if (r < 0)
+ return r;
+ }
+
/* First try to fit new partitions in, dropping by priority until it fits */
for (;;) {
if (context_allocate_partitions(context))
diff --git a/src/partition/test-repart.sh b/src/partition/test-repart.sh
index 730f9cc9c3..8b1ec6c4c1 100755
--- a/src/partition/test-repart.sh
+++ b/src/partition/test-repart.sh
@@ -154,6 +154,57 @@ EOF
cmp --bytes=41943040 --ignore-initial=0:$((512*4194264)) $D/block-copy $D/zzz
+if [ `id -u` == 0 ] && type -P cryptsetup diff losetup > /dev/null ; then
+ echo "### Testing Format=/Encrypt=/CopyFiles="
+
+ # These tests require privileges unfortunately
+
+ cat >$D/definitions/extra3.conf <$D/populated5
+
+ cmp $D/populated5 - < /dev/null
+ umount $D/mount
+ cryptsetup close $VOLUME
+ losetup -d $LOOP
+else
+ echo "### Skipping Format=/Encrypt=/CopyFiles= test, lacking privileges or missing cryptsetup/diff/losetup"
+fi
+
echo "### Testing json output ###"
$repart $D/zzz --size=3G --dry-run=no --seed=$SEED --definitions=$D/definitions --json=help
$repart $D/zzz --size=3G --dry-run=no --seed=$SEED --definitions=$D/definitions --json=pretty
diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c
index d573b2feec..00bb5da55f 100644
--- a/src/shared/dissect-image.c
+++ b/src/shared/dissect-image.c
@@ -1017,6 +1017,13 @@ static int mount_partition(
}
if (directory) {
+ if (!FLAGS_SET(flags, DISSECT_IMAGE_READ_ONLY)) {
+ /* Automatically create missing mount points, if necessary. */
+ r = mkdir_p_root(where, directory, uid_shift, (gid_t) uid_shift, 0755);
+ if (r < 0)
+ return r;
+ }
+
r = chase_symlinks(directory, where, CHASE_PREFIX_ROOT, &chased, NULL);
if (r < 0)
return r;
@@ -1062,7 +1069,7 @@ static int mount_partition(
}
int dissected_image_mount(DissectedImage *m, const char *where, uid_t uid_shift, DissectImageFlags flags) {
- int r, boot_mounted;
+ int r, xbootldr_mounted;
assert(m);
assert(where);
@@ -1116,29 +1123,47 @@ int dissected_image_mount(DissectedImage *m, const char *where, uid_t uid_shift,
if (r < 0)
return r;
- boot_mounted = mount_partition(m->partitions + PARTITION_XBOOTLDR, where, "/boot", uid_shift, flags);
- if (boot_mounted < 0)
- return boot_mounted;
+ xbootldr_mounted = mount_partition(m->partitions + PARTITION_XBOOTLDR, where, "/boot", uid_shift, flags);
+ if (xbootldr_mounted < 0)
+ return xbootldr_mounted;
if (m->partitions[PARTITION_ESP].found) {
+ int esp_done = false;
+
/* Mount the ESP to /efi if it exists. If it doesn't exist, use /boot instead, but only if it
* exists and is empty, and we didn't already mount the XBOOTLDR partition into it. */
r = chase_symlinks("/efi", where, CHASE_PREFIX_ROOT, NULL, NULL);
- if (r >= 0) {
+ if (r < 0) {
+ if (r != -ENOENT)
+ return r;
+
+ /* /efi doesn't exist. Let's see if /boot is suitable then */
+
+ if (!xbootldr_mounted) {
+ _cleanup_free_ char *p = NULL;
+
+ r = chase_symlinks("/boot", where, CHASE_PREFIX_ROOT, &p, NULL);
+ if (r < 0) {
+ if (r != -ENOENT)
+ return r;
+ } else if (dir_is_empty(p) > 0) {
+ /* It exists and is an empty directory. Let's mount the ESP there. */
+ r = mount_partition(m->partitions + PARTITION_ESP, where, "/boot", uid_shift, flags);
+ if (r < 0)
+ return r;
+
+ esp_done = true;
+ }
+ }
+ }
+
+ if (!esp_done) {
+ /* OK, let's mount the ESP now to /efi (possibly creating the dir if missing) */
+
r = mount_partition(m->partitions + PARTITION_ESP, where, "/efi", uid_shift, flags);
if (r < 0)
return r;
-
- } else if (boot_mounted <= 0) {
- _cleanup_free_ char *p = NULL;
-
- r = chase_symlinks("/boot", where, CHASE_PREFIX_ROOT, &p, NULL);
- if (r >= 0 && dir_is_empty(p) > 0) {
- r = mount_partition(m->partitions + PARTITION_ESP, where, "/boot", uid_shift, flags);
- if (r < 0)
- return r;
- }
}
}
diff --git a/src/shared/loop-util.c b/src/shared/loop-util.c
index 8db2fed66f..fcbd5a5f79 100644
--- a/src/shared/loop-util.c
+++ b/src/shared/loop-util.c
@@ -47,18 +47,42 @@ static int loop_configure(int fd, const struct loop_config *c) {
if (!ERRNO_IS_NOT_SUPPORTED(errno) && errno != EINVAL)
return -errno;
} else {
- if (!FLAGS_SET(c->info.lo_flags, LO_FLAGS_PARTSCAN))
+ bool good = true;
+
+ if (c->info.lo_sizelimit != 0) {
+ /* Kernel 5.8 vanilla doesn't properly propagate the size limit into the block
+ * device. If it's used, let's immediately check if it had the desired effect
+ * hence. And if not use classic LOOP_SET_STATUS64. */
+ uint64_t z;
+
+ if (ioctl(fd, BLKGETSIZE64, &z) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ if (z != c->info.lo_sizelimit) {
+ log_debug("LOOP_CONFIGURE is broken, doesn't honour .lo_sizelimit. Falling back to LOOP_SET_STATUS64.");
+ good = false;
+ }
+ }
+
+ if (FLAGS_SET(c->info.lo_flags, LO_FLAGS_PARTSCAN)) {
+ /* Kernel 5.8 vanilla doesn't properly propagate the partition scanning flag into the
+ * block device. Let's hence verify if things work correctly here before
+ * returning. */
+
+ r = blockdev_partscan_enabled(fd);
+ if (r < 0)
+ goto fail;
+ if (r == 0) {
+ log_debug("LOOP_CONFIGURE is broken, doesn't honour LO_FLAGS_PARTSCAN. Falling back to LOOP_SET_STATUS64.");
+ good = false;
+ }
+ }
+
+ if (good)
return 0;
- /* Kernel 5.8 vanilla doesn't properly propagate the partition scanning flag into the
- * block device. Let's hence verify if things work correctly here before returning. */
-
- r = blockdev_partscan_enabled(fd);
- if (r < 0)
- goto fail;
- if (r > 0)
- return 0; /* All is good. */
-
/* Otherwise, undo the attachment and use the old APIs */
(void) ioctl(fd, LOOP_CLR_FD);
}
@@ -472,3 +496,18 @@ int loop_device_flock(LoopDevice *d, int operation) {
return 0;
}
+
+int loop_device_sync(LoopDevice *d) {
+ assert(d);
+
+ /* We also do this implicitly in loop_device_unref(). Doing this explicitly here has the benefit that
+ * we can check the return value though. */
+
+ if (d->fd < 0)
+ return -EBADF;
+
+ if (fsync(d->fd) < 0)
+ return -errno;
+
+ return 0;
+}
diff --git a/src/shared/loop-util.h b/src/shared/loop-util.h
index 73f82a6d30..16f215b059 100644
--- a/src/shared/loop-util.h
+++ b/src/shared/loop-util.h
@@ -26,3 +26,4 @@ void loop_device_relinquish(LoopDevice *d);
int loop_device_refresh_size(LoopDevice *d, uint64_t offset, uint64_t size);
int loop_device_flock(LoopDevice *d, int operation);
+int loop_device_sync(LoopDevice *d);
diff --git a/src/shared/meson.build b/src/shared/meson.build
index ce3462b3dd..57f36af252 100644
--- a/src/shared/meson.build
+++ b/src/shared/meson.build
@@ -166,6 +166,8 @@ shared_sources = files('''
macvlan-util.c
macvlan-util.h
main-func.h
+ mkfs-util.c
+ mkfs-util.h
module-util.h
mount-util.c
mount-util.h
diff --git a/src/shared/mkfs-util.c b/src/shared/mkfs-util.c
new file mode 100644
index 0000000000..583ef90e65
--- /dev/null
+++ b/src/shared/mkfs-util.c
@@ -0,0 +1,135 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "id128-util.h"
+#include "mkfs-util.h"
+#include "path-util.h"
+#include "process-util.h"
+#include "stdio-util.h"
+#include "string-util.h"
+
+int mkfs_exists(const char *fstype) {
+ const char *mkfs;
+ int r;
+
+ assert(fstype);
+
+ if (STR_IN_SET(fstype, "auto", "swap")) /* these aren't real file system types, refuse early */
+ return -EINVAL;
+
+ mkfs = strjoina("mkfs.", fstype);
+ if (!filename_is_valid(mkfs)) /* refuse file system types with slashes and similar */
+ return -EINVAL;
+
+ r = find_binary(mkfs, NULL);
+ if (r == -ENOENT)
+ return false;
+ if (r < 0)
+ return r;
+
+ return true;
+}
+
+int make_filesystem(
+ const char *node,
+ const char *fstype,
+ const char *label,
+ sd_id128_t uuid,
+ bool discard) {
+
+ _cleanup_free_ char *mkfs = NULL;
+ int r;
+
+ assert(node);
+ assert(fstype);
+ assert(label);
+
+ if (streq(fstype, "swap")) {
+ r = find_binary("mkswap", &mkfs);
+ if (r == -ENOENT)
+ return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "mkswap binary not available.");
+ if (r < 0)
+ return log_error_errno(r, "Failed to determine whether mkswap binary exists: %m");
+ } else {
+ r = mkfs_exists(fstype);
+ if (r < 0)
+ return log_error_errno(r, "Failed to determine whether mkfs binary for %s exists: %m", fstype);
+ if (r == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "mkfs binary for %s is not available.", fstype);
+
+ mkfs = strjoin("mkfs.", fstype);
+ if (!mkfs)
+ return log_oom();
+ }
+
+ r = safe_fork("(mkfs)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_LOG|FORK_WAIT|FORK_STDOUT_TO_STDERR, NULL);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ char suuid[ID128_UUID_STRING_MAX];
+
+ /* Child */
+ id128_to_uuid_string(uuid, suuid);
+
+ if (streq(fstype, "ext4"))
+ (void) execlp(mkfs, mkfs,
+ "-L", label,
+ "-U", suuid,
+ "-I", "256",
+ "-O", "has_journal",
+ "-m", "0",
+ "-E", discard ? "lazy_itable_init=1,discard" : "lazy_itable_init=1,nodiscard",
+ node, NULL);
+
+ else if (streq(fstype, "btrfs")) {
+ if (discard)
+ (void) execlp(mkfs, mkfs, "-L", label, "-U", suuid, node, NULL);
+ else
+ (void) execlp(mkfs, mkfs, "-L", label, "-U", suuid, "--nodiscard", node, NULL);
+
+ } else if (streq(fstype, "xfs")) {
+ const char *j;
+
+ j = strjoina("uuid=", suuid);
+ if (discard)
+ (void) execlp(mkfs, mkfs, "-L", label, "-m", j, "-m", "reflink=1", node, NULL);
+ else
+ (void) execlp(mkfs, mkfs, "-L", label, "-m", j, "-m", "reflink=1", "-K", node, NULL);
+
+ } else if (streq(fstype, "vfat")) {
+ char mangled_label[8 + 3 + 1], vol_id[8 + 1];
+
+ /* Classic FAT only allows 11 character uppercase labels */
+ strncpy(mangled_label, label, sizeof(mangled_label)-1);
+ mangled_label[sizeof(mangled_label)-1] = 0;
+ ascii_strupper(mangled_label);
+
+ xsprintf(vol_id, "%08" PRIx32,
+ ((uint32_t) uuid.bytes[0] << 24) |
+ ((uint32_t) uuid.bytes[1] << 16) |
+ ((uint32_t) uuid.bytes[2] << 8) |
+ ((uint32_t) uuid.bytes[3])); /* Take first 32 byte of UUID */
+
+ (void) execlp(mkfs, mkfs,
+ "-i", vol_id,
+ "-n", mangled_label,
+ "-F", "32", /* yes, we force FAT32 here */
+ node, NULL);
+
+ } else if (streq(fstype, "swap")) {
+
+ (void) execlp(mkfs, mkfs,
+ "-L", label,
+ "-U", suuid,
+ node, NULL);
+
+ } else
+ /* Generic fallback for all other file systems */
+ (void) execlp(mkfs, mkfs, node, NULL);
+
+ log_error_errno(errno, "Failed to execute %s: %m", mkfs);
+
+ _exit(EXIT_FAILURE);
+ }
+
+ return 0;
+}
diff --git a/src/shared/mkfs-util.h b/src/shared/mkfs-util.h
new file mode 100644
index 0000000000..54e9b931e6
--- /dev/null
+++ b/src/shared/mkfs-util.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include
+
+#include "sd-id128.h"
+
+int mkfs_exists(const char *fstype);
+
+int make_filesystem(const char *node, const char *fstype, const char *label, sd_id128_t uuid, bool discard);