Problem
On iOS the Wine prefix sits on a case-sensitive volume. When find_file_in_dir can't stat a name in its exact case, it asks for the volume's case sensitivity and then reads the whole directory. lookup_unix_name repeats both for the reparse name (name?) when that lookup fails too. On iOS each of those path syscalls also goes through the sandbox's path check.
Source-engine games probe thousands of absent paths, one per search path for every material, model and sound. Team Fortress 2 on an iPhone 15 Pro Max (iPhone16,2, iOS 27.0.1) took about 10 minutes to reach a map. _profile showed one game thread spending ~90% of its time in find_file_in_dir → fstatat / get_dir_case_sensitivity → getattrlistat / openat, and it stayed busy with lookups during play.
Change
dlls/ntdll/unix/file.c, all under WINE_IOS:
- Matching: directory listings are kept in memory per unix path. Names are matched with the same
wcsnicmpthe scan uses, behind a case-folded hash, so hits, misses and the reparse retry make no syscall. - Freshness: a listing is trusted for 1 s while nothing was created, renamed, linked or deleted through Wine. After that, one
statof the directory (dev/ino/mtime) decides whether to read it again.open_unix_filebumps a generation counter after a create-capable open. The wineserver reports its own changes through the exportedmadeira_dircache_changed(); that hook is in the companion Madeira PR. - Scope: only root lookups that may ignore case are cached. Short (8.3) names, directories that can't be listed and directories with more than 65536 names still go through the scan. The cache flushes itself past 8192 directories or 1M names.
- Locking: the lock is
pthread_mutex_lock. Wine'smutex_lockturns into a no-op once any pseudo-process sets the sharedprocess_exiting. - Settings:
MADEIRA_DIRCACHE=0turns it off.MADEIRA_DIRCACHE_TTL_MSsets the trust window (default 1000; 0 checks the directory on every lookup). An[dircache]stats line is logged every 10 s while it's in use.
Validation
- On device: iPhone 15 Pro Max (iPhone16,2, iOS 27.0.1), in a fork build that carries this change with others, TF2 with DXMT's x64 d3d9 (willfaust/Madeira#242's frontend). Main menu in 45 s and a map in 55 s, down from about 10 minutes; 26–28 fps in the map instead of 22.7. In one session, 4,467,307 lookups (3,546,718 found, 921,588 absent) took 22,717 directory checks and 1,244 directory reads, and none fell back to the scan.
- Host test:
tests/host/check-dircache.pyin the Madeira PR compiles the cache under ASan/UBSan and checks it against a real directory scan. It covers other-case and absent names, outside writers, deletes, a replaced directory, unlistable directories, the size limit and readers racing a writer.
Companion: willfaust/Madeira#427 (server hooks, Settings entries, host test, pin).
🤖 Generated with Claude Code