filesystem_xfs

package module
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Nov 1, 2026 License: BSD-3-Clause Imports: 19 Imported by: 4

README

go-filesystems/xfs

xfs

Go Reference CI

Pure-Go read/write access to XFS filesystem images — no root privileges, no external tools, no CGO.

Supports v5 XFS images (CRC32c metadata checksums, ftype). MBR/GPT partition tables are auto-detected.

References

https://docs.kernel.org/filesystems/xfs/index.html

Support summary

Feature Status Notes
Open / Close ✅ Supports v5 XFS images (ftype, CRC32c)
Format ✅ Creates XFS images
Grow / Resize ✅ Whole-AG and partial last-AG growth (xfs_growfs-style); shrink returns ErrShrinkUnsupported
Reflink (shared extents / COW) ✅ FormatConfig.Reflink; per-AG refcount B-tree, FS.Reflink clone, COW-aware delete/overwrite
Quotas (user / group / project) ✅ FormatConfig.Quota; classic quota inodes + dquots, kept consistent by an inode-scan quotacheck
ReadFile ✅ Full file reads supported, incl. NREXT64 (64-bit extent counts) images from modern mkfs.xfs
WriteFile ✅ Full file writes supported
MkDir / Delete / Rename ✅ Directory and rename operations supported
Stat / timestamps ✅ Legacy and BIGTIME (64-bit) timestamps, as written by modern mkfs.xfs
ReadLink / Symlinks ✅ Inline and remote (extent-based) targets, incl. the v5 xfs_dsymlink_hdr written by the kernel
Partitioned images ✅ MBR/GPT auto-detected
Extended metadata ✅ ExtendedStat (uid/gid, timestamps, link count, generation); Chown / Chmod / Chtimes / Truncate / Link (hardlinks)
Volume label ✅ Label / SetLabel
Layered backends ✅ OpenFromDevice(BlockBackend, partIndex) for LUKS/qcow2/in-memory callers that don't hand over an *os.File-backed image

All three advanced features above produce xfs_repair -n-clean images (validated against xfsprogs 6.13 in CI on native amd64/arm64 runners).

Advanced features

