From a4a9347453640f26ed6ea8a5787542d6aeb49ded Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Fri, 6 Nov 2026 18:46:10 +0900 Subject: [PATCH] fix(fs): rename nested destinations relative to parent --- src/fs.cpp | 22 ++++++++++- tests/rename_beneath_preopen.cpp | 64 ++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 tests/rename_beneath_preopen.cpp diff --git a/src/fs.cpp b/src/fs.cpp index 6e6b722..f7108bb 100644 --- a/src/fs.cpp +++ b/src/fs.cpp @@ -1222,13 +1222,31 @@ static int rename_with(kal_dir from, const char* a, kal_uintptr alen, if (!okw::acceptable(a, alen) || !okw::acceptable(b, blen)) return kal_err_invalid; if (refused_write(source_root) || refused_write(target_root)) return kal_err_permission; + // FileRenameInformation takes a directory handle and a simple leaf name. + // A name beneath the supplied root may contain several components; open + // its parent through that root before asking the object manager to rename. + struct target_parent { + kal_dir handle{}; + ~target_parent() { if (handle.h) kal_fs_close_dir(handle); } + } parent; + void* target_directory = target_root; + kal_uintptr leaf = 0; + for (kal_uintptr i = 0; i < blen; ++i) + if (b[i] == '/' || b[i] == '\\') leaf = i + 1; + if (leaf != 0) { + const int opened = kal_fs_open_dir(to, b, leaf - 1, &parent.handle); + if (opened != kal_ok) return opened; + target_directory = dir_handle(parent.handle); + if (refused_write(target_directory)) return kal_err_permission; + } + void* h = nullptr; // The name is renamed, a link included, never what it leads to. const long r = open_relative(source_root, a, alen, DELETE | SYNCHRONIZE, okw::file_open, okw::file_open_reparse_point | okw::file_open_for_backup_intent, &h); if (!okw::ok(r)) return okw::translate_nt(r); - okw::wide_name w(b, blen); + okw::wide_name w(b + leaf, blen - leaf); if (!w.ok) { okw::NtClose(h); return kal_err_invalid; } // The renaming record carries the name inline. It is obtained rather than @@ -1243,7 +1261,7 @@ static int rename_with(kal_dir from, const char* a, kal_uintptr alen, // field clear: the object manager refuses with a name collision and changes // nothing, on every format that renames at all. info->replace_if_exists = replace ? 1 : 0; - info->root_directory = target_root; + info->root_directory = target_directory; info->file_name_length = w.string.length; for (unsigned i = 0; i < w.string.length / 2; ++i) info->file_name[i] = w.buffer[i]; diff --git a/tests/rename_beneath_preopen.cpp b/tests/rename_beneath_preopen.cpp new file mode 100644 index 0000000..9a298d4 --- /dev/null +++ b/tests/rename_beneath_preopen.cpp @@ -0,0 +1,64 @@ +// A rename into a nested name beneath a supplied directory uses the target's +// parent handle and its leaf. A compiler status file exercises this shape. +#include +#include + +namespace { +int failures = 0; +void check(bool ok, const char* what) { + std::printf("%s: %s\n", ok ? "ok" : "FAIL", what); + if (!ok) ++failures; +} +} + +int main() { + kal_dir root{}; + char name[512]{}; + kal_uintptr length = 0; + check(kal_fs_preopen(0, &root, name, sizeof name, &length) == kal_ok, + "the working directory is supplied"); + if (!root.h) return 1; + + constexpr char directory[] = "okw-rename-nested"; + constexpr char child[] = "okw-rename-nested/child"; + constexpr char source[] = "okw-rename-nested/child/source"; + constexpr char target[] = "okw-rename-nested/child/target"; + kal_fs_remove(root, source, sizeof source - 1); + kal_fs_remove(root, target, sizeof target - 1); + kal_fs_remove(root, child, sizeof child - 1); + kal_fs_remove(root, directory, sizeof directory - 1); + check(kal_fs_mkdir(root, directory, sizeof directory - 1) == kal_ok, + "the outer directory is made"); + check(kal_fs_mkdir(root, child, sizeof child - 1) == kal_ok, + "the nested directory is made"); + kal_file file{}; + const int created = kal_fs_open(root, source, sizeof source - 1, + KAL_OPEN_WRITE | KAL_OPEN_CREATE, &file); + check(created == kal_ok, "the source is created under the supplied root"); + if (file.h) kal_fs_close_file(file); + if (created == kal_ok) { + check(kal_fs_rename_flags(root, source, sizeof source - 1, + root, target, sizeof target - 1, + KAL_RENAME_NO_REPLACE) == kal_ok, + "a nested relative destination is renamed atomically"); + kal_file second{}; + const int made = kal_fs_open(root, source, sizeof source - 1, + KAL_OPEN_WRITE | KAL_OPEN_CREATE, &second); + check(made == kal_ok, "a second source is created"); + if (second.h) kal_fs_close_file(second); + if (made == kal_ok) { + check(kal_fs_rename_flags(root, source, sizeof source - 1, + root, target, sizeof target - 1, + KAL_RENAME_NO_REPLACE) == kal_err_exists, + "no-replace keeps the existing nested destination"); + check(kal_fs_rename(root, source, sizeof source - 1, + root, target, sizeof target - 1) == kal_ok, + "replace updates the nested destination"); + } + } + kal_fs_remove(root, source, sizeof source - 1); + kal_fs_remove(root, target, sizeof target - 1); + kal_fs_remove(root, child, sizeof child - 1); + kal_fs_remove(root, directory, sizeof directory - 1); + return failures ? 1 : 0; +}