1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
5 #include "dirent-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"
21 int mkfs_exists(const char *fstype
) {
27 if (STR_IN_SET(fstype
, "auto", "swap")) /* these aren't real file system types, refuse early */
30 mkfs
= strjoina("mkfs.", fstype
);
31 if (!filename_is_valid(mkfs
)) /* refuse file system types with slashes and similar */
34 r
= find_executable(mkfs
, NULL
);
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");
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) */
60 l
= utf8_encoded_valid_unichar(q
, SIZE_MAX
);
64 if ((size_t) (q
- s
+ l
) > max_len
)
69 ans
= memdup_suffix0(s
, q
- s
);
77 static int mangle_fat_label(const char *s
, char **ret
) {
80 _cleanup_free_
char *q
= NULL
;
83 r
= utf8_to_ascii(s
, '_', &q
);
87 /* Classic FAT only allows 11 character uppercase labels */
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
))
101 static int setup_userns(uid_t uid
, gid_t gid
) {
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. */
109 r
= write_string_filef("/proc/self/uid_map", WRITE_STRING_FILE_DISABLE_BUFFER
,
110 UID_FMT
" " UID_FMT
" " UID_FMT
, 0u, uid
, 1u);
112 return log_error_errno(r
,
113 "Failed to write mapping for "UID_FMT
" to /proc/self/uid_map: %m",
116 r
= write_string_file("/proc/self/setgroups", "deny", WRITE_STRING_FILE_DISABLE_BUFFER
);
118 return log_error_errno(r
, "Failed to write 'deny' to /proc/self/setgroups: %m");
120 r
= write_string_filef("/proc/self/gid_map", WRITE_STRING_FILE_DISABLE_BUFFER
,
121 GID_FMT
" " GID_FMT
" " GID_FMT
, 0u, gid
, 1u);
123 return log_error_errno(r
,
124 "Failed to write mapping for "GID_FMT
" to /proc/self/gid_map: %m",
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
;
141 /* Return early if there's nothing to copy. */
142 if (dir_is_empty(root
, /*ignore_hidden_or_backup=*/ false))
145 r
= find_executable("mcopy", &mcopy
);
147 return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT
), "Could not find mcopy binary.");
149 return log_error_errno(r
, "Failed to determine whether mcopy binary exists: %m");
151 argv
= strv_new(mcopy
, "-s", "-p", "-Q", "-m", "-i", node
);
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. */
158 rfd
= open(root
, O_RDONLY
|O_DIRECTORY
|O_CLOEXEC
);
160 return log_error_errno(errno
, "Failed to open directory '%s': %m", root
);
162 r
= readdir_all(rfd
, RECURSE_DIR_SORT
|RECURSE_DIR_ENSURE_TYPE
, &de
);
164 return log_error_errno(r
, "Failed to read '%s' contents: %m", root
);
166 for (size_t i
= 0; i
< de
->n_entries
; i
++) {
167 _cleanup_free_
char *p
= NULL
;
169 p
= path_join(root
, de
->entries
[i
]->d_name
);
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
);
178 r
= strv_consume(&argv
, TAKE_PTR(p
));
183 r
= strv_extend(&argv
, "::");
187 if (fstat(rfd
, &st
) < 0)
188 return log_error_errno(errno
, "Failed to stat '%s': %m", root
);
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
);
194 r
= setup_userns(st
.st_uid
, st
.st_gid
);
198 /* Avoid failures caused by mismatch in expectations between mkfs.vfat and mcopy by disabling
199 * the stricter mcopy checks using MTOOLS_SKIP_CHECK. */
200 execve(mcopy
, argv
, STRV_MAKE("MTOOLS_SKIP_CHECK=1"));
202 log_error_errno(errno
, "Failed to execute mcopy: %m");
210 static int protofile_print_item(
211 RecurseDirEvent event
,
215 const struct dirent
*de
,
216 const struct statx
*sx
,
219 FILE *f
= ASSERT_PTR(userdata
);
222 if (event
== RECURSE_DIR_LEAVE
) {
227 if (!IN_SET(event
, RECURSE_DIR_ENTER
, RECURSE_DIR_ENTRY
))
228 return RECURSE_DIR_CONTINUE
;
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;
237 return RECURSE_DIR_CONTINUE
;
239 fprintf(f
, "%s %c%c%c%03o 0 0 ",
242 sx
->stx_mode
& S_ISUID
? 'u' : '-',
243 sx
->stx_mode
& S_ISGID
? 'g' : '-',
244 (unsigned) (sx
->stx_mode
& 0777));
246 if (S_ISREG(sx
->stx_mode
))
248 else if (S_ISLNK(sx
->stx_mode
)) {
249 _cleanup_free_
char *p
= NULL
;
251 r
= readlinkat_malloc(dir_fd
, de
->d_name
, &p
);
253 return log_error_errno(r
, "Failed to read symlink %s: %m", path
);
256 } else if (S_ISBLK(sx
->stx_mode
) || S_ISCHR(sx
->stx_mode
))
257 fprintf(f
, "%" PRIu32
" %" PRIu32
, sx
->stx_rdev_major
, sx
->stx_rdev_minor
);
261 return RECURSE_DIR_CONTINUE
;
264 static int make_protofile(const char *root
, char **ret
) {
265 _cleanup_fclose_
FILE *f
= NULL
;
266 _cleanup_(unlink_and_freep
) char *p
= NULL
;
272 r
= var_tmp_dir(&vt
);
274 return log_error_errno(r
, "Failed to get persistent temporary directory: %m");
276 r
= fopen_temporary_child(vt
, &f
, &p
);
278 return log_error_errno(r
, "Failed to open temporary file: %m");
284 r
= recurse_dir_at(AT_FDCWD
, root
, STATX_TYPE
|STATX_MODE
, UINT_MAX
, RECURSE_DIR_SORT
, protofile_print_item
, f
);
286 return log_error_errno(r
, "Failed to recurse through %s: %m", root
);
290 r
= fflush_and_check(f
);
292 return log_error_errno(r
, "Failed to flush %s: %m", p
);
306 char * const *extra_mkfs_args
) {
308 _cleanup_free_
char *mkfs
= NULL
, *mangled_label
= NULL
;
309 _cleanup_strv_free_
char **argv
= NULL
;
310 _cleanup_(unlink_and_freep
) char *protofile
= NULL
;
311 char vol_id
[CONST_MAX(SD_ID128_UUID_STRING_MAX
, 8U + 1U)] = {};
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.",
324 if (streq(fstype
, "swap")) {
326 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
327 "A swap filesystem can't be populated, refusing");
328 r
= find_executable("mkswap", &mkfs
);
330 return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT
), "mkswap binary not available.");
332 return log_error_errno(r
, "Failed to determine whether mkswap binary exists: %m");
333 } else if (streq(fstype
, "squashfs")) {
334 r
= find_executable("mksquashfs", &mkfs
);
336 return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT
), "mksquashfs binary not available.");
338 return log_error_errno(r
, "Failed to determine whether mksquashfs binary exists: %m");
340 } else if (streq(fstype
, "erofs")) {
341 r
= find_executable("mkfs.erofs", &mkfs
);
343 return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT
), "mkfs.erofs binary not available.");
345 return log_error_errno(r
, "Failed to determine whether mkfs.erofs binary exists: %m");
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.",
352 if (root
&& !mkfs_supports_root_option(fstype
))
353 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP
),
354 "Populating with source tree is not supported for %s", fstype
);
355 r
= mkfs_exists(fstype
);
357 return log_error_errno(r
, "Failed to determine whether mkfs binary for %s exists: %m", fstype
);
359 return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT
), "mkfs binary for %s is not available.", fstype
);
361 mkfs
= strjoin("mkfs.", fstype
);
366 if (STR_IN_SET(fstype
, "ext2", "ext3", "ext4", "xfs", "swap")) {
368 streq(fstype
, "xfs") ? 12 :
369 streq(fstype
, "swap") ? 15 :
372 r
= mangle_linux_fs_label(label
, max_len
, &mangled_label
);
374 return log_error_errno(r
, "Failed to determine volume label from string \"%s\": %m", label
);
375 label
= mangled_label
;
377 } else if (streq(fstype
, "vfat")) {
378 r
= mangle_fat_label(label
, &mangled_label
);
380 return log_error_errno(r
, "Failed to determine FAT label from string \"%s\": %m", label
);
381 label
= mangled_label
;
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 */
391 assert_se(sd_id128_to_uuid_string(uuid
, vol_id
));
393 /* When changing this conditional, also adjust the log statement below. */
394 if (streq(fstype
, "ext2")) {
395 argv
= strv_new(mkfs
,
401 "-E", discard
? "discard,lazy_itable_init=1" : "nodiscard,lazy_itable_init=1",
407 r
= strv_extend_strv(&argv
, STRV_MAKE("-d", root
), false);
412 } else if (STR_IN_SET(fstype
, "ext3", "ext4")) {
413 argv
= strv_new(mkfs
,
420 "-E", discard
? "discard,lazy_itable_init=1" : "nodiscard,lazy_itable_init=1",
424 r
= strv_extend_strv(&argv
, STRV_MAKE("-d", root
), false);
429 } else if (streq(fstype
, "btrfs")) {
430 argv
= strv_new(mkfs
,
439 r
= strv_extend(&argv
, "--nodiscard");
445 r
= strv_extend_strv(&argv
, STRV_MAKE("-r", root
), false);
450 } else if (streq(fstype
, "f2fs")) {
451 argv
= strv_new(mkfs
,
453 "-g", /* "default options" */
454 "-f", /* force override, without this it doesn't seem to want to write to an empty partition */
457 "-t", one_zero(discard
),
460 } else if (streq(fstype
, "xfs")) {
463 j
= strjoina("uuid=", vol_id
);
465 argv
= strv_new(mkfs
,
475 r
= strv_extend(&argv
, "-K");
481 r
= make_protofile(root
, &protofile
);
485 r
= strv_extend_strv(&argv
, STRV_MAKE("-p", protofile
), false);
490 } else if (streq(fstype
, "vfat"))
492 argv
= strv_new(mkfs
,
495 "-F", "32", /* yes, we force FAT32 here */
498 else if (streq(fstype
, "swap"))
499 /* TODO: add --quiet here if
500 * https://github.com/util-linux/util-linux/issues/1499 resolved. */
502 argv
= strv_new(mkfs
,
507 else if (streq(fstype
, "squashfs"))
509 argv
= strv_new(mkfs
,
514 else if (streq(fstype
, "erofs"))
516 argv
= strv_new(mkfs
,
520 /* Generic fallback for all other file systems */
521 argv
= strv_new(mkfs
, node
);
526 if (extra_mkfs_args
) {
527 r
= strv_extend_strv(&argv
, extra_mkfs_args
, false);
532 if (root
&& stat(root
, &st
) < 0)
533 return log_error_errno(errno
, "Failed to stat %s: %m", root
);
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
);
542 r
= setup_userns(st
.st_uid
, st
.st_gid
);
549 log_error_errno(errno
, "Failed to execute %s: %m", mkfs
);
554 if (root
&& streq(fstype
, "vfat")) {
555 r
= do_mcopy(node
, root
);
560 if (STR_IN_SET(fstype
, "ext2", "ext3", "ext4", "btrfs", "f2fs", "xfs", "vfat", "swap"))
561 log_info("%s successfully formatted as %s (label \"%s\", uuid %s)",
562 node
, fstype
, label
, vol_id
);
563 else if (streq(fstype
, "erofs"))
564 log_info("%s successfully formatted as %s (uuid %s, no label)",
565 node
, fstype
, vol_id
);
567 log_info("%s successfully formatted as %s (no label or uuid specified)",