From: dongshengyuan <545258830@qq.com> Date: Wed, 22 Jul 2026 11:55:32 +0000 (+0800) Subject: machine-id-setup: follow symlink target on --commit X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=aa776aefba2f6af4acf00c86067b19e5a6761a95;p=thirdparty%2Fsystemd.git machine-id-setup: follow symlink target on --commit Resolve /etc/machine-id before checking the mount state and before writing back, so symlinked targets are committed correctly. Fixes: #43104 Follow-up for: efabf4e04466c85183c969af27de1ff0ecda8e79 --- diff --git a/src/shared/machine-id-setup.c b/src/shared/machine-id-setup.c index 0b637757481..1ae55ae2e91 100644 --- a/src/shared/machine-id-setup.c +++ b/src/shared/machine-id-setup.c @@ -318,17 +318,37 @@ int machine_id_commit(const char *root) { * in a mount namespace, a new file is created at the right place. Afterwards the mount is also removed in the * original mount namespace, thus revealing the file that was just created. */ - _cleanup_close_ int etc_fd = -EBADF; - _cleanup_free_ char *etc = NULL; - r = chase("/etc/", root, CHASE_PREFIX_ROOT|CHASE_MUST_BE_DIRECTORY, &etc, &etc_fd); - if (r < 0) - return log_error_errno(r, "Failed to open %s: %m", "/etc/"); + _cleanup_close_ int etc_machine_id_fd = -EBADF; + _cleanup_free_ char *etc_machine_id = NULL; + r = chase("/etc/machine-id", root, CHASE_PREFIX_ROOT|CHASE_MUST_BE_REGULAR, + &etc_machine_id, &etc_machine_id_fd); + if (r == -ENOENT) { + _cleanup_close_ int etc_fd = -EBADF; + _cleanup_free_ char *etc = NULL, *target = NULL; + + r = chase("/etc/", root, CHASE_PREFIX_ROOT|CHASE_MUST_BE_DIRECTORY, &etc, &etc_fd); + if (r < 0) + return log_error_errno(r, "Failed to open %s: %m", "/etc/"); + + etc_machine_id = path_join(etc, "machine-id"); + if (!etc_machine_id) + return log_oom(); - _cleanup_free_ char *etc_machine_id = path_join(etc, "machine-id"); - if (!etc_machine_id) - return log_oom(); + r = readlinkat_malloc(etc_fd, "machine-id", &target); + if (r == -ENOENT) { + log_debug_errno(r, "%s does not exist. Nothing to do.", etc_machine_id); + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to read symlink target of %s: %m", etc_machine_id); - r = is_mount_point_at(etc_fd, "machine-id", /* flags= */ 0); + log_debug("%s is a dangling symlink to %s. Nothing to do.", etc_machine_id, target); + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to open %s: %m", "/etc/machine-id"); + + r = is_mount_point_at(etc_machine_id_fd, /* path= */ NULL, /* flags= */ 0); if (r < 0) return log_error_errno(r, "Failed to determine whether %s is a mount point: %m", etc_machine_id); if (r == 0) { @@ -338,11 +358,12 @@ int machine_id_commit(const char *root) { /* Read existing machine-id */ - _cleanup_close_ int fd = xopenat_full(etc_fd, "machine-id", O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, XO_REGULAR, MODE_INVALID); + _cleanup_close_ int fd = xopenat_full(etc_machine_id_fd, /* path= */ NULL, + O_RDONLY|O_CLOEXEC|O_NOCTTY, XO_REGULAR, MODE_INVALID); if (fd < 0) return log_error_errno(fd, "Cannot open %s: %m", etc_machine_id); - etc_fd = safe_close(etc_fd); + etc_machine_id_fd = safe_close(etc_machine_id_fd); r = fd_is_temporary_fs(fd); if (r < 0) @@ -366,22 +387,34 @@ int machine_id_commit(const char *root) { if (r < 0) return log_error_errno(r, "Failed to set up new mount namespace: %m"); - /* Open /etc/ again after we transitioned into our own private mount namespace */ - _cleanup_close_ int etc_fd_again = -EBADF; - r = chase("/etc/", root, CHASE_PREFIX_ROOT|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &etc_fd_again); + _cleanup_free_ char *etc_machine_id_filename = NULL; + _cleanup_close_ int etc_machine_id_dir_fd = + chase_and_open_parent("/etc/machine-id", root, CHASE_PREFIX_ROOT, &etc_machine_id_filename); + if (etc_machine_id_dir_fd < 0) + return log_error_errno(etc_machine_id_dir_fd, + "Failed to open parent directory of %s: %m", + etc_machine_id); + + r = inode_same_at(fd, /* filea= */ NULL, + etc_machine_id_dir_fd, etc_machine_id_filename, AT_EMPTY_PATH); if (r < 0) - return log_error_errno(r, "Failed to open %s: %m", "/etc/"); + return log_error_errno(r, "Failed to verify %s before unmounting: %m", etc_machine_id); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(ESTALE), + "%s changed while preparing to commit.", + etc_machine_id); - r = umountat_detach_verbose(LOG_ERR, etc_fd_again, "machine-id"); + r = umountat_detach_verbose(LOG_ERR, etc_machine_id_dir_fd, etc_machine_id_filename); if (r < 0) return r; /* Update a persistent version of etc_machine_id */ - r = id128_write_at(etc_fd_again, "machine-id", ID128_FORMAT_PLAIN | ID128_SYNC_ON_WRITE, id); + r = id128_write_at(etc_machine_id_dir_fd, etc_machine_id_filename, + ID128_FORMAT_PLAIN | ID128_SYNC_ON_WRITE, id); if (r < 0) return log_error_errno(r, "Cannot write %s. This is mandatory to get a persistent machine ID: %m", etc_machine_id); - etc_fd_again = safe_close(etc_fd_again); + etc_machine_id_dir_fd = safe_close(etc_machine_id_dir_fd); /* Return to initial namespace and proceed a lazy tmpfs unmount */ r = namespace_enter(/* pidns_fd= */ -EBADF, diff --git a/test/units/TEST-74-AUX-UTILS.machine-id-setup.sh b/test/units/TEST-74-AUX-UTILS.machine-id-setup.sh index bcd2f315713..072a6aeaa9a 100755 --- a/test/units/TEST-74-AUX-UTILS.machine-id-setup.sh +++ b/test/units/TEST-74-AUX-UTILS.machine-id-setup.sh @@ -69,6 +69,27 @@ testcase_transient() { diff "$root/etc/machine-id" "$root/run/machine-id" } +testcase_transient_symlink() { + local root transient_id committed_id + + root="$(mktemp -d)" + trap "root_cleanup $root" RETURN + root_mock "$root" + + mkdir -p "$root/persist/etc" + echo abc >>"$root/persist/etc/machine-id" + ln -s /persist/etc/machine-id "$root/etc/machine-id" + mount -o remount,ro "$root" + mount -t tmpfs tmpfs "$root/run" + transient_id="$(systemd-machine-id-setup --print --root "$root")" + mountpoint "$root/persist/etc/machine-id" + mount -o remount,rw "$root" + committed_id="$(systemd-machine-id-setup --print --commit --root "$root")" + [[ "$transient_id" == "$committed_id" ]] + (! mountpoint "$root/persist/etc/machine-id") + diff "$root/persist/etc/machine-id" "$root/run/machine-id" +} + # Check if we correctly processed the invalid machine ID we set up in the respective # test.sh file systemctl --state=failed --no-legend --no-pager | tee /failed