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