1*4882a593Smuzhiyun# 2*4882a593Smuzhiyun# Copyright BitBake Contributors 3*4882a593Smuzhiyun# 4*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only 5*4882a593Smuzhiyun# 6*4882a593Smuzhiyun 7*4882a593Smuzhiyun""" 8*4882a593SmuzhiyunPython Daemonizing helper 9*4882a593Smuzhiyun 10*4882a593SmuzhiyunOriginally based on code Copyright (C) 2005 Chad J. Schroeder but now heavily modified 11*4882a593Smuzhiyunto allow a function to be daemonized and return for bitbake use by Richard Purdie 12*4882a593Smuzhiyun""" 13*4882a593Smuzhiyun 14*4882a593Smuzhiyunimport os 15*4882a593Smuzhiyunimport sys 16*4882a593Smuzhiyunimport io 17*4882a593Smuzhiyunimport traceback 18*4882a593Smuzhiyun 19*4882a593Smuzhiyunimport bb 20*4882a593Smuzhiyun 21*4882a593Smuzhiyundef createDaemon(function, logfile): 22*4882a593Smuzhiyun """ 23*4882a593Smuzhiyun Detach a process from the controlling terminal and run it in the 24*4882a593Smuzhiyun background as a daemon, returning control to the caller. 25*4882a593Smuzhiyun """ 26*4882a593Smuzhiyun 27*4882a593Smuzhiyun # Ensure stdout/stderror are flushed before forking to avoid duplicate output 28*4882a593Smuzhiyun sys.stdout.flush() 29*4882a593Smuzhiyun sys.stderr.flush() 30*4882a593Smuzhiyun 31*4882a593Smuzhiyun try: 32*4882a593Smuzhiyun # Fork a child process so the parent can exit. This returns control to 33*4882a593Smuzhiyun # the command-line or shell. It also guarantees that the child will not 34*4882a593Smuzhiyun # be a process group leader, since the child receives a new process ID 35*4882a593Smuzhiyun # and inherits the parent's process group ID. This step is required 36*4882a593Smuzhiyun # to insure that the next call to os.setsid is successful. 37*4882a593Smuzhiyun pid = os.fork() 38*4882a593Smuzhiyun except OSError as e: 39*4882a593Smuzhiyun raise Exception("%s [%d]" % (e.strerror, e.errno)) 40*4882a593Smuzhiyun 41*4882a593Smuzhiyun if (pid == 0): # The first child. 42*4882a593Smuzhiyun # To become the session leader of this new session and the process group 43*4882a593Smuzhiyun # leader of the new process group, we call os.setsid(). The process is 44*4882a593Smuzhiyun # also guaranteed not to have a controlling terminal. 45*4882a593Smuzhiyun os.setsid() 46*4882a593Smuzhiyun try: 47*4882a593Smuzhiyun # Fork a second child and exit immediately to prevent zombies. This 48*4882a593Smuzhiyun # causes the second child process to be orphaned, making the init 49*4882a593Smuzhiyun # process responsible for its cleanup. And, since the first child is 50*4882a593Smuzhiyun # a session leader without a controlling terminal, it's possible for 51*4882a593Smuzhiyun # it to acquire one by opening a terminal in the future (System V- 52*4882a593Smuzhiyun # based systems). This second fork guarantees that the child is no 53*4882a593Smuzhiyun # longer a session leader, preventing the daemon from ever acquiring 54*4882a593Smuzhiyun # a controlling terminal. 55*4882a593Smuzhiyun pid = os.fork() # Fork a second child. 56*4882a593Smuzhiyun except OSError as e: 57*4882a593Smuzhiyun raise Exception("%s [%d]" % (e.strerror, e.errno)) 58*4882a593Smuzhiyun 59*4882a593Smuzhiyun if (pid != 0): 60*4882a593Smuzhiyun # Parent (the first child) of the second child. 61*4882a593Smuzhiyun # exit() or _exit()? 62*4882a593Smuzhiyun # _exit is like exit(), but it doesn't call any functions registered 63*4882a593Smuzhiyun # with atexit (and on_exit) or any registered signal handlers. It also 64*4882a593Smuzhiyun # closes any open file descriptors, but doesn't flush any buffered output. 65*4882a593Smuzhiyun # Using exit() may cause all any temporary files to be unexpectedly 66*4882a593Smuzhiyun # removed. It's therefore recommended that child branches of a fork() 67*4882a593Smuzhiyun # and the parent branch(es) of a daemon use _exit(). 68*4882a593Smuzhiyun os._exit(0) 69*4882a593Smuzhiyun else: 70*4882a593Smuzhiyun os.waitpid(pid, 0) 71*4882a593Smuzhiyun return 72*4882a593Smuzhiyun 73*4882a593Smuzhiyun # The second child. 74*4882a593Smuzhiyun 75*4882a593Smuzhiyun # Replace standard fds with our own 76*4882a593Smuzhiyun with open('/dev/null', 'r') as si: 77*4882a593Smuzhiyun os.dup2(si.fileno(), sys.stdin.fileno()) 78*4882a593Smuzhiyun 79*4882a593Smuzhiyun with open(logfile, 'a+') as so: 80*4882a593Smuzhiyun try: 81*4882a593Smuzhiyun os.dup2(so.fileno(), sys.stdout.fileno()) 82*4882a593Smuzhiyun os.dup2(so.fileno(), sys.stderr.fileno()) 83*4882a593Smuzhiyun except io.UnsupportedOperation: 84*4882a593Smuzhiyun sys.stdout = so 85*4882a593Smuzhiyun 86*4882a593Smuzhiyun # Have stdout and stderr be the same so log output matches chronologically 87*4882a593Smuzhiyun # and there aren't two separate buffers 88*4882a593Smuzhiyun sys.stderr = sys.stdout 89*4882a593Smuzhiyun 90*4882a593Smuzhiyun try: 91*4882a593Smuzhiyun function() 92*4882a593Smuzhiyun except Exception as e: 93*4882a593Smuzhiyun traceback.print_exc() 94*4882a593Smuzhiyun finally: 95*4882a593Smuzhiyun bb.event.print_ui_queue() 96*4882a593Smuzhiyun # os._exit() doesn't flush open files like os.exit() does. Manually flush 97*4882a593Smuzhiyun # stdout and stderr so that any logging output will be seen, particularly 98*4882a593Smuzhiyun # exception tracebacks. 99*4882a593Smuzhiyun sys.stdout.flush() 100*4882a593Smuzhiyun sys.stderr.flush() 101*4882a593Smuzhiyun os._exit(0) 102