总共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:~#
