daemon: Integrate checking for an existing pidfile into daemonize_start().
[sliver-openvswitch.git] / python / ovs / daemon.py
1 # Copyright (c) 2010, 2011 Nicira Networks
2 #
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:
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
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.
14
15 import errno
16 import fcntl
17 import logging
18 import os
19 import resource
20 import signal
21 import sys
22 import time
23
24 import ovs.dirs
25 import ovs.fatal_signal
26 #import ovs.lockfile
27 import ovs.process
28 import ovs.socket_util
29 import ovs.timeval
30 import ovs.util
31
32 # --detach: Should we run in the background?
33 _detach = False
34
35 # --pidfile: Name of pidfile (null if none).
36 _pidfile = None
37
38 # Our pidfile's inode and device, if we have created one.
39 _pidfile_dev = None
40 _pidfile_ino = None
41
42 # --overwrite-pidfile: Create pidfile even if one already exists and is locked?
43 _overwrite_pidfile = False
44
45 # --no-chdir: Should we chdir to "/"?
46 _chdir = True
47
48 # --monitor: Should a supervisory process monitor the daemon and restart it if
49 # it dies due to an error signal?
50 _monitor = False
51
52 # File descriptor used by daemonize_start() and daemonize_complete().
53 _daemonize_fd = None
54
55 RESTART_EXIT_CODE = 5
56
57 def make_pidfile_name(name):
58     """Returns the file name that would be used for a pidfile if 'name' were
59     provided to set_pidfile()."""
60     if name is None or name == "":
61         return "%s/%s.pid" % (ovs.dirs.RUNDIR, ovs.util.PROGRAM_NAME)
62     else:
63         return ovs.util.abs_file_name(ovs.dirs.RUNDIR, name)
64
65 def set_pidfile(name):
66     """Sets up a following call to daemonize() to create a pidfile named
67     'name'.  If 'name' begins with '/', then it is treated as an absolute path.
68     Otherwise, it is taken relative to ovs.util.RUNDIR, which is
69     $(prefix)/var/run by default.
70     
71     If 'name' is null, then ovs.util.PROGRAM_NAME followed by ".pid" is
72     used."""
73     global _pidfile
74     _pidfile = make_pidfile_name(name)
75
76 def get_pidfile():
77     """Returns an absolute path to the configured pidfile, or None if no
78     pidfile is configured.  The caller must not modify or free the returned
79     string."""
80     return _pidfile
81
82 def set_no_chdir():
83     """Sets that we do not chdir to "/"."""
84     global _chdir
85     _chdir = False
86
87 def is_chdir_enabled():
88     """Will we chdir to "/" as part of daemonizing?"""
89     return _chdir
90
91 def ignore_existing_pidfile():
92     """Normally, daemonize() or daemonize_start() will terminate the program
93     with a message if a locked pidfile already exists.  If this function is
94     called, an existing pidfile will be replaced, with a warning."""
95     global _overwrite_pidfile
96     _overwrite_pidfile = True
97
98 def set_detach():
99     """Sets up a following call to daemonize() to detach from the foreground
100     session, running this process in the background."""
101     global _detach
102     _detach = True
103
104 def get_detach():
105     """Will daemonize() really detach?"""
106     return _detach
107
108 def set_monitor():
109     """Sets up a following call to daemonize() to fork a supervisory process to
110     monitor the daemon and restart it if it dies due to an error signal."""
111     global _monitor
112     _monitor = True
113
114 def _die_if_already_running():
115     """If a locked pidfile exists, issue a warning message and, unless
116     ignore_existing_pidfile() has been called, terminate the program."""
117     if _pidfile is None:
118         return
119     pid = read_pidfile_if_exists(_pidfile)
120     if pid > 0:
121         if not _overwrite_pidfile:
122             msg = "%s: already running as pid %d" % (_pidfile, pid)
123             logging.error("%s, aborting" % msg)
124             sys.stderr.write("%s\n" % msg)
125             sys.exit(1)
126         else:
127             logging.warn("%s: %s already running"
128                          % (get_pidfile(), ovs.util.PROGRAM_NAME))
129
130 def _make_pidfile():
131     """If a pidfile has been configured, creates it and stores the running
132     process's pid in it.  Ensures that the pidfile will be deleted when the
133     process exits."""
134     if _pidfile is not None:
135         # Create pidfile via temporary file, so that observers never see an
136         # empty pidfile or an unlocked pidfile.
137         pid = os.getpid()
138         tmpfile = "%s.tmp%d" % (_pidfile, pid)
139         ovs.fatal_signal.add_file_to_unlink(tmpfile)
140
141         try:
142             # This is global to keep Python from garbage-collecting and
143             # therefore closing our file after this function exits.  That would
144             # unlock the lock for us, and we don't want that.
145             global file
146
147             file = open(tmpfile, "w")
148         except IOError, e:
149             logging.error("%s: create failed: %s"
150                           % (tmpfile, os.strerror(e.errno)))
151             return
152
153         try:
154             fcntl.lockf(file, fcntl.LOCK_EX | fcntl.LOCK_NB)
155         except IOError, e:
156             logging.error("%s: fcntl failed: %s"
157                           % (tmpfile, os.strerror(e.errno)))
158             file.close()
159             return
160
161         try:
162             file.write("%s\n" % pid)
163             file.flush()
164             ovs.fatal_signal.add_file_to_unlink(_pidfile)
165         except OSError, e:
166             logging.error("%s: write failed: %s"
167                           % (tmpfile, os.strerror(e.errno)))
168             file.close()
169             return
170             
171         try:
172             os.rename(tmpfile, _pidfile)
173         except OSError, e:
174             ovs.fatal_signal.remove_file_to_unlink(_pidfile)
175             logging.error("failed to rename \"%s\" to \"%s\": %s"
176                           % (tmpfile, _pidfile, os.strerror(e.errno)))
177             file.close()
178             return
179
180         s = os.fstat(file.fileno())
181         _pidfile_dev = s.st_dev
182         _pidfile_ino = s.st_ino
183
184 def daemonize():
185     """If configured with set_pidfile() or set_detach(), creates the pid file
186     and detaches from the foreground session."""
187     daemonize_start()
188     daemonize_complete()
189
190 def _waitpid(pid, options):
191     while True:
192         try:
193             return os.waitpid(pid, options)
194         except OSError, e:
195             if e.errno == errno.EINTR:
196                 pass
197             return -e.errno, 0
198
199 def _fork_and_wait_for_startup():
200     try:
201         rfd, wfd = os.pipe()
202     except OSError, e:
203         sys.stderr.write("pipe failed: %s\n" % os.strerror(e.errno))
204         sys.exit(1)
205
206     try:
207         pid = os.fork()
208     except OSError, e:
209         sys.stderr.write("could not fork: %s\n" % os.strerror(e.errno))
210         sys.exit(1)
211
212     if pid > 0:
213         # Running in parent process.
214         os.close(wfd)
215         ovs.fatal_signal.fork()
216         while True:
217             try:
218                 s = os.read(rfd, 1)
219                 error = 0
220             except OSError, e:
221                 s = ""
222                 error = e.errno
223             if error != errno.EINTR:
224                 break
225         if len(s) != 1:
226             retval, status = _waitpid(pid, 0)
227             if (retval == pid and
228                 os.WIFEXITED(status) and os.WEXITSTATUS(status)):
229                 # Child exited with an error.  Convey the same error to
230                 # our parent process as a courtesy.
231                 sys.exit(os.WEXITSTATUS(status))
232             else:
233                 sys.stderr.write("fork child failed to signal startup\n")
234                 sys.exit(1)
235
236         os.close(rfd)
237     else:
238         # Running in parent process.
239         os.close(rfd)
240         ovs.timeval.postfork()
241         #ovs.lockfile.postfork()
242
243         global _daemonize_fd
244         _daemonize_fd = wfd
245     return pid
246
247 def _fork_notify_startup(fd):
248     if fd is not None:
249         error, bytes_written = ovs.socket_util.write_fully(fd, "0")
250         if error:
251             sys.stderr.write("could not write to pipe\n")
252             sys.exit(1)
253         os.close(fd)
254
255 def _should_restart(status):
256     global RESTART_EXIT_CODE
257
258     if os.WIFEXITED(status) and os.WEXITSTATUS(status) == RESTART_EXIT_CODE:
259         return True
260
261     if os.WIFSIGNALED(status):
262         for signame in ("SIGABRT", "SIGALRM", "SIGBUS", "SIGFPE", "SIGILL",
263                         "SIGPIPE", "SIGSEGV", "SIGXCPU", "SIGXFSZ"):
264             if (signame in signal.__dict__ and
265                 os.WTERMSIG(status) == signal.__dict__[signame]):
266                 return True
267     return False
268
269 def _monitor_daemon(daemon_pid):
270     # XXX should log daemon's stderr output at startup time
271     # XXX should use setproctitle module if available
272     last_restart = None
273     while True:
274         retval, status = _waitpid(daemon_pid, 0)
275         if retval < 0:
276             sys.stderr.write("waitpid failed\n")
277             sys.exit(1)
278         elif retval == daemon_pid:
279             status_msg = ("pid %d died, %s"
280                           % (daemon_pid, ovs.process.status_msg(status)))
281             
282             if _should_restart(status):
283                 if os.WCOREDUMP(status):
284                     # Disable further core dumps to save disk space.
285                     try:
286                         resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
287                     except resource.error:
288                         logging.warning("failed to disable core dumps")
289
290                 # Throttle restarts to no more than once every 10 seconds.
291                 if (last_restart is not None and
292                     ovs.timeval.msec() < last_restart + 10000):
293                     logging.warning("%s, waiting until 10 seconds since last "
294                                     "restart" % status_msg)
295                     while True:
296                         now = ovs.timeval.msec()
297                         wakeup = last_restart + 10000
298                         if now > wakeup:
299                             break
300                         print "sleep %f" % ((wakeup - now) / 1000.0)
301                         time.sleep((wakeup - now) / 1000.0)
302                 last_restart = ovs.timeval.msec()
303
304                 logging.error("%s, restarting" % status_msg)
305                 daemon_pid = _fork_and_wait_for_startup()
306                 if not daemon_pid:
307                     break
308             else:
309                 logging.info("%s, exiting" % status_msg)
310                 sys.exit(0)
311
312    # Running in new daemon process.
313
314 def _close_standard_fds():
315     """Close stdin, stdout, stderr.  If we're started from e.g. an SSH session,
316     then this keeps us from holding that session open artificially."""
317     null_fd = ovs.socket_util.get_null_fd()
318     if null_fd >= 0:
319         os.dup2(null_fd, 0)
320         os.dup2(null_fd, 1)
321         os.dup2(null_fd, 2)
322
323 def daemonize_start():
324     """If daemonization is configured, then starts daemonization, by forking
325     and returning in the child process.  The parent process hangs around until
326     the child lets it know either that it completed startup successfully (by
327     calling daemon_complete()) or that it failed to start up (by exiting with a
328     nonzero exit code)."""
329     
330     if _detach:
331         if _fork_and_wait_for_startup() > 0:
332             # Running in parent process.
333             sys.exit(0)
334         # Running in daemon or monitor process.
335
336     if _monitor:
337         saved_daemonize_fd = _daemonize_fd
338         daemon_pid = _fork_and_wait_for_startup()
339         if daemon_pid > 0:
340             # Running in monitor process.
341             _fork_notify_startup(saved_daemonize_fd)
342             _close_standard_fds()
343             _monitor_daemon(daemon_pid)
344         # Running in daemon process
345     
346     _die_if_already_running()
347     _make_pidfile()
348
349 def daemonize_complete():
350     """If daemonization is configured, then this function notifies the parent
351     process that the child process has completed startup successfully."""
352     _fork_notify_startup(_daemonize_fd)
353
354     if _detach:
355         os.setsid()
356         if _chdir:
357             os.chdir("/")
358         _close_standard_fds()
359
360 def usage():
361     sys.stdout.write("""
362 Daemon options:
363    --detach                run in background as daemon
364    --no-chdir              do not chdir to '/'
365    --pidfile[=FILE]        create pidfile (default: %s/%s.pid)
366    --overwrite-pidfile     with --pidfile, start even if already running
367 """ % (ovs.dirs.RUNDIR, ovs.util.PROGRAM_NAME))
368
369 def __read_pidfile(pidfile, must_exist):
370     if _pidfile_dev is not None:
371         try:
372             s = os.stat(pidfile)
373             if s.st_ino == _pidfile_ino and s.st_dev == _pidfile_dev:
374                 # It's our own pidfile.  We can't afford to open it,
375                 # because closing *any* fd for a file that a process
376                 # has locked also releases all the locks on that file.
377                 #
378                 # Fortunately, we know the associated pid anyhow.
379                 return os.getpid()
380         except OSError:
381             pass
382
383     try:
384         file = open(pidfile, "r")
385     except IOError, e:
386         if e.errno == errno.ENOENT and not must_exist:
387             return 0
388         logging.warning("%s: open: %s" % (pidfile, os.strerror(e.errno)))
389         return -e.errno
390
391     # Python fcntl doesn't directly support F_GETLK so we have to just try
392     # to lock it.  If we get a conflicting lock that's "success"; otherwise
393     # the file is not locked.
394     try:
395         fcntl.lockf(file, fcntl.LOCK_EX | fcntl.LOCK_NB)
396         # File isn't locked if we get here, so treat that as an error.
397         logging.warning("%s: pid file is not locked" % pidfile)
398         try:
399             # As a side effect, this drops the lock.
400             file.close()
401         except IOError:
402             pass
403         return -errno.ESRCH
404     except IOError, e:
405         if e.errno not in [errno.EACCES, errno.EAGAIN]:
406             logging.warn("%s: fcntl: %s" % (pidfile, os.strerror(e.errno)))
407             return -e.errno
408
409     try:
410         try:
411             return int(file.readline())
412         except IOError, e:
413             logging.warning("%s: read: %s" % (pidfile, e.strerror))
414             return -e.errno
415         except ValueError:
416             logging.warning("%s does not contain a pid" % pidfile)
417             return -errno.EINVAL
418     finally:
419         try:
420             file.close()
421         except IOError:
422             pass
423
424 def read_pidfile(pidfile):
425     """Opens and reads a PID from 'pidfile'.  Returns the positive PID if
426     successful, otherwise a negative errno value."""
427     return __read_pidfile(pidfile, True)
428
429 def read_pidfile_if_exists(pidfile):
430     """Opens and reads a PID from 'pidfile'.  Returns 0 if 'pidfile' does not
431     exist, the positive PID if successful, otherwise a negative errno value."""
432     return __read_pidfile(pidfile, False)
433
434 # XXX Python's getopt does not support options with optional arguments, so we
435 # have to separate --pidfile (with no argument) from --pidfile-name (with an
436 # argument).  Need to write our own getopt I guess.
437 LONG_OPTIONS = ["detach", "no-chdir", "pidfile", "pidfile-name=",
438                 "overwrite-pidfile", "monitor"]
439
440 def parse_opt(option, arg):
441     if option == '--detach':
442         set_detach()
443     elif option == '--no-chdir':
444         set_no_chdir()
445     elif option == '--pidfile':
446         set_pidfile(None)
447     elif option == '--pidfile-name':
448         set_pidfile(arg)
449     elif option == '--overwrite-pidfile':
450         ignore_existing_pidfile()
451     elif option == '--monitor':
452         set_monitor()
453     else:
454         return False
455     return True