/
niceSOFT
/
systemd
Обзор
Документация
Войти
/
niceSOFT
/
systemd
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/validatefs/validatefs.c
435 строк
15 KB
Yu Watanabe
tree-wide: drop DLOPEN_FOO() macros
17 июл 2026, 22:56
17 июл 2026, 22:56
661ab8b
Код
Авторство
О чём код?
/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "sd-device.h" #include "alloc-util.h" #include "blkid-util.h" #include "blockdev-util.h" #include "build.h" #include "chase.h" #include "device-util.h" #include "dlopen-note.h" #include "errno-util.h" #include "fd-util.h" #include "format-table.h" #include "gpt.h" #include "initrd-util.h" #include "log.h" #include "main-func.h" #include "mountpoint-util.h" #include "options.h" #include "parse-argument.h" #include "path-util.h" #include "pretty-print.h" #include "string-util.h" #include "strv.h" #include "utf8.h" #include "xattr-util.h" static char *arg_target = NULL; static char *arg_root = NULL; STATIC_DESTRUCTOR_REGISTER(arg_target, freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); static int help(void) { _cleanup_free_ char *link = NULL; _cleanup_(table_unrefp) Table *options = NULL; int r; r = terminal_urlify_man("systemd-validatefs@.service", "8", &link); if (r < 0) return log_oom(); r = option_parser_get_help_table(&options); if (r < 0) return r; printf("%s [OPTIONS...] /path/to/mountpoint\n" "\n%sCheck file system validation constraints.%s\n" "\nOptions:\n", program_invocation_short_name, ansi_highlight(), ansi_normal()); r = table_print_or_warn(options); if (r < 0) return r; printf("\nSee the %s for details.\n", link); return 0; } static int parse_argv(int argc, char *argv[]) { int r; assert(argc >= 0); assert(argv); OptionParser opts = { argc, argv }; FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { OPTION_COMMON_HELP: return help(); OPTION_COMMON_VERSION: return version(); OPTION_LONG("root", "PATH|auto", "Operate relative to the specified path"): if (streq(opts.arg, "auto")) r = free_and_strdup_warn(&arg_root, in_initrd() ? "/sysroot" : NULL); else { if (!path_is_absolute(opts.arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--root= argument must be 'auto' or absolute path, got: %s", opts.arg); r = parse_path_argument(opts.arg, /* suppress_root= */ true, &arg_root); } if (r < 0) return r; break; } char **args = option_parser_get_args(&opts); if (strv_length(args) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s expects exactly one argument (the mount point).", program_invocation_short_name); arg_target = strdup(args[0]); if (!arg_target) return log_oom(); if (arg_root && !path_startswith(arg_target, arg_root)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path '%s' does not start with specified root '%s', refusing.", arg_target, arg_root); return 1; } typedef struct ValidateFields { sd_id128_t *gpt_type_uuid; size_t n_gpt_type_uuid; char **gpt_label; char **mount_point; } ValidateFields; static void validate_fields_done(ValidateFields *f) { assert(f); free(f->gpt_type_uuid); strv_free(f->gpt_label); strv_free(f->mount_point); } static char* validate_fields_gpt_type_uuid_as_string(const ValidateFields *f) { _cleanup_free_ char *joined = NULL; assert(f); FOREACH_ARRAY(u, f->gpt_type_uuid, f->n_gpt_type_uuid) { if (!strextend_with_separator(&joined, ", ", SD_ID128_TO_UUID_STRING(*u))) return NULL; const char *id = gpt_partition_type_uuid_to_string(*u); if (id) (void) strextend(&joined, " (", id, ")"); } return TAKE_PTR(joined); } static int validate_fields_read(int fd, ValidateFields *ret) { _cleanup_(validate_fields_done) ValidateFields f = {}; int r; assert(fd >= 0); assert(ret); _cleanup_strv_free_ char **l = NULL; r = getxattr_at_strv(fd, /* path= */ NULL, "user.validatefs.gpt_type_uuid", AT_EMPTY_PATH, &l); if (r < 0) { if (r != -ENODATA && !ERRNO_IS_NOT_SUPPORTED(r)) return log_error_errno(r, "Failed to read 'user.validatefs.gpt_type_uuid' xattr: %m"); } else { STRV_FOREACH(i, l) { if (!GREEDY_REALLOC(f.gpt_type_uuid, f.n_gpt_type_uuid+1)) return log_oom(); r = sd_id128_from_string(*i, f.gpt_type_uuid + f.n_gpt_type_uuid); if (r < 0) return log_error_errno(r, "Failed to parse 'user.validatefs.gpt_type_uuid' xattr: %s", *i); f.n_gpt_type_uuid++; } } l = strv_free(l); r = getxattr_at_strv(fd, /* path= */ NULL, "user.validatefs.gpt_label", AT_EMPTY_PATH, &l); if (r < 0) { if (r != -ENODATA && !ERRNO_IS_NOT_SUPPORTED(r)) return log_error_errno(r, "Failed to read 'user.validatefs.gpt_label' xattr: %m"); } else { STRV_FOREACH(i, l) if (!utf8_is_valid(*i) || string_has_cc(*i, /* ok= */ NULL)) return log_error_errno( SYNTHETIC_ERRNO(EINVAL), "Extended attribute 'user.validatefs.gpt_label' contains invalid characters, refusing: %s", *i); f.gpt_label = TAKE_PTR(l); } l = strv_free(l); r = getxattr_at_strv(fd, /* path= */ NULL, "user.validatefs.mount_point", AT_EMPTY_PATH, &l); if (r < 0) { if (r != -ENODATA && !ERRNO_IS_NOT_SUPPORTED(r)) return log_error_errno(r, "Failed to read 'user.validatefs.mount_point' xattr: %m"); } else { STRV_FOREACH(i, l) if (!utf8_is_valid(*i) || string_has_cc(*i, /* ok= */ NULL) || !path_is_absolute(*i) || !path_is_normalized(*i)) return log_error_errno( SYNTHETIC_ERRNO(EINVAL), "Path listed in extended attribute 'user.validatefs.mount_point' is not a valid, normalized, absolute path or contains invalid characters, refusing: %s", *i); f.mount_point = TAKE_PTR(l); } r = f.n_gpt_type_uuid > 0 || !strv_isempty(f.gpt_label) || !strv_isempty(f.mount_point); *ret = TAKE_STRUCT(f); return r; } static int validate_mount_point(const char *path, const ValidateFields *f) { assert(path); assert(f); if (strv_isempty(f->mount_point)) return 0; STRV_FOREACH(i, f->mount_point) { _cleanup_free_ char *jj = NULL; const char *j; if (arg_root) { jj = path_join(arg_root, *i); if (!jj) return log_oom(); j = jj; } else j = *i; if (path_equal(path, j)) return 0; } _cleanup_free_ char *joined = strv_join(f->mount_point, ", "); return log_error_errno( SYNTHETIC_ERRNO(EPERM), "File system is supposed to be mounted on one of %s only, but is mounted on %s, refusing.", strna(joined), path); } static int validate_gpt_label(blkid_probe b, const ValidateFields *f) { assert(b); assert(f); if (strv_isempty(f->gpt_label)) return 0; const char *v = NULL; (void) sym_blkid_probe_lookup_value(b, "PART_ENTRY_NAME", &v, /* len= */ NULL); if (strv_contains(f->gpt_label, strempty(v))) return 0; _cleanup_free_ char *joined = strv_join(f->gpt_label, "', '"); return log_error_errno( SYNTHETIC_ERRNO(EPERM), "File system is supposed to be placed in a partition with labels '%s' only, but is placed in one labelled '%s', refusing.", strna(joined), strempty(v)); } static int validate_gpt_type(blkid_probe b, const ValidateFields *f) { assert(b); assert(f); if (f->n_gpt_type_uuid == 0) return 0; const char *v = NULL; (void) sym_blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, /* len= */ NULL); sd_id128_t id; if (!v || sd_id128_from_string(v, &id) < 0) { _cleanup_free_ char *joined = validate_fields_gpt_type_uuid_as_string(f); return log_error_errno( SYNTHETIC_ERRNO(EPERM), "File system is supposed to be placed in a partition of type UUIDs %s only, but has no type, refusing.", strna(joined)); } FOREACH_ARRAY(u, f->gpt_type_uuid, f->n_gpt_type_uuid) if (sd_id128_equal(*u, id)) return 0; _cleanup_free_ char *joined = validate_fields_gpt_type_uuid_as_string(f); return log_error_errno( SYNTHETIC_ERRNO(EPERM), "File system is supposed to be placed in a partition of type UUIDs %s only, but has type '%s', refusing.", strna(joined), SD_ID128_TO_UUID_STRING(id)); } static int validate_gpt_metadata_one(sd_device *d, const char *path, const ValidateFields *f) { int r; assert(d); assert(f); r = dlopen_libblkid(LOG_ERR); if (r < 0) return r; _cleanup_close_ int block_fd = sd_device_open(d, O_RDONLY|O_CLOEXEC|O_NONBLOCK); if (block_fd < 0) return log_error_errno(block_fd, "Failed to open block device backing '%s': %m", path); _cleanup_(blkid_free_probep) blkid_probe b = sym_blkid_new_probe(); if (!b) return log_oom(); errno = 0; r = sym_blkid_probe_set_device(b, block_fd, 0, 0); if (r != 0) return log_error_errno(errno_or_else(ENOMEM), "Failed to set up block device prober for '%s': %m", path); (void) sym_blkid_probe_enable_superblocks(b, 1); (void) sym_blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE|BLKID_SUBLKS_LABEL); (void) sym_blkid_probe_enable_partitions(b, 1); (void) sym_blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS); errno = 0; r = sym_blkid_do_safeprobe(b); if (r == _BLKID_SAFEPROBE_ERROR) return log_error_errno(errno_or_else(EIO), "Failed to probe block device of '%s': %m", path); if (r == _BLKID_SAFEPROBE_AMBIGUOUS) return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "Found multiple file system labels on block device '%s'.", path); if (r == _BLKID_SAFEPROBE_NOT_FOUND) return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "Found no file system label on block device '%s'.", path); assert(r == _BLKID_SAFEPROBE_FOUND); const char *v = NULL; (void) sym_blkid_probe_lookup_value(b, "PART_ENTRY_SCHEME", &v, /* len= */ NULL); if (!streq_ptr(v, "gpt")) return log_error_errno(SYNTHETIC_ERRNO(EPERM), "File system is supposed to be on a GPT partition table, but is not, refusing."); r = validate_gpt_label(b, f); if (r < 0) return r; r = validate_gpt_type(b, f); if (r < 0) return r; return 0; } static int validate_gpt_metadata(int fd, const char *path, const ValidateFields *f) { int r; assert(fd >= 0); assert(path); assert(f); if (strv_isempty(f->gpt_label) && f->n_gpt_type_uuid == 0) return 0; _cleanup_(sd_device_unrefp) sd_device *d = NULL; r = block_device_new_from_fd(fd, BLOCK_DEVICE_LOOKUP_BACKING, &d); if (r < 0) return log_error_errno(r, "Failed to find block device backing '%s': %m", path); /* Now validate all subordinate devices individually. */ bool have_slaves = false; const char *suffix; FOREACH_DEVICE_CHILD_WITH_SUFFIX(d, child, suffix) { if (!path_startswith(suffix, "slaves")) continue; have_slaves = true; r = validate_gpt_metadata_one(child, path, f); if (r < 0) return r; } /* If this device has no subordinate devices, then validate the device itself instead */ if (!have_slaves) { r = validate_gpt_metadata_one(d, path, f); if (r < 0) return r; } return 0; } static int validate_fields_check(int fd, const char *path, const ValidateFields *f) { int r; assert(fd >= 0); assert(path); assert(f); r = validate_mount_point(path, f); if (r < 0) return r; r = validate_gpt_metadata(fd, path, f); if (r < 0) return r; log_info("File system '%s' passed validation constraints, proceeding.", path); return 0; } static int run(int argc, char *argv[]) { int r; LIBBLKID_NOTE(required); log_setup(); r = parse_argv(argc, argv); if (r <= 0) return r; _cleanup_free_ char *resolved = NULL; _cleanup_close_ int target_fd = chase_and_open(arg_target, arg_root, CHASE_MUST_BE_DIRECTORY, O_DIRECTORY|O_CLOEXEC, &resolved); if (target_fd < 0) return log_error_errno(target_fd, "Failed to open directory '%s': %m", arg_target); r = is_mount_point_at(target_fd, /* path= */ NULL, /* flags= */ 0); if (r < 0) return log_error_errno(r, "Failed to determine whether '%s' is a mount point: %m", resolved); if (!r) return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "Directory '%s' is not a mount point.", resolved); _cleanup_(validate_fields_done) ValidateFields f = {}; r = validate_fields_read(target_fd, &f); if (r < 0) return r; if (r == 0) { log_info("File system '%s' has no validation constraints set, not validating.", resolved); return 0; } return validate_fields_check(target_fd, resolved, &f); } DEFINE_MAIN_FUNCTION(run);