1 # Copyright (c) 2010 Nicira Networks
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at:
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
25 import ovs.fatal_signal
28 import ovs.socket_util
32 # --detach: Should we run in the background?
35 # --pidfile: Name of pidfile (null if none).
38 # --overwrite-pidfile: Create pidfile even if one already exists and is locked?
39 _overwrite_pidfile = False
41 # --no-chdir: Should we chdir to "/"?
44 # --monitor: Should a supervisory process monitor the daemon and restart it if
45 # it dies due to an error signal?
48 # File descriptor used by daemonize_start() and daemonize_complete().
53 def make_pidfile_name(name):
54 """Returns the file name that would be used for a pidfile if 'name' were
55 provided to set_pidfile()."""
56 if name is None or name == "":
57 return "%s/%s.pid" % (ovs.dirs.RUNDIR, ovs.util.PROGRAM_NAME)
59 return ovs.util.abs_file_name(ovs.dirs.RUNDIR, name)
61 def set_pidfile(name):
62 """Sets up a following call to daemonize() to create a pidfile named
63 'name'. If 'name' begins with '/', then it is treated as an absolute path.
64 Otherwise, it is taken relative to ovs.util.RUNDIR, which is
65 $(prefix)/var/run by default.
67 If 'name' is null, then ovs.util.PROGRAM_NAME followed by ".pid" is
70 _pidfile = make_pidfile_name(name)
73 """Returns an absolute path to the configured pidfile, or None if no
74 pidfile is configured. The caller must not modify or free the returned
79 """Sets that we do not chdir to "/"."""
83 def is_chdir_enabled():
84 """Will we chdir to "/" as part of daemonizing?"""
87 def ignore_existing_pidfile():
88 """Normally, die_if_already_running() will terminate the program with a
89 message if a locked pidfile already exists. If this function is called,
90 die_if_already_running() will merely log a warning."""
91 global _overwrite_pidfile
92 _overwrite_pidfile = True
95 """Sets up a following call to daemonize() to detach from the foreground
96 session, running this process in the background."""
101 """Will daemonize() really detach?"""
105 """Sets up a following call to daemonize() to fork a supervisory process to
106 monitor the daemon and restart it if it dies due to an error signal."""
110 def _already_running():
111 """If a pidfile has been configured and that pidfile already exists and is
112 locked by a running process, returns True. Otherwise, returns False."""
113 if _pidfile is not None:
115 file = open(_pidfile, "r+")
118 fcntl.lockf(file, fcntl.LOCK_EX | fcntl.LOCK_NB)
120 if e.errno in [errno.EACCES, errno.EAGAIN]:
122 logging.error("error locking %s (%s)"
123 % (_pidfile, os.strerror(e.errno)))
126 # This releases the lock, which we don't really want.
129 if e.errno == errno.ENOENT:
131 logging.error("error opening %s (%s)"
132 % (_pidfile, os.strerror(e.errno)))
135 def die_if_already_running():
136 """If a locked pidfile exists, issue a warning message and, unless
137 ignore_existing_pidfile() has been called, terminate the program."""
138 if _already_running():
139 if not _overwrite_pidfile:
140 sys.stderr.write("%s: already running\n" % get_pidfile())
143 logging.warn("%s: %s already running"
144 % (get_pidfile(), ovs.util.PROGRAM_NAME))
147 """If a pidfile has been configured, creates it and stores the running
148 process's pid in it. Ensures that the pidfile will be deleted when the
150 if _pidfile is not None:
151 # Create pidfile via temporary file, so that observers never see an
152 # empty pidfile or an unlocked pidfile.
154 tmpfile = "%s.tmp%d" % (_pidfile, pid)
155 ovs.fatal_signal.add_file_to_unlink(tmpfile)
158 # This is global to keep Python from garbage-collecting and
159 # therefore closing our file after this function exits. That would
160 # unlock the lock for us, and we don't want that.
163 file = open(tmpfile, "w")
165 logging.error("%s: create failed: %s"
166 % (tmpfile, os.strerror(e.errno)))
170 fcntl.lockf(file, fcntl.LOCK_EX | fcntl.LOCK_NB)
172 logging.error("%s: fcntl failed: %s"
173 % (tmpfile, os.strerror(e.errno)))
178 file.write("%s\n" % pid)
180 ovs.fatal_signal.add_file_to_unlink(_pidfile)
182 logging.error("%s: write failed: %s"
183 % (tmpfile, os.strerror(e.errno)))
188 os.rename(tmpfile, _pidfile)
190 ovs.fatal_signal.remove_file_to_unlink(_pidfile)
191 logging.error("failed to rename \"%s\" to \"%s\": %s"
192 % (tmpfile, _pidfile, os.strerror(e.errno)))
197 """If configured with set_pidfile() or set_detach(), creates the pid file
198 and detaches from the foreground session."""
202 def _waitpid(pid, options):
205 return os.waitpid(pid, options)
207 if e.errno == errno.EINTR:
211 def _fork_and_wait_for_startup():
215 sys.stderr.write("pipe failed: %s\n" % os.strerror(e.errno))
221 sys.stderr.write("could not fork: %s\n" % os.strerror(e.errno))
225 # Running in parent process.
227 ovs.fatal_signal.fork()
233 retval, status = _waitpid(pid, 0)
234 if (retval == pid and
235 os.WIFEXITED(status) and os.WEXITSTATUS(status)):
236 # Child exited with an error. Convey the same error to
237 # our parent process as a courtesy.
238 sys.exit(os.WEXITSTATUS(status))
240 sys.stderr.write("fork child failed to signal startup\n")
245 # Running in parent process.
247 ovs.timeval.postfork()
248 #ovs.lockfile.postfork()
254 def _fork_notify_startup(fd):
256 error, bytes_written = ovs.socket_util.write_fully(fd, "0")
258 sys.stderr.write("could not write to pipe\n")
262 def _should_restart(status):
263 global RESTART_EXIT_CODE
265 if os.WIFEXITED(status) and os.WEXITSTATUS(status) == RESTART_EXIT_CODE:
268 if os.WIFSIGNALED(status):
269 for signame in ("SIGABRT", "SIGALRM", "SIGBUS", "SIGFPE", "SIGILL",
270 "SIGPIPE", "SIGSEGV", "SIGXCPU", "SIGXFSZ"):
271 if (signame in signal.__dict__ and
272 os.WTERMSIG(status) == signal.__dict__[signame]):
276 def _monitor_daemon(daemon_pid):
277 # XXX should log daemon's stderr output at startup time
278 # XXX should use setproctitle module if available
281 retval, status = _waitpid(daemon_pid, 0)
283 sys.stderr.write("waitpid failed\n")
285 elif retval == daemon_pid:
286 status_msg = ("pid %d died, %s"
287 % (daemon_pid, ovs.process.status_msg(status)))
289 if _should_restart(status):
290 if os.WCOREDUMP(status):
291 # Disable further core dumps to save disk space.
293 resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
294 except resource.error:
295 logging.warning("failed to disable core dumps")
297 # Throttle restarts to no more than once every 10 seconds.
298 if (last_restart is not None and
299 ovs.timeval.msec() < last_restart + 10000):
300 logging.warning("%s, waiting until 10 seconds since last "
301 "restart" % status_msg)
303 now = ovs.timeval.msec()
304 wakeup = last_restart + 10000
307 print "sleep %f" % ((wakeup - now) / 1000.0)
308 time.sleep((wakeup - now) / 1000.0)
309 last_restart = ovs.timeval.msec()
311 logging.error("%s, restarting" % status_msg)
312 daemon_pid = _fork_and_wait_for_startup()
316 logging.info("%s, exiting" % status_msg)
319 # Running in new daemon process.
321 def _close_standard_fds():
322 """Close stdin, stdout, stderr. If we're started from e.g. an SSH session,
323 then this keeps us from holding that session open artificially."""
324 null_fd = ovs.socket_util.get_null_fd()
330 def daemonize_start():
331 """If daemonization is configured, then starts daemonization, by forking
332 and returning in the child process. The parent process hangs around until
333 the child lets it know either that it completed startup successfully (by
334 calling daemon_complete()) or that it failed to start up (by exiting with a
335 nonzero exit code)."""
338 if _fork_and_wait_for_startup() > 0:
339 # Running in parent process.
341 # Running in daemon or monitor process.
344 saved_daemonize_fd = _daemonize_fd
345 daemon_pid = _fork_and_wait_for_startup()
347 # Running in monitor process.
348 _fork_notify_startup(saved_daemonize_fd)
349 _close_standard_fds()
350 _monitor_daemon(daemon_pid)
351 # Running in daemon process
355 def daemonize_complete():
356 """If daemonization is configured, then this function notifies the parent
357 process that the child process has completed startup successfully."""
358 _fork_notify_startup(_daemonize_fd)
364 _close_standard_fds()
369 --detach run in background as daemon
370 --no-chdir do not chdir to '/'
371 --pidfile[=FILE] create pidfile (default: %s/%s.pid)
372 --overwrite-pidfile with --pidfile, start even if already running
373 """ % (ovs.dirs.RUNDIR, ovs.util.PROGRAM_NAME))
375 def read_pidfile(pidfile):
376 """Opens and reads a PID from 'pidfile'. Returns the nonnegative PID if
377 successful, otherwise a negative errno value."""
379 file = open(pidfile, "r")
381 logging.warning("%s: open: %s" % (pidfile, os.strerror(e.errno)))
384 # Python fcntl doesn't directly support F_GETLK so we have to just try
385 # to lock it. If we get a conflicting lock that's "success"; otherwise
386 # the file is not locked.
388 fcntl.lockf(file, fcntl.LOCK_EX | fcntl.LOCK_NB)
389 # File isn't locked if we get here, so treat that as an error.
390 logging.warning("%s: pid file is not locked" % pidfile)
392 # As a side effect, this drops the lock.
398 if e.errno not in [errno.EACCES, errno.EAGAIN]:
399 logging.warn("%s: fcntl: %s" % (pidfile, os.strerror(e.errno)))
404 return int(file.readline())
406 logging.warning("%s: read: %s" % (pidfile, e.strerror))
409 logging.warning("%s does not contain a pid" % pidfile)
417 # XXX Python's getopt does not support options with optional arguments, so we
418 # have to separate --pidfile (with no argument) from --pidfile-name (with an
419 # argument). Need to write our own getopt I guess.
420 LONG_OPTIONS = ["detach", "no-chdir", "pidfile", "pidfile-name=",
421 "overwrite-pidfile", "monitor"]
423 def parse_opt(option, arg):
424 if option == '--detach':
426 elif option == '--no-chdir':
428 elif option == '--pidfile':
430 elif option == '--pidfile-name':
432 elif option == '--overwrite-pidfile':
433 ignore_existing_pidfile()
434 elif option == '--monitor':