ext4: expose inode metadata via FileInfo.Sys()#393
Conversation
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.
7252caa to
921312b
Compare
|
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 |
Closes #301.
What this does
filesystem/ext4.FileSystem.Stat()returns anfs.FileInfowhoseSys()previously exposed onlyUID/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 existingSys()escape hatch, matching the idiomatic Go pattern used byos.Statreturning*syscall.Stat_t.Changes are additive — the existing fields on
ext4.StatTare unchanged, andFileSystem.Stat's signature is unchanged. No change to thefilesystem.FileSysteminterface.StatTnow carriesUID,GIDinode.owner/inode.groupMajor,Minorinode.deviceNumber()Inoinode.numberNlinkinode.hardLinksBlocks uint64inode.blocksAccessTime time.Timeinode.accessTimeChangeTime time.Timeinode.changeTimeCreateTime time.Timeinode.createTimeLinkTarget stringinode.linkTarget(symlinks)Flags InodeFlagsinodeFlagsA new exported
InodeFlagsstruct exposes the practically useful flag bits — Immutable, AppendOnly, NoDump, NoAtime, Synchronous, Compressed, Encrypted, HashedIndexes, JournalData, HugeFile, Extents, ExtendedAttributes, InlineData, TopDirectory. The internalinodeFlagsstruct stays unexported.The existing
(i *inode).stat()helper is extended to populate the new fields, so all threeFileInfoconstruction sites (FileSystem.Stat,File.Stat,directoryEntryInfo.Info) continue to use a single source of truth.Usage
Tests
New
TestStatSysInodeMetadataexercises/random.dat,/foo,/, and/symlink.datfrom the existing test image — asserts non-zero inode, plausible timestamps, stable values across repeatedStatcalls, and non-emptyLinkTargetfor 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*RockRidgeInfowhen Rock Ridge is present, otherwisenil. The follow-up replacesRockRidgeInfowith a richeriso9660.StatTalways returned bySys(), 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 publicsquashfs.FileStat), which already exposesUID(),GID(),Xattrs(),Readlink()as accessor methods. To stay consistent with that existing public surface, the follow-up adds two more accessors —Inode() uint32andInodeType() string— rather than introducing a parallelStatTstruct. No breaking change to the existingFileStattype.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, andfilesystem/fat32+filesystem/fat16are thin wrappers that embed*fat12.FileSystemand overrideType(). Public API parity with the old fat32 surface is preserved through Go type aliases — for example,type SectorSize = fat12.SectorSizealready exists infat32.go. A type alias (A = B) is identity, not a new named type, sofat32.SectorSizeandfat12.SectorSizeare literally the same type.The follow-up applies that same pattern for the new
StatT. The implementation definesfat12.StatTonce and a(*directoryEntry).stat()helper that populates it fromparseDirEntriesdata already on the entry. Then:Why this is the right shape (not three separate copies)
One implementation, three packages covered.
directoryEntry.stat()is defined once.fat16andfat32inherit workingSys()automatically because they reusefat12'sdirectoryEntry.Info()andFile.Stat()code paths.Same type across packages, by design. Because
type StatT = fat12.StatTis an alias (not a new named type with the same field shape), a value of*fat32.StatTis a*fat12.StatT. A downstream library that wants to handle any FAT variant can type-assert against*fat12.StatTonce and have it match all three — same way*syscall.Stat_tmatches any UNIX filesystem reachable throughos.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.No drift risk. If FAT16 later needs a new attribute exposed, it goes in
fat12.StatTand the other two get it for free. The alternative (three separateStatTstructs with the same field shape) would inevitably diverge.Mirrors the upstream architectural choice already made. This isn't introducing a new pattern;
fat16/fat32are already wrappers aroundfat12.FileSystemand their public types are already aliases. AddingStatTas an alias is consistent with howSectorSizeworks today — anything else would create two competing conventions in the same package.Fields
All populated from data
parseDirEntriesalready extracts; no new parsing. Attribute bits (ReadOnly,Hidden,System,Archive,VolumeLabel), the two FAT timestamps that don't map toModTime()(CreateTime,AccessTime), and the file's startingCluster.