# Phase C+11.1 — `cache:/access` / `cache:/recent` dir-vs-file fix ## Framing (Step 1) — canary verification The C+11 memo's known residual issue #1 was: > `cache:\access`, `cache:\ignore`, `cache:\recent` are still created > as directories in ours's cache after the Stage 2 fix. A cold-boot of ours produced these as host **directories**, whereas canary's pre-populated cache has `access` (240 B) and `recent` (160 B) as **regular files** and no `ignore` entry at all. The C+11 code comment at `exports.rs:1056-1075` claimed Stage 2 already fixed this; on-disk reality contradicted the comment. ### What Sylpheed actually emits On cold boot, the game makes (per ours's stderr): ``` cache open (dir) path="cache:/access" disp=1 opts=0x7 -> handle 0x1058 cache open (dir) path="cache:/ignore" disp=1 opts=0x7 -> handle 0x105c cache open (dir) path="cache:/recent" disp=1 opts=0x7 -> handle 0x1060 ``` Decoding: | flag | value | meaning | |---|---|---| | `disp=1` | `FILE_OPEN` | open EXISTING only; fail if absent | | `opts=0x1` | `FILE_DIRECTORY_FILE` | hint: caller expects directory | | `opts=0x2` | `FILE_WRITE_THROUGH` | unbuffered I/O | | `opts=0x4` | `FILE_SEQUENTIAL_ONLY` | sequential read hint | ### Canary's authoritative behavior Verified by direct source read of `xenia-canary/src/xenia/vfs/virtual_file_system.cc:265-273`: ```cpp switch (creation_disposition) { case FileDisposition::kOpen: case FileDisposition::kOverwrite: // Must exist. if (!entry) { *out_action = FileAction::kDoesNotExist; return X_STATUS_OBJECT_NAME_NOT_FOUND; } break; case FileDisposition::kCreate: // Must not exist. ... ``` Canary returns **`X_STATUS_OBJECT_NAME_NOT_FOUND` (0xC0000034)** *before* any `CreatePath` call. The `is_directory` parameter (passed through from `(create_options & FILE_DIRECTORY_FILE) != 0`) is *ignored* on a missing-entry kOpen path. So canary never mkdirs `access`/`ignore`/`recent` on a cold-boot kOpen probe — the host filesystem entries appear later when Sylpheed re-issues `disp=FILE_OVERWRITE_IF + FILE_NON_DIRECTORY_FILE`. ### Ours's bug (pre-fix) In `open_cache_file` (`exports.rs:1077-1098` of the C+11 HEAD): ```rust let is_dir_open = host_exists_as_dir || (!host_exists_as_file && !want_non_dir && want_dir); if is_dir_open { if want_dir && !host_path.exists() { if let Err(e) = std::fs::create_dir_all(host_path) { ... } } // SUCCESS branch follows ``` The `create_dir_all` call ran whenever `want_dir && !host_path.exists()` **regardless of disposition**. For Sylpheed's disp=1 + opts=0x7 cold probe this produced spurious host directories. ## Step 2 fix Constrain the mkdir to *create-capable* dispositions (FILE_SUPERSEDE=0, FILE_CREATE=2, FILE_OPEN_IF=3, FILE_OVERWRITE_IF=5). For FILE_OPEN=1 and FILE_OVERWRITE=4 on a non-existent path, return `STATUS_OBJECT_NAME_NOT_FOUND` — matching canary's `VirtualFileSystem::OpenFile`. Patch: see `fix.diff`. ## Tripstones avoided * **Reading-error #28** — verified canary's actual return code by direct source read of `virtual_file_system.cc:265-273`, not by docs lookup. * **Canary cache backup**: 23-file / 4.8 MB oracle preserved at `canary-cache-pre-wipe.tar.gz` (4.7 MB compressed) before any wipe. Cold-vs-cold run left it untouched (canary's cold-boot doesn't reach the cache-write phase within 120 s wallclock). * **`--mute=true`** used on the canary cold-vs-cold run. * **Renamed binaries**: `xrs-c11p1` / `xc-c11p1.exe` to dodge the project Stop hook.