Documentation
¶
Overview ¶
Package inode provides block storage abstractions for filesystem implementations.
This file defines the BlockStore interfaces that enable different storage strategies ranging from simple byte arrays to sophisticated extent-based allocation.
Package inode provides thread-safe inode data structures for implementing in-memory filesystems.
Thread Safety ¶
All exported methods are safe for concurrent use by multiple goroutines. The package uses a hybrid approach to minimize lock contention:
Lock-Free Operations (using sync/atomic):
- Inode number allocation (Ino.New, Ino.NewDir)
- Link count updates (Nlink field)
- Timestamp updates (Ctime, Atime, Mtime via accessor methods)
RWMutex-Protected Operations:
- Directory operations (Link, Unlink, Resolve, Rename, ReadDir, Lookup)
The Rename operation uses lock ordering by pointer address to prevent deadlocks when modifying multiple directories.
Direct Field Access ¶
For callers that need to access Inode fields directly, Lock/Unlock and RLock/RUnlock methods are provided:
node.RLock() entries := node.Dir // Safe to read node.RUnlock() node.Lock() node.Size = 1024 // Safe to write node.Unlock()
However, prefer using the thread-safe methods (ReadDir, Lookup, etc.) when possible.
Usage Example ¶
var ino inode.Ino
root := ino.NewDir(0755)
// Create a file
file := ino.New(0644)
root.Link("hello.txt", file)
// Create a subdirectory
subdir := ino.NewDir(0755)
root.Link("subdir", subdir)
subdir.Link("..", root)
// Resolve a path
node, err := root.Resolve("/subdir")
Index ¶
- func Abs(cwd, name string) string
- func BlockCount(size int64, blockSize int) int64
- func BlockOffset(byteOffset int64, blockSize int) (block uint64, offset int64)
- func BlockRange(start, length int64, blockSize int) (firstBlock uint64, numBlocks uint64)
- func PopPath(path string) (string, string)
- func Walk(node *Inode, path string, fn func(path string, n *Inode) error) error
- type BlockChainStore
- type BlockSize
- type BlockStoreReadWriter
- type BlockStoreReader
- type BlockStoreWriter
- type ByteStore
- type COWMemExtentStore
- type COWStore
- type DirEntry
- type Directory
- type Extent
- type ExtentList
- type ExtentStore
- type Ino
- type Inode
- func (n *Inode) Atime() time.Time
- func (n *Inode) Ctime() time.Time
- func (n *Inode) IsDir() bool
- func (n *Inode) Link(name string, child *Inode) error
- func (n *Inode) Lock()
- func (n *Inode) Lookup(name string) *DirEntry
- func (n *Inode) Mtime() time.Time
- func (n *Inode) RLock()
- func (n *Inode) RUnlock()
- func (n *Inode) ReadDir() []*DirEntry
- func (n *Inode) Rename(oldpath, newpath string) error
- func (n *Inode) Resolve(path string) (*Inode, error)
- func (n *Inode) SetAtime(t time.Time)
- func (n *Inode) SetCtime(t time.Time)
- func (n *Inode) SetMtime(t time.Time)
- func (n *Inode) String() string
- func (n *Inode) Unlink(name string) error
- func (n *Inode) UnlinkAll()
- func (n *Inode) Unlock()
- type MemBlockChainStore
- func (s *MemBlockChainStore) AllocBlock() (uint64, error)
- func (s *MemBlockChainStore) BlockSize() int
- func (s *MemBlockChainStore) FreeBlock(blockID uint64) error
- func (s *MemBlockChainStore) GetBlockChain(ino uint64) ([]uint64, error)
- func (s *MemBlockChainStore) ReadBlock(blockID uint64) ([]byte, error)
- func (s *MemBlockChainStore) SetBlockChain(ino uint64, blockIDs []uint64) error
- func (s *MemBlockChainStore) WriteBlock(blockID uint64, data []byte) error
- type MemByteStore
- func (s *MemByteStore) ReadAt(ino uint64, p []byte, off int64) (int, error)
- func (s *MemByteStore) Remove(ino uint64) error
- func (s *MemByteStore) Stat(ino uint64) (int64, error)
- func (s *MemByteStore) Truncate(ino uint64, size int64) error
- func (s *MemByteStore) WriteAt(ino uint64, p []byte, off int64) (int, error)
- type MemExtentStore
- func (s *MemExtentStore) AllocExtent(desiredLength uint64) (uint64, uint64, error)
- func (s *MemExtentStore) BlockSize() int
- func (s *MemExtentStore) FreeExtent(physical uint64, length uint64) error
- func (s *MemExtentStore) GetExtents(ino uint64) (ExtentList, error)
- func (s *MemExtentStore) ReadExtent(physical uint64, length uint64) ([]byte, error)
- func (s *MemExtentStore) SetExtents(ino uint64, extents ExtentList) error
- func (s *MemExtentStore) WriteExtent(physical uint64, length uint64, data []byte) error
- type Stat
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Abs ¶
Abs returns name if name is an absolute path. If name is a relative path then an absolute path is constructed by using cwd as the current working directory.
func BlockCount ¶ added in v0.9.0
BlockCount returns the number of blocks needed to store size bytes.
func BlockOffset ¶ added in v0.9.0
BlockOffset returns the block number and offset within block for a byte offset.
Example with 4096-byte blocks:
block, offset := BlockOffset(5000, 4096) // returns (1, 904)
func BlockRange ¶ added in v0.9.0
BlockRange returns the range of blocks that contain the byte range [start, start+length).
Returns the first block number and the number of blocks in the range.
Example with 4096-byte blocks:
first, count := BlockRange(5000, 3000, 4096) // returns (1, 2) // Covers bytes 5000-7999, which spans blocks 1-2
Types ¶
type BlockChainStore ¶ added in v0.9.0
type BlockChainStore interface {
// BlockSize returns the fixed block size used by this store.
BlockSize() int
// AllocBlock allocates a new block and returns its unique ID.
// The block's initial contents are undefined (may be zeros or garbage).
AllocBlock() (blockID uint64, err error)
// FreeBlock releases a block for reuse.
// The block's contents become undefined after freeing.
// Freeing a block that is already free should not return an error.
FreeBlock(blockID uint64) error
// ReadBlock reads the entire contents of a block.
// Returns a byte slice of length BlockSize().
// Reading a freed or never-allocated block returns undefined data.
ReadBlock(blockID uint64) (data []byte, err error)
// WriteBlock writes the entire contents of a block.
// The data length must equal BlockSize().
// Writing to a freed block is an error.
WriteBlock(blockID uint64, data []byte) error
// GetBlockChain returns the ordered list of block IDs for an inode.
// For sparse files, unallocated blocks are represented as ID 0.
// An empty or non-existent file returns an empty slice.
GetBlockChain(ino uint64) (blockIDs []uint64, err error)
// SetBlockChain updates the block chain for an inode.
// This atomically replaces the entire block chain.
// Block IDs of 0 represent holes (sparse regions).
SetBlockChain(ino uint64, blockIDs []uint64) error
}
BlockChainStore provides fixed-size block storage with chaining.
Files are represented as sequences of fixed-size blocks, potentially with gaps (sparse files). This model enables incremental updates and efficient handling of large files with random access patterns.
Block IDs are opaque uint64 values. A block ID of 0 represents a hole (unallocated block) in sparse files.
Implementations should be safe for concurrent use by multiple goroutines.
type BlockSize ¶ added in v0.9.0
type BlockSize int
BlockSize represents a fixed block size in bytes.
const ( // BlockSize4K is the standard 4 KiB block size used by most filesystems. BlockSize4K BlockSize = 4096 // BlockSize8K is used by some filesystems for better performance. BlockSize8K BlockSize = 8192 // BlockSize16K is used by filesystems optimized for larger files. BlockSize16K BlockSize = 16384 // BlockSize64K is used for very large files or specific workloads. BlockSize64K BlockSize = 65536 )
type BlockStoreReadWriter ¶ added in v0.9.0
BlockStoreReadWriter wraps a ByteStore to implement io.ReaderAt and io.WriterAt.
type BlockStoreReader ¶ added in v0.9.0
BlockStoreReader wraps a ByteStore to implement io.ReaderAt.
type BlockStoreWriter ¶ added in v0.9.0
BlockStoreWriter wraps a ByteStore to implement io.WriterAt.
type ByteStore ¶ added in v0.9.0
type ByteStore interface {
// ReadAt reads len(p) bytes from the file at the given offset.
// Returns the number of bytes read and any error encountered.
// Returns io.EOF when offset is at or beyond the end of file.
//
// The behavior matches io.ReaderAt: a Read that encounters EOF
// may return either (n, io.EOF) or (n, nil) and a subsequent
// Read will return (0, io.EOF).
ReadAt(ino uint64, p []byte, off int64) (n int, err error)
// WriteAt writes len(p) bytes to the file at the given offset.
// Extends the file if necessary, filling any gaps with zeros.
// Returns the number of bytes written and any error encountered.
//
// WriteAt should not affect nor be affected by the underlying data
// representation - it should handle sparse files efficiently.
WriteAt(ino uint64, p []byte, off int64) (n int, err error)
// Truncate changes the file size.
// If the file is larger than size, it is truncated to size.
// If the file is smaller, it is extended with zero bytes to size.
Truncate(ino uint64, size int64) error
// Remove deletes all data associated with the inode.
// After Remove, operations on this inode should return an error.
// Calling Remove on a non-existent inode should not return an error.
Remove(ino uint64) error
// Stat returns the current size of the file in bytes.
// Returns (0, nil) for empty or non-existent files.
Stat(ino uint64) (size int64, err error)
}
ByteStore provides simple byte-oriented file storage.
Each inode has a single variable-sized byte array. This is the simplest storage model, best suited for in-memory filesystems or small files.
Implementations should be safe for concurrent use by multiple goroutines.
Example usage:
store := NewMemByteStore()
store.WriteAt(ino, []byte("hello"), 0)
data := make([]byte, 5)
n, err := store.ReadAt(ino, data, 0)
type COWMemExtentStore ¶ added in v0.9.0
type COWMemExtentStore struct {
*MemExtentStore
// contains filtered or unexported fields
}
COWMemExtentStore extends MemExtentStore with copy-on-write support.
Implements the COWStore interface to enable efficient cloning and snapshots.
func NewCOWMemExtentStore ¶ added in v0.9.0
func NewCOWMemExtentStore(blockSize int) *COWMemExtentStore
NewCOWMemExtentStore creates a new COW-enabled ExtentStore.
func (*COWMemExtentStore) BreakCOW ¶ added in v0.9.0
func (s *COWMemExtentStore) BreakCOW(ino uint64, logicalBlock uint64) (uint64, error)
func (*COWMemExtentStore) CloneExtents ¶ added in v0.9.0
func (s *COWMemExtentStore) CloneExtents(srcIno, dstIno uint64) error
func (*COWMemExtentStore) RefCount ¶ added in v0.9.0
func (s *COWMemExtentStore) RefCount(physical uint64) uint64
type COWStore ¶ added in v0.9.0
type COWStore interface {
// CloneExtents creates a copy-on-write clone of src's extent list for dst.
// Both inodes will share the same physical blocks until one modifies them.
// Reference counts for all shared blocks are incremented.
CloneExtents(srcIno, dstIno uint64) error
// BreakCOW ensures that the given extent is not shared.
// If the extent is shared (refcount > 1), allocates a new extent,
// copies the data, and updates the inode to use the new extent.
// If the extent is not shared, does nothing.
// Returns the (possibly new) physical block ID.
BreakCOW(ino uint64, logicalBlock uint64) (physical uint64, err error)
// RefCount returns the reference count for a physical block.
// Returns 0 if the block is not allocated or not tracked.
RefCount(physical uint64) uint64
}
COWStore extends a BlockStore with copy-on-write capabilities.
Copy-on-write allows multiple inodes to share the same physical blocks until one of them modifies the data. This enables efficient cloning and snapshotting.
Implementations must track reference counts for shared blocks and perform copy-before-write when modifying shared data.
type DirEntry ¶
type DirEntry struct {
Inode *Inode
// contains filtered or unexported fields
}
DirEntry represents a single entry in a directory. It associates a name with an Inode pointer and implements fs.DirEntry.
func NewDirEntry ¶ added in v0.9.0
NewDirEntry creates a new directory entry with the given name and inode.
func (*DirEntry) Info ¶ added in v0.9.0
Info returns the FileInfo for the file or subdirectory described by the entry. This method implements fs.DirEntry.
func (*DirEntry) Name ¶
Name returns the name of the file (or subdirectory) described by the entry. This implements fs.DirEntry.
type Directory ¶
type Directory []*DirEntry
Directory is a sorted slice of directory entries. It implements sort.Interface for maintaining alphabetical order by name. This enables O(log n) lookups via binary search.
type Extent ¶ added in v0.9.0
type Extent struct {
// Logical is the starting logical block number in the file.
// For a file with 4 KiB blocks, logical block 0 is bytes 0-4095,
// logical block 1 is bytes 4096-8191, etc.
Logical uint64
// Physical is the physical block ID where this extent's data is stored.
// This is opaque to the caller - the ExtentStore manages the mapping.
Physical uint64
// Length is the number of contiguous blocks in this extent.
// An extent maps blocks [Logical, Logical+Length) to
// physical blocks [Physical, Physical+Length).
Length uint64
}
Extent represents a contiguous range of blocks.
An extent maps a logical range in a file to a physical range in storage. This enables efficient representation of large contiguous allocations.
type ExtentList ¶ added in v0.9.0
type ExtentList []Extent
ExtentList is a list of extents for a file. Extents should be sorted by Logical block number with no overlaps. Gaps between extents represent sparse regions (holes).
func (ExtentList) FindExtent ¶ added in v0.9.0
func (el ExtentList) FindExtent(logicalBlock uint64) (extIdx int, offset uint64, found bool)
FindExtent locates the extent containing the given logical block.
Returns:
- extIdx: the index of the extent in the list
- offset: the offset within the extent (logicalBlock - extent.Logical)
- found: true if the block is within an allocated extent, false if it's in a hole
Example:
extents := ExtentList{
{Logical: 0, Physical: 100, Length: 10}, // blocks 0-9
{Logical: 20, Physical: 200, Length: 5}, // blocks 20-24
}
// Block 5 is in first extent at offset 5
extIdx, offset, found := extents.FindExtent(5) // returns (0, 5, true)
// Block 15 is in a hole
extIdx, offset, found := extents.FindExtent(15) // returns (0, 0, false)
func (ExtentList) Len ¶ added in v0.9.0
func (el ExtentList) Len() int
Len returns the number of extents in the list.
func (ExtentList) Less ¶ added in v0.9.0
func (el ExtentList) Less(i, j int) bool
Less reports whether the extent at index i should sort before extent j.
func (ExtentList) LogicalSize ¶ added in v0.9.0
func (el ExtentList) LogicalSize() uint64
LogicalSize returns the logical size of the file in blocks.
This is the highest logical block number that could be accessed, including any sparse regions. For an empty extent list, returns 0.
Example:
extents := ExtentList{
{Logical: 0, Physical: 100, Length: 10},
{Logical: 20, Physical: 200, Length: 5},
}
size := extents.LogicalSize() // returns 25 (blocks 0-24)
func (ExtentList) Swap ¶ added in v0.9.0
func (el ExtentList) Swap(i, j int)
Swap exchanges the extents at indices i and j.
func (ExtentList) TotalBlocks ¶ added in v0.9.0
func (el ExtentList) TotalBlocks() uint64
TotalBlocks returns the total number of allocated blocks across all extents.
This does not include sparse regions (holes). To get the total file size in blocks, use the last extent's Logical + Length.
type ExtentStore ¶ added in v0.9.0
type ExtentStore interface {
// BlockSize returns the fixed block size used by this store.
BlockSize() int
// AllocExtent allocates a contiguous range of blocks.
// Returns the physical block ID of the start and the actual length allocated.
// May allocate less than requested if contiguous space is unavailable.
// If any blocks are allocated, returns the extent and nil error.
// If no blocks can be allocated, returns zero extent and an error.
AllocExtent(desiredLength uint64) (physical uint64, length uint64, err error)
// FreeExtent releases a contiguous range of blocks for reuse.
// The blocks' contents become undefined after freeing.
// All blocks [physical, physical+length) must be currently allocated.
FreeExtent(physical uint64, length uint64) error
// ReadExtent reads data from a physical extent.
// Returns a byte slice of length (length * BlockSize()).
// Reading from freed or never-allocated blocks returns undefined data.
ReadExtent(physical uint64, length uint64) (data []byte, err error)
// WriteExtent writes data to a physical extent.
// The data length must equal (length * BlockSize()).
// All blocks in the range must be currently allocated.
WriteExtent(physical uint64, length uint64, data []byte) error
// GetExtents returns the extent list for an inode.
// Extents are sorted by logical block number.
// Gaps between extents represent sparse regions.
// An empty or non-existent file returns an empty extent list.
GetExtents(ino uint64) (ExtentList, error)
// SetExtents atomically updates the extent list for an inode.
// This replaces the entire extent list.
// Extents should be sorted by Logical block number with no overlaps.
SetExtents(ino uint64, extents ExtentList) error
}
ExtentStore provides extent-based block storage.
Files are represented as lists of extents - contiguous ranges of blocks. This is the most sophisticated storage model, providing excellent performance for large sequential files while supporting sparse files and enabling copy-on-write semantics.
Implementations should be safe for concurrent use by multiple goroutines.
type Ino ¶
type Ino uint64
Ino is an inode number allocator. It is safe for concurrent use. Each call to New or NewDir atomically increments the counter and returns a new Inode with a unique inode number.
type Inode ¶
type Inode struct {
Ino uint64
Mode os.FileMode
Nlink uint64
Size int64
Uid uint32
Gid uint32
Dir Directory
// contains filtered or unexported fields
}
An Inode represents the basic metadata of a file.
Thread Safety:
- Ino is immutable after creation
- Nlink, Size, Mode, Uid, Gid use atomic operations (lock-free)
- ctime, atime, mtime use atomic operations (lock-free, accessed via methods)
- Dir is protected by an internal RWMutex
- All exported methods are safe for concurrent use
- Direct field access requires external synchronization or use of Lock/RLock methods
func (*Inode) Link ¶
Link adds a directory entry (DirEntry) for the given node (assumed to be a directory) to the provided child Inode. If an entry with the same name exists, it is replaced. This method is safe for concurrent use.
func (*Inode) Lock ¶ added in v0.9.0
func (n *Inode) Lock()
Lock acquires an exclusive lock on this inode. Use this when modifying fields directly.
func (*Inode) Lookup ¶ added in v0.9.0
Lookup finds a directory entry by name and returns it. Returns nil if not found. This method is safe for concurrent use.
func (*Inode) RLock ¶ added in v0.9.0
func (n *Inode) RLock()
RLock acquires a read lock on this inode. Use this when reading Dir or timestamp fields directly.
func (*Inode) ReadDir ¶ added in v0.9.0
ReadDir returns a snapshot of directory entries. This method is safe for concurrent use.
func (*Inode) Rename ¶
Rename moves/renames a file or directory from oldpath to newpath. Both paths are resolved relative to this inode. This method is safe for concurrent use. It uses lock ordering by pointer address to prevent deadlocks when locking multiple directories.
func (*Inode) Resolve ¶
Resolve traverses the path and returns the target Inode. Supports both absolute and relative paths. This method is safe for concurrent use.
func (*Inode) Unlink ¶
Unlink removes the directory entry with the given name. This method is safe for concurrent use.
type MemBlockChainStore ¶ added in v0.9.0
type MemBlockChainStore struct {
// contains filtered or unexported fields
}
MemBlockChainStore implements BlockChainStore using in-memory storage.
Files are represented as chains of fixed-size blocks. This demonstrates the block chain pattern with support for sparse files.
Thread-safe for concurrent use.
func NewMemBlockChainStore ¶ added in v0.9.0
func NewMemBlockChainStore(blockSize int) *MemBlockChainStore
NewMemBlockChainStore creates a new in-memory BlockChainStore.
func (*MemBlockChainStore) AllocBlock ¶ added in v0.9.0
func (s *MemBlockChainStore) AllocBlock() (uint64, error)
func (*MemBlockChainStore) BlockSize ¶ added in v0.9.0
func (s *MemBlockChainStore) BlockSize() int
func (*MemBlockChainStore) FreeBlock ¶ added in v0.9.0
func (s *MemBlockChainStore) FreeBlock(blockID uint64) error
func (*MemBlockChainStore) GetBlockChain ¶ added in v0.9.0
func (s *MemBlockChainStore) GetBlockChain(ino uint64) ([]uint64, error)
func (*MemBlockChainStore) ReadBlock ¶ added in v0.9.0
func (s *MemBlockChainStore) ReadBlock(blockID uint64) ([]byte, error)
func (*MemBlockChainStore) SetBlockChain ¶ added in v0.9.0
func (s *MemBlockChainStore) SetBlockChain(ino uint64, blockIDs []uint64) error
func (*MemBlockChainStore) WriteBlock ¶ added in v0.9.0
func (s *MemBlockChainStore) WriteBlock(blockID uint64, data []byte) error
type MemByteStore ¶ added in v0.9.0
type MemByteStore struct {
// contains filtered or unexported fields
}
MemByteStore implements ByteStore using an in-memory map.
This is the simplest implementation, suitable for testing and small in-memory filesystems. It stores each file as a single contiguous byte slice.
Thread-safe for concurrent use.
func NewMemByteStore ¶ added in v0.9.0
func NewMemByteStore() *MemByteStore
NewMemByteStore creates a new in-memory ByteStore.
func (*MemByteStore) Remove ¶ added in v0.9.0
func (s *MemByteStore) Remove(ino uint64) error
type MemExtentStore ¶ added in v0.9.0
type MemExtentStore struct {
// contains filtered or unexported fields
}
MemExtentStore implements ExtentStore using in-memory storage.
Files are represented as lists of extents. This demonstrates the extent-based pattern with support for sparse files and efficient contiguous allocation.
Thread-safe for concurrent use.
func NewMemExtentStore ¶ added in v0.9.0
func NewMemExtentStore(blockSize int) *MemExtentStore
NewMemExtentStore creates a new in-memory ExtentStore.
func (*MemExtentStore) AllocExtent ¶ added in v0.9.0
func (s *MemExtentStore) AllocExtent(desiredLength uint64) (uint64, uint64, error)
func (*MemExtentStore) BlockSize ¶ added in v0.9.0
func (s *MemExtentStore) BlockSize() int
func (*MemExtentStore) FreeExtent ¶ added in v0.9.0
func (s *MemExtentStore) FreeExtent(physical uint64, length uint64) error
func (*MemExtentStore) GetExtents ¶ added in v0.9.0
func (s *MemExtentStore) GetExtents(ino uint64) (ExtentList, error)
func (*MemExtentStore) ReadExtent ¶ added in v0.9.0
func (s *MemExtentStore) ReadExtent(physical uint64, length uint64) ([]byte, error)
func (*MemExtentStore) SetExtents ¶ added in v0.9.0
func (s *MemExtentStore) SetExtents(ino uint64, extents ExtentList) error
func (*MemExtentStore) WriteExtent ¶ added in v0.9.0
func (s *MemExtentStore) WriteExtent(physical uint64, length uint64, data []byte) error