什么是 ‘Copy-on-Write (CoW) File System’?利用 Go 实现一个支持快照功能的轻量级文件系统内核

各位同仁,各位技术爱好者,大家好!

今天,我们将深入探讨一个在现代存储系统中无处不在但又常常被误解的关键概念——Copy-on-Write (CoW) 文件系统。我们将从零开始,理解其核心原理,并通过 Go 语言亲手实现一个支持快照功能的轻量级文件系统内核。这不仅仅是一次理论学习,更是一场实践之旅,旨在揭示复杂系统背后的优雅逻辑。

1. 认识 Copy-on-Write (CoW) 文件系统

1.1 什么是文件系统?

在深入 CoW 之前,我们先快速回顾一下文件系统的本质。文件系统是操作系统用来组织和存储计算机文件的一种方法,它将物理存储设备(如硬盘、SSD)上的原始数据块抽象成用户友好的文件和目录。它负责:

  • 数据存储: 将文件内容写入磁盘。
  • 元数据管理: 存储文件的属性(名称、大小、创建时间、权限等)。
  • 空间管理: 跟踪哪些磁盘空间被占用,哪些可用。
  • 目录结构: 组织文件成树状结构,方便查找。
  • 数据访问: 提供接口供应用程序读写文件。

1.2 CoW 的核心思想

Copy-on-Write,顾名思义,即“写时复制”。其核心思想是:当多个实体(例如,文件系统中的不同版本、进程的内存页)共享同一份数据时,只有当其中一个实体尝试修改这份数据时,才会为修改者创建一个副本。在此之前,所有实体都共享同一份原始数据。

这听起来简单,但在实际应用中却带来了巨大的好处:

  • 空间效率: 避免了不必要的全量数据复制。只有修改的数据才需要额外的存储空间。
  • 时间效率: 读操作可以直接访问共享数据,无需复制。写操作只有在真正需要时才进行复制。
  • 原子性与一致性: 结合日志或事务机制,CoW 可以更容易地实现数据修改的原子性和文件系统的一致性,因为修改是在副本上进行的,不会影响原始数据,直到副本修改完成并切换指针。
  • 快照功能: 这是 CoW 最直观和强大的应用之一。通过 CoW,我们可以高效地创建文件系统的瞬时状态副本(快照),而无需复制所有数据。

1.3 CoW 工作原理概述

让我们通过一个简单的例子来理解 CoW 在文件系统中的工作原理:

假设一个文件由一系列数据块组成:[A, B, C, D]

  1. 初始状态: 实时文件系统(Live FS)指向这些块。
  2. 创建快照: 当我们创建一个快照时,快照并不会复制 A、B、C、D 这些数据块。相反,它只是记录下当前 Live FS 所指向的块序列,并创建一个自己的“索引”或“根节点”,指向与 Live FS 相同的 A、B、C、D。此时,A、B、C、D 块的引用计数都为 2(Live FS 和 快照各引用一次)。
  3. Live FS 写入操作: 假设 Live FS 现在需要修改块 B。
    • 它首先检查块 B 的引用计数。发现引用计数为 2(大于 1),这意味着块 B 被共享。
    • Live FS 不会直接修改块 B。它会分配一个新的空闲块 B’
    • 将原始块 B 的内容复制到 B’。
    • Live FS 修改其内部指向,将原来指向 B 的指针改为指向 B’。
    • 现在,Live FS 将新数据写入 B’。
    • 原始块 B 的引用计数减 1(现在只有快照引用它)。新块 B’ 的引用计数为 1(只有 Live FS 引用它)。
    • 此时,Live FS 的数据块序列是 [A, B', C, D],而快照的数据块序列仍然是 [A, B, C, D]。两者互不影响。
  4. Live FS 删除操作: 假设 Live FS 现在要删除块 C。
    • Live FS 检查块 C 的引用计数。发现引用计数为 2。
    • Live FS 只是将其内部指向 C 的指针移除。
    • 块 C 的引用计数减 1。此时,块 C 的引用计数为 1(只有快照引用它),不能被真正释放。
    • 只有当所有引用都消失时,块才会被真正标记为空闲。

通过这种机制,快照提供了一个只读的、时间点上的数据视图,而 Live FS 的修改则在不影响快照的情况下进行。

1.4 CoW 的优缺点

优点:

  • 高效的快照: 无需复制整个数据集,快照创建速度极快,且占用空间极小(只存储元数据和后续修改的数据)。
  • 节省存储空间: 相同的数据块可以被多个版本共享,避免冗余存储。
  • 数据一致性: 修改操作是对副本进行的,原始数据始终保持一致,有助于实现事务性和数据恢复。
  • 性能提升(特定场景): 对于大量读操作和少量写操作的场景,CoW 表现出色。

缺点:

  • 写放大: 每次写入可能导致分配新块、复制旧数据、写入新数据,这会增加 I/O 操作量。
  • 数据碎片化: 新分配的块可能不连续,导致数据在物理存储上分散,影响顺序读性能。
  • 管理开销: 需要额外的机制来跟踪块的引用计数和映射关系,增加了文件系统实现的复杂性。
  • 垃圾回收复杂性: 释放空间时需要考虑引用计数,不能简单地标记为可用。

2. 设计我们的轻量级 CoW 文件系统内核

我们的目标是实现一个轻量级的文件系统内核,以 Go 语言为载体,核心在于演示 CoW 机制和快照功能。这不是一个生产级别的文件系统,我们将专注于核心逻辑,简化一些复杂性(如权限、用户、日志、崩溃恢复等)。

2.1 核心组件与抽象

我们将文件系统分解为以下几个关键组件:

  1. 块存储 (Block Storage): 最底层的抽象,负责与物理存储交互,读写固定大小的数据块。我们将使用一个文件来模拟这块“磁盘”。
  2. 块管理器 (Block Manager): 管理磁盘上的数据块。负责分配、释放块,并维护每个块的引用计数。这是 CoW 机制的核心。
  3. Inode 管理器 (Inode Manager): 管理文件和目录的元数据(Inode)。
  4. 文件系统核心 (File System Core): 封装上述组件,提供高级的文件操作接口(创建、读写、删除、快照等)。

2.2 文件系统布局

我们的文件系统将在一个单一的模拟磁盘文件上运行。它的布局大致如下:

区域 大小 描述
Superblock 1 个块 包含文件系统的全局元数据(如魔数、块大小、总块数、根目录 InodeID 等)。
Block Bitmap N 个块 标记每个数据块是否被占用。
Block Ref Counts N 个块 存储每个数据块的引用计数,这是 CoW 的关键。
Inode Table M 个块 存储所有 Inode 的数据。
Data Blocks 剩余所有块 实际存储文件内容和目录条目的数据。

2.3 关键数据结构

我们将定义以下 Go 结构体来表示文件系统的核心概念:

// consts.go - 常量定义
package main

import "math"

const (
    BlockSize       = 4096              // 每个数据块的大小(字节)
    FileSystemSize  = 1024 * BlockSize  // 模拟文件系统总大小 (1024 个块)
    SuperblockID    = 0                 // Superblock 位于第一个块
    MaxInodes       = 256               // 最大 Inode 数量
    MaxSnapshots    = 8                 // 最大快照数量

    MagicNumber     uint32 = 0xDEADBEEF // 文件系统魔数,用于识别
    MaxFileNameLen  = 28               // 文件名最大长度,为了对齐方便
    DirectBlocks    = 10               // Inode 直接指向的数据块数量
    IndirectBlocks  = BlockSize / 4    // 一个间接块可以指向的块数量 (假设 BlockID 是 4 字节)

    // Block Type for CoW
    BlockTypeFree   uint8 = 0
    BlockTypeData   uint8 = 1
    BlockTypeInode  uint8 = 2
    BlockTypeBitmap uint8 = 3
    BlockTypeRefCnt uint8 = 4
    BlockTypeSuper  uint8 = 5
    BlockTypeDir    uint8 = 6
    BlockTypeIndirect uint8 = 7

    InvalidBlockID  BlockID = math.MaxUint32 // 无效块ID
    InvalidInodeID  InodeID = math.MaxUint32 // 无效InodeID
)

// types.go - 类型定义
package main

import "time"

// BlockID 是一个块的唯一标识符
type BlockID uint32

// InodeID 是一个 Inode 的唯一标识符
type InodeID uint32

// Superblock 文件系统元数据
type Superblock struct {
    Magic         uint32    // 魔数
    BlockSize     uint32    // 块大小
    TotalBlocks   uint32    // 总块数
    FreeBlocks    uint32    // 空闲块数
    RootInodeID   InodeID   // 根目录 Inode ID
    NextInodeID   InodeID   // 下一个可用的 Inode ID
    InodeTableStartBlock BlockID // Inode Table 的起始块
    BlockBitmapStartBlock BlockID // 块位图的起始块
    BlockRefCntStartBlock BlockID // 块引用计数位的起始块
    DataBlocksStartBlock BlockID // 数据块的起始块
    SnapshotIDs   [MaxSnapshots]InodeID // 存储快照的根 Inode ID
    SnapshotCount uint8     // 当前快照数量
}

// Inode 文件/目录的元数据
type Inode struct {
    ID        InodeID       // Inode ID
    Type      uint8         // 文件类型: 0=未知, 1=文件, 2=目录
    Size      uint64        // 文件大小(字节)
    AccessTime  int64         // 最后访问时间
    ModifyTime  int64         // 最后修改时间
    CreateTime  int64         // 创建时间
    BlockCount  uint32        // 占用的数据块数量
    DirectBlocks [DirectBlocks]BlockID // 直接指向的数据块
    IndirectBlock BlockID       // 间接块指针
    // ... 更多字段如权限、所有者等,此处简化
}

// DirectoryEntry 目录中的一个条目
type DirectoryEntry struct {
    InodeID  InodeID // 指向的 Inode ID
    Name     [MaxFileNameLen]byte // 文件/目录名称
}

// Snapshot 代表一个文件系统的快照
// 实际上,快照就是某个时间点上文件系统根目录 Inode 的一个副本
// 并且它会共享数据块,直到数据块被修改。
type Snapshot struct {
    ID        InodeID    // 快照的根 Inode ID
    Timestamp time.Time  // 快照创建时间
    Name      string     // 快照名称
}

2.4 文件系统核心逻辑

我们将实现以下核心功能:

  • 初始化文件系统 (InitFS): 格式化磁盘,写入 Superblock、清空位图和引用计数,创建根目录。
  • 挂载文件系统 (MountFS): 从磁盘读取 Superblock 和其他元数据。
  • 分配/释放块 (AllocateBlock, FreeBlock): 块管理器负责管理空闲块。
  • 分配/释放 Inode (AllocateInode, FreeInode): Inode 管理器负责管理 Inode。
  • 读写文件内容 (ReadFile, WriteFile): 这是 CoW 逻辑的核心体现。
  • 目录操作 (CreateFile, CreateDir, ListDir, DeleteFile, DeleteDir): 文件和目录的创建、删除、列举。
  • 快照功能 (TakeSnapshot, ListSnapshots, MountSnapshot): CoW 的主要应用。

3. Go 语言实现:深入代码

为了更好地组织代码,我们将文件拆分成不同的 Go 文件。

3.1 block_store.go:底层块存储接口

这个接口模拟了与物理存储设备的交互。

// block_store.go
package main

import (
    "fmt"
    "os"
    "sync"
)

// BlockStore 接口定义了底层块存储的操作
type BlockStore interface {
    ReadBlock(id BlockID) ([]byte, error)
    WriteBlock(id BlockID, data []byte) error
    Size() uint32 // 返回总块数
    Close() error
}

// FileBlockStore 是 BlockStore 接口的一个文件实现
// 使用一个文件作为模拟磁盘
type FileBlockStore struct {
    file *os.File
    size uint32 // 总块数
    mu   sync.RWMutex
}

// NewFileBlockStore 创建并初始化一个 FileBlockStore
func NewFileBlockStore(path string, totalBlocks uint32) (*FileBlockStore, error) {
    file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644)
    if err != nil {
        return nil, fmt.Errorf("failed to open/create block store file: %w", err)
    }

    // 确保文件大小正确
    expectedSize := int64(totalBlocks) * BlockSize
    info, err := file.Stat()
    if err != nil {
        file.Close()
        return nil, fmt.Errorf("failed to stat block store file: %w", err)
    }

    if info.Size() < expectedSize {
        // 如果文件太小,进行扩容
        if err := file.Truncate(expectedSize); err != nil {
            file.Close()
            return nil, fmt.Errorf("failed to truncate block store file to %d bytes: %w", expectedSize, err)
        }
    } else if info.Size() > expectedSize {
        // 如果文件太大,截断
        if err := file.Truncate(expectedSize); err != nil {
            file.Close()
            return nil, fmt.Errorf("failed to truncate block store file to %d bytes: %w", expectedSize, err)
        }
    }

    return &FileBlockStore{
        file: file,
        size: totalBlocks,
    }, nil
}

// ReadBlock 从指定块 ID 读取数据
func (fbs *FileBlockStore) ReadBlock(id BlockID) ([]byte, error) {
    if id >= fbs.size {
        return nil, fmt.Errorf("block ID %d out of bounds (max %d)", id, fbs.size-1)
    }

    fbs.mu.RLock()
    defer fbs.mu.RUnlock()

    offset := int64(id) * BlockSize
    data := make([]byte, BlockSize)
    n, err := fbs.file.ReadAt(data, offset)
    if err != nil {
        return nil, fmt.Errorf("failed to read block %d at offset %d: %w", id, offset, err)
    }
    if n != BlockSize {
        return nil, fmt.Errorf("read %d bytes from block %d, expected %d", n, id, BlockSize)
    }
    return data, nil
}

// WriteBlock 将数据写入指定块 ID
func (fbs *FileBlockStore) WriteBlock(id BlockID, data []byte) error {
    if id >= fbs.size {
        return fmt.Errorf("block ID %d out of bounds (max %d)", id, fbs.size-1)
    }
    if len(data) != BlockSize {
        return fmt.Errorf("data size %d does not match block size %d", len(data), BlockSize)
    }

    fbs.mu.Lock()
    defer fbs.mu.Unlock()

    offset := int64(id) * BlockSize
    n, err := fbs.file.WriteAt(data, offset)
    if err != nil {
        return fmt.Errorf("failed to write block %d at offset %d: %w", id, offset, err)
    }
    if n != BlockSize {
        return fmt.Errorf("wrote %d bytes to block %d, expected %d", n, id, BlockSize)
    }
    return nil
}

// Size 返回总块数
func (fbs *FileBlockStore) Size() uint32 {
    return fbs.size
}

// Close 关闭文件
func (fbs *FileBlockStore) Close() error {
    fbs.mu.Lock()
    defer fbs.mu.Unlock()
    return fbs.file.Close()
}

3.2 block_manager.go:块管理与 CoW 引用计数

这是 CoW 机制的核心。BlockManager 负责维护块的位图(哪些块被占用)和引用计数(有多少 Inode 或快照引用一个块)。

// block_manager.go
package main

import (
    "bytes"
    "encoding/binary"
    "fmt"
    "sync"
)

// BlockManager 管理块的分配、释放和引用计数
type BlockManager struct {
    fs           *FileSystem // 引用文件系统实例,用于访问 Superblock 和底层 BlockStore
    blockBitmap  []byte      // 块位图,每个位代表一个块是否被占用
    blockRefCnts []uint8     // 块引用计数,每个字节代表一个块的引用计数
    mu           sync.RWMutex
    dirtyBitmap  bool        // 标记位图是否需要写入磁盘
    dirtyRefCnts bool        // 标记引用计数是否需要写入磁盘
}

// NewBlockManager 创建并初始化 BlockManager
func NewBlockManager(fs *FileSystem) *BlockManager {
    return &BlockManager{
        fs: fs,
    }
}

// Load 从磁盘加载块位图和引用计数
func (bm *BlockManager) Load() error {
    bm.mu.Lock()
    defer bm.mu.Unlock()

    sb := &bm.fs.superblock
    bitmapBlocks := (sb.TotalBlocks + BlockSize*8 - 1) / (BlockSize * 8)
    refCntBlocks := (sb.TotalBlocks + BlockSize - 1) / BlockSize

    bm.blockBitmap = make([]byte, bitmapBlocks * BlockSize)
    bm.blockRefCnts = make([]uint8, refCntBlocks * BlockSize)

    // 读取位图
    for i := uint32(0); i < bitmapBlocks; i++ {
        blockID := sb.BlockBitmapStartBlock + BlockID(i)
        data, err := bm.fs.blockStore.ReadBlock(blockID)
        if err != nil {
            return fmt.Errorf("failed to read block bitmap block %d: %w", blockID, err)
        }
        copy(bm.blockBitmap[i*BlockSize:(i+1)*BlockSize], data)
    }

    // 读取引用计数
    for i := uint32(0); i < refCntBlocks; i++ {
        blockID := sb.BlockRefCntStartBlock + BlockID(i)
        data, err := bm.fs.blockStore.ReadBlock(blockID)
        if err != nil {
            return fmt.Errorf("failed to read block ref count block %d: %w", blockID, err)
        }
        copy(bm.blockRefCnts[i*BlockSize:(i+1)*BlockSize], data)
    }

    bm.dirtyBitmap = false
    bm.dirtyRefCnts = false
    return nil
}

// Persist 将块位图和引用计数写入磁盘
func (bm *BlockManager) Persist() error {
    bm.mu.RLock()
    defer bm.mu.RUnlock()

    sb := &bm.fs.superblock
    var err error

    if bm.dirtyBitmap {
        bitmapBlocks := (sb.TotalBlocks + BlockSize*8 - 1) / (BlockSize * 8)
        for i := uint32(0); i < bitmapBlocks; i++ {
            blockID := sb.BlockBitmapStartBlock + BlockID(i)
            data := bm.blockBitmap[i*BlockSize : (i+1)*BlockSize]
            if err = bm.fs.blockStore.WriteBlock(blockID, data); err != nil {
                return fmt.Errorf("failed to write block bitmap block %d: %w", blockID, err)
            }
        }
        bm.dirtyBitmap = false
    }

    if bm.dirtyRefCnts {
        refCntBlocks := (sb.TotalBlocks + BlockSize - 1) / BlockSize
        for i := uint32(0); i < refCntBlocks; i++ {
            blockID := sb.BlockRefCntStartBlock + BlockID(i)
            data := bm.blockRefCnts[i*BlockSize : (i+1)*BlockSize]
            if err = bm.fs.blockStore.WriteBlock(blockID, data); err != nil {
                return fmt.Errorf("failed to write block ref count block %d: %w", blockID, err)
            }
        }
        bm.dirtyRefCnts = false
    }
    return nil
}

// isBlockAllocated 检查块是否已分配
func (bm *BlockManager) isBlockAllocated(id BlockID) bool {
    if id >= bm.fs.superblock.TotalBlocks {
        return false
    }
    byteIndex := id / 8
    bitIndex := id % 8
    return (bm.blockBitmap[byteIndex]>>(bitIndex))&1 == 1
}

// setBlockAllocated 设置块的分配状态
func (bm *BlockManager) setBlockAllocated(id BlockID, allocated bool) {
    if id >= bm.fs.superblock.TotalBlocks {
        return
    }
    byteIndex := id / 8
    bitIndex := id % 8
    if allocated {
        bm.blockBitmap[byteIndex] |= (1 << bitIndex)
    } else {
        bm.blockBitmap[byteIndex] &= ^(1 << bitIndex)
    }
    bm.dirtyBitmap = true
}

// AllocateBlock 分配一个空闲块
func (bm *BlockManager) AllocateBlock() (BlockID, error) {
    bm.mu.Lock()
    defer bm.mu.Unlock()

    for i := bm.fs.superblock.DataBlocksStartBlock; i < bm.fs.superblock.TotalBlocks; i++ {
        if !bm.isBlockAllocated(i) {
            bm.setBlockAllocated(i, true)
            bm.blockRefCnts[i] = 1 // 新分配的块引用计数为 1
            bm.dirtyRefCnts = true
            bm.fs.superblock.FreeBlocks--
            fmt.Printf("Allocated block: %d, FreeBlocks: %dn", i, bm.fs.superblock.FreeBlocks)
            return i, nil
        }
    }
    return InvalidBlockID, fmt.Errorf("no free blocks available")
}

// FreeBlock 释放一个块,减少其引用计数。如果引用计数归零,则标记为可用。
func (bm *BlockManager) FreeBlock(id BlockID) error {
    bm.mu.Lock()
    defer bm.mu.Unlock()

    if id == InvalidBlockID || !bm.isBlockAllocated(id) {
        return fmt.Errorf("attempt to free unallocated or invalid block %d", id)
    }

    if bm.blockRefCnts[id] == 0 {
        return fmt.Errorf("block %d reference count already zero", id)
    }

    bm.blockRefCnts[id]--
    bm.dirtyRefCnts = true
    fmt.Printf("Decremented ref count for block %d to %dn", id, bm.blockRefCnts[id])

    if bm.blockRefCnts[id] == 0 {
        bm.setBlockAllocated(id, false)
        bm.fs.superblock.FreeBlocks++
        fmt.Printf("Freed block %d (ref count 0), FreeBlocks: %dn", id, bm.fs.superblock.FreeBlocks)
    }
    return nil
}

// IncrementBlockRef 增加块的引用计数
func (bm *BlockManager) IncrementBlockRef(id BlockID) error {
    bm.mu.Lock()
    defer bm.mu.Unlock()

    if id == InvalidBlockID || !bm.isBlockAllocated(id) {
        return fmt.Errorf("attempt to increment ref count for unallocated or invalid block %d", id)
    }
    if bm.blockRefCnts[id] == math.MaxUint8 { // 避免溢出
        return fmt.Errorf("block %d ref count already at max", id)
    }
    bm.blockRefCnts[id]++
    bm.dirtyRefCnts = true
    fmt.Printf("Incremented ref count for block %d to %dn", id, bm.blockRefCnts[id])
    return nil
}

// GetBlockRef 获取块的引用计数
func (bm *BlockManager) GetBlockRef(id BlockID) (uint8, error) {
    bm.mu.RLock()
    defer bm.mu.RUnlock()

    if id == InvalidBlockID || !bm.isBlockAllocated(id) {
        return 0, fmt.Errorf("attempt to get ref count for unallocated or invalid block %d", id)
    }
    return bm.blockRefCnts[id], nil
}

// ReadBlockData 读取块数据
func (bm *BlockManager) ReadBlockData(id BlockID) ([]byte, error) {
    bm.mu.RLock()
    defer bm.mu.RUnlock()

    if id == InvalidBlockID || !bm.isBlockAllocated(id) {
        return nil, fmt.Errorf("attempt to read from unallocated or invalid block %d", id)
    }
    return bm.fs.blockStore.ReadBlock(id)
}

// WriteBlockData 写入块数据
// 这是 CoW 核心:如果块被共享,则先复制再写入
func (bm *BlockManager) WriteBlockData(id BlockID, data []byte) (BlockID, error) {
    bm.mu.Lock()
    defer bm.mu.Unlock()

    if id == InvalidBlockID || !bm.isBlockAllocated(id) {
        return InvalidBlockID, fmt.Errorf("attempt to write to unallocated or invalid block %d", id)
    }

    // 检查引用计数:如果大于 1,则需要进行写时复制
    if bm.blockRefCnts[id] > 1 {
        fmt.Printf("CoW triggered for block %d (ref count %d)n", id, bm.blockRefCnts[id])
        // 1. 分配新块
        newBlockID, err := bm.AllocateBlock()
        if err != nil {
            return InvalidBlockID, fmt.Errorf("failed to allocate new block for CoW: %w", err)
        }
        // 2. 复制旧块内容到新块(如果需要,此处我们直接写入新数据)
        // 如果是部分写入,则需要先读旧块再写。此处简化为全块写入。
        // oldData, err := bm.fs.blockStore.ReadBlock(id)
        // if err != nil {
        //  bm.FreeBlock(newBlockID) // 失败回滚
        //  return InvalidBlockID, fmt.Errorf("failed to read old block %d for CoW: %w", id, err)
        // }
        // copy(oldData, data) // 如果是部分写入,这里需要合并
        // err = bm.fs.blockStore.WriteBlock(newBlockID, oldData)
        err = bm.fs.blockStore.WriteBlock(newBlockID, data) // 直接写入新数据
        if err != nil {
            bm.FreeBlock(newBlockID) // 失败回滚
            return InvalidBlockID, fmt.Errorf("failed to write new block %d for CoW: %w", newBlockID, err)
        }
        // 3. 递减旧块的引用计数
        bm.blockRefCnts[id]--
        bm.dirtyRefCnts = true
        fmt.Printf("Original block %d ref count decremented to %dn", id, bm.blockRefCnts[id])

        // 新块的引用计数在 AllocateBlock 中已设置为 1
        return newBlockID, nil // 返回新块的 ID,调用者需要更新 Inode 指针
    } else {
        // 引用计数为 1,直接写入
        err := bm.fs.blockStore.WriteBlock(id, data)
        if err != nil {
            return InvalidBlockID, fmt.Errorf("failed to write block %d: %w", id, err)
        }
        return id, nil // 返回原块 ID
    }
}

3.3 inode_manager.go:Inode 管理

InodeManager 负责 Inode 的分配、释放、读写,以及 Inode 表的持久化。

// inode_manager.go
package main

import (
    "bytes"
    "encoding/binary"
    "fmt"
    "sync"
    "time"
)

// InodeManager 管理 Inode 的分配和持久化
type InodeManager struct {
    fs          *FileSystem // 引用文件系统实例
    inodeTable  []Inode     // 内存中的 Inode 表
    mu          sync.RWMutex
    dirtyInodes map[InodeID]bool // 标记哪些 Inode 需要写入磁盘
}

// NewInodeManager 创建并初始化 InodeManager
func NewInodeManager(fs *FileSystem) *InodeManager {
    return &InodeManager{
        fs:         fs,
        inodeTable: make([]Inode, MaxInodes),
        dirtyInodes: make(map[InodeID]bool),
    }
}

// Load 从磁盘加载 Inode Table
func (im *InodeManager) Load() error {
    im.mu.Lock()
    defer im.mu.Unlock()

    sb := &im.fs.superblock
    inodesPerBlock := BlockSize / binary.Size(Inode{})
    inodeTableBlocks := (MaxInodes + inodesPerBlock - 1) / inodesPerBlock

    for i := uint32(0); i < inodeTableBlocks; i++ {
        blockID := sb.InodeTableStartBlock + BlockID(i)
        data, err := im.fs.blockStore.ReadBlock(blockID)
        if err != nil {
            return fmt.Errorf("failed to read inode table block %d: %w", blockID, err)
        }
        buf := bytes.NewReader(data)
        for j := 0; j < inodesPerBlock; j++ {
            inodeIndex := i*uint32(inodesPerBlock) + uint32(j)
            if inodeIndex >= MaxInodes {
                break
            }
            var inode Inode
            if err := binary.Read(buf, binary.LittleEndian, &inode); err != nil {
                // 如果是文件末尾,可能不是错误
                if err.Error() == "EOF" {
                    break
                }
                return fmt.Errorf("failed to read inode %d from block %d: %w", inodeIndex, blockID, err)
            }
            im.inodeTable[inodeIndex] = inode
        }
    }
    im.dirtyInodes = make(map[InodeID]bool) // 清空脏标记
    return nil
}

// Persist 将脏 Inode 写入磁盘
func (im *InodeManager) Persist() error {
    im.mu.RLock()
    defer im.mu.RUnlock()

    if len(im.dirtyInodes) == 0 {
        return nil // 没有脏 Inode,无需持久化
    }

    sb := &im.fs.superblock
    inodesPerBlock := BlockSize / binary.Size(Inode{})
    inodeTableBlocks := (MaxInodes + inodesPerBlock - 1) / inodesPerBlock

    // 优化:只写入包含脏 Inode 的块
    blocksToPersist := make(map[BlockID]bool)
    for id := range im.dirtyInodes {
        blockIdx := uint32(id) / uint32(inodesPerBlock)
        blocksToPersist[sb.InodeTableStartBlock + BlockID(blockIdx)] = true
    }

    for blockID := range blocksToPersist {
        blockData := make([]byte, BlockSize)
        buf := bytes.NewBuffer(blockData)

        blockIdx := uint32(blockID - sb.InodeTableStartBlock)
        for j := 0; j < inodesPerBlock; j++ {
            inodeIndex := blockIdx*uint32(inodesPerBlock) + uint32(j)
            if inodeIndex >= MaxInodes {
                break
            }
            if err := binary.Write(buf, binary.LittleEndian, im.inodeTable[inodeIndex]); err != nil {
                return fmt.Errorf("failed to write inode %d to buffer: %w", inodeIndex, err)
            }
        }

        if err := im.fs.blockStore.WriteBlock(blockID, blockData); err != nil {
            return fmt.Errorf("failed to write inode table block %d: %w", blockID, err)
        }
    }
    im.dirtyInodes = make(map[InodeID]bool) // 清空脏标记
    return nil
}

// AllocateInode 分配一个空闲 Inode
func (im *InodeManager) AllocateInode() (InodeID, error) {
    im.mu.Lock()
    defer im.mu.Unlock()

    for i := InodeID(1); i < MaxInodes; i++ { // Inode 0 预留给 Superblock 或特殊用途,这里我们从1开始
        if im.inodeTable[i].ID == InvalidInodeID { // ID 为 InvalidInodeID 表示空闲
            now := time.Now().Unix()
            im.inodeTable[i] = Inode{
                ID:        i,
                Type:      0, // 未知类型
                Size:      0,
                AccessTime:  now,
                ModifyTime:  now,
                CreateTime:  now,
                BlockCount:  0,
                IndirectBlock: InvalidBlockID,
            }
            for j := 0; j < DirectBlocks; j++ {
                im.inodeTable[i].DirectBlocks[j] = InvalidBlockID
            }
            im.dirtyInodes[i] = true
            im.fs.superblock.NextInodeID = i + 1 // 更新下一个可用 Inode ID,简化处理
            return i, nil
        }
    }
    return InvalidInodeID, fmt.Errorf("no free inodes available")
}

// FreeInode 释放一个 Inode
func (im *InodeManager) FreeInode(id InodeID) error {
    im.mu.Lock()
    defer im.mu.Unlock()

    if id == InvalidInodeID || id >= MaxInodes || im.inodeTable[id].ID == InvalidInodeID {
        return fmt.Errorf("attempt to free invalid or unallocated inode %d", id)
    }

    // 释放 Inode 关联的所有数据块
    inode := &im.inodeTable[id]
    for _, blockID := range inode.DirectBlocks {
        if blockID != InvalidBlockID {
            if err := im.fs.blockManager.FreeBlock(blockID); err != nil {
                return fmt.Errorf("failed to free direct block %d for inode %d: %w", blockID, id, err)
            }
        }
    }
    // 释放间接块及其指向的块
    if inode.IndirectBlock != InvalidBlockID {
        indirectBlockData, err := im.fs.blockManager.ReadBlockData(inode.IndirectBlock)
        if err != nil {
            return fmt.Errorf("failed to read indirect block %d for inode %d: %w", inode.IndirectBlock, id, err)
        }
        buf := bytes.NewReader(indirectBlockData)
        for j := 0; j < IndirectBlocks; j++ {
            var dataBlockID BlockID
            if err := binary.Read(buf, binary.LittleEndian, &dataBlockID); err != nil {
                if err.Error() == "EOF" { break }
                return fmt.Errorf("failed to read data block ID from indirect block %d: %w", inode.IndirectBlock, err)
            }
            if dataBlockID != InvalidBlockID {
                if err := im.fs.blockManager.FreeBlock(dataBlockID); err != nil {
                    return fmt.Errorf("failed to free data block %d via indirect for inode %d: %w", dataBlockID, id, err)
                }
            }
        }
        if err := im.fs.blockManager.FreeBlock(inode.IndirectBlock); err != nil {
            return fmt.Errorf("failed to free indirect block %d for inode %d: %w", inode.IndirectBlock, id, err)
        }
    }

    // 清空 Inode
    im.inodeTable[id] = Inode{ID: InvalidInodeID} // 标记为可用
    im.dirtyInodes[id] = true
    return nil
}

// GetInode 获取 Inode
func (im *InodeManager) GetInode(id InodeID) (*Inode, error) {
    im.mu.RLock()
    defer im.mu.RUnlock()

    if id == InvalidInodeID || id >= MaxInodes || im.inodeTable[id].ID == InvalidInodeID {
        return nil, fmt.Errorf("invalid or unallocated inode %d", id)
    }
    // 返回副本以防止外部直接修改
    inodeCopy := im.inodeTable[id]
    return &inodeCopy, nil
}

// PutInode 将修改后的 Inode 写回内存并标记为脏
func (im *InodeManager) PutInode(inode *Inode) error {
    im.mu.Lock()
    defer im.mu.Unlock()

    if inode.ID == InvalidInodeID || inode.ID >= MaxInodes {
        return fmt.Errorf("invalid inode ID %d", inode.ID)
    }
    im.inodeTable[inode.ID] = *inode
    im.dirtyInodes[inode.ID] = true
    return nil
}

// getBlockPointer 获取 Inode 对应偏移量的块 ID
func (im *InodeManager) getBlockPointer(inode *Inode, blockOffset uint32) (BlockID, error) {
    if blockOffset < DirectBlocks {
        return inode.DirectBlocks[blockOffset], nil
    }

    // 间接块
    if inode.IndirectBlock == InvalidBlockID {
        return InvalidBlockID, nil // 还没有间接块
    }

    indirectBlockOffset := blockOffset - DirectBlocks
    if indirectBlockOffset >= IndirectBlocks {
        return InvalidBlockID, fmt.Errorf("block offset %d out of bounds for indirect blocks", blockOffset)
    }

    indirectData, err := im.fs.blockManager.ReadBlockData(inode.IndirectBlock)
    if err != nil {
        return InvalidBlockID, fmt.Errorf("failed to read indirect block %d: %w", inode.IndirectBlock, err)
    }
    buf := bytes.NewReader(indirectData[indirectBlockOffset*4 : (indirectBlockOffset+1)*4])
    var dataBlockID BlockID
    if err := binary.Read(buf, binary.LittleEndian, &dataBlockID); err != nil {
        return InvalidBlockID, fmt.Errorf("failed to read block ID from indirect block: %w", err)
    }
    return dataBlockID, nil
}

// setBlockPointer 设置 Inode 对应偏移量的块 ID
func (im *InodeManager) setBlockPointer(inode *Inode, blockOffset uint32, newBlockID BlockID) error {
    if blockOffset < DirectBlocks {
        inode.DirectBlocks[blockOffset] = newBlockID
        return nil
    }

    // 间接块
    indirectBlockOffset := blockOffset - DirectBlocks
    if indirectBlockOffset >= IndirectBlocks {
        return fmt.Errorf("block offset %d out of bounds for indirect blocks", blockOffset)
    }

    // 如果没有间接块,则分配一个
    if inode.IndirectBlock == InvalidBlockID {
        allocatedIndirectBlock, err := im.fs.blockManager.AllocateBlock()
        if err != nil {
            return fmt.Errorf("failed to allocate indirect block: %w", err)
        }
        inode.IndirectBlock = allocatedIndirectBlock
        // 初始化间接块
        emptyIndirectBlockData := make([]byte, BlockSize)
        if err := im.fs.blockStore.WriteBlock(allocatedIndirectBlock, emptyIndirectBlockData); err != nil {
            return fmt.Errorf("failed to initialize indirect block %d: %w", allocatedIndirectBlock, err)
        }
    }

    // 读取间接块数据
    indirectData, err := im.fs.blockManager.ReadBlockData(inode.IndirectBlock)
    if err != nil {
        return fmt.Errorf("failed to read indirect block %d: %w", inode.IndirectBlock, err)
    }

    // 更新间接块中的指针
    buf := bytes.NewBuffer(indirectData)
    buf.Seek(int64(indirectBlockOffset*4), 0) // 移动到正确位置
    if err := binary.Write(buf, binary.LittleEndian, newBlockID); err != nil {
        return fmt.Errorf("failed to write block ID to indirect block: %w", err)
    }

    // 写回间接块(CoW 机制在这里也适用,如果间接块被共享,也会被复制)
    // 注意:这里的 WriteBlockData 会处理 CoW 逻辑
    newIndirectBlockID, err := im.fs.blockManager.WriteBlockData(inode.IndirectBlock, buf.Bytes())
    if err != nil {
        return fmt.Errorf("failed to write updated indirect block %d: %w", inode.IndirectBlock, err)
    }
    inode.IndirectBlock = newIndirectBlockID // 如果发生了 CoW,更新 inode 的间接块指针
    return nil
}

3.4 filesystem.go:文件系统核心与快照

这是将所有组件整合起来的地方,并提供用户接口。


// filesystem.go
package main

import (
    "bytes"
    "encoding/binary"
    "fmt"
    "os"
    "strings"
    "sync"
    "time"
)

// FileSystem 是文件系统的核心结构
type FileSystem struct {
    path        string
    blockStore  BlockStore
    blockManager *BlockManager
    inodeManager *InodeManager
    superblock  Superblock
    mounted     bool
    mu          sync.RWMutex
}

// NewFileSystem 创建一个新的 FileSystem 实例
func NewFileSystem(path string) *FileSystem {
    return &FileSystem{
        path: path,
    }
}

// InitFS 格式化并初始化文件系统
func (fs *FileSystem) InitFS() error {
    fs.mu.Lock()
    defer fs.mu.Unlock()

    fmt.Println("Initializing file system...")

    var err error
    fs.blockStore, err = NewFileBlockStore(fs.path, FileSystemSize/BlockSize)
    if err != nil {
        return fmt.Errorf("failed to create block store: %w", err)
    }

    // 初始化 Superblock
    fs.superblock = Superblock{
        Magic:         MagicNumber,
        BlockSize:     BlockSize,
        TotalBlocks:   fs.blockStore.Size(),
        RootInodeID:   InvalidInodeID, // 待分配
        NextInodeID:   1,              // 从 1 开始分配 Inode
        SnapshotCount: 0,
    }

    // 计算各个区域的起始块
    fs.superblock.BlockBitmapStartBlock = SuperblockID + 1
    bitmapBlocks := (fs.superblock.TotalBlocks + BlockSize*8 - 1) / (BlockSize * 8)
    fs.superblock.BlockRefCntStartBlock = fs.superblock.BlockBitmapStartBlock + bitmapBlocks
    refCntBlocks := (fs.superblock.TotalBlocks + BlockSize - 1) / BlockSize
    fs.superblock.InodeTableStartBlock = fs.superblock.BlockRefCntStartBlock + refCntBlocks
    inodesPerBlock := BlockSize / binary.Size(Inode{})
    inodeTableBlocks := (MaxInodes + inodesPerBlock - 1) / inodesPerBlock
    fs.superblock.DataBlocksStartBlock = fs.superblock.InodeTableStartBlock + inodeTableBlocks

    fs.superblock.FreeBlocks = fs.superblock.TotalBlocks - fs.superblock.DataBlocksStartBlock // 初始空闲块

    // 初始化 BlockManager 和 InodeManager
    fs.blockManager = NewBlockManager(fs)
    fs.inodeManager = NewInodeManager(fs)

    // 标记 Superblock、位图、引用计数和 Inode 表的块为已使用
    // 这些块不参与 CoW,因为它们是文件系统元数据
    for i := BlockID(0); i < fs.superblock.DataBlocksStartBlock; i++ {
        fs.blockManager.setBlockAllocated(i, true)
        fs.blockManager.blockRefCnts[i] = 1 // 元数据块引用计数为 1
        fs.superblock.FreeBlocks--
    }
    fs.blockManager.dirtyBitmap = true
    fs.blockManager.dirtyRefCnts = true

    // 创建根目录
    rootInodeID, err := fs.inodeManager.AllocateInode()
    if err != nil {
        return fmt.Errorf("failed to allocate root inode: %w", err)
    }
    rootInode, _ := fs.inodeManager.GetInode(rootInodeID)
    rootInode.Type = BlockTypeDir
    rootInode.ModifyTime = time.Now().Unix()
    rootInode.AccessTime = rootInode.ModifyTime
    rootInode.CreateTime = rootInode.ModifyTime
    if err := fs.inodeManager.PutInode(rootInode); err != nil {
        return fmt.Errorf("failed to put root inode: %w", err)
    }
    fs.superblock.RootInodeID = rootInodeID

    // 持久化所有初始元数据
    if err := fs.flushMetadata(); err != nil {
        return fmt.Errorf("failed to flush initial metadata: %w", err)
    }

    fs.mounted = true
    fmt.Println("File system initialized successfully.")
    return nil
}

// MountFS 挂载文件系统
func (fs *FileSystem) MountFS() error {
    fs.mu.Lock()
    defer fs.mu.Unlock()

    fmt.Println("Mounting file system...")

    var err error
    fs.blockStore, err = NewFileBlockStore(fs.path, 0) // size 0 will read from file info
    if err != nil {
        return fmt.Errorf("failed to open block store: %w", err)
    }

    // 读取 Superblock
    sbData, err := fs.blockStore.ReadBlock(SuperblockID)
    if err != nil {
        fs.blockStore.Close()
        return fmt.Errorf("failed to read superblock: %w", err)
    }
    buf := bytes.NewReader(sbData)
    if err := binary.Read(buf, binary.LittleEndian, &fs.superblock); err != nil {
        fs.blockStore.Close()
        return fmt.Errorf("failed to decode superblock: %w", err)
    }

    if fs.superblock.Magic != MagicNumber {
        fs.blockStore.Close()
        return fmt.Errorf("invalid magic number, not a valid file system")
    }

    fs.blockManager = NewBlockManager(fs)
    fs.inodeManager = NewInodeManager(fs)

    if err := fs.blockManager.Load(); err != nil {
        fs.blockStore.Close()
        return fmt.Errorf("failed to load block manager: %w", err)
    }
    if err := fs.inodeManager.Load(); err != nil {
        fs.blockStore.Close()
        return fmt.Errorf("failed to load inode manager: %w", err)
    }

    fs.mounted = true
    fmt.Println("File system mounted successfully.")
    return nil
}

// UnmountFS 卸载文件系统
func (fs *FileSystem) UnmountFS() error {
    fs.mu.Lock()
    defer fs.mu.Unlock()

    if !fs.mounted {
        return fmt.Errorf("file system not mounted")
    }

    fmt.Println("Unmounting file system...")
    if err := fs.flushMetadata(); err != nil {
        return fmt.Errorf("failed to flush metadata during unmount: %w", err)
    }
    if err := fs.blockStore.Close(); err != nil {
        return fmt.Errorf("failed to close block store: %w", err)
    }
    fs.mounted = false
    fmt.Println("File system unmounted successfully.")
    return nil
}

// flushMetadata 将所有脏元数据写入磁盘
func (fs *FileSystem) flushMetadata() error {
    // 写入 Superblock
    sbData := new(bytes.Buffer)
    if err := binary.Write(sbData, binary.LittleEndian, fs.superblock); err != nil {
        return fmt.Errorf("failed to encode superblock: %w", err)
    }
    if err := fs.blockStore.WriteBlock(SuperblockID, padToBlockSize(sbData.Bytes())); err != nil {
        return fmt.Errorf("failed to write superblock: %w", err)
    }

    // 写入 BlockManager 的脏数据
    if err := fs.blockManager.Persist(); err != nil {
        return fmt.Errorf("failed to persist block manager data: %w", err)
    }

    // 写入 InodeManager 的脏数据
    if err := fs.inodeManager.Persist(); err != nil {
        return fmt.Errorf("failed to persist inode manager data: %w", err)
    }
    return nil
}

// padToBlockSize 填充数据到块大小
func padToBlockSize(data []byte) []byte {
    if len(data) > BlockSize {
        return data[:BlockSize]
    }
    padded := make([]byte, BlockSize)
    copy(padded, data)
    return padded
}

// findEntryInDir 在目录中查找条目
func (fs *FileSystem) findEntryInDir(dirInodeID InodeID, name string) (InodeID, error) {
    dirInode, err := fs.inodeManager.GetInode(dirInodeID)
    if err != nil {
        return InvalidInodeID, err
    }
    if dirInode.Type != BlockTypeDir {
        return InvalidInodeID, fmt.Errorf("inode %d is not a directory", dirInodeID)
    }

    dirEntriesPerBlock := BlockSize / binary.Size(DirectoryEntry{})
    currentSize := uint64(0)

    for blockOffset := uint32(0); currentSize < dirInode.Size; blockOffset++ {
        blockID, err := fs.inodeManager.getBlockPointer(dirInode, blockOffset)
        if err != nil {
            return InvalidInodeID, err
        }
        if blockID == InvalidBlockID {
            break // 没有更多块了
        }
        blockData, err := fs.blockManager.ReadBlockData(blockID)
        if err != nil {
            return InvalidInodeID, err
        }

        for i := 0; i < dirEntriesPerBlock; i++ {
            entryOffset := i * binary.Size(DirectoryEntry{})
            if currentSize+uint664(entryOffset) >= dirInode.Size {
                break
            }
            var entry DirectoryEntry
            buf := bytes.NewReader(blockData[entryOffset : entryOffset+binary.Size(DirectoryEntry{})])
            if err := binary.Read(buf, binary.LittleEndian, &entry); err != nil {
                return InvalidInodeID, fmt.Errorf("failed to read directory entry: %w", err)
            }
            entryName := string(bytes.Trim(entry.Name[:], "x00"))
            if entryName == name {
                return entry.InodeID, nil
            }
            currentSize += uint64(binary.Size(DirectoryEntry{}))
        }
    }
    return InvalidInodeID, fmt.Errorf("entry '%s' not found in directory inode %d", name, dirInodeID)
}

// addEntryToDir 向目录添加条目
func (fs *FileSystem) addEntryToDir(dirInodeID InodeID, entryName string, targetInodeID InodeID) error {
    dirInode, err := fs.inodeManager.GetInode(dirInodeID)
    if err != nil {
        return err
    }
    if dirInode.Type != BlockTypeDir {
        return fmt.Errorf("inode %d is not a directory", dirInodeID)
    }

    // 检查名称长度
    if len(entryName) > MaxFileNameLen {
        return fmt.Errorf("file name '%s' too long (max %d)", entryName, MaxFileNameLen)
    }

    // 检查是否已存在
    if _, err := fs.findEntryInDir(dirInodeID, entryName); err == nil {
        return fmt.Errorf("entry '%s' already exists in directory inode %d", entryName, dirInodeID)
    }

    dirEntriesPerBlock := BlockSize / binary.Size(DirectoryEntry{})
    newEntry := DirectoryEntry{InodeID: targetInodeID}
    copy(newEntry.Name[:], []byte(entryName))

    entryData := new(bytes.Buffer)
    if err := binary.Write(entryData, binary.LittleEndian, newEntry); err != nil {
        return fmt.Errorf("failed to encode directory entry: %w", err)
    }

    // 查找空闲位置或分配新块
    var targetBlockID BlockID
    var blockOffset uint32
    var entryInBlockOffset int // 字节偏移

    for blockOffset = 0; ; blockOffset++ {
        blockID, err := fs.inodeManager.getBlockPointer(dirInode, blockOffset)
        if err != nil {
            return err
        }

        if blockID == InvalidBlockID {
            // 需要分配新块
            newBlockID, err := fs.blockManager.AllocateBlock()
            if err != nil {
                return fmt.Errorf("failed to allocate block for directory entry: %w", err)
            }
            dirInode.BlockCount++
            if err := fs.inodeManager.setBlockPointer(dirInode, blockOffset, newBlockID); err != nil {
                fs.blockManager.FreeBlock(newBlockID) // 回滚
                return err
            }
            targetBlockID = newBlockID
            entryInBlockOffset = 0 // 新块的第一个位置
            break
        }

        // 读取当前块寻找空闲位置
        blockData, err := fs.blockManager.ReadBlockData(blockID)
        if err != nil {
            return err
        }

        foundFreeSpot := false
        for i := 0; i < dirEntriesPerBlock; i++ {
            currentEntryOffset := i * binary.Size(DirectoryEntry{})
            // 检查是否是有效条目(通过 InodeID 判断)
            var existingEntry DirectoryEntry
            buf := bytes.NewReader(blockData[currentEntryOffset : currentEntryOffset+binary.Size(DirectoryEntry{})])
            if err := binary.Read(buf, binary.LittleEndian, &existingEntry); err != nil {
                // 如果读取失败,可能是空数据,认为是空闲
                existingEntry.InodeID = InvalidInodeID
            }

            if existingEntry.InodeID == InvalidInodeID && dirInode.Size > uint64(blockOffset*BlockSize+uint32(currentEntryOffset)) {
                // 找到一个被删除后的空闲位置
                targetBlockID = blockID
                entryInBlockOffset = currentEntryOffset
                foundFreeSpot = true
                break
            }
        }
        if foundFreeSpot {
            break
        }
        // 如果当前块满了,继续下一个块
        if (blockOffset+1)*BlockSize > dirInode.Size {
            // 在目录大小末尾添加
            targetBlockID = blockID
            entryInBlockOffset = int(dirInode.Size % BlockSize)
            if entryInBlockOffset+binary.Size(DirectoryEntry{}) > BlockSize {
                // 当前块已满,需要新块
                continue // 循环到下一块分配
            }
            break
        }
    }

    // 读取目标块,更新并写回
    blockData, err := fs.blockManager.ReadBlockData(targetBlockID)
    if err != nil {
        return fmt.Errorf("failed to read target block %d: %w", targetBlockID, err)
    }
    copy(blockData[entryInBlockOffset:], entryData.Bytes())

    // 写回数据块 (CoW 机制在这里触发)
    newBlockID, err := fs.blockManager.WriteBlockData(targetBlockID, blockData)
    if err != nil {
        return fmt.Errorf("failed to write directory block %d: %w", targetBlockID, err)
    }
    if newBlockID != targetBlockID {
        // 发生了 CoW,需要更新 Inode 指针
        if err := fs.inodeManager.setBlockPointer(dirInode, blockOffset, newBlockID); err != nil {
            return err
        }
    }

    dirInode.Size += uint64(binary.Size(DirectoryEntry{}))
    dirInode.ModifyTime = time.Now().Unix()
    return fs.inodeManager.PutInode(dirInode)
}

// removeEntryFromDir 从目录中移除条目
func (fs *FileSystem) removeEntryFromDir(dirInodeID InodeID, name string) (InodeID, error) {
    dirInode, err := fs.inodeManager.GetInode(dirInodeID)
    if err != nil {
        return InvalidInodeID, err
    }
    if dirInode.Type != BlockTypeDir {
        return InvalidInodeID, fmt.Errorf("inode %d is not a directory", dirInodeID)
    }

    dirEntriesPerBlock := BlockSize / binary.Size(DirectoryEntry{})
    currentSize := uint64(0)

    for blockOffset := uint32(0); currentSize < dirInode.Size; blockOffset++ {
        blockID, err := fs.inodeManager.getBlockPointer(dirInode, blockOffset)
        if err != nil {
            return InvalidInodeID, err
        }
        if blockID == InvalidBlockID {
            break
        }
        blockData, err := fs.blockManager.ReadBlockData(blockID)
        if err != nil {
            return InvalidInodeID, err
        }

        for i := 0; i < dirEntriesPerBlock; i++ {
            entryOffset := i * binary.Size(DirectoryEntry{})
            if currentSize+uint64(entryOffset) >= dirInode.Size {
                break
            }
            var entry DirectoryEntry
            buf := bytes.NewReader(blockData[entryOffset : entryOffset+binary.Size(DirectoryEntry{})])
            if err := binary.Read(buf, binary.LittleEndian, &entry); err != nil {
                return InvalidInodeID, fmt.Errorf("failed to read directory entry: %w", err)
            }
            entryName := string(bytes.Trim(entry.Name[:], "x00"))
            if entryName == name {
                // 找到条目,将其标记为无效
                removedInodeID := entry.InodeID
                entry.InodeID = InvalidInodeID // 逻辑删除
                clear(entry.Name[:]) // 清空名称

                newEntryData := new(bytes.Buffer)
                if err := binary.Write(newEntryData, binary.LittleEndian, entry); err != nil {
                    return InvalidInodeID, fmt.Errorf("failed to encode cleared directory entry: %w", err)
                }
                copy(blockData[entryOffset:], newEntryData.Bytes())

                // 写回数据块 (CoW 机制在这里触发)
                newBlockID, err := fs.blockManager.WriteBlockData(blockID, blockData)
                if err != nil {
                    return InvalidInodeID, fmt.Errorf("failed to write directory block %d after removal: %w", blockID, err)
                }
                if newBlockID != blockID {
                    // 发生了 CoW,需要更新 Inode 指针
                    if err := fs.inodeManager.setBlockPointer(dirInode, blockOffset, newBlockID); err != nil {
                        return InvalidInodeID, err
                    }
                }

                dirInode.ModifyTime = time.Now().Unix()
                if err := fs.inodeManager.PutInode(dirInode); err != nil {
                    return InvalidInodeID, err
                }
                return removedInodeID, nil
            }
            currentSize += uint64(binary.Size(DirectoryEntry{}))
        }
    }
    return InvalidInodeID, fmt.Errorf("entry '%s' not found in directory inode %d for removal", name, dirInodeID)
}

// resolvePath 将路径解析为 InodeID
func (fs *FileSystem) resolvePath(path string) (InodeID, error) {
    fs.mu.RLock()
    defer fs.mu.RUnlock()

    if path == "/" {
        return fs.superblock.RootInodeID, nil
    }

    currentInodeID := fs.superblock.RootInodeID
    parts := strings.Split(path, "/")
    for _, part := range parts {
        if part == "" {
            continue
        }
        nextInodeID, err := fs.findEntryInDir(currentInodeID, part)
        if err != nil {
            return InvalidInodeID, fmt.Errorf("path component '%s' not found: %w", part, err)
        }
        currentInodeID = nextInodeID
    }
    return currentInodeID, nil
}

// CreateFile 创建文件
func (fs *FileSystem) CreateFile(parentPath, name string) error {
    fs.mu.Lock()
    defer fs.mu.Unlock()

    parentInodeID, err := fs.resolvePath(parentPath)
    if err != nil {
        return fmt.Errorf("failed to resolve parent path '%s': %w", parentPath, err)
    }

    newInodeID, err := fs.inodeManager.AllocateInode()
    if err != nil {
        return fmt.Errorf("failed to allocate inode for new file: %w", err)
    }
    newInode, _ := fs.inodeManager.GetInode(newInodeID)
    newInode.Type = BlockTypeData
    if err := fs.inodeManager.PutInode(newInode); err != nil {
        fs.inodeManager.FreeInode(newInodeID) // 回滚
        return fmt.Errorf("failed to put new file inode: %w", err)
    }

    if err := fs.addEntryToDir(parentInodeID, name, newInodeID); err != nil {
        fs.inodeManager.FreeInode(newInodeID) // 回滚
        return fmt.Errorf("failed to add entry to directory: %w", err)
    }

    fmt.Printf("File '%s' created with Inode ID %dn", name, newInodeID)
    return nil
}

// CreateDir 创建目录
func (fs *FileSystem) CreateDir(parentPath, name string) error {
    fs.mu.Lock()
    defer fs.mu.Unlock()

    parentInodeID, err := fs.resolvePath(parentPath)
    if err != nil {
        return fmt.Errorf("failed to resolve parent path '%s': %w", parentPath, err)
    }

    newInodeID, err := fs.inodeManager.AllocateInode()
    if err != nil {
        return fmt.Errorf("failed to allocate inode for new directory: %w", err)
    }
    newInode, _ := fs.inodeManager.GetInode(newInodeID)
    newInode.Type = BlockTypeDir
    if err := fs.inodeManager.PutInode(newInode); err != nil {
        fs.inodeManager.FreeInode(newInodeID) // 回滚
        return fmt.Errorf("failed to put new directory inode: %w", err)
    }

    if err := fs.addEntryToDir(parentInodeID, name, newInodeID); err != nil {
        fs.inodeManager.FreeInode(newInodeID) // 回滚
        return fmt.Errorf("failed to add directory entry: %w", err)
    }

    fmt.Printf("Directory '%s' created with Inode ID %dn", name, newInodeID)
    return nil
}

// ListDir 列出目录内容
func (fs *FileSystem) ListDir(path string) ([]DirectoryEntry, error) {
    fs.mu.RLock()
    defer fs.mu.RUnlock()

    dirInodeID, err := fs.resolvePath(path)
    if err != nil {
        return nil, fmt.Errorf("failed to resolve path '%s': %w", path, err)
    }
    dirInode, err := fs.inodeManager.GetInode(dirInodeID)
    if err != nil {
        return nil, err
    }
    if dirInode.Type != BlockTypeDir {
        return nil, fmt.Errorf("path '%s' is not a directory", path)
    }

    var entries []DirectoryEntry
    dirEntriesPerBlock := BlockSize / binary.Size(DirectoryEntry{})
    currentSize := uint64(0)

    for blockOffset := uint32(0); currentSize < dirInode.Size; blockOffset++ {
        blockID, err := fs.inodeManager.getBlockPointer(dirInode, blockOffset)
        if err != nil {
            return nil, err
        }
        if blockID == InvalidBlockID {
            break
        }
        blockData, err := fs.blockManager.ReadBlockData(blockID)
        if err != nil {
            return nil, err
        }

        for i := 0; i < dirEntriesPerBlock; i++ {
            entryOffset := i * binary.Size(DirectoryEntry{})
            if currentSize+uint64(entryOffset) >= dirInode.Size {
                break
            }
            var entry DirectoryEntry
            buf := bytes.NewReader(blockData[entryOffset : entryOffset+binary.Size(DirectoryEntry{})])
            if err := binary.Read(buf, binary.LittleEndian, &entry); err != nil {
                return nil, fmt.Errorf("failed to read directory entry: %w", err)
            }
            if entry.InodeID != InvalidInodeID { // 只显示有效条目
                entries = append(entries, entry)
            }
            currentSize += uint64(binary.Size(DirectoryEntry{}))
        }
    }
    return entries, nil
}

// WriteFile 写入文件
func (fs *FileSystem) WriteFile(path string, data []byte) error {
    fs.mu.Lock()
    defer fs.mu.Unlock()

    inodeID, err := fs.resolvePath(path)
    if err != nil {
        return fmt.Errorf("failed to resolve file path '%s': %w", path, err)
    }
    fileInode, err := fs.inodeManager.GetInode(inodeID)
    if err != nil {
        return err
    }
    if fileInode.Type != BlockTypeData {
        return fmt.Errorf("path '%s' is not a file", path)
    }

    // 释放旧块(如果文件内容被完全覆盖)
    // 简化处理:这里假设总是完全覆盖,实际文件系统会处理部分写入和追加
    for blockOffset := uint332(0); blockOffset < fileInode.BlockCount; blockOffset++ {
        oldBlockID, err := fs.inodeManager.getBlockPointer(fileInode, blockOffset)
        if err != nil {
            return err
        }
        if oldBlockID != InvalidBlockID {
            if err := fs.blockManager.FreeBlock(oldBlockID); err != nil {
                return err
            }
        }
    }

    fileInode.BlockCount = 0 // 重置块计数
    fileInode.Size = 0       // 重置文件大小

    numBlocks := (len(data) + BlockSize - 1) / BlockSize
    for i := uint32(0); i < uint32(numBlocks); i++ {
        start := i * BlockSize
        end := (i + 1) * BlockSize
        if end > uint32(len(data)) {
            end = uint32(len(data))
        }
        blockData := padToBlockSize(data[start:end])

        // 分配新块 (CoW 机制在这里触发,因为旧块已被 FreeBlock 释放,所以这里总是分配新块)
        newBlockID, err := fs.blockManager.AllocateBlock()
        if err != nil {
            return fmt.Errorf("failed to allocate block for file write: %w", err)
        }

        // 写入新块
        if err := fs.blockStore.WriteBlock(newBlockID, blockData); err != nil {
            fs.blockManager.FreeBlock(newBlockID) // 回滚
            return fmt.Errorf("failed to write data block %d: %w", newBlockID, err)
        }

        // 更新 Inode 指针
        if err := fs.inodeManager.setBlockPointer(fileInode, i, newBlockID); err != nil {
            fs.blockManager.FreeBlock(newBlockID) // 回滚
            return err
        }
        fileInode.BlockCount++
    }

    fileInode.Size = uint64(len(data))
    fileInode.ModifyTime = time.Now().Unix()
    if err := fs.inodeManager.PutInode(fileInode); err != nil {
        return fmt.Errorf("failed to put file inode after write: %w", err)
    }

    fmt.Printf("File '%s' written, new size %d bytes, using %d blocksn", path, fileInode.Size, fileInode.BlockCount)
    return nil
}

// ReadFile 读取文件
func (fs *FileSystem) ReadFile(path string) ([]byte, error) {
    fs.mu.RLock()
    defer fs.mu.RUnlock()

    inodeID, err := fs.resolvePath(path)
    if err !=

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注