I wrote a remote tty tool (called "rt"), which works like below:

[rt client(rtc)] <--->  [rt gateway(rtg)] <---> [rt server(rts)]

where rts is a host behind NAT firewall, it runs rt in /etc/rc.local which connect to rtg. When I want to connec to rts, I run rt on the rtc computer, which connect to rtg, which in turn send the request to rts. The rt program will launch /usr/bin/login in a TTY, thus created a remote terminal between rtc and rts.

My problem is, if I run some command in the remote terminal, then type exit to quit bash, I hope to return to my local terminal prompt, but it almost always hangs with a blank screen, except if I just run some bash command and didn't run any external program in the remote session.


It is a bit hard to precisely define what is external program. I have a feeling that if the program does not "daemonize" then it is considered "internal". i.e. if I run ls, cp, vi etc, it will be fine, but if I run a service program, with & at the end of command line, then rt will hang on exit.

I did some research but is is unfortunately hard to describe the problem in a single sentence. The most relevant resource I can find is:

When terminal emulator exits, why does shell also exits?

I guess it might help if I send SIGHUP somewhere, but how? Which program is the sender and which is the receiver?

  • Seems to me a if rtc does not recognize the termination of remote programs. That's something only you can fix since you developed that software. What do you mean by 'external programs' or 'bash command'? Are you talking about bash builtin functions? Another question: Why don't just you use 'ssh' for the job?
    – gerhard d.
    Mar 25 at 5:17
  • @gerhard rts does not expose any port outside, ssh is not usable. I will edit the post to explain what exactly is "external program"
    – xrfang
    Mar 25 at 5:44
  • 1
    (1) I suspect that, when you say “external”, you mean “asynchronous”.  Research that word and let us know if that’s what you mean. (2) What happens if you run your asynchronous command with nohup? Mar 25 at 6:16
  • @G-ManSays'ReinstateMonica' I tested confirmed that nohup does fix my problem. so, how should I proceed, I do not want to use nohup all the time...
    – xrfang
    Mar 25 at 7:24
  • On disconnecting, you should send SIGHUP to the remote shell (so it can terminate running jobs in the background). From man 7 signal: "Hangup detected on controlling terminal or death of controlling process"
    – Panki
    Mar 25 at 13:45

1 Answer 1


The behavior you describe is typical of controller software that reads from the TTY (that is the output of the remote terminal), and keeps on reading (waiting, if necessary) until it gets EOF.  But a pseudo-tty is like a pipe — a reading process doesn’t get EOF until all the processes that have it open for writing, close it.  (When a process exits, that causes it to close all open files.)

But, unless you redirect the standard output and the standard error, asynchronous processes keep the tty open for writing as stdout and stderr.  While nohup’s primary function is to ignore hangup (HUP) signals, it has the secondary function of ensuring that stdout and stderr do not point to the tty.  If you redirect the output streams with > and 2> (or variations thereon), that’s fine; nohup will leave them alone.  But, if you don’t redirect the output, nohup redirects it to nohup.out.  (There may be subtle details that are more complex, but irrelevant to this question.)

TL;DR If you run asynchronous command(s) without redirecting stdout and stderr (and, to be safe, also stdin), then, when you exit from the shell, your rt program will not exit until all the background processes close their file descriptors that refer to the tty.  In practical terms, this means it waits until the background processes exit.

It seems to me that the ‘‘simple and straightforward’’ solution is to modify your program to exit when the child process exits.  The interactive shell process that the user (i.e., you) get when you login is the same process as the one in which you launched /usr/bin/login.  I hope you are doing this with fork() and exec() (or some variation thereon).  So just keep track of the PID and call wait() (or some variation thereon).


  • You’ll need to use multiple processes, or at least multiple threads, to wait for the shell to exit while concurrently reading from the pseudo-TTY.
  • There are many programs, such as nc (netcat) and sshd, that already handle this situation, and I’m sure some of them are open source.  You could look at them to get some ideas (and some code).
  • One feature that you might want to consider is to sleep for a few seconds after the wait() returns before rt terminates, to handle anomalies in the buffering of the TTY output.  You’ll see that some of the programs I mentioned above do this.
  • The rt program is a daemon, which does not exit, and I currently do not fork a copy of rt and do the tty stuff in the forked instance (if that is what you mean). As a matter of fact, I primarily programming Go, and can barely read C source code :(. I will try to do a periodic monitoring solution first. Thanks.
    – xrfang
    Mar 26 at 2:59
  • I also tried to modify the background process' stdio, but to no avail yet. I posted a new question here: stackoverflow.com/questions/71623471/…
    – xrfang
    Mar 26 at 3:00
  • I added a goroutine to see if the shell is still there, if it is not, send a signal to rtc to disconnect.
    – xrfang
    Mar 26 at 8:46

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.