找到磁盘设备中的有效数据块

183 阅读5分钟

问题:

linux下一块磁盘(容量10G)格式化为ext4文件系统,挂载后,写入2G数据,通过分析linux ext4文件系统的结构,提供找到磁盘设备中的有效数据块的方法(即磁盘中哪个地址有数据,哪些地址是空块)

方案:

1.前期工作:磁盘(容量10G)格式化为ext4文件系统并挂载

1. 查看磁盘情况

首先,需要查看系统中所有磁盘的情况。这可以通过fdisk -llsblk命令来完成。

	sudo fdisk -l

	sudo lsblk

这将显示磁盘列表,包括它们的大小、分区情况以及挂载点(如果有的话)。

2. 使用fdiskparted进行分区

假设决定在/dev/sdb上创建一个新的分区。可以使用fdiskparted工具来创建分区。这里使用fdisk作为示例。

	sudo fdisk /dev/sdb

fdisk的命令行界面中,可以按照以下步骤操作:

  • 输入m获取帮助。
  • 输入n创建一个新分区。
  • 选择分区类型(通常选择主分区或逻辑分区)。
  • 选择分区的起始和结束位置。
  • 输入w保存并退出。
3. 格式化分区为EXT4

一旦分区被创建,可以使用mkfs命令来格式化它为EXT4文件系统。

	sudo mkfs.ext4 /dev/sdbX

这里的/dev/sdbX是刚刚创建的分区。注意X是分区号,例如sdb1sdb2等。

4. 挂载分区

在格式化之后,可以将分区挂载到文件系统的某个目录上,以便访问它。首先,创建一个挂载点目录:

	sudo mkdir /mnt/mydisk

然后,将分区挂载到这个目录:

	sudo mount /dev/sdbX /mnt/mydisk

现在,可以通过/mnt/mydisk目录来访问这个分区上的文件了

2.分析linux ext4文件系统的结构,提供找到磁盘设备中的有效数据块的方法

  • 打开设备文件:

    • 使用os.Open打开指定的磁盘设备文件(例如/dev/sdX1)。
    • 设备文件通常是块设备文件,表示磁盘分区。
  • 读取超级块:

    • 超级块位于磁盘的特定偏移量(通常为1024字节)。它包含文件系统的总体信息,如块大小、块组数量、每组的块和索引节点数量等。
    • readSuperblock函数从磁盘中读取超级块数据,并将其解析为Superblock结构体。
  • 读取块组描述符:

    • ext4文件系统将磁盘划分为多个块组。每个块组有一个描述符,存储块组的各种信息,包括块位图的偏移量。
    • readGroupDescriptor函数根据块组编号(示例中假设为块组0)计算块组描述符的位置,并读取描述符的内容。描述符包括块位图的位置。
  • 读取块位图:

    • 块位图是一个表示哪些数据块被使用的位图。每一位表示一个块的使用状态。
    • readBlockBitmap函数根据块位图在磁盘上的偏移量读取块位图数据。偏移量由超级块和块组描述符提供。
  • 打印有效数据块:

    • printUsedBlocks函数分析块位图数据。每个位表示一个块是否被使用。如果位为1,则块被使用;如果为0,则块为空闲。
    • 该函数遍历块位图,输出所有被标记为使用的数据块号。

3.附件代码


package main

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

const (
	// 偏移量为超级块的位置
	SUPERBLOCK_OFFSET = 1024
	// 每个块的大小
	BLOCK_SIZE = 4096
	// 块组描述符的数量
	GROUP_DESCRIPTOR_SIZE = 32
)

type Superblock struct {
	InodesCount       uint32
	BlocksCount       uint32
	ReservedBlocks    uint32
	FreeBlocksCount   uint32
	FreeInodesCount   uint32
	FirstDataBlock    uint32
	BlockSize         uint32
	BlocksPerGroup    uint32
	InodesPerGroup    uint32
	// 省略其他字段
}

type GroupDescriptor struct {
	TotalBlocks        uint32
	FreeBlocks         uint32
	FreeInodes         uint32
	Directories        uint32
	BlockBitmap        uint32
	InodeBitmap        uint32
	InodeTable         uint32
	// 省略其他字段
}

func readSuperblock(file *os.File) (*Superblock, error) {
	file.Seek(SUPERBLOCK_OFFSET, 0)
	buf := make([]byte, 1024)
	if _, err := file.Read(buf); err != nil {
		return nil, err
	}
	sb := &Superblock{}
	bufReader := bytes.NewReader(buf)
	if err := binary.Read(bufReader, binary.LittleEndian, sb); err != nil {
		return nil, err
	}
	return sb, nil
}

func readGroupDescriptor(file *os.File, group int) (*GroupDescriptor, error) {
	offset := SUPERBLOCK_OFFSET + BLOCK_SIZE + int64(group*GROUP_DESCRIPTOR_SIZE)
	file.Seek(offset, 0)
	buf := make([]byte, GROUP_DESCRIPTOR_SIZE)
	if _, err := file.Read(buf); err != nil {
		return nil, err
	}
	gd := &GroupDescriptor{}
	bufReader := bytes.NewReader(buf)
	if err := binary.Read(bufReader, binary.LittleEndian, gd); err != nil {
		return nil, err
	}
	return gd, nil
}

func readBlockBitmap(file *os.File, blockBitmapOffset int64) ([]byte, error) {
	file.Seek(blockBitmapOffset, 0)
	bitmap := make([]byte, BLOCK_SIZE)
	if _, err := file.Read(bitmap); err != nil {
		return nil, err
	}
	return bitmap, nil
}

func printUsedBlocks(bitmap []byte) {
	for i, b := range bitmap {
		for j := 0; j < 8; j++ {
			if (b & (1 << j)) != 0 {
				fmt.Printf("Block %d is used\n", i*8+j)
			}
		}
	}
}

func main() {
	if len(os.Args) < 2 {
		fmt.Println("Usage: go run main.go <device>")
		return
	}
	device := os.Args[1]

	file, err := os.Open(device)
	if err != nil {
		fmt.Printf("Error opening device: %v\n", err)
		return
	}
	defer file.Close()

	sb, err := readSuperblock(file)
	if err != nil {
		fmt.Printf("Error reading superblock: %v\n", err)
		return
	}

	fmt.Printf("Superblock Info: %+v\n", sb)

	// 假设块组0
	gd, err := readGroupDescriptor(file, 0)
	if err != nil {
		fmt.Printf("Error reading group descriptor: %v\n", err)
		return
	}

	blockBitmapOffset := SUPERBLOCK_OFFSET + int64(sb.BlockSize)*int64(gd.BlockBitmap)
	bitmap, err := readBlockBitmap(file, blockBitmapOffset)
	if err != nil {
		fmt.Printf("Error reading block bitmap: %v\n", err)
		return
	}

	printUsedBlocks(bitmap)
}

解释

  1. 超级块readSuperblock函数从磁盘读取超级块信息,包含文件系统的基本参数。
  2. 块组描述符readGroupDescriptor函数读取指定块组的描述符,从中获取块位图的位置。
  3. 块位图readBlockBitmap函数从指定位置读取块位图数据。
  4. 打印有效块printUsedBlocks函数根据块位图的数据打印出被使用的块号。

注意事项

  • 文件路径:运行时需要指定设备路径,例如/dev/sdX1
  • 偏移量和大小:不同文件系统或配置可能需要调整偏移量和大小。
  • 权限:访问设备文件需要适当的权限,通常需要以root用户或具有相应权限的用户运行。