Skip to content

feat(libc): add the Annex K stream printf, scanf and freopen_s functions - #9527

Open
AlxcBTW wants to merge 7 commits into
boykopovar:mainfrom
AlxcBTW:pr/libc-annex-k-stream-io
Open

AlxcBTW wants to merge 7 commits into
boykopovar:mainfrom
AlxcBTW:pr/libc-annex-k-stream-io

Conversation

@AlxcBTW

@AlxcBTW AlxcBTW commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

What

Adds twelve C11 Annex K functions that libSceLibcInternal exports (PS5 3.20 inventory) and that exist nowhere on main: fprintf_s, vfprintf_s, vprintf_s, fwprintf_s, wprintf_s, vfwprintf_s, vwprintf_s, scanf_s, fscanf_s, vscanf_s, vfscanf_s and freopen_s. SDK headers steer code to the _s forms, so a title built with them imports these. No title is named here; the change is generic. They sit beside their siblings and reuse the formatting and scanning engines of the unchecked and _s functions already on main.

Printf side (Formatting.cpp, FormattingWide.cpp):

  • vfprintf_s, fprintf_s and vprintf_s format with FormatWindows(..., secure = true), the engine snprintf_s uses, and write the finished text to the stream in one fwrite. A null stream, format or argument list, any %n, and a null %s, %ls or %S argument return -1 and write nothing. A closed stream sets EBADF as fprintf does. vprintf_s writes to the guest standard output stream.
  • vfwprintf_s, fwprintf_s, vwprintf_s and wprintf_s do the same through FormatWide(..., secure = true), the engine of swprintf_s and snwprintf_s, and return the number of wide characters like vfwprintf.

