a forking service blocks ssh connection even if the main process ends

33 Views Asked by At

I have a program that should be run from sshd. It should print few lines to stdout and then fork and exit. The forked program should do its stuff detached from sshd without blocking the program. The thing is that the program below runs correctly when executed from a terminal shell or via ssh with option -t. It blocks ssh connection when the terminal is not assigned.

#! /usr/bin/env python3

import os, time, sys

print("Hello world", flush=True)
if os.fork()==0:
    # child
    sys.stdin.close()
    sys.stdout.close()
    sys.stderr.close()
    os.setsid()
    # just in case make a second fork
    if os.fork()>0:
        os.exit()
    os.setsid()
    time.sleep(30)
    os.exit(0)
os.wait()
print("bye, world")

How can I make the program release ssh connection even if invoked without terminal?

  1. Direct run (runs correctly, exits immediately leaving forked child):
./test-prog.py
  1. Via ssh with forced terminal still works correctly:
ssh -t server ./test-prog.py
  1. Via ssh without forced terminal doesn't work correctly. It prints both lines and then waits 30 seconds to release ssh:
ssh server ./test-prog.py

I would run this program from a daemon connecting via ssh and exit leaving forked child detached from ssh. In real application this program just opens few tcp socket and writes their port number on stdout. The client reads the ports and then connects to it.

1

There are 1 best solutions below

0
ChewbaccaKL On

I found that in python sys.stdin.close() doesn't really close fd 0, neither does closing stdout and stderr close fd 1 and 2.

ssh keeps the connection if:

  1. the pty is open or

  2. all fds 0,1,2 (and perhaps other passed using -L and -R options) are closed if there is no pty allocated, i.e. there is no pty in calling process and no -t option to ssh.

So the correct way to close standard streams is to dup2 them to /dev/null, for instance with the following code:

import os
if os.fork()==0:
  # child
  f_in = open("/dev/null")
  f_out = open("/dev/null,"w")
  os.dup2( f_in.fileno(), 0)
  os.dup2( f_out.fileno(), 1)
  os.dup2( 1, 2)