Skip to content

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

Merged
adriadam10 merged 7 commits into
boykopovar:mainfrom
AlxcBTW:pr/libc-annex-k-stream-io
Oct 26, 2026
Merged

adriadam10 merged 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

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Progress report (5d48c62 → e0932f8)

📈 System libraries: 49.96% (+0.04%, +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

Changed libraries (implemented exports)

Library Before After Change
libc 1346/1356 (99.26%) 1357/1368 (99.20%) -0.07 pp

@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 L183
PR 9087 core/libs/prx/libc/src/Formatting.cpp L128-131, L145-147, L173-174, L237-241, L267-280
PR 10158 docs/dev/debt/libc.md L183
PR 10200 docs/dev/debt/libc.md L183
PR 10222 docs/dev/debt/libc.md L183
PR 10229 docs/dev/debt/libc.md L183
PR 10283 docs/dev/debt/libc.md L183
PR 10346 docs/dev/debt/libc.md L183
PR 10349 docs/dev/debt/libc.md L183
PR 10989 docs/dev/debt/libc.md L183

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/libc.md
PR 5303 core/libs/prx/libc/src/FormattingWide.cpp
docs/dev/debt/libc.md
PR 6713 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 7386 core/libs/prx/libc/src/Formatting.cpp
core/libs/tests/GuestStdioVariadic.cpp
docs/dev/debt/libc.md
PR 7447 core/libs/prx/libc/src/FileAndHeap.cpp
docs/dev/debt/libc.md
PR 7544 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 8417 docs/dev/debt/libc.md
PR 9269 docs/dev/debt/libc.md
PR 9371 docs/dev/debt/libc.md
PR 9669 docs/dev/debt/libc.md
PR 9771 docs/dev/debt/libc.md
PR 9863 docs/dev/debt/libc.md
PR 9873 docs/dev/debt/libc.md
PR 9929 docs/dev/debt/libc.md
PR 10007 docs/dev/debt/libc.md
PR 10113 docs/dev/debt/libc.md
PR 10226 docs/dev/debt/libc.md
PR 10405 core/libs/prx/libc/src/FileAndHeap.cpp
docs/dev/debt/libc.md
PR 10467 docs/dev/debt/libc.md
PR 10610 docs/dev/debt/libc.md
PR 10742 core/libs/prx/libc/include/Api.hpp
docs/dev/debt/libc.md
PR 10788 core/libs/prx/libc/include/Api.hpp
docs/dev/debt/libc.md
PR 10898 docs/dev/debt/libc.md

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

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.

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

I rechecked current head 239dc9bf3e: the merge with today’s main is clean, diff-check/conventions pass, and the reported Linux focused suite is 12/12. This branch is 7 ahead / 119 behind current main (merge base a6dcea927), so it still needs a current-main refresh and rerun.

There is also a concrete freopen_s mismatch in the new code and test. The implementation rejects filename == nullptr with EINVAL (FileAndHeap.cpp), and GuestFopenErrors.cpp asserts that result. C11 N1570 K.3.5.2.2 lists only newstreamptr, mode, and stream as runtime constraints; it describes a null filename as a request to attempt a mode change on the existing stream, with the permitted mode changes implementation-defined. See N1570 K.3.5.2.2 (PDF pp. 607–608). Please pass a non-constraint null filename through to freopen and test/report the underlying supported or unsupported mode-change result, unless you have PS5-specific evidence that this export deliberately diverges; if so, document that source and narrow the C11 claim.

The earlier overlap request also remains open: #9087 edits the same scanner/secure-format paths, and the overlap report names shared test/debt insertions with other open PRs. Please record the agreed order and any required Depends on entries before merge. I did not run a local build in this pass.

@MarcZX10

Copy link
Copy Markdown
Contributor

A fresh comparison also finds #9669 sharing the same docs/dev/debt/libc.md insertion point: the pairwise merge of #9527 and #9669 currently conflicts in that file. Since #9526’s author is also the author of this PR, please coordinate an order across #9526, #9527 and #9669, record the relevant Depends on links in each PR that must follow another, and preserve all entries in the combined debt file.

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 22, 2026
@ycbuzi

ycbuzi commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

Windows run of 239dc9b merged onto main a6646f8 (124686139e), the platform the description lists as not run. Windows 11 Home 10.0.26200, MinGW-w64 GCC 15.2.0 (ucrt-posix-seh r7), Release, BUILD_TESTING=ON.

The merge conflicts only in docs/dev/debt/libc.md, as GitHub shows; for this run I took this PR's side of that file. Built libc and guest_stdio_variadic_tests with no errors or warnings. ctest --repeat until-fail:2 on guest_stdio_variadic: passes on both repeats. guest_fopen_errors is registered only under if(UNIX) in core/libs/prx/libc/CMakeLists.txt, so it is not built on Windows. Not run: guest_fopen_errors (Unix only), a title.

danielradosa added a commit to danielradosa/AnyPS5 that referenced this pull request Oct 24, 2026
…, scanf and freopen_s functions

Took the PR's ScanSource/StreamSource scanner and all twelve functions; kept our HostFscanf (dropped the PR's duplicate) and ScanWideFile, restored RejectGuestWideScan for the Linux stream _s forms since our fscanf no longer throws for wide conversions, and reused the test's existing count variable.
@Retr0G28

Copy link
Copy Markdown
Contributor

@AlxcBTW Hi, #10620 (mine) lets freopen reopen a stream onto /dev/random or /dev/urandom. Your freopen_s calls freopen, so it picks that up with no changes, and the two PRs don't change the same lines. The conflicts in GuestStdioVariadic.cpp and at the end of libc.md are between this PR and current main (git merge-tree --write-tree origin/main on this head shows them without #10620). Neither PR needs the other first, so I've left Depends on: none on mine. If you prefer an order, tell me and I'll record it.

@adriadam10 adriadam10 self-assigned this Oct 25, 2026
@adriadam10
adriadam10 force-pushed the pr/libc-annex-k-stream-io branch from 239dc9b to 3c50123 Compare October 25, 2026 15:38
@github-actions github-actions Bot added needs rebase Conflicts with main, rebase needed and removed needs rebase Conflicts with main, rebase needed labels Oct 25, 2026
@adriadam10
adriadam10 force-pushed the pr/libc-annex-k-stream-io branch from 3c50123 to e0932f8 Compare October 25, 2026 18:09
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 25, 2026
@adriadam10
adriadam10 dismissed MarcZX10’s stale review October 25, 2026 18:58

Dismissing a stale review: the requested changes were addressed after it (new head) and the merge queue blocks on it. Please request changes again if anything is still open.

@adriadam10
adriadam10 merged commit 97d3346 into boykopovar:main Oct 26, 2026
17 checks passed
@adriadam10

Copy link
Copy Markdown
Collaborator

Rebased onto current main; the only conflict was docs/dev/debt/libc.md (kept the new main entry and this PR's three). Nothing else changed since the last review; 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.

6 participants