Tar

如何說服 tar (等)歸檔塊設備內容?

  • March 15, 2021

我有六個 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。

所以最近我想用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"
}

那是肉和土豆,真的。它寫入標頭併計算校驗和——相對而言,這是唯一困難的部分。它執行ustar標題格式…也許。至少,它模仿了 GNUtar似乎認為的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並完成它。這有時甚至可能有效,但是當存檔中有多個成員時它會變得混亂。不過,校驗和真的很容易。

首先,將其設為 7 個空格 - (我認為這是一個奇怪的 gnu 東西,正如規範所說的 8,但無論如何 - hack 就是 hack)。然後將標頭中每個字節的八進制值相加。那是你的校驗和。因此,在執行標頭之前您需要文件元數據,或者您沒有校驗和。這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

偽file.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包含任意數量的文件的文件。格式很簡單——你在新存檔的文件系統中定義一個目標文件的名稱/路徑,你給它一個模式和一個所有者,然後你告訴它執行哪個程序並從中讀取標準輸出。您可以根據需要創建任意數量 - 您可以使用 LZMA、GZIP、LZ4、XZ…嗯,還有更多…您喜歡的壓縮格式。最終結果是一個存檔,您可以將cd.

更多關於格式的資訊:

當然,這不僅僅是一個存檔——它是一個壓縮的、可掛載的 Linux 文件系統映像。它的格式是 Linux 核心的——它是一個香草核心支持的文件系統。通過這種方式,它與 vanilla Linux 核心一樣普遍。所以如果你告訴我你正在執行一個tar沒有安裝程序的普通 Linux 系統,我會懷疑——但我可能會相信你。但是如果你告訴我你正在執行一個squashfs不支持文件系統的普通 Linux 系統,我不會相信你。

引用自:https://unix.stackexchange.com/questions/151009