CNA-BUG-152: StorageDevice::getFreeSpaceProperty, getTotalSpaceProperty and getIsConnectedProperty pass the UTF-8 root to std::filesystem through the narrow path constructor
Evidence basis: source-verified at the pinned commit; inferred from the source (the behaviour was not run); tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit 009d40f5; unless a sentence says otherwise, nothing here was built or executed. Nothing on this page was executed unless the Evidence section says so.
The three device properties hand the UTF-8 root string straight to fs::exists, fs::space and fs::path, which on Windows reinterpret it in the ANSI code page, so with a non-ASCII root they query a different path.
- Identifier
CNA-BUG-152- Category
- Bug
- Subsystem
- Storage
- Status
- Open
- Verified against
- CNA
009d40f5(009d40f5dd085c4e674d3479675fac84b12b3e0a) - Severity
- Low (a triage suggestion, not a project priority)
- Evidence basis
- Inferred (strong): follows from the code, but the behaviour was not run
- Tests touching this area
- Yes: see Current tests
- Affected contract
- Microsoft::Xna::Framework::Storage::StorageDevice::getFreeSpaceProperty(), getTotalSpaceProperty() and getIsConnectedProperty()
Expected behaviour
CNA's path model (docs/filesystem-path-model.md, rule 2): a std::string holding a path is UTF-8 and "is converted back into a std::filesystem::path before it touches the filesystem again", through PathFromUtf8 (PathUtf8.hpp). Every other storage operation follows that rule.
Actual behaviour at TARGET
In StorageDevice.cpp, getFreeSpaceProperty and getTotalSpaceProperty call fs::exists(root) and fs::space(root) with the std::string root, and getIsConnectedProperty builds fs::path p(root). On POSIX the bytes pass through unchanged. On Windows the narrow constructor reads the string through the process ANSI code page, so every non-ASCII character in the root, including one the code page can spell (the two UTF-8 bytes of e-acute become two other characters), yields a different path. That affects a root containing such a character in the user profile path, in LOCALAPPDATA or in the application name given to SetAppNameEXT: fs::exists reports false, the space properties return LLONG_MAX ("unlimited") instead of the real figure, and getIsConnectedProperty walks up from the wrong path.
Source locations
modules/storage/src/StorageDevice.cpp— getFreeSpaceProperty, getTotalSpaceProperty and getIsConnectedProperty use the std::string root directlymodules/core/include/CNA/Internal/PathUtf8.hpp— PathFromUtf8, the conversion every other storage path usesdocs/filesystem-path-model.md— rule 2: a narrow path string is UTF-8 and is converted before it touches the filesystem
Evidence
Checked by reading StorageDevice.cpp and the path model at 009d40f5; not exercised on Windows. Every other path in StorageDevice.cpp and StorageContainer.cpp converts with PathFromUtf8, and EnsureStorageRoot reads the environment with GetEnvironmentVariableW precisely so that non-ASCII roots survive; these three properties are the remaining narrow sites in the module.
Independent re-verification: Checked by reading StorageDevice.cpp and the path model at 009d40f5; not exercised on Windows. Every other path in StorageDevice.cpp and StorageContainer.cpp converts with PathFromUtf8, and EnsureStorageRoot reads the environment with GetEnvironmentVariableW precisely so that non-ASCII roots survive; these three properties are the remaining narrow sites in the module. The Windows consequence rests on the standard library's narrow-to-wide conversion through the ANSI code page: CNA's own spikes/windows-unicode-path-spike records the ANSI behaviour of path::string() and of fopen given UTF-8 bytes (which fails for representable names such as cafe with an accent too) but did not measure the path(std::string) constructor itself, and a search of the tree finds no manifest that sets a UTF-8 active code page. The basis is therefore inferred, not executed. Tests: CApi_StorageSmoke (C ABI builds only) calls the free-space, total-space and connection queries on an ASCII root; nothing runs them on a non-ASCII root.
Focused reproduction
// Illustrative, Windows with ANSI code page 1252; not run.
StorageDevice::SetAppNameEXT("Hra\xC4\x8D"); // "Hrac" with c-caron (U+010D) in UTF-8; not in code page 1252
auto device = StorageDevice::EndShowSelector(
StorageDevice::BeginShowSelector(nullptr, nullptr).get());
long long freeBytes = device->getFreeSpaceProperty(); // LLONG_MAX: the narrowed root does not exist
Current tests
StorageDeviceTests.cpp tests none of the three properties; UnicodePathResolutionTests.cpp covers the core helpers, not storage.
Regression test
A Windows test with a non-ASCII application name expecting getFreeSpaceProperty() to equal fs::space(PathFromUtf8(GetStorageRootEXT())).available and getIsConnectedProperty() to be true; the fix is PathFromUtf8 in all three properties.
Blast radius
Windows users whose data root or application name contains characters outside the ANSI code page: free-space checks see unlimited space and the connection check examines the wrong directory. POSIX hosts, ASCII roots and every container file operation are unaffected.
Workaround
Keep the application name ASCII, and do not rely on the space properties when the Windows profile path is non-ASCII.
Related pages
The same subject is explained at several altitudes. These are the neighbouring pages at each one.
- User guide
- Storage guide: the storage root
- Internals
- Storage internals: root selection and the latch · Core internals: path conversion and containment
- Known issues
- Bug index