使用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,因为 tar 不知道具体的 errno。有没有办法使用 bash 脚本捕获错误gpg
并“重新引发”ENOSPC 错误?tar
例如,将 tar 与命名管道一起使用一旦gpg
失败就会导致管道损坏,并且 tar 随后会以 SIGPIPE 141 的形式存在——但是ENOSPC
仍然需要以某种方式向 tar 发出信号,而不是管道损坏错误。
我想避免指定固定磁带大小的解决方法。我还知道使用来mbuffer
处理磁带跨越,这是不可取的,因为磁带无法单独提取。
编辑:我刚刚意识到这会变得更加复杂,因为遇到 ENOSPC 时已经离开 tar 并位于缓冲区中的数据很可能会丢失。尽管大多数磁带驱动程序实现允许此后进行另一次写入操作,但 gpg 和 aespipe 不包含重试逻辑来将数据保存在缓冲区中。
编辑 2:进一步的研究表明,star
在 FreeBSD 上,可以选择-compress-program
结合执行加密-multivol
并new-volume-script=...
引发错误
star: Operation not permitted. Cannot lock fifo memory.
star: Can only compress files
当写入设备而不是文件时。所以这也是一个死胡同。
答案1
不可能通过管道传播写错误
即使可以通过某种黑客手段实现,管道也是如此缓冲当管道读取器尝试向管道写入器“发出信号”时,后者可能已经写入了导致错误的数据,已经获得了成功状态(> 0)并相应地更新了其状态。为了让它发挥作用,写作过程必须回到过去。最重要的是,管道读取器本身可能会进行自己的缓冲和状态保持,这会导致不同步。
唯一的出路是tar
直接调用加密例程,而不是通过某种通道传递数据。无需修改其源代码并重新编译它,可以通过猴子/实时修补它LD_PRELOAD
来替代write()
库函数并在将数据传递到原始write()
.
如何ENOSPC
使用LD_PRELOAD
hack进行模拟
ENOSPC
一旦尝试写入超过 40960 字节,这将导致对 fd 1 (stdout) 的写入失败,之后它会重置计数器并再次成功,等等。
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
答案2
你的问题有几个问题:
检测磁带结束情况的正确方法是检查是否有返回 0 且未设置 errno 的 write(2)。因此,支持多卷磁带存档的正确 tar 实现会检查 write(2) 返回 0。
仅当写入文件系统中的普通文件时才会创建 errno ENOSPC,因此该 errno 不适合用作多卷磁带存档的基础。
不可能通过管道将写错误传播回来。
UNIX tar 命令不支持多卷归档
gtar 支持写入多卷存档,但无法正确读回,概率约为 100%。 5%,因为它并不总是能够将后续存档识别为按顺序正确的卷号。这是由 gtar 中的设计缺陷引起的,如果不引入新的不兼容的多卷格式,就无法修复该缺陷。
star 仅在被称为 root 时尝试锁定 FIFO 内存。它在您引用的消息中写入的错误代码的意思是:不是sueruser(root)。您是否在“root”权限有限的环境中运行此明星实例?
如果输出不是普通文件,star 不会运行压缩程序,因为压缩程序的输出不会被阻止,但 tar 实现需要阻止输出。如果您想在这种情况下进行压缩,请调用如下命令:
star -c ... | compress ...
一般来说,如果您确实想加密 tar 程序的输出,则需要通过管理多卷磁带输出的程序来传输加密程序的输出。
顺便说一句:请随时发送更多信息以获得更深入的答案。