]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/mkfs-util.c
Merge pull request #26756 from yuwata/edit-util-cleanups
[thirdparty/systemd.git] / src / shared / mkfs-util.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
c95f9a23 2
3c9fbb99
ZJS
3#include <unistd.h>
4
bf3598be
DDM
5#include "dirent-util.h"
6#include "fd-util.h"
e59678b2 7#include "fileio.h"
aa6aa81c 8#include "fs-util.h"
c95f9a23
LP
9#include "id128-util.h"
10#include "mkfs-util.h"
eb43379c 11#include "mountpoint-util.h"
c95f9a23
LP
12#include "path-util.h"
13#include "process-util.h"
aa6aa81c 14#include "recurse-dir.h"
bf3598be 15#include "stat-util.h"
0f2b2c48 16#include "stdio-util.h"
c95f9a23 17#include "string-util.h"
aa6aa81c 18#include "tmpfile-util.h"
dc91c971 19#include "utf8.h"
c95f9a23
LP
20
21int mkfs_exists(const char *fstype) {
22 const char *mkfs;
23 int r;
24
25 assert(fstype);
26
27 if (STR_IN_SET(fstype, "auto", "swap")) /* these aren't real file system types, refuse early */
28 return -EINVAL;
29
30 mkfs = strjoina("mkfs.", fstype);
31 if (!filename_is_valid(mkfs)) /* refuse file system types with slashes and similar */
32 return -EINVAL;
33
f7bc0c32 34 r = find_executable(mkfs, NULL);
c95f9a23
LP
35 if (r == -ENOENT)
36 return false;
37 if (r < 0)
38 return r;
39
40 return true;
41}
42
59e2be46 43int mkfs_supports_root_option(const char *fstype) {
aa6aa81c 44 return fstype_is_ro(fstype) || STR_IN_SET(fstype, "ext2", "ext3", "ext4", "btrfs", "vfat", "xfs");
59e2be46
DDM
45}
46
7ffe593b
ZJS
47static int mangle_linux_fs_label(const char *s, size_t max_len, char **ret) {
48 /* Not more than max_len bytes (12 or 16) */
49
50 assert(s);
51 assert(max_len > 0);
52 assert(ret);
53
54 const char *q;
55 char *ans;
56
57 for (q = s; *q;) {
58 int l;
59
60 l = utf8_encoded_valid_unichar(q, SIZE_MAX);
61 if (l < 0)
62 return l;
63
64 if ((size_t) (q - s + l) > max_len)
65 break;
66 q += l;
67 }
68
69 ans = memdup_suffix0(s, q - s);
70 if (!ans)
71 return -ENOMEM;
72
73 *ret = ans;
74 return 0;
75}
76
dc91c971
ZJS
77static int mangle_fat_label(const char *s, char **ret) {
78 assert(s);
79
80 _cleanup_free_ char *q = NULL;
81 int r;
82
83 r = utf8_to_ascii(s, '_', &q);
84 if (r < 0)
85 return r;
86
87 /* Classic FAT only allows 11 character uppercase labels */
88 strshorten(q, 11);
89 ascii_strupper(q);
90
91 /* mkfs.vfat: Labels with characters *?.,;:/\|+=<>[]" are not allowed.
92 * Let's also replace any control chars. */
93 for (char *p = q; *p; p++)
94 if (strchr("*?.,;:/\\|+=<>[]\"", *p) || char_is_cc(*p))
95 *p = '_';
96
97 *ret = TAKE_PTR(q);
98 return 0;
99}
100
bf3598be 101static int do_mcopy(const char *node, const char *root) {
fe5779cf 102 _cleanup_free_ char *mcopy = NULL;
bf3598be 103 _cleanup_strv_free_ char **argv = NULL;
254d1313 104 _cleanup_close_ int rfd = -EBADF;
c75cf016 105 _cleanup_free_ DirectoryEntries *de = NULL;
bf3598be
DDM
106 int r;
107
108 assert(node);
109 assert(root);
110
111 /* Return early if there's nothing to copy. */
112 if (dir_is_empty(root, /*ignore_hidden_or_backup=*/ false))
113 return 0;
114
fe5779cf
DDM
115 r = find_executable("mcopy", &mcopy);
116 if (r == -ENOENT)
117 return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "Could not find mcopy binary.");
118 if (r < 0)
119 return log_error_errno(r, "Failed to determine whether mcopy binary exists: %m");
120
cf9c27b1 121 argv = strv_new(mcopy, "-s", "-p", "-Q", "-m", "-i", node);
bf3598be
DDM
122 if (!argv)
123 return log_oom();
124
125 /* mcopy copies the top level directory instead of everything in it so we have to pass all
126 * the subdirectories to mcopy instead to end up with the correct directory structure. */
127
c75cf016
DDM
128 rfd = open(root, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
129 if (rfd < 0)
130 return log_error_errno(errno, "Failed to open directory '%s': %m", root);
bf3598be 131
c75cf016
DDM
132 r = readdir_all(rfd, RECURSE_DIR_SORT|RECURSE_DIR_ENSURE_TYPE, &de);
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
ff1b55ff 155 r = safe_fork("(mcopy)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|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. */
fe5779cf 161 execve(mcopy, argv, STRV_MAKE("MTOOLS_SKIP_CHECK=1"));
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
aa6aa81c
DDM
171static int protofile_print_item(
172 RecurseDirEvent event,
173 const char *path,
174 int dir_fd,
175 int inode_fd,
176 const struct dirent *de,
177 const struct statx *sx,
178 void *userdata) {
179
180 FILE *f = ASSERT_PTR(userdata);
aa6aa81c
DDM
181 int r;
182
183 if (event == RECURSE_DIR_LEAVE) {
48ac1fd1 184 fputs("$\n", f);
aa6aa81c
DDM
185 return 0;
186 }
187
188 if (!IN_SET(event, RECURSE_DIR_ENTER, RECURSE_DIR_ENTRY))
189 return RECURSE_DIR_CONTINUE;
190
aa6aa81c
DDM
191 char type = S_ISDIR(sx->stx_mode) ? 'd' :
192 S_ISREG(sx->stx_mode) ? '-' :
193 S_ISLNK(sx->stx_mode) ? 'l' :
194 S_ISFIFO(sx->stx_mode) ? 'p' :
195 S_ISBLK(sx->stx_mode) ? 'b' :
196 S_ISCHR(sx->stx_mode) ? 'c' : 0;
197 if (type == 0)
198 return RECURSE_DIR_CONTINUE;
199
200 fprintf(f, "%s %c%c%c%03o 0 0 ",
48ac1fd1 201 de->d_name,
aa6aa81c
DDM
202 type,
203 sx->stx_mode & S_ISUID ? 'u' : '-',
204 sx->stx_mode & S_ISGID ? 'g' : '-',
205 (unsigned) (sx->stx_mode & 0777));
206
207 if (S_ISREG(sx->stx_mode))
48ac1fd1 208 fputs(path, f);
aa6aa81c
DDM
209 else if (S_ISLNK(sx->stx_mode)) {
210 _cleanup_free_ char *p = NULL;
211
48ac1fd1 212 r = readlinkat_malloc(dir_fd, de->d_name, &p);
aa6aa81c
DDM
213 if (r < 0)
214 return log_error_errno(r, "Failed to read symlink %s: %m", path);
215
48ac1fd1 216 fputs(p, f);
aa6aa81c 217 } else if (S_ISBLK(sx->stx_mode) || S_ISCHR(sx->stx_mode))
48ac1fd1 218 fprintf(f, "%" PRIu32 " %" PRIu32, sx->stx_rdev_major, sx->stx_rdev_minor);
aa6aa81c 219
48ac1fd1 220 fputc('\n', f);
aa6aa81c
DDM
221
222 return RECURSE_DIR_CONTINUE;
223}
224
225static int make_protofile(const char *root, char **ret) {
226 _cleanup_fclose_ FILE *f = NULL;
48ac1fd1
DDM
227 _cleanup_(unlink_and_freep) char *p = NULL;
228 const char *vt;
aa6aa81c
DDM
229 int r;
230
231 assert(ret);
232
48ac1fd1
DDM
233 r = var_tmp_dir(&vt);
234 if (r < 0)
235 return log_error_errno(r, "Failed to get persistent temporary directory: %m");
236
237 r = fopen_temporary_child(vt, &f, &p);
aa6aa81c
DDM
238 if (r < 0)
239 return log_error_errno(r, "Failed to open temporary file: %m");
240
48ac1fd1
DDM
241 fputs("/\n"
242 "0 0\n"
243 "d--755 0 0\n", f);
aa6aa81c 244
c75cf016 245 r = recurse_dir_at(AT_FDCWD, root, STATX_TYPE|STATX_MODE, UINT_MAX, RECURSE_DIR_SORT, protofile_print_item, f);
aa6aa81c
DDM
246 if (r < 0)
247 return log_error_errno(r, "Failed to recurse through %s: %m", root);
248
48ac1fd1 249 fputs("$\n", f);
aa6aa81c
DDM
250
251 r = fflush_and_check(f);
252 if (r < 0)
253 return log_error_errno(r, "Failed to flush %s: %m", p);
254
255 *ret = TAKE_PTR(p);
256
257 return 0;
258}
259
c95f9a23
LP
260int make_filesystem(
261 const char *node,
262 const char *fstype,
263 const char *label,
7f55ad77 264 const char *root,
c95f9a23 265 sd_id128_t uuid,
8f30c00c 266 bool discard,
e1878ef7 267 uint64_t sector_size,
8f30c00c 268 char * const *extra_mkfs_args) {
c95f9a23 269
dc91c971 270 _cleanup_free_ char *mkfs = NULL, *mangled_label = NULL;
ddf615a1 271 _cleanup_strv_free_ char **argv = NULL;
aa6aa81c 272 _cleanup_(unlink_and_freep) char *protofile = NULL;
b7416360 273 char vol_id[CONST_MAX(SD_ID128_UUID_STRING_MAX, 8U + 1U)] = {};
c95f9a23
LP
274 int r;
275
276 assert(node);
277 assert(fstype);
278 assert(label);
279
eb43379c
DDM
280 if (fstype_is_ro(fstype) && !root)
281 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
282 "Cannot generate read-only filesystem %s without a source tree.",
283 fstype);
284
c95f9a23 285 if (streq(fstype, "swap")) {
7f55ad77
DDM
286 if (root)
287 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
288 "A swap filesystem can't be populated, refusing");
f7bc0c32 289 r = find_executable("mkswap", &mkfs);
c95f9a23
LP
290 if (r == -ENOENT)
291 return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "mkswap binary not available.");
292 if (r < 0)
293 return log_error_errno(r, "Failed to determine whether mkswap binary exists: %m");
7f55ad77 294 } else if (streq(fstype, "squashfs")) {
7f55ad77
DDM
295 r = find_executable("mksquashfs", &mkfs);
296 if (r == -ENOENT)
297 return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "mksquashfs binary not available.");
298 if (r < 0)
299 return log_error_errno(r, "Failed to determine whether mksquashfs binary exists: %m");
09e917ea
LP
300
301 } else if (streq(fstype, "erofs")) {
302 r = find_executable("mkfs.erofs", &mkfs);
303 if (r == -ENOENT)
304 return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "mkfs.erofs binary not available.");
305 if (r < 0)
306 return log_error_errno(r, "Failed to determine whether mkfs.erofs binary exists: %m");
307
eaec6994
DDM
308 } else if (fstype_is_ro(fstype)) {
309 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
310 "Don't know how to create read-only file system '%s', refusing.",
311 fstype);
c95f9a23 312 } else {
59e2be46 313 if (root && !mkfs_supports_root_option(fstype))
7f55ad77 314 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
59e2be46 315 "Populating with source tree is not supported for %s", fstype);
c95f9a23
LP
316 r = mkfs_exists(fstype);
317 if (r < 0)
318 return log_error_errno(r, "Failed to determine whether mkfs binary for %s exists: %m", fstype);
319 if (r == 0)
320 return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "mkfs binary for %s is not available.", fstype);
321
322 mkfs = strjoin("mkfs.", fstype);
323 if (!mkfs)
324 return log_oom();
325 }
326
8d433a99
ZJS
327 if (STR_IN_SET(fstype, "ext2", "ext3", "ext4", "xfs", "swap")) {
328 size_t max_len =
329 streq(fstype, "xfs") ? 12 :
330 streq(fstype, "swap") ? 15 :
331 16;
332
333 r = mangle_linux_fs_label(label, max_len, &mangled_label);
7ffe593b 334 if (r < 0)
8d433a99 335 return log_error_errno(r, "Failed to determine volume label from string \"%s\": %m", label);
7ffe593b
ZJS
336 label = mangled_label;
337
338 } else if (streq(fstype, "vfat")) {
dc91c971
ZJS
339 r = mangle_fat_label(label, &mangled_label);
340 if (r < 0)
341 return log_error_errno(r, "Failed to determine FAT label from string \"%s\": %m", label);
4f05a11c
ZJS
342 label = mangled_label;
343
344 xsprintf(vol_id, "%08" PRIx32,
345 ((uint32_t) uuid.bytes[0] << 24) |
346 ((uint32_t) uuid.bytes[1] << 16) |
347 ((uint32_t) uuid.bytes[2] << 8) |
348 ((uint32_t) uuid.bytes[3])); /* Take first 32 bytes of UUID */
349 }
350
351 if (isempty(vol_id))
b7416360 352 assert_se(sd_id128_to_uuid_string(uuid, vol_id));
4f05a11c 353
ddf615a1 354 /* When changing this conditional, also adjust the log statement below. */
59e2be46 355 if (streq(fstype, "ext2")) {
ddf615a1
DDM
356 argv = strv_new(mkfs,
357 "-q",
358 "-L", label,
359 "-U", vol_id,
360 "-I", "256",
361 "-m", "0",
362 "-E", discard ? "discard,lazy_itable_init=1" : "nodiscard,lazy_itable_init=1",
e1878ef7 363 "-b", "4096",
ddf615a1 364 node);
59e2be46
DDM
365 if (!argv)
366 return log_oom();
367
065bdb6f
DDM
368 if (root && strv_extend_strv(&argv, STRV_MAKE("-d", root), false) < 0)
369 return log_oom();
ddf615a1 370
59e2be46 371 } else if (STR_IN_SET(fstype, "ext3", "ext4")) {
ddf615a1
DDM
372 argv = strv_new(mkfs,
373 "-q",
374 "-L", label,
375 "-U", vol_id,
376 "-I", "256",
377 "-O", "has_journal",
378 "-m", "0",
379 "-E", discard ? "discard,lazy_itable_init=1" : "nodiscard,lazy_itable_init=1",
e1878ef7 380 "-b", "4096",
ddf615a1
DDM
381 node);
382
065bdb6f
DDM
383 if (root && strv_extend_strv(&argv, STRV_MAKE("-d", root), false) < 0)
384 return log_oom();
59e2be46
DDM
385
386 } else if (streq(fstype, "btrfs")) {
ddf615a1
DDM
387 argv = strv_new(mkfs,
388 "-q",
389 "-L", label,
390 "-U", vol_id,
391 node);
392 if (!argv)
393 return log_oom();
394
065bdb6f
DDM
395 if (!discard && strv_extend(&argv, "--nodiscard") < 0)
396 return log_oom();
ddf615a1 397
065bdb6f
DDM
398 if (root && strv_extend_strv(&argv, STRV_MAKE("-r", root), false) < 0)
399 return log_oom();
59e2be46 400
ddf615a1
DDM
401 } else if (streq(fstype, "f2fs")) {
402 argv = strv_new(mkfs,
403 "-q",
404 "-g", /* "default options" */
405 "-f", /* force override, without this it doesn't seem to want to write to an empty partition */
406 "-l", label,
407 "-U", vol_id,
408 "-t", one_zero(discard),
409 node);
410
411 } else if (streq(fstype, "xfs")) {
412 const char *j;
413
414 j = strjoina("uuid=", vol_id);
415
416 argv = strv_new(mkfs,
417 "-q",
418 "-L", label,
419 "-m", j,
420 "-m", "reflink=1",
421 node);
422 if (!argv)
423 return log_oom();
424
065bdb6f
DDM
425 if (!discard && strv_extend(&argv, "-K") < 0)
426 return log_oom();
ddf615a1 427
aa6aa81c
DDM
428 if (root) {
429 r = make_protofile(root, &protofile);
430 if (r < 0)
431 return r;
432
065bdb6f 433 if (strv_extend_strv(&argv, STRV_MAKE("-p", protofile), false) < 0)
aa6aa81c
DDM
434 return log_oom();
435 }
436
e1878ef7
DDM
437 if (sector_size > 0) {
438 if (strv_extend(&argv, "-s") < 0)
439 return log_oom();
440
441 if (strv_extendf(&argv, "size=%"PRIu64, sector_size) < 0)
442 return log_oom();
443 }
444
445 } else if (streq(fstype, "vfat")) {
ddf615a1
DDM
446
447 argv = strv_new(mkfs,
448 "-i", vol_id,
449 "-n", label,
450 "-F", "32", /* yes, we force FAT32 here */
451 node);
452
e1878ef7
DDM
453 if (sector_size > 0) {
454 if (strv_extend(&argv, "-S") < 0)
455 return log_oom();
456
457 if (strv_extendf(&argv, "%"PRIu64, sector_size) < 0)
458 return log_oom();
459 }
460
461 } else if (streq(fstype, "swap"))
ddf615a1
DDM
462 /* TODO: add --quiet here if
463 * https://github.com/util-linux/util-linux/issues/1499 resolved. */
464
465 argv = strv_new(mkfs,
466 "-L", label,
467 "-U", vol_id,
468 node);
469
470 else if (streq(fstype, "squashfs"))
471
472 argv = strv_new(mkfs,
473 root, node,
474 "-quiet",
475 "-noappend");
09e917ea
LP
476
477 else if (streq(fstype, "erofs"))
478
479 argv = strv_new(mkfs,
480 "-U", vol_id,
481 node, root);
ddf615a1
DDM
482 else
483 /* Generic fallback for all other file systems */
484 argv = strv_new(mkfs, node);
485
486 if (!argv)
487 return log_oom();
488
065bdb6f
DDM
489 if (extra_mkfs_args && strv_extend_strv(&argv, extra_mkfs_args, false) < 0)
490 return log_oom();
8f30c00c 491
ff1b55ff 492 r = safe_fork("(mkfs)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_LOG|FORK_WAIT|FORK_STDOUT_TO_STDERR|FORK_CLOSE_ALL_FDS, NULL);
c95f9a23
LP
493 if (r < 0)
494 return r;
495 if (r == 0) {
c95f9a23 496 /* Child */
4f05a11c 497
ddf615a1 498 execvp(mkfs, argv);
c95f9a23
LP
499
500 log_error_errno(errno, "Failed to execute %s: %m", mkfs);
501
502 _exit(EXIT_FAILURE);
503 }
504
bf3598be
DDM
505 if (root && streq(fstype, "vfat")) {
506 r = do_mcopy(node, root);
507 if (r < 0)
508 return r;
509 }
510
2d96440f 511 if (STR_IN_SET(fstype, "ext2", "ext3", "ext4", "btrfs", "f2fs", "xfs", "vfat", "swap"))
4f05a11c
ZJS
512 log_info("%s successfully formatted as %s (label \"%s\", uuid %s)",
513 node, fstype, label, vol_id);
09e917ea
LP
514 else if (streq(fstype, "erofs"))
515 log_info("%s successfully formatted as %s (uuid %s, no label)",
516 node, fstype, vol_id);
4f05a11c
ZJS
517 else
518 log_info("%s successfully formatted as %s (no label or uuid specified)",
519 node, fstype);
520
c95f9a23
LP
521 return 0;
522}
4b8ce14f
DDM
523
524int mkfs_options_from_env(const char *component, const char *fstype, char ***ret) {
525 _cleanup_(strv_freep) char **l = NULL;
526 const char *e;
527 char *n;
528
529 assert(component);
530 assert(fstype);
531 assert(ret);
532
533 n = strjoina("SYSTEMD_", component, "_MKFS_OPTIONS_", fstype);
534 e = getenv(ascii_strupper(n));
535 if (e) {
536 l = strv_split(e, NULL);
537 if (!l)
538 return -ENOMEM;
539 }
540
541 *ret = TAKE_PTR(l);
542 return 0;
543}