Documentation
¶
Overview ¶
Package libxfs is a read-only, forensics-oriented parser for the XFS filesystem, written in pure Go with no external dependencies.
Entry points ¶
Open a volume from any io.ReaderAt — a file, a device handle, or a section of a larger image — with Open, or from a path with OpenVolumeFromPath:
volume, err := libxfs.OpenVolumeFromPath("disk.img")
if err != nil {
return err
}
defer volume.Close()
entries, err := volume.ListRootDirectoryEntries()
if err != nil {
return err
}
for _, entry := range entries {
fmt.Println(entry.Name, entry.InodeNumber, libxfs.DirEntryFileTypeName(entry.FileType))
}
From a volume, the API divides into five groups:
- Geometry and features: Volume.Superblock, and the feature predicates on Superblock such as Superblock.HasBigTimestamps.
- Metadata: Volume.OpenInode, Volume.OpenInodeByPath and the timestamp accessors on Inode.
- Content: Volume.ReadFileData, Volume.ReadInodeData and Volume.ListInodeExtendedAttributes.
- Directories: Volume.ListDirectoryEntries for the plain view, and Volume.ScanDirectoryRecordsWithOptions for the forensic view that also reports free slots and carved candidates.
- Completeness: Volume.EnumerateAllocatedInodes and Volume.InodeCompletenessReport, described below.
Completeness, and the inodes a walk cannot reach ¶
Walking directories answers "what can be reached from the root", which is not the same question as "what does this volume hold". An inode that was unlinked while a process still had it open, or whose parent directory entry was lost, is allocated and fully readable but appears in no directory. A tree walk cannot see it, and cannot tell you that it failed to.
Volume.EnumerateAllocatedInodes reads the per-allocation-group inode b-trees, which is the filesystem's own record of which inodes exist, and so returns the complete set regardless of reachability. Volume.InodeCompletenessReport reconciles that set against a walk of the tree and partitions it four ways — reachable, filesystem metadata, unlinked, and unreferenced — checking the total against both the allocation group headers and the superblock counters:
report, err := volume.InodeCompletenessReport(ctx, libxfs.InodeCompletenessOptions{})
if err != nil {
return err
}
if !report.Balanced {
// Anomalies says what did not add up; the listing is not complete.
}
for _, orphan := range report.Orphans {
fmt.Println(orphan.InodeNumber, orphan.Class, orphan.Size)
}
Balanced reports that every inode the filesystem says it has was accounted for, from three sources maintained independently of each other. That is what lets a file listing be presented as complete rather than merely as what was found. Orphans holds the unlinked and unreferenced inodes, which are readable with the ordinary content API.
The reconciliation is not free — it walks every allocation group's inode b-tree — so Volume.Report omits it unless ReportOptions.IncludeInodeCompleteness asks for it.
Reading damaged images ¶
The default behaviour is strict: a malformed structure is reported as an error rather than guessed at. Forensic callers usually want the opposite, so the directory scanners accept DirectoryScanOptions with BestEffort set, which keeps whatever was recovered from healthy blocks, resynchronises past the damage, and records a ReportAnomaly for each problem found.
Work is bounded on hostile input. Sizes are validated against the volume's own capacity, directory walks allocate a single directory block at a time rather than a recorded size, and path resolution is protected against directory loops.
Facts versus candidates ¶
Deleted directory entries are recovered by carving reclaimed space, which is inherently probabilistic. Every DirectoryRecord therefore says how it was obtained: use DirectoryRecord.IsVerified for records parsed from intact framing and DirectoryRecord.IsProbabilistic for carve candidates. Do not gate on Confidence alone — an active entry and a strong carve candidate can both be ConfidenceHigh.
Format coverage ¶
Both v4 and v5 (CRC) filesystems are supported, including short-form, block, leaf, node and btree directories, directory blocks larger than the filesystem block size, extent-list and b-tree data forks, short-form and block-based extended attributes with remote values, 64-bit "bigtime" timestamps, 64-bit (nrext64) extent counters, and both the classic and sparse-inode layouts of the inode b-tree records. An image carrying an incompatible feature this parser does not understand is refused rather than silently misread.
Writing is out of scope; this package never modifies its input.
Index ¶
- Constants
- Variables
- func DirEntryFileTypeName(fileType uint8) string
- type AllocatedInodeSource
- type AllocationGroupInodeCounts
- type Concurrency
- type DirectoryArtifactReport
- type DirectoryEntry
- type DirectoryIndexReport
- type DirectoryListing
- type DirectoryRecord
- type DirectoryRecordKind
- type DirectoryScanOptions
- type ExtendedAttribute
- type Extent
- type FragmentationReport
- type IOError
- type Inode
- type InodeChunk
- type InodeClass
- type InodeCompletenessOptions
- type InodeCompletenessReport
- type InodeEnumeration
- type InodeEnumerationOptions
- type InodeForensicReport
- type InodeFragment
- type InodeInformation
- type ParseError
- type RecoverableInode
- type RecoveryConfidence
- type ReportAnomaly
- type ReportOptions
- type ReportProvenance
- type Superblock
- func (s Superblock) AllocatedInodes() uint64
- func (s Superblock) HasBigTimestamps() bool
- func (s Superblock) HasFeatureIncompat(feature uint32) bool
- func (s Superblock) HasLargeExtentCounts() bool
- func (s Superblock) HasSparseInodes() bool
- func (s Superblock) LazySuperblockCounters() bool
- func (s Superblock) MetadataInodeNumbers() []uint64
- func (s Superblock) NeedsRepair() bool
- type UnlinkedInode
- type VerificationMode
- type Volume
- func (v *Volume) AllocationGroupCount() int
- func (v *Volume) AllocationGroupInodeInfo(index int) (InodeInformation, error)
- func (v *Volume) AnalyzeInodeFragmentation(inodeNumber uint64) (FragmentationReport, error)
- func (v *Volume) AnalyzeInodeFragmentationByPath(path string) (FragmentationReport, error)
- func (v *Volume) Close() error
- func (v *Volume) DirectoryArtifactReport(inodeNumber uint64) (DirectoryArtifactReport, error)
- func (v *Volume) DirectoryArtifactReportByPath(path string) (DirectoryArtifactReport, error)
- func (v *Volume) DirectoryParentInode(inodeNumber uint64) (uint64, error)
- func (v *Volume) DirectoryParentInodeByPath(path string) (uint64, error)
- func (v *Volume) EnumerateAllocatedInodes(ctx context.Context, options InodeEnumerationOptions) (InodeEnumeration, error)
- func (v *Volume) GetRootInode() (*Inode, error)
- func (v *Volume) InodeCompletenessReport(ctx context.Context, options InodeCompletenessOptions) (InodeCompletenessReport, error)
- func (v *Volume) InodeForensicReport(inodeNumber uint64) (InodeForensicReport, error)
- func (v *Volume) InodeForensicReportByPath(path string) (InodeForensicReport, error)
- func (v *Volume) IsClosed() bool
- func (v *Volume) ListDirectoryEntries(inodeNumber uint64) ([]DirectoryEntry, error)
- func (v *Volume) ListDirectoryEntriesByPath(path string) ([]DirectoryEntry, error)
- func (v *Volume) ListDirectoryEntriesReport(inodeNumber uint64) (DirectoryListing, error)
- func (v *Volume) ListDirectoryEntriesReportByPath(path string) (DirectoryListing, error)
- func (v *Volume) ListDirectoryEntriesWithOptions(inodeNumber uint64, options DirectoryScanOptions) (DirectoryListing, error)
- func (v *Volume) ListInodeExtendedAttributes(inodeNumber uint64) ([]ExtendedAttribute, error)
- func (v *Volume) ListRootDirectoryEntries() ([]DirectoryEntry, error)
- func (v *Volume) OpenInode(inodeNumber uint64) (*Inode, error)
- func (v *Volume) OpenInodeByPath(path string) (*Inode, error)
- func (v *Volume) ReadFileData(inodeNumber uint64) ([]byte, error)
- func (v *Volume) ReadFileDataByPath(path string) ([]byte, error)
- func (v *Volume) ReadInodeAttributeForkData(inodeNumber uint64) ([]byte, error)
- func (v *Volume) ReadInodeData(inodeNumber uint64, p []byte, off int64) (int, error)
- func (v *Volume) Report() (*XFSReport, error)
- func (v *Volume) ReportWithContext(ctx context.Context, options ReportOptions) (*XFSReport, error)
- func (v *Volume) ReportWithOptions(options ReportOptions) (*XFSReport, error)
- func (v *Volume) ResolveInodeByPath(path string) (uint64, error)
- func (v *Volume) ScanDirectoryRecords(inodeNumber uint64) ([]DirectoryRecord, error)
- func (v *Volume) ScanDirectoryRecordsByPath(path string) ([]DirectoryRecord, error)
- func (v *Volume) ScanDirectoryRecordsByPathWithOptions(path string, options DirectoryScanOptions) (DirectoryListing, error)
- func (v *Volume) ScanDirectoryRecordsWithOptions(inodeNumber uint64, options DirectoryScanOptions) (DirectoryListing, error)
- func (v *Volume) Superblock() Superblock
- func (v *Volume) UnlinkedInodes(ctx context.Context) ([]UnlinkedInode, []ReportAnomaly, error)
- func (v *Volume) VerifyDirectoryIndex(inodeNumber uint64) (DirectoryIndexReport, error)
- func (v *Volume) VerifyDirectoryIndexByPath(path string) (DirectoryIndexReport, error)
- func (v *Volume) VolumeIntegrityReport() (VolumeIntegrityReport, error)
- func (v *Volume) WalkInodeChunks(ctx context.Context, options InodeEnumerationOptions, ...) (InodeEnumeration, error)
- type VolumeError
- type VolumeIntegrityReport
- type XFSReport
Constants ¶
const ( FileTypeFIFO uint16 = 0x1000 FileTypeCharacterDevice uint16 = 0x2000 FileTypeDirectory uint16 = 0x4000 FileTypeBlockDevice uint16 = 0x6000 FileTypeRegularFile uint16 = 0x8000 FileTypeSymbolicLink uint16 = 0xa000 FileTypeSocket uint16 = 0xc000 )
const ( ForkTypeDevice uint8 = 0 ForkTypeInlineData uint8 = 1 ForkTypeExtents uint8 = 2 ForkTypeBtree uint8 = 3 )
const ( // FeatureIncompatFileType marks directory entries as carrying an ftype byte. FeatureIncompatFileType uint32 = 1 << 0 // FeatureIncompatSparseInodes marks sparse inode chunk allocation. FeatureIncompatSparseInodes uint32 = 1 << 1 // FeatureIncompatMetaUUID marks metadata stamped with a separate UUID. FeatureIncompatMetaUUID uint32 = 1 << 2 // FeatureIncompatBigTime marks 64-bit nanosecond inode timestamps. FeatureIncompatBigTime uint32 = 1 << 3 // FeatureIncompatNeedsRepair marks a filesystem left needing repair. FeatureIncompatNeedsRepair uint32 = 1 << 4 // FeatureIncompatLargeExtentCounts marks 64-bit inode extent counters // (the nrext64 feature). FeatureIncompatLargeExtentCounts uint32 = 1 << 5 )
Incompatible feature bits from the v5 superblock (sb_features_incompat).
A filesystem carrying an incompatible bit cannot be interpreted correctly by an implementation that does not understand it. BigTime and LargeExtentCounts both change the on-disk inode layout.
const ( // ExtentFlagSparse marks a range that reads back as zeros. It covers both // unmapped holes and preallocated-but-unwritten extents, because from a // reader's point of view they are the same thing. ExtentFlagSparse uint32 = 0x00000001 // ExtentFlagUnwritten marks a range that is allocated on disk but has // never been written, as produced by fallocate. It is always accompanied // by ExtentFlagSparse, since it too reads as zeros, but unlike a hole it // has a real PhysicalBlockNumber and occupies space. // // The distinction matters forensically: a hole says nothing was ever // stored there, while an unwritten extent names blocks that were reserved, // and whose previous contents may still be on the medium. ExtentFlagUnwritten uint32 = 0x00000002 )
Extent range flags reported in Extent.RangeFlags.
const ( DirEntryFileTypeUnknown uint8 = 0 DirEntryFileTypeRegularFile uint8 = 1 DirEntryFileTypeDirectory uint8 = 2 DirEntryFileTypeCharacterDevice uint8 = 3 DirEntryFileTypeBlockDevice uint8 = 4 DirEntryFileTypeFIFO uint8 = 5 DirEntryFileTypeSocket uint8 = 6 DirEntryFileTypeSymbolicLink uint8 = 7 DirEntryFileTypeWhiteout uint8 = 8 )
Directory entry file types (XFS_DIR3_FT_*).
These are stored in the optional ftype byte of a directory entry when the filesystem has the ftype feature enabled, and describe the target inode without requiring it to be read.
const ( DirectoryFormatShortForm = "short_form" DirectoryFormatBlock = "block" DirectoryFormatMultiBlock = "multi_block" )
Directory layout names reported in DirectoryListing.Format.
const ( DirectorySourceFormatShortForm = "short_form" DirectorySourceFormatBlock = "block" DirectorySourceFormatLeaf = "leaf" DirectorySourceFormatNode = "node" )
On-disk directory index formats reported in DirectoryListing.SourceFormat.
const ( XattrNamespaceUser = "user" XattrNamespaceTrusted = "trusted" XattrNamespaceSecurity = "security" )
Extended attribute namespace names, as Linux presents them. Downstream tools match on these strings, so they are part of the library's contract.
const ( SeverityInfo = "info" SeverityLow = "low" SeverityMedium = "medium" SeverityWarning = "warning" SeverityHigh = "high" SeverityError = "error" )
Severity levels reported in ReportAnomaly.Severity.
const ( InodeTypeFile = "file" InodeTypeDirectory = "directory" )
Inode type labels reported in InodeForensicReport.Type.
const ( ReasonIntactFraming = "intact_framing" ReasonTagMatchesOffset = "tag_matches_offset" ReasonNamePrintable = "name_printable" ReasonAlignedOffset = "aligned_offset" ReasonFileTypeValid = "ftype_valid" ReasonInodeAllocated = "inode_allocated" ReasonInodeUnallocated = "inode_unallocated" // ReasonInodeUnaddressable marks a recovered inode number that cannot // address anything on this volume — strong evidence of a false match. ReasonInodeUnaddressable = "inode_unaddressable" ReasonInFreeSlot = "in_free_slot" )
Evidence codes reported in DirectoryRecord.ConfidenceReasons.
const FilesystemTypeName = "xfs"
FilesystemTypeName is the filesystem label reported by VolumeIntegrityReport.
Variables ¶
var ( ErrInvalidSuperblock = errors.New("invalid or corrupted XFS superblock") ErrInvalidInode = errors.New("invalid or corrupted XFS inode") ErrInvalidInodeNumber = errors.New("invalid inode number") ErrInvalidPath = errors.New("invalid path") ErrInvalidInodeInfo = errors.New("invalid allocation-group inode information") ErrInvalidAttributeData = errors.New("invalid attribute fork data") ErrUnsupportedDirFormat = errors.New("unsupported directory format") ErrInodeNotFound = errors.New("inode not found") ErrUnsupportedFeatureFlag = errors.New("unsupported XFS feature flag") ErrUnsupportedXattrFormat = errors.New("unsupported extended attribute format") ErrVolumeClosed = errors.New("volume is closed") ErrVerificationFailed = errors.New("forensic verification failed") // ErrDirectoryTruncated reports that a directory scan stopped at a // configured cap and the listing is therefore not the whole directory. // It is returned alongside everything that was recovered. ErrDirectoryTruncated = errors.New("directory scan truncated at a configured limit") )
Functions ¶
func DirEntryFileTypeName ¶
DirEntryFileTypeName returns a human readable name for an XFS directory entry file type value.
Types ¶
type AllocatedInodeSource ¶ added in v0.3.1
type AllocatedInodeSource = string
AllocatedInodeSource labels how a set of allocated inodes was determined.
const ( // InodeSourceInodeBtree is the allocation group's inode b-tree: the // filesystem's own authoritative record of which inodes exist. Treat its // output as fact. InodeSourceInodeBtree AllocatedInodeSource = "inode_btree" // InodeSourceChunkScan is a linear sweep of the allocation group looking // for inode chunk magics, used only where the b-tree could not be walked. // It is best effort: it can miss chunks whose first inode is damaged, and // it can report stale inodes left in reclaimed space. InodeSourceChunkScan AllocatedInodeSource = "chunk_scan" // InodeSourceMixed means some allocation groups were read from the b-tree // and others from a fallback scan. Per-group sources are in // InodeEnumeration.PerAllocationGroup. InodeSourceMixed AllocatedInodeSource = "mixed" )
type AllocationGroupInodeCounts ¶ added in v0.3.1
type AllocationGroupInodeCounts struct {
AllocationGroup uint32 `json:"allocation_group"`
Source AllocatedInodeSource `json:"source"`
// Chunks, BackedSlots, Allocated and Free are derived by walking.
Chunks int `json:"chunks"`
BackedSlots uint64 `json:"backed_slots"`
Allocated uint64 `json:"allocated"`
Free uint64 `json:"free"`
// AGICount and AGIFreeCount are what agi_count and agi_freecount say. They
// are maintained transactionally by the kernel and are the standard this
// walk is measured against.
AGICount uint32 `json:"agi_count"`
AGIFreeCount uint32 `json:"agi_free_count"`
// Balanced records whether the walk and the AGI agree on both numbers.
Balanced bool `json:"balanced"`
}
AllocationGroupInodeCounts is what one allocation group was found to hold, beside what its AGI header claims it holds.
type Concurrency ¶
type Concurrency struct {
// Workers is the maximum number of tasks executed in parallel. Zero or one
// runs sequentially on the calling goroutine. Negative means "one per
// available CPU".
Workers int
}
Concurrency configures optional parallel execution.
The zero value is sequential.
type DirectoryArtifactReport ¶
type DirectoryArtifactReport struct {
InodeNumber uint64 `json:"inode_number"`
Path string `json:"path,omitempty"`
RecordCount int `json:"record_count"`
ActiveCount int `json:"active_count"`
DeletedCount int `json:"deleted_count"`
CarvedCount int `json:"carved_count"`
Records []DirectoryRecord `json:"records"`
Anomalies []ReportAnomaly `json:"anomalies,omitempty"`
}
DirectoryArtifactReport summarizes active/deleted/carved records for one directory inode.
type DirectoryEntry ¶
type DirectoryIndexReport ¶
type DirectoryIndexReport struct {
InodeNumber uint64 `json:"inode_number"`
// HasIndex is false for short-form and single-block directories, which
// have no separate hash index to check.
HasIndex bool `json:"has_index"`
// IndexedEntries counts usable entries in the hash index.
IndexedEntries int `json:"indexed_entries"`
// DataEntries counts active entries found by walking the data blocks.
DataEntries int `json:"data_entries"`
// MissingFromIndex lists entries present in the data blocks whose hash and
// address are absent from the index.
MissingFromIndex []string `json:"missing_from_index,omitempty"`
// DanglingIndexEntries counts index entries that do not resolve to a
// readable directory entry.
DanglingIndexEntries int `json:"dangling_index_entries"`
// HashMismatches lists entries whose name does not hash to the value the
// index records for it.
HashMismatches []string `json:"hash_mismatches,omitempty"`
Anomalies []ReportAnomaly `json:"anomalies,omitempty"`
}
DirectoryIndexReport compares a directory's hash index against the entries actually present in its data blocks.
The two structures are maintained together by the kernel, so any divergence means the directory was modified by something that did not maintain both — a tampering indicator that no other check in this package provides.
func (DirectoryIndexReport) Consistent ¶
func (r DirectoryIndexReport) Consistent() bool
Consistent reports whether the index and the data blocks agree.
type DirectoryListing ¶
type DirectoryListing struct {
InodeNumber uint64 `json:"inode_number"`
// Entries holds active entries only, in on-disk order.
Entries []DirectoryEntry `json:"entries,omitempty"`
// Records holds every record produced by a forensic scan: active entries,
// free slots and carved candidates. It is populated by the
// ScanDirectoryRecords* APIs. A plain listing leaves it empty, since
// building it would double the cost of the common path; use Entries there.
Records []DirectoryRecord `json:"records,omitempty"`
// Anomalies records structural problems encountered in best-effort mode.
Anomalies []ReportAnomaly `json:"anomalies,omitempty"`
// Truncated reports that a cap was reached and results are incomplete.
Truncated bool `json:"truncated,omitempty"`
// BlocksScanned counts directory blocks actually read.
BlocksScanned uint64 `json:"blocks_scanned,omitempty"`
// Format names the directory layout that was parsed. It distinguishes only
// what the walker had to do: a single block, or several. Use SourceFormat
// to learn the directory's actual on-disk shape.
Format string `json:"format,omitempty"`
// SourceFormat names the directory's on-disk index format: short_form,
// block, leaf or node.
//
// It is derived from the inode's fork type and from where the directory's
// blocks sit in its logical space, not from how many data blocks it has.
// Two directories with the same data-block count can be in different
// formats, so Format cannot answer this and must not be used to.
SourceFormat string `json:"source_format,omitempty"`
}
DirectoryListing is the result of a directory scan.
type DirectoryRecord ¶
type DirectoryRecord struct {
Name string
InodeNumber uint64
IsDeleted bool
Offset uint16
RecordLength uint16
IsCarved bool
Confidence RecoveryConfidence
// Kind describes how this record was obtained. Prefer it over the
// IsDeleted/IsCarved pair when gating downstream decisions.
Kind DirectoryRecordKind
// FileType is the XFS directory entry file type (DirEntryFileType*).
FileType uint8
// BlockIndex is the directory-block index this record was found in.
BlockIndex uint64
// LogicalOffset is the absolute byte offset of the record within the
// directory data stream. Offset is only meaningful within its block.
LogicalOffset uint64
// ConfidenceReasons lists the evidence codes behind Confidence.
ConfidenceReasons []string
}
DirectoryRecord represents an active or deleted slot recovered from directory data structures.
func (DirectoryRecord) IsProbabilistic ¶
func (r DirectoryRecord) IsProbabilistic() bool
IsProbabilistic reports whether the record was carved heuristically and must be presented as a candidate rather than as fact.
func (DirectoryRecord) IsVerified ¶
func (r DirectoryRecord) IsVerified() bool
IsVerified reports whether the record was parsed from intact directory framing and can be treated as fact.
type DirectoryRecordKind ¶
type DirectoryRecordKind = string
DirectoryRecordKind distinguishes how a record was obtained. Confidence alone is not a safe gate: an active entry and a carved candidate can both be ConfidenceHigh. Switch on Kind, or use IsVerified/IsProbabilistic.
const ( // RecordKindActive is an entry parsed from intact directory framing. RecordKindActive DirectoryRecordKind = "active" // RecordKindFreeSlot is an unused-space run. It marks reclaimed space and // carries no recovered name or inode number. RecordKindFreeSlot DirectoryRecordKind = "free_slot" // RecordKindCarved is a probabilistic candidate recovered from free space // by pattern matching. It may be stale, partial, or entirely spurious. RecordKindCarved DirectoryRecordKind = "carved" )
type DirectoryScanOptions ¶
type DirectoryScanOptions struct {
// IncludeDeleted reports free-space runs and carved candidates alongside
// active entries.
IncludeDeleted bool
// BestEffort keeps whatever was recovered when a block is malformed,
// recording a ReportAnomaly and resynchronising instead of failing. This
// is usually what forensic callers want on a damaged image.
BestEffort bool
// MaxBlocks caps the number of directory blocks read. Zero applies the
// default cap.
MaxBlocks uint64
// MaxEntries caps the number of records collected. Zero applies the
// default cap.
MaxEntries int
// contains filtered or unexported fields
}
DirectoryScanOptions controls how a directory is walked.
The zero value is strict: any framing error aborts the scan, and only active entries are reported.
type ExtendedAttribute ¶
type FragmentationReport ¶
type FragmentationReport struct {
InodeNumber uint64
Size uint64
DataExtentCount int
AllocatedExtentCount int
SparseExtentCount int
PhysicalFragmentRuns int
HasLogicalHoles bool
HasPhysicalFragmentation bool
HasAnyFragmentationOrHoles bool
}
FragmentationReport summarizes how a file's data is laid out across extents.
type Inode ¶
type Inode struct {
FormatVersion uint8
FileMode uint16
ForkType uint8
OwnerID uint32
GroupID uint32
NumberOfLinks uint32
AccessTimeNS int64
ModificationTimeNS int64
InodeChangeTimeNS int64
CreationTimeNS int64
Size uint64
// NumberOfDataExtents is the data fork extent count, saturated to 32 bits.
// On filesystems with the nrext64 feature the on-disk counter is 64 bits
// wide; prefer DataExtentCount, which cannot overflow.
NumberOfDataExtents uint32
// NumberOfAttributesExtent is the attribute fork extent count, saturated
// to 16 bits. Prefer AttributeExtentCount.
NumberOfAttributesExtent uint16
// DataExtentCount is the full-width data fork extent count.
DataExtentCount uint64
// AttributeExtentCount is the full-width attribute fork extent count.
AttributeExtentCount uint32
// HasBigTimestamps records whether this inode's timestamps were decoded
// using the 64-bit bigtime encoding.
HasBigTimestamps bool
AttributesForkType uint8
DeviceIdentifier uint32
DataForkOffset uint16
DataForkSize uint16
AttributesForkOffset uint16
AttributesForkSize uint16
InlineData []byte
DataExtents []Extent
InlineAttributesData []byte
AttributesExtents []Extent
Raw []byte
}
func (*Inode) AccessTime ¶
func (*Inode) CreationTime ¶
func (*Inode) InodeChangeTime ¶
func (*Inode) IsDirectory ¶
func (*Inode) ModificationTime ¶
type InodeChunk ¶ added in v0.3.1
type InodeChunk struct {
// AllocationGroup is the group this record was read from.
AllocationGroup uint32
// StartInode is the absolute inode number of slot 0. RelativeStartInode is
// ir_startino, the same value relative to the allocation group.
StartInode uint64
RelativeStartInode uint32
// HoleMask is ir_holemask. Each of its 16 bits covers four consecutive
// slots, and a set bit means those four are not backed by disk. It is zero
// on filesystems without the sparse-inodes feature.
HoleMask uint16
// Count is ir_count, the number of slots that are backed by disk. It is 64
// unless the chunk is sparse.
Count uint8
// FreeCount is ir_freecount, how many backed slots are unused.
FreeCount uint32
// FreeMask is ir_free. A set bit means that slot is free; note the
// inversion, which is the opposite of what "free mask" suggests to most
// readers.
FreeMask uint64
}
InodeChunk is one inode b-tree record: a run of 64 consecutive inode slots and their allocation state.
Slots are addressed 0..63 relative to StartInode. A slot is one of three things, and the distinction matters: allocated (an inode exists and is in use), free (backed by disk, currently unused, and its previous contents may still be readable), or a hole (no disk behind it at all, possible only with the sparse-inodes feature).
func (InodeChunk) AllocatedCount ¶ added in v0.3.1
func (c InodeChunk) AllocatedCount() int
AllocatedCount returns how many slots in this chunk hold a live inode.
func (InodeChunk) AllocatedInodes ¶ added in v0.3.1
func (c InodeChunk) AllocatedInodes(dst []uint64) []uint64
AllocatedInodes appends the absolute inode number of every allocated slot to dst, in ascending order, and returns the extended slice.
Appending to a caller-owned slice keeps enumeration of a large filesystem from allocating once per chunk.
func (InodeChunk) InodeNumber ¶ added in v0.3.1
func (c InodeChunk) InodeNumber(slot int) uint64
InodeNumber returns the absolute inode number of a slot, without regard to whether it is allocated.
func (InodeChunk) IsAllocated ¶ added in v0.3.1
func (c InodeChunk) IsAllocated(slot int) bool
IsAllocated reports whether a slot holds a live inode.
func (InodeChunk) IsFree ¶ added in v0.3.1
func (c InodeChunk) IsFree(slot int) bool
IsFree reports whether a slot is backed by disk but holds no live inode.
A free slot is not empty space: unless the chunk was only just allocated, it holds the remains of whatever inode used to live there.
func (InodeChunk) IsHole ¶ added in v0.3.1
func (c InodeChunk) IsHole(slot int) bool
IsHole reports whether a slot has no backing disk.
type InodeClass ¶ added in v0.3.1
type InodeClass = string
InodeClass says why an allocated inode is where it is.
const ( // InodeClassReachable means some path from the root leads to this inode. InodeClassReachable InodeClass = "reachable" // InodeClassMetadata means the filesystem allocated this inode for its own // use — a realtime bitmap or summary, or a quota file. It is unreachable by // design and is not evidence of anything. InodeClassMetadata InodeClass = "metadata" // InodeClassUnlinked means the inode is on an AGI unlinked chain: it was // deleted while a process still held it open. The filesystem itself is // recording the deletion, so this is a finding rather than an inference. InodeClassUnlinked InodeClass = "unlinked" // InodeClassUnreferenced means the inode is allocated, is not filesystem // metadata, is not on an unlinked chain, and no directory reachable from // the root names it. Something was lost: either the directory entry that // referred to it, or the directory that held that entry. InodeClassUnreferenced InodeClass = "unreferenced" )
type InodeCompletenessOptions ¶ added in v0.3.1
type InodeCompletenessOptions struct {
// RootPath is where the directory walk starts. Defaults to "/". Starting
// anywhere else makes the balance meaningless, since inodes outside that
// subtree are unreachable by construction; the report says so.
RootPath string
// Enumeration is passed through to [Volume.WalkInodeChunks].
Enumeration InodeEnumerationOptions
// MaxWalkEntries caps the directory walk. A capped walk cannot support any
// conclusion about completeness, so the report is marked unbalanced.
MaxWalkEntries int
// MaxOrphans caps how many unreachable inodes are described in detail.
// Counts are unaffected. Zero means unlimited.
MaxOrphans int
// SkipOrphanDetails counts unreachable inodes without opening them. It
// turns the report into a cheap consistency check on a volume with a very
// large number of them.
SkipOrphanDetails bool
// SkipOrphanedDirectoryDescent stops the report walking into orphaned
// directories to name what they contain.
SkipOrphanedDirectoryDescent bool
}
InodeCompletenessOptions controls the reconciliation.
type InodeCompletenessReport ¶ added in v0.3.1
type InodeCompletenessReport struct {
GeneratedAt time.Time `json:"generated_at"`
RootPath string `json:"root_path"`
Source AllocatedInodeSource `json:"source"`
// The superblock's own accounting, and whether it is the lazily maintained
// kind that a crash leaves stale.
SuperblockIcount uint64 `json:"superblock_icount"`
SuperblockIfree uint64 `json:"superblock_ifree"`
SuperblockAllocated uint64 `json:"superblock_allocated"`
SuperblockLazy bool `json:"superblock_counters_lazy"`
// The sum of the per-allocation-group headers, which is the standard the
// walk is judged against.
AllocationGroupCount uint64 `json:"allocation_group_count"`
AllocationGroupFree uint64 `json:"allocation_group_free"`
AllocationGroupAllocated uint64 `json:"allocation_group_allocated"`
// EnumeratedAllocated is what walking the inode b-trees actually found.
EnumeratedAllocated uint64 `json:"enumerated_allocated"`
// The four-way partition. Together they must account for every enumerated
// inode.
ReachableInodes uint64 `json:"reachable_inodes"`
MetadataInodes uint64 `json:"metadata_inodes"`
UnlinkedInodes uint64 `json:"unlinked_inodes"`
UnreferencedInodes uint64 `json:"unreferenced_inodes"`
ClassifiedInodes uint64 `json:"classified_inodes"`
// Orphans describes the unlinked and unreferenced inodes: everything that
// exists and holds data but that no directory walk would ever produce.
Orphans []RecoverableInode `json:"orphans,omitempty"`
OrphansTruncated bool `json:"orphans_truncated"`
PerAllocationGroup []AllocationGroupInodeCounts `json:"per_allocation_group,omitempty"`
// Balanced is the conclusion. When it is true, every inode the filesystem
// says it has was accounted for, from three independently maintained
// sources, and the file listing is complete. When it is false, Anomalies
// says what did not add up, and no completeness claim should be made.
Balanced bool `json:"balanced"`
Anomalies []ReportAnomaly `json:"anomalies,omitempty"`
}
InodeCompletenessReport reconciles a directory walk against everything the filesystem says it holds.
func (*InodeCompletenessReport) Summary ¶ added in v0.3.1
func (r *InodeCompletenessReport) Summary() string
Summary renders the reconciliation as a short human-readable block.
type InodeEnumeration ¶ added in v0.3.1
type InodeEnumeration struct {
// Source is InodeSourceInodeBtree when every allocation group was read
// from its b-tree. Anything else means part of the answer is best effort
// and must be presented as such.
Source AllocatedInodeSource `json:"source"`
// Inodes holds every allocated inode number in ascending order. It is
// populated by EnumerateAllocatedInodes and left empty by WalkInodeChunks,
// which streams instead.
Inodes []uint64 `json:"inodes,omitempty"`
// Chunks holds the decoded records, when IncludeChunks asked for them.
Chunks []InodeChunk `json:"chunks,omitempty"`
PerAllocationGroup []AllocationGroupInodeCounts `json:"per_allocation_group"`
// AllocatedCount, FreeCount and BackedSlotCount total the per-group
// figures. AllocatedCount is the number to compare against the superblock's
// AllocatedInodes.
AllocatedCount uint64 `json:"allocated_count"`
FreeCount uint64 `json:"free_count"`
BackedSlotCount uint64 `json:"backed_slot_count"`
// Truncated reports that MaxInodes cut the collected set short. Counts
// remain complete; Inodes does not.
Truncated bool `json:"truncated"`
// Balanced reports that every allocation group's walk agreed with its AGI
// header, and that no group needed a fallback scan. When it is false, the
// enumeration is evidence rather than proof, and Anomalies says why.
Balanced bool `json:"balanced"`
Anomalies []ReportAnomaly `json:"anomalies,omitempty"`
}
InodeEnumeration is the result of enumerating allocated inodes.
type InodeEnumerationOptions ¶ added in v0.3.1
type InodeEnumerationOptions struct {
// BestEffort keeps going when an allocation group's inode b-tree cannot be
// walked: the group falls back to a linear chunk scan and an anomaly is
// recorded. The zero value fails the whole enumeration instead, which is
// the right default for a caller that intends to reconcile counts.
BestEffort bool
// IncludeChunks retains every decoded chunk record in the result. Off by
// default because a large filesystem has a great many of them.
IncludeChunks bool
// MaxInodes caps how many inode numbers are collected, setting Truncated
// when it bites. Zero is unlimited. It bounds memory, not work: the walk
// still visits every group so that the counts stay meaningful.
MaxInodes int
// VerifyFreeInodeBtree additionally walks the free inode b-tree and checks
// that it agrees with the inode b-tree about which inodes are free. The two
// trees are maintained separately, so a disagreement is a strong signal of
// damage or tampering. v5 filesystems with the finobt feature only.
VerifyFreeInodeBtree bool
}
InodeEnumerationOptions controls how the allocated inode set is gathered.
type InodeForensicReport ¶
type InodeForensicReport struct {
InodeNumber uint64 `json:"inode_number"`
Path string `json:"path,omitempty"`
Type string `json:"type"`
FileMode uint16 `json:"file_mode"`
ForkType uint8 `json:"fork_type"`
Size uint64 `json:"size"`
OwnerID uint32 `json:"owner_id"`
GroupID uint32 `json:"group_id"`
NumberOfLinks uint32 `json:"number_of_links"`
AccessTime time.Time `json:"access_time"`
ModificationTime time.Time `json:"modification_time"`
InodeChangeTime time.Time `json:"inode_change_time"`
CreationTime time.Time `json:"creation_time,omitempty"`
DataExtentCount int `json:"data_extent_count"`
AttributesExtentCount int `json:"attributes_extent_count"`
HasInlineData bool `json:"has_inline_data"`
Fragmentation FragmentationReport `json:"fragmentation"`
Fragments []InodeFragment `json:"fragments,omitempty"`
ExtendedAttributeNames []string `json:"extended_attribute_names,omitempty"`
Anomalies []ReportAnomaly `json:"anomalies,omitempty"`
}
InodeForensicReport is structured metadata for one inode.
type InodeFragment ¶
type InodeFragment struct {
StartOffset uint64 `json:"start_offset"`
EndOffset uint64 `json:"end_offset"`
LengthBytes uint64 `json:"length_bytes"`
LogicalBlockNumber uint64 `json:"logical_block_number"`
PhysicalBlockNumber uint64 `json:"physical_block_number"`
NumberOfBlocks uint32 `json:"number_of_blocks"`
// IsSparse marks a fragment that reads back as zeros, whether because it
// is an unmapped hole or because it was preallocated and never written.
IsSparse bool `json:"is_sparse"`
// IsUnwritten distinguishes the second case: blocks were reserved on disk
// and never written to. Unlike a hole, PhysicalBlockNumber names real
// blocks whose prior contents may still be recoverable from the medium.
IsUnwritten bool `json:"is_unwritten"`
}
InodeFragment describes one extent from the inode data fork.
type InodeInformation ¶
type InodeInformation struct {
FormatVersion uint32
InodeBtreeRootBlock uint32
InodeBtreeDepth uint32
LastAllocatedChunk uint32
// SequenceNumber is agi_seqno, this group's own index. Length is
// agi_length, its size in filesystem blocks.
SequenceNumber uint32
Length uint32
// Count is agi_count, the number of inode slots allocated in this group,
// and FreeCount is agi_freecount, how many of them are unused.
Count uint32
FreeCount uint32
// LastDirectoryChunk is agi_dirino.
LastDirectoryChunk uint32
// UnlinkedBuckets is agi_unlinked: 64 chain heads, each a relative inode
// number or agiNullInode for an empty bucket. A non-empty bucket names an
// inode that was unlinked while a process still had it open, which is
// direct on-disk evidence of a deleted-but-recoverable file. The chains are
// threaded through di_next_unlinked in the inodes themselves.
//
// A cleanly unmounted filesystem has no unlinked inodes left: the kernel
// processes the chains as part of unmounting. A populated list is therefore
// a sign the image was captured from a live or crashed system.
UnlinkedBuckets [agiUnlinkedBuckets]uint32
// FreeInodeBtreeRootBlock and FreeInodeBtreeDepth locate the free inode
// b-tree (finobt). Both are zero on v4 filesystems and on v5 filesystems
// without the finobt feature.
FreeInodeBtreeRootBlock uint32
FreeInodeBtreeDepth uint32
// InodeBtreeBlocks and FreeInodeBtreeBlocks are agi_iblocks and
// agi_fblocks, the block counts of the two trees. v5 only.
InodeBtreeBlocks uint32
FreeInodeBtreeBlocks uint32
}
InodeInformation is one allocation group's inode header (xfs_agi).
Unlike the superblock counters, Count and FreeCount are updated transactionally with every inode allocation, so they are the authoritative answer to how many inodes an allocation group holds.
func (InodeInformation) AllocatedInodes ¶ added in v0.3.1
func (i InodeInformation) AllocatedInodes() uint32
AllocatedInodes returns the number of inodes in use in this allocation group.
func (InodeInformation) HasFreeInodeBtree ¶ added in v0.3.1
func (i InodeInformation) HasFreeInodeBtree() bool
HasFreeInodeBtree reports whether this allocation group carries a finobt that can be walked as an independent check on the inode b-tree.
func (InodeInformation) HasUnlinkedInodes ¶ added in v0.3.1
func (i InodeInformation) HasUnlinkedInodes() bool
HasUnlinkedInodes reports whether any unlinked chain in this allocation group is non-empty.
type ParseError ¶
func (*ParseError) Error ¶
func (e *ParseError) Error() string
func (*ParseError) Unwrap ¶
func (e *ParseError) Unwrap() error
type RecoverableInode ¶ added in v0.3.1
type RecoverableInode struct {
InodeNumber uint64 `json:"inode_number"`
Class InodeClass `json:"class"`
AllocationGroup uint32 `json:"allocation_group"`
// Type, FileMode, Size and the rest come from the inode itself and are
// zero when it could not be opened, in which case Anomalies says why. An
// inode that cannot be opened is still reported: that it exists is a fact
// established by the inode b-tree, independent of whether it parses.
Type string `json:"type,omitempty"`
FileMode uint16 `json:"file_mode,omitempty"`
Size uint64 `json:"size,omitempty"`
OwnerID uint32 `json:"owner_id,omitempty"`
GroupID uint32 `json:"group_id,omitempty"`
NumberOfLinks uint32 `json:"number_of_links"`
ModificationTime time.Time `json:"modification_time,omitempty"`
InodeChangeTime time.Time `json:"inode_change_time,omitempty"`
// UnlinkedBucket is which agi_unlinked chain this inode is on, or -1 when
// it is on none.
UnlinkedBucket int `json:"unlinked_bucket"`
// ParentInodeNumber is what this inode's ".." points at, for directories.
// ParentPath is that parent's path when the parent is reachable from the
// root, which locates an orphaned directory in the tree even though the
// entry naming it is gone.
ParentInodeNumber uint64 `json:"parent_inode_number,omitempty"`
ParentPath string `json:"parent_path,omitempty"`
// RecoveredPath is set for an inode found by descending an orphaned
// directory. It is relative to RecoveredPathRoot, the outermost orphaned
// directory it was reached from, because there is by definition no path
// from the filesystem root to it.
RecoveredPath string `json:"recovered_path,omitempty"`
RecoveredPathRoot uint64 `json:"recovered_path_root,omitempty"`
Anomalies []ReportAnomaly `json:"anomalies,omitempty"`
}
RecoverableInode is one allocated inode that the directory walk did not reach, described well enough to act on.
type RecoveryConfidence ¶
type RecoveryConfidence = string
RecoveryConfidence labels how much trust a recovered directory record deserves. It is a string alias so that existing comparisons against plain string literals keep compiling.
const ( ConfidenceLow RecoveryConfidence = "low" ConfidenceMedium RecoveryConfidence = "medium" ConfidenceHigh RecoveryConfidence = "high" )
Confidence levels applied to DirectoryRecord.Confidence.
type ReportAnomaly ¶
type ReportAnomaly struct {
Code string `json:"code"`
Severity string `json:"severity"`
Message string `json:"message"`
Path string `json:"path,omitempty"`
Inode uint64 `json:"inode,omitempty"`
}
ReportAnomaly captures a parsing or consistency concern encountered while building reports.
type ReportOptions ¶
type ReportOptions struct {
// RootPath selects the start path for traversal. Defaults to "/".
RootPath string
// MaxEntries limits the number of discovered inodes in Files.
// Zero or negative means unlimited.
MaxEntries int
// IncludeDirectoryArtifacts includes deleted/carved directory record output
// for each visited directory inode.
IncludeDirectoryArtifacts bool
// VerificationMode controls whether checksum/verification mismatches are
// fatal (`strict`) or recorded as anomalies (`best_effort`).
VerificationMode VerificationMode
// IncludeInodeCompleteness reconciles the walk against every inode the
// filesystem has allocated, and reports the ones no directory reaches.
//
// It is off by default because it walks every allocation group's inode
// b-tree, which a caller who only wants a file listing should not pay for.
// Turn it on to be able to say the listing is complete, and to surface
// deleted-but-open and orphaned files, which no tree walk can find.
IncludeInodeCompleteness bool
// InodeCompleteness tunes that reconciliation. Its RootPath is ignored in
// favour of ReportOptions.RootPath, and its MaxWalkEntries in favour of
// MaxEntries, so that both walks describe the same thing.
InodeCompleteness InodeCompletenessOptions
// Concurrency optionally analyses discovered inodes in parallel. The
// zero value is sequential. Output is identical regardless of the
// worker count.
Concurrency Concurrency
}
ReportOptions controls how volume-level report generation behaves.
type ReportProvenance ¶
type ReportProvenance struct {
VerificationMode VerificationMode `json:"verification_mode"`
Coverage []string `json:"coverage"`
SuperblockCRCChecked bool `json:"superblock_crc_checked"`
InodeCRCChecked bool `json:"inode_crc_checked"`
}
ReportProvenance captures parser and verification context for reproducibility.
type Superblock ¶
type Superblock struct {
BlockSize uint32
NumberOfBlocks uint64
JournalBlockNumber uint64
RootDirectoryInodeNumber uint64
AllocationGroupSize uint32
NumberOfAllocationGroups uint32
FormatVersion uint8
FeatureFlags uint16
SectorSize uint16
InodeSize uint16
DirectoryBlockSize uint32
VolumeLabel [12]byte
SecondaryFeatureFlags uint32
RelativeBlockNumberBits uint8
RelativeInodeNumberBits uint8
// v5-only feature words. These are zero on v4 filesystems, which do not
// have the fields at all.
FeaturesCompat uint32
FeaturesReadOnlyCompat uint32
FeaturesIncompat uint32
FeaturesLogIncompat uint32
// Icount is sb_icount: the number of inode slots the filesystem has
// allocated, counted in chunks rather than individually. Ifree is sb_ifree,
// how many of those slots are unused. The difference is the number of
// inodes actually in use.
//
// Both are lazily maintained when LazySuperblockCounters reports true,
// which is the default on any filesystem this parser is likely to meet.
// The kernel then only writes them out on a clean unmount, so on an image
// captured from a running or crashed system they are a hint, not a fact.
// [Volume.AllocationGroupInodeInfo] carries the authoritative per-group
// counters.
Icount uint64
Ifree uint64
// FeatureFlags2 is sb_features2. FeatureFlags2Backup is sb_bad_features2,
// the duplicate the kernel keeps to work around a historical alignment bug.
// They are normally identical; a difference means one of the two writes was
// lost and is worth reporting.
FeatureFlags2 uint32
FeatureFlags2Backup uint32
// SparseInodeAlignment is sb_spino_align, the sparse-inode allocation
// granularity in filesystem blocks. It is zero unless the sparse-inodes
// feature is enabled.
SparseInodeAlignment uint32
// Inodes the filesystem allocates for its own use. They are counted in
// Icount and are perfectly real, but nothing in the directory tree ever
// refers to them, so anything reconciling a directory walk against the
// allocated set has to account for them separately or report them as
// orphans. Each is zero or NULLFSINO when the filesystem has no such inode.
RealtimeBitmapInodeNumber uint64
RealtimeSummaryInodeNumber uint64
UserQuotaInodeNumber uint64
GroupQuotaInodeNumber uint64
ProjectQuotaInodeNumber uint64
}
func (Superblock) AllocatedInodes ¶ added in v0.3.1
func (s Superblock) AllocatedInodes() uint64
AllocatedInodes returns the number of inodes in use: sb_icount minus sb_ifree.
"Allocated" here means an inode that exists and is owned by something, which is the sense a caller counting recoverable objects wants. It is not sb_icount, which counts allocated inode *slots* including the free ones.
The result is zero rather than a wrapped value when Ifree exceeds Icount, which can only happen on an image whose counters are damaged or stale. Use Superblock.LazySuperblockCounters to tell whether these numbers are trustworthy at all, and reconcile against the per-allocation-group counters before relying on them.
func (Superblock) HasBigTimestamps ¶
func (s Superblock) HasBigTimestamps() bool
HasBigTimestamps reports whether inode timestamps use the 64-bit "bigtime" encoding rather than the legacy 32-bit seconds/nanoseconds pair.
func (Superblock) HasFeatureIncompat ¶
func (s Superblock) HasFeatureIncompat(feature uint32) bool
HasFeatureIncompat reports whether an incompatible feature bit is set.
func (Superblock) HasLargeExtentCounts ¶
func (s Superblock) HasLargeExtentCounts() bool
HasLargeExtentCounts reports whether inodes use the 64-bit extent counters introduced by the nrext64 feature.
func (Superblock) HasSparseInodes ¶ added in v0.3.1
func (s Superblock) HasSparseInodes() bool
HasSparseInodes reports whether inode chunks may be sparsely allocated. It changes the inode b-tree record layout: see InodeChunk.
func (Superblock) LazySuperblockCounters ¶ added in v0.3.1
func (s Superblock) LazySuperblockCounters() bool
LazySuperblockCounters reports whether Icount and Ifree are lazily maintained, and therefore only accurate as of the last clean unmount.
func (Superblock) MetadataInodeNumbers ¶ added in v0.3.1
func (s Superblock) MetadataInodeNumbers() []uint64
MetadataInodeNumbers returns the inodes the filesystem allocated for itself, in ascending order and with absent ones omitted.
These exist and are allocated, but no directory entry names them, which makes them the standing exception to "allocated but unreachable means deleted".
func (Superblock) NeedsRepair ¶
func (s Superblock) NeedsRepair() bool
NeedsRepair reports whether the filesystem was marked as requiring repair. Such an image was left in an inconsistent state and its metadata should be treated with suspicion.
type UnlinkedInode ¶ added in v0.3.1
type UnlinkedInode struct {
// InodeNumber is the absolute inode number, usable with
// [Volume.OpenInode] and the rest of the API.
InodeNumber uint64 `json:"inode_number"`
// AllocationGroup is the group whose AGI names this chain, and Bucket is
// which of its 64 chains the inode is on.
AllocationGroup uint32 `json:"allocation_group"`
Bucket int `json:"bucket"`
// Position is how far along the chain the inode sits, counting from zero at
// the head. The head is the most recently unlinked inode in that bucket.
Position int `json:"position"`
}
UnlinkedInode is one inode found on an allocation group's unlinked chain.
type VerificationMode ¶
type VerificationMode string
VerificationMode defines how report generation handles verification failures.
const ( // VerificationModeBestEffort records anomalies and continues report generation. VerificationModeBestEffort VerificationMode = "best_effort" // VerificationModeStrict fails report generation on verification mismatch. VerificationModeStrict VerificationMode = "strict" )
type Volume ¶
type Volume struct {
// contains filtered or unexported fields
}
Volume is an XFS volume parser with concurrency-safe read APIs.
func Open ¶
Open parses an XFS volume from a random-access reader.
The reader is not closed by Volume.Close; the caller retains ownership. Use OpenVolumeFromPath when the volume should own its file handle. To read a filesystem embedded in a larger image, pass an io.SectionReader covering the partition.
The returned Volume is safe for concurrent use.
func OpenVolumeFromPath ¶
OpenVolumeFromPath opens an XFS volume from a filesystem path.
This is a convenience wrapper around os.Open and Open. The returned volume owns the underlying file handle, and Volume.Close will close it.
Raw device access generally requires elevated privileges: run as Administrator on Windows and use a path such as \\.\PhysicalDrive0, or read a block device such as /dev/sda1 on Linux.
func (*Volume) AllocationGroupCount ¶ added in v0.3.1
AllocationGroupCount returns the number of allocation groups whose inode headers were parsed when the volume was opened.
It is normally equal to the superblock's NumberOfAllocationGroups. Prefer it when indexing into Volume.AllocationGroupInodeInfo, because it describes what was actually read.
func (*Volume) AllocationGroupInodeInfo ¶ added in v0.3.1
func (v *Volume) AllocationGroupInodeInfo(index int) (InodeInformation, error)
AllocationGroupInodeInfo returns one allocation group's inode header.
This is the authoritative source for how many inodes a group holds, and the only place the unlinked-inode chain heads are exposed. Unlike the superblock counters it is updated with every inode allocation, so it stays correct on an image captured from a running system.
func (*Volume) AnalyzeInodeFragmentation ¶
func (v *Volume) AnalyzeInodeFragmentation(inodeNumber uint64) (FragmentationReport, error)
AnalyzeInodeFragmentation analyzes the data-fork extent layout of an inode.
func (*Volume) AnalyzeInodeFragmentationByPath ¶
func (v *Volume) AnalyzeInodeFragmentationByPath(path string) (FragmentationReport, error)
AnalyzeInodeFragmentationByPath resolves a file path then analyzes fragmentation.
func (*Volume) Close ¶
Close releases the volume.
It waits for in-flight reads to finish before releasing the backing reader, so it is safe to call concurrently with reads. Subsequent operations return ErrVolumeClosed. Closing an already closed volume returns ErrVolumeClosed.
func (*Volume) DirectoryArtifactReport ¶
func (v *Volume) DirectoryArtifactReport(inodeNumber uint64) (DirectoryArtifactReport, error)
DirectoryArtifactReport reports active/deleted/carved records for one directory.
func (*Volume) DirectoryArtifactReportByPath ¶
func (v *Volume) DirectoryArtifactReportByPath(path string) (DirectoryArtifactReport, error)
DirectoryArtifactReportByPath resolves a path and reports directory artifacts.
func (*Volume) DirectoryParentInode ¶
DirectoryParentInode returns the inode number a directory's ".." refers to.
For short-form directories the parent is stored in the directory header; for block-backed directories it is the ".." entry in the first data block. It is the only in-inode link back up the tree, which makes it the starting point for reconstructing the path of an orphaned directory.
func (*Volume) DirectoryParentInodeByPath ¶
DirectoryParentInodeByPath resolves a directory path and returns its parent inode number.
func (*Volume) EnumerateAllocatedInodes ¶ added in v0.3.1
func (v *Volume) EnumerateAllocatedInodes(ctx context.Context, options InodeEnumerationOptions) (InodeEnumeration, error)
EnumerateAllocatedInodes returns every inode the filesystem has allocated, whether or not any directory refers to it.
This is the authoritative answer to what a volume contains. Comparing it against a directory walk is what turns "here is what we found" into "here is what we found, and here is the evidence that nothing else exists" — see Volume.InodeCompletenessReport, which does exactly that.
The returned inode numbers are ascending and unique. On a filesystem with many millions of inodes, prefer Volume.WalkInodeChunks, which streams.
func (*Volume) GetRootInode ¶
func (*Volume) InodeCompletenessReport ¶ added in v0.3.1
func (v *Volume) InodeCompletenessReport(ctx context.Context, options InodeCompletenessOptions) (InodeCompletenessReport, error)
InodeCompletenessReport reconciles the directory tree against every inode the filesystem has allocated, and reports what the walk could not reach.
This is the self-check that makes a file listing defensible. It needs no mount, no kernel support and no second tool: the evidence is already on the volume, in three places that are maintained separately and therefore corroborate each other.
Memory is bounded by the number of reachable inodes plus the number of orphans, not by the total inode count, because the allocated set is streamed rather than materialised.
func (*Volume) InodeForensicReport ¶
func (v *Volume) InodeForensicReport(inodeNumber uint64) (InodeForensicReport, error)
InodeForensicReport builds a structured report for one inode.
func (*Volume) InodeForensicReportByPath ¶
func (v *Volume) InodeForensicReportByPath(path string) (InodeForensicReport, error)
InodeForensicReportByPath resolves a path and reports that inode.
func (*Volume) IsClosed ¶
IsClosed reports whether the volume has been closed.
It is a point-in-time answer: on a volume shared with a goroutine that may call Close, prefer acting on ErrVolumeClosed from the operation itself.
func (*Volume) ListDirectoryEntries ¶
func (v *Volume) ListDirectoryEntries(inodeNumber uint64) ([]DirectoryEntry, error)
ListDirectoryEntries lists active entries for a directory inode.
Short-form, block, leaf, node and btree directories are all supported: entries always live in the directory's data-block region, which is walked one directory block at a time. Entries recovered before a failure are returned alongside the error: on a damaged image the blocks that did parse are evidence, and discarding them because a later block did not is how a recursive walk loses whole subtrees. Use ListDirectoryEntriesReport when the completeness of the listing matters.
func (*Volume) ListDirectoryEntriesByPath ¶
func (v *Volume) ListDirectoryEntriesByPath(path string) ([]DirectoryEntry, error)
ListDirectoryEntriesByPath resolves a directory path and lists its entries.
func (*Volume) ListDirectoryEntriesReport ¶ added in v0.3.0
func (v *Volume) ListDirectoryEntriesReport(inodeNumber uint64) (DirectoryListing, error)
ListDirectoryEntriesReport lists a directory and reports how the scan went.
ListDirectoryEntries returns only the entries, so a caller cannot tell a complete listing from one that stopped at a cap or skipped an unreadable block. The returned DirectoryListing carries Truncated, Anomalies, BlocksScanned and SourceFormat for callers that must know.
func (*Volume) ListDirectoryEntriesReportByPath ¶ added in v0.3.0
func (v *Volume) ListDirectoryEntriesReportByPath(path string) (DirectoryListing, error)
ListDirectoryEntriesReportByPath resolves a path and lists it with a report.
func (*Volume) ListDirectoryEntriesWithOptions ¶
func (v *Volume) ListDirectoryEntriesWithOptions(inodeNumber uint64, options DirectoryScanOptions) (DirectoryListing, error)
ListDirectoryEntriesWithOptions lists a directory under explicit scan options.
func (*Volume) ListInodeExtendedAttributes ¶
func (v *Volume) ListInodeExtendedAttributes(inodeNumber uint64) ([]ExtendedAttribute, error)
ListInodeExtendedAttributes lists decoded inode extended attributes.
For block-based attribute trees, returned entries preserve traversal order. If duplicate fully-qualified names are present, duplicates are preserved in the returned slice (no deduplication is performed).
func (*Volume) ListRootDirectoryEntries ¶
func (v *Volume) ListRootDirectoryEntries() ([]DirectoryEntry, error)
ListRootDirectoryEntries lists entries for the root directory inode.
func (*Volume) OpenInodeByPath ¶
OpenInodeByPath resolves an absolute path and opens the corresponding inode.
func (*Volume) ReadFileData ¶
ReadFileData reads all data bytes from a non-directory inode.
func (*Volume) ReadFileDataByPath ¶
ReadFileDataByPath resolves an absolute path and reads all file data bytes.
func (*Volume) ReadInodeAttributeForkData ¶
ReadInodeAttributeForkData reads the inode attributes fork payload. For inline attributes, this returns the inline bytes; for extent/btree forks, it reconstructs data from mapped extents up to the attributes fork size.
func (*Volume) ReadInodeData ¶
ReadInodeData reads file data from an inode at offset. It supports inline data and extent-list backed regular files.
func (*Volume) ReportWithContext ¶
ReportWithContext builds a combined forensic report, honouring context cancellation.
Discovery of the inode set is sequential, because a directory must be read before its children are known. Per-inode analysis then runs across the pool configured by options.Concurrency. The result does not depend on the worker count: entries are stored by position and sorted before returning.
func (*Volume) ReportWithOptions ¶
func (v *Volume) ReportWithOptions(options ReportOptions) (*XFSReport, error)
ReportWithOptions builds a combined forensic report.
It is equivalent to ReportWithContext with a background context.
func (*Volume) ResolveInodeByPath ¶
ResolveInodeByPath resolves an absolute path to an inode number.
func (*Volume) ScanDirectoryRecords ¶
func (v *Volume) ScanDirectoryRecords(inodeNumber uint64) ([]DirectoryRecord, error)
ScanDirectoryRecords lists active and deleted directory records for an inode.
Records carrying IsCarved are probabilistic: see DirectoryRecord.Kind and IsProbabilistic before presenting them as fact.
func (*Volume) ScanDirectoryRecordsByPath ¶
func (v *Volume) ScanDirectoryRecordsByPath(path string) ([]DirectoryRecord, error)
ScanDirectoryRecordsByPath resolves a directory path and scans its records.
func (*Volume) ScanDirectoryRecordsByPathWithOptions ¶
func (v *Volume) ScanDirectoryRecordsByPathWithOptions(path string, options DirectoryScanOptions) (DirectoryListing, error)
ScanDirectoryRecordsByPathWithOptions resolves a path and scans it.
func (*Volume) ScanDirectoryRecordsWithOptions ¶
func (v *Volume) ScanDirectoryRecordsWithOptions(inodeNumber uint64, options DirectoryScanOptions) (DirectoryListing, error)
ScanDirectoryRecordsWithOptions scans a directory for active, free and carved records under explicit scan options.
func (*Volume) Superblock ¶
func (v *Volume) Superblock() Superblock
func (*Volume) UnlinkedInodes ¶ added in v0.3.1
func (v *Volume) UnlinkedInodes(ctx context.Context) ([]UnlinkedInode, []ReportAnomaly, error)
UnlinkedInodes returns every inode on an AGI unlinked chain, in allocation group, bucket and chain order.
These are files that were deleted while still open. They are allocated, intact and fully readable, and no directory refers to them, so a walk of the directory tree cannot find them. The returned anomalies describe any chain that could not be followed to its end.
func (*Volume) VerifyDirectoryIndex ¶
func (v *Volume) VerifyDirectoryIndex(inodeNumber uint64) (DirectoryIndexReport, error)
VerifyDirectoryIndex cross-checks a directory's hash index against its data blocks. Directories with no index report HasIndex false and are consistent by definition.
func (*Volume) VerifyDirectoryIndexByPath ¶
func (v *Volume) VerifyDirectoryIndexByPath(path string) (DirectoryIndexReport, error)
VerifyDirectoryIndexByPath resolves a path and verifies its hash index.
func (*Volume) VolumeIntegrityReport ¶
func (v *Volume) VolumeIntegrityReport() (VolumeIntegrityReport, error)
VolumeIntegrityReport builds a metadata/geometry report for the open volume.
func (*Volume) WalkInodeChunks ¶ added in v0.3.1
func (v *Volume) WalkInodeChunks(ctx context.Context, options InodeEnumerationOptions, fn func(InodeChunk) error) (InodeEnumeration, error)
WalkInodeChunks calls fn for every inode b-tree record on the volume, in allocation group then inode number order.
It is the streaming form of Volume.EnumerateAllocatedInodes: nothing is retained between calls, so a filesystem with hundreds of millions of inodes costs a bounded amount of memory. Returning an error from fn stops the walk and returns that error, with the partially populated result.
The returned InodeEnumeration carries the counts and anomalies but an empty Inodes, since the caller has already seen every record.
type VolumeError ¶
func (*VolumeError) Error ¶
func (e *VolumeError) Error() string
func (*VolumeError) Unwrap ¶
func (e *VolumeError) Unwrap() error
type VolumeIntegrityReport ¶
type VolumeIntegrityReport struct {
Type string `json:"type"`
FormatVersion uint8 `json:"format_version"`
BlockSize uint32 `json:"block_size"`
InodeSize uint16 `json:"inode_size"`
DirectoryBlockSize uint32 `json:"directory_block_size"`
RootDirectoryInodeNumber uint64 `json:"root_directory_inode"`
AllocationGroupSize uint32 `json:"allocation_group_size"`
NumberOfAllocationGroups uint32 `json:"number_of_allocation_groups"`
NumberOfBlocks uint64 `json:"number_of_blocks"`
VolumeLabel string `json:"volume_label,omitempty"`
SuperblockCRCChecked bool `json:"superblock_crc_checked"`
SuperblockCRCValid bool `json:"superblock_crc_valid"`
Anomalies []ReportAnomaly `json:"anomalies,omitempty"`
}
VolumeIntegrityReport summarizes core XFS geometry and validation findings.
type XFSReport ¶
type XFSReport struct {
GeneratedAt time.Time `json:"generated_at"`
RootPath string `json:"root_path"`
Provenance ReportProvenance `json:"provenance"`
Volume VolumeIntegrityReport `json:"volume"`
Files []InodeForensicReport `json:"files"`
DirectoryArtifacts []DirectoryArtifactReport `json:"directory_artifacts,omitempty"`
// Completeness is present when ReportOptions.IncludeInodeCompleteness asked
// for it. It is what turns Files from a list of what was found into a list
// with evidence that nothing else exists.
Completeness *InodeCompletenessReport `json:"completeness,omitempty"`
Anomalies []ReportAnomaly `json:"anomalies,omitempty"`
}
XFSReport is a combined volume + inode + directory-artifact report.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
basic
command
|
|
|
completeness
command
Command completeness reconciles a directory walk against every inode an XFS volume has allocated, and lists the ones no directory reaches.
|
Command completeness reconciles a directory walk against every inode an XFS volume has allocated, and lists the ones no directory reaches. |
|
dirscan
command
|
|
|
extract
command
|
|
|
forensics
command
|
|
|
fragmentation
command
|
|
|
inode_read
command
|
|
|
report
command
|
|
|
traverse
command
|
|
|
xattrs
command
|
|
|
tools
|
|
|
corpus/oracle
command
Command oracle walks a mounted filesystem and emits a canonical record for every path it contains.
|
Command oracle walks a mounted filesystem and emits a canonical record for every path it contains. |