1
0

daemon.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. # The contents of this file are subject to the BitTorrent Open Source License
  2. # Version 1.1 (the License). You may not copy or use this file, in either
  3. # source code or executable form, except in compliance with the License. You
  4. # may obtain a copy of the License at http://www.bittorrent.com/license/.
  5. #
  6. # Software distributed under the License is distributed on an AS IS basis,
  7. # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  8. # for the specific language governing rights and limitations under the
  9. # License.
  10. # by David Harrison
  11. import errno
  12. import os
  13. import sys
  14. import errno
  15. import pwd
  16. import grp
  17. import stat
  18. import logging
  19. from twisted.python.util import switchUID
  20. #from twisted.scripts import twistd
  21. #try:
  22. # from twisted.scripts.twistd import checkPID
  23. #except:
  24. # from twisted.scripts._twistd_unix import checkPID
  25. from BTL.log import injectLogger, ERROR, INFO, DEBUG
  26. from BTL.platform import app_name
  27. log = logging.getLogger("daemon")
  28. noisy = False
  29. def getuid_from_username(username):
  30. return pwd.getpwnam(username)[2]
  31. def getgid_from_username(username):
  32. return pwd.getpwnam(username)[3]
  33. def getgid_from_groupname(groupname):
  34. return grp.getgrnam(groupname)[2]
  35. def daemon(
  36. username = None, groupname = None,
  37. use_syslog = None, log_file = None, logfile = None, # HACK for backwards compat.
  38. verbose = False,
  39. capture_output = True,
  40. twisted_error_log_level = ERROR,
  41. twisted_info_log_level = INFO,
  42. capture_stderr_log_level = ERROR,
  43. capture_stdout_log_level = INFO,
  44. capture_stderr_name = 'stderr',
  45. capture_stdout_name = 'stdout',
  46. log_level = DEBUG,
  47. log_twisted = True,
  48. pidfile = None,
  49. use_localtime=False):
  50. """When this function returns, you are a daemon.
  51. If use_syslog or a log_file is specified then this installs a logger.
  52. Iff capture_output is specified then stdout and stderr
  53. are also directed to syslog.
  54. If use_syslog is None then it defaults to True if no log_file
  55. is provided and the platform is not Darwin.
  56. The following arguments are passed through to BTL.log.injectLogger.
  57. use_syslog, log_file, verbose,
  58. capture_output, twisted_error_log_level,
  59. twisted_info_log_level, capture_stderr_log_level,
  60. capture_stdout_log_level, capture_stderr_name,
  61. capture_stdout_name, log_level, log_twisted
  62. daemon no longer removes pid file. Ex: If
  63. a monitor sees that a pidfile exists and the process is not
  64. running then the monitor restarts the process.
  65. If you want the process to REALLY die then the
  66. pid file should be removed external to the program,
  67. e.g., by an init.d script that is passed "stop".
  68. """
  69. assert log_file is None or logfile is None, "logfile was provided for backwards " \
  70. "compatibility. You cannot specify both log_file and logfile."
  71. if log_file is None:
  72. log_file = logfile
  73. try:
  74. if os.name == 'mac':
  75. raise NotImplementedError( "Daemonization doesn't work on macs." )
  76. if noisy:
  77. print "in daemon"
  78. uid = os.getuid()
  79. gid = os.getgid()
  80. if uid == 0 and username is None:
  81. raise Exception( "If you start with root privileges you need to "
  82. "provide a username argument so that daemon() can shed those "
  83. "privileges before returning." )
  84. if username:
  85. uid = getuid_from_username(username)
  86. if noisy:
  87. print "setting username to uid of '%s', which is %d." % ( username, uid )
  88. if uid != os.getuid() and os.getuid() != 0:
  89. raise Exception( "When specifying a uid other than your own "
  90. "you must be running as root for setuid to work. "
  91. "Your uid is %d, while the specified user '%s' has uid %d."
  92. % ( os.getuid(), username, uid ) )
  93. gid = getgid_from_username(username) # uses this user's group
  94. if groupname:
  95. if noisy:
  96. print "setting groupname to gid of '%s', which is %d." % (groupname,gid)
  97. gid = getgid_from_groupname(groupname)
  98. pid_dir = os.path.split(pidfile)[0]
  99. if pid_dir and not os.path.exists(pid_dir):
  100. os.mkdir(pid_dir)
  101. os.chown(pid_dir,uid,gid)
  102. checkPID(pidfile)
  103. if use_syslog is None:
  104. use_syslog = sys.platform != 'darwin' and not log_file
  105. if log_file:
  106. if use_syslog:
  107. raise Exception( "You have specified both a log_file and "
  108. "that the daemon should use_syslog. Specify one or "
  109. "the other." )
  110. print "Calling injectLogger"
  111. injectLogger(use_syslog=False, log_file = log_file, log_level = log_level,
  112. capture_output = capture_output, verbose = verbose,
  113. capture_stdout_name = capture_stdout_name,
  114. capture_stderr_name = capture_stderr_name,
  115. twisted_info_log_level = twisted_info_log_level,
  116. twisted_error_log_level = twisted_error_log_level,
  117. capture_stdout_log_level = capture_stdout_log_level,
  118. capture_stderr_log_level = capture_stderr_log_level,
  119. use_localtime = use_localtime )
  120. elif use_syslog:
  121. injectLogger(use_syslog=True, log_level = log_level, verbose = verbose,
  122. capture_output = capture_output,
  123. capture_stdout_name = capture_stdout_name,
  124. capture_stderr_name = capture_stderr_name,
  125. twisted_info_log_level = twisted_info_log_level,
  126. twisted_error_log_level = twisted_error_log_level,
  127. capture_stdout_log_level = capture_stdout_log_level,
  128. capture_stderr_log_level = capture_stderr_log_level )
  129. else:
  130. raise Exception( "You are attempting to daemonize without a log file,"
  131. "and with use_syslog set to false. A daemon must "
  132. "output to syslog, a logfile, or both." )
  133. if pidfile is None:
  134. pid_dir = os.path.join("/var/run/", app_name )
  135. pidfile = os.path.join( pid_dir, app_name + ".pid")
  136. daemonize() # forks, moves into its own process group, forks again,
  137. # middle process exits with status 0. Redirects stdout,
  138. # stderr to /dev/null.
  139. # I should now be a daemon.
  140. open(pidfile,'wb').write(str(os.getpid()))
  141. if not os.path.exists(pidfile):
  142. raise Exception( "pidfile %s does not exist" % pidfile )
  143. os.chmod(pidfile, stat.S_IRUSR|stat.S_IWUSR|stat.S_IROTH|stat.S_IRGRP)
  144. if os.getuid() == 0:
  145. if uid is not None or gid is not None:
  146. switchUID(uid, gid)
  147. if os.getuid() != uid:
  148. raise Exception( "failed to setuid to uid %d" % uid )
  149. if os.getgid() != gid:
  150. raise Exception( "failed to setgid to gid %d" % gid )
  151. except:
  152. log.exception("daemonizing may have failed")
  153. import traceback
  154. traceback.print_exc()
  155. raise
  156. # Copied from twistd.... see daemonize for reason.
  157. def checkPID(pidfile):
  158. if not pidfile:
  159. return
  160. if os.path.exists(pidfile):
  161. try:
  162. pid = int(open(pidfile).read())
  163. except ValueError:
  164. sys.exit('Pidfile %s contains non-numeric value' % pidfile)
  165. try:
  166. os.kill(pid, 0)
  167. except OSError, why:
  168. if why[0] == errno.ESRCH:
  169. # The pid doesnt exists.
  170. log.warning('Removing stale pidfile %s' % pidfile)
  171. os.remove(pidfile)
  172. else:
  173. sys.exit("Can't check status of PID %s from pidfile %s: %s" %
  174. (pid, pidfile, why[1]))
  175. else:
  176. sys.exit("""\
  177. Another twistd server is running, PID %s\n
  178. This could either be a previously started instance of your application or a
  179. different application entirely. To start a new one, either run it in some other
  180. directory, or use the --pidfile and --logfile parameters to avoid clashes.
  181. """ % pid)
  182. # Copied from twistd. twistd considers this an internal function
  183. # and across versions it got moved. To prevent future breakage,
  184. # I just assume incorporate daemonize directly.
  185. def daemonize():
  186. # See http://www.erlenstar.demon.co.uk/unix/faq_toc.html#TOC16
  187. if os.fork(): # launch child and...
  188. os._exit(0) # kill off parent
  189. os.setsid()
  190. if os.fork(): # launch child and...
  191. os._exit(0) # kill off parent again.
  192. os.umask(077)
  193. null=os.open('/dev/null', os.O_RDWR)
  194. for i in range(3):
  195. try:
  196. os.dup2(null, i)
  197. except OSError, e:
  198. if e.errno != errno.EBADF:
  199. raise
  200. os.close(null)