Skip to content

proposal: os: add Root.ReadDir #81526

Open

Description

@nnnkkk7

Proposal Details

I propose adding a ReadDir method to *os.Root:

package os

// ReadDir reads the named directory in the root,
// returning all its directory entries sorted by filename.
// See [ReadDir] for more details.
func (r *Root) ReadDir(name string) ([]DirEntry, error)

This is a convenience method, like Root.ReadFile and Root.WriteFile (#73126). I propose adding it because:

  1. os.ReadDir is a useful convenience function, and it is useful to have an equivalent that works directly with os.Root.
  2. The io/fs package includes both ReadDir and the ReadDirFS interface, suggesting that directory listing is core filesystem functionality.
  3. fs.ReadDir(r.FS(), name) uses io/fs path syntax, while methods on Root accept native os path syntax.

This method was considered during the original Root proposal and explicitly left as a possible follow-up (#67002 (comment)).

This gap has also appeared in practice: apko explicitly notes that *os.Root does not expose ReadDir and works around it using Root.Open followed by File.ReadDir.

Activity

  1. added this to the Proposal milestone on Sep 14, 2026
  2. ianlancetaylor commented on Sep 14, 2026

    @ianlancetaylor
    Contributor
  3. mpx commented on Sep 16, 2026

    @mpx
    Contributor

    I recently hit a usage bug with os.Root where os.Root.FS() was used with io/fs.ReadDir. Accessing files via an fs.FS enforces path naming restrictions (valid UTF-8) that don't apply on the underlying filesystems. This prevented the tooling from managing oddly named files under the root.

    Had os.Root.ReadDir existed it would have been used instead and avoided the bug.

    The other common convenience functions exist, I think ReadDir should be added too.

  4. neild commented on Sep 16, 2026

    @neild
    Contributor

    Why ReadDir and not also Readdir and Readdirnames?

  5. mpx commented on Sep 16, 2026

    @mpx
    Contributor

    I find ReadDir is the better choice out of the 3 options (faster, only pay for what you need) -- io/fs made the same choice with that API. os.Root is a new API too, I think it would similarly make sense to support ReadDir.

    I don't have a good reason to use Readdir or Readdirnames anymore - I wouldn't miss them if they weren't there. I'll let others advocate for them if desired, but the single API offers less confusion than all 3.

  6. apparentlymart commented on Sep 16, 2026

    @apparentlymart

    It seems like the logical conclusion of proposals like this is that os.Root eventually grows to have a method for every function in os which takes a filesystem path as an argument, with equivalent meaning except that the filesystem path is interpreted relative to the root and constrained to it instead of relative to cwd and unconstrained.

    Is there some specific rule dividing os functions which are echoed as os.Root methods from those that aren't? Ideally this would be a policy set just once and applied automatically to anything new added to package os in future, rather than arguing each function individually when these Root-based methods have essentially the same use-cases as the package-level functions. 🤔

  7. neild commented on Sep 16, 2026

    @neild
    Contributor

    IIRC, the initial set of os.Root methods was every filesystem operation in os that operates on a path and couldn't be emulated by some other mechanism.

    So Root.Chtimes exists because os.Chtimes exists. Root.Chmod exists because, while File.Chmod does exist, it requires that you have the ability to open a file before you can operate on it and isn't a perfect substitute. Root.Truncate does not exist, because you can get the same results with other operations.

    There isn't any rule about what else gets added. Perhaps we should have one. What os functions operating on files are left that don't have an os.Root equivalent? From a quick scan, I see MkdirTemp, ReadDir, TempDir, and Truncate. I might have overlooked some.

    I deliberately left Truncate out; see #67002 (comment). (tl;dr, I think the only reason Truncate exists is because Unix has a truncate(2) syscall, there is no equivalent path-scoped syscall, and you can easily truncate a file using other mechanisms.)

    Root.ReadDir seems pretty reasonable.

    I don't have much opinion on whether MkdirTemp and TempDir should exist on Root. Maybe, as the only remaining top-level functions that don't? And at that point, maybe we should add Root.Truncate for completeness?

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

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions