Skip to content
GuangYu-yuPublic

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

125 Commits

Folders and files

Repository files navigation

DiskEdit

把磁盘与磁盘镜像统一当作可按偏移读写的字节存储,在其上做分区表编辑、文件系统操作与分区搬移。

  • 分区表:GPT/MBR 的解析与写入不经过 libparted/sfdisk。GPT 提交按「备条目数组 → 备头 → 主条目数组 → 主头」四步,每步 sync,任意落点断电都至少留一份自洽副本,不一致可经 CRC 检出。块设备上挂载中的分区内核重读必然失败,该路径改用 sfdisk --no-reread 写表 + partx -u 同步内核。
  • 文件系统:mkfs/fsck/resize/label/uuid 经 offset loop(losetup)调用各 FS 官方工具。
  • 镜像与块设备:.img 与 /dev/sdX 走同一条代码路径,差别只在打开方式。

运行要求

权限:

操作 镜像文件 块设备
info / plan 不需要 root 不需要 root
分区表写入(new/add/delete/resize-part/copy/set/undo/move/apply) 不需要 root 需要 root
FS 层(mkfs/resizefs/check/set label|uuid) 需要 root 需要 root

resize 分两种:需要动文件系统时要 root;加 --no-fs 表示不把 FS 增长算作后置条件(镜像上无需 root)。目标 FS 无法识别(unknown)时,不带 --no-fs 一律拒绝——"认不出"不等于"里面没有东西"。

FS 层要 root 是因为经 losetup 映射分区;块设备写入靠设备节点权限,权限不足时在打开阶段即失败。

平台能力门:只有 Linux 承诺安全写盘;其他平台在命令分派处按能力白名单拒绝,只放行 info、plan、abandon。其余命令(含 undo)退出码 30,文案为 this platform does not support safe disk writes — run this on Linux. Nothing was written (no target bytes, lock, journal, checkpoint, or log).。能力门在几何计算、取锁与任何伴随文件创建之前,先于确认门。

占用边界:块设备写入靠整盘 O_EXCL 独占打开——任一分区被挂载或作 swap 时,内核以 EBUSY 拒绝打开;写盘前另有占用闸复核(块设备上逐分区探测,是纵深防御)。镜像文件没有这层内核语义,占用闸改为反向枚举 loop:以已打开镜像句柄的 (st_dev, st_ino) 与 LOOP_GET_STATUS64 返回的 (lo_device, lo_inode) 比对,命中后检查该 loop 及其分区是否被挂载(遍历所有可读 mount namespace)、是否为活动 swap、是否被块设备栈持有。判定不依赖 loop/backing_file 路径,硬链接别名、已删除的 backing 路径、其它 mount namespace 内的挂载都能命中。本进程登记的 loop 独占权是进程级名字表,条目每次消费都按当前活映射复核(名字只用于找回登记);占用判定以 backing 的 (st_dev, st_ino) 命中该文件的全部 loop,因此能判“这个文件正被映射/持有”,不区分同一文件系统上重复叠挂的是哪一条。直接改表的命令(new/add/delete/create/set name|type|flag/undo)要求目标完全空闲:任一分区在用都会让新表与内核视图分叉,故不区分本次改哪个分区。copy 是例外——它只在经重叠校验的空闲区新增条目,不改既有分区的起止,也不搬移目标分区的 extent。搬移类命令(resize-part/move/resize/apply)与 mkfs 按操作形状区分:起点不变、末端只增的写入放行"仅被 dm 持有"的目标(LVM PV / dm-crypt 的增长语义),搬移、缩小与整段重写一律拒绝。探测中任一枚举到的 loop 节点打不开、ioctl 返回 ENXIO 之外的错误、sysfs 读失败、/proc/<pid>/mountinfo 因权限读不到(如 hidepid),都按"无法确认"拒绝写入(退出码 30)——即使那个 loop 与目标无关;这是安全优先的运行环境限制,不能当作目标被占用的证据。

