-
Notifications
You must be signed in to change notification settings - Fork 1
/
daemon_ctrl.py
executable file
·163 lines (135 loc) · 4.95 KB
/
daemon_ctrl.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
#!/usr/bin/env python3
# SPDX-FileCopyrightText: © 2022 ELABIT GmbH <[email protected]>
# SPDX-License-Identifier: GPL-3.0-or-later
import os
import sys
import signal
import subprocess
from pathlib import Path
import platform
import psutil
import re
from abc import ABC, abstractmethod
daemon_py = "daemon.py"
interpreter = sys.executable
thisdir = Path(os.path.dirname(os.path.abspath(__file__)))
program = thisdir / daemon_py
def main():
# Windows:
# - calls daemon as a DETACHED daemon process
# - daemon.py recognizes OS=Windows, will not double fork itself (runs already detached)
# - it is NOT possible to run the daemon directly with "daemon.py start" - it
# will start, but remain in its own process, without detaching.
# Linux:
# - calls daemon as normal process
# - daemon.py will double fork (forks itself and then exits)
daemon_ctrl = DaemonCtrl()
if not daemon_ctrl.is_daemon_running():
daemon_ctrl.create_process()
class DaemonCtrl:
def __init__(self):
self.proc_pattern = ".*python.*%s" % daemon_py
pidfile = daemon_py + ".pid"
self.daemon_pidfile = Path(os.getenv("TEMP", "/tmp")) / pidfile
if platform.system() == "Linux":
self.strategy = LinuxStrategy()
elif platform.system() == "Windows":
self.strategy = WindowsStrategy()
def is_daemon_running(self):
"""Checks daemon existence in two ways:
- is process runing
- is statefile present
and does the housekeeping.
"""
processes = self.get_process_list()
pid = self.get_pid_from_file()
if len(processes) == 0:
print("No processes are running.")
if pid:
self.unlink_pidfile()
return False
elif len(processes) == 1:
# One instance is running
if pid:
print(
"One instance of %s is already running (PID: %d)."
% (daemon_py, int(pid))
)
return True
else:
print(
"One instance of %s is already running, but no PID file found."
% daemon_py
)
print("Cleaning up.")
# Instance without PID file
self.kill_all(processes)
return False
elif len(processes) > 1:
print("More than one instance of %s is running." % daemon_py)
print("Cleaning up.")
# fucked up, clean all
self.kill_all(processes)
self.unlink_pidfile()
return False
def unlink_pidfile(self):
"""Deletes the PID file"""
if os.path.exists(self.daemon_pidfile):
os.remove(self.daemon_pidfile)
def kill_all(self, processes):
for process in processes:
os.kill(process["pid"], signal.SIGTERM)
def get_process_list(self):
"""Returns a list of Process objects matching the search pattern"""
listOfProcessObjects = []
# Iterate over the all the running process
for proc in psutil.process_iter():
try:
pinfo = proc.as_dict(attrs=["pid", "name", "cmdline"])
# Check if process name contains the given name string.
if pinfo["cmdline"] and re.match(
self.proc_pattern, " ".join(pinfo["cmdline"])
):
listOfProcessObjects.append(pinfo)
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
pass
return listOfProcessObjects
def get_pid_from_file(self):
"""Reads PID from pidfile, returns None if not found"""
try:
with open(self.daemon_pidfile, "r") as pf:
pid = int(pf.read().strip())
except IOError:
pid = None
return pid
def create_process(self):
self.strategy.create_process()
class SubprocesStrategy(ABC):
@abstractmethod
def create_process(self):
pass
class LinuxStrategy(SubprocesStrategy):
creationflags = None
def create_process(self):
print("Starting daemon...")
process = subprocess.Popen([interpreter, program, "start"], self.creationflags)
print(
"Started! PID = {} (linux double fork will create another one)".format(
process.pid
)
)
class WindowsStrategy(SubprocesStrategy):
flags = 0
flags |= 0x00000008 # DETACHED_PROCESS
flags |= 0x00000200 # CREATE_NEW_PROCESS_GROUP
flags |= 0x08000000 # CREATE_NO_WINDOW
pkwargs = {
"close_fds": True, # close stdin/stdout/stderr on child
"creationflags": flags,
}
def create_process(self):
print("Starting daemon...")
process = subprocess.Popen([interpreter, program, "start"], **self.pkwargs)
print("Started! Daemon PID = {}".format(process.pid))
if __name__ == "__main__":
main()