]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/mkfs-util.c
Merge pull request #25168 from valentindavid/valentindavid/umount-move-recursive...
[thirdparty/systemd.git] / src / shared / mkfs-util.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <unistd.h>
4
5 #include "dirent-util.h"
6 #include "fd-util.h"
7 #include "fileio.h"
8 #include "fs-util.h"
9 #include "id128-util.h"
10 #include "mkfs-util.h"
11 #include "mountpoint-util.h"
12 #include "path-util.h"
13 #include "process-util.h"
14 #include "recurse-dir.h"
15 #include "stat-util.h"
16 #include "stdio-util.h"
17 #include "string-util.h"
18 #include "tmpfile-util.h"
19 #include "utf8.h"
20
21 int 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
34 r = find_executable(mkfs, NULL);
35 if (r == -ENOENT)
36 return false;
37 if (r < 0)
38 return r;
39
40 return true;
41 }
42
43 int mkfs_supports_root_option(const char *fstype) {
44 return fstype_is_ro(fstype) || STR_IN_SET(fstype, "ext2", "ext3", "ext4", "btrfs", "vfat", "xfs");
45 }
46
47 static 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
77 static 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
101 static 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,
121 GID_FMT " " GID_FMT " " GID_FMT, 0u, gid, 1u);
122 if (r < 0)
123 return log_error_errno(r,
124 "Failed to write mapping for "GID_FMT" to /proc/self/gid_map: %m",
125 gid);
126
127 return 0;
128 }
129
130 static int do_mcopy(const char *node, const char *root) {
131 _cleanup_free_ char *mcopy = NULL;
132 _cleanup_strv_free_ char **argv = NULL;
133 _cleanup_close_ int rfd = -EBADF;
134 _cleanup_free_ DirectoryEntries *de = NULL;
135 struct stat st;
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
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
151 argv = strv_new(mcopy, "-s", "-p", "-Q", "-m", "-i", node);
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
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);
161
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++) {
167 _cleanup_free_ char *p = NULL;
168
169 p = path_join(root, de->entries[i]->d_name);
170 if (!p)
171 return log_oom();
172
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
178 if (strv_consume(&argv, TAKE_PTR(p)) < 0)
179 return log_oom();
180 }
181
182 if (strv_extend(&argv, "::") < 0)
183 return log_oom();
184
185 if (fstat(rfd, &st) < 0)
186 return log_error_errno(errno, "Failed to stat '%s': %m", root);
187
188 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);
189 if (r < 0)
190 return r;
191 if (r == 0) {
192 r = setup_userns(st.st_uid, st.st_gid);
193 if (r < 0)
194 _exit(EXIT_FAILURE);
195
196 /* Avoid failures caused by mismatch in expectations between mkfs.vfat and mcopy by disabling
197 * the stricter mcopy checks using MTOOLS_SKIP_CHECK. */
198 execve(mcopy, argv, STRV_MAKE("MTOOLS_SKIP_CHECK=1"));
199
200 log_error_errno(errno, "Failed to execute mcopy: %m");
201
202 _exit(EXIT_FAILURE);
203 }
204
205 return 0;
206 }
207
208 static int protofile_print_item(
209 RecurseDirEvent event,
210 const char *path,
211 int dir_fd,
212 int inode_fd,
213 const struct dirent *de,
214 const struct statx *sx,
215 void *userdata) {
216
217 FILE *f = ASSERT_PTR(userdata);
218 int r;
219
220 if (event == RECURSE_DIR_LEAVE) {
221 fputs("$\n", f);
222 return 0;
223 }
224
225 if (!IN_SET(event, RECURSE_DIR_ENTER, RECURSE_DIR_ENTRY))
226 return RECURSE_DIR_CONTINUE;
227
228 char type = S_ISDIR(sx->stx_mode) ? 'd' :
229 S_ISREG(sx->stx_mode) ? '-' :
230 S_ISLNK(sx->stx_mode) ? 'l' :
231 S_ISFIFO(sx->stx_mode) ? 'p' :
232 S_ISBLK(sx->stx_mode) ? 'b' :
233 S_ISCHR(sx->stx_mode) ? 'c' : 0;
234 if (type == 0)
235 return RECURSE_DIR_CONTINUE;
236
237 fprintf(f, "%s %c%c%c%03o 0 0 ",
238 de->d_name,
239 type,
240 sx->stx_mode & S_ISUID ? 'u' : '-',
241 sx->stx_mode & S_ISGID ? 'g' : '-',
242 (unsigned) (sx->stx_mode & 0777));
243
244 if (S_ISREG(sx->stx_mode))
245 fputs(path, f);
246 else if (S_ISLNK(sx->stx_mode)) {
247 _cleanup_free_ char *p = NULL;
248
249 r = readlinkat_malloc(dir_fd, de->d_name, &p);
250 if (r < 0)
251 return log_error_errno(r, "Failed to read symlink %s: %m", path);
252
253 fputs(p, f);
254 } else if (S_ISBLK(sx->stx_mode) || S_ISCHR(sx->stx_mode))
255 fprintf(f, "%" PRIu32 " %" PRIu32, sx->stx_rdev_major, sx->stx_rdev_minor);
256
257 fputc('\n', f);
258
259 return RECURSE_DIR_CONTINUE;
260 }
261
262 static int make_protofile(const char *root, char **ret) {
263 _cleanup_fclose_ FILE *f = NULL;
264 _cleanup_(unlink_and_freep) char *p = NULL;
265 const char *vt;
266 int r;
267
268 assert(ret);
269
270 r = var_tmp_dir(&vt);
271 if (r < 0)
272 return log_error_errno(r, "Failed to get persistent temporary directory: %m");
273
274 r = fopen_temporary_child(vt, &f, &p);
275 if (r < 0)
276 return log_error_errno(r, "Failed to open temporary file: %m");
277
278 fputs("/\n"
279 "0 0\n"
280 "d--755 0 0\n", f);
281
282 r = recurse_dir_at(AT_FDCWD, root, STATX_TYPE|STATX_MODE, UINT_MAX, RECURSE_DIR_SORT, protofile_print_item, f);
283 if (r < 0)
284 return log_error_errno(r, "Failed to recurse through %s: %m", root);
285
286 fputs("$\n", f);
287
288 r = fflush_and_check(f);
289 if (r < 0)
290 return log_error_errno(r, "Failed to flush %s: %m", p);
291
292 *ret = TAKE_PTR(p);
293
294 return 0;
295 }
296
297 int make_filesystem(
298 const char *node,
299 const char *fstype,
300 const char *label,
301 const char *root,
302 sd_id128_t uuid,
303 bool discard,
304 uint64_t sector_size,
305 char * const *extra_mkfs_args) {
306
307 _cleanup_free_ char *mkfs = NULL, *mangled_label = NULL;
308 _cleanup_strv_free_ char **argv = NULL;
309 _cleanup_(unlink_and_freep) char *protofile = NULL;
310 char vol_id[CONST_MAX(SD_ID128_UUID_STRING_MAX, 8U + 1U)] = {};
311 struct stat st;
312 int r;
313
314 assert(node);
315 assert(fstype);
316 assert(label);
317
318 if (fstype_is_ro(fstype) && !root)
319 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
320 "Cannot generate read-only filesystem %s without a source tree.",
321 fstype);
322
323 if (streq(fstype, "swap")) {
324 if (root)
325 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
326 "A swap filesystem can't be populated, refusing");
327 r = find_executable("mkswap", &mkfs);
328 if (r == -ENOENT)
329 return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "mkswap binary not available.");
330 if (r < 0)
331 return log_error_errno(r, "Failed to determine whether mkswap binary exists: %m");
332 } else if (streq(fstype, "squashfs")) {
333 r = find_executable("mksquashfs", &mkfs);
334 if (r == -ENOENT)
335 return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "mksquashfs binary not available.");
336 if (r < 0)
337 return log_error_errno(r, "Failed to determine whether mksquashfs binary exists: %m");
338
339 } else if (streq(fstype, "erofs")) {
340 r = find_executable("mkfs.erofs", &mkfs);
341 if (r == -ENOENT)
342 return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "mkfs.erofs binary not available.");
343 if (r < 0)
344 return log_error_errno(r, "Failed to determine whether mkfs.erofs binary exists: %m");
345
346 } else if (fstype_is_ro(fstype)) {
347 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
348 "Don't know how to create read-only file system '%s', refusing.",
349 fstype);
350 } else {
351 if (root && !mkfs_supports_root_option(fstype))
352 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
353 "Populating with source tree is not supported for %s", fstype);
354 r = mkfs_exists(fstype);
355 if (r < 0)
356 return log_error_errno(r, "Failed to determine whether mkfs binary for %s exists: %m", fstype);
357 if (r == 0)
358 return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "mkfs binary for %s is not available.", fstype);
359
360 mkfs = strjoin("mkfs.", fstype);
361 if (!mkfs)
362 return log_oom();
363 }
364
365 if (STR_IN_SET(fstype, "ext2", "ext3", "ext4", "xfs", "swap")) {
366 size_t max_len =
367 streq(fstype, "xfs") ? 12 :
368 streq(fstype, "swap") ? 15 :
369 16;
370
371 r = mangle_linux_fs_label(label, max_len, &mangled_label);
372 if (r < 0)
373 return log_error_errno(r, "Failed to determine volume label from string \"%s\": %m", label);
374 label = mangled_label;
375
376 } else if (streq(fstype, "vfat")) {
377 r = mangle_fat_label(label, &mangled_label);
378 if (r < 0)
379 return log_error_errno(r, "Failed to determine FAT label from string \"%s\": %m", label);
380 label = mangled_label;
381
382 xsprintf(vol_id, "%08" PRIx32,
383 ((uint32_t) uuid.bytes[0] << 24) |
384 ((uint32_t) uuid.bytes[1] << 16) |
385 ((uint32_t) uuid.bytes[2] << 8) |
386 ((uint32_t) uuid.bytes[3])); /* Take first 32 bytes of UUID */
387 }
388
389 if (isempty(vol_id))
390 assert_se(sd_id128_to_uuid_string(uuid, vol_id));
391
392 /* When changing this conditional, also adjust the log statement below. */
393 if (streq(fstype, "ext2")) {
394 argv = strv_new(mkfs,
395 "-q",
396 "-L", label,
397 "-U", vol_id,
398 "-I", "256",
399 "-m", "0",
400 "-E", discard ? "discard,lazy_itable_init=1" : "nodiscard,lazy_itable_init=1",
401 "-b", "4096",
402 node);
403 if (!argv)
404 return log_oom();
405
406 if (root && strv_extend_strv(&argv, STRV_MAKE("-d", root), false) < 0)
407 return log_oom();
408
409 } else if (STR_IN_SET(fstype, "ext3", "ext4")) {
410 argv = strv_new(mkfs,
411 "-q",
412 "-L", label,
413 "-U", vol_id,
414 "-I", "256",
415 "-O", "has_journal",
416 "-m", "0",
417 "-E", discard ? "discard,lazy_itable_init=1" : "nodiscard,lazy_itable_init=1",
418 "-b", "4096",
419 node);
420
421 if (root && strv_extend_strv(&argv, STRV_MAKE("-d", root), false) < 0)
422 return log_oom();
423
424 } else if (streq(fstype, "btrfs")) {
425 argv = strv_new(mkfs,
426 "-q",
427 "-L", label,
428 "-U", vol_id,
429 node);
430 if (!argv)
431 return log_oom();
432
433 if (!discard && strv_extend(&argv, "--nodiscard") < 0)
434 return log_oom();
435
436 if (root && strv_extend_strv(&argv, STRV_MAKE("-r", root), false) < 0)
437 return log_oom();
438
439 } else if (streq(fstype, "f2fs")) {
440 argv = strv_new(mkfs,
441 "-q",
442 "-g", /* "default options" */
443 "-f", /* force override, without this it doesn't seem to want to write to an empty partition */
444 "-l", label,
445 "-U", vol_id,
446 "-t", one_zero(discard),
447 node);
448
449 } else if (streq(fstype, "xfs")) {
450 const char *j;
451
452 j = strjoina("uuid=", vol_id);
453
454 argv = strv_new(mkfs,
455 "-q",
456 "-L", label,
457 "-m", j,
458 "-m", "reflink=1",
459 node);
460 if (!argv)
461 return log_oom();
462
463 if (!discard && strv_extend(&argv, "-K") < 0)
464 return log_oom();
465
466 if (root) {
467 r = make_protofile(root, &protofile);
468 if (r < 0)
469 return r;
470
471 if (strv_extend_strv(&argv, STRV_MAKE("-p", protofile), false) < 0)
472 return log_oom();
473 }
474
475 if (sector_size > 0) {
476 if (strv_extend(&argv, "-s") < 0)
477 return log_oom();
478
479 if (strv_extendf(&argv, "size=%"PRIu64, sector_size) < 0)
480 return log_oom();
481 }
482
483 } else if (streq(fstype, "vfat")) {
484
485 argv = strv_new(mkfs,
486 "-i", vol_id,
487 "-n", label,
488 "-F", "32", /* yes, we force FAT32 here */
489 node);
490
491 if (sector_size > 0) {
492 if (strv_extend(&argv, "-S") < 0)
493 return log_oom();
494
495 if (strv_extendf(&argv, "%"PRIu64, sector_size) < 0)
496 return log_oom();
497 }
498
499 } else if (streq(fstype, "swap"))
500 /* TODO: add --quiet here if
501 * https://github.com/util-linux/util-linux/issues/1499 resolved. */
502
503 argv = strv_new(mkfs,
504 "-L", label,
505 "-U", vol_id,
506 node);
507
508 else if (streq(fstype, "squashfs"))
509
510 argv = strv_new(mkfs,
511 root, node,
512 "-quiet",
513 "-noappend");
514
515 else if (streq(fstype, "erofs"))
516
517 argv = strv_new(mkfs,
518 "-U", vol_id,
519 node, root);
520 else
521 /* Generic fallback for all other file systems */
522 argv = strv_new(mkfs, node);
523
524 if (!argv)
525 return log_oom();
526
527 if (extra_mkfs_args && strv_extend_strv(&argv, extra_mkfs_args, false) < 0)
528 return log_oom();
529
530 if (root && stat(root, &st) < 0)
531 return log_error_errno(errno, "Failed to stat %s: %m", root);
532
533 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);
534 if (r < 0)
535 return r;
536 if (r == 0) {
537 /* Child */
538
539 if (root) {
540 r = setup_userns(st.st_uid, st.st_gid);
541 if (r < 0)
542 _exit(EXIT_FAILURE);
543 }
544
545 execvp(mkfs, argv);
546
547 log_error_errno(errno, "Failed to execute %s: %m", mkfs);
548
549 _exit(EXIT_FAILURE);
550 }
551
552 if (root && streq(fstype, "vfat")) {
553 r = do_mcopy(node, root);
554 if (r < 0)
555 return r;
556 }
557
558 if (STR_IN_SET(fstype, "ext2", "ext3", "ext4", "btrfs", "f2fs", "xfs", "vfat", "swap"))
559 log_info("%s successfully formatted as %s (label \"%s\", uuid %s)",
560 node, fstype, label, vol_id);
561 else if (streq(fstype, "erofs"))
562 log_info("%s successfully formatted as %s (uuid %s, no label)",
563 node, fstype, vol_id);
564 else
565 log_info("%s successfully formatted as %s (no label or uuid specified)",
566 node, fstype);
567
568 return 0;
569 }