Scanf side (Formatting.cpp):

  • ScanGuest, the scanner behind sscanf_s, read its input as a NUL-terminated string. It now takes its input through a small StringSource/StreamSource pair (peek, skip, advance, scan one directive) and the body is shared: ScanGuest keeps its signature and behaviour for strings, and ScanGuestStream runs the same scanner over a FileStream by reading each directive with the host fscanf and one character of look-ahead pushed back. So the size rules of sscanf_s (size after the pointer for %s, %c, %[, as unsigned int; an undersized destination gets a null first element and returns the count so far; a null destination returns EOF) apply unchanged, and the stream stops after the last character a conversion used.
  • vfscanf_s is the entry; fscanf_s, scanf_s and vscanf_s call it (scanf_s and vscanf_s read the guest standard input stream, as scanf and vscanf do). A null stream or format returns EOF; a closed stream sets EBADF and returns EOF. The 16-bit wide conversions throw on Linux in the stream forms, exactly as in fscanf.

freopen_s (FileAndHeap.cpp) returns EINVAL (22) for a null result, name, mode or stream (and sets *result to null when it can); otherwise it calls freopen, stores the stream in *result and returns 0, or stores null and returns the errno of the failed reopen.

Constraint handling is the one main has for the siblings: a violation only returns, the installed constraint handler is not called (see snprintf_s, fopen_s, sscanf_s and their debt entries). The result values for the stream forms were not measured on a PS5; they are recorded as such in docs/dev/debt/libc.md (three new entries). RSIZE_MAX is not touched (#9360, #8965).

Relation to #9087 (open, "follow snprintf_s and sscanf_s constraints as measured on a PS5"): it rewrites ScanGuest and FormatSecure in the same file (handler calls, the -23 result, size_t sizes). This PR is based on main and does not depend on it; whichever lands second needs a mechanical rebase of the ScanGuest hunk, after which the new stream functions should call the handler the way #9087 does for their siblings. The values #9087 measured are for snprintf_s and sscanf_s only, so I did not copy them to the stream functions.

Tests extend GuestStdioVariadic.cpp (registered as guest_stdio_variadic) and GuestFopenErrors.cpp (guest_fopen_errors); no new target.

Tested

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 (no named title import)
  • GPUs tested: n/a
  • AI-assisted: yes

@github-actions

Copy link
Copy Markdown

Progress report (a6dcea9 → 239dc9b)

📈 System libraries: 42.39% (+0.06%, +11 implemented, +1 declared functions)

✅ 11 implemented
Library Name
libc fprintf_s_nid_postfix
libc freopen_s_nid_postfix
libc fscanf_s_nid_postfix
libc fwprintf_s_nid_postfix
libc scanf_s_nid_postfix
libc vfprintf_s_nid_postfix
libc vfwprintf_s_nid_postfix
libc vprintf_s_nid_postfix
libc vscanf_s_nid_postfix
libc vwprintf_s_nid_postfix
libc wprintf_s_nid_postfix
🆕 1 declared as stubs
Library Name
libc vfscanf_s_nid_postfix

@MarcZX10 MarcZX10 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.

The changed scanner appears to preserve the existing string-scanner behavior while factoring it into the stream source, and the secure output/reopen cases have useful focused coverage. The author-reported Linux CTest filter passes; current eligibility, conventions, intake, labels, report, relinker matrix/ASan, register-context, and write-watch checks pass. Hosted full Linux and Windows product jobs were skipped, and I did not run a local build/test.

Please resolve the overlap order before merge. The live overlap scan reports shared scanner code with #9087 and #7386, the shared test file with #8593, and the debt insertion with #6565, #8872, #8880, #9001, #9087, #9133, #9170, #9447, and #9526; several older conflicted-base PR comparisons also require manual review. The body acknowledges #9087's same ScanGuest/FormatSecure hunk and says whichever lands second will rebase, but the checklist currently says Depends on: none. Please coordinate the shared-file order with the affected authors, record the agreed prerequisite(s) in Depends on, and rebase/resolve the overlap before merge. This request is for the repository's required overlap coordination, not a claim that the new functions duplicate another PR.

@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
PR 6565 docs/dev/debt/libc.md L136
PR 7386 core/libs/prx/libc/src/Formatting.cpp L121-124, L132, L138-140, L167, L178-233, L255-269, L276
PR 8593 core/libs/tests/GuestStdioVariadic.cpp L18
PR 8872 docs/dev/debt/libc.md L136
PR 8880 docs/dev/debt/libc.md L136
PR 9001 docs/dev/debt/libc.md L136
PR 9087 core/libs/prx/libc/src/Formatting.cpp L121-124, L138-140, L166-167, L230-234, L260-273
PR 9133 docs/dev/debt/libc.md L136
PR 9170 docs/dev/debt/libc.md L136

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 3965 docs/dev/debt/libc.md
PR 5021 docs/dev/debt/libc.md
PR 5303 core/libs/prx/libc/src/FormattingWide.cpp
docs/dev/debt/libc.md
PR 6469 docs/dev/debt/libc.md
PR 6819 docs/dev/debt/libc.md
PR 6879 docs/dev/debt/libc.md
PR 7081 core/libs/prx/libc/src/FileAndHeap.cpp
docs/dev/debt/libc.md
PR 7201 core/libs/prx/libc/src/FileAndHeap.cpp
PR 7447 core/libs/prx/libc/src/FileAndHeap.cpp
docs/dev/debt/libc.md
PR 7648 docs/dev/debt/libc.md
PR 7715 docs/dev/debt/libc.md
PR 8150 docs/dev/debt/libc.md
PR 8965 docs/dev/debt/libc.md
PR 9408 docs/dev/debt/libc.md

@harisibrar1 harisibrar1 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Checked against main 4a17c71, C11 n1570 K.3.5.2.2 and the ps5-payload-dev/sdk libSceLibcInternal stubs:

  • All twelve exports are in the sdk stub list, and the NIDs match. The SysV varargs entry points use __builtin_sysv_va_* and APS5_VA_BEGIN on Windows, as fprintf and scanf do. I traced the new fscanf_s stream cases by hand and the expected values hold.
  • freopen_s (FileAndHeap.cpp:398) returns EINVAL for a null filename. But K.3.5.2.2 lists only newstreamptr, mode and stream as runtime constraints, and says that with a null filename the call "attempts to change the mode of the stream". So freopen_s(&r, NULL, "rb", f) should not fail as a constraint violation.
  • Plain freopen on main already reports that case as unsupported (errno 45, FileAndHeap.cpp:312). Could freopen_s pass the null name through to freopen, so that the existing gap shows instead of a constraint failure? The test at GuestFopenErrors.cpp:105 and the debt line (libc.md:139, "follows C11 K.3.5.2.2") would need the same change. If the console's Dinkumware does reject a null name, a citation in the debt entry would settle it.

Reviewed with AI assistance (Claude Code); the checks above were run locally on my machine.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants