]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/mkfs-util.c
man/systemd-sysext: list ephemeral/ephemeral-import in the list of options
[thirdparty/systemd.git] / src / shared / mkfs-util.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
c95f9a23 2
69a283c5 3#include <stdlib.h>
969eb039 4#include <sys/mount.h>
3c9fbb99
ZJS
5#include <unistd.h>
6
bf3598be 7#include "fd-util.h"
e59678b2 8#include "fileio.h"
69a283c5 9#include "format-util.h"
aa6aa81c 10#include "fs-util.h"
8aa304d3 11#include "log.h"
c95f9a23 12#include "mkfs-util.h"
969eb039 13#include "mount-util.h"
eb43379c 14#include "mountpoint-util.h"
c95f9a23
LP
15#include "path-util.h"
16#include "process-util.h"
aa6aa81c 17#include "recurse-dir.h"
776be596 18#include "rm-rf.h"
bf3598be 19#include "stat-util.h"
0f2b2c48 20#include "stdio-util.h"
c95f9a23 21#include "string-util.h"
69a283c5 22#include "strv.h"
aa6aa81c 23#include "tmpfile-util.h"
dc91c971 24#include "utf8.h"
c95f9a23
LP
25
26int mkfs_exists(const char *fstype) {
27 const char *mkfs;
28 int r;
29
30 assert(fstype);
31
32 if (STR_IN_SET(fstype, "auto", "swap")) /* these aren't real file system types, refuse early */
33 return -EINVAL;
34
35 mkfs = strjoina("mkfs.", fstype);
36 if (!filename_is_valid(mkfs)) /* refuse file system types with slashes and similar */
37 return -EINVAL;
38
f7bc0c32 39 r = find_executable(mkfs, NULL);
c95f9a23
LP
40 if (r == -ENOENT)
41 return false;
42 if (r < 0)
43 return r;
44
45 return true;
46}
47
59e2be46 48int mkfs_supports_root_option(const char *fstype) {
aa6aa81c 49 return fstype_is_ro(fstype) || STR_IN_SET(fstype, "ext2", "ext3", "ext4", "btrfs", "vfat", "xfs");
59e2be46
DDM
50}
51
7ffe593b
ZJS
52static int mangle_linux_fs_label(const char *s, size_t max_len, char **ret) {
53 /* Not more than max_len bytes (12 or 16) */
54
55 assert(s);
56 assert(max_len > 0);
57 assert(ret);
58
59 const char *q;
60 char *ans;
61
62 for (q = s; *q;) {
63 int l;
64
65 l = utf8_encoded_valid_unichar(q, SIZE_MAX);
66 if (l < 0)
67 return l;
68
69 if ((size_t) (q - s + l) > max_len)
70 break;
71 q += l;
72 }
73
74 ans = memdup_suffix0(s, q - s);
75 if (!ans)
76 return -ENOMEM;
77
78 *ret = ans;
79 return 0;
80}
81
dc91c971
ZJS
82static int mangle_fat_label(const char *s, char **ret) {
83 assert(s);
84
85 _cleanup_free_ char *q = NULL;
86 int r;
87
88 r = utf8_to_ascii(s, '_', &q);
89 if (r < 0)
90 return r;
91
92 /* Classic FAT only allows 11 character uppercase labels */
93 strshorten(q, 11);
94 ascii_strupper(q);
95
96 /* mkfs.vfat: Labels with characters *?.,;:/\|+=<>[]" are not allowed.
97 * Let's also replace any control chars. */
98 for (char *p = q; *p; p++)
99 if (strchr("*?.,;:/\\|+=<>[]\"", *p) || char_is_cc(*p))
100 *p = '_';
101
102 *ret = TAKE_PTR(q);
103 return 0;
104}
105
bf3598be 106static int do_mcopy(const char *node, const char *root) {
fe5779cf 107 _cleanup_free_ char *mcopy = NULL;
bf3598be 108 _cleanup_strv_free_ char **argv = NULL;
c75cf016 109 _cleanup_free_ DirectoryEntries *de = NULL;
bf3598be
DDM
110 int r;
111
112 assert(node);
113 assert(root);
114
115 /* Return early if there's nothing to copy. */
116 if (dir_is_empty(root, /*ignore_hidden_or_backup=*/ false))
117 return 0;
118
fe5779cf
DDM
119 r = find_executable("mcopy", &mcopy);
120 if (r == -ENOENT)
121 return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "Could not find mcopy binary.");
122 if (r < 0)
123 return log_error_errno(r, "Failed to determine whether mcopy binary exists: %m");
124
cf9c27b1 125 argv = strv_new(mcopy, "-s", "-p", "-Q", "-m", "-i", node);
bf3598be
DDM
126 if (!argv)
127 return log_oom();
128
129 /* mcopy copies the top level directory instead of everything in it so we have to pass all
130 * the subdirectories to mcopy instead to end up with the correct directory structure. */
131
4d564427 132 r = readdir_all_at(AT_FDCWD, root, RECURSE_DIR_SORT|RECURSE_DIR_ENSURE_TYPE, &de);
c75cf016
DDM
133 if (r < 0)
134 return log_error_errno(r, "Failed to read '%s' contents: %m", root);
135
136 for (size_t i = 0; i < de->n_entries; i++) {
f3c8cb27
YW
137 _cleanup_free_ char *p = NULL;
138
139 p = path_join(root, de->entries[i]->d_name);
bf3598be
DDM
140 if (!p)
141 return log_oom();
142
c75cf016
DDM
143 if (!IN_SET(de->entries[i]->d_type, DT_REG, DT_DIR)) {
144 log_debug("%s is not a file/directory which are the only file types supported by vfat, ignoring", p);
145 continue;
146 }
147
065bdb6f 148 if (strv_consume(&argv, TAKE_PTR(p)) < 0)
bf3598be
DDM
149 return log_oom();
150 }
151
065bdb6f 152 if (strv_extend(&argv, "::") < 0)
bf3598be
DDM
153 return log_oom();
154
e9ccae31 155 r = safe_fork("(mcopy)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT|FORK_STDOUT_TO_STDERR|FORK_CLOSE_ALL_FDS, NULL);
bf3598be
DDM
156 if (r < 0)
157 return r;
158 if (r == 0) {
159 /* Avoid failures caused by mismatch in expectations between mkfs.vfat and mcopy by disabling
160 * the stricter mcopy checks using MTOOLS_SKIP_CHECK. */
b2942c76 161 execve(mcopy, argv, STRV_MAKE("MTOOLS_SKIP_CHECK=1", "TZ=UTC", strv_find_prefix(environ, "SOURCE_DATE_EPOCH=")));
bf3598be
DDM
162
163 log_error_errno(errno, "Failed to execute mcopy: %m");
164
165 _exit(EXIT_FAILURE);
166 }
167
168 return 0;
169}
170
776be596
DDM
171typedef struct ProtofileData {
172 FILE *file;
173 bool has_filename_with_spaces;
174 const char *tmpdir;
175} ProtofileData;
176
aa6aa81c
DDM
177static int protofile_print_item(
178 RecurseDirEvent event,
179 const char *path,
180 int dir_fd,
181 int inode_fd,
182 const struct dirent *de,
183 const struct statx *sx,
184 void *userdata) {
185
776be596
DDM
186 ProtofileData *data = ASSERT_PTR(userdata);
187 _cleanup_free_ char *copy = NULL;
aa6aa81c
DDM
188 int r;
189
190 if (event == RECURSE_DIR_LEAVE) {
776be596 191 fputs("$\n", data->file);
aa6aa81c
DDM
192 return 0;
193 }
194
195 if (!IN_SET(event, RECURSE_DIR_ENTER, RECURSE_DIR_ENTRY))
196 return RECURSE_DIR_CONTINUE;
197
aa6aa81c
DDM
198 char type = S_ISDIR(sx->stx_mode) ? 'd' :
199 S_ISREG(sx->stx_mode) ? '-' :
200 S_ISLNK(sx->stx_mode) ? 'l' :
201 S_ISFIFO(sx->stx_mode) ? 'p' :
202 S_ISBLK(sx->stx_mode) ? 'b' :
203 S_ISCHR(sx->stx_mode) ? 'c' : 0;
204 if (type == 0)
205 return RECURSE_DIR_CONTINUE;
206
776be596
DDM
207 /* The protofile format does not support spaces in filenames as whitespace is used as a token
208 * delimiter. To work around this limitation, mkfs.xfs allows escaping whitespace by using the /
209 * character (which isn't allowed in filenames and as such can be used to escape whitespace). See
210 * https://lore.kernel.org/linux-xfs/20230222090303.h6tujm7y32gjhgal@andromeda/T/#m8066b3e7d62a080ee7434faac4861d944e64493b
d7306348 211 * for more information. */
776be596
DDM
212
213 if (strchr(de->d_name, ' ')) {
214 copy = strdup(de->d_name);
215 if (!copy)
216 return log_oom();
217
218 string_replace_char(copy, ' ', '/');
219 data->has_filename_with_spaces = true;
220 }
221
7b794ba0 222 fprintf(data->file, "%s %c%c%c%03o "UID_FMT" "GID_FMT" ",
776be596 223 copy ?: de->d_name,
aa6aa81c
DDM
224 type,
225 sx->stx_mode & S_ISUID ? 'u' : '-',
226 sx->stx_mode & S_ISGID ? 'g' : '-',
7b794ba0
DDM
227 (unsigned) (sx->stx_mode & 0777),
228 sx->stx_uid, sx->stx_gid);
aa6aa81c 229
776be596
DDM
230 if (S_ISREG(sx->stx_mode)) {
231 _cleanup_free_ char *p = NULL;
232
233 /* While we can escape whitespace in the filename, we cannot escape whitespace in the source
234 * path, so hack around that by creating a symlink to the path in a temporary directory and
235 * using the symlink as the source path instead. */
236
237 if (strchr(path, ' ')) {
238 r = tempfn_random_child(data->tmpdir, "mkfs-xfs", &p);
239 if (r < 0)
240 return log_error_errno(r, "Failed to generate random child name in %s: %m", data->tmpdir);
241
242 if (symlink(path, p) < 0)
243 return log_error_errno(errno, "Failed to symlink %s to %s: %m", p, path);
244 }
245
246 fputs(p ?: path, data->file);
247 } else if (S_ISLNK(sx->stx_mode)) {
aa6aa81c
DDM
248 _cleanup_free_ char *p = NULL;
249
48ac1fd1 250 r = readlinkat_malloc(dir_fd, de->d_name, &p);
aa6aa81c
DDM
251 if (r < 0)
252 return log_error_errno(r, "Failed to read symlink %s: %m", path);
253
776be596
DDM
254 /* If we have a symlink to a path with whitespace in it, we're out of luck, as there's no way
255 * to encode that in the mkfs.xfs protofile format. */
256
257 if (strchr(p, ' '))
258 return log_error_errno(r, "Symlinks to paths containing whitespace are not supported by mkfs.xfs: %m");
259
260 fputs(p, data->file);
aa6aa81c 261 } else if (S_ISBLK(sx->stx_mode) || S_ISCHR(sx->stx_mode))
776be596 262 fprintf(data->file, "%" PRIu32 " %" PRIu32, sx->stx_rdev_major, sx->stx_rdev_minor);
aa6aa81c 263
776be596 264 fputc('\n', data->file);
aa6aa81c
DDM
265
266 return RECURSE_DIR_CONTINUE;
267}
268
776be596
DDM
269static int make_protofile(const char *root, char **ret_path, bool *ret_has_filename_with_spaces, char **ret_tmpdir) {
270 _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL;
aa6aa81c 271 _cleanup_fclose_ FILE *f = NULL;
48ac1fd1 272 _cleanup_(unlink_and_freep) char *p = NULL;
776be596 273 struct ProtofileData data = {};
48ac1fd1 274 const char *vt;
aa6aa81c
DDM
275 int r;
276
776be596
DDM
277 assert(ret_path);
278 assert(ret_has_filename_with_spaces);
279 assert(ret_tmpdir);
aa6aa81c 280
48ac1fd1
DDM
281 r = var_tmp_dir(&vt);
282 if (r < 0)
283 return log_error_errno(r, "Failed to get persistent temporary directory: %m");
284
285 r = fopen_temporary_child(vt, &f, &p);
aa6aa81c
DDM
286 if (r < 0)
287 return log_error_errno(r, "Failed to open temporary file: %m");
288
776be596
DDM
289 /* Explicitly use /tmp here because this directory cannot have spaces its path. */
290 r = mkdtemp_malloc("/tmp/systemd-mkfs-XXXXXX", &tmpdir);
291 if (r < 0)
292 return log_error_errno(r, "Failed to create temporary directory: %m");
293
294 data.file = f;
295 data.tmpdir = tmpdir;
296
48ac1fd1
DDM
297 fputs("/\n"
298 "0 0\n"
299 "d--755 0 0\n", f);
aa6aa81c 300
7b794ba0
DDM
301 r = recurse_dir_at(AT_FDCWD, root, STATX_TYPE|STATX_MODE|STATX_UID|STATX_GID, UINT_MAX,
302 RECURSE_DIR_SORT, protofile_print_item, &data);
aa6aa81c
DDM
303 if (r < 0)
304 return log_error_errno(r, "Failed to recurse through %s: %m", root);
305
48ac1fd1 306 fputs("$\n", f);
aa6aa81c
DDM
307
308 r = fflush_and_check(f);
309 if (r < 0)
310 return log_error_errno(r, "Failed to flush %s: %m", p);
311
776be596
DDM
312 *ret_path = TAKE_PTR(p);
313 *ret_has_filename_with_spaces = data.has_filename_with_spaces;
314 *ret_tmpdir = TAKE_PTR(tmpdir);
aa6aa81c
DDM
315
316 return 0;
317}
318
c95f9a23
LP
319int make_filesystem(
320 const char *node,
321 const char *fstype,
322 const char *label,
7f55ad77 323 const char *root,
c95f9a23 324 sd_id128_t uuid,
d89283b4 325 MakeFileSystemFlags flags,
e1878ef7 326 uint64_t sector_size,
27cacec9
DDM
327 char *compression,
328 char *compression_level,
8f30c00c 329 char * const *extra_mkfs_args) {
c95f9a23 330
dc91c971 331 _cleanup_free_ char *mkfs = NULL, *mangled_label = NULL;
89dfac6b 332 _cleanup_strv_free_ char **argv = NULL, **env = NULL;
776be596 333 _cleanup_(rm_rf_physical_and_freep) char *protofile_tmpdir = NULL;
aa6aa81c 334 _cleanup_(unlink_and_freep) char *protofile = NULL;
b7416360 335 char vol_id[CONST_MAX(SD_ID128_UUID_STRING_MAX, 8U + 1U)] = {};
49f2e129 336 int stdio_fds[3] = { -EBADF, STDERR_FILENO, STDERR_FILENO};
c69598a3 337 ForkFlags fork_flags = FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT|
e0784901 338 FORK_CLOSE_ALL_FDS|FORK_REARRANGE_STDIO|FORK_REOPEN_LOG;
c95f9a23
LP
339 int r;
340
341 assert(node);
342 assert(fstype);
343 assert(label);
344
eb43379c
DDM
345 if (fstype_is_ro(fstype) && !root)
346 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
347 "Cannot generate read-only filesystem %s without a source tree.",
348 fstype);
349
c95f9a23 350 if (streq(fstype, "swap")) {
7f55ad77
DDM
351 if (root)
352 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
353 "A swap filesystem can't be populated, refusing");
f7bc0c32 354 r = find_executable("mkswap", &mkfs);
c95f9a23
LP
355 if (r == -ENOENT)
356 return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "mkswap binary not available.");
357 if (r < 0)
358 return log_error_errno(r, "Failed to determine whether mkswap binary exists: %m");
7f55ad77 359 } else if (streq(fstype, "squashfs")) {
7f55ad77
DDM
360 r = find_executable("mksquashfs", &mkfs);
361 if (r == -ENOENT)
362 return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "mksquashfs binary not available.");
363 if (r < 0)
364 return log_error_errno(r, "Failed to determine whether mksquashfs binary exists: %m");
09e917ea
LP
365
366 } else if (streq(fstype, "erofs")) {
367 r = find_executable("mkfs.erofs", &mkfs);
368 if (r == -ENOENT)
369 return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "mkfs.erofs binary not available.");
370 if (r < 0)
371 return log_error_errno(r, "Failed to determine whether mkfs.erofs binary exists: %m");
372
eaec6994
DDM
373 } else if (fstype_is_ro(fstype)) {
374 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
375 "Don't know how to create read-only file system '%s', refusing.",
376 fstype);
c95f9a23 377 } else {
59e2be46 378 if (root && !mkfs_supports_root_option(fstype))
7f55ad77 379 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
59e2be46 380 "Populating with source tree is not supported for %s", fstype);
c95f9a23
LP
381 r = mkfs_exists(fstype);
382 if (r < 0)
383 return log_error_errno(r, "Failed to determine whether mkfs binary for %s exists: %m", fstype);
384 if (r == 0)
385 return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "mkfs binary for %s is not available.", fstype);
386
387 mkfs = strjoin("mkfs.", fstype);
388 if (!mkfs)
389 return log_oom();
390 }
391
8d433a99
ZJS
392 if (STR_IN_SET(fstype, "ext2", "ext3", "ext4", "xfs", "swap")) {
393 size_t max_len =
394 streq(fstype, "xfs") ? 12 :
395 streq(fstype, "swap") ? 15 :
396 16;
397
398 r = mangle_linux_fs_label(label, max_len, &mangled_label);
7ffe593b 399 if (r < 0)
8d433a99 400 return log_error_errno(r, "Failed to determine volume label from string \"%s\": %m", label);
7ffe593b
ZJS
401 label = mangled_label;
402
403 } else if (streq(fstype, "vfat")) {
dc91c971
ZJS
404 r = mangle_fat_label(label, &mangled_label);
405 if (r < 0)
406 return log_error_errno(r, "Failed to determine FAT label from string \"%s\": %m", label);
4f05a11c
ZJS
407 label = mangled_label;
408
409 xsprintf(vol_id, "%08" PRIx32,
410 ((uint32_t) uuid.bytes[0] << 24) |
411 ((uint32_t) uuid.bytes[1] << 16) |
412 ((uint32_t) uuid.bytes[2] << 8) |
413 ((uint32_t) uuid.bytes[3])); /* Take first 32 bytes of UUID */
414 }
415
416 if (isempty(vol_id))
b7416360 417 assert_se(sd_id128_to_uuid_string(uuid, vol_id));
4f05a11c 418
ddf615a1 419 /* When changing this conditional, also adjust the log statement below. */
2417fa8e 420 if (STR_IN_SET(fstype, "ext2", "ext3", "ext4")) {
ddf615a1 421 argv = strv_new(mkfs,
ddf615a1
DDM
422 "-L", label,
423 "-U", vol_id,
424 "-I", "256",
425 "-m", "0",
d89283b4 426 "-E", FLAGS_SET(flags, MKFS_DISCARD) ? "discard,lazy_itable_init=1" : "nodiscard,lazy_itable_init=1",
e1878ef7 427 "-b", "4096",
9b18dcf8 428 "-T", "default");
362efc38
DDM
429 if (!argv)
430 return log_oom();
ddf615a1 431
69f3c619 432 if (root && strv_extend_many(&argv, "-d", root) < 0)
065bdb6f 433 return log_oom();
59e2be46 434
d89283b4 435 if (FLAGS_SET(flags, MKFS_QUIET) && strv_extend(&argv, "-q") < 0)
2bc161dd
DDM
436 return log_oom();
437
a46bb377
AK
438 if (FLAGS_SET(flags, MKFS_FS_VERITY) && strv_extend_many(&argv, "-O", "verity") < 0)
439 return log_oom();
440
9b18dcf8
DDM
441 if (strv_extend(&argv, node) < 0)
442 return log_oom();
443
b1110c81
DDM
444 if (sector_size > 0) {
445 if (strv_extend(&env, "MKE2FS_DEVICE_SECTSIZE") < 0)
89dfac6b
DDM
446 return log_oom();
447
b1110c81
DDM
448 if (strv_extendf(&env, "%"PRIu64, sector_size) < 0)
449 return log_oom();
450 }
89dfac6b 451
59e2be46 452 } else if (streq(fstype, "btrfs")) {
ddf615a1 453 argv = strv_new(mkfs,
ddf615a1 454 "-L", label,
9b18dcf8 455 "-U", vol_id);
ddf615a1
DDM
456 if (!argv)
457 return log_oom();
458
d89283b4 459 if (!FLAGS_SET(flags, MKFS_DISCARD) && strv_extend(&argv, "--nodiscard") < 0)
065bdb6f 460 return log_oom();
ddf615a1 461
69f3c619 462 if (root && strv_extend_many(&argv, "-r", root) < 0)
065bdb6f 463 return log_oom();
59e2be46 464
d89283b4 465 if (FLAGS_SET(flags, MKFS_QUIET) && strv_extend(&argv, "-q") < 0)
2bc161dd
DDM
466 return log_oom();
467
42839efb
DDM
468 if (compression) {
469 _cleanup_free_ char *c = NULL;
470
471 c = strdup(compression);
472 if (!c)
473 return log_oom();
474
475 if (compression_level && !strextend(&c, ":", compression_level))
476 return log_oom();
477
478 if (strv_extend_many(&argv, "--compress", c) < 0)
479 return log_oom();
480 }
481
a1a4bbea
DDM
482 /* mkfs.btrfs unconditionally warns about several settings changing from v5.15 onwards which
483 * isn't silenced by "-q", so let's redirect stdout to /dev/null as well. */
d89283b4 484 if (FLAGS_SET(flags, MKFS_QUIET))
a1a4bbea
DDM
485 stdio_fds[1] = -EBADF;
486
9fc4388a
DDM
487 /* mkfs.btrfs expects a sector size of at least 4k bytes. */
488 if (sector_size > 0 && strv_extendf(&argv, "--sectorsize=%"PRIu64, MAX(sector_size, 4 * U64_KB)) < 0)
489 return log_oom();
03c9e88f 490
9b18dcf8
DDM
491 if (strv_extend(&argv, node) < 0)
492 return log_oom();
493
ddf615a1
DDM
494 } else if (streq(fstype, "f2fs")) {
495 argv = strv_new(mkfs,
ddf615a1
DDM
496 "-g", /* "default options" */
497 "-f", /* force override, without this it doesn't seem to want to write to an empty partition */
498 "-l", label,
499 "-U", vol_id,
d89283b4 500 "-t", one_zero(FLAGS_SET(flags, MKFS_DISCARD)));
362efc38
DDM
501 if (!argv)
502 return log_oom();
ddf615a1 503
d89283b4 504 if (FLAGS_SET(flags, MKFS_QUIET) && strv_extend(&argv, "-q") < 0)
2bc161dd
DDM
505 return log_oom();
506
a46bb377
AK
507 if (FLAGS_SET(flags, MKFS_FS_VERITY) && strv_extend_many(&argv, "-O", "verity") < 0)
508 return log_oom();
509
1d117b06
DDM
510 if (sector_size > 0) {
511 if (strv_extend(&argv, "-w") < 0)
512 return log_oom();
513
514 if (strv_extendf(&argv, "%"PRIu64, sector_size) < 0)
515 return log_oom();
516 }
517
9b18dcf8
DDM
518 if (strv_extend(&argv, node) < 0)
519 return log_oom();
520
ddf615a1
DDM
521 } else if (streq(fstype, "xfs")) {
522 const char *j;
523
524 j = strjoina("uuid=", vol_id);
525
526 argv = strv_new(mkfs,
ddf615a1
DDM
527 "-L", label,
528 "-m", j,
9b18dcf8 529 "-m", "reflink=1");
ddf615a1
DDM
530 if (!argv)
531 return log_oom();
532
d89283b4 533 if (!FLAGS_SET(flags, MKFS_DISCARD) && strv_extend(&argv, "-K") < 0)
065bdb6f 534 return log_oom();
ddf615a1 535
aa6aa81c 536 if (root) {
776be596
DDM
537 bool has_filename_with_spaces = false;
538 _cleanup_free_ char *protofile_with_opt = NULL;
539
540 r = make_protofile(root, &protofile, &has_filename_with_spaces, &protofile_tmpdir);
aa6aa81c
DDM
541 if (r < 0)
542 return r;
543
776be596
DDM
544 /* Gross hack to make mkfs.xfs interpret slashes as spaces so we can encode filenames
545 * with spaces in the protofile format. */
546 if (has_filename_with_spaces)
547 protofile_with_opt = strjoin("slashes_are_spaces=1,", protofile);
548 else
549 protofile_with_opt = strdup(protofile);
550 if (!protofile_with_opt)
551 return -ENOMEM;
552
69f3c619 553 if (strv_extend_many(&argv, "-p", protofile_with_opt) < 0)
aa6aa81c
DDM
554 return log_oom();
555 }
556
e1878ef7
DDM
557 if (sector_size > 0) {
558 if (strv_extend(&argv, "-s") < 0)
559 return log_oom();
560
561 if (strv_extendf(&argv, "size=%"PRIu64, sector_size) < 0)
562 return log_oom();
563 }
564
d89283b4 565 if (FLAGS_SET(flags, MKFS_QUIET) && strv_extend(&argv, "-q") < 0)
2bc161dd
DDM
566 return log_oom();
567
9b18dcf8
DDM
568 if (strv_extend(&argv, node) < 0)
569 return log_oom();
570
e1878ef7 571 } else if (streq(fstype, "vfat")) {
ddf615a1
DDM
572
573 argv = strv_new(mkfs,
574 "-i", vol_id,
575 "-n", label,
9b18dcf8 576 "-F", "32"); /* yes, we force FAT32 here */
362efc38
DDM
577 if (!argv)
578 return log_oom();
ddf615a1 579
e1878ef7
DDM
580 if (sector_size > 0) {
581 if (strv_extend(&argv, "-S") < 0)
582 return log_oom();
583
584 if (strv_extendf(&argv, "%"PRIu64, sector_size) < 0)
585 return log_oom();
586 }
587
9b18dcf8
DDM
588 if (strv_extend(&argv, node) < 0)
589 return log_oom();
590
49f2e129 591 /* mkfs.vfat does not have a --quiet option so let's redirect stdout to /dev/null instead. */
d89283b4 592 if (FLAGS_SET(flags, MKFS_QUIET))
2bc161dd 593 stdio_fds[1] = -EBADF;
49f2e129 594
2bc161dd
DDM
595 } else if (streq(fstype, "swap")) {
596 /* TODO: add --quiet once util-linux v2.38 is available everywhere. */
ddf615a1
DDM
597
598 argv = strv_new(mkfs,
599 "-L", label,
600 "-U", vol_id,
601 node);
362efc38
DDM
602 if (!argv)
603 return log_oom();
ddf615a1 604
d89283b4 605 if (FLAGS_SET(flags, MKFS_QUIET))
2bc161dd
DDM
606 stdio_fds[1] = -EBADF;
607
608 } else if (streq(fstype, "squashfs")) {
ddf615a1
DDM
609
610 argv = strv_new(mkfs,
9b18dcf8 611 root, node, /* mksquashfs expects its arguments before the options. */
ddf615a1 612 "-noappend");
362efc38
DDM
613 if (!argv)
614 return log_oom();
09e917ea 615
27cacec9
DDM
616 if (compression) {
617 if (strv_extend_many(&argv, "-comp", compression) < 0)
618 return log_oom();
619
620 if (compression_level && strv_extend_many(&argv, "-Xcompression-level", compression_level) < 0)
621 return log_oom();
622 }
623
85c494f7 624 /* mksquashfs -quiet option is pretty new so let's redirect stdout to /dev/null instead. */
d89283b4 625 if (FLAGS_SET(flags, MKFS_QUIET))
2bc161dd 626 stdio_fds[1] = -EBADF;
85c494f7 627
2bc161dd 628 } else if (streq(fstype, "erofs")) {
09e917ea 629 argv = strv_new(mkfs,
9b18dcf8 630 "-U", vol_id);
362efc38
DDM
631 if (!argv)
632 return log_oom();
2bc161dd 633
d89283b4 634 if (FLAGS_SET(flags, MKFS_QUIET) && strv_extend(&argv, "--quiet") < 0)
2bc161dd
DDM
635 return log_oom();
636
27cacec9
DDM
637 if (compression) {
638 _cleanup_free_ char *c = NULL;
639
640 c = strjoin("-z", compression);
641 if (!c)
642 return log_oom();
643
644 if (compression_level && !strextend(&c, ",level=", compression_level))
645 return log_oom();
646
647 if (strv_extend(&argv, c) < 0)
648 return log_oom();
649 }
650
9b18dcf8
DDM
651 if (strv_extend_many(&argv, node, root) < 0)
652 return log_oom();
653
362efc38 654 } else {
ddf615a1
DDM
655 /* Generic fallback for all other file systems */
656 argv = strv_new(mkfs, node);
362efc38
DDM
657 if (!argv)
658 return log_oom();
659 }
ddf615a1 660
065bdb6f
DDM
661 if (extra_mkfs_args && strv_extend_strv(&argv, extra_mkfs_args, false) < 0)
662 return log_oom();
8f30c00c 663
e0784901
DDM
664 if (streq(fstype, "btrfs")) {
665 struct stat st;
666
667 if (stat(node, &st) < 0)
668 return log_error_errno(r, "Failed to stat '%s': %m", node);
669
670 if (S_ISBLK(st.st_mode))
c69598a3 671 fork_flags |= FORK_NEW_MOUNTNS;
e0784901
DDM
672 }
673
b24bfd6e
DDM
674 if (DEBUG_LOGGING) {
675 _cleanup_free_ char *j = NULL;
676
677 j = strv_join(argv, " ");
678 log_debug("Executing mkfs command: %s", strna(j));
679 }
680
49f2e129
DDM
681 r = safe_fork_full(
682 "(mkfs)",
683 stdio_fds,
684 /*except_fds=*/ NULL,
685 /*n_except_fds=*/ 0,
c69598a3 686 fork_flags,
49f2e129 687 /*ret_pid=*/ NULL);
c95f9a23
LP
688 if (r < 0)
689 return r;
690 if (r == 0) {
c95f9a23 691 /* Child */
4f05a11c 692
89dfac6b
DDM
693 STRV_FOREACH_PAIR(k, v, env)
694 if (setenv(*k, *v, /* replace = */ true) < 0) {
695 log_error_errno(r, "Failed to set %s=%s environment variable: %m", *k, *v);
696 _exit(EXIT_FAILURE);
697 }
698
969eb039
DDM
699 /* mkfs.btrfs refuses to operate on block devices with mounted partitions, even if operating
700 * on unformatted free space, so let's trick it and other mkfs tools into thinking no
701 * partitions are mounted. See https://github.com/kdave/btrfs-progs/issues/640 for more
702 ° information. */
c69598a3 703 if (fork_flags & FORK_NEW_MOUNTNS)
e0784901 704 (void) mount_nofollow_verbose(LOG_DEBUG, "/dev/null", "/proc/self/mounts", NULL, MS_BIND, NULL);
969eb039 705
ddf615a1 706 execvp(mkfs, argv);
c95f9a23
LP
707
708 log_error_errno(errno, "Failed to execute %s: %m", mkfs);
709
710 _exit(EXIT_FAILURE);
711 }
712
bf3598be
DDM
713 if (root && streq(fstype, "vfat")) {
714 r = do_mcopy(node, root);
715 if (r < 0)
716 return r;
717 }
718
2d96440f 719 if (STR_IN_SET(fstype, "ext2", "ext3", "ext4", "btrfs", "f2fs", "xfs", "vfat", "swap"))
4f05a11c
ZJS
720 log_info("%s successfully formatted as %s (label \"%s\", uuid %s)",
721 node, fstype, label, vol_id);
09e917ea
LP
722 else if (streq(fstype, "erofs"))
723 log_info("%s successfully formatted as %s (uuid %s, no label)",
724 node, fstype, vol_id);
4f05a11c
ZJS
725 else
726 log_info("%s successfully formatted as %s (no label or uuid specified)",
727 node, fstype);
728
c95f9a23
LP
729 return 0;
730}
4b8ce14f
DDM
731
732int mkfs_options_from_env(const char *component, const char *fstype, char ***ret) {
5d2a48da 733 _cleanup_strv_free_ char **l = NULL;
4b8ce14f
DDM
734 const char *e;
735 char *n;
736
737 assert(component);
738 assert(fstype);
739 assert(ret);
740
741 n = strjoina("SYSTEMD_", component, "_MKFS_OPTIONS_", fstype);
742 e = getenv(ascii_strupper(n));
743 if (e) {
744 l = strv_split(e, NULL);
745 if (!l)
746 return -ENOMEM;
747 }
748
749 *ret = TAKE_PTR(l);
750 return 0;
751}