LVM 收尾的边界:镜像内 PV 的 pvresize/lvextend 经临时 loop 映射执行。若该 PV 已被另一个 loop 映射且 VG 中有活动 LV,LVM 会在同一 PV 的两个设备视图间优先选择"已被 LV 使用"的那个,对临时映射的 pvresize 不生效(退出码仍为 0)。本工具据后置条件判据报 PARTIAL(20) 并打印补救指引,不宣称成功;分区表扩展已提交,PV、LV 与 FS 均未被改动。要在这种配置下完成 LVM 收尾,需先停用 VG、释放用户侧的 loop 再重跑。本工具不替其它持有栈抬高 loop 的 sizelimit:外部 loop 与本次请求 offset 相同、但 sizelimit 不一致(抬高与收缩都算)时直接拒绝,文案指向由持有该映射的那一层解绑并重绑(losetup -d <dev>,让栈重新映射)后再试;--grow-lv 同理,持有方不先重绑就不会替你完成 LVM 收尾。

镜像 PV 上 --grow-lv 成功时,lvextend -r 会把原本非活动的 LV 留在活动状态,临时 loop 因此不能立即解除:losetup -d 返回成功,内核把该 loop 标为 autoclear,映射在 VG 停用(vgchange -an)时随之释放。这是临时映射的生命周期,不是泄漏;停用 VG 后 losetup -a 不再列出该镜像。

并发边界:目标的独占锁是 advisory 的——它只在本工具的各次调用之间互斥,不阻止其它程序直接写同一个目标(机制见 src/targetlock.rs 模块注释);块设备的整盘 O_EXCL 与挂载占用闸只挡内核语义内的写者。镜像的 loop 反查是写盘前的时点判定:检查完成到实际写入之间,外部进程仍可能新建 loop、启动 swap 或挂载该镜像,本工具的锁不约束这些进程。分区表写入在解析期读一次表,提交期写回的是那份快照;提交前会重读条目数组并与快照逐条比对(本层改过的槽位允许等于目标值),任何一条对不上即拒绝写表,外部进程在窗口内的改动因此不会被静默覆盖(修复步走同一判据,覆盖 add/rename/set/delete 与搬移、resize 的全过程)。要与其它写者共用同一目标,请在外部自行串行化。inode 级第二道凭据只在 Unix 上取,非 Unix 平台只承诺锁文件这一道,硬链接 / 符号链接 / junction 别名各拿一把锁、各有一份现场,经别名路径的并发使用在支持边界之外(std::fs::File::try_lock 在 Windows 上是强制锁且锁整个文件,不能替代这道凭据)。

设备身份边界:块设备的 journal / checkpoint / 锁按所在整设备的身份键落在系统状态目录下——分区节点取父设备的键,/dev/sdb1 与整盘 /dev/sdb 因此共用同一现场与同一把锁。设备层身份按序取 dm/uuid、dm/name、md/uuid、wwid、device/wwid、device/serial,都取不到时退化为 sysfs 节点自身的 {节点名}-{容量扇区数};sysfs 拓扑(节点、partition 属性、父设备)读不出来时拒绝,不退化成调用方路径的 {devname}-{容量}(那个退化会让整盘与分区落进两个序列化域)。容量不参与分区身份(见 src/dev/identity.rs、src/dev/topology.rs)。退化键里没有绑定具体物理盘的成分:同槽位换入同节点名、同容量的另一块盘会落进同一身份域,上一块盘留下的现场会被新盘继承(未收尾的作业表现为目标被占用;undo 的分叉核对以盘上字节判定,逐字节相同的克隆盘不构成区分)。镜像身份按路径派生,标识的是"这个路径"而非"这个文件"。Linux 上 realpath 只合并符号链接与路径写法,不合并硬链接:同一 inode 的两个硬链接名各派生一份 journal / checkpoint,经 A 名中断的作业在 B 名上不可见,也无法从 B 名续跑或 undo——这类作业要用同一个名字收尾。inode 级第二道凭据(仅 Unix)仍把经别名路径的并发写入串行化,但两份现场不会因此合并。非 Unix 平台只承诺锁文件这一道,硬链接 / 符号链接 / junction 别名各拿一把锁、各有一份现场,经别名路径的并发使用在支持边界之外(std::fs::File::try_lock 在 Windows 上是强制锁且锁整个文件,不能替代这道凭据)。

恢复现场的身份核对:journal 与 checkpoint 随现场记下创建时的目标指纹,回放 / 续跑前逐字段比对,任一可比字段不符即拒绝。字段的证据强度按平台与载体分级:尺寸(Metadata::len())处处可比;device id / inode 在 Unix 上取 Metadata 的扩展字段,在 Windows 上从句柄取 (卷序列号, 文件索引),两者之外的平台恒零,真块设备的节点上同样恒零(udev 重建节点即变,节点元数据长度也恒 0)。零值表示给不出这个字段,不参与比对,也不被当作匹配。尺寸本身不足以唯一标识目标:同尺寸的替换文件仍可能通过该字段。一个可比字段都拿不到的目标(无 device id、也无文件身份)在任何字节落盘之前就被拒绝:那种现场中断后既无法续跑也无法 undo,不能等恢复时才发现无路可走;此时只有一个「无证据」分支的旧现场仍会照旧被回放前的核对拒掉,undo 打印 note 说明这一点。

journal v7 的盘 GUID 凭据:journal v7 在原有身份段之外再记目标 GPT 的 Disk GUID,三态为 Present / Unavailable / GeneratedThisOperation。Present 必须与盘面一致;Unavailable 表示给不出该字段,不构成证据;GeneratedThisOperation 只出现在记录侧(表由本次操作生成),与盘面 GUID 不做比对,收养与否仍由旧身份段决定。v6 旧格式不自动迁移:写命令以 30 拒绝并说明本构建既不回放也不迁移、abandon 是唯一释放方式;undo 以 10 拒绝并说明不迁移、不回放、不删除。

恢复文件的落点:journal / checkpoint 的“现场”由 RecoveryFile 判定,唯一构造点用 symlink_metadata。符号链接、目录、FIFO、设备节点等非普通文件一律不是本工具的恢复文件(即使链接指向一个普通 journal)。删除与改名只作用于该路径入口本身,不跟随链接、不改动链接目标;条目不是恢复文件或删除失败时按“现场仍在”处理,不报干净。

Windows 上的目录项持久化未验证:删除或改名恢复文件后,本工具不宣称目录项已落盘,只打印 note: removed <path>; persistence of the directory entry was not verified on this platform(改名同理)。掉电后该条目是否消失/保留未验证,不能当作“已持久化”。

验证范围:本工具按 Linux 的 util-linux / 各 FS 官方工具的命令行契约工作,不绑定发行版;回归套件(tests/wsl/)在 Ubuntu + util-linux 2.41.3 上验证,其他发行版与版本未经验证。占用探测按下列分档记录,不把未跑过的路径说成已通过:

证据等级 内容
已运行验证 loop 身份编码(st_dev/st_ino 对 lo_device/lo_inode)、未绑定 loop 的 ENXIO、硬链接别名与已删除 backing 路径、跨 mount namespace 的挂载命中、loop 与分区挂载、活动 swap、sysfs holders、已退出进程的 mountinfo(EINVAL)、探测失败 fail-closed(退出码 30)、LVM PV 无持有者时的 --grow-lv 全链、活动 LV 持有分区时 AllowDm 不被通用规则拒绝(链进入 LVM 步)且 mkfs 整段重写仍被拒、挂载镜像上 new/add/delete/create/set name|type|flag/undo 全部被拒且盘面字节不变、多个 loop 命中时拒绝消息指向实际持有者、非 root 用户(打不开 root:disk 的 loop 节点)的镜像操作可用
未验证 容器与独立 PID namespace 组合下的 loop 枚举完整性;hidepid 等权限限制下 mountinfo 不可读的实际部署(代码按探测失败拒绝,未在真实 hidepid 环境运行验证);上表之外的发行版与内核版本;镜像内 PV 已被另一 loop 映射且 VG 有活动 LV 时,--grow-lv 的完整收尾链(见上节,LVM 重复 PV 规则使 pvresize 对临时映射 no-op,本工具报 PARTIAL(20),该场景下工具不宣称成功)
设计限制 占用闸是写盘前的时点判定,不阻止检查后外部新建 loop / swap / 挂载;镜像侧枚举只覆盖本进程可见的 loop 节点,因权限不足打不开的 loop 节点(root:disk 权限)按未绑定跳过——绑定与节点访问同权限类,该类用户建不出指向本镜像的绑定;节点不存在、ENODEV 等无法确认身份的情形按探测失败拒绝,此边界随内核/udev 权限模型演进;fail-closed 对无关 loop 的探测失败同样生效;journal / checkpoint 的零值字段不参与比对

