2 # -*- coding: utf-8 -*-
4 # Daemon: superclass used to implement a daemon easily
6 # Copyright (C)2009-2012, UPMC Paris Universitas
8 # Marc-Olivier Buob <marc-olivier.buob@lip6.fr>
10 # see also: http://www.jejik.com/files/examples/daemon3x.py
12 # This is used to import the daemon package instead of the local module which is
13 # named identically...
14 from __future__ import absolute_import
16 from manifold.util.singleton import Singleton
17 from manifold.util.log import Log
18 from manifold.util.options import Options
20 import atexit, os, signal, lockfile, logging, sys
23 __metaclass__ = Singleton
29 "working_directory" : "/",
32 "pid_filename" : "/var/run/%s.pid" % Options().get_name()
35 #-------------------------------------------------------------------------
37 #-------------------------------------------------------------------------
39 def check_python_daemon(self):
41 \brief Check whether python-daemon is properly installed
42 \return True if everything is file, False otherwise
44 # http://www.python.org/dev/peps/pep-3143/
48 getattr(daemon, "DaemonContext")
50 except AttributeError, e:
52 # daemon and python-daemon conflict with each other
53 Log.critical("Please install python-daemon instead of daemon. Remove daemon first.")
55 Log.critical("Please install python-daemon - easy_install python-daemon.")
58 #------------------------------------------------------------------------
60 #------------------------------------------------------------------------
62 def make_handler_rsyslog(self, rsyslog_host, rsyslog_port, log_level):
64 \brief (Internal usage) Prepare logging via rsyslog
65 \param rsyslog_host The hostname of the rsyslog server
66 \param rsyslog_port The port of the rsyslog server
67 \param log_level Log level
70 shandler = handlers.SysLogHandler(
71 (rsyslog_host, rsyslog_port),
72 facility = handlers.SysLogHandler.LOG_DAEMON
75 # The log file must remain open while daemonizing
76 self.files_to_keep.append(shandler.socket)
77 self.prepare_handler(shandler, log_level)
80 def make_handler_locallog(self, log_filename, log_level):
82 \brief (Internal usage) Prepare local logging
83 \param log_filename The file in which we write the logs
84 \param log_level Log level
86 # Create directory in which we store the log file
87 log_dir = os.path.dirname(log_filename)
88 if not os.path.exists(log_dir):
92 log_error("OS error: %s" % why)
95 shandler = logging.handlers.RotatingFileHandler(
100 # The log file must remain open while daemonizing
101 self.files_to_keep.append(shandler.stream)
102 self.prepare_handler(shandler, log_level)
105 def prepare_handler(self, shandler, log_level):
107 \brief (Internal usage)
108 \param shandler Handler used to log information
109 \param log_level Log level
111 shandler.setLevel(log_level)
112 formatter = logging.Formatter("%(asctime)s: %(name)s: %(levelname)s %(message)s")
113 shandler.setFormatter(formatter)
114 self.log.addHandler(shandler)
115 self.log.setLevel(getattr(logging, log_level, logging.INFO))
120 terminate_callback = None
123 #working_directory = "/",
124 #pid_filename = None,
127 #log = None, # logging.getLogger("plop")
128 #rsyslog_host = "localhost", # Pass None if no rsyslog server
131 #log_level = logging.INFO
135 \param daemon_name The name of the daemon
136 \param uid UID used to run the daemon
137 \param gid GID used to run the daemon
138 \param working_directory Working directory used to run the daemon.
139 Example: /var/lib/foo/
140 \param pid_filename Absolute path of the PID file
141 Example: /var/run/foo.pid
142 (ignored if no_daemon == True)
143 \param no_daemon Do not detach the daemon from the terminal
144 \param debug Run daemon in debug mode
145 \param log The logger, pass None if unused
146 Example: logging.getLogger('foo'))
147 \param rsyslog_host Rsyslog hostname, pass None if unused.
148 If rsyslog_host is set to None, log are stored locally
149 \param rsyslog_port Rsyslog port
150 \param log_file Absolute path of the local log file.
151 Example: /var/log/foo.log)
152 \param log_level Log level
153 Example: logging.INFO
157 #self.daemon_name = daemon_name
158 self.terminate_callback = terminate_callback
161 #Options().working_directory = working_directory
162 #self.pid_filename = None if no_daemon else pid_filename
163 #Options().no_daemon = no_daemon
164 #Options().lock_file = None
165 #Options().debug = debug
167 #self.rsyslog_host = rsyslog_host
168 #self.rsyslog_port = rsyslog_port
169 #self.log_file = log_file
170 #self.log_level = log_level
172 # Reference which file descriptors must remain opened while
173 # daemonizing (for instance the file descriptor related to
175 self.files_to_keep = []
177 # Initialize self.log (require self.files_to_keep)
178 #if self.log: # for debugging by using stdout, log may be equal to None
180 # shandler = self.make_handler_rsyslog(
186 # shandler = self.make_handler_locallog(
192 def init_options(self):
196 "--uid", dest = "uid",
197 help = "UID used to run the dispatcher.",
198 default = self.DEFAULTS['uid']
201 "--gid", dest = "gid",
202 help = "GID used to run the dispatcher.",
203 default = self.DEFAULTS['gid']
206 "-w", "--working-directory", dest = "working_directory",
207 help = "Working directory.",
208 default = self.DEFAULTS['working_directory']
211 "-D", "--debugmode", action = "store_false", dest = "debugmode",
212 help = "Daemon debug mode (useful for developers).",
213 default = self.DEFAULTS['debugmode']
216 "-n", "--no-daemon", action = "store_true", dest = "no_daemon",
217 help = "Run as daemon (detach from terminal).",
218 default = self.DEFAULTS["no_daemon"]
221 "-i", "--pid-file", dest = "pid_filename",
222 help = "Absolute path to the pid-file to use when running as daemon.",
223 default = self.DEFAULTS['pid_filename']
228 #------------------------------------------------------------------------
230 #------------------------------------------------------------------------
232 def remove_pid_file(self):
234 \brief Remove the pid file (internal usage)
236 # The lock file is implicitely released while removing the pid file
237 Log.debug("Removing %s" % Options().pid_filename)
238 if os.path.exists(Options().pid_filename) == True:
239 os.remove(Options().pid_filename)
241 def make_pid_file(self):
243 \brief Create a pid file in which we store the PID of the daemon if needed
245 if Options().pid_filename and Options().no_daemon == False:
246 atexit.register(self.remove_pid_file)
247 file(Options().pid_filename, "w+").write("%s\n" % str(os.getpid()))
249 def get_pid_from_pid_file(self):
251 \brief Retrieve the PID of the daemon thanks to the pid file.
252 \return None if the pid file is not readable or does not exists
255 if Options().pid_filename:
257 f_pid = file(Options().pid_filename, "r")
258 pid = int(f_pid.read().strip())
264 def make_lock_file(self):
266 \brief Prepare the lock file required to manage the pid file
267 Initialize Options().lock_file
269 if Options().pid_filename and Options().no_daemon == False:
270 Log.debug("Daemonizing using pid file '%s'" % Options().pid_filename)
271 Options().lock_file = lockfile.FileLock(Options().pid_filename)
272 if Options().lock_file.is_locked() == True:
273 log_error("'%s' is already running ('%s' is locked)." % (Options().get_name(), Options().pid_filename))
275 Options().lock_file.acquire()
277 Options().lock_file = None
281 \brief Start the daemon
283 # Check whether daemon module is properly installed
284 if self.check_python_daemon() == False:
288 # Prepare Options().lock_file
289 self.make_lock_file()
291 # Prepare the daemon context
292 dcontext = daemon.DaemonContext(
293 detach_process = (not Options().no_daemon),
294 working_directory = Options().working_directory,
295 pidfile = Options().lock_file if not Options().no_daemon else None,
301 files_preserve = Log().files_to_keep
304 # Prepare signal handling to stop properly if the daemon is killed
305 # Note that signal.SIGKILL can't be handled:
306 # http://crunchtools.com/unixlinux-signals-101/
307 dcontext.signal_map = {
308 signal.SIGTERM : self.signal_handler,
309 signal.SIGQUIT : self.signal_handler,
310 signal.SIGINT : self.signal_handler
313 if Options().debugmode == True:
320 except Exception, why:
321 Log.error("Unhandled exception in start: %s" % why)
323 def signal_handler(self, signal_id, frame):
325 \brief Stop the daemon (signal handler)
326 The lockfile is implicitly released by the daemon package
327 \param signal_id The integer identifying the signal
328 (see also "man 7 signal")
329 Example: 15 if the received signal is signal.SIGTERM
335 Log.debug("Stopping '%s'" % self.daemon_name)
338 if self.terminate_callback:
339 self.terminate_callback()
343 Daemon.init_options()