hanslub42 / rlwrap

A readline wrapper
GNU General Public License v2.0
2.59k stars 151 forks source link

rlwrap kills forked children #171

Closed hmkemppainen closed 1 year ago

hmkemppainen commented 1 year ago

I don't know whether this is intentional behavior or not, but I noticed that sometimes programs that fork children to run (and persist) in the background don't work correctly if using rlwrap. It looks like the session gets killed when parent process exits. If forked child calls setsid() before parent exits, it'll survive. Depending on program, there may be a window of opportunity for the parent to exit before child calls setsid(), hence intermittent failures.

Minimal code to repro:

#include <err.h>
#include <fcntl.h>
#include <unistd.h>
int main(void) {
 int f,p;
 if((f=open("/tmp/foo.log",O_WRONLY|O_CREAT|O_APPEND,0644))==-1)err(1,"open");
 if((p=fork())==-1)err(1,"fork");
 if(p)return write(f,"parent writes\n",14)==14?0:10;
 usleep(1<<15),setsid();
 return write(f,"child writes\n",13)==13;
}

Observe tail -F /tmp/foo.log and run the program a few times. You should expect to see the parent and the child each print a line on every run:

$ tail -F /tmp/foo.log
parent writes
child writes
parent writes
child writes
parent writes
child writes
parent writes
child writes
parent writes
child writes
parent writes
child writes

Now run it under rlwrap and you'll observe that only the parent writes:

parent writes
parent writes
parent writes
parent writes
parent writes
parent writes

Remove the sleep and you may or may not get a child write. The sleep here is just to demonstrate the race. In a real program where the parent does more than just exit immediately, you may witness random behavior (and see the behavior change again when you try to debug it with strace..).

Weird behavior noticed while using rlwrap 0.45.2 on Fedora.

hmkemppainen commented 1 year ago

rlwrap creates a new session, right? So when the session leader exits, SIGHUP gets sent to all processes as described here.. https://unix.stackexchange.com/questions/407448/what-happens-to-a-unix-session-when-the-session-leader-exits

Is that what is happening? Which process is the session leader, rlwrap or the program it runs?

hanslub42 commented 1 year ago

Yes, the rlwrapped process becomes a session leader, with the slave pty as a controlling terminal. That is actually the very first code that I wrote (or, rather, took almost verbatim from, rxvt). At that time, I envisaged rlwrap more as a sort of terminal emulator - a program that runs other programs and makes them behave (like in: not messing up the screen when you press an arrow key, but also, exiting properly, not leaving zombies, etc)

Actually, on QNX, rlwrap already skips the setsid() (as QNX doesn't have the corresponding system call.

From the standpoint of transparency, this is a bit of overreach, as you correctly pointed out. On the other hand, without the setsid(), your child process will unexpectedly find its stdin closed when the parent exits, which wouldn't happen when you run your program without rlwrap. In other words: this would break transparency as well.

In practice, a command that exits immediately will rarely need rlwrap, but anyway: I added a --skip-sctty option in 8c387299d1a3fa25d3