CNA-BUG-153: StorageContainer::DeleteFile removes an empty directory and DeleteDirectory removes a file

CNA snapshot 009d40f5  ·  Known Issues › Current bugs  ·  source links pinned to 009d40f5

✓

Evidence basis: source-verified at the pinned commit; 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.

Both methods call std::filesystem::remove on the resolved path without checking the entry type, so each deletes the other kind of entry, and a missing directory is a silent no-op where XNA throws.

Identifier
CNA-BUG-153
Category
Bug
Subsystem
Storage
Status
Open
Verified against
CNA 009d40f5 (009d40f5dd085c4e674d3479675fac84b12b3e0a)
Severity
Low (a triage suggestion, not a project priority)
Evidence basis
Source-verified: read at TARGET, not executed
Tests touching this area
Yes: see Current tests
Affected contract
Microsoft::Xna::Framework::Storage::StorageContainer::DeleteFile(const std::string&) and DeleteDirectory(const std::string&)

Expected behaviour

XNA 4.0 delegates to File.Delete and Directory.Delete (decompiled StorageContainer): the first refuses a directory, the second refuses a file and throws DirectoryNotFoundException for a missing directory. CNA's header documents DeleteFile as "Deletes a relative file" and DeleteDirectory as "Deletes a relative directory (must be empty)".

Actual behaviour at TARGET

After the containment check both methods in StorageContainer.cpp call fs::remove(ResolveNativePath(path)). DeleteFile("slot1") therefore removes an empty directory named slot1, DeleteDirectory("notes.txt") removes a file, a missing entry of either kind returns silently, and a non-empty directory throws std::filesystem::filesystem_error from either method.

Source locations

Evidence

Checked by reading StorageContainer.cpp and its header at 009d40f5; not executed. The XNA behaviour is from the decompiled StorageContainer and the documented semantics of File.Delete/Directory.Delete. The candidate also noted GlobMatch's byte-wise ?; that part is published under CNA-BUG-047. The storage internals page records the type-blind deletes.

Focused reproduction

// Illustrative; not compiled or run for this entry.
container->CreateDirectory("slot1");
container->DeleteFile("slot1");          // removes the empty directory
auto note = container->CreateFile("notes.txt");
note.reset();
container->DeleteDirectory("notes.txt"); // removes the file
container->DeleteDirectory("missing");   // returns silently (XNA: DirectoryNotFoundException)

Current tests

StorageDeviceTests.cpp calls both methods only with escaping paths, to prove they are refused (ContainerOperationsRejectLexicalEscapes). Nothing checks the entry type or a missing entry.

Regression test

Tests that DeleteFile on a directory and DeleteDirectory on a file both throw and remove nothing, and that DeleteDirectory on a missing name throws; fix by checking is_regular_file/is_directory on the resolved native path first.

Blast radius

Cleanup code that deletes entries by name without knowing their type can remove a directory or file it did not mean to; code that relies on an exception for a missing directory never gets one. Deleting an existing entry of the right type behaves as documented.

Workaround

Check FileExists or DirectoryExists before deleting.

The same subject is explained at several altitudes. These are the neighbouring pages at each one.

Known issues
Bug index