filesystem_btrfs

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: 20 Imported by: 5

README

go-filesystems/btrfs

filesystem-btrfs

Go Reference CI

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

Supports single-device Btrfs images with CRC32c metadata checksums (btrfs-progs ≥ 5.x). MBR/GPT partition tables are auto-detected.

References

https://btrfs.readthedocs.io/en/latest/

Support summary

Feature Status Notes
Open / Close ✅ Single-device images; OpenFromDevice/OpenFromDevices for layered/multi-device backends
Format ✅ Creates a new single-device Btrfs image
ReadFile / WriteFile ✅ Full file I/O supported
MkDir / Delete / Rename ✅ Directory and rename operations supported
ReadLink / Symlinks ✅ Read + create (FS.Symlink)
Hardlinks ✅ FS.Link
Xattrs ✅ Xattrs / GetXattr / SetXattr / RemoveXattr
Extended metadata ✅ ExtendedStat (uid/gid, timestamps, nlink, nbytes, transid/sequence, flags); Chown / Chmod / Chtimes / Truncate
Volume label ✅ Label / SetLabel
Subvolumes / snapshots ✅ read-only Subvolumes enumerates ROOT_TREE entries; OpenSubvolumeByID/OpenSubvolumeByName open one read-only. Creating a subvolume or snapshot is not supported (needs ref-counted extent backrefs)
Multi-device / RAID (RAID0/1/10/5/6/DUP) ✅ read-only Decoded via OpenFromDevices; writes are single-device only
Grow / Shrink / Resize ✅ Shrink refuses to discard live data; requires an idle filesystem (no concurrent writers during resize)
Partitioned images ✅ MBR/GPT auto-detected

Limitations

  • Subvolume/snapshot creation and send/receive are not implemented (read-only support exists — see above).
  • Quotas and reflink are not implemented.
  • Multi-device/RAID writes are not implemented (reading multi-device pools is).
  • No online device add/remove or balance operations.
  • Intended for testing and tooling; not recommended for production use.

Untrusted input

An image is attacker-controlled bytes. Every allocation, walk and recursion the driver sizes from a value read off the image is checked against what the format can produce before it happens (tests assert the allocation or the device-read count for each refused road). Bounds that come from the kernel source are cited in harden.go:

Input Bound
compressed extent (ram_bytes, zlib / LZO / zstd) 128 KiB: the kernel's BTRFS_MAX_UNCOMPRESSED (fs/btrfs/compression.h); LZO output capacity follows the input (one page per 5 input bytes), not the declared size
zstd frame window 128 KiB: the window the kernel decodes btrfs extents with (ZSTD_BTRFS_MAX_WINDOWLOG, fs/btrfs/zstd.c); a frame declaring more is refused
file extent items must not overlap (check_extent_data_item, fs/btrfs/tree-checker.c): an overlapping leaf cost items x file size to read
chunk stripe_len (RAID0/5/6/10) exactly 64 KiB (check_chunk_item): stripe_len = 1 was one device read and one stack frame per byte
file size, tree nodes, tree depth device size, 2^20 nodes, 64 levels (unchanged)

Fuzz targets: FuzzDecompressExtent, FuzzStripedRead (allocation and device-read count stay proportional to the input), plus FuzzMountImage, FuzzReadFileData, FuzzMountPatched.

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

API

FS is an interface (not a struct) — Open/OpenFromDevice/OpenFromDevices return FS; Format returns the narrower filesystem.Filesystem. Every method below is called through the interface value (fs.Symlink(...), not (*FS).Symlink).

Format / Open
type FormatConfig struct {
    UUID  [16]byte // zero = randomly generated
    Label string    // up to 255 bytes, NUL-padded on disk
}

func Format(path string, sizeBytes int64, cfg FormatConfig) (filesystem.Filesystem, error)
func Open(imagePath string, partIndex int) (FS, error)
func OpenFromDevice(dev BlockBackend, partIndex int) (FS, error)
func OpenFromDevices(devs []BlockBackend, partIndex int) (FS, error) // multi-device RAID, read-only
FS interface
type FS interface {
    filesystem.Filesystem // Close, ReadFile, ListDir, Stat, WriteFile, ReadLink,
                           // MkDir, DeleteFile, DeleteDir, Rename

    Link(oldPath, newPath string) error
    Symlink(target, linkPath string) error

    Xattrs(path string) (map[string][]byte, error)
    GetXattr(path, name string) ([]byte, error)
    SetXattr(path, name string, value []byte) error
    RemoveXattr(path, name string) 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
    Truncate(path string, newSize int64) error

    Label() string
    SetLabel(label string) error

    // Subvolume / snapshot read support (creation is not supported).
    Subvolumes() ([]Subvolume, error)
    OpenSubvolumeByID(id uint64) (filesystem.Filesystem, error)
    OpenSubvolumeByName(name string) (filesystem.Filesystem, error)

    // Filesystem-level resize. Requires an idle FS (no concurrent writers).
    Grow(newSizeBytes int64) error
    Shrink(newSizeBytes int64) error
    Resize(newSizeBytes int64) error
}

Integration test

Set integrationImagePath in btrfs_test.go and run (from the repo root):

go test -v -run TestOpen_Integration .

Implements

This package implements the filesystem.Filesystem contract from github.com/go-filesystems/interface. Use the interface in higher-level tools to operate on multiple filesystem backends interchangeably.

Example:

import (
    filesystem "github.com/go-filesystems/interface"
    fsb "github.com/go-filesystems/btrfs"
)

f, _ := fsb.Open("btrfs.img", -1)
defer f.Close()
var fs filesystem.Filesystem = f
_, _ = fs.ReadFile("/data")

Documentation

Overview

Package btrfs provides read/write access to Btrfs filesystem images. It targets the common Btrfs on-disk format (single device, CRC32c checksums) as used by Fedora Cloud images.

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

Supported operations:

  • Open, Close, Format (single device)
  • ReadFile, ListDir, Stat, ReadLink
  • WriteFile, MkDir, DeleteFile, DeleteDir, Rename
  • Symlink, Link, Truncate
  • Chmod, Chown, Chtimes, get/set volume label, get/set xattrs
  • Grow / Resize (shrink trims free trailing space only)

Multi-device profiles (RAID0/1/10/5/6/DUP) are decoded for reading; writes are single-device.

Package btrfs provides read/write access to Btrfs filesystem images without requiring root privileges or external tools. It targets the Btrfs on-disk format as used by Fedora Cloud images (single device, CRC32c checksums).

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

All read and write operations are fully implemented.

Index

Constants

View Source
const MaxLabelLen = 255

MaxLabelLen is the on-disk size of the btrfs volume label (sb_fname / sbfLabel, 256 bytes). The kernel imposes an additional limit of 255 (to leave room for a trailing NUL), so we mirror that.

Variables

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

ErrNotFound is returned when a path component cannot be located.

Functions

func Format

func Format(path string, sizeBytes int64, cfg FormatConfig) (filesystem.Filesystem, error)

Format creates a new Btrfs filesystem in the file at path. The file is created (or truncated) and formatted. sizeBytes must be at least 1 MiB and a multiple of 4096.

On success the newly formatted filesystem is opened and returned; the caller must Close it when done.

Types

type BlockBackend

type BlockBackend = blockBackend

