From 10a7a2357d58d8224dcef3d04ffd67c417634f45 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Sat, 7 Nov 2026 05:11:36 +0900 Subject: [PATCH 1/4] 0.3.1: a name in the long spelling is the name it spells locate compared Windows' long spelling (\\?\C:\..., \\?\UNC\...) as written, so the remainder kept ?/C: and openkal refused the colon: every located operation upon a name fs::extended produced failed with errc::invalid. The long spelling is read as the plain name it denotes; the Windows test writes, reads, stamps, identifies and renames through both spellings. The kernel layer is a compatible range. Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com> --- CHANGELOG.md | 16 ++++++++++++++++ README.md | 2 +- mcpp.toml | 12 ++++++------ src/fs.cpp | 19 ++++++++++++++++++- tests/fs_test.cpp | 20 ++++++++++++++++++++ 5 files changed, 61 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ce535d..fbd3081 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## 0.3.1 + +- **A name in the long spelling is the name it spells.** `fs::extended` writes + `\\?\C:\...` and `\\?\UNC\server\share\...` on Windows. `locate` compared that + spelling as written: the leading `//` matched the supplied directory of shares + and the remainder kept `?/C:`, whose colon openkal refuses, so `read`, `write`, + `rename`, `stamp_of`, `identity_of` and every other located operation upon such a + name failed with `errc::invalid` (mcpp-community/mcpp#889). The long spelling is + now read as the plain name it denotes. The Windows test writes, reads, stamps, + identifies and renames a file through both spellings. +- **The kernel layer is a compatible range.** openkal `^0.24.0` and its + implementations `^0.25.0` (Linux), `^0.22.0` (macOS), `^0.20.0` (Windows) and + `^0.13.0` (Emscripten). A target that selects openkal-platform receives the + versions of its version group; any other build receives the newest compatible + releases, recorded in `mcpp.lock`. + ## 0.2.7 - **openkal 0.21.** The implementations of openkal 0.21 (openkal-linux 0.22.0, diff --git a/README.md b/README.md index 469704b..a1c7804 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ with compile-time platform constants and a few platform modules. ```toml [dependencies] -os = { namespace = "mcpplibs", version = "0.3.0" } +os = { namespace = "mcpplibs", version = "0.3.1" } ``` ```cpp diff --git a/mcpp.toml b/mcpp.toml index ebc4091..bf627a8 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "os" -version = "0.3.0" +version = "0.3.1" description = "mcpplibs.os: a C++ operating-system library over openkal --- processes, files, environment, terminals, directories and local IPC, with compile-time platform constants and a few platform modules." license = "Apache-2.0" authors = ["mcpplibs"] @@ -12,23 +12,23 @@ repo = "https://github.com/mcpplibs/os" # own C and C++ runtime. A program that wants the standalone arrangement, or # the image arrangement of `-openkal`, selects it in its own manifest. [dependencies] -openkal = "0.24.0" +openkal = "^0.24.0" [target.linux.dependencies] -openkal-linux = "0.25.0" +openkal-linux = "^0.25.0" [target.macos.dependencies] -openkal-macos = "0.22.0" +openkal-macos = "^0.22.0" [target.windows.dependencies] -openkal-windows = "0.20.0" +openkal-windows = "^0.20.0" # Emscripten (`wasm32-emscripten`): openkal written above the C library, with # twelve of openkal's fifteen interfaces. `openkal.process', `openkal.exec' and # `openkal.space' are absent, so a program that starts one fails at link # naming the symbol; `openkal.task' is present in a `-pthread' link only. [target.'cfg(os = "emscripten")'.dependencies] -openkal-emscripten = "0.13.0" +openkal-emscripten = "^0.13.0" [targets.os] kind = "lib" diff --git a/src/fs.cpp b/src/fs.cpp index f20dbe5..ecc4e39 100644 --- a/src/fs.cpp +++ b/src/fs.cpp @@ -212,9 +212,26 @@ bool names_a_place(std::string_view name) { return name.find('/') != std::string_view::npos; } +// A NAME IN THE LONG SPELLING IS THE NAME IT SPELLS (0.3.1). `extended' writes +// `\\?\C:\...' and `\\?\UNC\server\share\...'. Compared as written, the leading +// `//' matched the supplied directory of shares and the remainder kept `?/C:', +// whose colon openkal refuses as a name: every operation upon such a name +// failed with errc::invalid (mcpp-community/mcpp#889). The long spelling only +// lifts Windows' length limit; the file it names is the plain one. +namespace { +std::string plain_spelling(std::string path) { + if (host() != platform::windows || !path.starts_with("//?/")) return path; + if (path.size() > 8 && (path.compare(4, 4, "UNC/") == 0 || path.compare(4, 4, "unc/") == 0)) + return "//" + path.substr(8); + if (path.size() >= 6 && std::isalpha(static_cast(path[4])) && path[5] == ':') + return path.substr(4); + return path; +} +} // namespace + std::optional locate(const std::filesystem::path& name) { const auto whole = mcpplibs::os::fs::absolute(name); - const std::string path = slashed(generic_utf8(whole.lexically_normal())); + const std::string path = plain_spelling(slashed(generic_utf8(whole.lexically_normal()))); const bool fold = conventions().case_insensitive_names; std::optional best; std::size_t best_len = 0; diff --git a/tests/fs_test.cpp b/tests/fs_test.cpp index 4291e1d..fdfed42 100644 --- a/tests/fs_test.cpp +++ b/tests/fs_test.cpp @@ -201,5 +201,25 @@ int main() { check(os::fs::windows_extended("C:/..") == "\\\\?\\C:\\", "and `..' never climbs above the drive"); check(os::fs::windows_extended("rel/x") == "rel/x", "a relative name is not this rule's"); if (os::host() != os::platform::windows) check(os::fs::extended("/a/b") == "/a/b", "elsewhere the name is itself"); + + // THE LONG SPELLING NAMES THE SAME FILE (0.3.1). Every operation that + // locates a name accepts the name `extended' produces. + if (os::host() == os::platform::windows) { + const auto base = stdfs::temp_directory_path() / std::format("mcpplibs-os-long-{}", run_tag()); + std::error_code ec; + stdfs::create_directories(base, ec); + const auto plain = base / "a.txt"; + const auto longer = os::fs::extended(plain); + check(os::fs::generic_utf8(longer).starts_with("//?/"), "the long spelling is produced"); + const auto at = os::fs::locate(longer); + check(at && at->relative.find(':') == std::string::npos, "it is located beneath a drive, not beneath the shares"); + check(os::fs::write(longer, "one").has_value(), "a file is written through it"); + check(os::fs::read(longer).value_or("") == "one" && os::fs::read(plain).value_or("") == "one", + "and read through either spelling"); + check(os::fs::stamp_of(longer).has_value() && os::fs::identity_of(longer).has_value(), "its stamp and identity are read"); + const auto moved = os::fs::extended(base / "b.txt"); + check(os::fs::rename(longer, moved, true).has_value() && stdfs::exists(base / "b.txt"), "and it is renamed"); + stdfs::remove_all(base, ec); + } return g_failures; } From b1639b3182345f7d91a0b27c6bbb79379161841d Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Sat, 7 Nov 2026 05:26:19 +0900 Subject: [PATCH 2/4] test: the long spelling is compared with the plain name, not with one spelling of it Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com> --- tests/fs_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/fs_test.cpp b/tests/fs_test.cpp index fdfed42..8441e71 100644 --- a/tests/fs_test.cpp +++ b/tests/fs_test.cpp @@ -210,7 +210,7 @@ int main() { stdfs::create_directories(base, ec); const auto plain = base / "a.txt"; const auto longer = os::fs::extended(plain); - check(os::fs::generic_utf8(longer).starts_with("//?/"), "the long spelling is produced"); + check(longer != plain, "the long spelling is produced"); const auto at = os::fs::locate(longer); check(at && at->relative.find(':') == std::string::npos, "it is located beneath a drive, not beneath the shares"); check(os::fs::write(longer, "one").has_value(), "a file is written through it"); From 11ed637113d82d270a67aecf2a1df4cad9a98163 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Sat, 7 Nov 2026 10:04:46 +0900 Subject: [PATCH 3/4] fix: the long spelling is read before the name is normalized, and extended() reads a Windows name itself Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com> --- CHANGELOG.md | 10 ++++++++-- checks/hosted/mcpp.toml | 2 +- src/fs.cpp | 15 ++++++++++----- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fbd3081..be00d97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,14 @@ and the remainder kept `?/C:`, whose colon openkal refuses, so `read`, `write`, `rename`, `stamp_of`, `identity_of` and every other located operation upon such a name failed with `errc::invalid` (mcpp-community/mcpp#889). The long spelling is - now read as the plain name it denotes. The Windows test writes, reads, stamps, - identifies and renames a file through both spellings. + now read as the plain name it denotes, before the name is normalized (a C++ + library with POSIX's rules reads the leading `//` as `/`). `fs::extended` makes a + name absolute by this library's own reading of a Windows name: over the hosted + arrangement `std::filesystem::absolute` took `C:\Users\...` for a relative name + and put the working directory before it. The Windows test writes, reads, + stamps, identifies and renames a file through both spellings, natively and over + the hosted arrangement (openkal-platform 0.24.2, whose C++ runtime keeps the + volume of a normalized name, openkal-llvm-runtime#68). - **The kernel layer is a compatible range.** openkal `^0.24.0` and its implementations `^0.25.0` (Linux), `^0.22.0` (macOS), `^0.20.0` (Windows) and `^0.13.0` (Emscripten). A target that selects openkal-platform receives the diff --git a/checks/hosted/mcpp.toml b/checks/hosted/mcpp.toml index 5f5f3ba..30507ab 100644 --- a/checks/hosted/mcpp.toml +++ b/checks/hosted/mcpp.toml @@ -13,7 +13,7 @@ version = "0.1.0" os = { path = "../.." } [target.'cfg(any(linux, macos, windows))'] -platform = "openkal-platform@0.24.0" +platform = "openkal-platform@0.24.1" # The tests are the package's own, each reached by an include from # tests/.cpp here (a test program is a file of the package that runs it). diff --git a/src/fs.cpp b/src/fs.cpp index ecc4e39..b760dd6 100644 --- a/src/fs.cpp +++ b/src/fs.cpp @@ -230,8 +230,10 @@ std::string plain_spelling(std::string path) { } // namespace std::optional locate(const std::filesystem::path& name) { - const auto whole = mcpplibs::os::fs::absolute(name); - const std::string path = plain_spelling(slashed(generic_utf8(whole.lexically_normal()))); + // The plain spelling first: a C++ library with POSIX's rules reads the + // leading `//' of `//?/C:/...' as `/' when it normalizes the name. + const auto whole = mcpplibs::os::fs::absolute(from_utf8(plain_spelling(slashed(generic_utf8(name))))); + const std::string path = slashed(generic_utf8(whole.lexically_normal())); const bool fold = conventions().case_insensitive_names; std::optional best; std::size_t best_len = 0; @@ -615,9 +617,12 @@ std::string windows_extended(std::string_view in) { std::filesystem::path extended(const std::filesystem::path& name) { if (host() != platform::windows || name.empty()) return name; - std::error_code ec; - const auto abs = std::filesystem::absolute(name, ec); - if (ec) return name; + // This library's own reading of a Windows name (0.3.1): `C:\...' is + // absolute however the C++ library beneath reads it. Over the hosted + // arrangement std::filesystem takes `\' as an ordinary character, so + // `std::filesystem::absolute' put the working directory before the drive + // (`Z:/cwd/C:\Users\...'), and the long spelling named no file. + const auto abs = mcpplibs::os::fs::absolute(name); const auto u8 = abs.generic_u8string(); const auto spelled = windows_extended(std::string(reinterpret_cast(u8.data()), u8.size())); return std::filesystem::path(std::u8string(reinterpret_cast(spelled.data()), spelled.size())); From 722044dccfcb7f24b48db94b87c21ccc0bace468 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Sat, 7 Nov 2026 10:28:52 +0900 Subject: [PATCH 4/4] ci: the hosted and image rows over the openkal-platform 0.24.2 group Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com> --- .github/workflows/ci.yml | 18 ++++++++++-------- README.md | 2 +- checks/hosted/mcpp.toml | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3de86d0..7ff9eaa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -224,10 +224,11 @@ jobs: runs-on: ubuntu-24.04 timeout-minutes: 120 env: - # openkal 0.23.0, which this release is written against, is the one - # openkal-platform 0.23.3's version group names; mcpp 2026.11.3.1 and - # later pin it for their openkal rows. - MCPP_PLATFORM: openkal-platform@0.24.0 + # openkal 0.24.0, which this release is written against, in the group of + # openkal-platform 0.24.2, whose C++ run-time keeps the volume of a + # normalized name (openkal-llvm-runtime 0.19.2); the long-spelling tests + # of 0.3.1 need it on Windows. + MCPP_PLATFORM: openkal-platform@0.24.2 defaults: run: shell: bash @@ -276,10 +277,11 @@ jobs: runs-on: ${{ matrix.os }} timeout-minutes: 45 env: - # openkal 0.23.0, which this release is written against, is the one - # openkal-platform 0.23.3's version group names; mcpp 2026.11.3.1 and - # later pin it for their openkal rows. - MCPP_PLATFORM: openkal-platform@0.24.0 + # openkal 0.24.0, which this release is written against, in the group of + # openkal-platform 0.24.2, whose C++ run-time keeps the volume of a + # normalized name (openkal-llvm-runtime 0.19.2); the long-spelling tests + # of 0.3.1 need it on Windows. + MCPP_PLATFORM: openkal-platform@0.24.2 strategy: fail-fast: false matrix: diff --git a/README.md b/README.md index a1c7804..cb7ff0f 100644 --- a/README.md +++ b/README.md @@ -199,5 +199,5 @@ The Linux backend is openkal-linux 0.24.1. It requires Landlock ABI 3 before claiming confinement, which protects read-only grants against raw file truncation. Older ABIs refuse a confinement request before starting a program. The backend's ABI probe cache uses atomic accesses during concurrent first -queries. Hosted and image CI select the matching openkal-platform 0.23.3 group. +queries. Hosted and image CI select the openkal-platform 0.24.2 group. The library's own implementation is unchanged. diff --git a/checks/hosted/mcpp.toml b/checks/hosted/mcpp.toml index 30507ab..f0f0705 100644 --- a/checks/hosted/mcpp.toml +++ b/checks/hosted/mcpp.toml @@ -13,7 +13,7 @@ version = "0.1.0" os = { path = "../.." } [target.'cfg(any(linux, macos, windows))'] -platform = "openkal-platform@0.24.1" +platform = "openkal-platform@0.24.2" # The tests are the package's own, each reached by an include from # tests/.cpp here (a test program is a file of the package that runs it).