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