BlockBackend is the exported alias of blockBackend — uniform across go-filesystems/* so a single adapter (e.g. cloud-boot init's luksAsBlock) serves every FS.

type FS

type FS interface {
	filesystem.Filesystem

	Link(oldPath, newPath string) error
	Symlink(target, linkPath string) error

	Xattrs(path string) (map[string][]byte, error)
	GetXattr(path, name string) ([]byte, error)
	SetXattr(path, name string, value []byte) error
	RemoveXattr(path, name string) 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
	Truncate(path string, newSize int64) error

	Label() string
	SetLabel(label string) error

	// Subvolume / snapshot READ support. Subvolumes enumerates the subvolumes
	// and snapshots recorded in the ROOT_TREE (including the default FS_TREE,
	// id 5). OpenSubvolumeBy{ID,Name} return a read-only filesystem.Filesystem
	// rooted at the named subvolume/snapshot tree so ReadFile / ListDir / Stat
	// / ReadLink operate within it. Creation of subvolumes/snapshots is not
	// supported (it needs ref-counted extent backrefs).
	Subvolumes() ([]Subvolume, error)
	OpenSubvolumeByID(id uint64) (filesystem.Filesystem, error)
	OpenSubvolumeByName(name string) (filesystem.Filesystem, error)

	// Filesystem-level resize. Grow extends; Shrink reduces (refuses to
	// discard live data); Resize dispatches to whichever direction the
	// new size implies (no-op when equal). All three require an idle
	// FS — concurrent writers during a resize aren't safe.
	Grow(newSizeBytes int64) error
	Shrink(newSizeBytes int64) error
	Resize(newSizeBytes int64) error
}

FS is the public interface returned by Open. It extends the common filesystem.Filesystem with btrfs-specific operations — hardlinks / symlinks creation, xattrs read+write, rich inode stat, ownership / permission / timestamp setters, truncate.

Callers that want to reach the btrfs-only operations should consume this interface type (or a type-assert from the common Filesystem) rather than the concrete *btrfsFS.

func Open

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

Open opens a Btrfs filesystem image at imagePath, auto-detecting the partition table (MBR/GPT) and selecting the first Linux partition. Pass partIndex = -1 for auto-detection.

func OpenFromDevice

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

OpenFromDevice opens a Btrfs filesystem backed by an arbitrary blockBackend. Layered callers (LUKS / qcow2 / in-memory) feed the FS without an *os.File-backed image. Same shape as the ext4 / xfs / zfs equivalents.

func OpenFromDevices

func OpenFromDevices(devs []BlockBackend, partIndex int) (FS, error)

OpenFromDevices opens a multi-device Btrfs filesystem. The first element of `devs` is treated as the "primary" — its superblock is read, its dev_item.devid drives the local-stripe selection used by the legacy single-leg fast path, and its partition table (if any) determines partOffset. Additional devices in the slice are added to the pool by their dev_item.devid (read from each device's superblock) so that RAID0 / RAID10 / RAID5 / RAID6 chunks can be served.

For SINGLE / DUP / RAID1 / RAID1Cn filesystems passing just the primary works (single-leg open via dev_item.devid matching). RAID0 and the parity profiles need at least the data-bearing devices present in the slice; missing data devices surface as a clear error at read time.

btrfs uses the on-disk fsid to bind a multi-device pool together; callers are responsible for grouping legs that share the same fsid (cloud-boot-init scans /sys/block for matching fsids).

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 255 bytes, NUL-padded on disk).
	Label string
}

FormatConfig holds optional parameters for Format. All fields are optional; sensible defaults are used when left at their zero value.

type InodeStat

type InodeStat struct {
	Inode      uint64
	Mode       uint16
	Size       uint64
	NBytes     uint64
	NLink      uint32
	UID        uint32
	GID        uint32
	Generation uint64
	TransID    uint64
	Sequence   uint64
	Flags      uint64
	ATime      time.Time
	CTime      time.Time
	MTime      time.Time
	OTime      time.Time
}

InodeStat is the full metadata view of a btrfs INODE_ITEM. The common filesystem.Filesystem interface only exposes mode/size/inode via Stat; callers that need the rich btrfs picture (uid/gid, timestamps, nlink, nbytes, transid/sequence, flags) use ExtendedStat instead.

All four timestamp fields are returned in UTC.

func (*InodeStat) IsDir

func (s *InodeStat) IsDir() bool

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

func (*InodeStat) IsRegular

func (s *InodeStat) IsRegular() bool
func (s *InodeStat) IsSymlink() bool

type Subvolume

type Subvolume struct {
	ID         uint64 // ROOT_ITEM objectid (subvolume / tree id)
	ParentID   uint64 // parent subvolume id (0 for the FS_TREE / unreferenced roots)
	Name       string // name within the parent's directory (empty for FS_TREE)
	RootBytenr uint64 // logical address of this tree's root node
	Generation uint64 // root_item.generation
}

Subvolume describes one subvolume or snapshot enumerated from the ROOT_TREE.

A snapshot is indistinguishable from a subvolume at this layer: both are ROOT_ITEMs in the ROOT_TREE pointing at an fs-tree root node. Name and ParentID come from the corresponding ROOT_REF; the top-level FS_TREE (id 5) has no ROOT_REF and is reported with an empty Name and ParentID 0.

Directories

Path Synopsis
benchmarks module

Jump to

Keyboard shortcuts

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