libxfs

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 7, 2026 License: MIT Imports: 14 Imported by: 0

README

libxfs

Thread-safe, forensics-friendly XFS parsing in pure Go.

libxfs reads XFS volumes and disk images with strong validation, typed errors, and an API designed for tooling, incident response, and data recovery workflows.

Why libxfs

  • Pure Go parser with zero external runtime dependencies
  • Concurrency-safe volume APIs for parallel reads
  • Corruption-aware parsing with strict bounds checks
  • Typed errors with context (errors.Is / errors.As friendly)
  • Practical path-based and inode-based APIs for common workflows
  • Built-in support for fragmentation analysis and deleted directory artifact scanning

Installation

go get github.com/aoiflux/libxfs

Go Version

  • Requires Go 1.21+
  • Tested on Go 1.21 through current stable, on Linux, Windows and macOS

Quick Start

package main

import (
	"fmt"
	"log"

	"github.com/aoiflux/libxfs"
)

func main() {
	vol, err := libxfs.OpenVolumeFromPath("disk.img")
	if err != nil {
		log.Fatal(err)
	}
	defer vol.Close()

	sb := vol.Superblock()
	fmt.Printf("XFS v%d block=%d inode=%d root=%d\n",
		sb.FormatVersion,
		sb.BlockSize,
		sb.InodeSize,
		sb.RootDirectoryInodeNumber,
	)

	entries, err := vol.ListRootDirectoryEntries()
	if err != nil {
		log.Fatal(err)
	}
	for _, e := range entries {
		fmt.Printf("%s -> inode %d\n", e.Name, e.InodeNumber)
	}
}

Feature Support

Implemented:

  • Superblock parsing and validation, including the v5 feature words
  • 64-bit "bigtime" inode timestamps and 64-bit (nrext64) extent counters
  • AGI and inode-btree driven inode lookup
  • Inode parsing (v1/v2/v3 layouts)
  • Data reads from inline, extent-list, and extent-btree-backed forks
  • Attribute fork parsing and xattr listing
  • Short-form and block-based xattrs (including remote values)
  • Directory listing for every XFS directory layout:
    • short-form inline directories
    • block-format directories (XD2B / XDB3)
    • leaf, node and btree directories, walked across all data blocks
    • directory blocks larger than the filesystem block size (sb_dirblklog > 0)
  • Directory entry file types (ftype) and parent-inode recovery
  • Path resolution and file reads by absolute path
  • File extraction support via example tooling
  • Fragmentation analysis APIs
  • Directory forensics APIs (active entries, deleted slots, carved deleted candidates)

Current limitations:

  • Directory lookups walk data blocks linearly; the leaf/node hash index is not used to accelerate path resolution on very large directories
  • Deleted entry recovery uses heuristic carving and is explicitly labelled probabilistic output (see Directory Forensics below)
  • Write support is intentionally out of scope (read-only parsing library)

API Highlights

Volume-level:

  • Open(reader io.ReaderAt) (*Volume, error)
  • OpenVolumeFromPath(path string) (*Volume, error)
  • (*Volume).Close() error
  • (*Volume).Superblock() Superblock
  • (*Volume).GetRootInode() (*Inode, error)

Inode/path access:

  • (*Volume).OpenInode(inodeNumber uint64) (*Inode, error)
  • (*Volume).OpenInodeByPath(path string) (*Inode, error)
  • (*Volume).ResolveInodeByPath(path string) (uint64, error)

Directory and file reads:

  • (*Volume).ListDirectoryEntries(inodeNumber uint64) ([]DirectoryEntry, error)
  • (*Volume).ListDirectoryEntriesByPath(path string) ([]DirectoryEntry, error)
  • (*Volume).ListRootDirectoryEntries() ([]DirectoryEntry, error)
  • (*Volume).ListDirectoryEntriesWithOptions(inodeNumber uint64, options DirectoryScanOptions) (DirectoryListing, error)
  • (*Volume).DirectoryParentInode(inodeNumber uint64) (uint64, error)
  • (*Volume).DirectoryParentInodeByPath(path string) (uint64, error)
  • (*Volume).ReadInodeData(inodeNumber uint64, p []byte, off int64) (int, error)
  • (*Volume).ReadFileData(inodeNumber uint64) ([]byte, error)
  • (*Volume).ReadFileDataByPath(path string) ([]byte, error)

xattrs:

  • (*Volume).ListInodeExtendedAttributes(inodeNumber uint64) ([]ExtendedAttribute, error)

Forensics:

  • (*Volume).AnalyzeInodeFragmentation(inodeNumber uint64) (FragmentationReport, error)
  • (*Volume).AnalyzeInodeFragmentationByPath(path string) (FragmentationReport, error)
  • (*Volume).ScanDirectoryRecords(inodeNumber uint64) ([]DirectoryRecord, error)
  • (*Volume).ScanDirectoryRecordsByPath(path string) ([]DirectoryRecord, error)
  • (*Volume).ScanDirectoryRecordsWithOptions(inodeNumber uint64, options DirectoryScanOptions) (DirectoryListing, error)
  • (*Volume).ScanDirectoryRecordsByPathWithOptions(path string, options DirectoryScanOptions) (DirectoryListing, error)
  • (*Volume).VerifyDirectoryIndex(inodeNumber uint64) (DirectoryIndexReport, error)
  • (*Volume).VerifyDirectoryIndexByPath(path string) (DirectoryIndexReport, error)
  • (*Volume).VolumeIntegrityReport() (VolumeIntegrityReport, error)
  • (*Volume).InodeForensicReport(inodeNumber uint64) (InodeForensicReport, error)
  • (*Volume).InodeForensicReportByPath(path string) (InodeForensicReport, error)
  • (*Volume).DirectoryArtifactReport(inodeNumber uint64) (DirectoryArtifactReport, error)
  • (*Volume).DirectoryArtifactReportByPath(path string) (DirectoryArtifactReport, error)
  • (*Volume).Report() (*XFSReport, error)
  • (*Volume).ReportWithOptions(options ReportOptions) (*XFSReport, error)

Directory Forensics

ScanDirectoryRecords returns active entries, free-space runs and carved candidates together. Confidence alone is not a safe gate: an intact active entry and a strong carve candidate can both be high. Switch on Kind, or use the helpers.

Kind Meaning Safe to present as fact
RecordKindActive Parsed from intact directory framing Yes
RecordKindFreeSlot Reclaimed space; carries no name or inode number Not an entry at all
RecordKindCarved Pattern-matched inside reclaimed space No — candidate only
listing, err := vol.ScanDirectoryRecordsWithOptions(ino, libxfs.DirectoryScanOptions{
    BestEffort: true, // keep what parsed, report anomalies, instead of failing
})

for _, r := range listing.Records {
    switch {
    case r.IsVerified():
        // Fact: an active entry.
    case r.IsProbabilistic():
        // Candidate: may be stale, partial, or a coincidental byte pattern.
        // r.Confidence and r.ConfidenceReasons explain how strong the match is.
    }
}

ConfidenceReasons carries machine-readable evidence codes (for example tag_matches_offset, name_printable, aligned_offset, ftype_valid) so forensic output can show its reasoning rather than assert a verdict.

Every record is addressable within the directory stream via BlockIndex and LogicalOffset; Offset is only meaningful within its own directory block.

Directory index verification

Leaf and node format directories carry a hash index alongside their entries. The kernel maintains both together, so disagreement between them means something modified the directory without maintaining both.

report, err := vol.VerifyDirectoryIndex(ino)
if err == nil && report.HasIndex && !report.Consistent() {
    // report.MissingFromIndex, report.HashMismatches, report.DanglingIndexEntries
}

Directories with no index — short-form and single-block — report HasIndex false and are consistent by definition. Path resolution uses the index when it is present and falls back to a linear walk whenever it is absent, unreadable, or inconsistent; an index never reduces what can be recovered.

Damaged directories

The zero-value options are strict: any framing error aborts the scan. Set BestEffort to keep what was recovered from healthy blocks, resynchronise past damage, and collect ReportAnomaly entries describing what went wrong. MaxBlocks and MaxEntries bound the work; when a cap is reached the listing reports Truncated rather than silently looking complete.

Filesystem Features

v5 filesystems record feature flags that change the on-disk layout. libxfs reads them and adapts; an image carrying an incompatible feature it does not understand is refused rather than silently misparsed.

sb := vol.Superblock()
sb.HasBigTimestamps()     // 64-bit nanosecond timestamps (mkfs default since 2021)
sb.HasLargeExtentCounts() // nrext64: 64-bit extent counters
sb.NeedsRepair()          // filesystem was left needing repair; treat metadata with suspicion

Timestamps matter most here. A bigtime timestamp decoded as the legacy format yields a date roughly 27 years early, which is plausible enough to pass unnoticed, so Inode.HasBigTimestamps records which encoding was used.

Error Handling

libxfs uses wrapped typed errors so callers can reliably inspect failures.

if _, err := vol.ResolveInodeByPath("/nope"); err != nil {
	if errors.Is(err, libxfs.ErrInodeNotFound) {
		// path component not found
	}

	var pErr *libxfs.ParseError
	if errors.As(err, &pErr) {
		fmt.Printf("parse field=%s offset=%d\n", pErr.Field, pErr.Offset)
	}
}

Examples

See examples/README.md for full details.

Available examples:

  • examples/basic: open volume, show metadata, list root directory
  • examples/traverse: recursive traversal with depth control
  • examples/xattrs: list inode extended attributes
  • examples/inode_read: read bytes from inode data stream
  • examples/extract: extract a file by absolute XFS path
  • examples/fragmentation: report file fragmentation and logical holes
  • examples/forensics: inspect active/deleted directory records and carved candidates
  • examples/report: build structured forensic report output (JSON + summary)
  • examples/dirscan: scan a large or damaged directory with best-effort recovery, separating verified entries from carved candidates

Run one example:

cd examples/basic
go run . <xfs_volume_or_image>

Platform Notes

Raw volume access usually requires elevated privileges.

Windows:

  • Run terminal as Administrator
  • Use paths like \\.\\C: or \\.\\PhysicalDrive0

Linux:

  • Use block-device paths like /dev/sda1
  • Prefer read-only / forensic-safe acquisition workflows

Disk image files are supported on all platforms.

Development

Run checks:

go test ./...
go vet ./...

Conformance tests can be run against a real XFS image. They are skipped unless an image is supplied:

LIBXFS_TEST_IMAGE=/path/to/xfs.dd go test ./...

Synthetic fixtures only prove the parser agrees with its author's reading of the format. Testing against an image from mkfs.xfs is what catches a misunderstanding shared by both the parser and its fixtures.

Project Status

Core parsing features and forensic helper APIs are implemented and tested, including validation against real mkfs.xfs images, with ongoing hardening around malformed metadata and edge-case directory layouts.

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 four groups:

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, and 64-bit (nrext64) extent counters. 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

View Source
const (
	FileTypeFIFO            uint16 = 0x1000
	FileTypeCharacterDevice uint16 = 0x2000
	FileTypeDirectory       uint16 = 0x4000
	FileTypeBlockDevice     uint16 = 0x6000
	FileTypeRegularFile     uint16 = 0x8000
	FileTypeSymbolicLink    uint16 = 0xa000
	FileTypeSocket          uint16 = 0xc000
)
View Source
const (
	ForkTypeDevice     uint8 = 0
	ForkTypeInlineData uint8 = 1
	ForkTypeExtents    uint8 = 2
	ForkTypeBtree      uint8 = 3
)
View Source
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.

View Source
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.

View Source
const (
	DirectoryFormatShortForm  = "short_form"
	DirectoryFormatBlock      = "block"
	DirectoryFormatMultiBlock = "multi_block"
)

Directory layout names reported in DirectoryListing.Format.

View Source
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.

View Source
const (
	SeverityInfo    = "info"
	SeverityLow     = "low"
	SeverityMedium  = "medium"
	SeverityWarning = "warning"
	SeverityHigh    = "high"
	SeverityError   = "error"
)

Severity levels reported in ReportAnomaly.Severity.

View Source
const (
	InodeTypeFile      = "file"
	InodeTypeDirectory = "directory"
)

Inode type labels reported in InodeForensicReport.Type.

View Source
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.

View Source
const (
	ExtentFlagSparse uint32 = 0x00000001
)
View Source
const FilesystemTypeName = "xfs"

FilesystemTypeName is the filesystem label reported by VolumeIntegrityReport.

Variables

View Source
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")
)

Functions

func DirEntryFileTypeName

func DirEntryFileTypeName(fileType uint8) string

DirEntryFileTypeName returns a human readable name for an XFS directory entry file type value.

Types

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 DirectoryEntry struct {
	Name        string
	InodeNumber uint64
	// FileType is the XFS directory entry file type (DirEntryFileType*).
	// It is DirEntryFileTypeUnknown on filesystems without the ftype feature.
	FileType uint8
}

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.
	Format string `json:"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
}

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 ExtendedAttribute struct {
	Name      string
	Namespace string
	Value     []byte
	Flags     uint8
}

type Extent

type Extent struct {
	LogicalBlockNumber  uint64
	PhysicalBlockNumber uint64
	NumberOfBlocks      uint32
	RangeFlags          uint32
}

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 IOError

type IOError struct {
	Op     string
	Offset int64
	Size   int
	Err    error
}

func (*IOError) Error

func (e *IOError) Error() string

func (*IOError) Unwrap

func (e *IOError) Unwrap() error

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 (i *Inode) AccessTime() time.Time

func (*Inode) CreationTime

func (i *Inode) CreationTime() time.Time

func (*Inode) InodeChangeTime

func (i *Inode) InodeChangeTime() time.Time

func (*Inode) IsDirectory

func (i *Inode) IsDirectory() bool

func (*Inode) ModificationTime

func (i *Inode) ModificationTime() time.Time

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            bool   `json:"is_sparse"`
}

InodeFragment describes one extent from the inode data fork.

type InodeInformation

type InodeInformation struct {
	FormatVersion       uint32
	InodeBtreeRootBlock uint32
	InodeBtreeDepth     uint32
	LastAllocatedChunk  uint32
}

type ParseError

type ParseError struct {
	Offset int64
	Field  string
	Err    error
}

func (*ParseError) Error

func (e *ParseError) Error() string

func (*ParseError) Unwrap

func (e *ParseError) Unwrap() error

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
	// 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
}

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) 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 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

func Open(reader io.ReaderAt) (*Volume, error)

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

func OpenVolumeFromPath(path string) (*Volume, error)

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) 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

func (v *Volume) Close() error

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

func (v *Volume) DirectoryParentInode(inodeNumber uint64) (uint64, error)

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

func (v *Volume) DirectoryParentInodeByPath(path string) (uint64, error)

DirectoryParentInodeByPath resolves a directory path and returns its parent inode number.

func (*Volume) GetRootInode

func (v *Volume) GetRootInode() (*Inode, error)

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

func (v *Volume) IsClosed() bool

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.

func (*Volume) ListDirectoryEntriesByPath

func (v *Volume) ListDirectoryEntriesByPath(path string) ([]DirectoryEntry, error)

ListDirectoryEntriesByPath resolves a directory path and lists its entries.

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) OpenInode

func (v *Volume) OpenInode(inodeNumber uint64) (*Inode, error)

func (*Volume) OpenInodeByPath

func (v *Volume) OpenInodeByPath(path string) (*Inode, error)

OpenInodeByPath resolves an absolute path and opens the corresponding inode.

func (*Volume) ReadFileData

func (v *Volume) ReadFileData(inodeNumber uint64) ([]byte, error)

ReadFileData reads all data bytes from a non-directory inode.

func (*Volume) ReadFileDataByPath

func (v *Volume) ReadFileDataByPath(path string) ([]byte, error)

ReadFileDataByPath resolves an absolute path and reads all file data bytes.

func (*Volume) ReadInodeAttributeForkData

func (v *Volume) ReadInodeAttributeForkData(inodeNumber uint64) ([]byte, error)

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

func (v *Volume) ReadInodeData(inodeNumber uint64, p []byte, off int64) (int, error)

ReadInodeData reads file data from an inode at offset. It supports inline data and extent-list backed regular files.

func (*Volume) Report

func (v *Volume) Report() (*XFSReport, error)

Report builds a combined report from root path "/".

func (*Volume) ReportWithContext

func (v *Volume) ReportWithContext(ctx context.Context, options ReportOptions) (*XFSReport, error)

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

func (v *Volume) ResolveInodeByPath(path string) (uint64, error)

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) 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.

type VolumeError

type VolumeError struct {
	Op  string
	Err error
}

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"`
	Anomalies          []ReportAnomaly           `json:"anomalies,omitempty"`
}

XFSReport is a combined volume + inode + directory-artifact report.

func (*XFSReport) Summary

func (r *XFSReport) Summary() string

Summary returns a human-readable summary of the report.

Directories

Path Synopsis
examples
basic command
dirscan command
extract command
forensics command
fragmentation command
inode_read command
report command
traverse command
xattrs command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL