3

Using tar in multi-volume mode relies on a ENOSPC error to detect the end of the first tape and prompt the user for the next tape. To simulate this behaviour consider the following example by writing to /dev/full

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

as expected results in

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

A problem arises when piping the output of tar through an encyption program like aespipe or gpg

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

which causes gpg to exit with code 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

The ENOSPC is obviously not propagated to tar, which isn't made aware of the specific errno. Is there a way to catch the error from gpg and "re-raise" the ENOSPC error to tar with a bash script?

For example, using tar with a named pipe results in a broken pipe once gpg fails and tar subsequently exists with SIGPIPE 141 -- however ENOSPC still has to be signaled to tar in some way instead of the broken pipe error.

I would like to avoid the workaround of specifying a fixed tape size. I am also aware of using mbuffer to handle tape spanning, which is undesireable because tapes can not be extracted individually.

EDIT: I just realized this is going to be a lot more complicated, as the data that has already left tar and was in the buffer when ENOSPC was encountered is most likely lost. Though most tape driver implementations allow another write operation after that, gpg and aespipe include no retry logic to save the data in the buffer.

EDIT 2: Further research shows that star on FreeBSD with the -compress-program option to perform the encryption in conjunction with -multivol and new-volume-script=... raises the error

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

when writing to a device instead of a file. So that's a dead end too.

6
  • After some quick testing, if you're using tar -M -cf - > ... and hit enter at the "Prepare volume prompt", tar will try to write to its STDIN. That doesn't feel right, I'm guessing that not many people have used its multi-volume feature with -f - ;-) Also, it does make no sense to have multiple volumes concatenated into a single stream; when you try to extract that, you'll get a lot of "skipping to the next header" errors. What you probably want is tar -cf - ... | gpg ... | split_cat where split_cat can be implemented in various ways depending on your requirements. – user414777 Oct 5 '20 at 0:12
  • You're right - I should probably have clarified that my actual implementation so far uses a named pipe and the tar --new-volume-script option to handle the restart of the encryption program on the new stream (so they are not simply concatenated). The tar | gpg | split variant suffers from the fact that the second tape can not be read as a tar archive individually. All previous tapes have to be available and read(!) to extract files from it. – Stefan Oct 5 '20 at 9:18
  • One last resort might be to write a FUSE/CUSE driver to emulate the tape drive for writing with tar and transparently perform the encryption with the ability to return the proper error code. – Stefan Oct 5 '20 at 9:23
  • No need of fuse/cuse. A simple ld_preload hack which overrides write() will do. (In fact, his is how I tested the -M option of tar). From there, you could gpg-encode directly (and keep the encoding state in sync), instead of piping to another command. – user414777 Oct 5 '20 at 21:07
  • @user414777 Thank you for that suggestions, that indeed seems to be the better option I was not aware of. Would you mind sharing your test code? – Stefan Oct 5 '20 at 23:25
2

It's not possible to propagate write errors back through a pipeline

And even if it were possible with some kind of hack, the pipes are buffering and by the time the pipe reader tries to "signal" the pipe writer, the latter could've already written the data which is causing the error further down the line, already got a successful status (>0) and updated its state accordingly. For it to work, the writing process would have to go back in time. On top of that, the pipe reader itself may do its own buffering and state keeping which would go out of sync.

The only way out is for tar to call the encryption routines directly, instead of passing the data through some kind of channel. Instead of modifying its source code and recompiling it, that could be done by monkey/live patching it with a LD_PRELOAD hack which overrides the write() library function and processes the data before passing it to the original write().

How to simulate ENOSPC with a LD_PRELOAD hack

This will cause an write to fd 1 (stdout) to fail with ENOSPC as soon as it tries to write more than 40960 bytes to it, after which it resets the counter and succeeds again, etc.

If you want it to work with tar -cf filename, instead of tar -cf -, you should probably change the fd == 1 test to 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
1
  • Thank you, I will try to put something together. It really is astonishing to me that there is no way to achieve this using standard tools. – Stefan Oct 6 '20 at 20:24
1

There are several problems in your question:

  • The correct way to detect an end of tape situation is to check for a write(2) that returns 0 without setting errno. A correct tar implementatoion that supports multi volume tape archives thus checks for write(2) to return 0.

  • The errno ENOSPC is only created when writing to a plain file in a filesystem and this errno is thus unsuited to be used as a base for multi-volume tape archives.

  • It is impossible to propagate a write error back via a pipe.

  • The UNIX tar command does not support multi-volume archives

  • gtar suports to write multi-volume archives but fails to read them back correctly with a probabilty of approx. 5% because it is not always able to recognise a followup archive as being the right volume number in order. This is caused by a design flaw in gtar that cannot be fixed without introducing a new incompatible multi-volume format.

  • star only tries to lock the FIFO memory in case it is called as root. The error code it writes in the message you quoted means: not sueruser (root). Are you running this star instance in an environment where "root" has limited rights?

  • star does not run a compress program if the output is not a plain file because the output of a compress program is not blocked,but blocking the output is required for tar implementations. If you like to compress in such a case, call something like: star -c ... | compress ...

In general, if you really like to encrypt the output of a tar program, you would need to pipe the output of the encrypting program through a program that manages mult-volume tape output.

BTW: feel free to send more information to get a more in depth answer.

2
  • Thank you @schily for your answer. I indeed did not perform the test with an actual tape drive, so I didn't catch the return 0 on write problem. The star issue regarding the locked FIFO occurred in a FreeBSD jail, though I will have to check the actual permissions that are configured root in that jail. Is that a separate issue from Can only compress files or are these two errors related? – Stefan Nov 23 '20 at 20:30
  • "because the output of a compress program is not blocked,but blocking the output is required for tar implementations" True, but with e.g. AES it would be possible to encrypt in blocks where the output size is equals to the input size (though aespipe uses a predictable IV for each block, which is less than ideal). Have you ever considered making some kind of encryption scheme a core functionality of star that can be used together with multi-volume archives? – Stefan Nov 23 '20 at 20:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.