如何说服tar(等)归档块设备内容?


13

我有六个Linux逻辑卷,它们一起支持一个虚拟机。虚拟机当前已关闭,因此易于为它们拍摄一致的图像。

我想将所有六个图像打包到一个档案中。琐碎地,我可以做这样的事情:

cp /dev/Zia/vm_lvraid_* /tmp/somedir
tar c /tmp/somedir | whatever

但这当然会创建一个额外的副本。我想避免多余的副本。

显而易见的方法:

tar c /dev/Zia/vm_lvraid_* | whatever

之所以不起作用,是因为tar可以识别特殊的文件(在这种情况下为符号链接),并且基本上将其存储ln -s在档案中。或者,使用--dereference或直接指向/dev/dm-X,它将其识别为特殊文件(设备文件),并将其基本上存储mknod在存档中。

我搜索了tar的命令行选项以覆盖此行为,但找不到任何。我也尝试过cpio,同样的问题,也找不到任何替代它的选项。我也尝试过7z(同上)。与相同pax。我什至尝试过zip,这让自己感到困惑。

编辑:查看GNU tar和GNU cpio的源代码,看来他们两个都无法做到这一点。至少,并非没有严重的欺骗手段(不能禁用对设备文件的特殊处理)。因此,建议您使用严重的欺骗手段或使用其他实用程序。

TLDR:是否有一些存档程序可以将多个磁盘映像打包在一起(从原始设备中获取)并流输出输出,而无需进行额外的磁盘副本?我的偏好将以通用格式输出,例如POSIX或GNU tar。


我说服了。
mikeserv

Answers:


11

所以最近我想用tar。一些调查告诉我,我做不到,这有点荒谬。我确实想出了这个奇怪的split --filter="cat >file; tar -r ..."东西,但是,那太慢了。而且我读tar的越多,看起来就越荒谬。

您看到tar的只是记录的串联列表。组成文件不会以任何方式更改-它们完全在归档文件中。但是它们在512字节的边界上被阻止,并且在每个文件之前都有一个header。而已。标头格式也非常非常简单。

所以,我写了我自己的tar。我把它叫做... shitar

z() (IFS=0; printf '%.s\\0' $(printf "%.$(($1-${#2}))d"))
chk() (IFS=${IFS#??}; set -f; set -- $(     
        printf "$(fmt)" "$n" "$@" '' "$un" "$gn"               
);  IFS=; a="$*"; printf %06o "$(($(
        while printf %d+ "'${a:?}"; do a=${a#?}; done 2>/dev/null
)0))")                                                                 
fmt() { printf '%s\\'"${1:-n}" %s "${1:+$(z 99 "$n")}%07d" \
    %07o %07o %011o %011o "%-${1:-7}s" ' 0' "${1:+$(z 99)}ustar  " %s \
    "${1:+$(z 31 "$un")}%s"
}

真的是肉和土豆。它写入标头并计算chksum-相对而言,这是唯一困难的部分。它采用ustar标头格式... 也许。至少,它模拟了GNU tar似乎认为ustar报头格式的东西,以至于它不会抱怨。还有更多,只是我还没有真正凝结它。在这里,我将向您展示:

for f in 1 2; do echo hey > file$f; done
{ tar -cf - file[123]; echo .; } | tr \\0 \\n | grep -b .

0:file1                      #filename - first 100 bytes
100:0000644                  #octal mode - next 8
108:0001750                  #octal uid,
116:0001750                  #gid - next 16
124:00000000004              #octal filesize - next 12
136:12401536267              #octal epoch mod time - next 12
148:012235                   #chksum - more on this
155: 0                       #file type - gnu is weird here - so is shitar
257:ustar                    #magic string - header type
265:mikeserv                 #owner
297:mikeserv                 #group - link name... others shitar doesnt do
512:hey                      #512-bytes - start of file   
1024:file2                   #512 more - start of header 2
1124:0000644
1132:0001750
1140:0001750
1148:00000000004
1160:12401536267
1172:012236
1179: 0
1281:ustar  
1289:mikeserv
1321:mikeserv
1536:hey
10240:.                     #default blocking factor 20 * 512

