Skip to content

libc, libkernel: successful path calls change errno on Linux and Windows (open creating a file, mkdir, rename, link, chdir, rmdir and others); fix branch ready #5527

Closed as not planned

Description

@Sevthered

A path call that succeeds leaves errno changed on both hosts. FreeBSD's system call stubs store errno only on failure, so a title that clears errno, calls mkdir and tests errno afterwards takes a successful call for a failed one.

Measured on main 665ad7da7 with a probe that sets errno to 12345 before each of 51 calls on paths and directory descriptors and reads it after a successful return. Three runs gave the same result.

Call Linux (Ubuntu 24.04, GCC 13.3, glibc 2.39) Windows 11 (WinLibs GCC 15.2.0 r7)
first path call of the process 22 unchanged
open, sceKernelOpen, creat creating a file 2 0
open of a directory unchanged 13
mkdir 2 0
sceKernelMkdir 2 2
fopen with "w" for a new file 2 0
opendir, closedir unchanged 0
rename, sceKernelRename, renameat 2 2
link 2 2
symlink 2 fails without the privilege
chdir, fchdir 22 unchanged
rmdir, sceKernelRmdir 21 0
unlinkat with AT_REMOVEDIR unchanged 0

That is 18 of the 51 calls on Linux and 17 on Windows. The rest (access, stat, lstat, unlink, remove, truncate, chmod, utimes, readlink, realpath, getcwd, pathconf, openat, mkdirat, fchmodat and the sceKernel forms of these) leave it alone.

Causes:

  • The first path resolution builds the working directory state with std::filesystem::canonical, and glibc's realpath leaves errno at EINVAL when it succeeds. chdir and fchdir make the same call every time.
  • The path resolver asks the host about the path it resolves (ResolveHostPath in core/libs/prx/libc/src/General.cpp), so resolving a path that does not exist yet leaves ENOENT, and every function that creates something inherits it.
  • sceKernelMkdir, link, symlink, rename and rmdir make failing queries of their own before they act.
  • On Windows the CRT calls behind open, mkdir, opendir and rmdir reset errno to 0, and opening a directory fails with EACCES first, before the directory descriptor is opened.

statvfs already saves and restores errno around its work (core/libs/prx/libSceLibcInternal/PathStatistics.cpp), and guest_statvfs checks it.

A branch is ready that does the same in the resolver, the working directory code and the functions above (7 files, 38 lines) with a test, guest_path_errno, that fails on main on both hosts. With it the probe reports 0 of 51 on both hosts. The full suites pass with it on both hosts, apart from the two tests that also fail on main in my WSL (agc_driver_depth_compare_copy, guest_socket_read_write). I can open the pull request once my open one (#5440) is merged; a first-time contributor may have one open.

Question: is restoring errno in each function, as statvfs does, the form you want, or would you prefer one shared guard?

AI-assisted: yes (Claude Code); the measurements are from my machine.

Activity

  1. MarcZX10 commented on Oct 13, 2026

    @MarcZX10
    Contributor

    For the API contract, I would preserve the caller’s errno on successful returns and keep the real error from a failed call. A shared success-aware guard/helper can make that consistent, but a guard that restores unconditionally (especially in the shared path resolver) could erase ENOENT or another error that the caller needs to report. I would test both sides: seed a sentinel errno and verify success leaves it untouched, then exercise representative failures and verify they still set the expected errno. Your per-function statvfs-style save/restore is also reasonable if each function only restores on success. I have not started a parallel implementation since your note says you plan to open your ready branch after #5440 merges.

  2. Sevthered commented on Oct 13, 2026

    @Sevthered
    ContributorAuthor

    Thanks, that matches what the branch does. Each function saves errno on entry and puts it back only right before its successful return, so every failure path keeps the error it set. The shared resolver reports failure by throwing and the callers turn that into errno, so nothing gets restored over an error there either. The test seeds a sentinel and checks both directions. After your note I added failure cases where the error comes out of the resolver itself (a .. past a regular file gives ENOTDIR, past a missing directory ENOENT) for open, mkdir, rename, chdir and fopen. They pass on Windows and Linux, and the mkdir one fails if I make that function restore unconditionally. Still planning to open it once #5440 is in. If the maintainers would rather have one shared success-aware helper I can switch to that.

  3. MarcZX10 commented on Oct 13, 2026

    @MarcZX10
    Contributor

    That matches the contract I had in mind: preserve incoming errno only on successful returns, and leave the new error intact on each failure path, including failures translated from the resolver. The added resolver-failure cases cover the main risk. I don’t think a shared helper is required if the per-function guards remain success-only and clear.

  4. MarcZX10 commented on Oct 14, 2026

    @MarcZX10
    Contributor

    Small status update: #5440 closed without merging, so the wait condition you mentioned has changed. Are you still planning to submit the ready errno-preservation branch as a PR, or has that work been dropped? I have not started a parallel implementation and will avoid duplicating it while you decide.

  5. oneandonlydean commented on Oct 14, 2026

    @oneandonlydean
    Collaborator

    Closing: no title is known to reach this, and functions are only changed when a title needs them. If a title stops on or misbehaves because of it on current main, comment here with the title ID and the log line.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions