33
|
1 # -*- coding: utf-8 -*-
|
|
2
|
|
3 # daemon/daemon.py
|
|
4 # Part of ‘python-daemon’, an implementation of PEP 3143.
|
|
5 #
|
|
6 # Copyright © 2008–2015 Ben Finney <ben+python@benfinney.id.au>
|
|
7 # Copyright © 2007–2008 Robert Niederreiter, Jens Klein
|
|
8 # Copyright © 2004–2005 Chad J. Schroeder
|
|
9 # Copyright © 2003 Clark Evans
|
|
10 # Copyright © 2002 Noah Spurrier
|
|
11 # Copyright © 2001 Jürgen Hermann
|
|
12 #
|
|
13 # This is free software: you may copy, modify, and/or distribute this work
|
|
14 # under the terms of the Apache License, version 2.0 as published by the
|
|
15 # Apache Software Foundation.
|
|
16 # No warranty expressed or implied. See the file ‘LICENSE.ASF-2’ for details.
|
|
17
|
|
18 """ Daemon process behaviour.
|
|
19 """
|
|
20
|
|
21 from __future__ import (absolute_import, unicode_literals)
|
|
22
|
|
23 import os
|
|
24 import sys
|
|
25 import resource
|
|
26 import errno
|
|
27 import signal
|
|
28 import socket
|
|
29 import atexit
|
|
30 try:
|
|
31 # Python 2 has both ‘str’ (bytes) and ‘unicode’ (text).
|
|
32 basestring = basestring
|
|
33 unicode = unicode
|
|
34 except NameError:
|
|
35 # Python 3 names the Unicode data type ‘str’.
|
|
36 basestring = str
|
|
37 unicode = str
|
|
38
|
|
39
|
|
40 class DaemonError(Exception):
|
|
41 """ Base exception class for errors from this module. """
|
|
42
|
|
43 def __init__(self, *args, **kwargs):
|
|
44 self._chain_from_context()
|
|
45
|
|
46 super(DaemonError, self).__init__(*args, **kwargs)
|
|
47
|
|
48 def _chain_from_context(self):
|
|
49 _chain_exception_from_existing_exception_context(self, as_cause=True)
|
|
50
|
|
51
|
|
52 class DaemonOSEnvironmentError(DaemonError, OSError):
|
|
53 """ Exception raised when daemon OS environment setup receives error. """
|
|
54
|
|
55
|
|
56 class DaemonProcessDetachError(DaemonError, OSError):
|
|
57 """ Exception raised when process detach fails. """
|
|
58
|
|
59
|
|
60 class DaemonContext:
|
|
61 """ Context for turning the current program into a daemon process.
|
|
62
|
|
63 A `DaemonContext` instance represents the behaviour settings and
|
|
64 process context for the program when it becomes a daemon. The
|
|
65 behaviour and environment is customised by setting options on the
|
|
66 instance, before calling the `open` method.
|
|
67
|
|
68 Each option can be passed as a keyword argument to the `DaemonContext`
|
|
69 constructor, or subsequently altered by assigning to an attribute on
|
|
70 the instance at any time prior to calling `open`. That is, for
|
|
71 options named `wibble` and `wubble`, the following invocation::
|
|
72
|
|
73 foo = daemon.DaemonContext(wibble=bar, wubble=baz)
|
|
74 foo.open()
|
|
75
|
|
76 is equivalent to::
|
|
77
|
|
78 foo = daemon.DaemonContext()
|
|
79 foo.wibble = bar
|
|
80 foo.wubble = baz
|
|
81 foo.open()
|
|
82
|
|
83 The following options are defined.
|
|
84
|
|
85 `files_preserve`
|
|
86 :Default: ``None``
|
|
87
|
|
88 List of files that should *not* be closed when starting the
|
|
89 daemon. If ``None``, all open file descriptors will be closed.
|
|
90
|
|
91 Elements of the list are file descriptors (as returned by a file
|
|
92 object's `fileno()` method) or Python `file` objects. Each
|
|
93 specifies a file that is not to be closed during daemon start.
|
|
94
|
|
95 `chroot_directory`
|
|
96 :Default: ``None``
|
|
97
|
|
98 Full path to a directory to set as the effective root directory of
|
|
99 the process. If ``None``, specifies that the root directory is not
|
|
100 to be changed.
|
|
101
|
|
102 `working_directory`
|
|
103 :Default: ``'/'``
|
|
104
|
|
105 Full path of the working directory to which the process should
|
|
106 change on daemon start.
|
|
107
|
|
108 Since a filesystem cannot be unmounted if a process has its
|
|
109 current working directory on that filesystem, this should either
|
|
110 be left at default or set to a directory that is a sensible “home
|
|
111 directory” for the daemon while it is running.
|
|
112
|
|
113 `umask`
|
|
114 :Default: ``0``
|
|
115
|
|
116 File access creation mask (“umask”) to set for the process on
|
|
117 daemon start.
|
|
118
|
|
119 A daemon should not rely on the parent process's umask value,
|
|
120 which is beyond its control and may prevent creating a file with
|
|
121 the required access mode. So when the daemon context opens, the
|
|
122 umask is set to an explicit known value.
|
|
123
|
|
124 If the conventional value of 0 is too open, consider setting a
|
|
125 value such as 0o022, 0o027, 0o077, or another specific value.
|
|
126 Otherwise, ensure the daemon creates every file with an
|
|
127 explicit access mode for the purpose.
|
|
128
|
|
129 `pidfile`
|
|
130 :Default: ``None``
|
|
131
|
|
132 Context manager for a PID lock file. When the daemon context opens
|
|
133 and closes, it enters and exits the `pidfile` context manager.
|
|
134
|
|
135 `detach_process`
|
|
136 :Default: ``None``
|
|
137
|
|
138 If ``True``, detach the process context when opening the daemon
|
|
139 context; if ``False``, do not detach.
|
|
140
|
|
141 If unspecified (``None``) during initialisation of the instance,
|
|
142 this will be set to ``True`` by default, and ``False`` only if
|
|
143 detaching the process is determined to be redundant; for example,
|
|
144 in the case when the process was started by `init`, by `initd`, or
|
|
145 by `inetd`.
|
|
146
|
|
147 `signal_map`
|
|
148 :Default: system-dependent
|
|
149
|
|
150 Mapping from operating system signals to callback actions.
|
|
151
|
|
152 The mapping is used when the daemon context opens, and determines
|
|
153 the action for each signal's signal handler:
|
|
154
|
|
155 * A value of ``None`` will ignore the signal (by setting the
|
|
156 signal action to ``signal.SIG_IGN``).
|
|
157
|
|
158 * A string value will be used as the name of an attribute on the
|
|
159 ``DaemonContext`` instance. The attribute's value will be used
|
|
160 as the action for the signal handler.
|
|
161
|
|
162 * Any other value will be used as the action for the
|
|
163 signal handler. See the ``signal.signal`` documentation
|
|
164 for details of the signal handler interface.
|
|
165
|
|
166 The default value depends on which signals are defined on the
|
|
167 running system. Each item from the list below whose signal is
|
|
168 actually defined in the ``signal`` module will appear in the
|
|
169 default map:
|
|
170
|
|
171 * ``signal.SIGTTIN``: ``None``
|
|
172
|
|
173 * ``signal.SIGTTOU``: ``None``
|
|
174
|
|
175 * ``signal.SIGTSTP``: ``None``
|
|
176
|
|
177 * ``signal.SIGTERM``: ``'terminate'``
|
|
178
|
|
179 Depending on how the program will interact with its child
|
|
180 processes, it may need to specify a signal map that
|
|
181 includes the ``signal.SIGCHLD`` signal (received when a
|
|
182 child process exits). See the specific operating system's
|
|
183 documentation for more detail on how to determine what
|
|
184 circumstances dictate the need for signal handlers.
|
|
185
|
|
186 `uid`
|
|
187 :Default: ``os.getuid()``
|
|
188
|
|
189 `gid`
|
|
190 :Default: ``os.getgid()``
|
|
191
|
|
192 The user ID (“UID”) value and group ID (“GID”) value to switch
|
|
193 the process to on daemon start.
|
|
194
|
|
195 The default values, the real UID and GID of the process, will
|
|
196 relinquish any effective privilege elevation inherited by the
|
|
197 process.
|
|
198
|
|
199 `prevent_core`
|
|
200 :Default: ``True``
|
|
201
|
|
202 If true, prevents the generation of core files, in order to avoid
|
|
203 leaking sensitive information from daemons run as `root`.
|
|
204
|
|
205 `stdin`
|
|
206 :Default: ``None``
|
|
207
|
|
208 `stdout`
|
|
209 :Default: ``None``
|
|
210
|
|
211 `stderr`
|
|
212 :Default: ``None``
|
|
213
|
|
214 Each of `stdin`, `stdout`, and `stderr` is a file-like object
|
|
215 which will be used as the new file for the standard I/O stream
|
|
216 `sys.stdin`, `sys.stdout`, and `sys.stderr` respectively. The file
|
|
217 should therefore be open, with a minimum of mode 'r' in the case
|
|
218 of `stdin`, and mimimum of mode 'w+' in the case of `stdout` and
|
|
219 `stderr`.
|
|
220
|
|
221 If the object has a `fileno()` method that returns a file
|
|
222 descriptor, the corresponding file will be excluded from being
|
|
223 closed during daemon start (that is, it will be treated as though
|
|
224 it were listed in `files_preserve`).
|
|
225
|
|
226 If ``None``, the corresponding system stream is re-bound to the
|
|
227 file named by `os.devnull`.
|
|
228
|
|
229 """
|
|
230
|
|
231 __metaclass__ = type
|
|
232
|
|
233 def __init__(
|
|
234 self,
|
|
235 chroot_directory=None,
|
|
236 working_directory="/",
|
|
237 umask=0,
|
|
238 uid=None,
|
|
239 gid=None,
|
|
240 prevent_core=True,
|
|
241 detach_process=None,
|
|
242 files_preserve=None,
|
|
243 pidfile=None,
|
|
244 stdin=None,
|
|
245 stdout=None,
|
|
246 stderr=None,
|
|
247 signal_map=None,
|
|
248 ):
|
|
249 """ Set up a new instance. """
|
|
250 self.chroot_directory = chroot_directory
|
|
251 self.working_directory = working_directory
|
|
252 self.umask = umask
|
|
253 self.prevent_core = prevent_core
|
|
254 self.files_preserve = files_preserve
|
|
255 self.pidfile = pidfile
|
|
256 self.stdin = stdin
|
|
257 self.stdout = stdout
|
|
258 self.stderr = stderr
|
|
259
|
|
260 if uid is None:
|
|
261 uid = os.getuid()
|
|
262 self.uid = uid
|
|
263 if gid is None:
|
|
264 gid = os.getgid()
|
|
265 self.gid = gid
|
|
266
|
|
267 if detach_process is None:
|
|
268 detach_process = is_detach_process_context_required()
|
|
269 self.detach_process = detach_process
|
|
270
|
|
271 if signal_map is None:
|
|
272 signal_map = make_default_signal_map()
|
|
273 self.signal_map = signal_map
|
|
274
|
|
275 self._is_open = False
|
|
276
|
|
277 @property
|
|
278 def is_open(self):
|
|
279 """ ``True`` if the instance is currently open. """
|
|
280 return self._is_open
|
|
281
|
|
282 def open(self):
|
|
283 """ Become a daemon process.
|
|
284
|
|
285 :return: ``None``.
|
|
286
|
|
287 Open the daemon context, turning the current program into a daemon
|
|
288 process. This performs the following steps:
|
|
289
|
|
290 * If this instance's `is_open` property is true, return
|
|
291 immediately. This makes it safe to call `open` multiple times on
|
|
292 an instance.
|
|
293
|
|
294 * If the `prevent_core` attribute is true, set the resource limits
|
|
295 for the process to prevent any core dump from the process.
|
|
296
|
|
297 * If the `chroot_directory` attribute is not ``None``, set the
|
|
298 effective root directory of the process to that directory (via
|
|
299 `os.chroot`).
|
|
300
|
|
301 This allows running the daemon process inside a “chroot gaol”
|
|
302 as a means of limiting the system's exposure to rogue behaviour
|
|
303 by the process. Note that the specified directory needs to
|
|
304 already be set up for this purpose.
|
|
305
|
|
306 * Set the process UID and GID to the `uid` and `gid` attribute
|
|
307 values.
|
|
308
|
|
309 * Close all open file descriptors. This excludes those listed in
|
|
310 the `files_preserve` attribute, and those that correspond to the
|
|
311 `stdin`, `stdout`, or `stderr` attributes.
|
|
312
|
|
313 * Change current working directory to the path specified by the
|
|
314 `working_directory` attribute.
|
|
315
|
|
316 * Reset the file access creation mask to the value specified by
|
|
317 the `umask` attribute.
|
|
318
|
|
319 * If the `detach_process` option is true, detach the current
|
|
320 process into its own process group, and disassociate from any
|
|
321 controlling terminal.
|
|
322
|
|
323 * Set signal handlers as specified by the `signal_map` attribute.
|
|
324
|
|
325 * If any of the attributes `stdin`, `stdout`, `stderr` are not
|
|
326 ``None``, bind the system streams `sys.stdin`, `sys.stdout`,
|
|
327 and/or `sys.stderr` to the files represented by the
|
|
328 corresponding attributes. Where the attribute has a file
|
|
329 descriptor, the descriptor is duplicated (instead of re-binding
|
|
330 the name).
|
|
331
|
|
332 * If the `pidfile` attribute is not ``None``, enter its context
|
|
333 manager.
|
|
334
|
|
335 * Mark this instance as open (for the purpose of future `open` and
|
|
336 `close` calls).
|
|
337
|
|
338 * Register the `close` method to be called during Python's exit
|
|
339 processing.
|
|
340
|
|
341 When the function returns, the running program is a daemon
|
|
342 process.
|
|
343
|
|
344 """
|
|
345 if self.is_open:
|
|
346 return
|
|
347
|
|
348 if self.chroot_directory is not None:
|
|
349 change_root_directory(self.chroot_directory)
|
|
350
|
|
351 if self.prevent_core:
|
|
352 prevent_core_dump()
|
|
353
|
|
354 change_file_creation_mask(self.umask)
|
|
355 change_working_directory(self.working_directory)
|
|
356 change_process_owner(self.uid, self.gid)
|
|
357
|
|
358 if self.detach_process:
|
|
359 detach_process_context()
|
|
360
|
|
361 signal_handler_map = self._make_signal_handler_map()
|
|
362 set_signal_handlers(signal_handler_map)
|
|
363
|
|
364 exclude_fds = self._get_exclude_file_descriptors()
|
|
365 close_all_open_files(exclude=exclude_fds)
|
|
366
|
|
367 redirect_stream(sys.stdin, self.stdin)
|
|
368 redirect_stream(sys.stdout, self.stdout)
|
|
369 redirect_stream(sys.stderr, self.stderr)
|
|
370
|
|
371 if self.pidfile is not None:
|
|
372 self.pidfile.__enter__()
|
|
373
|
|
374 self._is_open = True
|
|
375
|
|
376 register_atexit_function(self.close)
|
|
377
|
|
378 def __enter__(self):
|
|
379 """ Context manager entry point. """
|
|
380 self.open()
|
|
381 return self
|
|
382
|
|
383 def close(self):
|
|
384 """ Exit the daemon process context.
|
|
385
|
|
386 :return: ``None``.
|
|
387
|
|
388 Close the daemon context. This performs the following steps:
|
|
389
|
|
390 * If this instance's `is_open` property is false, return
|
|
391 immediately. This makes it safe to call `close` multiple times
|
|
392 on an instance.
|
|
393
|
|
394 * If the `pidfile` attribute is not ``None``, exit its context
|
|
395 manager.
|
|
396
|
|
397 * Mark this instance as closed (for the purpose of future `open`
|
|
398 and `close` calls).
|
|
399
|
|
400 """
|
|
401 if not self.is_open:
|
|
402 return
|
|
403
|
|
404 if self.pidfile is not None:
|
|
405 # Follow the interface for telling a context manager to exit,
|
|
406 # <URL:http://docs.python.org/library/stdtypes.html#typecontextmanager>.
|
|
407 self.pidfile.__exit__(None, None, None)
|
|
408
|
|
409 self._is_open = False
|
|
410
|
|
411 def __exit__(self, exc_type, exc_value, traceback):
|
|
412 """ Context manager exit point. """
|
|
413 self.close()
|
|
414
|
|
415 def terminate(self, signal_number, stack_frame):
|
|
416 """ Signal handler for end-process signals.
|
|
417
|
|
418 :param signal_number: The OS signal number received.
|
|
419 :param stack_frame: The frame object at the point the
|
|
420 signal was received.
|
|
421 :return: ``None``.
|
|
422
|
|
423 Signal handler for the ``signal.SIGTERM`` signal. Performs the
|
|
424 following step:
|
|
425
|
|
426 * Raise a ``SystemExit`` exception explaining the signal.
|
|
427
|
|
428 """
|
|
429 exception = SystemExit(
|
|
430 "Terminating on signal {signal_number!r}".format(
|
|
431 signal_number=signal_number))
|
|
432 raise exception
|
|
433
|
|
434 def _get_exclude_file_descriptors(self):
|
|
435 """ Get the set of file descriptors to exclude closing.
|
|
436
|
|
437 :return: A set containing the file descriptors for the
|
|
438 files to be preserved.
|
|
439
|
|
440 The file descriptors to be preserved are those from the
|
|
441 items in `files_preserve`, and also each of `stdin`,
|
|
442 `stdout`, and `stderr`. For each item:
|
|
443
|
|
444 * If the item is ``None``, it is omitted from the return
|
|
445 set.
|
|
446
|
|
447 * If the item's ``fileno()`` method returns a value, that
|
|
448 value is in the return set.
|
|
449
|
|
450 * Otherwise, the item is in the return set verbatim.
|
|
451
|
|
452 """
|
|
453 files_preserve = self.files_preserve
|
|
454 if files_preserve is None:
|
|
455 files_preserve = []
|
|
456 files_preserve.extend(
|
|
457 item for item in [self.stdin, self.stdout, self.stderr]
|
|
458 if hasattr(item, 'fileno'))
|
|
459
|
|
460 exclude_descriptors = set()
|
|
461 for item in files_preserve:
|
|
462 if item is None:
|
|
463 continue
|
|
464 file_descriptor = _get_file_descriptor(item)
|
|
465 if file_descriptor is not None:
|
|
466 exclude_descriptors.add(file_descriptor)
|
|
467 else:
|
|
468 exclude_descriptors.add(item)
|
|
469
|
|
470 return exclude_descriptors
|
|
471
|
|
472 def _make_signal_handler(self, target):
|
|
473 """ Make the signal handler for a specified target object.
|
|
474
|
|
475 :param target: A specification of the target for the
|
|
476 handler; see below.
|
|
477 :return: The value for use by `signal.signal()`.
|
|
478
|
|
479 If `target` is ``None``, return ``signal.SIG_IGN``. If `target`
|
|
480 is a text string, return the attribute of this instance named
|
|
481 by that string. Otherwise, return `target` itself.
|
|
482
|
|
483 """
|
|
484 if target is None:
|
|
485 result = signal.SIG_IGN
|
|
486 elif isinstance(target, unicode):
|
|
487 name = target
|
|
488 result = getattr(self, name)
|
|
489 else:
|
|
490 result = target
|
|
491
|
|
492 return result
|
|
493
|
|
494 def _make_signal_handler_map(self):
|
|
495 """ Make the map from signals to handlers for this instance.
|
|
496
|
|
497 :return: The constructed signal map for this instance.
|
|
498
|
|
499 Construct a map from signal numbers to handlers for this
|
|
500 context instance, suitable for passing to
|
|
501 `set_signal_handlers`.
|
|
502
|
|
503 """
|
|
504 signal_handler_map = dict(
|
|
505 (signal_number, self._make_signal_handler(target))
|
|
506 for (signal_number, target) in self.signal_map.items())
|
|
507 return signal_handler_map
|
|
508
|
|
509
|
|
510 def _get_file_descriptor(obj):
|
|
511 """ Get the file descriptor, if the object has one.
|
|
512
|
|
513 :param obj: The object expected to be a file-like object.
|
|
514 :return: The file descriptor iff the file supports it; otherwise
|
|
515 ``None``.
|
|
516
|
|
517 The object may be a non-file object. It may also be a
|
|
518 file-like object with no support for a file descriptor. In
|
|
519 either case, return ``None``.
|
|
520
|
|
521 """
|
|
522 file_descriptor = None
|
|
523 if hasattr(obj, 'fileno'):
|
|
524 try:
|
|
525 file_descriptor = obj.fileno()
|
|
526 except ValueError:
|
|
527 # The item doesn't support a file descriptor.
|
|
528 pass
|
|
529
|
|
530 return file_descriptor
|
|
531
|
|
532
|
|
533 def change_working_directory(directory):
|
|
534 """ Change the working directory of this process.
|
|
535
|
|
536 :param directory: The target directory path.
|
|
537 :return: ``None``.
|
|
538
|
|
539 """
|
|
540 try:
|
|
541 os.chdir(directory)
|
|
542 except Exception as exc:
|
|
543 error = DaemonOSEnvironmentError(
|
|
544 "Unable to change working directory ({exc})".format(exc=exc))
|
|
545 raise error
|
|
546
|
|
547
|
|
548 def change_root_directory(directory):
|
|
549 """ Change the root directory of this process.
|
|
550
|
|
551 :param directory: The target directory path.
|
|
552 :return: ``None``.
|
|
553
|
|
554 Set the current working directory, then the process root directory,
|
|
555 to the specified `directory`. Requires appropriate OS privileges
|
|
556 for this process.
|
|
557
|
|
558 """
|
|
559 try:
|
|
560 os.chdir(directory)
|
|
561 os.chroot(directory)
|
|
562 except Exception as exc:
|
|
563 error = DaemonOSEnvironmentError(
|
|
564 "Unable to change root directory ({exc})".format(exc=exc))
|
|
565 raise error
|
|
566
|
|
567
|
|
568 def change_file_creation_mask(mask):
|
|
569 """ Change the file creation mask for this process.
|
|
570
|
|
571 :param mask: The numeric file creation mask to set.
|
|
572 :return: ``None``.
|
|
573
|
|
574 """
|
|
575 try:
|
|
576 os.umask(mask)
|
|
577 except Exception as exc:
|
|
578 error = DaemonOSEnvironmentError(
|
|
579 "Unable to change file creation mask ({exc})".format(exc=exc))
|
|
580 raise error
|
|
581
|
|
582
|
|
583 def change_process_owner(uid, gid):
|
|
584 """ Change the owning UID and GID of this process.
|
|
585
|
|
586 :param uid: The target UID for the daemon process.
|
|
587 :param gid: The target GID for the daemon process.
|
|
588 :return: ``None``.
|
|
589
|
|
590 Set the GID then the UID of the process (in that order, to avoid
|
|
591 permission errors) to the specified `gid` and `uid` values.
|
|
592 Requires appropriate OS privileges for this process.
|
|
593
|
|
594 """
|
|
595 try:
|
|
596 os.setgid(gid)
|
|
597 os.setuid(uid)
|
|
598 except Exception as exc:
|
|
599 error = DaemonOSEnvironmentError(
|
|
600 "Unable to change process owner ({exc})".format(exc=exc))
|
|
601 raise error
|
|
602
|
|
603
|
|
604 def prevent_core_dump():
|
|
605 """ Prevent this process from generating a core dump.
|
|
606
|
|
607 :return: ``None``.
|
|
608
|
|
609 Set the soft and hard limits for core dump size to zero. On Unix,
|
|
610 this entirely prevents the process from creating core dump.
|
|
611
|
|
612 """
|
|
613 core_resource = resource.RLIMIT_CORE
|
|
614
|
|
615 try:
|
|
616 # Ensure the resource limit exists on this platform, by requesting
|
|
617 # its current value.
|
|
618 core_limit_prev = resource.getrlimit(core_resource)
|
|
619 except ValueError as exc:
|
|
620 error = DaemonOSEnvironmentError(
|
|
621 "System does not support RLIMIT_CORE resource limit"
|
|
622 " ({exc})".format(exc=exc))
|
|
623 raise error
|
|
624
|
|
625 # Set hard and soft limits to zero, i.e. no core dump at all.
|
|
626 core_limit = (0, 0)
|
|
627 resource.setrlimit(core_resource, core_limit)
|
|
628
|
|
629
|
|
630 def detach_process_context():
|
|
631 """ Detach the process context from parent and session.
|
|
632
|
|
633 :return: ``None``.
|
|
634
|
|
635 Detach from the parent process and session group, allowing the
|
|
636 parent to exit while this process continues running.
|
|
637
|
|
638 Reference: “Advanced Programming in the Unix Environment”,
|
|
639 section 13.3, by W. Richard Stevens, published 1993 by
|
|
640 Addison-Wesley.
|
|
641
|
|
642 """
|
|
643
|
|
644 def fork_then_exit_parent(error_message):
|
|
645 """ Fork a child process, then exit the parent process.
|
|
646
|
|
647 :param error_message: Message for the exception in case of a
|
|
648 detach failure.
|
|
649 :return: ``None``.
|
|
650 :raise DaemonProcessDetachError: If the fork fails.
|
|
651
|
|
652 """
|
|
653 try:
|
|
654 pid = os.fork()
|
|
655 if pid > 0:
|
|
656 os._exit(0)
|
|
657 except OSError as exc:
|
|
658 error = DaemonProcessDetachError(
|
|
659 "{message}: [{exc.errno:d}] {exc.strerror}".format(
|
|
660 message=error_message, exc=exc))
|
|
661 raise error
|
|
662
|
|
663 fork_then_exit_parent(error_message="Failed first fork")
|
|
664 os.setsid()
|
|
665 fork_then_exit_parent(error_message="Failed second fork")
|
|
666
|
|
667
|
|
668 def is_process_started_by_init():
|
|
669 """ Determine whether the current process is started by `init`.
|
|
670
|
|
671 :return: ``True`` iff the parent process is `init`; otherwise
|
|
672 ``False``.
|
|
673
|
|
674 The `init` process is the one with process ID of 1.
|
|
675
|
|
676 """
|
|
677 result = False
|
|
678
|
|
679 init_pid = 1
|
|
680 if os.getppid() == init_pid:
|
|
681 result = True
|
|
682
|
|
683 return result
|
|
684
|
|
685
|
|
686 def is_socket(fd):
|
|
687 """ Determine whether the file descriptor is a socket.
|
|
688
|
|
689 :param fd: The file descriptor to interrogate.
|
|
690 :return: ``True`` iff the file descriptor is a socket; otherwise
|
|
691 ``False``.
|
|
692
|
|
693 Query the socket type of `fd`. If there is no error, the file is a
|
|
694 socket.
|
|
695
|
|
696 """
|
|
697 result = False
|
|
698
|
|
699 file_socket = socket.fromfd(fd, socket.AF_INET, socket.SOCK_RAW)
|
|
700
|
|
701 try:
|
|
702 socket_type = file_socket.getsockopt(
|
|
703 socket.SOL_SOCKET, socket.SO_TYPE)
|
|
704 except socket.error as exc:
|
|
705 exc_errno = exc.args[0]
|
|
706 if exc_errno == errno.ENOTSOCK:
|
|
707 # Socket operation on non-socket.
|
|
708 pass
|
|
709 else:
|
|
710 # Some other socket error.
|
|
711 result = True
|
|
712 else:
|
|
713 # No error getting socket type.
|
|
714 result = True
|
|
715
|
|
716 return result
|
|
717
|
|
718
|
|
719 def is_process_started_by_superserver():
|
|
720 """ Determine whether the current process is started by the superserver.
|
|
721
|
|
722 :return: ``True`` if this process was started by the internet
|
|
723 superserver; otherwise ``False``.
|
|
724
|
|
725 The internet superserver creates a network socket, and
|
|
726 attaches it to the standard streams of the child process. If
|
|
727 that is the case for this process, return ``True``, otherwise
|
|
728 ``False``.
|
|
729
|
|
730 """
|
|
731 result = False
|
|
732
|
|
733 stdin_fd = sys.__stdin__.fileno()
|
|
734 if is_socket(stdin_fd):
|
|
735 result = True
|
|
736
|
|
737 return result
|
|
738
|
|
739
|
|
740 def is_detach_process_context_required():
|
|
741 """ Determine whether detaching the process context is required.
|
|
742
|
|
743 :return: ``True`` iff the process is already detached; otherwise
|
|
744 ``False``.
|
|
745
|
|
746 The process environment is interrogated for the following:
|
|
747
|
|
748 * Process was started by `init`; or
|
|
749
|
|
750 * Process was started by `inetd`.
|
|
751
|
|
752 If any of the above are true, the process is deemed to be already
|
|
753 detached.
|
|
754
|
|
755 """
|
|
756 result = True
|
|
757 if is_process_started_by_init() or is_process_started_by_superserver():
|
|
758 result = False
|
|
759
|
|
760 return result
|
|
761
|
|
762
|
|
763 def close_file_descriptor_if_open(fd):
|
|
764 """ Close a file descriptor if already open.
|
|
765
|
|
766 :param fd: The file descriptor to close.
|
|
767 :return: ``None``.
|
|
768
|
|
769 Close the file descriptor `fd`, suppressing an error in the
|
|
770 case the file was not open.
|
|
771
|
|
772 """
|
|
773 try:
|
|
774 os.close(fd)
|
|
775 except EnvironmentError as exc:
|
|
776 if exc.errno == errno.EBADF:
|
|
777 # File descriptor was not open.
|
|
778 pass
|
|
779 else:
|
|
780 error = DaemonOSEnvironmentError(
|
|
781 "Failed to close file descriptor {fd:d} ({exc})".format(
|
|
782 fd=fd, exc=exc))
|
|
783 raise error
|
|
784
|
|
785
|
|
786 MAXFD = 2048
|
|
787
|
|
788 def get_maximum_file_descriptors():
|
|
789 """ Get the maximum number of open file descriptors for this process.
|
|
790
|
|
791 :return: The number (integer) to use as the maximum number of open
|
|
792 files for this process.
|
|
793
|
|
794 The maximum is the process hard resource limit of maximum number of
|
|
795 open file descriptors. If the limit is “infinity”, a default value
|
|
796 of ``MAXFD`` is returned.
|
|
797
|
|
798 """
|
|
799 limits = resource.getrlimit(resource.RLIMIT_NOFILE)
|
|
800 result = limits[1]
|
|
801 if result == resource.RLIM_INFINITY:
|
|
802 result = MAXFD
|
|
803 return result
|
|
804
|
|
805
|
|
806 def close_all_open_files(exclude=set()):
|
|
807 """ Close all open file descriptors.
|
|
808
|
|
809 :param exclude: Collection of file descriptors to skip when closing
|
|
810 files.
|
|
811 :return: ``None``.
|
|
812
|
|
813 Closes every file descriptor (if open) of this process. If
|
|
814 specified, `exclude` is a set of file descriptors to *not*
|
|
815 close.
|
|
816
|
|
817 """
|
|
818 maxfd = get_maximum_file_descriptors()
|
|
819 for fd in reversed(range(maxfd)):
|
|
820 if fd not in exclude:
|
|
821 close_file_descriptor_if_open(fd)
|
|
822
|
|
823
|
|
824 def redirect_stream(system_stream, target_stream):
|
|
825 """ Redirect a system stream to a specified file.
|
|
826
|
|
827 :param standard_stream: A file object representing a standard I/O
|
|
828 stream.
|
|
829 :param target_stream: The target file object for the redirected
|
|
830 stream, or ``None`` to specify the null device.
|
|
831 :return: ``None``.
|
|
832
|
|
833 `system_stream` is a standard system stream such as
|
|
834 ``sys.stdout``. `target_stream` is an open file object that
|
|
835 should replace the corresponding system stream object.
|
|
836
|
|
837 If `target_stream` is ``None``, defaults to opening the
|
|
838 operating system's null device and using its file descriptor.
|
|
839
|
|
840 """
|
|
841 if target_stream is None:
|
|
842 target_fd = os.open(os.devnull, os.O_RDWR)
|
|
843 else:
|
|
844 target_fd = target_stream.fileno()
|
|
845 os.dup2(target_fd, system_stream.fileno())
|
|
846
|
|
847
|
|
848 def make_default_signal_map():
|
|
849 """ Make the default signal map for this system.
|
|
850
|
|
851 :return: A mapping from signal number to handler object.
|
|
852
|
|
853 The signals available differ by system. The map will not contain
|
|
854 any signals not defined on the running system.
|
|
855
|
|
856 """
|
|
857 name_map = {
|
|
858 'SIGTSTP': None,
|
|
859 'SIGTTIN': None,
|
|
860 'SIGTTOU': None,
|
|
861 'SIGTERM': 'terminate',
|
|
862 }
|
|
863 signal_map = dict(
|
|
864 (getattr(signal, name), target)
|
|
865 for (name, target) in name_map.items()
|
|
866 if hasattr(signal, name))
|
|
867
|
|
868 return signal_map
|
|
869
|
|
870
|
|
871 def set_signal_handlers(signal_handler_map):
|
|
872 """ Set the signal handlers as specified.
|
|
873
|
|
874 :param signal_handler_map: A map from signal number to handler
|
|
875 object.
|
|
876 :return: ``None``.
|
|
877
|
|
878 See the `signal` module for details on signal numbers and signal
|
|
879 handlers.
|
|
880
|
|
881 """
|
|
882 for (signal_number, handler) in signal_handler_map.items():
|
|
883 signal.signal(signal_number, handler)
|
|
884
|
|
885
|
|
886 def register_atexit_function(func):
|
|
887 """ Register a function for processing at program exit.
|
|
888
|
|
889 :param func: A callable function expecting no arguments.
|
|
890 :return: ``None``.
|
|
891
|
|
892 The function `func` is registered for a call with no arguments
|
|
893 at program exit.
|
|
894
|
|
895 """
|
|
896 atexit.register(func)
|
|
897
|
|
898
|
|
899 def _chain_exception_from_existing_exception_context(exc, as_cause=False):
|
|
900 """ Decorate the specified exception with the existing exception context.
|
|
901
|
|
902 :param exc: The exception instance to decorate.
|
|
903 :param as_cause: If true, the existing context is declared to be
|
|
904 the cause of the exception.
|
|
905 :return: ``None``.
|
|
906
|
|
907 :PEP:`344` describes syntax and attributes (`__traceback__`,
|
|
908 `__context__`, `__cause__`) for use in exception chaining.
|
|
909
|
|
910 Python 2 does not have that syntax, so this function decorates
|
|
911 the exception with values from the current exception context.
|
|
912
|
|
913 """
|
|
914 (existing_exc_type, existing_exc, existing_traceback) = sys.exc_info()
|
|
915 if as_cause:
|
|
916 exc.__cause__ = existing_exc
|
|
917 else:
|
|
918 exc.__context__ = existing_exc
|
|
919 exc.__traceback__ = existing_traceback
|
|
920
|
|
921
|
|
922 # Local variables:
|
|
923 # coding: utf-8
|
|
924 # mode: python
|
|
925 # End:
|
|
926 # vim: fileencoding=utf-8 filetype=python :
|