Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
# 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, 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
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,
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
2 changes: 1 addition & 1 deletion checks/hosted/mcpp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.2"

# The tests are the package's own, each reached by an include from
# tests/<name>.cpp here (a test program is a file of the package that runs it).
Expand Down
12 changes: 6 additions & 6 deletions mcpp.toml
Original file line number Diff line number Diff line change
@@ -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"]
Expand All @@ -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 `<arch>-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"
Expand Down
30 changes: 26 additions & 4 deletions src/fs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,27 @@ 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<unsigned char>(path[4])) && path[5] == ':')
return path.substr(4);
return path;
}
} // namespace

std::optional<located> locate(const std::filesystem::path& name) {
const auto whole = mcpplibs::os::fs::absolute(name);
// 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<located> best;
Expand Down Expand Up @@ -598,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<const char*>(u8.data()), u8.size()));
return std::filesystem::path(std::u8string(reinterpret_cast<const char8_t*>(spelled.data()), spelled.size()));
Expand Down
20 changes: 20 additions & 0 deletions tests/fs_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(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");
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;
}
Loading