ubuntu26.04坏块分区隔离急速版

总共3个步骤

扫描 使用 sudo python3 s1.py

python 复制代码
import os
import sys
import time
import struct
import fcntl
import subprocess

# --- 配置参数 ---
DEVICE_PATH = "/dev/sdb"       # 目标设备路径,请根据实际情况修改
OUTPUT_FILE = "found_bad_blocks.txt"  # 坏块记录文件
BLOCK_SIZE = 4096              # 块大小 (字节)
SKIP_SIZE_MB = 500             # 发现坏块后跳过的距离 (MB)
ALL_current_block = 83876080  #自定义当前块!!!


# 计算跳跃的块数
SKIP_BLOCKS = (SKIP_SIZE_MB * 1024 * 1024) // BLOCK_SIZE

def get_block_device_size(device_path):
    """
    获取块设备大小的稳健方法
    优先使用 os.path.getsize,失败则尝试 ioctl,最后尝试 blockdev 命令
    """
    # 方法 1: 使用 os.path.getsize (现代 Linux 内核通常支持)
    try:
        size = os.path.getsize(device_path)
        if size > 0:
            return size
    except Exception as e:
        print(f"os.path.getsize 尝试失败: {e}")

    # 方法 2: 使用 ioctl BLKGETSIZE64
    try:
        # O_NONBLOCK 防止在某些特殊设备上挂起
        fd = os.open(device_path, os.O_RDONLY | os.O_NONBLOCK)
        try:
            # BLKGETSIZE64 请求码: 0x80081272
            # 缓冲区必须是 8 字节
            buf = b'\x00' * 8
            ret_buf = fcntl.ioctl(fd, 0x80081272, buf)
            # '<Q' 表示小端序 unsigned long long (8 bytes)
            size = struct.unpack('<Q', ret_buf)
            if size > 0:
                return size
        except Exception as e:
            print(f"ioctl 尝试失败: {e}")
        finally:
            os.close(fd)
    except Exception as e:
        print(f"打开设备文件失败: {e}")

    # 方法 3: 使用 blockdev 命令作为最后的手段
    try:
        result = subprocess.run(
            ['blockdev', '--getsize64', device_path], 
            stdout=subprocess.PIPE, 
            stderr=subprocess.PIPE, 
            text=True,
            timeout=5
        )
        if result.returncode == 0:
            return int(result.stdout.strip())
    except Exception as e:
        print(f"blockdev 命令执行失败: {e}")

    return 0

def check_block_readability(device_path, block_index):
    """
    尝试读取指定索引的块。
    如果成功返回 True,如果失败(IOError/OSError)返回 False。
    """
    try:
        with open(device_path, 'rb') as f:
            f.seek(block_index * BLOCK_SIZE)
            data = f.read(BLOCK_SIZE)
            if len(data) < BLOCK_SIZE:
                return False 
            return True
    except OSError:
        return False
    except Exception:
        return False

def scan_device():
    bad_blocks = []
    
    # 获取设备大小
    print(f"正在检测设备 {DEVICE_PATH} ...")
    device_size = get_block_device_size(DEVICE_PATH)
    
    if device_size == 0:
        print(f"错误: 无法获取设备 {DEVICE_PATH} 的大小。")
        print("请执行以下检查:")
        print(f"1. 运行 'lsblk {DEVICE_PATH}' 确认设备存在且显示大小。")
        print(f"2. 运行 'sudo blockdev --getsize64 {DEVICE_PATH}' 确认内核能识别大小。")
        print("3. 确保设备未处于休眠状态且连接正常。")
        return

    total_blocks = device_size // BLOCK_SIZE
    
    print(f"设备: {DEVICE_PATH}")
    print(f"总大小: {device_size / (1024**3):.2f} GB")
    print(f"总块数 (4K): {total_blocks}")
    print(f"策略: 遇到坏块跳过 {SKIP_SIZE_MB} MB ({SKIP_BLOCKS} blocks)")
    print("-" * 30)
    #更改当前进度
    current_block = ALL_current_block #6790000
    start_time = time.time()

    try:
        # 以只读、二进制模式打开
        with open(DEVICE_PATH, 'rb') as f:
            while current_block < total_blocks:
                # 进度显示
                if current_block % 10000 == 0:
                    progress = (current_block / total_blocks) * 100
                    elapsed = time.time() - start_time
                    speed = current_block / elapsed if elapsed > 0 else 0
                    sys.stdout.write(f"\r扫描进度: {progress:.2f}% | 当前块: {current_block} | 速度: {speed:.0f} blocks/s")
                    sys.stdout.flush()

                # 检测当前块
                if not check_block_readability(DEVICE_PATH, current_block):
                    print(f"\n[!] 发现坏块 at Block Index: {current_block} (Offset: {current_block * BLOCK_SIZE})")
                    bad_blocks.append(current_block)
                    
                    # 记录到文件
                    with open(OUTPUT_FILE, 'a') as out_f:
                        out_f.write(f"{current_block}\n")
                    
                    print(f"[->] 跳过 {SKIP_SIZE_MB} MB...")
                    current_block += SKIP_BLOCKS
                else:
                    current_block += 1
                    
        print("\n扫描完成。")
        print(f"共发现 {len(bad_blocks)} 个坏块区域。")
		# 记录到文件
        with open(OUTPUT_FILE, 'a') as out_f:
            out_f.write(f"{total_blocks}\n")
        print(f"结果已保存至: {OUTPUT_FILE}")

    except PermissionError:
        print("错误: 权限不足。请使用 sudo 运行此脚本。")
    except FileNotFoundError:
        print(f"错误: 设备 {DEVICE_PATH} 不存在。")
    except Exception as e:
        print(f"发生未知错误: {e}")

if __name__ == "__main__":
    if os.geteuid() != 0:
        print("警告: 建议以 root 权限运行以访问原始设备。")
    
    # 不清空旧结果
    #if os.path.exists(OUTPUT_FILE):
		#os.remove(OUTPUT_FILE)
        
    scan_device()

2 生成分区文件 使用 sudo python3 s2.py

python 复制代码
#!/usr/bin/env python3
import os
import sys

# 配置常量
INPUT_FILE = "/root/found_bad_blocks.txt"
OUTPUT_FILE = "/root/large_gap_bad_blocks.txt"  # 导出的新文件路径
BLOCK_SIZE = 4096          # 块大小:4096 字节
THRESHOLD_MB = 512 * 3 + 100           # 间隔阈值:1 MB 自定义!!!!

def export_large_gap_blocks(input_path, output_path, block_size, threshold_mb):
    """
    读取排序后的坏块文件,找出间隔超过阈值的坏块对,并导出到新文件。
    """
    if not os.path.exists(input_path):
        print(f"错误: 输入文件 {input_path} 不存在")
        return

    # 计算阈值对应的字节数
    threshold_bytes = threshold_mb * 1024 * 1024
    
    prev_block = None
    line_count = 0
    exported_count = 0
    
    print(f"正在处理文件: {input_path}")
    print(f"阈值设置: 间隔 > {threshold_mb} MB")
    
    try:
        # 以写入模式打开输出文件
        with open(input_path, 'r') as infile, open(output_path, 'w') as outfile:
            for line in infile:
                line = line.strip()
                if not line:
                    continue
                
                try:
                    current_block = int(line)
                except ValueError:
                    continue
                
                line_count += 1
                
                # 如果不是第一行,计算间隔
                if prev_block is not None:
                    gap_bytes = (current_block - prev_block) * block_size
                    
                    # 如果间隔大于阈值,将这两个坏块号写入新文件
                    if gap_bytes > threshold_bytes:
                        outfile.write(f"{prev_block}\n")
                        outfile.write(f"{current_block}\n")
                        exported_count += 2  # 每次写入两个坏块号
                
                # 更新上一个坏块
                prev_block = current_block

        print(f"处理完成。")
        print(f"总读取坏块数: {line_count}")
        print(f"导出的坏块数: {exported_count}")
        print(f"结果已保存至: {output_path}")

    except Exception as e:
        print(f"发生错误: {e}")
        sys.exit(1)

if __name__ == "__main__":
    export_large_gap_blocks(INPUT_FILE, OUTPUT_FILE, BLOCK_SIZE, THRESHOLD_MB)

3 分区 使用 sudo python3 s3.py

python 复制代码
#!/usr/bin/env python3
import os
import sys
import struct
import subprocess
import time

# --- 配置参数 ---
DEVICE = "/dev/sdb"                  # 目标磁盘设备,请根据实际情况修改
INPUT_FILE = "/root/large_gap_bad_blocks.txt" # 坏块/分区定义文件
BLOCK_SIZE = 4096                    # 逻辑块大小 (字节)
SECTOR_SIZE = 512                    # 物理扇区大小 (字节)
SGDISK_CMD = "sgdisk"                # GPT分区工具
# 1M 256 blk
# 262,144 1G对应的blk
# THRESHOLD_MB =  512 * 3 + 100           # 间隔阈值:1 MB  ##1G有效空间,两边空余50M
ALL_start_blk_add_blk=256 * (512 + 50) 
ALL_end_blk_xor_blk=256 * 50


def check_root():
    if os.geteuid() != 0:
        print("错误: 此脚本需要 root 权限 (sudo) 才能操作原始磁盘设备。")
        sys.exit(1)

def check_dependencies():
    if not subprocess.run(['which', SGDISK_CMD], stdout=subprocess.PIPE, stderr=subprocess.PIPE).returncode == 0:
        print(f"错误: 未找到命令 '{SGDISK_CMD}'。")
        print("请安装 gdisk 包: sudo apt-get install gdisk")
        sys.exit(1)

def read_partition_definitions(filepath):
    """
    读取文件,每两行为一组:起始块, 结束块
    返回: [(start_block, end_block), ...]
    """
    if not os.path.exists(filepath):
        print(f"错误: 文件 {filepath} 不存在。")
        sys.exit(1)

    pairs = []
    with open(filepath, 'r') as f:
        lines = [line.strip() for line in f if line.strip()]
    
    if len(lines) % 2 != 0:
        print("错误: 输入文件格式错误。行数必须为偶数(每两行定义一个分区:起始块, 结束块)。")
        sys.exit(1)
        
    for i in range(0, len(lines), 2):
        try:
            start_blk = int(lines[i]) + ALL_start_blk_add_blk
            end_blk = int(lines[i+1]) - ALL_end_blk_xor_blk
            if start_blk < 0 or end_blk < 0:
                raise ValueError("块索引不能为负数")
            if start_blk > end_blk:
                print(f"警告: 跳过无效区间 (Start {start_blk} > End {end_blk})")
                continue
            pairs.append((start_blk, end_blk))
        except ValueError as e:
            print(f"错误: 无法解析行 '{lines[i]}' 或 '{lines[i+1]}'。{e}")
            sys.exit(1)
            
    if not pairs:
        print("错误: 文件中没有有效的分区定义。")
        sys.exit(1)
        
    return pairs

