-
Notifications
You must be signed in to change notification settings - Fork 1
/
daemon.py
executable file
·194 lines (157 loc) · 5.24 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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
#!/usr/bin/env python3
# SPDX-FileCopyrightText: © 2022 ELABIT GmbH <[email protected]>
# SPDX-License-Identifier: GPL-3.0-or-later
import sys, os, time, atexit, signal
import platform
from abc import ABC, abstractmethod
from pathlib import Path
import re
daemon_py = "daemon.py"
class ForkStrategy(ABC):
def __init__(self, daemon):
self.daemon = daemon
@abstractmethod
def daemonize(self):
pass
class LinuxStrategy(ForkStrategy):
def daemonize(self):
try:
# FORK I) the child process
pid = os.fork()
if pid > 0:
# exit the parent process
sys.exit(0)
except OSError as err:
sys.stderr.write("fork #1 failed: {0}\n".format(err))
sys.exit(1)
# executed as child process
# decouple from parent environment, start new session
# with no controlling terminals
os.chdir("/")
os.setsid()
os.umask(0)
# FORK II) the grandchild process
try:
pid = os.fork()
if pid > 0:
# exit the child process
sys.exit(0)
except OSError as err:
sys.stderr.write("fork #2 failed: {0}\n".format(err))
sys.exit(1)
# here we are the grandchild process,
# daemonize it, connect fds to /dev/null stream
sys.stdout.flush()
sys.stderr.flush()
si = open(os.devnull, "r")
so = open(os.devnull, "a+")
se = open(os.devnull, "a+")
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
class WindowsStrategy(ForkStrategy):
def daemonize(self):
# On Windows, no double fork is needed (Dameon is already started detached)
pass
class Daemon:
def __init__(self, pidfile):
if not pidfile:
tmpdir = Path(os.getenv("TEMP", "/tmp"))
pidfile = "%s.pid" % daemon_py
self.pidfile = tmpdir / pidfile
else:
self.pidfile = Path(pidfile)
if platform.system() == "Linux":
self.fork_strategy = LinuxStrategy(self)
elif platform.system() == "Windows":
self.fork_strategy = WindowsStrategy(self)
def daemonize(self):
self.fork_strategy.daemonize()
self.write_and_register_pidfile()
@property
def pid(self):
return str(os.getpid())
def get_pid_from_file(self):
try:
with open(self.pidfile, "r") as pf:
pid = int(pf.read().strip())
except IOError:
pid = None
return pid
def delpid(self):
os.remove(self.pidfile)
def write_and_register_pidfile(self):
with open(self.pidfile, "w+") as f:
f.write(self.pid + "\n")
atexit.register(self.delpid)
def start(self):
# Check for a pidfile to see if the daemon already runs
pid = self.get_pid_from_file()
if pid:
msg = "One instance of %s is already running (PID: %d).\n" % (
daemon_py,
int(pid),
)
sys.stderr.write(msg.format(self.pidfile))
sys.exit(1)
else:
# daemonize according to the strategy
self.daemonize()
# Do the work
self.run()
def stop(self):
# Check for a pidfile to see if the daemon already runs
pid = self.get_pid_from_file()
if not pid:
message = "pidfile {0} does not exist. " + "Daemon does not seem to run.\n"
sys.stderr.write(message.format(self.pidfile))
return # not an error in a restart
# Try killing the daemon process
try:
while 1:
os.kill(pid, signal.SIGTERM)
time.sleep(0.1)
except OSError as err:
e = str(err.args)
# e = "(22, 'Falscher Parameter', None, 87, None)"
# kommt manchmal - abfangen: (13, 'Zugriff verweigert', None, 5, None)
if e.find("No such process") > 0 or re.match(".*22.*87", e):
if os.path.exists(self.pidfile):
os.remove(self.pidfile)
else:
print(str(err.args))
sys.exit(1)
def restart(self):
"""Restart the daemon."""
self.stop()
self.start()
def run(self):
"""You should override this method when you subclass Daemon.
It will be called after the process has been daemonized by
start() or restart()."""
class MyDaemon(Daemon):
def __init__(self, pidfile=None):
super().__init__(pidfile)
def run(self):
while True:
# DUMMY DAEMON CODE
print("Daemon is running ... ")
time.sleep(1)
def usage():
print("Usage: %s start|stop|restart" % sys.argv[0])
if __name__ == "__main__":
daemon = MyDaemon()
if len(sys.argv) == 2:
if "start" == sys.argv[1]:
daemon.start()
elif "stop" == sys.argv[1]:
daemon.stop()
elif "restart" == sys.argv[1]:
daemon.restart()
else:
usage()
sys.exit(2)
sys.exit(0)
else:
usage()
sys.exit(2)