Skip to content

fix(libkernel): open directories whose host path ends in a separator on Windows - #4513

Merged
oneandonlydean merged 1 commit into
boykopovar:mainfrom
winstrex:fix/libkernel-open-directory-trailing-separator
Oct 12, 2026
Merged

oneandonlydean merged 1 commit into
boykopovar:mainfrom
winstrex:fix/libkernel-open-directory-trailing-separator

Conversation

@winstrex

Copy link
Copy Markdown
Contributor

What

On Windows, sceKernelOpen with O_DIRECTORY fails with 0x80020002 (ENOENT) on an existing directory when the resolved host path ends in a separator. That covers the guest root /, ., <dir>/ and <dir>/... Only <dir> without a trailing separator opens.

Found with The Pathless (PPSA01825). At boot, Unreal calls sceKernelOpen("/app0/..", O_DIRECTORY) to list the parent of the content root. On Windows the call returned ENOENT and getdents was never reached:

[src/Open.cpp sceKernelOpen] path=/app0/.. flags=0x20000 nativeFlags=0x8000 mode=0555
-> 0x80020002

Cause: in OpenNative (core/libs/prx/libkernel/File/src/Open.cpp), the CRT _wopen fails on a path ending in \ with ENOENT, and the fallback to File::OpenDirectoryDescriptor only runs when errno != ENOENT. A directory whose host path ends in a separator never reaches it.

Fix: when the host path has no filename, the fallback runs regardless of the errno. Trailing separators are stripped with the same loop sceKernelMkdir uses since #2819 (while (!path.has_filename() && path.has_relative_path()) path = path.parent_path()), so a host drive root such as C:\ is kept as is. The directory descriptor is then opened on the stripped path. A missing path without a trailing separator still returns ENOENT without an extra stat, and the EEXIST/EISDIR checks are unchanged. Linux is unaffected because open(2) accepts a trailing slash on a directory.

This follows the earlier trailing-separator fixes in libkernel: #2743/#2819 (sceKernelMkdir) and #2300 (sceKernelStat).

Tested

Windows 11, WinLibs GCC 15.2.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed in TechnicalDebt
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Unblocks: none (PPSA01825 gets past this open but stops right after it, see Tested)
  • AI-assisted: yes

…on Windows

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@oneandonlydean oneandonlydean self-assigned this Oct 12, 2026
@xXPBSXx

xXPBSXx commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

Windows test: head a47178b86 merged onto current main d592d54e1 (clean merge, no conflicts). Windows 10, MinGW GCC (WinLibs), Ninja Release, -j2: full build 0 errors; ctest with the filter guest_filesystem: guest_filesystem passed (1 test(s), 0 failed); python tools/check_conventions.py --base upstream/main passed. I did not run the new tests against main without the change.

AI-assisted (Claude Code).

@oneandonlydean
oneandonlydean merged commit f7b238e into boykopovar:main Oct 12, 2026
9 checks passed
@winstrex
winstrex deleted the fix/libkernel-open-directory-trailing-separator branch October 25, 2026 05:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants