diff options
Diffstat (limited to 'src')
43 files changed, 702 insertions, 390 deletions
diff --git a/src/basic/chase.c b/src/basic/chase.c index 600e2b9d33..a4d9edb4c9 100644 --- a/src/basic/chase.c +++ b/src/basic/chase.c @@ -81,7 +81,6 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int const char *todo; int r; - assert(path); assert(!FLAGS_SET(flags, CHASE_PREFIX_ROOT)); assert(!FLAGS_SET(flags, CHASE_STEP|CHASE_EXTRACT_FILENAME)); assert(!FLAGS_SET(flags, CHASE_TRAIL_SLASH|CHASE_EXTRACT_FILENAME)); @@ -618,11 +617,51 @@ int chaseat_prefix_root(const char *path, const char *root, char **ret) { return 0; } +int chase_extract_filename(const char *path, const char *root, char **ret) { + int r; + + /* This is similar to path_extract_filename(), but takes root directory. + * The result should be consistent with chase() with CHASE_EXTRACT_FILENAME. */ + + assert(path); + assert(ret); + + if (isempty(path)) + return -EINVAL; + + if (!path_is_absolute(path)) + return -EINVAL; + + if (!empty_or_root(root)) { + _cleanup_free_ char *root_abs = NULL; + + r = path_make_absolute_cwd(root, &root_abs); + if (r < 0) + return r; + + path = path_startswith(path, root_abs); + if (!path) + return -EINVAL; + } + + if (!isempty(path)) { + r = path_extract_filename(path, ret); + if (r != -EADDRNOTAVAIL) + return r; + } + + char *fname = strdup("."); + if (!fname) + return -ENOMEM; + + *ret = fname; + return 0; +} + int chase_and_open(const char *path, const char *root, ChaseFlags chase_flags, int open_flags, char **ret_path) { _cleanup_close_ int path_fd = -EBADF; _cleanup_free_ char *p = NULL, *fname = NULL; mode_t mode = open_flags & O_DIRECTORY ? 0755 : 0644; - const char *q; int r; assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); @@ -640,13 +679,10 @@ int chase_and_open(const char *path, const char *root, ChaseFlags chase_flags, i return r; assert(path_fd >= 0); - assert_se(q = path_startswith(p, empty_to_root(root))); - if (isempty(q)) - q = "."; - - if (!FLAGS_SET(chase_flags, CHASE_PARENT)) { - r = path_extract_filename(q, &fname); - if (r < 0 && r != -EADDRNOTAVAIL) + if (!FLAGS_SET(chase_flags, CHASE_PARENT) && + !FLAGS_SET(chase_flags, CHASE_EXTRACT_FILENAME)) { + r = chase_extract_filename(p, root, &fname); + if (r < 0) return r; } diff --git a/src/basic/chase.h b/src/basic/chase.h index f37e836822..cfc714b9f7 100644 --- a/src/basic/chase.h +++ b/src/basic/chase.h @@ -43,6 +43,7 @@ bool unsafe_transition(const struct stat *a, const struct stat *b); int chase(const char *path_with_prefix, const char *root, ChaseFlags chase_flags, char **ret_path, int *ret_fd); int chaseat_prefix_root(const char *path, const char *root, char **ret); +int chase_extract_filename(const char *path, const char *root, char **ret); int chase_and_open(const char *path, const char *root, ChaseFlags chase_flags, int open_flags, char **ret_path); int chase_and_opendir(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir); diff --git a/src/basic/chattr-util.c b/src/basic/chattr-util.c index 3c66a3e0c8..c59fb8a84e 100644 --- a/src/basic/chattr-util.c +++ b/src/basic/chattr-util.c @@ -9,29 +9,29 @@ #include "chattr-util.h" #include "errno-util.h" #include "fd-util.h" +#include "fs-util.h" #include "macro.h" #include "string-util.h" -int chattr_full(const char *path, - int fd, - unsigned value, - unsigned mask, - unsigned *ret_previous, - unsigned *ret_final, - ChattrApplyFlags flags) { +int chattr_full( + int dir_fd, + const char *path, + unsigned value, + unsigned mask, + unsigned *ret_previous, + unsigned *ret_final, + ChattrApplyFlags flags) { - _cleanup_close_ int fd_will_close = -EBADF; + _cleanup_close_ int fd = -EBADF; unsigned old_attr, new_attr; int set_flags_errno = 0; struct stat st; - assert(path || fd >= 0); + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); - if (fd < 0) { - fd = fd_will_close = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - if (fd < 0) - return -errno; - } + fd = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, /* xopen_flags = */ 0, /* mode = */ 0); + if (fd < 0) + return -errno; if (fstat(fd, &st) < 0) return -errno; diff --git a/src/basic/chattr-util.h b/src/basic/chattr-util.h index 82f91c66d9..c1ee63b4fa 100644 --- a/src/basic/chattr-util.h +++ b/src/basic/chattr-util.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include <fcntl.h> #include <linux/fs.h> #include <stdbool.h> #include <stddef.h> @@ -39,13 +40,15 @@ typedef enum ChattrApplyFlags { CHATTR_WARN_UNSUPPORTED_FLAGS = 1 << 1, } ChattrApplyFlags; -int chattr_full(const char *path, int fd, unsigned value, unsigned mask, unsigned *ret_previous, unsigned *ret_final, ChattrApplyFlags flags); - +int chattr_full(int dir_fd, const char *path, unsigned value, unsigned mask, unsigned *ret_previous, unsigned *ret_final, ChattrApplyFlags flags); +static inline int chattr_at(int dir_fd, const char *path, unsigned value, unsigned mask, unsigned *previous) { + return chattr_full(dir_fd, path, value, mask, previous, NULL, 0); +} static inline int chattr_fd(int fd, unsigned value, unsigned mask, unsigned *previous) { - return chattr_full(NULL, fd, value, mask, previous, NULL, 0); + return chattr_full(fd, NULL, value, mask, previous, NULL, 0); } static inline int chattr_path(const char *path, unsigned value, unsigned mask, unsigned *previous) { - return chattr_full(path, -1, value, mask, previous, NULL, 0); + return chattr_full(AT_FDCWD, path, value, mask, previous, NULL, 0); } int read_attr_fd(int fd, unsigned *ret); @@ -57,5 +60,5 @@ int read_attr_path(const char *p, unsigned *ret); #define CHATTR_SECRET_FLAGS (FS_SECRM_FL|FS_NODUMP_FL|FS_SYNC_FL|FS_NOCOW_FL) static inline int chattr_secret(int fd, ChattrApplyFlags flags) { - return chattr_full(NULL, fd, CHATTR_SECRET_FLAGS, CHATTR_SECRET_FLAGS, NULL, NULL, flags|CHATTR_FALLBACK_BITWISE); + return chattr_full(fd, NULL, CHATTR_SECRET_FLAGS, CHATTR_SECRET_FLAGS, NULL, NULL, flags|CHATTR_FALLBACK_BITWISE); } diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index 907bfeb600..ecbe58a9f8 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -891,12 +891,21 @@ int fd_get_diskseq(int fd, uint64_t *ret) { return 0; } -int dir_fd_is_root(int dir_fd) { +int path_is_root_at(int dir_fd, const char *path) { STRUCT_NEW_STATX_DEFINE(st); STRUCT_NEW_STATX_DEFINE(pst); + _cleanup_close_ int fd = -EBADF; int r; - assert(dir_fd >= 0); + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + + if (!isempty(path)) { + fd = openat(dir_fd, path, O_PATH|O_CLOEXEC); + if (fd < 0) + return -errno; + + dir_fd = fd; + } r = statx_fallback(dir_fd, ".", 0, STATX_TYPE|STATX_INO|STATX_MNT_ID, &st.sx); if (r == -ENOTDIR) diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h index 2f59e334c5..c870a1b899 100644 --- a/src/basic/fd-util.h +++ b/src/basic/fd-util.h @@ -101,9 +101,12 @@ int fd_is_opath(int fd); int read_nr_open(void); int fd_get_diskseq(int fd, uint64_t *ret); -int dir_fd_is_root(int dir_fd); +int path_is_root_at(int dir_fd, const char *path); +static inline int dir_fd_is_root(int dir_fd) { + return path_is_root_at(dir_fd, NULL); +} static inline int dir_fd_is_root_or_cwd(int dir_fd) { - return dir_fd == AT_FDCWD ? true : dir_fd_is_root(dir_fd); + return dir_fd == AT_FDCWD ? true : path_is_root_at(dir_fd, NULL); } /* The maximum length a buffer for a /proc/self/fd/<fd> path needs */ diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index 78e92609c6..1cf0f5b945 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -1097,7 +1097,6 @@ int xopenat(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags int r; assert(dir_fd >= 0 || dir_fd == AT_FDCWD); - assert(path); if (isempty(path)) { assert(!FLAGS_SET(open_flags, O_CREAT|O_EXCL)); diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 633d9479dd..3c999098e1 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -207,20 +207,13 @@ bool is_fs_type(const struct statfs *s, statfs_f_type_t magic_value) { return F_TYPE_EQUAL(s->f_type, magic_value); } -int fd_is_fs_type(int fd, statfs_f_type_t magic_value) { - struct statfs s; - - if (fstatfs(fd, &s) < 0) - return -errno; - - return is_fs_type(&s, magic_value); -} - -int path_is_fs_type(const char *path, statfs_f_type_t magic_value) { +int is_fs_type_at(int dir_fd, const char *path, statfs_f_type_t magic_value) { struct statfs s; + int r; - if (statfs(path, &s) < 0) - return -errno; + r = xstatfsat(dir_fd, path, &s); + if (r < 0) + return r; return is_fs_type(&s, magic_value); } @@ -461,7 +454,6 @@ int xstatfsat(int dir_fd, const char *path, struct statfs *ret) { _cleanup_close_ int fd = -EBADF; assert(dir_fd >= 0 || dir_fd == AT_FDCWD); - assert(path); assert(ret); fd = xopenat(dir_fd, path, O_PATH|O_CLOEXEC|O_NOCTTY, /* xopen_flags = */ 0, /* mode = */ 0); diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h index 3ae8b3eeb1..ae0aaf8f51 100644 --- a/src/basic/stat-util.h +++ b/src/basic/stat-util.h @@ -48,8 +48,13 @@ static inline int inode_same(const char *filea, const char *fileb, int flags) { typedef typeof(((struct statfs*)NULL)->f_type) statfs_f_type_t; bool is_fs_type(const struct statfs *s, statfs_f_type_t magic_value) _pure_; -int fd_is_fs_type(int fd, statfs_f_type_t magic_value); -int path_is_fs_type(const char *path, statfs_f_type_t magic_value); +int is_fs_type_at(int dir_fd, const char *path, statfs_f_type_t magic_value); +static inline int fd_is_fs_type(int fd, statfs_f_type_t magic_value) { + return is_fs_type_at(fd, NULL, magic_value); +} +static inline int path_is_fs_type(const char *path, statfs_f_type_t magic_value) { + return is_fs_type_at(AT_FDCWD, path, magic_value); +} bool is_temporary_fs(const struct statfs *s) _pure_; bool is_network_fs(const struct statfs *s) _pure_; diff --git a/src/boot/efi/meson.build b/src/boot/efi/meson.build index b573e6996d..68c19fbcc9 100644 --- a/src/boot/efi/meson.build +++ b/src/boot/efi/meson.build @@ -1,6 +1,7 @@ # SPDX-License-Identifier: LGPL-2.1-or-later efi_config_h_dir = meson.current_build_dir() +efi_addon = '' if efi_arch != '' libefitest = static_library( @@ -376,6 +377,11 @@ foreach efi_elf_binary : efi_elf_binaries if name.startswith('linux') boot_stubs += exe endif + + # This is supposed to match exactly one time + if name == 'addon@0@.efi.stub'.format(efi_arch) + efi_addon = exe.full_path() + endif endforeach alias_target('systemd-boot', boot_targets) diff --git a/src/cryptsetup/cryptsetup-generator.c b/src/cryptsetup/cryptsetup-generator.c index a22d932342..702b2615e2 100644 --- a/src/cryptsetup/cryptsetup-generator.c +++ b/src/cryptsetup/cryptsetup-generator.c @@ -562,7 +562,8 @@ static int create_disk( if (!noauto && !nofail) { r = write_drop_in(arg_dest, dmname, 40, "device-timeout", "# Automatically generated by systemd-cryptsetup-generator\n\n" - "[Unit]\nJobTimeoutSec=0"); + "[Unit]\n" + "JobTimeoutSec=infinity\n"); if (r < 0) log_warning_errno(r, "Failed to write device timeout drop-in: %m"); } diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 5c742497d5..25bdd68cd2 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -1286,7 +1286,7 @@ static int action_list_or_mtree_or_copy(DissectedImage *m, LoopDevice *d) { } /* Try to copy as directory? */ - r = copy_directory_fd(source_fd, arg_target, COPY_REFLINK|COPY_MERGE_EMPTY|COPY_SIGINT|COPY_HARDLINKS); + r = copy_directory_at(source_fd, NULL, AT_FDCWD, arg_target, COPY_REFLINK|COPY_MERGE_EMPTY|COPY_SIGINT|COPY_HARDLINKS); if (r >= 0) return 0; if (r != -ENOTDIR) diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c index 8036724034..77f51c4245 100644 --- a/src/gpt-auto-generator/gpt-auto-generator.c +++ b/src/gpt-auto-generator/gpt-auto-generator.c @@ -142,7 +142,7 @@ static int add_cryptsetup( r = write_drop_in_format(arg_dest, dmname, 50, "job-timeout", "# Automatically generated by systemd-gpt-auto-generator\n\n" "[Unit]\n" - "JobTimeoutSec=0"); /* the binary handles timeouts anyway */ + "JobTimeoutSec=infinity"); /* the binary handles timeouts anyway */ if (r < 0) log_warning_errno(r, "Failed to write device timeout drop-in, ignoring: %m"); diff --git a/src/hibernate-resume/hibernate-resume-generator.c b/src/hibernate-resume/hibernate-resume-generator.c index be52c21d00..1bcf9d69df 100644 --- a/src/hibernate-resume/hibernate-resume-generator.c +++ b/src/hibernate-resume/hibernate-resume-generator.c @@ -95,7 +95,8 @@ static int process_resume(void) { r = write_drop_in(arg_dest, device_unit, 40, "device-timeout", "# Automatically generated by systemd-hibernate-resume-generator\n\n" - "[Unit]\nJobTimeoutSec=0"); + "[Unit]\n" + "JobTimeoutSec=infinity\n"); if (r < 0) log_warning_errno(r, "Failed to write device timeout drop-in: %m"); diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index e1f0ff1dcb..2359699801 100644 --- a/src/home/homework-luks.c +++ b/src/home/homework-luks.c @@ -2281,7 +2281,7 @@ int home_create_luks( setup->temporary_image_path = TAKE_PTR(t); - r = chattr_full(t, setup->image_fd, FS_NOCOW_FL|FS_NOCOMP_FL, FS_NOCOW_FL|FS_NOCOMP_FL, NULL, NULL, CHATTR_FALLBACK_BITWISE); + r = chattr_full(setup->image_fd, NULL, FS_NOCOW_FL|FS_NOCOMP_FL, FS_NOCOW_FL|FS_NOCOMP_FL, NULL, NULL, CHATTR_FALLBACK_BITWISE); if (r < 0 && r != -ENOANO) /* ENOANO → some bits didn't work; which we skip logging about because chattr_full() already debug logs about those flags */ log_full_errno(ERRNO_IS_NOT_SUPPORTED(r) ? LOG_DEBUG : LOG_WARNING, r, "Failed to set file attributes on %s, ignoring: %m", setup->temporary_image_path); diff --git a/src/import/export-tar.c b/src/import/export-tar.c index 4aa8204983..2f54cd8114 100644 --- a/src/import/export-tar.c +++ b/src/import/export-tar.c @@ -293,7 +293,7 @@ int tar_export_start(TarExport *e, const char *path, int fd, ImportCompressType return r; /* Let's try to make a snapshot, if we can, so that the export is atomic */ - r = btrfs_subvol_snapshot_fd(sfd, e->temp_path, BTRFS_SNAPSHOT_READ_ONLY|BTRFS_SNAPSHOT_RECURSIVE); + r = btrfs_subvol_snapshot_at(sfd, NULL, AT_FDCWD, e->temp_path, BTRFS_SNAPSHOT_READ_ONLY|BTRFS_SNAPSHOT_RECURSIVE); if (r < 0) { log_debug_errno(r, "Couldn't create snapshot %s of %s, not exporting atomically: %m", e->temp_path, path); e->temp_path = mfree(e->temp_path); diff --git a/src/import/import-fs.c b/src/import/import-fs.c index d81dd13ffd..fd79c8f01a 100644 --- a/src/import/import-fs.c +++ b/src/import/import-fs.c @@ -194,9 +194,9 @@ static int import_fs(int argc, char *argv[], void *userdata) { BLOCK_SIGNALS(SIGINT, SIGTERM); if (arg_btrfs_subvol) - r = btrfs_subvol_snapshot_fd_full( - fd, - dest, + r = btrfs_subvol_snapshot_at_full( + fd, NULL, + AT_FDCWD, dest, BTRFS_SNAPSHOT_FALLBACK_COPY| BTRFS_SNAPSHOT_FALLBACK_DIRECTORY| BTRFS_SNAPSHOT_RECURSIVE| @@ -206,9 +206,9 @@ static int import_fs(int argc, char *argv[], void *userdata) { progress_bytes, &progress); else - r = copy_directory_fd_full( - fd, - dest, + r = copy_directory_at_full( + fd, NULL, + AT_FDCWD, dest, COPY_REFLINK| COPY_SAME_MOUNT| COPY_HARDLINKS| diff --git a/src/import/pull-tar.c b/src/import/pull-tar.c index f22eb0e3a3..e474dffa72 100644 --- a/src/import/pull-tar.c +++ b/src/import/pull-tar.c @@ -237,9 +237,9 @@ static int tar_pull_make_local_copy(TarPull *i) { return log_error_errno(r, "Failed to generate temporary filename for %s: %m", p); if (i->flags & PULL_BTRFS_SUBVOL) - r = btrfs_subvol_snapshot( - i->final_path, - t, + r = btrfs_subvol_snapshot_at( + AT_FDCWD, i->final_path, + AT_FDCWD, t, (i->flags & PULL_BTRFS_QUOTA ? BTRFS_SNAPSHOT_QUOTA : 0)| BTRFS_SNAPSHOT_FALLBACK_COPY| BTRFS_SNAPSHOT_FALLBACK_DIRECTORY| diff --git a/src/integritysetup/integritysetup-generator.c b/src/integritysetup/integritysetup-generator.c index 15f508902d..5df6d81a84 100644 --- a/src/integritysetup/integritysetup-generator.c +++ b/src/integritysetup/integritysetup-generator.c @@ -100,7 +100,7 @@ static int create_disk( "[Service]\n" "Type=oneshot\n" "RemainAfterExit=yes\n" - "TimeoutSec=0\n" + "TimeoutSec=infinity\n" "ExecStart=" ROOTLIBEXECDIR "/systemd-integritysetup attach '%s' '%s' '%s' '%s'\n" "ExecStop=" ROOTLIBEXECDIR "/systemd-integritysetup detach '%s'\n", name_escaped, device, empty_to_dash(key_file_escaped), empty_to_dash(options), diff --git a/src/kernel-install/60-ukify.install.in b/src/kernel-install/60-ukify.install.in index 7c29f7e8af..0927bd7a2e 100755 --- a/src/kernel-install/60-ukify.install.in +++ b/src/kernel-install/60-ukify.install.in @@ -183,11 +183,10 @@ def call_ukify(opts): # The solution with runpy gives a dictionary, which isn't great, but will do. ukify = runpy.run_path(UKIFY, run_name='ukify') - # Create "empty" namespace. We want to override just a few settings, - # so it doesn't make sense to duplicate all the fields. We use a hack - # to pre-populate the namespace like argparse would, all defaults. - # We need to specify the two mandatory arguments to not get an error. - opts2 = ukify['create_parser']().parse_args(('A','B')) + # Create "empty" namespace. We want to override just a few settings, so it + # doesn't make sense to configure everything. We pretend to parse an empty + # argument set to prepopulate the namespace with the defaults. + opts2 = ukify['create_parser']().parse_args(()) opts2.config = config_file_location() opts2.uname = opts.kernel_version diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym index 936a3577f5..ea13e02719 100644 --- a/src/libsystemd/libsystemd.sym +++ b/src/libsystemd/libsystemd.sym @@ -826,4 +826,5 @@ global: sd_pid_notify_barrier; sd_event_source_leave_ratelimit; sd_journal_step_one; + sd_session_get_leader; } LIBSYSTEMD_253; diff --git a/src/libsystemd/sd-login/sd-login.c b/src/libsystemd/sd-login/sd-login.c index c5d54bbf74..4d09b15653 100644 --- a/src/libsystemd/sd-login/sd-login.c +++ b/src/libsystemd/sd-login/sd-login.c @@ -870,6 +870,25 @@ _public_ int sd_session_get_remote_host(const char *session, char **remote_host) return session_get_string(session, "REMOTE_HOST", remote_host); } +_public_ int sd_session_get_leader(const char *session, pid_t *leader) { + _cleanup_free_ char *leader_string = NULL; + pid_t pid; + int r; + + assert_return(leader, -EINVAL); + + r = session_get_string(session, "LEADER", &leader_string); + if (r < 0) + return r; + + r = parse_pid(leader_string, &pid); + if (r < 0) + return r; + + *leader = pid; + return 0; +} + _public_ int sd_seat_get_active(const char *seat, char **session, uid_t *uid) { _cleanup_free_ char *p = NULL, *s = NULL, *t = NULL; int r; diff --git a/src/login/systemd-user.in b/src/login/systemd-user.in index 9a665bd959..8a3c9e0165 100644 --- a/src/login/systemd-user.in +++ b/src/login/systemd-user.in @@ -19,4 +19,5 @@ session required pam_namespace.so {% if ENABLE_HOMED %} -session optional pam_systemd_home.so {% endif %} +session optional pam_umask.so silent session optional pam_systemd.so diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index f54e955cae..ab61184d9f 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -5564,13 +5564,13 @@ static int run(int argc, char *argv[]) { { BLOCK_SIGNALS(SIGINT); - r = btrfs_subvol_snapshot(arg_directory, np, - (arg_read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | - BTRFS_SNAPSHOT_FALLBACK_COPY | - BTRFS_SNAPSHOT_FALLBACK_DIRECTORY | - BTRFS_SNAPSHOT_RECURSIVE | - BTRFS_SNAPSHOT_QUOTA | - BTRFS_SNAPSHOT_SIGINT); + r = btrfs_subvol_snapshot_at(AT_FDCWD, arg_directory, AT_FDCWD, np, + (arg_read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | + BTRFS_SNAPSHOT_FALLBACK_COPY | + BTRFS_SNAPSHOT_FALLBACK_DIRECTORY | + BTRFS_SNAPSHOT_RECURSIVE | + BTRFS_SNAPSHOT_QUOTA | + BTRFS_SNAPSHOT_SIGINT); } if (r == -EINTR) { log_error_errno(r, "Interrupted while copying file system tree to %s, removed again.", np); @@ -5605,14 +5605,14 @@ static int run(int argc, char *argv[]) { { BLOCK_SIGNALS(SIGINT); - r = btrfs_subvol_snapshot(arg_template, arg_directory, - (arg_read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | - BTRFS_SNAPSHOT_FALLBACK_COPY | - BTRFS_SNAPSHOT_FALLBACK_DIRECTORY | - BTRFS_SNAPSHOT_FALLBACK_IMMUTABLE | - BTRFS_SNAPSHOT_RECURSIVE | - BTRFS_SNAPSHOT_QUOTA | - BTRFS_SNAPSHOT_SIGINT); + r = btrfs_subvol_snapshot_at(AT_FDCWD, arg_template, AT_FDCWD, arg_directory, + (arg_read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | + BTRFS_SNAPSHOT_FALLBACK_COPY | + BTRFS_SNAPSHOT_FALLBACK_DIRECTORY | + BTRFS_SNAPSHOT_FALLBACK_IMMUTABLE | + BTRFS_SNAPSHOT_RECURSIVE | + BTRFS_SNAPSHOT_QUOTA | + BTRFS_SNAPSHOT_SIGINT); } if (r == -EEXIST) log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, diff --git a/src/partition/repart.c b/src/partition/repart.c index 78bd06a537..4cf4e4c1f7 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -3155,29 +3155,85 @@ static int context_wipe_and_discard(Context *context) { return 0; } +typedef struct DecryptedPartitionTarget { + int fd; + char *volume; + struct crypt_device *device; +} DecryptedPartitionTarget; + +static DecryptedPartitionTarget* decrypted_partition_target_free(DecryptedPartitionTarget *t) { +#ifdef HAVE_LIBCRYPTSETUP + _cleanup_free_ char *name = NULL; + int r; + + if (!t) + return NULL; + + r = path_extract_filename(t->volume, &name); + if (r < 0) { + assert(r == -ENOMEM); + log_oom(); + } + + safe_close(t->fd); + + if (name) { + /* udev or so might access out block device in the background while we are done. Let's hence + * force detach the volume. We sync'ed before, hence this should be safe. */ + r = sym_crypt_deactivate_by_name(t->device, name, CRYPT_DEACTIVATE_FORCE); + if (r < 0) + log_error_errno(r, "Failed to deactivate LUKS device: %m"); + } + + sym_crypt_free(t->device); + free(t->volume); + free(t); +#endif + return NULL; +} + typedef struct { LoopDevice *loop; int fd; char *path; int whole_fd; + DecryptedPartitionTarget *decrypted; } PartitionTarget; static int partition_target_fd(PartitionTarget *t) { assert(t); assert(t->loop || t->fd >= 0 || t->whole_fd >= 0); - return t->loop ? t->loop->fd : t->fd >= 0 ? t->fd : t->whole_fd; + + if (t->decrypted) + return t->decrypted->fd; + + if (t->loop) + return t->loop->fd; + + if (t->fd >= 0) + return t->fd; + + return t->whole_fd; } static const char* partition_target_path(PartitionTarget *t) { assert(t); assert(t->loop || t->path); - return t->loop ? t->loop->node : t->path; + + if (t->decrypted) + return t->decrypted->volume; + + if (t->loop) + return t->loop->node; + + return t->path; } static PartitionTarget *partition_target_free(PartitionTarget *t) { if (!t) return NULL; + decrypted_partition_target_free(t->decrypted); loop_device_unref(t->loop); safe_close(t->fd); unlink_and_free(t->path); @@ -3285,6 +3341,7 @@ static int partition_target_grow(PartitionTarget *t, uint64_t size) { int r; assert(t); + assert(!t->decrypted); if (t->loop) { r = loop_device_refresh_size(t->loop, UINT64_MAX, size); @@ -3308,6 +3365,9 @@ static int partition_target_sync(Context *context, Partition *p, PartitionTarget assert_se((whole_fd = fdisk_get_devfd(context->fdisk_context)) >= 0); + if (t->decrypted && fsync(t->decrypted->fd) < 0) + return log_error_errno(errno, "Failed to sync changes to '%s': %m", t->decrypted->volume); + if (t->loop) { r = loop_device_sync(t->loop); if (r < 0) @@ -3340,12 +3400,13 @@ static int partition_target_sync(Context *context, Partition *p, PartitionTarget return 0; } -static int partition_encrypt(Context *context, Partition *p, const char *node) { +static int partition_encrypt(Context *context, Partition *p, PartitionTarget *target, bool offline) { #if HAVE_LIBCRYPTSETUP && HAVE_CRYPT_SET_DATA_OFFSET && HAVE_CRYPT_REENCRYPT_INIT_BY_PASSPHRASE && HAVE_CRYPT_REENCRYPT + const char *node = partition_target_path(target); struct crypt_params_luks2 luks_params = { .label = strempty(ASSERT_PTR(p)->new_label), .sector_size = ASSERT_PTR(context)->sector_size, - .data_device = node, + .data_device = offline ? node : NULL, }; struct crypt_params_reencrypt reencrypt_params = { .mode = CRYPT_REENCRYPT_ENCRYPT, @@ -3358,7 +3419,7 @@ static int partition_encrypt(Context *context, Partition *p, const char *node) { _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; _cleanup_(erase_and_freep) char *base64_encoded = NULL; _cleanup_fclose_ FILE *h = NULL; - _cleanup_free_ char *hp = NULL; + _cleanup_free_ char *hp = NULL, *vol = NULL, *dm_name = NULL; const char *passphrase = NULL; size_t passphrase_size = 0; const char *vt; @@ -3374,39 +3435,51 @@ static int partition_encrypt(Context *context, Partition *p, const char *node) { log_info("Encrypting future partition %" PRIu64 "...", p->partno); - r = var_tmp_dir(&vt); - if (r < 0) - return log_error_errno(r, "Failed to determine temporary files directory: %m"); + if (offline) { + r = var_tmp_dir(&vt); + if (r < 0) + return log_error_errno(r, "Failed to determine temporary files directory: %m"); - r = fopen_temporary_child(vt, &h, &hp); - if (r < 0) - return log_error_errno(r, "Failed to create temporary LUKS header file: %m"); + r = fopen_temporary_child(vt, &h, &hp); + if (r < 0) + return log_error_errno(r, "Failed to create temporary LUKS header file: %m"); - /* Weird cryptsetup requirement which requires the header file to be the size of at least one sector. */ - r = ftruncate(fileno(h), context->sector_size); - if (r < 0) - return log_error_errno(r, "Failed to grow temporary LUKS header file: %m"); + /* Weird cryptsetup requirement which requires the header file to be the size of at least one + * sector. */ + r = ftruncate(fileno(h), context->sector_size); + if (r < 0) + return log_error_errno(r, "Failed to grow temporary LUKS header file: %m"); + } else { + if (asprintf(&dm_name, "luks-repart-%08" PRIx64, random_u64()) < 0) + return log_oom(); + + vol = path_join("/dev/mapper/", dm_name); + if (!vol) + return log_oom(); + } - r = sym_crypt_init(&cd, hp); + r = sym_crypt_init(&cd, offline ? hp : node); if (r < 0) return log_error_errno(r, "Failed to allocate libcryptsetup context for %s: %m", hp); cryptsetup_enable_logging(cd); - /* Disable kernel keyring usage by libcryptsetup as a workaround for - * https://gitlab.com/cryptsetup/cryptsetup/-/merge_requests/273. This makes sure that we can do - * offline encryption even when repart is running in a container. */ - r = sym_crypt_volume_key_keyring(cd, false); - if (r < 0) - return log_error_errno(r, "Failed to disable kernel keyring: %m"); + if (offline) { + /* Disable kernel keyring usage by libcryptsetup as a workaround for + * https://gitlab.com/cryptsetup/cryptsetup/-/merge_requests/273. This makes sure that we can + * do offline encryption even when repart is running in a container. */ + r = sym_crypt_volume_key_keyring(cd, false); + if (r < 0) + return log_error_errno(r, "Failed to disable kernel keyring: %m"); - r = sym_crypt_metadata_locking(cd, false); - if (r < 0) - return log_error_errno(r, "Failed to disable metadata locking: %m"); + r = sym_crypt_metadata_locking(cd, false); + if (r < 0) + return log_error_errno(r, "Failed to disable metadata locking: %m"); - r = sym_crypt_set_data_offset(cd, LUKS2_METADATA_SIZE / 512); - if (r < 0) - return log_error_errno(r, "Failed to set data offset: %m"); + r = sym_crypt_set_data_offset(cd, LUKS2_METADATA_SIZE / 512); + if (r < 0) + return log_error_errno(r, "Failed to set data offset: %m"); + } r = sym_crypt_format(cd, CRYPT_LUKS2, @@ -3517,51 +3590,84 @@ static int partition_encrypt(Context *context, Partition *p, const char *node) { #endif } - r = sym_crypt_reencrypt_init_by_passphrase( - cd, - NULL, - passphrase, - passphrase_size, - CRYPT_ANY_SLOT, - 0, - sym_crypt_get_cipher(cd), - sym_crypt_get_cipher_mode(cd), - &reencrypt_params); - if (r < 0) - return log_error_errno(r, "Failed to prepare for reencryption: %m"); + if (offline) { + r = sym_crypt_reencrypt_init_by_passphrase( + cd, + NULL, + passphrase, + passphrase_size, + CRYPT_ANY_SLOT, + 0, + sym_crypt_get_cipher(cd), + sym_crypt_get_cipher_mode(cd), + &reencrypt_params); + if (r < 0) + return log_error_errno(r, "Failed to prepare for reencryption: %m"); - /* crypt_reencrypt_init_by_passphrase() doesn't actually put the LUKS header at the front, we have - * to do that ourselves. */ + /* crypt_reencrypt_init_by_passphrase() doesn't actually put the LUKS header at the front, we + * have to do that ourselves. */ - sym_crypt_free(cd); - cd = NULL; + sym_crypt_free(cd); + cd = NULL; - r = sym_crypt_init(&cd, node); - if (r < 0) - return log_error_errno(r, "Failed to allocate libcryptsetup context for %s: %m", node); + r = sym_crypt_init(&cd, node); + if (r < 0) + return log_error_errno(r, "Failed to allocate libcryptsetup context for %s: %m", node); - r = sym_crypt_header_restore(cd, CRYPT_LUKS2, hp); - if (r < 0) - return log_error_errno(r, "Failed to place new LUKS header at head of %s: %m", node); + r = sym_crypt_header_restore(cd, CRYPT_LUKS2, hp); + if (r < 0) + return log_error_errno(r, "Failed to place new LUKS header at head of %s: %m", node); - reencrypt_params.flags &= ~CRYPT_REENCRYPT_INITIALIZE_ONLY; + reencrypt_params.flags &= ~CRYPT_REENCRYPT_INITIALIZE_ONLY; - r = sym_crypt_reencrypt_init_by_passphrase( - cd, - NULL, - passphrase, - passphrase_size, - CRYPT_ANY_SLOT, - 0, - NULL, - NULL, - &reencrypt_params); - if (r < 0) - return log_error_errno(r, "Failed to load reencryption context: %m"); + r = sym_crypt_reencrypt_init_by_passphrase( + cd, + NULL, + passphrase, + passphrase_size, + CRYPT_ANY_SLOT, + 0, + NULL, + NULL, + &reencrypt_params); + if (r < 0) + return log_error_errno(r, "Failed to load reencryption context: %m"); - r = sym_crypt_reencrypt(cd, NULL); - if (r < 0) - return log_error_errno(r, "Failed to encrypt %s: %m", node); + r = sym_crypt_reencrypt(cd, NULL); + if (r < 0) + return log_error_errno(r, "Failed to encrypt %s: %m", node); + } else { + _cleanup_free_ DecryptedPartitionTarget *t = NULL; + _cleanup_close_ int dev_fd = -1; + + r = sym_crypt_activate_by_volume_key( + cd, + dm_name, + NULL, + VOLUME_KEY_SIZE, + arg_discard ? CRYPT_ACTIVATE_ALLOW_DISCARDS : 0); + if (r < 0) + return log_error_errno(r, "Failed to activate LUKS superblock: %m"); + + dev_fd = open(vol, O_RDWR|O_CLOEXEC|O_NOCTTY); + if (dev_fd < 0) + return log_error_errno(errno, "Failed to open LUKS volume '%s': %m", vol); + + if (flock(dev_fd, LOCK_EX) < 0) + return log_error_errno(errno, "Failed to lock '%s': %m", vol); + + t = new(DecryptedPartitionTarget, 1); + if (!t) + return log_oom(); + + *t = (DecryptedPartitionTarget) { + .fd = TAKE_FD(dev_fd), + .volume = TAKE_PTR(vol), + .device = TAKE_PTR(cd), + }; + + target->decrypted = TAKE_PTR(t); + } log_info("Successfully encrypted future partition %" PRIu64 ".", p->partno); @@ -3833,6 +3939,12 @@ static int context_copy_blocks(Context *context) { if (r < 0) return r; + if (p->encrypt != ENCRYPT_OFF && t->loop) { + r = partition_encrypt(context, p, t, /* offline = */ false); + if (r < 0) + return r; + } + log_info("Copying in '%s' (%s) on block level into future partition %" PRIu64 ".", p->copy_blocks_path, FORMAT_BYTES(p->copy_blocks_size), p->partno); @@ -3840,8 +3952,10 @@ static int context_copy_blocks(Context *context) { if (r < 0) return log_error_errno(r, "Failed to copy in data from '%s': %m", p->copy_blocks_path); - if (p->encrypt != ENCRYPT_OFF) { - r = partition_encrypt(context, p, partition_target_path(t)); + log_info("Copying in of '%s' on block level completed.", p->copy_blocks_path); + + if (p->encrypt != ENCRYPT_OFF && !t->loop) { + r = partition_encrypt(context, p, t, /* offline = */ true); if (r < 0) return r; } @@ -3850,8 +3964,6 @@ static int context_copy_blocks(Context *context) { if (r < 0) return r; - log_info("Copying in of '%s' on block level completed.", p->copy_blocks_path); - if (p->siblings[VERITY_HASH] && !partition_defer(p->siblings[VERITY_HASH])) { r = partition_format_verity_hash(context, p->siblings[VERITY_HASH], /* node = */ NULL, partition_target_path(t)); @@ -4073,14 +4185,14 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { sfd, ".", pfd, fn, UID_INVALID, GID_INVALID, - COPY_REFLINK|COPY_HOLES|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS|COPY_ALL_XATTRS|COPY_GRACEFUL_WARN, + COPY_REFLINK|COPY_HOLES|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS|COPY_ALL_XATTRS|COPY_GRACEFUL_WARN|COPY_TRUNCATE, denylist); } else r = copy_tree_at( sfd, ".", tfd, ".", UID_INVALID, GID_INVALID, - COPY_REFLINK|COPY_HOLES|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS|COPY_ALL_XATTRS|COPY_GRACEFUL_WARN, + COPY_REFLINK|COPY_HOLES|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS|COPY_ALL_XATTRS|COPY_GRACEFUL_WARN|COPY_TRUNCATE, denylist); if (r < 0) return log_error_errno(r, "Failed to copy '%s%s' to '%s%s': %m", @@ -4113,7 +4225,7 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { if (tfd < 0) return log_error_errno(errno, "Failed to create target file '%s': %m", *target); - r = copy_bytes(sfd, tfd, UINT64_MAX, COPY_REFLINK|COPY_HOLES|COPY_SIGINT); + r = copy_bytes(sfd, tfd, UINT64_MAX, COPY_REFLINK|COPY_HOLES|COPY_SIGINT|COPY_TRUNCATE); if (r < 0) return log_error_errno(r, "Failed to copy '%s' to '%s%s': %m", *source, strempty(arg_root), *target); @@ -4270,6 +4382,16 @@ static int context_mkfs(Context *context) { if (r < 0) return r; + if (p->encrypt != ENCRYPT_OFF && t->loop) { + r = partition_target_grow(t, p->new_size); + if (r < 0) + return r; + + r = partition_encrypt(context, p, t, /* offline = */ false); + if (r < 0) + return log_error_errno(r, "Failed to encrypt device: %m"); + } + log_info("Formatting future partition %" PRIu64 ".", p->partno); /* If we're not writing to a loop device or if we're populating a read-only filesystem, we @@ -4305,17 +4427,17 @@ static int context_mkfs(Context *context) { if (partition_needs_populate(p) && !root) { assert(t->loop); - r = partition_populate_filesystem(context, p, t->loop->node); + r = partition_populate_filesystem(context, p, partition_target_path(t)); if (r < 0) return r; } - if (p->encrypt != ENCRYPT_OFF) { + if (p->encrypt != ENCRYPT_OFF && !t->loop) { r = partition_target_grow(t, p->new_size); if (r < 0) return r; - r = partition_encrypt(context, p, partition_target_path(t)); + r = partition_encrypt(context, p, t, /* offline = */ true); if (r < 0) return log_error_errno(r, "Failed to encrypt device: %m"); } @@ -4926,7 +5048,7 @@ static int context_split(Context *context) { if (lseek(fd, p->offset, SEEK_SET) < 0) return log_error_errno(errno, "Failed to seek to partition offset: %m"); - r = copy_bytes(fd, fdt, p->new_size, COPY_REFLINK|COPY_HOLES); + r = copy_bytes(fd, fdt, p->new_size, COPY_REFLINK|COPY_HOLES|COPY_TRUNCATE); if (r < 0) return log_error_errno(r, "Failed to copy to split partition %s: %m", p->split_path); } diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index 574a1a4be9..d63760b7d1 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -911,9 +911,9 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, const DnsAns if (r < 0) goto fail; - /* RFC 2782 states "Unless and until permitted by future standards - * action, name compression is not to be used for this field." */ - r = dns_packet_append_name(p, rr->srv.name, false, true, NULL); + /* RFC 2782 states "Unless and until permitted by future standards action, name compression + * is not to be used for this field." Hence we turn off compression here. */ + r = dns_packet_append_name(p, rr->srv.name, /* allow_compression= */ false, /* canonical_candidate= */ true, NULL); break; case DNS_TYPE_PTR: @@ -1728,7 +1728,13 @@ int dns_packet_read_rr( r = dns_packet_read_uint16(p, &rr->srv.port, NULL); if (r < 0) return r; - r = dns_packet_read_name(p, &rr->srv.name, true, NULL); + + /* RFC 2782 states "Unless and until permitted by future standards action, name compression + * is not to be used for this field." Nonetheless, we support it here, in the interest of + * increasing compatibility with implementations that do not implement this correctly. After + * all we didn't do this right once upon a time ourselves (see + * https://github.com/systemd/systemd/issues/9793). */ + r = dns_packet_read_name(p, &rr->srv.name, /* allow_compression= */ true, NULL); break; case DNS_TYPE_PTR: diff --git a/src/shared/btrfs-util.c b/src/shared/btrfs-util.c index 16295a5823..429e5afe11 100644 --- a/src/shared/btrfs-util.c +++ b/src/shared/btrfs-util.c @@ -17,6 +17,7 @@ #include "alloc-util.h" #include "blockdev-util.h" #include "btrfs-util.h" +#include "chase.h" #include "chattr-util.h" #include "copy.h" #include "fd-util.h" @@ -70,32 +71,20 @@ static int extract_subvolume_name(const char *path, char **ret) { return 0; } -int btrfs_is_subvol_fd(int fd) { +int btrfs_is_subvol_at(int dir_fd, const char *path) { struct stat st; - assert(fd >= 0); + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); /* On btrfs subvolumes always have the inode 256 */ - if (fstat(fd, &st) < 0) + if (fstatat(dir_fd, strempty(path), &st, (isempty(path) ? AT_EMPTY_PATH : 0)) < 0) return -errno; if (!btrfs_might_be_subvol(&st)) return 0; - return fd_is_fs_type(fd, BTRFS_SUPER_MAGIC); -} - -int btrfs_is_subvol(const char *path) { - _cleanup_close_ int fd = -EBADF; - - assert(path); - - fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (fd < 0) - return -errno; - - return btrfs_is_subvol_fd(fd); + return is_fs_type_at(dir_fd, path, BTRFS_SUPER_MAGIC); } int btrfs_subvol_make_fd(int fd, const char *subvolume) { @@ -163,11 +152,16 @@ int btrfs_subvol_make_fallback(const char *path, mode_t mode) { return 0; /* plain directory */ } -int btrfs_subvol_set_read_only_fd(int fd, bool b) { +int btrfs_subvol_set_read_only_at(int dir_fd, const char *path, bool b) { + _cleanup_close_ int fd = -EBADF; uint64_t flags, nflags; struct stat st; - assert(fd >= 0); + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + + fd = xopenat(dir_fd, path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY, /* xopen_flags = */ 0, /* mode = */ 0); + if (fd < 0) + return fd; if (fstat(fd, &st) < 0) return -errno; @@ -185,16 +179,6 @@ int btrfs_subvol_set_read_only_fd(int fd, bool b) { return RET_NERRNO(ioctl(fd, BTRFS_IOC_SUBVOL_SETFLAGS, &nflags)); } -int btrfs_subvol_set_read_only(const char *path, bool b) { - _cleanup_close_ int fd = -EBADF; - - fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (fd < 0) - return -errno; - - return btrfs_subvol_set_read_only_fd(fd, b); -} - int btrfs_subvol_get_read_only_fd(int fd) { uint64_t flags; struct stat st; @@ -1135,25 +1119,21 @@ static int subvol_remove_children(int fd, const char *subvolume, uint64_t subvol return 0; } -int btrfs_subvol_remove(const char *path, BtrfsRemoveFlags flags) { +int btrfs_subvol_remove_at(int dir_fd, const char *path, BtrfsRemoveFlags flags) { _cleanup_free_ char *subvolume = NULL; _cleanup_close_ int fd = -EBADF; int r; assert(path); - r = extract_subvolume_name(path, &subvolume); - if (r < 0) - return r; - - fd = open_parent(path, O_CLOEXEC, 0); + fd = chase_and_openat(dir_fd, path, CHASE_PARENT|CHASE_EXTRACT_FILENAME, O_CLOEXEC, &subvolume); if (fd < 0) return fd; - return subvol_remove_children(fd, subvolume, 0, flags); -} + r = validate_subvolume_name(subvolume); + if (r < 0) + return r; -int btrfs_subvol_remove_fd(int fd, const char *subvolume, BtrfsRemoveFlags flags) { return subvol_remove_children(fd, subvolume, 0, flags); } @@ -1526,22 +1506,37 @@ static int subvol_snapshot_children( return 0; } -int btrfs_subvol_snapshot_fd_full( - int old_fd, - const char *new_path, +int btrfs_subvol_snapshot_at_full( + int dir_fdf, + const char *from, + int dir_fdt, + const char *to, BtrfsSnapshotFlags flags, copy_progress_path_t progress_path, copy_progress_bytes_t progress_bytes, void *userdata) { _cleanup_free_ char *subvolume = NULL; - _cleanup_close_ int new_fd = -EBADF; + _cleanup_close_ int old_fd = -EBADF, new_fd = -EBADF; int r; - assert(old_fd >= 0); - assert(new_path); + assert(dir_fdf >= 0 || dir_fdf == AT_FDCWD); + assert(dir_fdt >= 0 || dir_fdt == AT_FDCWD); + assert(to); + + old_fd = xopenat(dir_fdf, from, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY, /* xopen_flags = */ 0, /* mode = */ 0); + if (old_fd < 0) + return old_fd; - r = btrfs_is_subvol_fd(old_fd); + new_fd = chase_and_openat(dir_fdt, to, CHASE_PARENT|CHASE_EXTRACT_FILENAME, O_CLOEXEC, &subvolume); + if (new_fd < 0) + return new_fd; + + r = validate_subvolume_name(subvolume); + if (r < 0) + return r; + + r = btrfs_is_subvol_at(dir_fdf, from); if (r < 0) return r; if (r == 0) { @@ -1551,18 +1546,19 @@ int btrfs_subvol_snapshot_fd_full( if (!(flags & BTRFS_SNAPSHOT_FALLBACK_COPY)) return -EISDIR; - r = btrfs_subvol_make(new_path); + r = btrfs_subvol_make_fd(new_fd, subvolume); if (ERRNO_IS_NOT_SUPPORTED(r) && (flags & BTRFS_SNAPSHOT_FALLBACK_DIRECTORY)) { /* If the destination doesn't support subvolumes, then use a plain directory, if that's requested. */ - if (mkdir(new_path, 0755) < 0) + if (mkdirat(new_fd, subvolume, 0755) < 0) return -errno; plain_directory = true; } else if (r < 0) return r; - r = copy_directory_fd_full( - old_fd, new_path, + r = copy_directory_at_full( + dir_fdf, from, + new_fd, subvolume, COPY_MERGE_EMPTY| COPY_REFLINK| COPY_SAME_MOUNT| @@ -1583,9 +1579,9 @@ int btrfs_subvol_snapshot_fd_full( * it: the IMMUTABLE bit. Let's use this here, if this is requested. */ if (flags & BTRFS_SNAPSHOT_FALLBACK_IMMUTABLE) - (void) chattr_path(new_path, FS_IMMUTABLE_FL, FS_IMMUTABLE_FL, NULL); + (void) chattr_at(new_fd, subvolume, FS_IMMUTABLE_FL, FS_IMMUTABLE_FL, NULL); } else { - r = btrfs_subvol_set_read_only(new_path, true); + r = btrfs_subvol_set_read_only_at(new_fd, subvolume, true); if (r < 0) goto fallback_fail; } @@ -1594,41 +1590,13 @@ int btrfs_subvol_snapshot_fd_full( return 0; fallback_fail: - (void) rm_rf(new_path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); + (void) rm_rf_at(new_fd, subvolume, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); return r; } - r = extract_subvolume_name(new_path, &subvolume); - if (r < 0) - return r; - - new_fd = open_parent(new_path, O_CLOEXEC, 0); - if (new_fd < 0) - return new_fd; - return subvol_snapshot_children(old_fd, new_fd, subvolume, 0, flags); } -int btrfs_subvol_snapshot_full( - const char *old_path, - const char *new_path, - BtrfsSnapshotFlags flags, - copy_progress_path_t progress_path, - copy_progress_bytes_t progress_bytes, - void *userdata) { - - _cleanup_close_ int old_fd = -EBADF; - - assert(old_path); - assert(new_path); - - old_fd = open(old_path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (old_fd < 0) - return -errno; - - return btrfs_subvol_snapshot_fd_full(old_fd, new_path, flags, progress_path, progress_bytes, userdata); -} - int btrfs_qgroup_find_parents(int fd, uint64_t qgroupid, uint64_t **ret) { struct btrfs_ioctl_search_args args = { diff --git a/src/shared/btrfs-util.h b/src/shared/btrfs-util.h index 0ce6d4b96a..de38f1e45c 100644 --- a/src/shared/btrfs-util.h +++ b/src/shared/btrfs-util.h @@ -43,8 +43,13 @@ typedef enum BtrfsRemoveFlags { BTRFS_REMOVE_QUOTA = 1 << 1, } BtrfsRemoveFlags; -int btrfs_is_subvol_fd(int fd); -int btrfs_is_subvol(const char *path); +int btrfs_is_subvol_at(int dir_fd, const char *path); +static inline int btrfs_is_subvol_fd(int fd) { + return btrfs_is_subvol_at(fd, NULL); +} +static inline int btrfs_is_subvol(const char *path) { + return btrfs_is_subvol_at(AT_FDCWD, path); +} int btrfs_get_block_device_at(int dir_fd, const char *path, dev_t *ret); static inline int btrfs_get_block_device(const char *path, dev_t *ret) { @@ -69,21 +74,24 @@ int btrfs_subvol_make_fd(int fd, const char *subvolume); int btrfs_subvol_make_fallback(const char *path, mode_t); -int btrfs_subvol_snapshot_fd_full(int old_fd, const char *new_path, BtrfsSnapshotFlags flags, copy_progress_path_t progress_path, copy_progress_bytes_t progress_bytes, void *userdata); -static inline int btrfs_subvol_snapshot_fd(int old_fd, const char *new_path, BtrfsSnapshotFlags flags) { - return btrfs_subvol_snapshot_fd_full(old_fd, new_path, flags, NULL, NULL, NULL); +int btrfs_subvol_snapshot_at_full(int dir_fdf, const char *from, int dir_fdt, const char *to, BtrfsSnapshotFlags flags, copy_progress_path_t progress_path, copy_progress_bytes_t progress_bytes, void *userdata); +static inline int btrfs_subvol_snapshot_at(int dir_fdf, const char *from, int dir_fdt, const char *to, BtrfsSnapshotFlags flags) { + return btrfs_subvol_snapshot_at_full(dir_fdf, from, dir_fdt, to, flags, NULL, NULL, NULL); } -int btrfs_subvol_snapshot_full(const char *old_path, const char *new_path, BtrfsSnapshotFlags flags, copy_progress_path_t progress_path, copy_progress_bytes_t progress_bytes, void *userdata); -static inline int btrfs_subvol_snapshot(const char *old_path, const char *new_path, BtrfsSnapshotFlags flags) { - return btrfs_subvol_snapshot_full(old_path, new_path, flags, NULL, NULL, NULL); +int btrfs_subvol_remove_at(int dir_fd, const char *path, BtrfsRemoveFlags flags); +static inline int btrfs_subvol_remove(const char *path, BtrfsRemoveFlags flags) { + return btrfs_subvol_remove_at(AT_FDCWD, path, flags); } -int btrfs_subvol_remove(const char *path, BtrfsRemoveFlags flags); -int btrfs_subvol_remove_fd(int fd, const char *subvolume, BtrfsRemoveFlags flags); +int btrfs_subvol_set_read_only_at(int dir_fd, const char *path, bool b); +static inline int btrfs_subvol_set_read_only_fd(int fd, bool b) { + return btrfs_subvol_set_read_only_at(fd, NULL, b); +} +static inline int btrfs_subvol_set_read_only(const char *path, bool b) { + return btrfs_subvol_set_read_only_at(AT_FDCWD, path, b); +} -int btrfs_subvol_set_read_only_fd(int fd, bool b); -int btrfs_subvol_set_read_only(const char *path, bool b); int btrfs_subvol_get_read_only_fd(int fd); int btrfs_subvol_get_id(int fd, const char *subvolume, uint64_t *ret); diff --git a/src/shared/copy.c b/src/shared/copy.c index 14b9b61d8a..6b9d4b9942 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -245,7 +245,7 @@ int copy_bytes_full( ssize_t n; if (max_bytes <= 0) - return 1; /* return > 0 if we hit the max_bytes limit */ + break; r = look_for_signals(copy_flags); if (r < 0) @@ -468,7 +468,16 @@ int copy_bytes_full( copied_something = true; } - return 0; /* return 0 if we hit EOF earlier than the size limit */ + if (copy_flags & COPY_TRUNCATE) { + off_t off = lseek(fdt, 0, SEEK_CUR); + if (off < 0) + return -errno; + + if (ftruncate(fdt, off) < 0) + return -errno; + } + + return max_bytes <= 0; /* return 0 if we hit EOF earlier than the size limit */ } static int fd_copy_symlink( @@ -1208,61 +1217,22 @@ int copy_tree_at_full( return 0; } -static int sync_dir_by_flags(const char *path, CopyFlags copy_flags) { +static int sync_dir_by_flags(int dir_fd, const char *path, CopyFlags copy_flags) { + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(path); if (copy_flags & COPY_SYNCFS) - return syncfs_path(AT_FDCWD, path); + return syncfs_path(dir_fd, path); if (copy_flags & COPY_FSYNC_FULL) - return fsync_parent_at(AT_FDCWD, path); - - return 0; -} - -int copy_directory_fd_full( - int dirfd, - const char *to, - CopyFlags copy_flags, - copy_progress_path_t progress_path, - copy_progress_bytes_t progress_bytes, - void *userdata) { - - struct stat st; - int r; - - assert(dirfd >= 0); - assert(to); - - if (fstat(dirfd, &st) < 0) - return -errno; - - r = stat_verify_directory(&st); - if (r < 0) - return r; - - r = fd_copy_directory( - dirfd, NULL, - &st, - AT_FDCWD, to, - st.st_dev, - COPY_DEPTH_MAX, - UID_INVALID, GID_INVALID, - copy_flags, - NULL, NULL, NULL, - progress_path, - progress_bytes, - userdata); - if (r < 0) - return r; - - r = sync_dir_by_flags(to, copy_flags); - if (r < 0) - return r; + return fsync_parent_at(dir_fd, path); return 0; } -int copy_directory_full( +int copy_directory_at_full( + int dir_fdf, const char *from, + int dir_fdt, const char *to, CopyFlags copy_flags, copy_progress_path_t progress_path, @@ -1272,10 +1242,11 @@ int copy_directory_full( struct stat st; int r; - assert(from); + assert(dir_fdf >= 0 || dir_fdf == AT_FDCWD); + assert(dir_fdt >= 0 || dir_fdt == AT_FDCWD); assert(to); - if (lstat(from, &st) < 0) + if (fstatat(dir_fdf, strempty(from), &st, AT_SYMLINK_NOFOLLOW|(isempty(from) ? AT_EMPTY_PATH : 0)) < 0) return -errno; r = stat_verify_directory(&st); @@ -1283,9 +1254,9 @@ int copy_directory_full( return r; r = fd_copy_directory( - AT_FDCWD, from, + dir_fdf, from, &st, - AT_FDCWD, to, + dir_fdt, to, st.st_dev, COPY_DEPTH_MAX, UID_INVALID, GID_INVALID, @@ -1297,7 +1268,7 @@ int copy_directory_full( if (r < 0) return r; - r = sync_dir_by_flags(to, copy_flags); + r = sync_dir_by_flags(dir_fdt, to, copy_flags); if (r < 0) return r; diff --git a/src/shared/copy.h b/src/shared/copy.h index c4482eba7e..da084b66f2 100644 --- a/src/shared/copy.h +++ b/src/shared/copy.h @@ -28,6 +28,7 @@ typedef enum CopyFlags { COPY_ALL_XATTRS = 1 << 13, /* Preserve all xattrs when copying, not just those in the user namespace */ COPY_HOLES = 1 << 14, /* Copy holes */ COPY_GRACEFUL_WARN = 1 << 15, /* Skip copying file types that aren't supported by the target filesystem */ + COPY_TRUNCATE = 1 << 16, /* Truncate to current file offset after copying */ } CopyFlags; typedef enum DenyType { @@ -82,14 +83,9 @@ static inline int copy_tree(const char *from, const char *to, uid_t override_uid return copy_tree_at_full(AT_FDCWD, from, AT_FDCWD, to, override_uid, override_gid, copy_flags, denylist, NULL, NULL, NULL); } -int copy_directory_fd_full(int dirfd, const char *to, CopyFlags copy_flags, copy_progress_path_t progress_path, copy_progress_bytes_t progress_bytes, void *userdata); -static inline int copy_directory_fd(int dirfd, const char *to, CopyFlags copy_flags) { - return copy_directory_fd_full(dirfd, to, copy_flags, NULL, NULL, NULL); -} - -int copy_directory_full(const char *from, const char *to, CopyFlags copy_flags, copy_progress_path_t progress_path, copy_progress_bytes_t progress_bytes, void *userdata); -static inline int copy_directory(const char *from, const char *to, CopyFlags copy_flags) { - return copy_directory_full(from, to, copy_flags, NULL, NULL, NULL); +int copy_directory_at_full(int dir_fdf, const char *from, int dir_fdt, const char *to, CopyFlags copy_flags, copy_progress_path_t progress_path, copy_progress_bytes_t progress_bytes, void *userdata); +static inline int copy_directory_at(int dir_fdf, const char *from, int dir_fdt, const char *to, CopyFlags copy_flags) { + return copy_directory_at_full(dir_fdf, from, dir_fdt, to, copy_flags, NULL, NULL, NULL); } int copy_bytes_full(int fdf, int fdt, uint64_t max_bytes, CopyFlags copy_flags, void **ret_remains, size_t *ret_remains_size, copy_progress_bytes_t progress, void *userdata); diff --git a/src/shared/discover-image.c b/src/shared/discover-image.c index 198c975c44..d93a1cc74c 100644 --- a/src/shared/discover-image.c +++ b/src/shared/discover-image.c @@ -923,13 +923,13 @@ int image_clone(Image *i, const char *new_name, bool read_only) { new_path = strjoina("/var/lib/machines/", new_name); - r = btrfs_subvol_snapshot(i->path, new_path, - (read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | - BTRFS_SNAPSHOT_FALLBACK_COPY | - BTRFS_SNAPSHOT_FALLBACK_DIRECTORY | - BTRFS_SNAPSHOT_FALLBACK_IMMUTABLE | - BTRFS_SNAPSHOT_RECURSIVE | - BTRFS_SNAPSHOT_QUOTA); + r = btrfs_subvol_snapshot_at(AT_FDCWD, i->path, AT_FDCWD, new_path, + (read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | + BTRFS_SNAPSHOT_FALLBACK_COPY | + BTRFS_SNAPSHOT_FALLBACK_DIRECTORY | + BTRFS_SNAPSHOT_FALLBACK_IMMUTABLE | + BTRFS_SNAPSHOT_RECURSIVE | + BTRFS_SNAPSHOT_QUOTA); if (r >= 0) /* Enable "subtree" quotas for the copy, if we didn't copy any quota from the source. */ (void) btrfs_subvol_auto_qgroup(new_path, 0, true); diff --git a/src/shared/generator.c b/src/shared/generator.c index 49c5fe5c05..44ed319922 100644 --- a/src/shared/generator.c +++ b/src/shared/generator.c @@ -235,7 +235,7 @@ static int write_fsck_sysroot_service( "Type=oneshot\n" "RemainAfterExit=yes\n" "ExecStart=" SYSTEMD_FSCK_PATH " %7$s\n" - "TimeoutSec=0\n", + "TimeoutSec=infinity\n", program_invocation_short_name, escaped, unit, @@ -530,7 +530,7 @@ int generator_hook_up_mkswap( "Type=oneshot\n" "RemainAfterExit=yes\n" "ExecStart="SYSTEMD_MAKEFS_PATH " swap %s\n" - "TimeoutSec=0\n", + "TimeoutSec=infinity\n", program_invocation_short_name, where_unit, escaped); @@ -619,7 +619,7 @@ int generator_hook_up_mkfs( "Type=oneshot\n" "RemainAfterExit=yes\n" "ExecStart="SYSTEMD_MAKEFS_PATH " %s %s\n" - "TimeoutSec=0\n", + "TimeoutSec=infinity\n", program_invocation_short_name, fsck_unit, where_unit, @@ -801,7 +801,7 @@ int generator_write_cryptsetup_service_section( "[Service]\n" "Type=oneshot\n" "RemainAfterExit=yes\n" - "TimeoutSec=0\n" /* The binary handles timeouts on its own */ + "TimeoutSec=infinity\n" /* The binary handles timeouts on its own */ "KeyringMode=shared\n" /* Make sure we can share cached keys among instances */ "OOMScoreAdjust=500\n" /* Unlocking can allocate a lot of memory if Argon2 is used */ "ExecStart=" SYSTEMD_CRYPTSETUP_PATH " attach '%s' '%s' '%s' '%s'\n" diff --git a/src/shared/rm-rf.c b/src/shared/rm-rf.c index 8e459f9d69..3a34281bb9 100644 --- a/src/shared/rm-rf.c +++ b/src/shared/rm-rf.c @@ -228,7 +228,7 @@ static int rm_rf_inner_child( if ((flags & REMOVE_SUBVOLUME) && btrfs_might_be_subvol(&st)) { /* This could be a subvolume, try to remove it */ - r = btrfs_subvol_remove_fd(fd, fname, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA); + r = btrfs_subvol_remove_at(fd, fname, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA); if (r < 0) { if (!IN_SET(r, -ENOTTY, -EINVAL)) return r; @@ -425,11 +425,14 @@ static int rm_rf_children_impl( return ret; } -int rm_rf(const char *path, RemoveFlags flags) { +int rm_rf_at(int dir_fd, const char *path, RemoveFlags flags) { mode_t old_mode; int fd, r, q = 0; - assert(path); + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + + if (FLAGS_SET(flags, REMOVE_ROOT)) + assert(path && !dot_or_dot_dot(path)); /* unlinkat() does not support AT_EMPTY_PATH or "." so a path must be provided here. */ /* For now, don't support dropping subvols when also only dropping directories, since we can't do * this race-freely. */ @@ -438,14 +441,13 @@ int rm_rf(const char *path, RemoveFlags flags) { /* We refuse to clean the root file system with this call. This is extra paranoia to never cause a * really seriously broken system. */ - if (path_equal_or_inode_same(path, "/", AT_SYMLINK_NOFOLLOW)) + if (path_is_root_at(dir_fd, path) > 0) return log_error_errno(SYNTHETIC_ERRNO(EPERM), - "Attempted to remove entire root file system (\"%s\"), and we can't allow that.", - path); + "Attempted to remove entire root file system, and we can't allow that."); if (FLAGS_SET(flags, REMOVE_SUBVOLUME | REMOVE_ROOT | REMOVE_PHYSICAL)) { /* Try to remove as subvolume first */ - r = btrfs_subvol_remove(path, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA); + r = btrfs_subvol_remove_at(dir_fd, path, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA); if (r >= 0) return r; @@ -458,13 +460,13 @@ int rm_rf(const char *path, RemoveFlags flags) { /* Not btrfs or not a subvolume */ } - fd = openat_harder(AT_FDCWD, path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME, flags, &old_mode); + fd = openat_harder(dir_fd, path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME, flags, &old_mode); if (fd >= 0) { /* We have a dir */ r = rm_rf_children_impl(fd, flags, NULL, old_mode); if (FLAGS_SET(flags, REMOVE_ROOT)) - q = RET_NERRNO(rmdir(path)); + q = RET_NERRNO(unlinkat(dir_fd, path, AT_REMOVEDIR)); } else { r = fd; if (FLAGS_SET(flags, REMOVE_MISSING_OK) && r == -ENOENT) @@ -479,8 +481,9 @@ int rm_rf(const char *path, RemoveFlags flags) { if (!FLAGS_SET(flags, REMOVE_PHYSICAL)) { struct statfs s; - if (statfs(path, &s) < 0) - return -errno; + r = xstatfsat(dir_fd, path, &s); + if (r < 0) + return r; if (is_physical_fs(&s)) return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Attempted to remove files from a disk file system under \"%s\", refusing.", @@ -488,7 +491,7 @@ int rm_rf(const char *path, RemoveFlags flags) { } r = 0; - q = RET_NERRNO(unlink(path)); + q = RET_NERRNO(unlinkat(dir_fd, path, 0)); } if (r < 0) diff --git a/src/shared/rm-rf.h b/src/shared/rm-rf.h index 24fd9a2aa2..9d93692e0a 100644 --- a/src/shared/rm-rf.h +++ b/src/shared/rm-rf.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include <fcntl.h> #include <sys/stat.h> #include "alloc-util.h" @@ -26,7 +27,10 @@ int fstatat_harder(int dfd, int rm_rf_children(int fd, RemoveFlags flags, const struct stat *root_dev); int rm_rf_child(int fd, const char *name, RemoveFlags flags); -int rm_rf(const char *path, RemoveFlags flags); +int rm_rf_at(int dir_fd, const char *path, RemoveFlags flags); +static inline int rm_rf(const char *path, RemoveFlags flags) { + return rm_rf_at(AT_FDCWD, path, flags); +} /* Useful for usage with _cleanup_(), destroys a directory and frees the pointer */ static inline char *rm_rf_physical_and_free(char *p) { diff --git a/src/systemd/sd-login.h b/src/systemd/sd-login.h index 7246d9f472..4700a76441 100644 --- a/src/systemd/sd-login.h +++ b/src/systemd/sd-login.h @@ -184,6 +184,9 @@ int sd_session_get_desktop(const char *session, char **desktop); /* Determine the X11 display of this session. */ int sd_session_get_display(const char *session, char **display); +/* Determine the leader process of this session. */ +int sd_session_get_leader(const char *session, pid_t *leader); + /* Determine the remote host of this session. */ int sd_session_get_remote_host(const char *session, char **remote_host); diff --git a/src/test/meson.build b/src/test/meson.build index 84d642bf6e..fe1a0fec21 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -15,6 +15,10 @@ test_env.set('SYSTEMD_LANGUAGE_FALLBACK_MAP', language_fallback_map) test_env.set('PATH', project_build_root + ':' + path) test_env.set('PROJECT_BUILD_ROOT', project_build_root) +if efi_addon != '' + test_env.set('EFI_ADDON', efi_addon) +endif + ############################################################ generate_sym_test_py = find_program('generate-sym-test.py') diff --git a/src/test/test-btrfs.c b/src/test/test-btrfs.c index 67acba23a7..ccb1661a91 100644 --- a/src/test/test-btrfs.c +++ b/src/test/test-btrfs.c @@ -54,11 +54,11 @@ int main(int argc, char *argv[]) { if (r < 0) log_error_errno(r, "Failed to write file: %m"); - r = btrfs_subvol_snapshot("/xxxtest", "/xxxtest2", 0); + r = btrfs_subvol_snapshot_at(AT_FDCWD, "/xxxtest", AT_FDCWD, "/xxxtest2", 0); if (r < 0) log_error_errno(r, "Failed to make snapshot: %m"); - r = btrfs_subvol_snapshot("/xxxtest", "/xxxtest3", BTRFS_SNAPSHOT_READ_ONLY); + r = btrfs_subvol_snapshot_at(AT_FDCWD, "/xxxtest", AT_FDCWD, "/xxxtest3", BTRFS_SNAPSHOT_READ_ONLY); if (r < 0) log_error_errno(r, "Failed to make snapshot: %m"); @@ -74,7 +74,8 @@ int main(int argc, char *argv[]) { if (r < 0) log_error_errno(r, "Failed to remove subvolume: %m"); - r = btrfs_subvol_snapshot("/etc", "/etc2", BTRFS_SNAPSHOT_READ_ONLY|BTRFS_SNAPSHOT_FALLBACK_COPY); + r = btrfs_subvol_snapshot_at(AT_FDCWD, "/etc", AT_FDCWD, "/etc2", + BTRFS_SNAPSHOT_READ_ONLY|BTRFS_SNAPSHOT_FALLBACK_COPY); if (r < 0) log_error_errno(r, "Failed to make snapshot: %m"); @@ -115,7 +116,7 @@ int main(int argc, char *argv[]) { if (mkdir("/xxxrectest/mnt", 0755) < 0) log_error_errno(errno, "Failed to make directory: %m"); - r = btrfs_subvol_snapshot("/xxxrectest", "/xxxrectest2", BTRFS_SNAPSHOT_RECURSIVE); + r = btrfs_subvol_snapshot_at(AT_FDCWD, "/xxxrectest", AT_FDCWD, "/xxxrectest2", BTRFS_SNAPSHOT_RECURSIVE); if (r < 0) log_error_errno(r, "Failed to snapshot subvolume: %m"); @@ -151,7 +152,8 @@ int main(int argc, char *argv[]) { if (r < 0) log_error_errno(r, "Failed to set up quota limit: %m"); - r = btrfs_subvol_snapshot("/xxxquotatest", "/xxxquotatest2", BTRFS_SNAPSHOT_RECURSIVE|BTRFS_SNAPSHOT_QUOTA); + r = btrfs_subvol_snapshot_at(AT_FDCWD, "/xxxquotatest", AT_FDCWD, "/xxxquotatest2", + BTRFS_SNAPSHOT_RECURSIVE|BTRFS_SNAPSHOT_QUOTA); if (r < 0) log_error_errno(r, "Failed to set up snapshot: %m"); diff --git a/src/test/test-chase.c b/src/test/test-chase.c index 558f4109e3..75c508970e 100644 --- a/src/test/test-chase.c +++ b/src/test/test-chase.c @@ -17,6 +17,19 @@ static const char *arg_test_dir = NULL; +static void test_chase_extract_filename_one(const char *path, const char *root, const char *expected) { + _cleanup_free_ char *ret1 = NULL, *ret2 = NULL, *fname = NULL; + + log_debug("/* %s(path=%s, root=%s) */", __func__, path, strnull(root)); + + assert_se(chase(path, root, CHASE_EXTRACT_FILENAME, &ret1, NULL) > 0); + assert_se(streq(ret1, expected)); + + assert_se(chase(path, root, 0, &ret2, NULL) > 0); + assert_se(chase_extract_filename(ret2, root, &fname) >= 0); + assert_se(streq(fname, expected)); +} + TEST(chase) { _cleanup_free_ char *result = NULL, *pwd = NULL; _cleanup_close_ int pfd = -EBADF; @@ -111,6 +124,19 @@ TEST(chase) { assert_se(path_equal(result, temp)); result = mfree(result); + /* Tests for CHASE_EXTRACT_FILENAME and chase_extract_filename() */ + + p = strjoina(temp, "/start"); + pslash = strjoina(p, "/"); + test_chase_extract_filename_one(p, NULL, "usr"); + test_chase_extract_filename_one(pslash, NULL, "usr"); + test_chase_extract_filename_one(p, temp, "usr"); + test_chase_extract_filename_one(pslash, temp, "usr"); + + p = strjoina(temp, "/slash"); + test_chase_extract_filename_one(p, NULL, "."); + test_chase_extract_filename_one(p, temp, "."); + /* Paths that would "escape" outside of the "root" */ p = strjoina(temp, "/6dots"); @@ -548,7 +574,7 @@ TEST(chaseat) { assert_se(chaseat(tfd, "i/../p", CHASE_MKDIR_0755|CHASE_NONEXISTENT, NULL, NULL) == -ENOENT); - /* Test CHASE_FILENAME */ + /* Test CHASE_EXTRACT_FILENAME */ assert_se(chaseat(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_NOFOLLOW|CHASE_EXTRACT_FILENAME, &result, &fd) >= 0); assert_se(faccessat(fd, result, F_OK, AT_SYMLINK_NOFOLLOW) >= 0); @@ -570,6 +596,10 @@ TEST(chaseat) { assert_se(streq(result, ".")); result = mfree(result); + assert_se(chaseat(tfd, NULL, CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, NULL) >= 0); + assert_se(streq(result, ".")); + result = mfree(result); + /* Test chase_and_openat() */ fd = chase_and_openat(tfd, "o/p/e/n/f/i/l/e", CHASE_MKDIR_0755, O_CREAT|O_EXCL|O_CLOEXEC, NULL); @@ -582,6 +612,12 @@ TEST(chaseat) { assert_se(fd_verify_directory(fd) >= 0); fd = safe_close(fd); + fd = chase_and_openat(tfd, NULL, CHASE_PARENT|CHASE_EXTRACT_FILENAME, O_PATH|O_DIRECTORY|O_CLOEXEC, &result); + assert_se(fd >= 0); + assert_se(streq(result, ".")); + fd = safe_close(fd); + result = mfree(result); + /* Test chase_and_openatdir() */ assert_se(chase_and_opendirat(tfd, "o/p/e/n/d/i", 0, &result, &dir) >= 0); @@ -643,11 +679,6 @@ TEST(chaseat) { result = mfree(result); } -static int intro(void) { - arg_test_dir = saved_argv[1]; - return EXIT_SUCCESS; -} - TEST(chaseat_prefix_root) { _cleanup_free_ char *cwd = NULL, *ret = NULL, *expected = NULL; @@ -685,4 +716,9 @@ TEST(chaseat_prefix_root) { assert_se(streq(ret, expected)); } +static int intro(void) { + arg_test_dir = saved_argv[1]; + return EXIT_SUCCESS; +} + DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro); diff --git a/src/test/test-execute.c b/src/test/test-execute.c index a07c837e3f..a63afa873b 100644 --- a/src/test/test-execute.c +++ b/src/test/test-execute.c @@ -1299,7 +1299,7 @@ static int prepare_ns(const char *process_name) { /* Copy unit files to make them accessible even when unprivileged. */ assert_se(get_testdata_dir("test-execute/", &unit_dir) >= 0); - assert_se(copy_directory(unit_dir, PRIVATE_UNIT_DIR, COPY_MERGE_EMPTY) >= 0); + assert_se(copy_directory_at(AT_FDCWD, unit_dir, AT_FDCWD, PRIVATE_UNIT_DIR, COPY_MERGE_EMPTY) >= 0); /* Prepare credstore like tmpfiles.d/credstore.conf for LoadCredential= tests. */ FOREACH_STRING(p, "/run/credstore", "/run/credstore.encrypted") { diff --git a/src/test/test-path-util.c b/src/test/test-path-util.c index 6b685a816f..31e2a3d296 100644 --- a/src/test/test-path-util.c +++ b/src/test/test-path-util.c @@ -600,6 +600,17 @@ TEST(path_startswith) { test_path_startswith_one("/foo/bar/barfoo/", "////foo/bar/barfoo/", "/foo/bar/barfoo/", ""); test_path_startswith_one("/foo/bar/barfoo/", "/foo/bar/barfoo", "/foo/bar/barfoo/", ""); + test_path_startswith_one("/foo/./bar///barfoo/./.", "/foo", "/foo/./", "bar///barfoo/./."); + test_path_startswith_one("/foo/./bar///barfoo/./.", "/foo/", "/foo/./", "bar///barfoo/./."); + test_path_startswith_one("/foo/./bar///barfoo/./.", "/", "/", "foo/./bar///barfoo/./."); + test_path_startswith_one("/foo/./bar///barfoo/./.", "////", "/", "foo/./bar///barfoo/./."); + test_path_startswith_one("/foo/./bar///barfoo/./.", "/foo//bar/////barfoo///", "/foo/./bar///barfoo/./.", ""); + test_path_startswith_one("/foo/./bar///barfoo/./.", "/foo/bar/barfoo////", "/foo/./bar///barfoo/./.", ""); + test_path_startswith_one("/foo/./bar///barfoo/./.", "/foo/bar///barfoo/", "/foo/./bar///barfoo/./.", ""); + test_path_startswith_one("/foo/./bar///barfoo/./.", "/foo////bar/barfoo/", "/foo/./bar///barfoo/./.", ""); + test_path_startswith_one("/foo/./bar///barfoo/./.", "////foo/bar/barfoo/", "/foo/./bar///barfoo/./.", ""); + test_path_startswith_one("/foo/./bar///barfoo/./.", "/foo/bar/barfoo", "/foo/./bar///barfoo/./.", ""); + test_path_startswith_one("/foo/bar/barfoo/", "/foo/bar/barfooa/", NULL, NULL); test_path_startswith_one("/foo/bar/barfoo/", "/foo/bar/barfooa", NULL, NULL); test_path_startswith_one("/foo/bar/barfoo/", "", NULL, NULL); diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index af1b904d32..e0087d997b 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -1540,7 +1540,7 @@ static int fd_set_attribute( return log_error_errno(procfs_fd, "Failed to re-open '%s': %m", path); unsigned previous, current; - r = chattr_full(NULL, procfs_fd, f, item->attribute_mask, &previous, ¤t, CHATTR_FALLBACK_BITWISE); + r = chattr_full(procfs_fd, NULL, f, item->attribute_mask, &previous, ¤t, CHATTR_FALLBACK_BITWISE); if (r == -ENOANO) log_warning("Cannot set file attributes for '%s', maybe due to incompatibility in specified attributes, " "previous=0x%08x, current=0x%08x, expected=0x%08x, ignoring.", diff --git a/src/ukify/test/test_ukify.py b/src/ukify/test/test_ukify.py index 692b7a384b..eae82c7f88 100755 --- a/src/ukify/test/test_ukify.py +++ b/src/ukify/test/test_ukify.py @@ -50,9 +50,9 @@ def test_round_up(): assert ukify.round_up(4097) == 8192 def test_namespace_creation(): - ns = ukify.create_parser().parse_args(('A','B')) - assert ns.linux == pathlib.Path('A') - assert ns.initrd == [pathlib.Path('B')] + ns = ukify.create_parser().parse_args(()) + assert ns.linux is None + assert ns.initrd is None def test_config_example(): ex = ukify.config_example() @@ -87,7 +87,7 @@ def test_apply_config(tmp_path): Phases = {':'.join(ukify.KNOWN_PHASES)} ''')) - ns = ukify.create_parser().parse_args(('A','B')) + ns = ukify.create_parser().parse_args(()) ns.linux = None ns.initrd = [] ukify.apply_config(ns, config) @@ -143,7 +143,7 @@ def test_parse_args_minimal(): assert opts.os_release in (pathlib.Path('/etc/os-release'), pathlib.Path('/usr/lib/os-release')) -def test_parse_args_many(): +def test_parse_args_many_deprecated(): opts = ukify.parse_args( ['/ARG1', '///ARG2', '/ARG3 WITH SPACE', '--cmdline=a b c', @@ -186,9 +186,57 @@ def test_parse_args_many(): assert opts.output == pathlib.Path('OUTPUT') assert opts.measure is False +def test_parse_args_many(): + opts = ukify.parse_args( + ['build', + '--linux=/ARG1', + '--initrd=///ARG2', + '--initrd=/ARG3 WITH SPACE', + '--cmdline=a b c', + '--os-release=K1=V1\nK2=V2', + '--devicetree=DDDDTTTT', + '--splash=splash', + '--pcrpkey=PATH', + '--uname=1.2.3', + '--stub=STUBPATH', + '--pcr-private-key=PKEY1', + '--pcr-public-key=PKEY2', + '--pcr-banks=SHA1,SHA256', + '--signing-engine=ENGINE', + '--secureboot-private-key=SBKEY', + '--secureboot-certificate=SBCERT', + '--sign-kernel', + '--no-sign-kernel', + '--tools=TOOLZ///', + '--output=OUTPUT', + '--measure', + '--no-measure', + ]) + assert opts.linux == pathlib.Path('/ARG1') + assert opts.initrd == [pathlib.Path('/ARG2'), pathlib.Path('/ARG3 WITH SPACE')] + assert opts.cmdline == 'a b c' + assert opts.os_release == 'K1=V1\nK2=V2' + assert opts.devicetree == pathlib.Path('DDDDTTTT') + assert opts.splash == pathlib.Path('splash') + assert opts.pcrpkey == pathlib.Path('PATH') + assert opts.uname == '1.2.3' + assert opts.stub == pathlib.Path('STUBPATH') + assert opts.pcr_private_keys == [pathlib.Path('PKEY1')] + assert opts.pcr_public_keys == [pathlib.Path('PKEY2')] + assert opts.pcr_banks == ['SHA1', 'SHA256'] + assert opts.signing_engine == 'ENGINE' + assert opts.sb_key == 'SBKEY' + assert opts.sb_cert == 'SBCERT' + assert opts.sign_kernel is False + assert opts.tools == [pathlib.Path('TOOLZ/')] + assert opts.output == pathlib.Path('OUTPUT') + assert opts.measure is False + def test_parse_sections(): opts = ukify.parse_args( - ['/ARG1', '/ARG2', + ['build', + '--linux=/ARG1', + '--initrd=/ARG2', '--section=test:TESTTESTTEST', '--section=test2:@FILE', ]) @@ -239,7 +287,10 @@ def test_config_priority(tmp_path): ''')) opts = ukify.parse_args( - ['/ARG1', '///ARG2', '/ARG3 WITH SPACE', + ['build', + '--linux=/ARG1', + '--initrd=///ARG2', + '--initrd=/ARG3 WITH SPACE', '--cmdline= a b c ', '--os-release=K1=V1\nK2=V2', '--devicetree=DDDDTTTT', @@ -302,7 +353,7 @@ def test_help(capsys): assert '--section' in out.out assert not out.err -def test_help_error(capsys): +def test_help_error_deprecated(capsys): with pytest.raises(SystemExit): ukify.parse_args(['a', 'b', '--no-such-option']) out = capsys.readouterr() @@ -310,6 +361,14 @@ def test_help_error(capsys): assert '--no-such-option' in out.err assert len(out.err.splitlines()) == 1 +def test_help_error(capsys): + with pytest.raises(SystemExit): + ukify.parse_args(['build', '--no-such-option']) + out = capsys.readouterr() + assert not out.out + assert '--no-such-option' in out.err + assert len(out.err.splitlines()) == 1 + @pytest.fixture(scope='session') def kernel_initrd(): try: @@ -326,7 +385,7 @@ def kernel_initrd(): initrd = f"{item['root']}{item['initrd'][0].split(' ')[0]}" except (KeyError, IndexError): continue - return [linux, initrd] + return ['--linux', linux, '--initrd', initrd] else: return None @@ -345,7 +404,11 @@ def test_basic_operation(kernel_initrd, tmpdir): pytest.skip('linux+initrd not found') output = f'{tmpdir}/basic.efi' - opts = ukify.parse_args(kernel_initrd + [f'--output={output}']) + opts = ukify.parse_args([ + 'build', + *kernel_initrd, + f'--output={output}', + ]) try: ukify.check_inputs(opts) except OSError as e: @@ -362,6 +425,7 @@ def test_sections(kernel_initrd, tmpdir): output = f'{tmpdir}/basic.efi' opts = ukify.parse_args([ + 'build', *kernel_initrd, f'--output={output}', '--uname=1.2.3', @@ -385,15 +449,22 @@ def test_sections(kernel_initrd, tmpdir): def test_addon(kernel_initrd, tmpdir): output = f'{tmpdir}/addon.efi' - opts = ukify.parse_args([ + args = [ + 'build', f'--output={output}', '--cmdline=ARG1 ARG2 ARG3', '--section=.test:CONTENTZ', - ]) + ] + if stub := os.getenv('EFI_ADDON'): + args += [f'--stub={stub}'] + expected_exceptions = () + else: + expected_exceptions = FileNotFoundError, + opts = ukify.parse_args(args) try: ukify.check_inputs(opts) - except OSError as e: + except expected_exceptions as e: pytest.skip(str(e)) ukify.make_uki(opts) @@ -416,7 +487,8 @@ def test_uname_scraping(kernel_initrd): if kernel_initrd is None: pytest.skip('linux+initrd not found') - uname = ukify.Uname.scrape(kernel_initrd[0]) + assert kernel_initrd[0] == '--linux' + uname = ukify.Uname.scrape(kernel_initrd[1]) assert re.match(r'\d+\.\d+\.\d+', uname) def test_efi_signing_sbsign(kernel_initrd, tmpdir): @@ -431,6 +503,7 @@ def test_efi_signing_sbsign(kernel_initrd, tmpdir): output = f'{tmpdir}/signed.efi' opts = ukify.parse_args([ + 'build', *kernel_initrd, f'--output={output}', '--uname=1.2.3', @@ -474,6 +547,7 @@ def test_efi_signing_pesign(kernel_initrd, tmpdir): output = f'{tmpdir}/signed.efi' opts = ukify.parse_args([ + 'build', *kernel_initrd, f'--output={output}', '--uname=1.2.3', @@ -501,10 +575,6 @@ def test_efi_signing_pesign(kernel_initrd, tmpdir): def test_pcr_signing(kernel_initrd, tmpdir): if kernel_initrd is None: pytest.skip('linux+initrd not found') - if os.getuid() != 0: - pytest.skip('must be root to access tpm2') - if subprocess.call(['systemd-creds', 'has-tpm2', '-q']) != 0: - pytest.skip('tpm2 is not available') ourdir = pathlib.Path(__file__).parent pub = unbase64(ourdir / 'example.tpm2-pcr-public.pem.base64') @@ -512,6 +582,7 @@ def test_pcr_signing(kernel_initrd, tmpdir): output = f'{tmpdir}/signed.efi' opts = ukify.parse_args([ + 'build', *kernel_initrd, f'--output={output}', '--uname=1.2.3', @@ -562,10 +633,6 @@ def test_pcr_signing(kernel_initrd, tmpdir): def test_pcr_signing2(kernel_initrd, tmpdir): if kernel_initrd is None: pytest.skip('linux+initrd not found') - if os.getuid() != 0: - pytest.skip('must be root to access tpm2') - if subprocess.call(['systemd-creds', 'has-tpm2', '-q']) != 0: - pytest.skip('tpm2 is not available') ourdir = pathlib.Path(__file__).parent pub = unbase64(ourdir / 'example.tpm2-pcr-public.pem.base64') @@ -578,8 +645,12 @@ def test_pcr_signing2(kernel_initrd, tmpdir): microcode.write(b'1234567890') output = f'{tmpdir}/signed.efi' + assert kernel_initrd[0] == '--linux' opts = ukify.parse_args([ - kernel_initrd[0], microcode.name, kernel_initrd[1], + 'build', + *kernel_initrd[:2], + f'--initrd={microcode.name}', + *kernel_initrd[2:], f'--output={output}', '--uname=1.2.3', '--cmdline=ARG1 ARG2 ARG3', diff --git a/src/ukify/ukify.py b/src/ukify/ukify.py index 19896afac3..a9c21601df 100755 --- a/src/ukify/ukify.py +++ b/src/ukify/ukify.py @@ -19,7 +19,9 @@ # pylint: disable=missing-docstring,invalid-name,import-outside-toplevel # pylint: disable=consider-using-with,unspecified-encoding,line-too-long # pylint: disable=too-many-locals,too-many-statements,too-many-return-statements -# pylint: disable=too-many-branches,fixme +# pylint: disable=too-many-branches,too-many-lines,too-many-instance-attributes +# pylint: disable=too-many-arguments,unnecessary-lambda-assignment,fixme +# pylint: disable=unused-argument import argparse import configparser @@ -436,9 +438,9 @@ def call_systemd_measure(uki, linux, opts): def join_initrds(initrds): - if len(initrds) == 0: + if not initrds: return None - elif len(initrds) == 1: + if len(initrds) == 1: return initrds[0] seq = [] @@ -478,6 +480,9 @@ def pe_add_sections(uki: UKI, output: str): pe.FILE_HEADER.IMAGE_FILE_LOCAL_SYMS_STRIPPED = True # Old stubs might have been stripped, leading to unaligned raw data values, so let's fix them up here. + # pylint thinks that Structure doesn't have various members that it has… + # pylint: disable=no-member + for i, section in enumerate(pe.sections): oldp = section.PointerToRawData oldsz = section.SizeOfRawData @@ -745,6 +750,7 @@ class ConfigItem: ) -> None: "Set namespace.<dest>[idx] to value, with idx derived from group" + # pylint: disable=protected-access if group not in namespace._groups: namespace._groups += [group] idx = namespace._groups.index(group) @@ -814,7 +820,10 @@ class ConfigItem: else: conv = lambda s:s - if self.nargs == '*': + # This is a bit ugly, but --initrd is the only option which is specified + # with multiple args on the command line and a space-separated list in the + # config file. + if self.name == '--initrd': value = [conv(v) for v in value.split()] else: value = conv(value) @@ -834,8 +843,17 @@ class ConfigItem: return (section_name, key, value) +VERBS = ('build',) + CONFIG_ITEMS = [ ConfigItem( + 'positional', + metavar = 'VERB', + nargs = '*', + help = f"operation to perform ({','.join(VERBS)})", + ), + + ConfigItem( '--version', action = 'version', version = f'ukify {__version__}', @@ -848,20 +866,18 @@ CONFIG_ITEMS = [ ), ConfigItem( - 'linux', - metavar = 'LINUX', + '--linux', type = pathlib.Path, - nargs = '?', help = 'vmlinuz file [.linux section]', config_key = 'UKI/Linux', ), ConfigItem( - 'initrd', - metavar = 'INITRD…', + '--initrd', + metavar = 'INITRD', type = pathlib.Path, - nargs = '*', - help = 'initrd files [.initrd section]', + action = 'append', + help = 'initrd file [part of .initrd section]', config_key = 'UKI/Initrd', config_push = ConfigItem.config_list_prepend, ), @@ -1068,7 +1084,7 @@ def apply_config(namespace, filename=None): # Fill in ._groups based on --pcr-public-key=, --pcr-private-key=, and --phases=. assert '_groups' not in namespace n_pcr_priv = len(namespace.pcr_private_keys or ()) - namespace._groups = list(range(n_pcr_priv)) + namespace._groups = list(range(n_pcr_priv)) # pylint: disable=protected-access cp = configparser.ConfigParser( comment_prefixes='#', @@ -1193,6 +1209,20 @@ def parse_args(args=None): p = create_parser() opts = p.parse_args(args) + # Figure out which syntax is being used, one of: + # ukify verb --arg --arg --arg + # ukify linux initrd… + if len(opts.positional) == 1 and opts.positional[0] in VERBS: + opts.verb = opts.positional[0] + elif opts.linux or opts.initrd: + raise ValueError('--linux/--initrd options cannot be used with positional arguments') + else: + print("Assuming obsolete commandline syntax with no verb. Please use 'build'.") + if opts.positional: + opts.linux = pathlib.Path(opts.positional[0]) + opts.initrd = [pathlib.Path(arg) for arg in opts.positional[1:]] + opts.verb = 'build' + # Check that --pcr-public-key=, --pcr-private-key=, and --phases= # have either the same number of arguments are are not specified at all. n_pcr_pub = None if opts.pcr_public_keys is None else len(opts.pcr_public_keys) @@ -1213,6 +1243,7 @@ def parse_args(args=None): def main(): opts = parse_args() check_inputs(opts) + assert opts.verb == 'build' make_uki(opts) |