A very subtle bug (2010)
blog.nelhage.com
blog.nelhage.com
In the end it's fine and you just have to handle broken pipes but it adds a lot of boilerplate to small CLI programs that in C just work as expected. Another twist (and possible further subtle bug) is that most shells set the exit status of a program that exits from a broken pipe to 141 (128 + the signal number, in this case 13). So when you catch the pipe you can exit the process with a status of 141, but that's the shell's behaviour and there's no safe way to fake an exit status.
That’s appalling and irresponsible if true. Not every program is a server! If you want to ignore SIGPIPE you can do that yourself.
Rust is intended to be a systems programming language, and a replacement for C/C++. It should not be mucking about with signal handler defaults before main() runs.
The one thing that Rust does right, though, is it resets SIGPIPE to SIG_DFL when spawning processes[0]. Of course, I assume it only does that if you use std::process::Command, not if you use libc::fork()/libc::exec(), or get there some other way.
[0] https://doc.rust-lang.org/beta/unstable-book/language-featur...
Well, Python wasn't intended to write the sort of programs that C and Rust gets used for.
In Python, such a choice is not necessarily a design failure. In systems languages it is.
137 - 128 == 9
... that's SIGKILL, probably via some sort of pipe/subshell. We'd figured it was OOM-killer or something similar, but now it seems like there's a bit more of the dots connected between.
Who can/will send random `kill -9`s? Probably the kernel, or some sort of supervisory process.
The compiler adds library calls? Seriously?
Fortunately, Python 3.2 added the `restore_signals` argument for exactly this purpose, and it is enabled by default. The (2010) in the title is quite relevant.
There is a backport fixed version though: https://code.google.com/archive/p/python-subprocess32/
From what I see, the code should be expected to work without requiring workarounds; instead, it is one of following:
- a python design problem
- gzip signal handling bug
- a flaw in linux pipe spec
Tar expects gzip to react graciously to SIGPIPE. Gzip only registers a SIGPIPE handler if SIGPIPE is not ignored. This is either a bug in gzip, or tar has to make sure it starts gzip without SIGPIPE ignored.
This doesn't mean the failure is any less complex. Tar incorrectly assumes that starting gzip without extra setup doesn't make it ignore SIGPIPE, which is subtly wrong.
As it works perfectly in the equivalent shell script, I'd point the finger at Python for not wanting to handle SIGPIPE signals.
Python not wanting to handle SIGPIPE is perfectly fine. It is less clear whether it's fine for python to keep it disabled for subprocesses.
The other side: Is it okay for `tar` to fail if it is started with SIGPIPE disabled? That's definitely not what I'd expect, although it's maybe excusable since disabled SIGPIPE is nonstandard. You can argue about whether a system utility like tar is expected to handle nonstandard conditions -- I'd tend towards 'yes'.
That said, I don't think it's an unreasonable bug. I certainly wouldn't think any less of a programmer who wrote it.
How do similar languages like Perl do it? They seem to work fine doing things the Unix way.
if tarball or path is user input then they could be used to inject tar command options.
This might be safer (command injection) and maybe more reliable.
However I don't know what I would have chosen and hindsight is always 20/20 and there might be other external commands and requirements...
> It probably should be using tarfile, but let’s ignore that for the moment.
I'm not sure if tarfile got better since I've last used it.