那是tar。一切都与填充\0空值,所以我只是把em\newlines的可读性。和shitar

#the rest, kind of, calls z(), fmt(), chk() + gets $mdata and blocks w/ dd
for n in file[123]
do d=$n; un=$USER; gn=$(id --group --name)
   set -- $(stat --printf "%a\n%u\n%g\n%s\n%Y" "$n")
   printf "$(fmt 0)" "$n" "$@" "$(chk "$@")" "$un" "$gn"
   printf "$(z $((512-298)) "$gn")"; cat "$d"  
   printf "$(x=$(($4%512));z $(($4>512?($x>0?$x:512):512-$4)))"
done |
{ dd iflag=fullblock conv=sync bs=10240 2>/dev/null; echo .; } |
tr \\0 \\n | grep -b .

输出值

0:file1                 #it's the same. I shortened it.
100:0000644             #but the whole first file is here
108:0001750
116:0001750
124:00000000004
136:12401536267
148:012235              #including its checksum
155: 0
257:ustar  
265:mikeserv
297:mikeserv
512:hey
1024:file2
...
1172:012236             #and file2s checksum
...
1536:hey
10240:.

我说的那里,因为那不是shitar目的- tar已经做到了这一点。我只是想展示它是如何工作的-这意味着我需要触摸一下chksum。如果不是那样的话,我只是将文件dd放在头上tar并完成它。有时甚至可能会起作用,但是当存档中有多个成员时会变得混乱。不过,chksum确实很容易。

首先,将其设置为7个空格- (我认为这是一个奇怪的gnu东西,正如规范所说,为8,但无论如何-hack就是hack)。然后将标头中每个字节的八进制值相加。那是你的钱。因此,在执行标头之前,您需要文件元数据,否则您将没有chksum。那主要是一个ustar档案。

好。现在,这意味着要执行的操作:

cd /tmp; mkdir -p mnt     
for d in 1 2 3                                                
do  fallocate -l $((1024*1024*500)) disk$d
    lp=$(sudo losetup -f --show disk$d)
    sync
    sudo mkfs.vfat -n disk$d "$lp"
    sudo mount  "$lp" mnt
    echo disk$d file$d | sudo tee mnt/file$d
    sudo umount mnt
    sudo losetup -d "$lp"
done

这样就制作了三个500M磁盘映像,分别格式化和挂载,并向每个磁盘写入文件。

for n in disk[123]
do d=$(sudo losetup -f --show "$n")
   un=$USER; gn=$(id --group --name)
   set -- $(stat --printf "%a\n%u\n%g\n$(lsblk -bno SIZE "$d")\n%Y" "$n")
   printf "$(fmt 0)" "$n" "$@" "$(chk "$@")" "$un" "$gn"
   printf "$(z $((512-298)) "$gn")"
   sudo cat "$d"
   sudo losetup -d "$d"
done | 
dd iflag=fullblock conv=sync bs=10240 2>/dev/null |
xz >disks.tar.xz

注意 -显然,阻止设备只会始终正确阻止。很方便。

这就是tar流内磁盘设备文件的内容,并将输出通过管道传输到xz

ls -l disk*
-rw-r--r-- 1 mikeserv mikeserv 524288000 Sep  3 01:01 disk1
-rw-r--r-- 1 mikeserv mikeserv 524288000 Sep  3 01:01 disk2
-rw-r--r-- 1 mikeserv mikeserv 524288000 Sep  3 01:01 disk3
-rw-r--r-- 1 mikeserv mikeserv    229796 Sep  3 01:05 disks.tar.xz

现在,关键时刻

 xz -d <./disks.tar.xz| tar -tvf -
-rw-r--r-- mikeserv/mikeserv 524288000 2014-09-03 01:01 disk1
-rw-r--r-- mikeserv/mikeserv 524288000 2014-09-03 01:01 disk2
-rw-r--r-- mikeserv/mikeserv 524288000 2014-09-03 01:01 disk3

万岁!萃取...

xz -d <./disks.tar.xz| tar -xf - --xform='s/[123]/1&/'  
ls -l disk*
-rw-r--r-- 1 mikeserv mikeserv 524288000 Sep  3 01:01 disk1
-rw-r--r-- 1 mikeserv mikeserv 524288000 Sep  3 01:01 disk11
-rw-r--r-- 1 mikeserv mikeserv 524288000 Sep  3 01:01 disk12
-rw-r--r-- 1 mikeserv mikeserv 524288000 Sep  3 01:01 disk13
-rw-r--r-- 1 mikeserv mikeserv 524288000 Sep  3 01:01 disk2
-rw-r--r-- 1 mikeserv mikeserv 524288000 Sep  3 01:01 disk3
-rw-r--r-- 1 mikeserv mikeserv    229796 Sep  3 01:05 disks.tar.xz

比较...

cmp disk1 disk11 && echo yay || echo shite
yay

还有坐骑

sudo mount disk13 mnt
cat mnt/*
disk3 file3

因此,shitar我猜在这种情况下,执行正常。我宁愿不进入所有它的事情不会做的很好。但是,我会说-至少不要在文件名中使用换行符。

考虑到我提供的替代方法,您也可以这样做-也许应该这样做-可以这样做squashfs。您不仅可以从流中获取单个存档,而且还mount可以并将其内置到内核的vfs

伪文件。example

# Copy 10K from the device /dev/sda1 into the file input.  Ordinarily
# Mksquashfs given a device, fifo, or named socket will place that special file
# within the Squashfs filesystem, this allows input from these special
# files to be captured and placed in the Squashfs filesystem.
input f 444 root root dd if=/dev/sda1 bs=1024 count=10

# Creating a block or character device examples

# Create a character device "chr_dev" with major:minor 100:1 and
# a block device "blk_dev" with major:minor 200:200, both with root
# uid/gid and a mode of rw-rw-rw.
chr_dev c 666 root root 100 1
blk_dev b 666 0 0 200 200

您可能还可以使用btrfs (send|receive)将子卷流式传输到stdin您喜欢的任何支持功能的压缩器中。当然,在决定将其用作压缩容器之前,不需要存在该子卷。

不过,关于squashfs...

我不相信自己在伸张正义。这是一个非常简单的示例:

 cd /tmp; mkdir ./emptydir
 mksquashfs ./emptydir /tmp/tmp.sfs -p \
    'file f 644 mikeserv mikeserv echo "this is the contents of file"'                             

Parallel mksquashfs: Using 6 processors
Creating 4.0 filesystem on /tmp/tmp.sfs, block size 131072.
[==================================================================================|] 1/1 100%
Exportable Squashfs 4.0 filesystem, gzip compressed, data block size 131072
        compressed data, compressed metadata, compressed fragments,... 
###...
###AND SO ON
###...

echo '/tmp/tmp.sfs /tmp/imgmnt squashfs loop,defaults,user 0 0'|
    sudo tee -a /etc/fstab >/dev/null

mount ./tmp.sfs     
cd ./imgmnt
ls

total 1
-rw-r--r-- 1 mikeserv mikeserv 29 Aug 20 11:34 file

cat file

this is the contents of file

cd ..
umount ./imgmnt

那只是的内联-p参数mksquash。您可以在文件中-pf包含任意数量的文件。格式很简单-在新档案的文件系统中定义目标文件的名称/路径,为它提供一个模式和一个所有者,然后告诉它要执行哪个过程并从中读取stdout。您可以创建任意数量的压缩文件-可以使用LZMA,GZIP,LZ4,XZ ...嗯,还有更多...压缩格式。最终的结果是将您存档cd

不过,有关格式的更多信息:

当然,这不仅是存档,而且是压缩的,可安装的Linux文件系统映像。它的格式是Linux内核的格式-它是香草内核支持的文件系统。这样,它与普通Linux内核一样普遍。因此,如果您告诉我您正在运行tar未安装该程序的普通Linux系统,那我会怀疑-但我可能会相信您。但是,如果您告诉我您正在运行一个squashfs不支持该文件系统的普通Linux系统,我不会相信您。


迈克,我们可以麻烦您创建一个小型的独立示例,让人们尝试一下吗?看来您可能至少在做上述事情的一部分,但我不确定。在input f 444 root root dd if=/dev/sda1 bs=1024 count=10为f文件输入?也许最好创建一个玩具设备,将其填充数据并从中写入数据?所有这些都需要root吗?
Faheem Mitha 2014年

@FaheemMitha-是的,我可以做到,但是我在这里没有做到。链接指向官方文档-直接从中获取。如果我做了一个命令示例,那会更好。我以前做过-很酷。无论如何-该input文件是squashfs归档文件中的文件-运行命令所生成的文件系统映像。执行此操作时mksquash,可以为运行的命令指定这些伪文件命令,并stdout在压缩时从中捕获这些命令。
mikeserv

@FaheemMitha-哦,它不需要root进行压缩,尽管它可以进行挂载-它是生成的文件系统映像。所有Linux Live光盘都使用相同的文件系统。实际上,一件很酷的事情是,您可以使用那些伪文件创建一个拥有根目录的映像,而无需成为根目录-例如设置设备文件和任意MAJ:MIN编号。
mikeserv

我想应该可以创建设备文件,对其进行写操作,然后再从该文件中进行安装而无需挂载它,对吗?因此,也许不需要root,这显然是更好的选择。
Faheem Mitha 2014年

好吧,这里没有涉及btrfs,所以不会起作用。但是,squashfs非常疯狂,可能会起作用。尽管它有一个不常见的存档格式的缺点。
derobert 2014年

4

您的问题困扰了我一段时间,我想我已经找到了可行的解决方案。

我认为您可以使用该-si{NAME}标志实现7z的目标。

您将能够适应您的需求。

7z a test.7z -siSDA2.txt < /dev/sda1
7z a test.7z -siSDA2.txt < /dev/sda2

7z l test.7z 

7-Zip [64] 9.20  Copyright (c) 1999-2010 Igor Pavlov  2010-11-18
p7zip Version 9.20 (locale=en_US.UTF-8,Utf16=on,HugeFiles=on,8 CPUs)

Listing archive: test.7z

--
Path = test.7z
Type = 7z
Method = LZMA
Solid = -
Blocks = 2
Physical Size = 1770
Headers Size = 162

   Date      Time    Attr         Size   Compressed  Name
------------------- ----- ------------ ------------  ------------------------
2014-08-19 22:01:08 .....         6314          804  SDA1.txt
2014-08-19 22:01:11 .....         6314          804  SDA2.txt
------------------- ----- ------------ ------------  ------------------------
                                 12628         1608  2 files, 0 folders

编辑:删除猫的无用使用


举一个可供人们尝试的小例子可能会有所帮助。例如,创建一个块设备,对其进行写入,然后从中写入。不需要root将是一个加号。
Faheem Mitha 2014年

在示例中,/ dev / sda1是一个块设备。cat命令的目的是将设备的内容转储到stdout。然后7z创建(或更新)归档文件,并将数据存储在stdin的-si参数指定的文件名中。归档中的结果是每个块设备的内容。“ cat”命令需要root才能从设备读取数据。
托尼

那是无用用途,但在其他方面非常合适。奇怪的是,我的7z联机帮助页没有提到-si可以使用文件名,但是可以。它不是完美的(输出不能通过管道传递到某个地方),但是绝对是迄今为止以通用格式输出的最好的。
derobert

是否需要root用户的@FaheemMitha将取决于系统上的权限设置,尽管只有root用户才能创建新的块设备。
derobert 2014年

@derobert删除了猫:)
托尼
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.