Skip to content

ext4: expose inode metadata via FileInfo.Sys()#393

Merged
deitch merged 1 commit into
diskfs:masterfrom
luthermonson:ext4-expose-inode-info
May 14, 2026
Merged

ext4: expose inode metadata via FileInfo.Sys()#393
deitch merged 1 commit into
diskfs:masterfrom
luthermonson:ext4-expose-inode-info

Conversation

@luthermonson
Copy link
Copy Markdown
Contributor

@luthermonson luthermonson commented May 14, 2026

Closes #301.

What this does

filesystem/ext4.FileSystem.Stat() returns an fs.FileInfo whose Sys() previously exposed only UID/GID (and, as of recent upstream changes, Major/Minor/Ino/Nlink). The on-disk ext4 inode carries much more — block count, access/change/create timestamps, symlink target, and a flag bitmap — and this PR surfaces all of it through the existing Sys() escape hatch, matching the idiomatic Go pattern used by os.Stat returning *syscall.Stat_t.

Changes are additive — the existing fields on ext4.StatT are unchanged, and FileSystem.Stat's signature is unchanged. No change to the filesystem.FileSystem interface.

StatT now carries

Field Source Added in
UID, GID inode.owner / inode.group (already there)
Major, Minor inode.deviceNumber() (already there)
Ino inode.number (already there)
Nlink inode.hardLinks (already there)
Blocks uint64 inode.blocks this PR
AccessTime time.Time inode.accessTime this PR
ChangeTime time.Time inode.changeTime this PR
CreateTime time.Time inode.createTime this PR
LinkTarget string inode.linkTarget (symlinks) this PR
Flags InodeFlags curated subset of inodeFlags this PR

A new exported InodeFlags struct exposes the practically useful flag bits — Immutable, AppendOnly, NoDump, NoAtime, Synchronous, Compressed, Encrypted, HashedIndexes, JournalData, HugeFile, Extents, ExtendedAttributes, InlineData, TopDirectory. The internal inodeFlags struct stays unexported.

The existing (i *inode).stat() helper is extended to populate the new fields, so all three FileInfo construction sites (FileSystem.Stat, File.Stat, directoryEntryInfo.Info) continue to use a single source of truth.

Usage

fi, _ := fs.Stat("/some/path")
if st, ok := fi.Sys().(*ext4.StatT); ok {
    fmt.Println(st.Ino, st.Nlink, st.CreateTime, st.Flags.Immutable)
    if st.LinkTarget != "" {
        fmt.Println("symlink ->", st.LinkTarget)
    }
}

Tests

New TestStatSysInodeMetadata exercises /random.dat, /foo, /, and /symlink.dat from the existing test image — asserts non-zero inode, plausible timestamps, stable values across repeated Stat calls, and non-empty LinkTarget for symlinks.


Follow-up PRs

If this lands, three sibling PRs apply the same Sys()-based pattern to the other read-supporting filesystems. Each is independent and reviewable on its own.

iso9660 — #394 (draft)

directoryEntry.Sys() currently returns *RockRidgeInfo when Rock Ridge is present, otherwise nil. The follow-up replaces RockRidgeInfo with a richer iso9660.StatT always returned by Sys(), carrying ISO9660 fields (Location, VolumeSequence, IsHidden, IsAssociated, ExtAttrSize, HasExtendedAttrs, HasOwnerGroupPermissions) plus Rock Ridge fields parsed from PX and SL records (UID, GID, NLink, Inode, LinkTarget, RockRidge bool). RR fields stay zero-valued when no extensions are present. This is a breaking change for any caller currently asserting *RockRidgeInfo; the in-repo test assertions are updated as part of the PR.

squashfs — #395 (draft)

squashfs is the outlier: Sys() already returns *directoryEntry (aliased as the public squashfs.FileStat), which already exposes UID(), GID(), Xattrs(), Readlink() as accessor methods. To stay consistent with that existing public surface, the follow-up adds two more accessors — Inode() uint32 and InodeType() string — rather than introducing a parallel StatT struct. No breaking change to the existing FileStat type.

