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
= -1;
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
, "-b", "-s", "-p", "-Q", "-n", "-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 char *p
= path_join(root
, de
->entries
[i
]->d_name
);
171 if (!IN_SET(de
->entries
[i
]->d_type
, DT_REG
, DT_DIR
)) {
172 log_debug("%s is not a file/directory which are the only file types supported by vfat, ignoring", p
);
176 r
= strv_consume(&argv
, TAKE_PTR(p
));
181 r
= strv_extend(&argv
, "::");
185 if (fstat(rfd
, &st
) < 0)
186 return log_error_errno(errno
, "Failed to stat '%s': %m", root
);
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
);
192 r
= setup_userns(st
.st_uid
, st
.st_gid
);
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"));
200 log_error_errno(errno
, "Failed to execute mcopy: %m");
208 static int protofile_print_item(
209 RecurseDirEvent event
,
213 const struct dirent
*de
,
214 const struct statx
*sx
,
217 FILE *f
= ASSERT_PTR(userdata
);
220 if (event
== RECURSE_DIR_LEAVE
) {
225 if (!IN_SET(event
, RECURSE_DIR_ENTER
, RECURSE_DIR_ENTRY
))
226 return RECURSE_DIR_CONTINUE
;
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;
235 return RECURSE_DIR_CONTINUE
;
237 fprintf(f
, "%s %c%c%c%03o 0 0 ",
240 sx
->stx_mode
& S_ISUID
? 'u' : '-',
241 sx
->stx_mode
& S_ISGID
? 'g' : '-',
242 (unsigned) (sx
->stx_mode
& 0777));
244 if (S_ISREG(sx
->stx_mode
))
246 else if (S_ISLNK(sx
->stx_mode
)) {
247 _cleanup_free_
char *p
= NULL
;
249 r
= readlinkat_malloc(dir_fd
, de
->d_name
, &p
);
251 return log_error_errno(r
, "Failed to read symlink %s: %m", path
);
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
);
259 return RECURSE_DIR_CONTINUE
;
262 static int make_protofile(const char *root
, char **ret
) {
263 _cleanup_fclose_
FILE *f
= NULL
;
264 _cleanup_(unlink_and_freep
) char *p
= NULL
;
270 r
= var_tmp_dir(&vt
);
272 return log_error_errno(r
, "Failed to get persistent temporary directory: %m");
274 r
= fopen_temporary_child(vt
, &f
, &p
);
276 return log_error_errno(r
, "Failed to open temporary file: %m");
282 r
= recurse_dir_at(AT_FDCWD
, root
, STATX_TYPE
|STATX_MODE
, UINT_MAX
, RECURSE_DIR_SORT
, protofile_print_item
, f
);
284 return log_error_errno(r
, "Failed to recurse through %s: %m", root
);
288 r
= fflush_and_check(f
);
290 return log_error_errno(r
, "Failed to flush %s: %m", p
);
305 _cleanup_free_
char *mkfs
= NULL
, *mangled_label
= NULL
;
306 _cleanup_strv_free_
char **argv
= NULL
;
307 _cleanup_(unlink_and_freep
) char *protofile
= NULL
;
308 char vol_id
[CONST_MAX(SD_ID128_UUID_STRING_MAX
, 8U + 1U)] = {};
316 if (fstype_is_ro(fstype
) && !root
)
317 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
318 "Cannot generate read-only filesystem %s without a source tree.",
321 if (streq(fstype
, "swap")) {
323 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
324 "A swap filesystem can't be populated, refusing");
325 r
= find_executable("mkswap", &mkfs
);
327 return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT
), "mkswap binary not available.");
329 return log_error_errno(r
, "Failed to determine whether mkswap binary exists: %m");
330 } else if (streq(fstype
, "squashfs")) {
331 r
= find_executable("mksquashfs", &mkfs
);
333 return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT
), "mksquashfs binary not available.");
335 return log_error_errno(r
, "Failed to determine whether mksquashfs binary exists: %m");
336 } else if (fstype_is_ro(fstype
)) {
337 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP
),
338 "Don't know how to create read-only file system '%s', refusing.",
341 if (root
&& !mkfs_supports_root_option(fstype
))
342 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP
),
343 "Populating with source tree is not supported for %s", fstype
);
344 r
= mkfs_exists(fstype
);
346 return log_error_errno(r
, "Failed to determine whether mkfs binary for %s exists: %m", fstype
);
348 return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT
), "mkfs binary for %s is not available.", fstype
);
350 mkfs
= strjoin("mkfs.", fstype
);
355 if (STR_IN_SET(fstype
, "ext2", "ext3", "ext4", "xfs", "swap")) {
357 streq(fstype
, "xfs") ? 12 :
358 streq(fstype
, "swap") ? 15 :
361 r
= mangle_linux_fs_label(label
, max_len
, &mangled_label
);
363 return log_error_errno(r
, "Failed to determine volume label from string \"%s\": %m", label
);
364 label
= mangled_label
;
366 } else if (streq(fstype
, "vfat")) {
367 r
= mangle_fat_label(label
, &mangled_label
);
369 return log_error_errno(r
, "Failed to determine FAT label from string \"%s\": %m", label
);
370 label
= mangled_label
;
372 xsprintf(vol_id
, "%08" PRIx32
,
373 ((uint32_t) uuid
.bytes
[0] << 24) |
374 ((uint32_t) uuid
.bytes
[1] << 16) |
375 ((uint32_t) uuid
.bytes
[2] << 8) |
376 ((uint32_t) uuid
.bytes
[3])); /* Take first 32 bytes of UUID */
380 assert_se(sd_id128_to_uuid_string(uuid
, vol_id
));
382 /* When changing this conditional, also adjust the log statement below. */
383 if (streq(fstype
, "ext2")) {
384 argv
= strv_new(mkfs
,
390 "-E", discard
? "discard,lazy_itable_init=1" : "nodiscard,lazy_itable_init=1",
396 r
= strv_extend_strv(&argv
, STRV_MAKE("-d", root
), false);
401 } else if (STR_IN_SET(fstype
, "ext3", "ext4")) {
402 argv
= strv_new(mkfs
,
409 "-E", discard
? "discard,lazy_itable_init=1" : "nodiscard,lazy_itable_init=1",
413 r
= strv_extend_strv(&argv
, STRV_MAKE("-d", root
), false);
418 } else if (streq(fstype
, "btrfs")) {
419 argv
= strv_new(mkfs
,
428 r
= strv_extend(&argv
, "--nodiscard");
434 r
= strv_extend_strv(&argv
, STRV_MAKE("-r", root
), false);
439 } else if (streq(fstype
, "f2fs")) {
440 argv
= strv_new(mkfs
,
442 "-g", /* "default options" */
443 "-f", /* force override, without this it doesn't seem to want to write to an empty partition */
446 "-t", one_zero(discard
),
449 } else if (streq(fstype
, "xfs")) {
452 j
= strjoina("uuid=", vol_id
);
454 argv
= strv_new(mkfs
,
464 r
= strv_extend(&argv
, "-K");
470 r
= make_protofile(root
, &protofile
);
474 r
= strv_extend_strv(&argv
, STRV_MAKE("-p", protofile
), false);
479 } else if (streq(fstype
, "vfat"))
481 argv
= strv_new(mkfs
,
484 "-F", "32", /* yes, we force FAT32 here */
487 else if (streq(fstype
, "swap"))
488 /* TODO: add --quiet here if
489 * https://github.com/util-linux/util-linux/issues/1499 resolved. */
491 argv
= strv_new(mkfs
,
496 else if (streq(fstype
, "squashfs"))
498 argv
= strv_new(mkfs
,
503 /* Generic fallback for all other file systems */
504 argv
= strv_new(mkfs
, node
);
509 if (root
&& stat(root
, &st
) < 0)
510 return log_error_errno(errno
, "Failed to stat %s: %m", root
);
512 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
);
519 r
= setup_userns(st
.st_uid
, st
.st_gid
);
526 log_error_errno(errno
, "Failed to execute %s: %m", mkfs
);
531 if (root
&& streq(fstype
, "vfat")) {
532 r
= do_mcopy(node
, root
);
537 if (STR_IN_SET(fstype
, "ext2", "ext3", "ext4", "btrfs", "f2fs", "xfs", "vfat", "swap"))
538 log_info("%s successfully formatted as %s (label \"%s\", uuid %s)",
539 node
, fstype
, label
, vol_id
);
541 log_info("%s successfully formatted as %s (no label or uuid specified)",