Skip to content

libkernel: lseek on a directory descriptor does nothing on Windows, so a directory cannot be listed again after rewinding it #3102

Closed

Description

@husseinkad

What

On Windows a guest directory descriptor sits on the NUL device, so sceKernelLseek on it returns 0 and does nothing: after lseek(fd, 0, SEEK_SET) a second getdents returns 0 entries instead of listing the directory again. FreeBSD's getdirentries(2) documents that the position "may be set and retrieved by lseek(2)", and offset 0 is the standard rewind (it is what rewinddir does). A title that walks a directory twice with one descriptor sees an empty second listing. Linux hosts use the real lseek and are fine.

Reproduce

Windows 11, WinLibs GCC 15.2.0 r7, main at the time of writing: open a directory with a few files, read it with sceKernelGetdents until it returns 0, call sceKernelLseek(fd, 0, SEEK_SET), call sceKernelGetdents again: 0 bytes (FreeBSD: the full listing again).

Fix

A tested fix is on my fork: husseinkad:fix/kernel-windows-directory-rewind (three commits on main; PR #2680, closed by the intake check): File::SeekDirectoryDescriptor handles lseek on directory descriptors. Offset 0 with SEEK_SET rewinds and the next read lists the live directory again; SEEK_CUR returns the entry index (offset 0 is a pure query that does not touch the snapshot); other SEEK_SET/SEEK_CUR offsets are entry indexes; a negative result throws like any other lseek failure (EINVAL) and an overflow reports EOVERFLOW; SEEK_END throws via NotImplemented_nid_no_patch (FreeBSD defines it relative to the directory size, which is not modelled; it used to return the NUL descriptor's 0). Per-descriptor state has its own lock, so a slow listing does not block lseek on other descriptors, and lseek/close on non-directory descriptors return before taking any lock while no directory is open. The limits (re-listing costs O(entries) per rewind, getdirentries still reporting 0 through basep) are in TechnicalDebt. Open PR #2144 also rewrites sceKernelLseek in Open.cpp; the directory hook there would need to follow whichever style lands first.

guest_directory_rewind covers rewind after a partial and a full read, a file added between listings, SEEK_CUR exactly 2 after . and .., a seek past the end, negative and overflowing seeks, SEEK_CUR 0 not disturbing a listing, rewind after getdents failed on a too-small buffer, and a reused descriptor behaving as a file; it fails without the change. Probes with 0, 1, 5 and 10,000 entries relisted correctly, and a 3-thread, 3-second stress of getdents + directory lseek + regular-file lseek (about 12.6 million operations) had no errors. Linux not run.

AI-assisted: yes.

Activity

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