Skip to content

feat(vm): File::Stat/File/Dir — real-FS metadata, link ops, and Dir instances - #346

Merged
tannevaled merged 4 commits into
mainfrom
feat/file-stat-dir
Aug 10, 2026
Merged

feat(vm): File::Stat/File/Dir — real-FS metadata, link ops, and Dir instances#346
tannevaled merged 4 commits into
mainfrom
feat/file-stat-dir

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

Horodate: 2026-08-10 09:55 CEST

Advances real-filesystem File::Stat / File / Dir support toward MRI 4.0.5. All behaviour cross-checked against ruby 4.0.5 and through rbgo itself. Pure-Go (CGO=0), GOWORK=off, go 1.26.4.

What's implemented

File::Stat accessor surface (new)

setuid? setgid? sticky? world_readable? grpowned? readable_real? writable_real? executable_real? rdev blocks dev_major dev_minor rdev_major rdev_minor birthtime (raises NotImplementedError, as MRI on unsupported platforms/filesystems).

statFields now carries rdev/blocks (from syscall.Stat_t on unix; 0 on windows/wasm). Access checks were refactored around a shared permFor, so the effective-id (accessible) and real-id (accessibleReal) predicates share one owner/group/other decision. Device numbers decompose with the glibc gnu_dev_major/minor encoding.

File.* class methods (new)

Predicates delegating to a following stat: pipe? socket? blockdev? chardev? setuid? setgid? sticky? owned? grpowned? world_readable? world_writable? identical? readable_real? writable_real? executable_real?; metadata atime ctime birthtime; and filesystem ops link symlink readlink truncate realdirpath path — each with MRI-faithful arity/type checks and error classes (Errno::EEXIST/EINVAL/ENOENT, SystemCallError).

Dir instances (new)

rbgo's Dir had only class methods; this adds the DirObj value with Dir.new / Dir.open and the full instance surface: read pos tell (alias of pos) seek pos= rewind path to_path (alias path/to_path) fileno close each each_child. Semantics match MRI: a closed handle raises IOError on every read-side method (path/to_path keep working); fileno raises NotImplementedError (no dirfd in rbgo); Dir.open with a block closes the handle on exit — even on a raised exception — and returns the block value; a missing path raises a SystemCallError; blockless each/each_child return an Enumerator.

Deferred (noted precisely)

  • File.fnmatch? + File::FNM_* flags, and Dir recursive/brace glob (** / {a,b} / char-class + FNM flags) — the **/PATHNAME/./ edge semantics are intricate; left for a focused follow-up rather than risk a partial matcher.
  • Full File::Stat#inspect and a real #birthtime (platform-specific: real on darwin/BSD, statx-gated on Linux).
  • File.lchmod (unsupported on Linux; MRI raises NotImplementedError there).

Verification

  • go test ./... exit 0 (no regressions).
  • CI-exact whole-package coverage gate shows only the pre-existing tolerated partials (cmpFloat, rangeSize, newFiber, fiberResume, ioGetsParagraph, registerIOClassMethods, registerNetHTTP, classOf, ivarTable, registerSleep) — every function added or touched is 100%, including error branches.
  • gofmt + go vet ./internal/vm/ clean.
  • ruby/spec ratchet: 12583 passing (from 12382 at this branch's base; floor was 12352), exit 0, via a freshly built binary. FLOOR bumped 12352 → 12553 (measured − 30).
  • No prelude edits (no re-freeze needed); no shadowed defs introduced.

🤖 Generated with Claude Code

tannevaled and others added 4 commits August 10, 2026 09:35
…ta surface

Horodate: 2026-08-10 09:40 CEST

Extends the File::Stat accessor surface and the File.* class-method
predicates toward MRI 4.0.5:

File::Stat instance methods (new):
  setuid? setgid? sticky? world_readable? grpowned?
  readable_real? writable_real? executable_real?
  rdev blocks dev_major dev_minor rdev_major rdev_minor
  birthtime (NotImplementedError, as MRI on unsupported platforms/FS)

File.* class methods (new):
  pipe? socket? blockdev? chardev? setuid? setgid? sticky?
  owned? grpowned? world_readable? world_writable? identical?
  atime ctime birthtime readable_real? writable_real? executable_real?

statFields now carries rdev/blocks (from syscall.Stat_t on unix; 0 on
windows/wasm). Access checks refactored around a shared permFor so the
effective-id (accessible) and real-id (accessibleReal) predicates share
one owner/group/other decision. Device numbers decomposed with the glibc
gnu_dev_major/minor encoding.

Deferred (not in this commit): File.link/symlink/readlink/realdirpath/
truncate/fnmatch?, Dir recursive glob + Dir instances, full File::Stat#inspect
and real birthtime (platform-specific).

Verification (darwin, go 1.26.4, GOWORK=off, CGO=0):
  - behaviour cross-checked against ruby 4.0.5
  - go test ./... exit 0
  - CI-exact whole-package coverage gate: only the pre-existing tolerated
    partials remain (new code 100%, incl. error branches)
  - gofmt + go vet clean

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Horodate: 2026-08-10 09:47 CEST

File.* class methods (new): link, symlink, readlink, truncate,
realdirpath, path — each with MRI-faithful arity/type checks and error
classes (Errno::EEXIST/EINVAL/ENOENT), verified against ruby 4.0.5.

Dir instances (new): rbgo's Dir had only class methods; this adds the
DirObj value and Dir.new / Dir.open plus the instance surface —
read, pos, tell (alias of pos), seek, pos=, rewind, path, to_path
(alias path/to_path), fileno, close, each, each_child. Semantics match
MRI: a closed handle raises IOError on every read-side method (path/
to_path keep working); fileno raises NotImplementedError (no dirfd);
Dir.open with a block closes the handle on exit, even on a raised
exception, and returns the block value; a missing path raises a
SystemCallError. Blockless each/each_child return an Enumerator.

Deferred: File.fnmatch? + File::FNM_* flags, Dir recursive/brace glob
(** / {a,b} / char-class flags), File.chmod/lchmod symlink variants,
full File::Stat#inspect and real birthtime (platform-specific).

Verification (darwin, go 1.26.4, GOWORK=off, CGO=0):
  - behaviour cross-checked against ruby 4.0.5 and through rbgo itself
  - go test ./... exit 0
  - CI-exact whole-package coverage gate: only the pre-existing tolerated
    partials remain (new code 100%, incl. error branches)
  - gofmt + go vet clean

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Horodate: 2026-08-10 09:54 CEST

The File::Stat / File / Dir work lifts the measured ruby/spec total to
12583 passing (from 12382 at this branch's base). Lock in the win at the
measured total minus the usual 30-example margin.

Measured: passing examples 12583 (files loaded 2104, failed to load 102),
ratchet OK, exit 0 — via a freshly built binary.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts:
#	scripts/conformance/rubyspec/FLOOR
@tannevaled
tannevaled merged commit 7263397 into main Aug 10, 2026
5 of 8 checks passed
@tannevaled
tannevaled deleted the feat/file-stat-dir branch August 10, 2026 08:29
tannevaled added a commit that referenced this pull request Aug 10, 2026
2026-08-10 11:40 CEST

Implements the File/Dir pattern-matching items deferred by the File/Dir PR
(#346), with a self-contained matcher (Go's path/filepath.Match supports
neither '**', '{}', nor the FNM_* flags).

What / deferred items now implemented:
- File.fnmatch? / File.fnmatch (pattern, path, flags=0) and the File::FNM_*
  constants (NOESCAPE=1, PATHNAME=2, DOTMATCH=4, CASEFOLD=8, EXTGLOB=16,
  SYSCASE=0). Full matcher: '*' (no '/' under PATHNAME), '**' (recursive
  '**/' segment under PATHNAME, else '*'), '?', '[set]' with ranges +
  negation + ']'-closes-empty-class semantics, '{a,b}' under EXTGLOB
  (nested/empty alts), '\'-escapes unless NOESCAPE, case fold, and the
  leading-'.' rule unless DOTMATCH.
- Dir.glob / Dir.[] over the real filesystem: String or Array patterns,
  positional flags plus base:/sort:/flags: keywords, block form (yields,
  returns nil), '{a,b}' brace expansion, '**'/'**/' recursion (zero dirs,
  hidden dirs skipped without DOTMATCH), '?', '[set]', the "." synthetic
  entry MRI emits, trailing-'/' directory-only matches, and dedup+sort.

Verification:
- fnmatch matcher truth table (70+ rows) and Dir.glob over a known tree
  captured from ruby 4.0.5 (File.fnmatch? / Dir.glob); rbgo matches byte
  for byte.
- New code at 100% coverage incl. error branches (CI-exact
  -coverpkg gate: only the pre-existing tolerated partials remain below
  100%).
- go test ./... green; gofmt + go vet clean.
- ruby/spec ratchet: 12698 passing; FLOOR bumped 12553 -> 12668.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
tannevaled added a commit that referenced this pull request Aug 10, 2026
…s) (#348)

* feat(vm): File.fnmatch? + Dir.glob full patterns (WIP)

2026-08-10 11:05 CEST

WIP: self-contained fnmatch matcher (*, **, ?, [set], {alt}, FNM_* flags)
and Dir.glob/Dir.[] engine over the real filesystem. Verified against
ruby 4.0.5 for fnmatch (70+ cases) and glob over a known tree. Tests next.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(vm): File.fnmatch? + Dir.glob full patterns (**, {}, [set], flags)

2026-08-10 11:40 CEST

Implements the File/Dir pattern-matching items deferred by the File/Dir PR
(#346), with a self-contained matcher (Go's path/filepath.Match supports
neither '**', '{}', nor the FNM_* flags).

What / deferred items now implemented:
- File.fnmatch? / File.fnmatch (pattern, path, flags=0) and the File::FNM_*
  constants (NOESCAPE=1, PATHNAME=2, DOTMATCH=4, CASEFOLD=8, EXTGLOB=16,
  SYSCASE=0). Full matcher: '*' (no '/' under PATHNAME), '**' (recursive
  '**/' segment under PATHNAME, else '*'), '?', '[set]' with ranges +
  negation + ']'-closes-empty-class semantics, '{a,b}' under EXTGLOB
  (nested/empty alts), '\'-escapes unless NOESCAPE, case fold, and the
  leading-'.' rule unless DOTMATCH.
- Dir.glob / Dir.[] over the real filesystem: String or Array patterns,
  positional flags plus base:/sort:/flags: keywords, block form (yields,
  returns nil), '{a,b}' brace expansion, '**'/'**/' recursion (zero dirs,
  hidden dirs skipped without DOTMATCH), '?', '[set]', the "." synthetic
  entry MRI emits, trailing-'/' directory-only matches, and dedup+sort.

Verification:
- fnmatch matcher truth table (70+ rows) and Dir.glob over a known tree
  captured from ruby 4.0.5 (File.fnmatch? / Dir.glob); rbgo matches byte
  for byte.
- New code at 100% coverage incl. error branches (CI-exact
  -coverpkg gate: only the pre-existing tolerated partials remain below
  100%).
- go test ./... green; gofmt + go vet clean.
- ruby/spec ratchet: 12698 passing; FLOOR bumped 12553 -> 12668.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(conformance): bump ruby/spec FLOOR to 12776

2026-08-10 11:55 CEST

After merging origin/main (IO descriptors work) the 16-way ruby/spec run
reports 12806 passing on the merged tree; lock in N-30 = 12776 (was 12668
on this branch, 12553 on main). Verified in the foreground on the merged
tree: ratchet OK, CI-exact -coverpkg gate clean (only pre-existing
tolerated partials), go test ./... green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* conformance: set FLOOR 12650 (conservative) for fnmatch/glob — FS/case-sensitivity platform margin

**Horodate : 2026-08-10 11:16 CEST**

Dir.glob/File.fnmatch? specs are filesystem- and case-sensitivity-sensitive;
this machine's ratchet was measured on macOS (case-insensitive FS by default),
and the org CI runner is currently evicting every ratchet run (SIGTERM), so a
clean Linux number is unavailable. Setting FLOOR to 12650 (measured 12806, ~156
cushion) keeps main's ratchet lane robust across platforms rather than the tight
12776 (30 buffer). Per-PR full `go test ./...` + independent verification remain
the primary regression guard; the ratchet FLOOR is a secondary backstop.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant