Skip to content

feat(libkernel): implement tcgetattr and tcsetattr - #1738

Merged
adriadam10 merged 2 commits into
boykopovar:mainfrom
SleglJan:feat/libkernel-termios
Oct 9, 2026
Merged

adriadam10 merged 2 commits into
boykopovar:mainfrom
SleglJan:feat/libkernel-termios

Conversation

@SleglJan

@SleglJan SleglJan commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

What

Adds tcgetattr and tcsetattr to libkernel, in a new File/src/Terminal.cpp, on the assumption that a title's descriptors are never terminals: a title runs without a controlling terminal, and what the console gives it as descriptors 0 to 2 is unknown (recorded in TechnicalDebt). The errors are the ones FreeBSD 11's termios.c and ioctl path give for a descriptor that is not a terminal:

  • A file or pipe descriptor is ENOTTY, and tcgetattr leaves the termios untouched, as TIOCGETA copies out only on success. A guest socket gets what its protocol's control routine returns to a termios ioctl, EADDRNOTAVAIL for AF_INET (in_control without an interface) and EOPNOTSUPP otherwise (in6_control, pru_control_notsupp).
  • tcsetattr gives EINVAL for an action other than TCSANOW, TCSADRAIN or TCSAFLUSH, with or without TCSASOFT, before anything else; EFAULT for a null termios, which sys_ioctl copies in before it looks at the descriptor. With TCSASOFT and a null termios FreeBSD's libc faults while copying it, so that case throws.
  • A descriptor is invalid (EBADF) when it is negative, a closed guest socket, or a host descriptor the host does not know: fcntl(F_GETFD) on Linux, _get_osfhandle on Windows under the thread-local invalid-parameter handler, as NativeClose in Open.cpp does.

Of the 34 relinked titles in the homebrew catalog (#1578), 16 import tcgetattr and tcsetattr and one stops at tcgetattr on main 4b6ab6b, PPSA99728 OpenRCT2 Native Port: relinked with main's relinker and run for 8 s in a bubblewrap sandbox on Linux, it dies in the host loader with app.elf: symbol lookup error: app.elf: undefined symbol: 5kBfZ40bAuk.

Overlap: #1613 adds isatty to libScePosixForWebKit through the host's isatty, which can say yes to a host terminal on descriptors 0 to 2 where this says no; the two should agree once either lands. #1645 and #1192 insert source lines next to Stdio.cpp in libkernel/CMakeLists.txt; this one goes after NativeStat.cpp, so none of the three overlap. #1727 and #1091 add declarations to SocketRuntime.hpp where this adds Family; whichever merges second rebases one line.

Tested

  • Linux (CachyOS, kernel 7.2.9, GCC 16.2.1), full build with BUILD_TESTING=ON and ANYPS5_ENABLE_SPIRV_TOOLS=ON on this branch (based on main 2b29784): ctest --repeat until-fail:3 468/468; tools/check_test_registration.py and tools/check_conventions.py --base upstream/main pass.
  • New guest_terminal test: ENOTTY with an untouched termios from tcgetattr, and from tcsetattr with every valid action, on a file opened through open, descriptors 0, 1 and 2 and both ends of a pipe; EADDRNOTAVAIL on an AF_INET socket with the termios untouched; EINVAL for actions 3, -1 and TCSASOFT | 3 even on an invalid descriptor; EFAULT for a null termios even on an invalid descriptor; EBADF for -1, a closed socket and a closed file. Written before the implementation; it failed on the two unresolved exports and passes with it.
  • The patched libkernel.prx exports the NIDs (5kBfZ40bAuk, La7xW-jcZwQ).
  • tools/import_audit.py on five of the 16 catalog titles, absent imports on main 4b005b2 → this branch: PPSA17641 SymphonyStation5 35 → 33, PPSA97510 PSXS5 72 → 70, PPSA99000 ProsperoStore 40 → 38, PPSA99009 ProsperoLichess 37 → 35, PPSA99039 EVO Player 24 → 22. None of them loads yet; the remaining imports are listed in Four open-source homebrew titles stop at start-up on unresolved imports (sysctl, pthread_set_name_np, isatty, mkstemp, signal sets, Opus decoders) #1578.
  • Windows: the same Build workflow run on this commit in the fork's Actions (merged onto main 6b56175), run 37822114975: guest_terminal passes its three repeats, full suite 474/474. The only host call there is _get_osfhandle under the invalid-parameter guard; the UCRT keeps descriptors 0 to 2 open in a child process, so the test's ENOTTY checks on them hold.

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
  • AI-assisted: yes (Claude Code)

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Oct 8, 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
#1772 __error_nid_postfix
#1860 __error_nid_postfix
#2030 core/libs/prx/libkernel/CMakeLists.txt L134
#2051 __error_nid_postfix
#2123 __error_nid_postfix

@hikazey

hikazey commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Windows run, since the description lists Linux only: Windows 11 Pro (26200), MinGW-w64 GCC 15.2.0 (WinLibs r7, the CI build), Release, configured like CI, Ryzen 9 5900X. This commit merged onto main b858fdc4; only docs/dev/TechnicalDebt.md needed an auto-merge.

build guest_terminal
this PR 5/5 passed
Windows HostOpen forced to true (closed host descriptors no longer EBADF), test kept 5/5 failed
full suite with this PR 478/478 passed

So the Windows-only branch, _get_osfhandle under the thread-local invalid-parameter handler, runs without tripping the CRT's invalid-parameter abort. It's also what the test's EBADF cases depend on: they fail as soon as that check is bypassed. Descriptors 0–2 under ctest report ENOTTY here too.

AI-assisted: yes (Claude Code).

@nauroth

nauroth commented Oct 9, 2026

Copy link
Copy Markdown

Second Linux run, on current main: Ubuntu 24.04, GCC 13.3.0, glibc 2.39, Release. The commit cherry-picks cleanly onto d70b899 and guest_terminal passes 3/3 with --repeat until-fail:3.

Since the description names the kernel routines without linking them, I checked the socket error codes against FreeBSD stable/11:

  • sys/kern/sys_socket.c, soo_ioctl: the default: case routes group 'i' to ifioctl, 'r' to rtioctl_fib, and everything else to pru_control with ifp = 0. TIOCGETA/TIOCSETA* are group 't', so they reach pru_control.
  • sys/netinet/in.c, in_control: the first statement is if (ifp == NULL) return (EADDRNOTAVAIL); → AF_INET gets 49.
  • sys/netinet6/in6.c, in6_control: after the two ifp-independent switches it does if (ifp == NULL) return (EOPNOTSUPP); → AF_INET6 gets 45, same as pru_control_notsupp for the other families.

The tcsetattr ordering also matches: EINVAL for a bad action comes from libc's termios.c before any ioctl, and EFAULT for a null termios comes from the copyin in sys_ioctl, which runs before fget, so it wins over EBADF. Nothing to change.

SleglJan and others added 2 commits October 9, 2026 23:17
A title's descriptors are taken as never being terminals: a file or
pipe gets ENOTTY with the termios untouched, a socket gets what its
protocol answers a termios ioctl on FreeBSD (EADDRNOTAVAIL for AF_INET,
EOPNOTSUPP otherwise), and a closed or invalid descriptor EBADF.
tcsetattr gives EINVAL for an action other than TCSANOW, TCSADRAIN or
TCSAFLUSH, with or without TCSASOFT, before anything else, and EFAULT
for a null termios before the descriptor is checked; with TCSASOFT a
null termios throws, where FreeBSD's libc faults copying it. What the
console gives a title as descriptors 0 to 2 is unknown, as recorded in
TechnicalDebt.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@adriadam10

Copy link
Copy Markdown
Collaborator

Checked against FreeBSD 11 (termios.c, vn_ioctl, in_control): error codes and validation order match. Rebased on current main (clean), full build + ctest 523/523, guest_terminal passes. Windows/Linux runs by @hikazey and @nauroth also cover it.

Added one commit:

  • docs/dev/TechnicalDebt.md: moved the tcgetattr, tcsetattr entry from the end of "Unknown function info" (detached from the list by a blank line) into the list, to avoid adjacency conflicts.

Merging once CI is green.

@adriadam10
adriadam10 force-pushed the feat/libkernel-termios branch from af948ec to 96418e0 Compare October 9, 2026 21:22
@adriadam10
adriadam10 merged commit 3d19932 into boykopovar:main Oct 9, 2026
8 checks passed
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown

Progress report (870ac04 → 96418e0)

📈 System libraries: 85.24% (+0.01%, +2 implemented functions)

✅ 2 implemented
Library Name
libkernel tcgetattr_nid_postfix
libkernel tcsetattr_nid_postfix

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