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