1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
6 #include "dirent-util.h"
10 #include "id128-util.h"
11 #include "mkfs-util.h"
12 #include "mount-util.h"
13 #include "mountpoint-util.h"
14 #include "path-util.h"
15 #include "process-util.h"
16 #include "recurse-dir.h"
18 #include "stat-util.h"
19 #include "stdio-util.h"
20 #include "string-util.h"
21 #include "tmpfile-util.h"
24 int mkfs_exists(const char *fstype
) {
30 if (STR_IN_SET(fstype
, "auto", "swap")) /* these aren't real file system types, refuse early */
33 mkfs
= strjoina("mkfs.", fstype
);
34 if (!filename_is_valid(mkfs
)) /* refuse file system types with slashes and similar */
37 r
= find_executable(mkfs
, NULL
);
46 int mkfs_supports_root_option(const char *fstype
) {
47 return fstype_is_ro(fstype
) || STR_IN_SET(fstype
, "ext2", "ext3", "ext4", "btrfs", "vfat", "xfs");
50 static int mangle_linux_fs_label(const char *s
, size_t max_len
, char **ret
) {
51 /* Not more than max_len bytes (12 or 16) */
63 l
= utf8_encoded_valid_unichar(q
, SIZE_MAX
);
67 if ((size_t) (q
- s
+ l
) > max_len
)
72 ans
= memdup_suffix0(s
, q
- s
);
80 static int mangle_fat_label(const char *s
, char **ret
) {
83 _cleanup_free_
char *q
= NULL
;
86 r
= utf8_to_ascii(s
, '_', &q
);
90 /* Classic FAT only allows 11 character uppercase labels */
94 /* mkfs.vfat: Labels with characters *?.,;:/\|+=<>[]" are not allowed.
95 * Let's also replace any control chars. */
96 for (char *p
= q
; *p
; p
++)
97 if (strchr("*?.,;:/\\|+=<>[]\"", *p
) || char_is_cc(*p
))
104 static int do_mcopy(const char *node
, const char *root
) {
105 _cleanup_free_
char *mcopy
= NULL
;
106 _cleanup_strv_free_
char **argv
= NULL
;
107 _cleanup_close_
int rfd
= -EBADF
;
108 _cleanup_free_ DirectoryEntries
*de
= NULL
;
114 /* Return early if there's nothing to copy. */
115 if (dir_is_empty(root
, /*ignore_hidden_or_backup=*/ false))
118 r
= find_executable("mcopy", &mcopy
);
120 return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT
), "Could not find mcopy binary.");
122 return log_error_errno(r
, "Failed to determine whether mcopy binary exists: %m");
124 argv
= strv_new(mcopy
, "-s", "-p", "-Q", "-m", "-i", node
);
128 /* mcopy copies the top level directory instead of everything in it so we have to pass all
129 * the subdirectories to mcopy instead to end up with the correct directory structure. */
131 rfd
= open(root
, O_RDONLY
|O_DIRECTORY
|O_CLOEXEC
);
133 return log_error_errno(errno
, "Failed to open directory '%s': %m", root
);
135 r
= readdir_all(rfd
, RECURSE_DIR_SORT
|RECURSE_DIR_ENSURE_TYPE
, &de
);
137 return log_error_errno(r
, "Failed to read '%s' contents: %m", root
);
139 for (size_t i
= 0; i
< de
->n_entries
; i
++) {
140 _cleanup_free_
char *p
= NULL
;
142 p
= path_join(root
, de
->entries
[i
]->d_name
);
146 if (!IN_SET(de
->entries
[i
]->d_type
, DT_REG
, DT_DIR
)) {
147 log_debug("%s is not a file/directory which are the only file types supported by vfat, ignoring", p
);
151 if (strv_consume(&argv
, TAKE_PTR(p
)) < 0)
155 if (strv_extend(&argv
, "::") < 0)
158 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
);
162 /* Avoid failures caused by mismatch in expectations between mkfs.vfat and mcopy by disabling
163 * the stricter mcopy checks using MTOOLS_SKIP_CHECK. */
164 execve(mcopy
, argv
, STRV_MAKE("MTOOLS_SKIP_CHECK=1", strv_find_prefix(environ
, "SOURCE_DATE_EPOCH=")));
166 log_error_errno(errno
, "Failed to execute mcopy: %m");
174 typedef struct ProtofileData
{
176 bool has_filename_with_spaces
;
180 static int protofile_print_item(
181 RecurseDirEvent event
,
185 const struct dirent
*de
,
186 const struct statx
*sx
,
189 ProtofileData
*data
= ASSERT_PTR(userdata
);
190 _cleanup_free_
char *copy
= NULL
;
193 if (event
== RECURSE_DIR_LEAVE
) {
194 fputs("$\n", data
->file
);
198 if (!IN_SET(event
, RECURSE_DIR_ENTER
, RECURSE_DIR_ENTRY
))
199 return RECURSE_DIR_CONTINUE
;
201 char type
= S_ISDIR(sx
->stx_mode
) ? 'd' :
202 S_ISREG(sx
->stx_mode
) ? '-' :
203 S_ISLNK(sx
->stx_mode
) ? 'l' :
204 S_ISFIFO(sx
->stx_mode
) ? 'p' :
205 S_ISBLK(sx
->stx_mode
) ? 'b' :
206 S_ISCHR(sx
->stx_mode
) ? 'c' : 0;
208 return RECURSE_DIR_CONTINUE
;
210 /* The protofile format does not support spaces in filenames as whitespace is used as a token
211 * delimiter. To work around this limitation, mkfs.xfs allows escaping whitespace by using the /
212 * character (which isn't allowed in filenames and as such can be used to escape whitespace). See
213 * https://lore.kernel.org/linux-xfs/20230222090303.h6tujm7y32gjhgal@andromeda/T/#m8066b3e7d62a080ee7434faac4861d944e64493b
214 * for more information.*/
216 if (strchr(de
->d_name
, ' ')) {
217 copy
= strdup(de
->d_name
);
221 string_replace_char(copy
, ' ', '/');
222 data
->has_filename_with_spaces
= true;
225 fprintf(data
->file
, "%s %c%c%c%03o 0 0 ",
228 sx
->stx_mode
& S_ISUID
? 'u' : '-',
229 sx
->stx_mode
& S_ISGID
? 'g' : '-',
230 (unsigned) (sx
->stx_mode
& 0777));
232 if (S_ISREG(sx
->stx_mode
)) {
233 _cleanup_free_
char *p
= NULL
;
235 /* While we can escape whitespace in the filename, we cannot escape whitespace in the source
236 * path, so hack around that by creating a symlink to the path in a temporary directory and
237 * using the symlink as the source path instead. */
239 if (strchr(path
, ' ')) {
240 r
= tempfn_random_child(data
->tmpdir
, "mkfs-xfs", &p
);
242 return log_error_errno(r
, "Failed to generate random child name in %s: %m", data
->tmpdir
);
244 if (symlink(path
, p
) < 0)
245 return log_error_errno(errno
, "Failed to symlink %s to %s: %m", p
, path
);
248 fputs(p
?: path
, data
->file
);
249 } else if (S_ISLNK(sx
->stx_mode
)) {
250 _cleanup_free_
char *p
= NULL
;
252 r
= readlinkat_malloc(dir_fd
, de
->d_name
, &p
);
254 return log_error_errno(r
, "Failed to read symlink %s: %m", path
);
256 /* If we have a symlink to a path with whitespace in it, we're out of luck, as there's no way
257 * to encode that in the mkfs.xfs protofile format. */
260 return log_error_errno(r
, "Symlinks to paths containing whitespace are not supported by mkfs.xfs: %m");
262 fputs(p
, data
->file
);
263 } else if (S_ISBLK(sx
->stx_mode
) || S_ISCHR(sx
->stx_mode
))
264 fprintf(data
->file
, "%" PRIu32
" %" PRIu32
, sx
->stx_rdev_major
, sx
->stx_rdev_minor
);
266 fputc('\n', data
->file
);
268 return RECURSE_DIR_CONTINUE
;
271 static int make_protofile(const char *root
, char **ret_path
, bool *ret_has_filename_with_spaces
, char **ret_tmpdir
) {
272 _cleanup_(rm_rf_physical_and_freep
) char *tmpdir
= NULL
;
273 _cleanup_fclose_
FILE *f
= NULL
;
274 _cleanup_(unlink_and_freep
) char *p
= NULL
;
275 struct ProtofileData data
= {};
280 assert(ret_has_filename_with_spaces
);
283 r
= var_tmp_dir(&vt
);
285 return log_error_errno(r
, "Failed to get persistent temporary directory: %m");
287 r
= fopen_temporary_child(vt
, &f
, &p
);
289 return log_error_errno(r
, "Failed to open temporary file: %m");
291 /* Explicitly use /tmp here because this directory cannot have spaces its path. */
292 r
= mkdtemp_malloc("/tmp/systemd-mkfs-XXXXXX", &tmpdir
);
294 return log_error_errno(r
, "Failed to create temporary directory: %m");
297 data
.tmpdir
= tmpdir
;
303 r
= recurse_dir_at(AT_FDCWD
, root
, STATX_TYPE
|STATX_MODE
, UINT_MAX
, RECURSE_DIR_SORT
, protofile_print_item
, &data
);
305 return log_error_errno(r
, "Failed to recurse through %s: %m", root
);
309 r
= fflush_and_check(f
);
311 return log_error_errno(r
, "Failed to flush %s: %m", p
);
313 *ret_path
= TAKE_PTR(p
);
314 *ret_has_filename_with_spaces
= data
.has_filename_with_spaces
;
315 *ret_tmpdir
= TAKE_PTR(tmpdir
);
328 uint64_t sector_size
,
329 char * const *extra_mkfs_args
) {
331 _cleanup_free_
char *mkfs
= NULL
, *mangled_label
= NULL
;
332 _cleanup_strv_free_
char **argv
= NULL
, **env
= NULL
;
333 _cleanup_(rm_rf_physical_and_freep
) char *protofile_tmpdir
= NULL
;
334 _cleanup_(unlink_and_freep
) char *protofile
= NULL
;
335 char vol_id
[CONST_MAX(SD_ID128_UUID_STRING_MAX
, 8U + 1U)] = {};
336 int stdio_fds
[3] = { -EBADF
, STDERR_FILENO
, STDERR_FILENO
};
343 if (fstype_is_ro(fstype
) && !root
)
344 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
345 "Cannot generate read-only filesystem %s without a source tree.",
348 if (streq(fstype
, "swap")) {
350 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
351 "A swap filesystem can't be populated, refusing");
352 r
= find_executable("mkswap", &mkfs
);
354 return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT
), "mkswap binary not available.");
356 return log_error_errno(r
, "Failed to determine whether mkswap binary exists: %m");
357 } else if (streq(fstype
, "squashfs")) {
358 r
= find_executable("mksquashfs", &mkfs
);
360 return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT
), "mksquashfs binary not available.");
362 return log_error_errno(r
, "Failed to determine whether mksquashfs binary exists: %m");
364 } else if (streq(fstype
, "erofs")) {
365 r
= find_executable("mkfs.erofs", &mkfs
);
367 return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT
), "mkfs.erofs binary not available.");
369 return log_error_errno(r
, "Failed to determine whether mkfs.erofs binary exists: %m");
371 } else if (fstype_is_ro(fstype
)) {
372 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP
),
373 "Don't know how to create read-only file system '%s', refusing.",
376 if (root
&& !mkfs_supports_root_option(fstype
))
377 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP
),
378 "Populating with source tree is not supported for %s", fstype
);
379 r
= mkfs_exists(fstype
);
381 return log_error_errno(r
, "Failed to determine whether mkfs binary for %s exists: %m", fstype
);
383 return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT
), "mkfs binary for %s is not available.", fstype
);
385 mkfs
= strjoin("mkfs.", fstype
);
390 if (STR_IN_SET(fstype
, "ext2", "ext3", "ext4", "xfs", "swap")) {
392 streq(fstype
, "xfs") ? 12 :
393 streq(fstype
, "swap") ? 15 :
396 r
= mangle_linux_fs_label(label
, max_len
, &mangled_label
);
398 return log_error_errno(r
, "Failed to determine volume label from string \"%s\": %m", label
);
399 label
= mangled_label
;
401 } else if (streq(fstype
, "vfat")) {
402 r
= mangle_fat_label(label
, &mangled_label
);
404 return log_error_errno(r
, "Failed to determine FAT label from string \"%s\": %m", label
);
405 label
= mangled_label
;
407 xsprintf(vol_id
, "%08" PRIx32
,
408 ((uint32_t) uuid
.bytes
[0] << 24) |
409 ((uint32_t) uuid
.bytes
[1] << 16) |
410 ((uint32_t) uuid
.bytes
[2] << 8) |
411 ((uint32_t) uuid
.bytes
[3])); /* Take first 32 bytes of UUID */
415 assert_se(sd_id128_to_uuid_string(uuid
, vol_id
));
417 /* When changing this conditional, also adjust the log statement below. */
418 if (STR_IN_SET(fstype
, "ext2", "ext3", "ext4")) {
419 argv
= strv_new(mkfs
,
424 "-E", discard
? "discard,lazy_itable_init=1" : "nodiscard,lazy_itable_init=1",
429 if (root
&& strv_extend_strv(&argv
, STRV_MAKE("-d", root
), false) < 0)
432 if (quiet
&& strv_extend(&argv
, "-q") < 0)
435 if (sector_size
> 0) {
436 if (strv_extend(&env
, "MKE2FS_DEVICE_SECTSIZE") < 0)
439 if (strv_extendf(&env
, "%"PRIu64
, sector_size
) < 0)
443 } else if (streq(fstype
, "btrfs")) {
444 argv
= strv_new(mkfs
,
451 if (!discard
&& strv_extend(&argv
, "--nodiscard") < 0)
454 if (root
&& strv_extend_strv(&argv
, STRV_MAKE("-r", root
), false) < 0)
457 if (quiet
&& strv_extend(&argv
, "-q") < 0)
460 /* mkfs.btrfs unconditionally warns about several settings changing from v5.15 onwards which
461 * isn't silenced by "-q", so let's redirect stdout to /dev/null as well. */
463 stdio_fds
[1] = -EBADF
;
465 } else if (streq(fstype
, "f2fs")) {
466 argv
= strv_new(mkfs
,
467 "-g", /* "default options" */
468 "-f", /* force override, without this it doesn't seem to want to write to an empty partition */
471 "-t", one_zero(discard
),
474 if (quiet
&& strv_extend(&argv
, "-q") < 0)
477 if (sector_size
> 0) {
478 if (strv_extend(&argv
, "-w") < 0)
481 if (strv_extendf(&argv
, "%"PRIu64
, sector_size
) < 0)
485 } else if (streq(fstype
, "xfs")) {
488 j
= strjoina("uuid=", vol_id
);
490 argv
= strv_new(mkfs
,
498 if (!discard
&& strv_extend(&argv
, "-K") < 0)
502 bool has_filename_with_spaces
= false;
503 _cleanup_free_
char *protofile_with_opt
= NULL
;
505 r
= make_protofile(root
, &protofile
, &has_filename_with_spaces
, &protofile_tmpdir
);
509 /* Gross hack to make mkfs.xfs interpret slashes as spaces so we can encode filenames
510 * with spaces in the protofile format. */
511 if (has_filename_with_spaces
)
512 protofile_with_opt
= strjoin("slashes_are_spaces=1,", protofile
);
514 protofile_with_opt
= strdup(protofile
);
515 if (!protofile_with_opt
)
518 if (strv_extend_strv(&argv
, STRV_MAKE("-p", protofile_with_opt
), false) < 0)
522 if (sector_size
> 0) {
523 if (strv_extend(&argv
, "-s") < 0)
526 if (strv_extendf(&argv
, "size=%"PRIu64
, sector_size
) < 0)
530 if (quiet
&& strv_extend(&argv
, "-q") < 0)
533 } else if (streq(fstype
, "vfat")) {
535 argv
= strv_new(mkfs
,
538 "-F", "32", /* yes, we force FAT32 here */
541 if (sector_size
> 0) {
542 if (strv_extend(&argv
, "-S") < 0)
545 if (strv_extendf(&argv
, "%"PRIu64
, sector_size
) < 0)
549 /* mkfs.vfat does not have a --quiet option so let's redirect stdout to /dev/null instead. */
551 stdio_fds
[1] = -EBADF
;
553 } else if (streq(fstype
, "swap")) {
554 /* TODO: add --quiet once util-linux v2.38 is available everywhere. */
556 argv
= strv_new(mkfs
,
562 stdio_fds
[1] = -EBADF
;
564 } else if (streq(fstype
, "squashfs")) {
566 argv
= strv_new(mkfs
,
570 /* mksquashfs -quiet option is pretty new so let's redirect stdout to /dev/null instead. */
572 stdio_fds
[1] = -EBADF
;
574 } else if (streq(fstype
, "erofs")) {
576 argv
= strv_new(mkfs
,
580 if (quiet
&& strv_extend(&argv
, "--quiet") < 0)
584 /* Generic fallback for all other file systems */
585 argv
= strv_new(mkfs
, node
);
590 if (extra_mkfs_args
&& strv_extend_strv(&argv
, extra_mkfs_args
, false) < 0)
594 _cleanup_free_
char *j
= NULL
;
596 j
= strv_join(argv
, " ");
597 log_debug("Executing mkfs command: %s", strna(j
));
603 /*except_fds=*/ NULL
,
605 FORK_RESET_SIGNALS
|FORK_RLIMIT_NOFILE_SAFE
|FORK_DEATHSIG
|FORK_LOG
|FORK_WAIT
|
606 FORK_CLOSE_ALL_FDS
|FORK_REARRANGE_STDIO
|FORK_NEW_MOUNTNS
|FORK_REOPEN_LOG
,
613 STRV_FOREACH_PAIR(k
, v
, env
)
614 if (setenv(*k
, *v
, /* replace = */ true) < 0) {
615 log_error_errno(r
, "Failed to set %s=%s environment variable: %m", *k
, *v
);
619 /* mkfs.btrfs refuses to operate on block devices with mounted partitions, even if operating
620 * on unformatted free space, so let's trick it and other mkfs tools into thinking no
621 * partitions are mounted. See https://github.com/kdave/btrfs-progs/issues/640 for more
623 (void) mount_nofollow_verbose(LOG_DEBUG
, "/dev/null", "/proc/self/mounts", NULL
, MS_BIND
, NULL
);
627 log_error_errno(errno
, "Failed to execute %s: %m", mkfs
);
632 if (root
&& streq(fstype
, "vfat")) {
633 r
= do_mcopy(node
, root
);
638 if (STR_IN_SET(fstype
, "ext2", "ext3", "ext4", "btrfs", "f2fs", "xfs", "vfat", "swap"))
639 log_info("%s successfully formatted as %s (label \"%s\", uuid %s)",
640 node
, fstype
, label
, vol_id
);
641 else if (streq(fstype
, "erofs"))
642 log_info("%s successfully formatted as %s (uuid %s, no label)",
643 node
, fstype
, vol_id
);
645 log_info("%s successfully formatted as %s (no label or uuid specified)",
651 int mkfs_options_from_env(const char *component
, const char *fstype
, char ***ret
) {
652 _cleanup_strv_free_
char **l
= NULL
;
660 n
= strjoina("SYSTEMD_", component
, "_MKFS_OPTIONS_", fstype
);
661 e
= getenv(ascii_strupper(n
));
663 l
= strv_split(e
, NULL
);