-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathdaemon.py
132 lines (102 loc) · 4.56 KB
/
daemon.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
"""
<Program Name>
daemon.py
<Purpose>
Daemon Module --- basic facilities for becoming a daemon process
Goals of a daemon process:
* Detaches from its ancestor processes so that it doesn't block
the launching terminal / script, and is not affected by their
fate (e.g. termination)
* Detaches from any controlling TTY (interactive terminal or similar)
so it cannot be stopped or otherwise interacted with inadvertently,
and drops privileges that would allow it to reclaim a TTY
* Can be created from any starting point -- interactive shell, Python
script, cron, ...
* If done very correctly, also detaches from the creator's environment
such as mount points to minimize interference. (NOT IMPLEMENTED HERE!)
<Details>
(See the Web for a more detailed write-up including references:
https://github.com/SeattleTestbed/nodemanager/issues/115 )
0. Terminology:
PID=process ID, PPID=parent PID, PGRP=process group, SID=session ID.
1. We start with the parent process that wants to create a daemonized
copy of itself. Let's assume we started Parent in an interactive
shell.
Parent process: PID=parentID, PPID=shellID, PGRP=parentID, SID=shellID
2. Parent calls fork() to fork off Child 1. The parent process wait()s
for Child 1 to exit.
Child 1: PID=child1ID, PPID=parentID, PGRP=parentID, SID=shellID
(Child 1 has the parent process as its parent, shares its process
group, and is in the shell's session.)
3. We are not done yet, because Parent is wait()ing for Child 1 to terminate.
4. Child 1 now calls setsid(), creating a new session, becoming its
leader, and also becoming the process group leader. (Its leadership
will become important only after the next fork(), see below).
Child 1: PID=child1ID, PPID=parentID, PGRP=child1ID, SID=child1ID
5. (Depending on the requirements of the implementation, Child 1 should
also close all of the file descriptors it inherited, chdir into /,
and set its umask to 0. Alternatively, this might be done in
Child 2 instead.)
6. We are not done yet because
* Parent is still wait()ing for Child 1, so if Child 1 would continue
to run, this would keep Parent alive too.
* Child 1 is the leader of the new session, and can thus reacquire
the controlling terminal even if it closed the file descriptors that
it inherited from Parent. We specifically wanted to make this
impossible for the daemon process.
7. Thus, Child 1 calls fork() itself, creating Child 2 which is neither
the process group nor session leader, and therefore cannot reacquire
the controlling terminal. Note that Parent does not wait() for
Child 2, as this is a grand-child.
Child 2: PID=child2ID, PPID=child1ID, PGRP=child1ID, SID=child1ID
8. Following the fork, Child 1 exits. This leaves Child 2 without a
parent for a moment, but an init process will adopt it soon. The
consequence of Child 1's exit is that Parent can exit now, too.
Eventually, we are left with only Child 2 which is now a daemon:
Child 2: PID=child2ID, PPID=initID, PGRP=child1ID, SID=child1ID
Note that in contrast to traditional lore, the process ID of the init
process (initID above) is not necessarily 1. Upstart (and possibly
other init replacements) has init --user processes with different PIDs
for graphical sessions aka "User Session Mode".
<Notes>
Combines ideas from Steinar Knutsens daemonize.py and
Jeff Kunces demonize.py
Originally posted to python-list; an archive of the post is available
here: http://aspn.activestate.com/ASPN/Mail/Message/python-list/504777
Assumed is that the author intended for the (fairly trivial body of) code
to be freely usable by any developer.
"""
import os
import time
import sys
class NullDevice:
def write(self, s):
pass
def daemonize():
"""
daemonize:
Purpose:
Detach from stdin/stdout/stderr, return control of the term to the user.
Returns:
Nothing.
"""
if os.name == "nt" or os.name == "ce":
# No way to fork or daemonize on windows. Just do nothing for now?
return
if not os.fork():
# get our own session and fixup std[in,out,err]
os.setsid()
sys.stdin.close()
sys.stdout = NullDevice()
sys.stderr = NullDevice()
if not os.fork():
# hang around till adopted by init
while os.getppid() == os.getpgrp():
time.sleep(0.5)
else:
# time for child to die
os._exit(0)
else:
# wait for child to die and then bail
os.wait()
sys.exit()