续跑边界:GPT 路径的搬移与单分区 resize 各带 checkpoint,中断后重跑原命令即续跑。MBR 没有 checkpoint——表项写入后中断,只能先用 undo 回滚或 abandon 释放现场,再看盘上现状重新发起;重跑同一条命令不会被当成续跑。缩容步进记三态(未缩 / 已验证 / 未验证):回读不出 FS 尺寸时停在缩容这一步并按 PARTIAL(20) 报未收尾,重跑先回读实际尺寸,确认未缩才重做一次缩容。收尾时 checkpoint 只在三条件同时成立才撤除:操作已提交(表/几何已是目标态)、最终状态已核验(该写的 FS / 内核视图 / swap / LVM 步骤没有新增待办)、后续恢复不再依赖它(LVM 等延后收尾已消化)。任一条不成立即保留,并打印 checkpoint <path> retained: <reason>,<reason> 取 the operation is not committed; the checkpoint is the only resume point / the final state is not verified; pending work remains / later recovery steps still depend on it。删除本身失败时事务报 PARTIAL(20) 而不是成功。搬移的数据按批同步并推进 durable 边界(同步先于 checkpoint,边界永不超前于已落盘字节);提交批量按提交耗时自适应,崩溃后最多重做 256 MiB 已拷数据,块字节大于该窗口时退化为逐块提交。自重叠搬移(位移小于源区间长度)无论哪条路径都逐块提交——批量攒下的未持久化块会探入重做区间,重做会读到被同批写覆盖的源数据。

外部工具(按需存在即可,用到才查找):

用途 工具
内核同步 / 在线写表 sfdisk、partx
loop 与挂载 losetup、udevadm、mount、umount
ext2/3/4 mke2fs、e2fsck、resize2fs、dumpe2fs、tune2fs
xfs mkfs.xfs、xfs_growfs、xfs_repair、xfs_admin
btrfs mkfs.btrfs、btrfs、btrfstune
ntfs mkfs.ntfs、ntfsresize、ntfsfix、ntfslabel
f2fs mkfs.f2fs、fsck.f2fs、resize.f2fs
vfat mkfs.vfat、fsck.vfat、fatresize、fatlabel
exfat mkfs.exfat、fsck.exfat、exfatlabel
swap mkswap、swaplabel
LVM pvs、lvs、vgs、pvresize、lvextend

所有外部调用注入 LC_ALL=C,避免本地化输出破坏解析。

命令

<TARGET> 为镜像路径或块设备;:N 寻址第 N 个分区(1 起),:last 指最后一个可操作分区(MBR 上扩展容器不算,无分区表的目标两者都不接受),--grow 同此两形。diskedit help <CMD> 查看单命令详助。

命令 作用 写盘
info <TARGET> 分区表、逐分区 FS 识别(LVM PV 显示为 lvm2_pv) 否
resize <TARGET>[:N] <SIZE> 分区 + FS 一起扩缩,自动选在线/离线 是
move <TARGET>:N --start <LBA|end> 移动分区,数据跟随 是
copy <TARGET>:N --start <LBA|end> [--name S] [--keep-partuuid] 复制分区到新位置,源不动 是
create <TARGET> [--size SIZE] [--name S] [--fs F] 在空闲空间创建分区 是
delete <TARGET>:N --yes 删除分区表项(不擦数据区) 是
set <TARGET>:N name S | label S | uuid U | flag F on|off 设置分区属性 是
check <TARGET>:N FS 一致性检查 视工具
mkfs <TARGET>:N <FS> --yes 创建文件系统 是
resizefs <TARGET>:N / <MOUNTPOINT> [BYTES] 单独调整 FS(形态即模式::N 离线,挂载点在线) 是
undo <TARGET> --yes 按 journal 撤销本工具的写入 是
new / add / resize-part / plan / apply 低阶命令 见详助

resize 的 SIZE 支持绝对值(10G)、增量(+2G / -500M)、百分比(+10% / -10%)与 grow 关键字。

--sector-size N 只对镜像文件适用:镜像不携带扇区信息,默认按 512 处理,4Kn 镜像须显式指定。块设备的逻辑扇区大小由内核报告(BLKSSZGET),显式给出即拒绝;各命令是否接受该选项以 help <CMD> 为准(abandon 不接受,resizefs 的在线形式(挂载点)显式拒绝)。

选项

各命令实际接受哪些选项以 help <CMD> 为准;下表是全部选项与取值形态,取值列为 — 的选项不带值。

