]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/mkfs-util.c
tree-wide: use -EBADF for fd initialization
[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
e59678b2
DDM
101static int setup_userns(uid_t uid, gid_t gid) {
102 int r;
103
104 /* mkfs programs tend to keep ownership intact when bootstrapping themselves from a root directory.
105 * However, we'd like for the files to be owned by root instead, so we fork off a user namespace and
106 * inside of it, map the uid/gid of the root directory to root in the user namespace. mkfs programs
107 * will pick up on this and the files will be owned by root in the generated filesystem. */
108
109 r = write_string_filef("/proc/self/uid_map", WRITE_STRING_FILE_DISABLE_BUFFER,
110 UID_FMT " " UID_FMT " " UID_FMT, 0u, uid, 1u);
111 if (r < 0)
112 return log_error_errno(r,
113 "Failed to write mapping for "UID_FMT" to /proc/self/uid_map: %m",
114 uid);
115
116 r = write_string_file("/proc/self/setgroups", "deny", WRITE_STRING_FILE_DISABLE_BUFFER);
117 if (r < 0)
118 return log_error_errno(r, "Failed to write 'deny' to /proc/self/setgroups: %m");
119
120 r = write_string_filef("/proc/self/gid_map", WRITE_STRING_FILE_DISABLE_BUFFER,
29ec4bce 121 GID_FMT " " GID_FMT " " GID_FMT, 0u, gid, 1u);
e59678b2
DDM
122 if (r < 0)
123 return log_error_errno(r,
29ec4bce 124 "Failed to write mapping for "GID_FMT" to /proc/self/gid_map: %m",
e59678b2
DDM
125 gid);
126
127 return 0;
128}
129
bf3598be 130static int do_mcopy(const char *node, const char *root) {
fe5779cf 131 _cleanup_free_ char *mcopy = NULL;
bf3598be 132 _cleanup_strv_free_ char **argv = NULL;
254d1313 133 _cleanup_close_ int rfd = -EBADF;
c75cf016 134 _cleanup_free_ DirectoryEntries *de = NULL;
e59678b2 135 struct stat st;
bf3598be
DDM
136 int r;
137
138 assert(node);
139 assert(root);
140
141 /* Return early if there's nothing to copy. */
142 if (dir_is_empty(root, /*ignore_hidden_or_backup=*/ false))
143 return 0;
144
fe5779cf
DDM
145 r = find_executable("mcopy", &mcopy);
146 if (r == -ENOENT)
147 return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "Could not find mcopy binary.");
148 if (r < 0)
149 return log_error_errno(r, "Failed to determine whether mcopy binary exists: %m");
150
cf9c27b1 151 argv = strv_new(mcopy, "-s", "-p", "-Q", "-m", "-i", node);
bf3598be
DDM
152 if (!argv)
153 return log_oom();
154
155 /* mcopy copies the top level directory instead of everything in it so we have to pass all
156 * the subdirectories to mcopy instead to end up with the correct directory structure. */
157
c75cf016
DDM
158 rfd = open(root, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
159 if (rfd < 0)
160 return log_error_errno(errno, "Failed to open directory '%s': %m", root);
bf3598be 161
c75cf016
DDM
162 r = readdir_all(rfd, RECURSE_DIR_SORT|RECURSE_DIR_ENSURE_TYPE, &de);
163 if (r < 0)
164 return log_error_errno(r, "Failed to read '%s' contents: %m", root);
165
166 for (size_t i = 0; i < de->n_entries; i++) {
f3c8cb27
YW
167 _cleanup_free_ char *p = NULL;
168
169 p = path_join(root, de->entries[i]->d_name);
bf3598be
DDM
170 if (!p)
171 return log_oom();
172
c75cf016
DDM
173 if (!IN_SET(de->entries[i]->d_type, DT_REG, DT_DIR)) {
174 log_debug("%s is not a file/directory which are the only file types supported by vfat, ignoring", p);
175 continue;
176 }
177
bf3598be
DDM
178 r = strv_consume(&argv, TAKE_PTR(p));
179 if (r < 0)
180 return log_oom();
181 }
182
183 r = strv_extend(&argv, "::");
184 if (r < 0)
185 return log_oom();
186
c75cf016 187 if (fstat(rfd, &st) < 0)
e59678b2
DDM
188 return log_error_errno(errno, "Failed to stat '%s': %m", root);
189
a9abef7f 190 r = safe_fork("(mcopy)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_LOG|FORK_WAIT|FORK_STDOUT_TO_STDERR|FORK_NEW_USERNS|FORK_CLOSE_ALL_FDS, NULL);
bf3598be
DDM
191 if (r < 0)
192 return r;
193 if (r == 0) {
e59678b2
DDM
194 r = setup_userns(st.st_uid, st.st_gid);
195 if (r < 0)
196 _exit(EXIT_FAILURE);
197
bf3598be
DDM
198 /* Avoid failures caused by mismatch in expectations between mkfs.vfat and mcopy by disabling
199 * the stricter mcopy checks using MTOOLS_SKIP_CHECK. */
fe5779cf 200 execve(mcopy, argv, STRV_MAKE("MTOOLS_SKIP_CHECK=1"));
bf3598be
DDM
201
202 log_error_errno(errno, "Failed to execute mcopy: %m");
203
204 _exit(EXIT_FAILURE);
205 }
206
207 return 0;
208}
209
aa6aa81c
DDM
210static int protofile_print_item(
211 RecurseDirEvent event,
212 const char *path,
213 int dir_fd,
214 int inode_fd,
215 const struct dirent *de,
216 const struct statx *sx,
217 void *userdata) {
218
219 FILE *f = ASSERT_PTR(userdata);
aa6aa81c
DDM
220 int r;
221
222 if (event == RECURSE_DIR_LEAVE) {
48ac1fd1 223 fputs("$\n", f);
aa6aa81c
DDM
224 return 0;
225 }
226
227 if (!IN_SET(event, RECURSE_DIR_ENTER, RECURSE_DIR_ENTRY))
228 return RECURSE_DIR_CONTINUE;
229
aa6aa81c
DDM
230 char type = S_ISDIR(sx->stx_mode) ? 'd' :
231 S_ISREG(sx->stx_mode) ? '-' :
232 S_ISLNK(sx->stx_mode) ? 'l' :
233 S_ISFIFO(sx->stx_mode) ? 'p' :
234 S_ISBLK(sx->stx_mode) ? 'b' :
235 S_ISCHR(sx->stx_mode) ? 'c' : 0;
236 if (type == 0)
237 return RECURSE_DIR_CONTINUE;
238
239 fprintf(f, "%s %c%c%c%03o 0 0 ",
48ac1fd1 240 de->d_name,
aa6aa81c
DDM
241 type,
242 sx->stx_mode & S_ISUID ? 'u' : '-',
243 sx->stx_mode & S_ISGID ? 'g' : '-',
244 (unsigned) (sx->stx_mode & 0777));
245
246 if (S_ISREG(sx->stx_mode))
48ac1fd1 247 fputs(path, f);
aa6aa81c
DDM
248 else if (S_ISLNK(sx->stx_mode)) {
249 _cleanup_free_ char *p = NULL;
250
48ac1fd1 251 r = readlinkat_malloc(dir_fd, de->d_name, &p);
aa6aa81c
DDM
252 if (r < 0)
253 return log_error_errno(r, "Failed to read symlink %s: %m", path);
254
48ac1fd1 255 fputs(p, f);
aa6aa81c 256 } else if (S_ISBLK(sx->stx_mode) || S_ISCHR(sx->stx_mode))
48ac1fd1 257 fprintf(f, "%" PRIu32 " %" PRIu32, sx->stx_rdev_major, sx->stx_rdev_minor);
aa6aa81c 258
48ac1fd1 259 fputc('\n', f);
aa6aa81c
DDM
260
261 return RECURSE_DIR_CONTINUE;
262}
263
264static int make_protofile(const char *root, char **ret) {
265 _cleanup_fclose_ FILE *f = NULL;
48ac1fd1
DDM
266 _cleanup_(unlink_and_freep) char *p = NULL;
267 const char *vt;
aa6aa81c
DDM
268 int r;
269
270 assert(ret);
271
48ac1fd1
DDM
272 r = var_tmp_dir(&vt);
273 if (r < 0)
274 return log_error_errno(r, "Failed to get persistent temporary directory: %m");
275
276 r = fopen_temporary_child(vt, &f, &p);
aa6aa81c
DDM
277 if (r < 0)
278 return log_error_errno(r, "Failed to open temporary file: %m");
279
48ac1fd1
DDM
280 fputs("/\n"
281 "0 0\n"
282 "d--755 0 0\n", f);
aa6aa81c 283
c75cf016 284 r = recurse_dir_at(AT_FDCWD, root, STATX_TYPE|STATX_MODE, UINT_MAX, RECURSE_DIR_SORT, protofile_print_item, f);
aa6aa81c
DDM
285 if (r < 0)
286 return log_error_errno(r, "Failed to recurse through %s: %m", root);
287
48ac1fd1 288 fputs("$\n", f);
aa6aa81c
DDM
289
290 r = fflush_and_check(f);
291 if (r < 0)
292 return log_error_errno(r, "Failed to flush %s: %m", p);
293
294 *ret = TAKE_PTR(p);
295
296 return 0;
297}
298
c95f9a23
LP
299int make_filesystem(
300 const char *node,
301 const char *fstype,
302 const char *label,
7f55ad77 303 const char *root,
c95f9a23 304 sd_id128_t uuid,
8f30c00c
AD
305 bool discard,
306 char * const *extra_mkfs_args) {
c95f9a23 307
dc91c971 308 _cleanup_free_ char *mkfs = NULL, *mangled_label = NULL;
ddf615a1 309 _cleanup_strv_free_ char **argv = NULL;
aa6aa81c 310 _cleanup_(unlink_and_freep) char *protofile = NULL;
b7416360 311 char vol_id[CONST_MAX(SD_ID128_UUID_STRING_MAX, 8U + 1U)] = {};
e59678b2 312 struct stat st;
c95f9a23
LP
313 int r;
314
315 assert(node);
316 assert(fstype);
317 assert(label);
318
eb43379c
DDM
319 if (fstype_is_ro(fstype) && !root)
320 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
321 "Cannot generate read-only filesystem %s without a source tree.",
322 fstype);
323
c95f9a23 324 if (streq(fstype, "swap")) {
7f55ad77
DDM
325 if (root)
326 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
327 "A swap filesystem can't be populated, refusing");
f7bc0c32 328 r = find_executable("mkswap", &mkfs);
c95f9a23
LP
329 if (r == -ENOENT)
330 return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "mkswap binary not available.");
331 if (r < 0)
332 return log_error_errno(r, "Failed to determine whether mkswap binary exists: %m");
7f55ad77 333 } else if (streq(fstype, "squashfs")) {
7f55ad77
DDM
334 r = find_executable("mksquashfs", &mkfs);
335 if (r == -ENOENT)
336 return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "mksquashfs binary not available.");
337 if (r < 0)
338 return log_error_errno(r, "Failed to determine whether mksquashfs binary exists: %m");
09e917ea
LP
339
340 } else if (streq(fstype, "erofs")) {
341 r = find_executable("mkfs.erofs", &mkfs);
342 if (r == -ENOENT)
343 return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "mkfs.erofs binary not available.");
344 if (r < 0)
345 return log_error_errno(r, "Failed to determine whether mkfs.erofs binary exists: %m");
346
eaec6994
DDM
347 } else if (fstype_is_ro(fstype)) {
348 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
349 "Don't know how to create read-only file system '%s', refusing.",
350 fstype);
c95f9a23 351 } else {
59e2be46 352 if (root && !mkfs_supports_root_option(fstype))
7f55ad77 353 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
59e2be46 354 "Populating with source tree is not supported for %s", fstype);
c95f9a23
LP
355 r = mkfs_exists(fstype);
356 if (r < 0)
357 return log_error_errno(r, "Failed to determine whether mkfs binary for %s exists: %m", fstype);
358 if (r == 0)
359 return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "mkfs binary for %s is not available.", fstype);
360
361 mkfs = strjoin("mkfs.", fstype);
362 if (!mkfs)
363 return log_oom();
364 }
365
8d433a99
ZJS
366 if (STR_IN_SET(fstype, "ext2", "ext3", "ext4", "xfs", "swap")) {
367 size_t max_len =
368 streq(fstype, "xfs") ? 12 :
369 streq(fstype, "swap") ? 15 :
370 16;
371
372 r = mangle_linux_fs_label(label, max_len, &mangled_label);
7ffe593b 373 if (r < 0)
8d433a99 374 return log_error_errno(r, "Failed to determine volume label from string \"%s\": %m", label);
7ffe593b
ZJS
375 label = mangled_label;
376
377 } else if (streq(fstype, "vfat")) {
dc91c971
ZJS
378 r = mangle_fat_label(label, &mangled_label);
379 if (r < 0)
380 return log_error_errno(r, "Failed to determine FAT label from string \"%s\": %m", label);
4f05a11c
ZJS
381 label = mangled_label;
382
383 xsprintf(vol_id, "%08" PRIx32,
384 ((uint32_t) uuid.bytes[0] << 24) |
385 ((uint32_t) uuid.bytes[1] << 16) |
386 ((uint32_t) uuid.bytes[2] << 8) |
387 ((uint32_t) uuid.bytes[3])); /* Take first 32 bytes of UUID */
388 }
389
390 if (isempty(vol_id))
b7416360 391 assert_se(sd_id128_to_uuid_string(uuid, vol_id));
4f05a11c 392
ddf615a1 393 /* When changing this conditional, also adjust the log statement below. */
59e2be46 394 if (streq(fstype, "ext2")) {
ddf615a1
DDM
395 argv = strv_new(mkfs,
396 "-q",
397 "-L", label,
398 "-U", vol_id,
399 "-I", "256",
400 "-m", "0",
401 "-E", discard ? "discard,lazy_itable_init=1" : "nodiscard,lazy_itable_init=1",
402 node);
59e2be46
DDM
403 if (!argv)
404 return log_oom();
405
406 if (root) {
407 r = strv_extend_strv(&argv, STRV_MAKE("-d", root), false);
408 if (r < 0)
409 return log_oom();
410 }
ddf615a1 411
59e2be46 412 } else if (STR_IN_SET(fstype, "ext3", "ext4")) {
ddf615a1
DDM
413 argv = strv_new(mkfs,
414 "-q",
415 "-L", label,
416 "-U", vol_id,
417 "-I", "256",
418 "-O", "has_journal",
419 "-m", "0",
420 "-E", discard ? "discard,lazy_itable_init=1" : "nodiscard,lazy_itable_init=1",
421 node);
422
59e2be46
DDM
423 if (root) {
424 r = strv_extend_strv(&argv, STRV_MAKE("-d", root), false);
425 if (r < 0)
426 return log_oom();
427 }
428
429 } else if (streq(fstype, "btrfs")) {
ddf615a1
DDM
430 argv = strv_new(mkfs,
431 "-q",
432 "-L", label,
433 "-U", vol_id,
434 node);
435 if (!argv)
436 return log_oom();
437
438 if (!discard) {
439 r = strv_extend(&argv, "--nodiscard");
440 if (r < 0)
441 return log_oom();
442 }
443
59e2be46
DDM
444 if (root) {
445 r = strv_extend_strv(&argv, STRV_MAKE("-r", root), false);
446 if (r < 0)
447 return log_oom();
448 }
449
ddf615a1
DDM
450 } else if (streq(fstype, "f2fs")) {
451 argv = strv_new(mkfs,
452 "-q",
453 "-g", /* "default options" */
454 "-f", /* force override, without this it doesn't seem to want to write to an empty partition */
455 "-l", label,
456 "-U", vol_id,
457 "-t", one_zero(discard),
458 node);
459
460 } else if (streq(fstype, "xfs")) {
461 const char *j;
462
463 j = strjoina("uuid=", vol_id);
464
465 argv = strv_new(mkfs,
466 "-q",
467 "-L", label,
468 "-m", j,
469 "-m", "reflink=1",
470 node);
471 if (!argv)
472 return log_oom();
473
474 if (!discard) {
475 r = strv_extend(&argv, "-K");
476 if (r < 0)
477 return log_oom();
478 }
479
aa6aa81c
DDM
480 if (root) {
481 r = make_protofile(root, &protofile);
482 if (r < 0)
483 return r;
484
485 r = strv_extend_strv(&argv, STRV_MAKE("-p", protofile), false);
486 if (r < 0)
487 return log_oom();
488 }
489
ddf615a1
DDM
490 } else if (streq(fstype, "vfat"))
491
492 argv = strv_new(mkfs,
493 "-i", vol_id,
494 "-n", label,
495 "-F", "32", /* yes, we force FAT32 here */
496 node);
497
498 else if (streq(fstype, "swap"))
499 /* TODO: add --quiet here if
500 * https://github.com/util-linux/util-linux/issues/1499 resolved. */
501
502 argv = strv_new(mkfs,
503 "-L", label,
504 "-U", vol_id,
505 node);
506
507 else if (streq(fstype, "squashfs"))
508
509 argv = strv_new(mkfs,
510 root, node,
511 "-quiet",
512 "-noappend");
09e917ea
LP
513
514 else if (streq(fstype, "erofs"))
515
516 argv = strv_new(mkfs,
517 "-U", vol_id,
518 node, root);
ddf615a1
DDM
519 else
520 /* Generic fallback for all other file systems */
521 argv = strv_new(mkfs, node);
522
523 if (!argv)
524 return log_oom();
525
8f30c00c
AD
526 if (extra_mkfs_args) {
527 r = strv_extend_strv(&argv, extra_mkfs_args, false);
528 if (r < 0)
529 return log_oom();
530 }
531
e59678b2
DDM
532 if (root && stat(root, &st) < 0)
533 return log_error_errno(errno, "Failed to stat %s: %m", root);
534
a9abef7f 535 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|(root ? FORK_NEW_USERNS : 0), NULL);
c95f9a23
LP
536 if (r < 0)
537 return r;
538 if (r == 0) {
c95f9a23 539 /* Child */
4f05a11c 540
e59678b2
DDM
541 if (root) {
542 r = setup_userns(st.st_uid, st.st_gid);
543 if (r < 0)
544 _exit(EXIT_FAILURE);
545 }
546
ddf615a1 547 execvp(mkfs, argv);
c95f9a23
LP
548
549 log_error_errno(errno, "Failed to execute %s: %m", mkfs);
550
551 _exit(EXIT_FAILURE);
552 }
553
bf3598be
DDM
554 if (root && streq(fstype, "vfat")) {
555 r = do_mcopy(node, root);
556 if (r < 0)
557 return r;
558 }
559
2d96440f 560 if (STR_IN_SET(fstype, "ext2", "ext3", "ext4", "btrfs", "f2fs", "xfs", "vfat", "swap"))
4f05a11c
ZJS
561 log_info("%s successfully formatted as %s (label \"%s\", uuid %s)",
562 node, fstype, label, vol_id);
09e917ea
LP
563 else if (streq(fstype, "erofs"))
564 log_info("%s successfully formatted as %s (uuid %s, no label)",
565 node, fstype, vol_id);
4f05a11c
ZJS
566 else
567 log_info("%s successfully formatted as %s (no label or uuid specified)",
568 node, fstype);
569
c95f9a23
LP
570 return 0;
571}