[Bug 60220] New: GetLastError() returns garbage after file API calls, breaking .NET exception mapping
http://bugs.winehq.org/show_bug.cgi?id=60220 Bug ID: 60220 Summary: GetLastError() returns garbage after file API calls, breaking .NET exception mapping Product: Wine Version: 10.0 Hardware: x86-64 OS: MacOS Status: UNCONFIRMED Severity: normal Priority: P2 Component: kernel32 Assignee: wine-bugs@list.winehq.org Reporter: winebugs@protonmail.ch Target Milestone: --- SUMMARY GetLastError() returns a garbage value after several file API calls -- both after successful calls and, critically, on the failure return of failing calls, where the value is what distinguishes "file not found" from a genuine I/O error. Callers that branch on the error code therefore take the wrong path. Managed runtimes are hit hardest because .NET/Mono map the Win32 error to an exception TYPE: with a garbage code, .NET throws a generic IOException -- often with the nonsensical message "Success" (i.e. strerror(0)) -- instead of DirectoryNotFoundException or FileNotFoundException. Application code that catches the specific type to handle a benign case instead sees a fatal error. ENVIRONMENT Wine : 10.0 (Sikarugir build, Kegworks/WineskinNavy wrapper) OS : macOS 26, Apple M3 Max (arm64, x86_64 via Rosetta 2) App : Cities: Skylines II 1.6.0f1 (Unity 2022, Mono runtime) OBSERVED IN Directory.Delete(path, recursive: true) fails on a directory that deletes cleanly on Windows. In CoreFX's FileSystem.RemoveDirectoryRecursive, the post-loop error check after FindNextFile reads a garbage GetLastError (neither 0 nor ERROR_NO_MORE_FILES / 18) and throws BEFORE reaching the actual RemoveDirectoryInternal call, so the directory is never removed. The same class of failure appears in File.Delete, directory enumeration of a nonexistent path, and deletion of a path with an open handle (which should report ERROR_SHARING_VIOLATION). STEPS TO REPRODUCE A self-contained probe is attached; it needs no game install. It runs a small .NET assembly under the same Mono runtime the application uses. 1. Build monohost.exe from monohost.c (mingw-w64): x86_64-w64-mingw32-gcc monohost.c -o monohost.exe 2. Build the probe with the prefix's own C# compiler: wine "<prefix>/drive_c/windows/Microsoft.NET/Framework64/v4.0.30319/csc.exe" \ /out:filetest_net.exe filetest_net.cs 3. Run: WINEPREFIX=<prefix> wine 'Z:\path\monohost.exe' \ 'Z:\path\filetest_net.exe' 'C:\probe' 4. Read sections [5] [6] [7] and [10]-[13]. EXPECTED vs ACTUAL Directory.Delete(dir, recursive:true) on a populated directory expected : succeeds, directory removed actual : throws; directory remains Directory.GetDirectories(<nonexistent path>) expected : DirectoryNotFoundException actual : generic IOException, message "Success" File.Delete / Directory.Delete on a path with an open handle expected : IOException (sharing violation) actual : garbage error code File.Delete(<nonexistent file>) expected : silent no-op actual : throws, though the file is genuinely absent IMPACT Eight separate binary patches to mscorlib.dll and the application's own IO layer exist solely to make callers tolerate this. Any managed application doing routine filesystem work is affected. ADDITIONAL INFORMATION Linux/Proton does NOT reproduce this, which suggests it is specific to the macOS backend rather than to Wine's file layer generally. -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=60220 --- Comment #1 from James <winebugs@protonmail.ch> --- Created attachment 81921 --> http://bugs.winehq.org/attachment.cgi?id=81921 Reproducer: .NET probe exercising the affected file APIs (sections 5,6,7,10-13) -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=60220 --- Comment #2 from James <winebugs@protonmail.ch> --- Created attachment 81922 --> http://bugs.winehq.org/attachment.cgi?id=81922 Reproducer host: runs the probe under the application's own Mono runtime -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=60220 James <winebugs@protonmail.ch> changed: What |Removed |Added ---------------------------------------------------------------------------- Resolution|--- |INVALID Status|UNCONFIRMED |RESOLVED --- Comment #3 from James <winebugs@protonmail.ch> --- Correcting my own report: the central claim here is wrong, and I am closing it. After filing, I built a focused native Win32 probe that exercises the failure paths this report names, and ran the SAME binary on two Wine versions. GetLastError is CORRECT in every case, including the FindNextFile-exhausted case I specifically called out: test wine-11.15 wine-10.0 (Sikarugir) -------------------------------------------- ----------- --------------------- CreateFile(missing file) -> 2 OK OK CreateFile(under missing dir) -> 3 OK OK DeleteFile(missing) -> 2 OK OK FindFirstFile(missing dir) -> 3 OK OK GetLastError after FindNextFile loop exhausted -> 18 OK OK RemoveDirectory(non-empty) -> 145 OK OK CreateFile(already held excl.) -> 32 OK OK DeleteFile(open handle) -> 32 OK OK RemoveDirectory(missing) -> 2 OK OK 9/9 correct on both versions. So the Win32 layer is behaving correctly and this bug, as written, is invalid. The underlying symptom is real and reproducible in the application (a .NET/Unity game): System.IO throws a generic IOException with the message "Success" (strerror(0)) instead of the typed DirectoryNotFoundException / FileNotFoundException. But the cause is not the raw Win32 error code, and I misattributed it. Two candidates I have not yet separated: 1. Unity's forked Mono runtime (mono-2.0-bdwgc.dll) mishandling the error in its P/Invoke layer -- this would be a wine-mono / mscoree matter, not kernel32, and may not be a Wine bug at all. 2. Concurrency. The failure was originally observed under the application's heavily concurrent IO. My probe is single-threaded and passes consistently, so it cannot reproduce a race. I would rather withdraw a wrong report than leave it consuming triage time. Closing as INVALID. If I can characterise it properly -- specifically, reproduce it under concurrency or isolate it to the managed layer -- I will file a new bug against the correct component with a reproducer that actually demonstrates it. Apologies for the noise, and thanks for maintaining Wine. -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
participants (1)
-
WineHQ Bugzilla