Format with FormatConfig{Reflink: true} to enable the XFS_SB_FEAT_RO_COMPAT_REFLINK feature bit and a per-AG reference-count B-tree (refcountbt, matching mkfs.xfs -m reflink=1,rmapbt=0). FS.Reflink(src, dst) then clones a file so it shares physical extents; deleting or overwriting a reflinked file is copy-on-write (the shared extents' reference counts are decremented and blocks are freed only when no longer shared).

Quotas

Format with FormatConfig{Quota: QuotaConfig{User, Group, Project, Enforce}} to create the classic (non-metadir) quota inodes (sb_uquotino/sb_gquotino/ sb_pquotino), seed their dquot clusters, and set sb_qflags + the XFS_SB_VERSION_QUOTABIT. Block/inode accounting is kept consistent after every mutating operation by a full inode-scan quotacheck.

Limitations

  • Snapshots are an LVM/device-mapper concern, not an XFS on-disk feature, and are out of scope for this filesystem-format library.
  • Residuals in the advanced features: the reference-count B-tree is single-level (a share touching more distinct extents than fit one root block returns an error rather than growing the tree); growing a filesystem whose current last AG is already partial (in-place extension of that AG) is not supported — the last AG must be full before the next grow; quota files are a single dquot block, so identities beyond ~30 (one block of dquots) are not tracked.
  • Intended for testing and tooling; not recommended for production workloads.

Untrusted input

An image is attacker-controlled bytes. Every allocation and walk the driver sizes from a value read off the image is checked against what the image can hold before it happens (tests assert the allocation or the device-read count for each refused road):

Input Bound
di_size for ReadFile / remote symlinks the smallest of di_nblocks (+1 block), the superblock's sb_dblocks, and DefaultMaxReadSize (1 GiB; fs.(interface{ SetMaxReadSize(int64) }) changes it). di_nblocks and sb_dblocks are themselves read from the image, so the read limit is what finally bounds the buffer; refused with ErrTooLarge
bmbt extent records (in-inode and B-tree) records that overlap, in file offset or in physical blocks, are refused: each would make a reader read the same blocks again (8 overlapping records read the file 8 times)
B-tree leaf numrecs capped by the records the block actually holds
allocation groups the last AG must start inside the device; per-AG loops (free-space scans, superblock copies, inode allocation) run once per agcount, a 32-bit field of the image
inline source of a reflink di_size must fit the data fork
B-tree descent and sibling walks depth = level, 65536 leaves, no block visited twice (unchanged)

Fuzz targets: FuzzReadFileBomb, FuzzValidateExtents (allocation and device reads stay proportional to the input; accepted extent lists never overlap), plus the existing FuzzReadSuperblock, FuzzReadExtents, FuzzBtreeExtents, FuzzCntFindBlock, FuzzReadSymlink, FuzzPartitionOffset.

Module

github.com/go-filesystems/xfs

Supported operations

Operation Status
Open / Close ✅ implemented
Format ✅ implemented
Stat ✅ implemented
ListDir ✅ implemented
ReadFile ✅ implemented
WriteFile ✅ implemented
MkDir ✅ implemented
DeleteFile ✅ implemented
DeleteDir ✅ implemented
Rename ✅ implemented
ReadLink ✅ implemented
Reflink ✅ implemented
Grow / Resize ✅ implemented

API

FS is an interface (not a struct) — Format, Open, and OpenFromDevice all return FS, and every method below is called through that interface value (fs.Reflink(...), not (*FS).Reflink).

Format / Open
type FormatConfig struct {
    UUID    [16]byte // zero = randomly generated
    Label   string
    Reflink bool        // enable shared-extent (COW) support
    Quota   QuotaConfig // enable user/group/project quota accounting
}

type QuotaConfig struct {
    User, Group, Project bool // which quota types to account
    Enforce              bool // also set the *_ENFD (limit enforcement) flags
}

func Format(path string, sizeBytes int64, cfg FormatConfig) (FS, error)
func Open(imagePath string, partIndex int) (FS, error)
func OpenFromDevice(dev BlockBackend, partIndex int) (FS, error) // layered backends (LUKS/qcow2/in-memory)
FS interface
type FS interface {
    filesystem.Filesystem // Close, ReadFile, ListDir, Stat, WriteFile, ReadLink,
                           // MkDir, DeleteFile, DeleteDir, Rename

    Grow(newSizeBytes int64) error   // extend to newSizeBytes, whole-AG-aligned
    Resize(newSizeBytes int64) error // routes to Grow; shrink returns filesystem.ErrShrinkUnsupported
    GrowTo(newSizeBytes int64) error // alias of Grow (filesystem.Grower)

    ExtendedStat(path string) (*InodeStat, error) // uid/gid, timestamps, link count, generation
    Chown(path string, uid, gid uint32) error
    Chmod(path string, perm os.FileMode) error
    Chtimes(path string, atime, mtime time.Time) error
    Symlink(target, linkPath string) error
    Truncate(path string, newSize int64) error
    Link(oldPath, newPath string) error

    // Reflink clones srcPath into a new file dstPath sharing srcPath's extents
    // (requires FormatConfig.Reflink); HasReflink reports the feature bit.
    Reflink(srcPath, dstPath string) error
    HasReflink() bool

    Label() string
    SetLabel(label string) error
}

Implements

This package implements the filesystem.Filesystem contract defined in the github.com/go-filesystems/interface module. Use the interface to write generic tooling that works with other filesystem implementations in this repository. Example:

import (
    filesystem     "github.com/go-filesystems/interface"
    filesystem_xfs "github.com/go-filesystems/xfs"
)

f, _ := filesystem_xfs.Open("image.img", -1)
defer f.Close()
var fs filesystem.Filesystem = f
_, _ = fs.ReadFile("/hello.txt")

Documentation

Overview

Package xfs provides read/write access to XFS filesystem images without requiring root privileges or external tools. It targets XFS v5 (CRC-enabled superblock, v3 inodes) as used by Rocky Linux 9, AlmaLinux 9, and other RHEL-family distributions. Version 4 filesystems are also supported for reading.

Partition tables (MBR/GPT) are detected automatically; pass partIndex = -1 to auto-select the first Linux data partition.

Supported operations:

  • ReadFile, ListDir, Stat — complete for all directory formats.
  • WriteFile — creates or overwrites regular files. For existing files the data is rewritten in-place when the new content fits the current block allocation; larger rewrites allocate additional blocks. New files are created by allocating an inode and data blocks via the AG free-space B-trees and inserting an entry in the parent directory.
  • DeleteFile — removes a file and frees its inode and data blocks.

Index

Constants

View Source
const DefaultMaxReadSize int64 = 1 << 30

DefaultMaxReadSize is the largest file ReadFile will materialise in memory unless SetMaxReadSize says otherwise: 1 GiB.

di_size, di_nblocks and the superblock's sb_dblocks all come from the image, so none of them can bound an allocation by itself: an inode forged to nblocks = 2^40 "owns" enough blocks for any size, and a superblock may declare a filesystem as large as it likes. Only the caller knows how much memory it is willing to spend, so there is a documented ceiling.

View Source
const MaxLabelLen = 12

MaxLabelLen is the on-disk size of the XFS volume label (sb_fname, at superblock offset 108).

Variables

View Source
var ErrBlockOutOfRange = fmt.Errorf("xfs: block offset out of range")

ErrBlockOutOfRange is returned when a block number's byte offset would overflow int64 (a forged extent/btree pointer).

View Source
var ErrNotFound = errors.New("not found")

ErrNotFound is returned when a path component is not found.

View Source
var ErrSymlinkLoop = errors.New("xfs: too many levels of symbolic links")

ErrSymlinkLoop is returned when symlink resolution exceeds maxSymlinkDepth.

View Source
var ErrTooLarge = safeio.ErrTooLarge

ErrTooLarge is returned, wrapped, when an on-disk size or count would make the driver allocate or walk more than the image can legitimately hold. It is safeio.ErrTooLarge, so errors.Is works against either name.

Functions

This section is empty.

Types

type BlockBackend

type BlockBackend = blockBackend

BlockBackend is the exported alias of blockBackend, so external packages can implement it and feed instances to OpenFromDevice. Same shape as ext4.BlockDevice and zfs.BlockBackend — keeping the interface uniform across go-filesystems lets a single adapter (e.g. luksAsBlock) serve every FS.

type DirEntry

type DirEntry struct {
	Inode    uint64
	Name     string
	FileType uint8
}

DirEntry is a parsed directory entry.

type FS

type FS interface {
	filesystem.Filesystem
	// Grow is the canonical "extend to newSizeBytes" entry point. It
	// requires newSizeBytes to be at least the current filesystem size
	// and to align to a whole number of allocation groups (each AG =
	// sb_agblocks × blockSize). Equal sizes are a no-op.
	Grow(newSizeBytes int64) error
	// Resize routes the uniform filesystem.Resizer call to Grow when
	// expanding; shrink is intentionally not supported in XFS (matches
	// the kernel + xfsprogs semantics) and returns
	// filesystem.ErrShrinkUnsupported when newSizeBytes < current.
	Resize(newSizeBytes int64) error
	// GrowTo is the historical name kept for callers that target the
	// filesystem.Grower interface. It is a thin alias of Grow.
	GrowTo(newSizeBytes int64) error

	ExtendedStat(path string) (*InodeStat, error)
	Chown(path string, uid, gid uint32) error
	Chmod(path string, perm os.FileMode) error
	Chtimes(path string, atime, mtime time.Time) error
	Symlink(target, linkPath string) error
	Truncate(path string, newSize int64) error
	Link(oldPath, newPath string) error
	// Reflink clones srcPath into a new file dstPath that shares srcPath's
	// data extents copy-on-write (the equivalent of `cp --reflink`). It
	// requires the filesystem to have been formatted with reflink support
	// (FormatConfig.Reflink); dstPath must not already exist.
	Reflink(srcPath, dstPath string) error
	// HasReflink reports whether the filesystem carries the reflink feature.
	HasReflink() bool
	Label() string
	SetLabel(label string) error
}

FS is the public interface returned by Open. Extends filesystem.Filesystem with the XFS-specific operations — image resize (Grow/Resize/GrowTo) plus inode metadata setters and the rich ExtendedStat reader.

func Format

func Format(path string, sizeBytes int64, cfg FormatConfig) (FS, error)

Format creates a new XFS v5 filesystem in the file at path. sizeBytes must be a multiple of 4096 and at least 64 MiB (one AG). On success the newly formatted filesystem is opened and returned.

func Open

func Open(imagePath string, partIndex int) (FS, error)

Open opens an XFS filesystem image, auto-detecting the partition table and selecting the first Linux data partition. Pass partIndex ≥ 0 to pick a specific (0-based) partition entry; pass -1 for auto-detection.

func OpenFromDevice

func OpenFromDevice(dev BlockBackend, partIndex int) (FS, error)

OpenFromDevice opens an XFS filesystem backed by an arbitrary blockBackend. Layered callers (LUKS / qcow2 / in-memory) feed the FS without an *os.File-backed image.

type FormatConfig

type FormatConfig struct {
	// UUID is the filesystem UUID. A random UUID is generated when all bytes are zero.
	UUID [16]byte
	// Label is the volume label (up to 12 bytes; silently truncated).
	Label string
	// Reflink enables shared-extent (copy-on-write) support: the
	// XFS_SB_FEAT_RO_COMPAT_REFLINK feature bit plus an empty per-AG refcount
	// B-tree, matching mkfs.xfs -m reflink=1,rmapbt=0. Files can then be cloned
	// with FS.Reflink so they share physical blocks.
	Reflink bool
	// Quota, when non-zero, enables on-disk quota accounting: the requested
	// quota inodes (user/group/project) are created and sb_qflags is set.
	// See QuotaConfig. The zero value leaves quotas disabled.
	Quota QuotaConfig
}

FormatConfig holds optional parameters for Format.

type InodeStat

type InodeStat struct {
	Inode      uint64
	Mode       uint16
	Size       uint64
	NBlocks    uint64 // di_nblocks: count of filesystem blocks allocated
	NLink      uint32
	UID        uint32
	GID        uint32
	Generation uint32
	ATime      time.Time
	CTime      time.Time
	MTime      time.Time
	CRTime     time.Time // v3 only — birth time; zero on v1/v2 inodes
}

InodeStat is the rich metadata view of an XFS inode. The common filesystem.Stat only exposes mode/size/inode; callers that want uid/gid, timestamps, link count or the inode generation number use ExtendedStat.

All timestamps are returned in UTC.

func (*InodeStat) IsDir

func (s *InodeStat) IsDir() bool

func (*InodeStat) IsRegular

func (s *InodeStat) IsRegular() bool

IsRegular / IsDir / IsSymlink mirror the on-disk mode predicates.

func (s *InodeStat) IsSymlink() bool

type QuotaConfig

type QuotaConfig struct {
	User    bool // user quota accounting (sb_uquotino)
	Group   bool // group quota accounting (sb_gquotino)
	Project bool // project quota accounting (sb_pquotino)
	Enforce bool // also enforce limits (set *_ENFD flags)
}

QuotaConfig selects which quota types a Format enables. The zero value leaves quotas off. Enforce additionally sets the *_ENFD (limit enforcement) flags; without it only accounting (*_ACCT) plus the *_CHKD "counts verified" flags are set, which is the state of a freshly quota-checked filesystem.

type Stat

type Stat struct {
	Mode  uint16
	Size  uint64
	Inode uint64
}

Stat holds basic metadata for a filesystem path.

Directories

Path Synopsis
benchmarks module

Jump to

Keyboard shortcuts

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