fat (fat12 / fat16 / fat32) — #396 (draft)

The architectural picture

The recent FAT12/16 support PR (#358) restructured the FAT codebase: the actual implementation now lives in filesystem/fat12, and filesystem/fat32 + filesystem/fat16 are thin wrappers that embed *fat12.FileSystem and override Type(). Public API parity with the old fat32 surface is preserved through Go type aliases — for example, type SectorSize = fat12.SectorSize already exists in fat32.go. A type alias (A = B) is identity, not a new named type, so fat32.SectorSize and fat12.SectorSize are literally the same type.

The follow-up applies that same pattern for the new StatT. The implementation defines fat12.StatT once and a (*directoryEntry).stat() helper that populates it from parseDirEntries data already on the entry. Then:

// filesystem/fat12/fileinfo.go — single source of truth
type StatT struct {
    ReadOnly, Hidden, System, Archive, VolumeLabel bool
    CreateTime, AccessTime                          time.Time
    Cluster                                         uint32
}

// filesystem/fat32/fat32.go
type StatT = fat12.StatT

// filesystem/fat16/fat16.go
type StatT = fat12.StatT

Why this is the right shape (not three separate copies)

  1. One implementation, three packages covered. directoryEntry.stat() is defined once. fat16 and fat32 inherit working Sys() automatically because they reuse fat12's directoryEntry.Info() and File.Stat() code paths.

  2. Same type across packages, by design. Because type StatT = fat12.StatT is an alias (not a new named type with the same field shape), a value of *fat32.StatT is a *fat12.StatT. A downstream library that wants to handle any FAT variant can type-assert against *fat12.StatT once and have it match all three — same way *syscall.Stat_t matches any UNIX filesystem reachable through os.Stat. Callers using a specific FAT package still write the natural import (fi.Sys().(*fat32.StatT) works for fat32 users) without forcing them to learn about the fat12 base package.

  3. No drift risk. If FAT16 later needs a new attribute exposed, it goes in fat12.StatT and the other two get it for free. The alternative (three separate StatT structs with the same field shape) would inevitably diverge.

  4. Mirrors the upstream architectural choice already made. This isn't introducing a new pattern; fat16/fat32 are already wrappers around fat12.FileSystem and their public types are already aliases. Adding StatT as an alias is consistent with how SectorSize works today — anything else would create two competing conventions in the same package.

Fields

All populated from data parseDirEntries already extracts; no new parsing. Attribute bits (ReadOnly, Hidden, System, Archive, VolumeLabel), the two FAT timestamps that don't map to ModTime() (CreateTime, AccessTime), and the file's starting Cluster.

Expand ext4.StatT (returned by FileInfo.Sys()) with the inode number,
hardlink count, block count, access/change/create times, link target,
and a curated InodeFlags struct exposing the practically useful inode
flag bits. Populate StatT at all three FileInfo construction sites
(FileSystem.Stat, File.Stat, and directoryEntryInfo.Info).

The on-disk inode already carries this information; previously only
UID and GID were exposed.
@deitch
Copy link
Copy Markdown
Collaborator

deitch commented May 14, 2026

This is good. I don't like the PR comment much. I know it is AI generated, and just don't care. It is good and detailed. But it references the follow-on PRs too much, and describes the logic of those in this comment. Not going to hold it up for that, though.

What agent are you using and with which skills, such that it not only creates the comments, but also includes links to PRs?

@deitch deitch merged commit 116635b into diskfs:master May 14, 2026
20 checks passed
@luthermonson
Copy link
Copy Markdown
Contributor Author

This is good. I don't like the PR comment much. I know it is AI generated, and just don't care. It is good and detailed. But it references the follow-on PRs too much, and describes the logic of those in this comment. Not going to hold it up for that, though.

What agent are you using and with which skills, such that it not only creates the comments, but also includes links to PRs?

claude opus 4.7

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.

[Feature request] expose filesystem inode information

2 participants