def wipe_disk(device):
    """
    清除磁盘现有的分区表 (MBR/GPT)
    """
    print(f"[步骤 1/3] 正在清除 {device} 上的现有分区表...")
    try:
        # -Z: zap (destroy) GPT and MBR data structures
        result = subprocess.run(
            [SGDISK_CMD, '-Z', device],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True
        )
        if result.returncode != 0:
            # 有时如果磁盘全是0,sgdisk可能会报错说没发现有效签名,这通常是可以接受的
            if "No valid GPT or MBR" in result.stderr or "No partition table found" in result.stderr:
                print("   磁盘似乎已经是空的或无有效分区表。")
            else:
                print(f"   清除失败: {result.stderr}")
                return False
        else:
            print("   分区表已清除。")
        
        # 强制内核重新读取分区表
        subprocess.run(['partprobe', device], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        time.sleep(1) 
        return True
    except Exception as e:
        print(f"   执行清除命令时出错: {e}")
        return False

def create_partitions(device, partitions):
    """
    使用 sgdisk 创建分区
    注意: sgdisk 使用 512 字节扇区作为单位。
    转换: 扇区索引 = 块索引 * (BLOCK_SIZE / SECTOR_SIZE)
    """
    multiplier = BLOCK_SIZE // SECTOR_SIZE  # 通常为 8
    
    print(f"[步骤 2/3] 开始在 {device} 上创建 {len(partitions)} 个分区...")
    
    for idx, (start_blk, end_blk) in enumerate(partitions, 1):
        start_sec = start_blk * multiplier
        end_sec = end_blk * multiplier
        
        # sgdisk 分区号从 1 开始
        part_num = idx
        
        print(f"   -> 分区 {part_num}: 块 [{start_blk}-{end_blk}] => 扇区 [{start_sec}-{end_sec}]")
        
        try:
            # -n: new partition
            # 格式: -n part_num:start_sector:end_sector
            cmd = [SGDISK_CMD, '-n', f'{part_num}:{start_sec}:{end_sec}', device]
            
            result = subprocess.run(
                cmd,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True
            )
            
            if result.returncode != 0:
                print(f"      [失败] {result.stderr.strip()}")
                return False
            else:
                print(f"      [成功]")
                
        except Exception as e:
            print(f"      [错误] {e}")
            return False
            
    return True

def verify_partitions(device):
    """
    验证并打印最终分区表
    """
    print(f"[步骤 3/3] 验证分区表...")
    try:
        result = subprocess.run(
            [SGDISK_CMD, '-p', device],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True
        )
        if result.returncode == 0:
            print("\n--- 当前磁盘分区结构 ---")
            print(result.stdout)
            print("------------------------")
            return True
        else:
            print(f"验证失败: {result.stderr}")
            return False
    except Exception as e:
        print(f"验证时出错: {e}")
        return False

def main():
    print("="*50)
    print("GPT 自动分区工具")
    print("="*50)
    
    check_root()
    check_dependencies()
    
    print(f"目标设备: {DEVICE}")
    print(f"定义文件: {INPUT_FILE}")
    print(f"块大小:   {BLOCK_SIZE} Bytes")
    print("-"*50)
    
    # 1. 读取定义
    partitions = read_partition_definitions(INPUT_FILE)
    print(f"已加载 {len(partitions)} 个分区定义。")
    
    # 确认操作
    print("\n警告: 此操作将清除磁盘所有数据并重建分区表!")
    confirm = input("请输入 'yes' 继续: ")
    if confirm.lower() != 'yes':
        print("操作已取消。")
        sys.exit(0)
        
    # 2. 清除磁盘
    if not wipe_disk(DEVICE):
        print(" aborting due to wipe failure.")
        sys.exit(1)
        
    # 3. 创建分区
    if not create_partitions(DEVICE, partitions):
        print(" aborting due to partition creation failure.")
        sys.exit(1)
        
    # 4. 验证
    verify_partitions(DEVICE)
    
    print("\n所有操作完成。")

if __name__ == "__main__":
    main()

然后查看分区 lsblk

sdb 8:16 0 320G 0 disk

├─sdb1 8:17 0 1.8G 0 part

├─sdb2 8:18 0 8.4G 0 part

├─sdb3 8:19 0 15.2G 0 part

├─sdb4 8:20 0 10.2G 0 part

├─sdb5 8:21 0 27.7G 0 part

└─sdb6 8:22 0 29.2G 0 part

然后使用pvcreate /dev/sdb1 ...

组成vg 就可以正常使用了

或者直接raid0组在一起也可以

我这个硬盘最终能用的320-33=297G Total free space is 71011036 sectors (33.9 GiB)

只损失了34G还行,挂上去临时下载还是可以的

root@pi2b4:~# sudo python3 3-gpt-part.py

==================================================

GPT 自动分区工具

==================================================

目标设备: /dev/sdb

定义文件: /root/large_gap_bad_blocks.txt

块大小: 4096 Bytes


已加载 17 个分区定义。

警告: 此操作将清除磁盘所有数据并重建分区表!

请输入 'yes' 继续: yes

步骤 1/3 正在清除 /dev/sdb 上的现有分区表...

分区表已清除。

步骤 2/3 开始在 /dev/sdb 上创建 17 个分区...

-> 分区 1: 块 484119-806934 => 扇区 3872952-6455472

成功

-> 分区 2: 块 1192898-3225565 => 扇区 9543184-25804520

成功

-> 分区 3: 块 4128213-7950113 => 扇区 33025704-63600904

成功

-> 分区 4: 块 8106785-10620977 => 扇区 64854280-84967816

成功

-> 分区 5: 块 11842308-18940744 => 扇区 94738464-151525952

成功

-> 分区 6: 块 19765257-27259688 => 扇区 158122056-218077504

成功

-> 分区 7: 块 27416360-28943204 => 扇区 219330880-231545632

成功

-> 分区 8: 块 29099876-31163871 => 扇区 232799008-249310968

成功

-> 分区 9: 块 31320543-33129883 => 扇区 250564344-265039064

成功

-> 分区 10: 块 35010872-36724873 => 扇区 280086976-293798984

成功

-> 分区 11: 块 37023802-59326067 => 扇区 296190416-474608536

成功

-> 分区 12: 块 59482739-61660948 => 扇区 475861912-493287584

成功

-> 分区 13: 块 62547259-62821821 => 扇区 500378072-502574568

成功

-> 分区 14: 块 62978493-64274660 => 扇区 503827944-514197280

成功

-> 分区 15: 块 65001743-65763271 => 扇区 520013944-526106168

成功

-> 分区 16: 块 65919943-66484623 => 扇区 527359544-531876984

成功

-> 分区 17: 块 66641295-78130006 => 扇区 533130360-625040048

成功

步骤 3/3 验证分区表...

--- 当前磁盘分区结构 ---

Disk /dev/sdb: 625142448 sectors, 298.1 GiB

Model: BIAZE

Sector size (logical/physical): 512/512 bytes

Disk identifier (GUID): 2A60937A-192D-4705-91B1-B67487E3B60C

Partition table holds up to 128 entries

Main partition table begins at sector 2 and ends at sector 33

First usable sector is 34, last usable sector is 625142414

Partitions will be aligned on 2048-sector boundaries

Total free space is 71011036 sectors (33.9 GiB)

Number Start (sector) End (sector) Size Code Name

1 3872768 6455472 1.2 GiB 8300

2 9541632 25804520 7.8 GiB 8300

3 33024000 63600904 14.6 GiB 8300

4 64854016 84967816 9.6 GiB 8300

5 94738432 151525952 27.1 GiB 8300

6 158121984 218077504 28.6 GiB 8300

7 219330560 231545632 5.8 GiB 8300

8 232798208 249310968 7.9 GiB 8300

9 250562560 265039064 6.9 GiB 8300

10 280086528 293798984 6.5 GiB 8300

11 296189952 474608536 85.1 GiB 8300

12 475860992 493287584 8.3 GiB 8300

13 500377600 502574568 1.0 GiB 8300

14 503826432 514197280 4.9 GiB 8300

15 520013824 526106168 2.9 GiB 8300

16 527357952 531876984 2.2 GiB 8300

17 533129216 625040048 43.8 GiB 8300


所有操作完成。

root@pi2b4:~#

相关推荐
两只羊ovo16 分钟前
给 Agent 装上手脚:手写 mini-Cursor,从建项目到跑起来
前端
茉莉玫瑰花茶19 分钟前
知识库的构建 [ 3 ]
开发语言·python
旧梦952722 分钟前
Java 枚举类详解:从基础语法到高级用法
java·开发语言·python
yivifu22 分钟前
查拼音程序升级版
开发语言·python
Hive_MOM27 分钟前
制造企业库存精益化管理数字化:从“库存压资金、找货靠人翻“到“库存水位实时可见、资金周转提速“
前端·制造
2601_9621741730 分钟前
Spring 核心技术解析【纯干货版】- XI:Spring 数据访问模块 Spring-Oxm 模块精讲
数据库·python·spring
光影少年35 分钟前
react navite调试方案:Flipper、远程调试
前端·javascript·react native·react.js·前端框架
海兰39 分钟前
【应用】基于 Next.js 16 + Python mplfinance的金融K线图与技术指标可视化平台(二)
javascript·python·金融
计算机魔术师43 分钟前
智谱开源 GLM-5.3 模型权重,主打智能体编程与网络防御
前端