Skip to content

feat(libSceLibcInternal): handle every __opendir2 flag - #12977

Merged
adriadam10 merged 1 commit into
boykopovar:mainfrom
KernelPanicNT:feat/libcinternal-opendir2-flags
Oct 30, 2026
Merged

adriadam10 merged 1 commit into
boykopovar:mainfrom
KernelPanicNT:feat/libcinternal-opendir2-flags

Conversation

@KernelPanicNT

Copy link
Copy Markdown
Contributor

What

__opendir2 no longer throws for flags outside DTF_HIDEW | DTF_NODUP | DTF_REWIND. It now handles every flag value the way FreeBSD 11.4 does.

Source of the behaviour. FreeBSD 11.4 libc, the release the libSceUsbd and libScePosixForWebKit debt entries already pin:

  • __opendir2 returns NULL before it opens anything, with errno unchanged, when __DTF_READALL (0x8) or __DTF_SKIPREAD (0x10) is set. Any other value goes to __opendir_common.
  • __opendir_common stores the flags in dd_flags and acts only on DTF_NODUP, and only when fstatfs reports unionfs or MNT_UNION. No guest filesystem reports either. DTF_REWIND is defined but never read by libc. DTF_HIDEW only makes readdir skip DT_WHT entries, and host filesystems never produce those. Bits above 0x10 are stored and never read. The flag values are in dirent.h.
  • opendir is __opendir2(name, DTF_HIDEW|DTF_NODUP). So every value without 0x8 or 0x10 opens the same stream as the in-tree opendir_nid_postfix.
  • The guest DIR is opaque: FreeBSD declares struct _dirdesc only in gen-private.h, and the AnyPS5 stream is a host record. That leaves no guest-visible dd_flags to fill.

Lines that change on main:

The device's answer was not measured, and the debt entry still says so.

Changes:

  • libSceLibcInternal/Directory.cpp: __opendir2 returns null with errno untouched when 0x8 or 0x10 is set. Otherwise it calls opendir. The NotImplemented_nid_no_patch path is gone.
  • guest_opendir2:
    • 0x0, 0x1, 0x20, 0x27, 0x100, 0x40000000 and 0xffffffe7 list the same entries as opendir;
    • 0x8, 0x10, 0x18, 0xf, 0x1f, 0x7fffffff and -1 return null and leave errno at a sentinel, both for an existing directory and for a null name;
    • a missing path with 0x20 fails with opendir's errno.
  • docs/dev/debt/libSceLibcInternal.md: the entry is cut to what stays open, which is that the device's answer is not measured.

Tested

Linux (GitHub Codespace, native build of this branch on bb04a4f1ea):

  • guest_opendir2 passes.
  • Negative control: with Directory.cpp from main, guest_opendir2 aborts (flag 0x20 reaches NotImplemented_nid_no_patch).

Checks:

  • python3 tools/check_conventions.py --base origin/main passes.
  • tools/progress.py --compare gives libSceLibcInternal 72/75 to 73/75 (+1, __opendir2_nid_postfix).

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 under Silent stubs in their library's technical debt file
  • 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
  • Fixes: none
  • Unblocks: none
  • GPUs tested: n/a
  • AI-assisted: yes

Follow FreeBSD 11.4's __opendir2: __DTF_READALL or __DTF_SKIPREAD
returns NULL with errno unchanged, and every other value opens the
directory as opendir does, since the remaining flags only act on union
mounts and whiteout entries.
@github-actions

Copy link
Copy Markdown

Progress report (bb04a4f → 03fc7bb)

📈 System libraries: 46.5% (+0.01%, +1 implemented functions)

✅ 1 implemented
Library Name
libSceLibcInternal __opendir2_nid_postfix

Changed libraries (implemented exports)

Library Before After Change
libSceLibcInternal 72/75 (96.00%) 73/75 (97.33%) +1.33 pp

@adriadam10 adriadam10 self-assigned this Oct 29, 2026

@rooklz rooklz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed head 03fc7bbca3 against main bb04a4f1ea, on macOS arm64. I only checked it on the host. I did not build or run anything on the console.

Gates

  • tools/check_conventions.py --base upstream/main passes.
  • git diff --check is clean, and git merge-tree against bb04a4f1ea is clean.
  • pr_overlap merged 12 of 12 PRs. The only shared file is the debt file: #5021 and #10749 already conflict with main on it, so neither could be compared.

Source check (FreeBSD d106002967)

  • __opendir2 (opendir.c L83-84) returns NULL with errno untouched for __DTF_READALL|__DTF_SKIPREAD before _open, so a null name never gets dereferenced. This matches the PR's null-name rows.
  • __opendir_common stores dd_flags (L293) and branches only on DTF_NODUP, and only for unionfs/MNT_UNION (L312-317).
  • In readdir.c, DTF_HIDEW is read only for DT_WHT (L85). The __DTF_* bits are internal state that readdir, rewinddir and telldir set and clear. Nothing reads bits above 0x10.
  • opendir is __opendir2(name, DTF_HIDEW|DTF_NODUP) (L61).

So "every value without 0x8/0x10 is opendir" holds as long as there is no union mount and the DIR stays opaque.

Standalone harness
I used the verbatim PR body and main body over host opendir, with the PR's test rows ported 1:1, built with clang++ -O1 and ASan+UBSan:

  • PR head: PASS.

  • main: aborts at the first new flag (0x20 → NotImplemented). This matches the author's negative control.

  • Mutants:

    • only 0x8 checked: KILLED (0x10 row)
    • only 0x10 checked: KILLED
    • sets errno=EINVAL: KILLED (errno sentinel row)
    • rejects all of ~0x7: KILLED (0x20/0x100 rows)
    • requires both bits: KILLED

    All 5 mutants were killed.

Not run

  • The real guest_opendir2 target. Submodules are not initialised in my lane. The PR's windows check passed, and linux was skipped by CI.
  • The device's answer for any flag. The debt line says this openly, which is the right call.

Verdict: APPROVE.

@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 5021 docs/dev/debt/libSceLibcInternal.md
PR 11749 docs/dev/debt/libSceLibcInternal.md
PR 11750 docs/dev/debt/libSceLibcInternal.md
PR 11751 docs/dev/debt/libSceLibcInternal.md

@JJenkx

JJenkx commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

Windows run of this head (03fc7bbca3), merged onto main bb04a4f1ea (clean merge, local only). Windows 11 Enterprise 10.0.22621, Ryzen 9 5950X, WinLibs GCC 15.2.0 UCRT r7, Ninja, Release, BUILD_TESTING=ON, no Vulkan driver visible.

  • This PR: guest_opendir2 passes 3 of 3 (ctest --repeat until-fail:3).
  • Negative control: with this PR's source files (core/libs/prx/libSceLibcInternal/Directory.cpp) put back to main's version and its tests kept, guest_opendir2 fails.

AI-assisted: yes (Claude Code).

@adriadam10
adriadam10 merged commit d6cf4d4 into boykopovar:main Oct 30, 2026
11 checks passed
@adriadam10

Copy link
Copy Markdown
Collaborator

Checked __opendir2 against FreeBSD 11.4.0 opendir.c (NULL before opening when __DTF_READALL/__DTF_SKIPREAD is set, everything else goes to the common path) and the DTF_* values in dirent.h; the new test rows pin both branches and the errno behaviour, and flag 0x20 threw on main; merged.

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.

4 participants