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