Pipe

tar 多卷模式下的磁帶結束檢測 (ENOSPC),帶有用於加密的管道

  • November 18, 2020

tar多卷模式下使用依賴於ENOSPC錯誤來檢測第一個磁帶的結尾並提示使用者輸入下一個磁帶。要模擬此行為,請考慮以下範例,方法是寫入/dev/full

tar -cvf - --multi-volume . > /dev/full

正如預期的結果

[...]
Prepare volume #2 for ‘-’ and hit return:

tar通過加密程序(如aespipe或)傳輸輸出時會出現問題gpg

tar -cvf - --multi-volume . | gpg -c --batch -q --passphrase 123 > /dev/full

這導致gpg退出程式碼2

gpg: [stdout]: write error: No space left on device
gpg: [stdout]: write error: No space left on device
gpg: filter_flush failed on close: No space left on device

ENOSPC 顯然沒有傳播到 tar,它不知道具體的 errno。有沒有辦法從bash 腳本中擷取錯誤gpg並“重新引發”ENOSPC 錯誤?tar

例如,將 tar 與命名管道一起使用會導致管道一旦gpg失敗,並且 tar 隨後與 SIGPIPE 141 一起存在 - 但是ENOSPC仍然必須以某種方式向 tar 發出信號,而不是管道損壞錯誤。

我想避免指定固定磁帶大小的解決方法。我也知道使用mbuffer處理磁帶跨越,這是不可取的,因為磁帶不能單獨提取。

編輯:我剛剛意識到這將變得更加複雜,因為在遇到 ENOSPC 時已經離開 tar 並且在緩衝區中的數據很可能會失去。儘管大多數磁帶驅動程序實現都允許在此之後進行另一個寫入操作,但 gpg 和 aespipe 不包含將數據保存在緩衝區中的重試邏輯。

編輯 2:進一步的研究表明,star在 FreeBSD 上-compress-program可以選擇執行加密-multivolnew-volume-script=...引發錯誤

star: Operation not permitted. Cannot lock fifo memory.
star: Can only compress files

寫入設備而不是文件時。所以這也是一個死胡同。

不可能通過管道將寫入錯誤傳播回來

即使有可能通過某種黑客攻擊,管道正在緩衝,並且當管道讀取器嘗試“發送信號”管道寫入器時,後者可能已經寫入了導致錯誤進一步下降的數據, 已經獲得了成功的狀態 (>0) 並相應地更新了它的狀態。為了讓它發揮作用,寫作過程必須及時回溯。最重要的是,管道讀取器本身可能會進行自己的緩沖和狀態保持,這會不同步。

唯一的出路是tar直接呼叫加密常式,而不是通過某種通道傳遞數據。而不是修改它的原始碼並重新編譯它,這可以通過猴子/實時修補它來完成,LD_PRELOAD它覆蓋write()庫函式並在將數據傳遞給原始write().

如何ENOSPC使用LD_PRELOADhack進行模擬

這將導致對 fd 1 (stdout) 的寫入在ENOSPC嘗試向其寫入超過 40960 字節時失敗,之後它會重置計數器並再次成功,等等。

如果您希望它使用tar -cf filename,而不是tar -cf -,您可能應該將fd == 1測試更改為fd != 2.

$ cat <<'EOT' >enospc.c
#define _GNU_SOURCE
#include <unistd.h>
#include <dlfcn.h>
#include <err.h>
#include <errno.h>

#define MAX     40960

ssize_t write(int fd, const void *b, size_t z){
       ssize_t w;
       static typeof (write) *o_write;
       static size_t count;
       if(!o_write) o_write = dlsym(RTLD_NEXT, "write");
       if(fd == 1 && count + z > MAX){
               count = 0;
               errno = ENOSPC;
               return -1;
       }
       w = o_write(fd, b, z);
       if(w > 0) count += w;
       return w;
}
EOT

$ cc -Wall -shared enospc.c -o enospc.so -ldl

$ seq -f 'n foo%04g.tar' 1 10000 |
 LD_PRELOAD=./enospc.so tar -M -cf- /etc/X11 > foo0000.tar
tar: Removing leading `/' from member names
Prepare volume #2 for ‘-’ and hit return: Prepare volume #3 for ‘/tmp/foo0001.tar’ and hit return: Prepare volume #4 for ‘/tmp/foo0002.tar’ and hit return: Prepare volume #5 for ‘/tmp/foo0003.tar’ and hit return: Prepare volume #6 for ‘/tmp/foo0004.tar’ and hit return: Prepare volume #7 for ‘/tmp/foo0005.tar’ and hit return: Prepare volume #8 for ‘/tmp/foo0006.tar’ and hit return: Prepare volume #9 for ‘/tmp/foo0007.tar’ and hit return: $

$ ls foo000*
foo0000.tar  foo0002.tar  foo0004.tar  foo0006.tar  foo0008.tar
foo0001.tar  foo0003.tar  foo0005.tar  foo0007.tar

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