选项 取值 含义
--align A 放置对齐:mib、cyl 或 none(默认 mib = 1MiB;none 按给定 LBA 使用)
--allow-move — resize 在 GPT 上允许搬开挡路的分区
--chunk-size N 搬移的恢复粒度(MiB,默认 4);中断后续跑须用同值
--end LBA add、resize-part 的结束 LBA
--fs F create 时一并创建的文件系统
--grow N|last plan、apply 要扩展的分区
--grow-lv — resize 在 LVM PV 上继续扩 LV 及其文件系统
--grow-to-end — resize-part 末端钉在可用空间末尾
--keep-partuuid — copy 沿用源分区的 PARTUUID
--lv NAME --grow-lv 要扩的 LV(省略时取该 PV 上唯一的 LV)
--name S 分区名
--no-fs — 跳过文件系统步骤
--overwrite-partition-data — new 授权覆盖旧分区落入新表元数据写入区的字节
--random — set uuid 要求目标生成新随机值(仅 ntfs)
--sector-size N 镜像逻辑扇区大小(512..=65536 的 2 的幂)
--size SIZE create 的绝对值尺寸
--start LBA|end 新起点 LBA;move、copy 另接受 end
--table gpt|msdos new 创建的分区表类型
--type T 分区类型(GPT 用 GUID 文本,MBR 用 0xXX)
--yes — 确认越过 undo 边界的写入:搬移(move、apply --grow、发生位移的 resize-part、走搬移的 resize)、复制(copy)、缩容(resize 到更小 SIZE)、在新落点建 FS(create --fs)。判据是几何算出的影响面;没有 --yes 一律直接拒绝,不弹交互提示,非交互与交互一致。resize-part 缩小但不位移不要求 --yes;delete、mkfs、undo、abandon 的 --yes 语义不变

check 不是零写入:ext 走 e2fsck -fp(preen 自动修复),ntfs 走 ntfsfix -d(清 dirty 位),两者都可能写入。只读检查为 xfs(xfs_repair -n)、btrfs(btrfs check)、vfat(fsck.vfat -n)、exfat(fsck.exfat -n)、f2fs(fsck.f2fs 无参)。

LVM PV 不能用 mkfs 重建:工具显式拒绝,须用 pvcreate(8) / wipefs(8)。

支持的 FS

识别(fsid.rs,按魔数):ext、xfs、btrfs、f2fs、ntfs、vfat、exfat、swap、squashfs、erofs、hfsplus、apfs、lvm2_pv;都不匹配则为 unknown。

动作覆盖:

FS mkfs 扩容 缩容 check label uuid
ext2/3/4 ✓ ✓ ✓ ✓ ✓ ✓
xfs ✓ ✓ — ✓ ✓ ✓
btrfs ✓ ✓ ✓ ✓ ✓ ✓
ntfs ✓ ✓ ✓ ✓ ✓ ✓
f2fs ✓ ✓ — ✓ — —
vfat ✓ ✓ — ✓ ✓ —
exfat ✓ — — ✓ ✓ —
swap ✓ ✓ — — ✓ ✓
lvm2_pv — ✓ — — — —
  • ntfs 的 uuid 只能生成随机新序号,ntfslabel 不支持指定值。
  • vfat 的 label 在本工具内只收 ASCII:卷标字段是 11 字节、按 DOS/OEM 代码页存放,本工具不做代码页转换(非 ASCII 标签请直接用 fatlabel 设置)。
  • swap 的扩容是扩完表项后用 mkswap 重建,不搬数据;UUID、PARTUUID 与分区号保持。
  • lvm2_pv 的扩容只到 PV 层,加 --grow-lv 才继续扩 LV 及其文件系统;缩容一律拒绝——链与扩容反向(先 lvreduce -r 缩 LV+FS,再 pvresize --setphysicalvolumesize,最后改分区表),本工具只管后者。

缩容时先缩 FS、后改分区边界;FS 缩不动就不动表。

OpenWrt combined 布局(同分区内 squashfs/erofs 只读根 + 尾部 RW overlay):扩容识别内层 FS 后经 offset loop 只扩 RW 层,只扩不缩;内层非 ext/f2fs 时拒绝。尾部 RW 层尚未格式化(首启安装现场)不算失败:分区照扩,该层由首次挂载时的 fstools 初始化,本次无事可做。多设备 btrfs 拒绝扩容。

License

MIT(见 LICENSES/)

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages