1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """utilities for interacting with processes"""
23
24 import errno
25 import os
26 import signal
27 import sys
28 import time
29
30 from flumotion.common import log
31 from flumotion.common.common import ensureDir
32 from flumotion.configure import configure
33
34 __version__ = "$Rev: 6690 $"
35
36
37 -def startup(processType, processName, daemonize=False, daemonizeTo='/'):
38 """
39 Prepare a process for starting, logging appropriate standarised messages.
40 First daemonizes the process, if daemonize is true.
41 """
42 log.info(processType, "Starting %s '%s'", processType, processName)
43
44 if daemonize:
45 _daemonizeHelper(processType, daemonizeTo, processName)
46
47 log.info(processType, "Started %s '%s'", processType, processName)
48
49 def shutdownStarted():
50 log.info(processType, "Stopping %s '%s'", processType, processName)
51 def shutdownEnded():
52 log.info(processType, "Stopped %s '%s'", processType, processName)
53
54
55 from twisted.internet import reactor
56 reactor.addSystemEventTrigger('before', 'shutdown',
57 shutdownStarted)
58 reactor.addSystemEventTrigger('after', 'shutdown',
59 shutdownEnded)
60
61 -def _daemonize(stdin='/dev/null', stdout='/dev/null', stderr='/dev/null',
62 directory='/'):
63 '''
64 This forks the current process into a daemon.
65 The stdin, stdout, and stderr arguments are file names that
66 will be opened and be used to replace the standard file descriptors
67 in sys.stdin, sys.stdout, and sys.stderr.
68 These arguments are optional and default to /dev/null.
69
70 The fork will switch to the given directory.
71 '''
72
73 si = open(stdin, 'r')
74 os.dup2(si.fileno(), sys.stdin.fileno())
75 try:
76 log.outputToFiles(stdout, stderr)
77 except IOError, e:
78 if e.errno == errno.EACCES:
79 log.error('common', 'Permission denied writing to log file %s.',
80 e.filename)
81
82
83 try:
84 pid = os.fork()
85 if pid > 0:
86 sys.exit(0)
87 except OSError, e:
88 sys.stderr.write("Failed to fork: (%d) %s\n" % (e.errno, e.strerror))
89 sys.exit(1)
90
91
92 try:
93 os.chdir(directory)
94 except OSError, e:
95 from flumotion.common import errors
96 raise errors.FatalError, "Failed to change directory to %s: %s" % (
97 directory, e.strerror)
98 os.umask(0)
99 os.setsid()
100
101
102 try:
103 pid = os.fork()
104 if pid > 0:
105 sys.exit(0)
106 except OSError, e:
107 sys.stderr.write("Failed to fork: (%d) %s\n" % (e.errno, e.strerror))
108 sys.exit(1)
109
110
111
112
113
115 """
116 Daemonize a process, writing log files and PID files to conventional
117 locations.
118
119 @param processType: The process type, for example 'worker'. Used
120 as part of the log file and PID file names.
121 @type processType: str
122 @param daemonizeTo: The directory that the daemon should run in.
123 @type daemonizeTo: str
124 @param processName: The service name of the process. Used to
125 disambiguate different instances of the same daemon.
126 @type processName: str
127 """
128
129 ensureDir(configure.logdir, "log dir")
130 ensureDir(configure.rundir, "run dir")
131 ensureDir(configure.cachedir, "cache dir")
132 ensureDir(configure.registrydir, "registry dir")
133
134 pid = getPid(processType, processName)
135 if pid:
136 raise SystemError(
137 "A %s service named '%s' is already running with pid %d"
138 % (processType, processName or processType, pid))
139
140 log.debug(processType, "%s service named '%s' daemonizing",
141 processType, processName)
142
143 if processName:
144 logPath = os.path.join(configure.logdir,
145 '%s.%s.log' % (processType, processName))
146 else:
147 logPath = os.path.join(configure.logdir,
148 '%s.log' % (processType,))
149 log.debug(processType, 'Further logging will be done to %s', logPath)
150
151 pidFile = _acquirePidFile(processType, processName)
152
153
154 _daemonize(stdout=logPath, stderr=logPath, directory=daemonizeTo)
155
156 log.debug(processType, 'Started daemon')
157
158
159 path = writePidFile(processType, processName, file=pidFile)
160 log.debug(processType, 'written pid file %s', path)
161
162
163 from twisted.internet import reactor
164 def _deletePidFile():
165 log.debug(processType, 'deleting pid file')
166 deletePidFile(processType, processName)
167 reactor.addSystemEventTrigger('after', 'shutdown',
168 _deletePidFile)
169
170
181
183 """
184 Write a pid file in the run directory, using the given process type
185 and process name for the filename.
186
187 @rtype: str
188 @returns: full path to the pid file that was written
189 """
190
191 pidFile = file
192 if pidFile is None:
193 ensureDir(configure.rundir, "rundir")
194 filename = _getPidPath(type, name)
195 pidFile = open(filename, 'w')
196 else:
197 filename = pidFile.name
198 pidFile.write("%d\n" % (os.getpid(),))
199 pidFile.close()
200 os.chmod(filename, 0644)
201 return filename
202
204 """
205 Open a PID file for writing, using the given process type and
206 process name for the filename. The returned file can be then passed
207 to writePidFile after forking.
208
209 @rtype: str
210 @returns: file object, open for writing
211 """
212 ensureDir(configure.rundir, "rundir")
213 path = _getPidPath(type, name)
214 return open(path, 'w')
215
217 """
218 Delete the pid file in the run directory, using the given process type
219 and process name for the filename.
220
221 @rtype: str
222 @returns: full path to the pid file that was written
223 """
224 path = _getPidPath(type, name)
225 os.unlink(path)
226 return path
227
229 """
230 Get the pid from the pid file in the run directory, using the given
231 process type and process name for the filename.
232
233 @returns: pid of the process, or None if not running or file not found.
234 """
235
236 pidPath = _getPidPath(type, name)
237 log.log('common', 'pidfile for %s %s is %s' % (type, name, pidPath))
238 if not os.path.exists(pidPath):
239 return
240
241 pidFile = open(pidPath, 'r')
242 pid = pidFile.readline()
243 pidFile.close()
244 if not pid or int(pid) == 0:
245 return
246
247 return int(pid)
248
250 """
251 Send the given process a signal.
252
253 @returns: whether or not the process with the given pid was running
254 """
255 try:
256 os.kill(pid, signum)
257 return True
258 except OSError, e:
259
260 if e.errno == errno.EPERM:
261
262 return True
263 if e.errno == errno.ESRCH:
264
265 return False
266 raise
267
269 """
270 Send the given process a TERM signal.
271
272 @returns: whether or not the process with the given pid was running
273 """
274 return signalPid(pid, signal.SIGTERM)
275
277 """
278 Send the given process a KILL signal.
279
280 @returns: whether or not the process with the given pid was running
281 """
282 return signalPid(pid, signal.SIGKILL)
283
285 """
286 Check if the given pid is currently running.
287
288 @returns: whether or not a process with that pid is active.
289 """
290 return signalPid(pid, 0)
291
293 """
294 Wait for the given process type and name to have started and created
295 a pid file.
296
297 Return the pid.
298 """
299
300 pid = getPid(type, name)
301
302 while not pid:
303 time.sleep(0.1)
304 pid = getPid(type, name)
305
306 return pid
307
309 """
310 Wait until we get killed by a TERM signal (from someone else).
311 """
312
313 class Waiter:
314 def __init__(self):
315 self.sleeping = True
316 import signal
317 self.oldhandler = signal.signal(signal.SIGTERM,
318 self._SIGTERMHandler)
319
320 def _SIGTERMHandler(self, number, frame):
321 self.sleeping = False
322
323 def sleep(self):
324 while self.sleeping:
325 time.sleep(0.1)
326
327 waiter = Waiter()
328 waiter.sleep()
329