我正在创建带有多个 ext4 分区的磁盘映像。每个 ext4 分区的内容都是用其创建的,mkfs.ext4
并且生成的文件系统映像文件是“稀疏的”。
当我dd
从所有这些单独的文件系统创建系统映像(一开始有一个分区表)时,生成的映像将不再稀疏。
这样做是错误的,dd conv=sparse
因为这会使输出变得稀疏全部-NUL 输出块。但这不是我想要的。如果输入是稀疏的,我希望输出也是稀疏的。
这是必要的,因为最终我希望能够刷新映像,这样bmaptool
就不必将“稀疏”的块写入物理设备,从而节省写入映像的时间。如果我将所有只有零的块视为稀疏,那么磁盘上非 NUL 的随机数据将不会被覆盖,这对于文件系统上实际上应该包含零的文件来说是一个问题。
那么,是否有一个工具可以做到这一点dd
,并能够根据给定的偏移量将一个文件复制到另一个文件中,但将输入文件中稀疏的块保留在输出文件中?
答案1
由于我无法找到一个可以正常工作dd
但又能在输出中重现输入的稀疏性的工具,因此我编写了一个小型 Python 脚本来执行此操作:
#!/usr/bin/env python
import subprocess
import os
import sys
def main():
infd = os.open(sys.argv[1], os.O_RDONLY)
inlength = os.lseek(infd, 0, os.SEEK_END)
outfd = os.open(sys.argv[2], os.O_CREAT | os.O_WRONLY)
outlength = os.lseek(outfd, 0, os.SEEK_END)
offset = int(sys.argv[3])
curr = 0
while True:
try:
data = os.lseek(infd, curr, os.SEEK_DATA)
except OSError:
# no more data
break
try:
hole = os.lseek(infd, data, os.SEEK_HOLE)
except OSError:
# no hole afterwards, copy until EOF
hole = inlength
print(
f"copying range {data}..{hole} ({100*hole/inlength:.2f}%)", file=sys.stderr
)
os.copy_file_range(
infd, outfd, hole - data, offset_src=data, offset_dst=data + offset
)
curr = hole
if outlength < inlength + offset:
os.truncate(outfd, inlength + offset)
os.close(infd)
os.close(outfd)
if __name__ == "__main__":
main()
根据du
我的最终系统图像现在仅占用 3.7 G 的磁盘空间,而不是图像文件大小的全部 5.1 G。
对于我的 3 G ext4 文件系统,上面的主循环只迭代大约 500 次,因此大部分时间都花在 Python 上copy_file_range()
而不是 Python 上,所以我并不担心代码不是用 C 编写的,但将其移植到 C 上可能很简单。
编辑
好的,这是一个用 C 语言编写的版本:
#define _GNU_SOURCE
#define _LARGEFILE64_SOURCE
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
int main(int argc, char *argv[]) {
if (argc != 3 && argc != 4) {
fprintf(stderr, "Usage: %s infile outfile [offset]\n", argv[0]);
exit(EXIT_FAILURE);
}
int infd = open(argv[1], O_RDONLY);
if (infd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
off64_t inlength = lseek64(infd, 0, SEEK_END);
if (inlength == -1) {
perror("lseek64");
exit(EXIT_FAILURE);
}
int outfd = open(argv[2], O_CREAT | O_WRONLY);
if (outfd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
off64_t outlength = lseek64(outfd, 0, SEEK_END);
if (outlength == -1) {
perror("lseek64");
exit(EXIT_FAILURE);
}
long long offset = 0;
if (argc == 4) {
offset = strtoll(argv[3], NULL, 10);
if (errno != 0) {
perror("strtoll");
exit(EXIT_FAILURE);
}
}
off64_t curr = 0;
while (true) {
off64_t data = lseek64(infd, curr, SEEK_DATA);
if (data == -1) {
break;
}
off64_t hole = lseek64(infd, data, SEEK_HOLE);
if (hole == -1) {
hole = inlength;
}
off64_t off_out = data + offset;
ssize_t ret = copy_file_range(infd, &data, outfd, &off_out, hole - data, 0);
if (ret == -1) {
perror("copy_file_range");
exit(EXIT_FAILURE);
}
curr = hole;
}
if (outlength < inlength + offset) {
int ret = ftruncate(outfd, inlength + offset);
if (ret == -1) {
perror("ftruncate");
exit(EXIT_FAILURE);
}
}
close(infd);
close(outfd);
exit(EXIT_SUCCESS);
}