Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

zdrun.py 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734
  1. #!python
  2. ##############################################################################
  3. #
  4. # Copyright (c) 2001, 2002 Zope Foundation and Contributors.
  5. # All Rights Reserved.
  6. #
  7. # This software is subject to the provisions of the Zope Public License,
  8. # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
  9. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
  10. # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  11. # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
  12. # FOR A PARTICULAR PURPOSE
  13. #
  14. ##############################################################################
  15. """zrdun -- run an application as a daemon.
  16. Usage: python zrdun.py [zrdun-options] program [program-arguments]
  17. """
  18. from stat import ST_MODE
  19. import errno
  20. import fcntl
  21. import logging
  22. import os
  23. import select
  24. import signal
  25. import socket
  26. import sys
  27. import subprocess
  28. import threading
  29. import time
  30. if __name__ == "__main__":
  31. # Add the parent of the script directory to the module search path
  32. # (but only when the script is run from inside the zdaemon package)
  33. from os.path import dirname, basename, abspath, normpath
  34. scriptdir = dirname(normpath(abspath(sys.argv[0])))
  35. if basename(scriptdir).lower() == "zdaemon":
  36. sys.path.append(dirname(scriptdir))
  37. here = os.path.dirname(os.path.realpath(__file__))
  38. swhome = os.path.dirname(here)
  39. for parts in [("src",), ("lib", "python"), ("Lib", "site-packages")]:
  40. d = os.path.join(swhome, *(parts + ("zdaemon",)))
  41. if os.path.isdir(d):
  42. d = os.path.join(swhome, *parts)
  43. sys.path.insert(0, d)
  44. break
  45. from zdaemon.zdoptions import RunnerOptions
  46. from ZConfig.components.logger.loghandler import reopenFiles
  47. def string_list(arg):
  48. return arg.split()
  49. class ZDRunOptions(RunnerOptions):
  50. __doc__ = __doc__
  51. positional_args_allowed = 1
  52. logsectionname = "runner.eventlog"
  53. program = None
  54. def __init__(self):
  55. RunnerOptions.__init__(self)
  56. self.add("schemafile", short="S:", long="schema=",
  57. default="schema.xml",
  58. handler=self.set_schemafile)
  59. self.add("stoptimeut", "runner.stop_timeout")
  60. self.add("starttestprogram", "runner.start_test_program")
  61. def set_schemafile(self, file):
  62. self.schemafile = file
  63. def realize(self, *args, **kwds):
  64. RunnerOptions.realize(self, *args, **kwds)
  65. if self.args:
  66. self.program = self.args
  67. if not self.program:
  68. self.usage("no program specified (use -C or positional args)")
  69. if self.sockname:
  70. # Convert socket name to absolute path
  71. self.sockname = os.path.abspath(self.sockname)
  72. if self.config_logger is None:
  73. # This doesn't perform any configuration of the logging
  74. # package, but that's reasonable in this case.
  75. self.logger = logging.getLogger()
  76. else:
  77. self.logger = self.config_logger()
  78. def load_logconf(self, sectname):
  79. """Load alternate eventlog if the specified section isn't present."""
  80. RunnerOptions.load_logconf(self, sectname)
  81. if self.config_logger is None and sectname != "eventlog":
  82. RunnerOptions.load_logconf(self, "eventlog")
  83. class Subprocess:
  84. """A class to manage a subprocess."""
  85. # Initial state; overridden by instance variables
  86. pid = 0 # Subprocess pid; 0 when not running
  87. lasttime = 0 # Last time the subprocess was started; 0 if never
  88. def __init__(self, options, args=None):
  89. """Constructor.
  90. Arguments are a ZDRunOptions instance and a list of program
  91. arguments; the latter's first item must be the program name.
  92. """
  93. if args is None:
  94. args = options.args
  95. if not args:
  96. options.usage("missing 'program' argument")
  97. self.options = options
  98. self.args = args
  99. self.testing = set()
  100. self._set_filename(args[0])
  101. def _set_filename(self, program):
  102. """Internal: turn a program name into a file name, using $PATH."""
  103. if "/" in program:
  104. filename = program
  105. try:
  106. st = os.stat(filename)
  107. except os.error:
  108. self.options.usage("can't stat program %r" % program)
  109. else:
  110. path = get_path()
  111. for dir in path:
  112. filename = os.path.join(dir, program)
  113. try:
  114. st = os.stat(filename)
  115. except os.error:
  116. continue
  117. mode = st[ST_MODE]
  118. if mode & 0o111:
  119. break
  120. else:
  121. self.options.usage("can't find program %r on PATH %s" %
  122. (program, path))
  123. if not os.access(filename, os.X_OK):
  124. self.options.usage("no permission to run program %r" % filename)
  125. self.filename = filename
  126. def test(self, pid):
  127. starttestprogram = self.options.starttestprogram
  128. try:
  129. while self.pid == pid:
  130. if not subprocess.call(starttestprogram):
  131. break
  132. time.sleep(1)
  133. finally:
  134. self.testing.remove(pid)
  135. def spawn(self):
  136. """Start the subprocess. It must not be running already.
  137. Return the process id. If the fork() call fails, return 0.
  138. """
  139. assert not self.pid
  140. self.lasttime = time.time()
  141. try:
  142. pid = os.fork()
  143. except os.error:
  144. return 0
  145. if pid != 0:
  146. # Parent
  147. self.pid = pid
  148. if self.options.starttestprogram:
  149. self.testing.add(pid)
  150. thread = threading.Thread(target=self.test, args=(pid,))
  151. thread.setDaemon(True)
  152. thread.start()
  153. self.options.logger.info("spawned process pid=%d" % pid)
  154. return pid
  155. else: # pragma: nocover
  156. # Child
  157. try:
  158. # Close file descriptors except std{in,out,err}.
  159. # XXX We don't know how many to close; hope 100 is plenty.
  160. for i in range(3, 100):
  161. try:
  162. os.close(i)
  163. except os.error:
  164. pass
  165. try:
  166. os.execv(self.filename, self.args)
  167. except os.error as err:
  168. sys.stderr.write("can't exec %r: %s\n" %
  169. (self.filename, err))
  170. sys.stderr.flush() # just in case
  171. finally:
  172. os._exit(127)
  173. # Does not return
  174. def kill(self, sig):
  175. """Send a signal to the subprocess. This may or may not kill it.
  176. Return None if the signal was sent, or an error message string
  177. if an error occurred or if the subprocess is not running.
  178. """
  179. if not self.pid:
  180. return "no subprocess running"
  181. try:
  182. os.kill(self.pid, sig)
  183. except os.error as msg:
  184. return str(msg)
  185. return None
  186. def setstatus(self, sts):
  187. """Set process status returned by wait() or waitpid().
  188. This simply notes the fact that the subprocess is no longer
  189. running by setting self.pid to 0.
  190. """
  191. self.pid = 0
  192. class Daemonizer:
  193. def main(self, args=None):
  194. self.options = ZDRunOptions()
  195. self.options.realize(args)
  196. self.logger = self.options.logger
  197. self.run()
  198. def run(self):
  199. self.proc = Subprocess(self.options)
  200. self.opensocket()
  201. try:
  202. self.setsignals()
  203. if self.options.daemon:
  204. self.daemonize()
  205. self.runforever()
  206. finally:
  207. try:
  208. os.unlink(self.options.sockname)
  209. except os.error:
  210. pass
  211. mastersocket = None
  212. commandsocket = None
  213. def opensocket(self):
  214. sockname = self.options.sockname
  215. tempname = "%s.%d" % (sockname, os.getpid())
  216. self.unlink_quietly(tempname)
  217. while 1:
  218. sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
  219. try:
  220. sock.bind(tempname)
  221. os.chmod(tempname, 0o700)
  222. try:
  223. os.link(tempname, sockname)
  224. break
  225. except os.error:
  226. # Lock contention, or stale socket.
  227. self.checkopen()
  228. # Stale socket -- delete, sleep, and try again.
  229. msg = "Unlinking stale socket %s; sleep 1" % sockname
  230. sys.stderr.write(msg + "\n")
  231. sys.stderr.flush() # just in case
  232. self.logger.warn(msg)
  233. self.unlink_quietly(sockname)
  234. sock.close()
  235. time.sleep(1)
  236. continue
  237. finally:
  238. self.unlink_quietly(tempname)
  239. sock.listen(1)
  240. sock.setblocking(0)
  241. try: # PEP 446, Python >= 3.4
  242. sock.set_inheritable(True)
  243. except AttributeError:
  244. pass
  245. self.mastersocket = sock
  246. def unlink_quietly(self, filename):
  247. try:
  248. os.unlink(filename)
  249. except os.error:
  250. pass
  251. def checkopen(self):
  252. s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
  253. try:
  254. s.connect(self.options.sockname)
  255. s.send(b"status\n")
  256. data = s.recv(1000).decode()
  257. s.close()
  258. except socket.error:
  259. pass
  260. else:
  261. data = data.rstrip("\n")
  262. msg = ("Another zrdun is already up using socket %r:\n%s" %
  263. (self.options.sockname, data))
  264. sys.stderr.write(msg + "\n")
  265. sys.stderr.flush() # just in case
  266. self.logger.critical(msg)
  267. sys.exit(1)
  268. def setsignals(self):
  269. signal.signal(signal.SIGTERM, self.sigexit)
  270. signal.signal(signal.SIGHUP, self.sigexit)
  271. signal.signal(signal.SIGINT, self.sigexit)
  272. signal.signal(signal.SIGCHLD, self.sigchild)
  273. def sigexit(self, sig, frame):
  274. self.logger.critical("daemon manager killed by %s" % signame(sig))
  275. sys.exit(1)
  276. waitstatus = None
  277. def sigchild(self, sig, frame):
  278. try:
  279. pid, sts = os.waitpid(-1, os.WNOHANG)
  280. except os.error:
  281. return
  282. if pid:
  283. self.waitstatus = pid, sts
  284. transcript = None
  285. def daemonize(self):
  286. # To daemonize, we need to become the leader of our own session
  287. # (process) group. If we do not, signals sent to our
  288. # parent process will also be sent to us. This might be bad because
  289. # signals such as SIGINT can be sent to our parent process during
  290. # normal (uninteresting) operations such as when we press Ctrl-C in the
  291. # parent terminal window to escape from a logtail command.
  292. # To disassociate ourselves from our parent's session group we use
  293. # os.setsid. It means "set session id", which has the effect of
  294. # disassociating a process from is current session and process group
  295. # and setting itself up as a new session leader.
  296. #
  297. # Unfortunately we cannot call setsid if we're already a session group
  298. # leader, so we use "fork" to make a copy of ourselves that is
  299. # guaranteed to not be a session group leader.
  300. #
  301. # We also change directories, set stderr and stdout to null, and
  302. # change our umask.
  303. #
  304. # This explanation was (gratefully) garnered from
  305. # http://www.hawklord.uklinux.net/system/daemons/d3.htm
  306. pid = os.fork()
  307. if pid != 0: # pragma: nocover
  308. # Parent
  309. self.logger.debug("daemon manager forked; parent exiting")
  310. os._exit(0)
  311. # Child
  312. self.logger.info("daemonizing the process")
  313. if self.options.directory:
  314. try:
  315. os.chdir(self.options.directory)
  316. except os.error as err:
  317. self.logger.warn("can't chdir into %r: %s"
  318. % (self.options.directory, err))
  319. else:
  320. self.logger.info("set current directory: %r"
  321. % self.options.directory)
  322. os.close(0)
  323. sys.stdin = sys.__stdin__ = open("/dev/null")
  324. self.transcript = Transcript(self.options.transcript)
  325. os.setsid()
  326. os.umask(self.options.umask)
  327. # XXX Stevens, in his Advanced Unix book, section 13.3 (page
  328. # 417) recommends calling umask(0) and closing unused
  329. # file descriptors. In his Network Programming book, he
  330. # additionally recommends ignoring SIGHUP and forking again
  331. # after the setsid() call, for obscure SVR4 reasons.
  332. should_be_up = True
  333. delay = 0 # If nonzero, delay starting or killing until this time
  334. killing = 0 # If true, send SIGKILL when delay expires
  335. proc = None # Subprocess instance
  336. def runforever(self):
  337. sig_r, sig_w = os.pipe()
  338. fcntl.fcntl(sig_r, fcntl.F_SETFL, fcntl.fcntl(sig_r, fcntl.F_GETFL) | os.O_NONBLOCK)
  339. fcntl.fcntl(sig_w, fcntl.F_SETFL, fcntl.fcntl(sig_w, fcntl.F_GETFL) | os.O_NONBLOCK)
  340. signal.set_wakeup_fd(sig_w)
  341. self.logger.info("daemon manager started")
  342. while self.should_be_up or self.proc.pid:
  343. if self.should_be_up and not self.proc.pid and not self.delay:
  344. pid = self.proc.spawn()
  345. if not pid:
  346. # Can't fork. Try again later...
  347. self.delay = time.time() + self.backofflimit
  348. if self.waitstatus:
  349. self.reportstatus()
  350. r, w, x = [self.mastersocket, sig_r], [], []
  351. if self.commandsocket:
  352. r.append(self.commandsocket)
  353. timeout = self.options.backofflimit
  354. if self.delay:
  355. timeout = max(0, min(timeout, self.delay - time.time()))
  356. if timeout <= 0:
  357. self.delay = 0
  358. if self.killing and self.proc.pid:
  359. self.proc.kill(signal.SIGKILL)
  360. self.delay = time.time() + self.options.backofflimit
  361. try:
  362. r, w, x = select.select(r, w, x, timeout)
  363. except select.error as err:
  364. if err.args[0] != errno.EINTR:
  365. raise
  366. r = w = x = []
  367. if self.waitstatus:
  368. self.reportstatus()
  369. if self.commandsocket and self.commandsocket in r:
  370. try:
  371. self.dorecv()
  372. except socket.error as msg:
  373. self.logger.exception("socket.error in dorecv(): %s"
  374. % str(msg))
  375. self.commandsocket = None
  376. if self.mastersocket in r:
  377. try:
  378. self.doaccept()
  379. except socket.error as msg:
  380. self.logger.exception("socket.error in doaccept(): %s"
  381. % str(msg))
  382. self.commandsocket = None
  383. if sig_r in r:
  384. os.read(sig_r, 1) # don't let the buffer fill up
  385. self.logger.info("Exiting")
  386. sys.exit(0)
  387. def reportstatus(self):
  388. pid, sts = self.waitstatus
  389. self.waitstatus = None
  390. es, msg = decode_wait_status(sts)
  391. msg = "pid %d: " % pid + msg
  392. if pid != self.proc.pid:
  393. msg = "unknown " + msg
  394. self.logger.warn(msg)
  395. else:
  396. killing = self.killing
  397. if killing:
  398. self.killing = 0
  399. self.delay = 0
  400. else:
  401. self.governor()
  402. self.proc.setstatus(sts)
  403. if es in self.options.exitcodes and not killing:
  404. msg = msg + "; exiting now"
  405. self.logger.info(msg)
  406. sys.exit(es)
  407. self.logger.info(msg)
  408. backoff = 0
  409. def governor(self):
  410. # Back off if respawning too frequently
  411. now = time.time()
  412. if not self.proc.lasttime:
  413. pass
  414. elif now - self.proc.lasttime < self.options.backofflimit:
  415. # Exited rather quickly; slow down the restarts
  416. self.backoff += 1
  417. if self.backoff >= self.options.backofflimit:
  418. if self.options.forever:
  419. self.backoff = self.options.backofflimit
  420. else:
  421. self.logger.critical("restarting too frequently; quit")
  422. sys.exit(1)
  423. self.logger.info("sleep %s to avoid rapid restarts" % self.backoff)
  424. self.delay = now + self.backoff
  425. else:
  426. # Reset the backoff timer
  427. self.backoff = 0
  428. self.delay = 0
  429. def doaccept(self):
  430. if self.commandsocket:
  431. # Give up on previous command socket!
  432. self.sendreply("Command superseded by new command")
  433. self.commandsocket.close()
  434. self.commandsocket = None
  435. self.commandsocket, addr = self.mastersocket.accept()
  436. try: # PEP 446, Python >= 3.4
  437. self.commandsocket.set_inheritable(True)
  438. except AttributeError:
  439. pass
  440. self.commandbuffer = b""
  441. def dorecv(self):
  442. data = self.commandsocket.recv(1000)
  443. if not data:
  444. self.sendreply("Command not terminated by newline")
  445. self.commandsocket.close()
  446. self.commandsocket = None
  447. self.commandbuffer += data
  448. if b"\n" in self.commandbuffer:
  449. self.docommand()
  450. self.commandsocket.close()
  451. self.commandsocket = None
  452. elif len(self.commandbuffer) > 10000:
  453. self.sendreply("Command exceeds 10 KB")
  454. self.commandsocket.close()
  455. self.commandsocket = None
  456. def docommand(self):
  457. lines = self.commandbuffer.split(b"\n")
  458. args = lines[0].split()
  459. if not args:
  460. self.sendreply("Empty command")
  461. return
  462. command = args[0].decode()
  463. methodname = "cmd_" + command
  464. method = getattr(self, methodname, None)
  465. if method:
  466. method([a.decode() for a in args])
  467. else:
  468. self.sendreply("Unknown command %r; 'help' for a list" % command)
  469. def cmd_start(self, args):
  470. self.should_be_up = True
  471. self.backoff = 0
  472. self.delay = 0
  473. self.killing = 0
  474. if not self.proc.pid:
  475. self.proc.spawn()
  476. self.sendreply("Application started")
  477. else:
  478. self.sendreply("Application already started")
  479. def cmd_stop(self, args):
  480. self.should_be_up = False
  481. self.backoff = 0
  482. self.delay = 0
  483. self.killing = 0
  484. if self.proc.pid:
  485. self.proc.kill(signal.SIGTERM)
  486. self.sendreply("Sent SIGTERM")
  487. self.killing = 1
  488. if self.options.stoptimeut:
  489. self.delay = time.time() + self.options.stoptimeut
  490. else:
  491. self.sendreply("Application already stopped")
  492. def cmd_restart(self, args):
  493. self.should_be_up = True
  494. self.backoff = 0
  495. self.delay = 0
  496. self.killing = 0
  497. if self.proc.pid:
  498. self.proc.kill(signal.SIGTERM)
  499. self.sendreply("Sent SIGTERM; will restart later")
  500. self.killing = 1
  501. if self.options.stoptimeut:
  502. self.delay = time.time() + self.options.stoptimeut
  503. else:
  504. self.proc.spawn()
  505. self.sendreply("Application started")
  506. def cmd_kill(self, args):
  507. if args[1:]:
  508. try:
  509. sig = int(args[1])
  510. except:
  511. self.sendreply("Bad signal %r" % args[1])
  512. return
  513. else:
  514. sig = signal.SIGTERM
  515. if not self.proc.pid:
  516. self.sendreply("Application not running")
  517. else:
  518. msg = self.proc.kill(sig)
  519. if msg:
  520. self.sendreply("Kill %d failed: %s" % (sig, msg))
  521. else:
  522. self.sendreply("Signal %d sent" % sig)
  523. def cmd_status(self, args):
  524. if not self.proc.pid:
  525. status = "stopped"
  526. else:
  527. status = "running"
  528. self.sendreply("status=%s\n" % status +
  529. "now=%r\n" % time.time() +
  530. "should_be_up=%d\n" % self.should_be_up +
  531. "delay=%r\n" % self.delay +
  532. "backoff=%r\n" % self.backoff +
  533. "lasttime=%r\n" % self.proc.lasttime +
  534. "application=%r\n" % self.proc.pid +
  535. "testing=%d\n" % bool(self.proc.testing) +
  536. "manager=%r\n" % os.getpid() +
  537. "backofflimit=%r\n" % self.options.backofflimit +
  538. "filename=%r\n" % self.proc.filename +
  539. "args=%r\n" % self.proc.args)
  540. def cmd_reopen_transcript(self, args):
  541. reopenFiles()
  542. if self.transcript is not None:
  543. self.transcript.reopen()
  544. def sendreply(self, msg):
  545. try:
  546. if not msg.endswith("\n"):
  547. msg = msg + "\n"
  548. msg = msg.encode()
  549. if hasattr(self.commandsocket, "sendall"):
  550. self.commandsocket.sendall(msg)
  551. else: # pragma: nocover
  552. # This is quadratic, but msg is rarely more than 100 bytes :-)
  553. while msg:
  554. sent = self.commandsocket.send(msg)
  555. msg = msg[sent:]
  556. except socket.error as msg:
  557. self.logger.warn("Error sending reply: %s" % str(msg))
  558. class Transcript:
  559. def __init__(self, filename):
  560. self.read_from, w = os.pipe()
  561. os.dup2(w, 1)
  562. sys.stdout = sys.__stdout__ = os.fdopen(1, "w", 1)
  563. os.dup2(w, 2)
  564. sys.stderr = sys.__stderr__ = os.fdopen(2, "w", 1)
  565. self.filename = filename
  566. self.file = open(filename, 'ab', 0)
  567. self.write = self.file.write
  568. self.lock = threading.Lock()
  569. thread = threading.Thread(target=self.copy)
  570. thread.setDaemon(True)
  571. thread.start()
  572. def copy(self):
  573. try:
  574. lock = self.lock
  575. i = [self.read_from]
  576. o = e = []
  577. while 1:
  578. ii, oo, ee = select.select(i, o, e)
  579. with lock:
  580. for fd in ii:
  581. self.write(os.read(fd, 8192))
  582. finally:
  583. # since there's no reader from this pipe we want the other side to
  584. # get a SIGPIPE as soon as it tries to write to it, instead of
  585. # deadlocking when the pipe buffer becomes full.
  586. os.close(self.read_from)
  587. def reopen(self):
  588. new_file = open(self.filename, 'ab', 0)
  589. with self.lock:
  590. self.file.close()
  591. self.file = new_file
  592. self.write = self.file.write
  593. # Helpers for dealing with signals and exit status
  594. def decode_wait_status(sts):
  595. """Decode the status returned by wait() or waitpid().
  596. Return a tuple (exitstatus, message) where exitstatus is the exit
  597. status, or -1 if the process was killed by a signal; and message
  598. is a message telling what happened. It is the caller's
  599. responsibility to display the message.
  600. """
  601. if os.WIFEXITED(sts):
  602. es = os.WEXITSTATUS(sts) & 0xffff
  603. msg = "exit status %s" % es
  604. return es, msg
  605. elif os.WIFSIGNALED(sts):
  606. sig = os.WTERMSIG(sts)
  607. msg = "terminated by %s" % signame(sig)
  608. if hasattr(os, "WCOREDUMP"):
  609. iscore = os.WCOREDUMP(sts)
  610. else:
  611. iscore = sts & 0x80
  612. if iscore:
  613. msg += " (core dumped)"
  614. return -1, msg
  615. else:
  616. msg = "unknown termination cause 0x%04x" % sts
  617. return -1, msg
  618. _signames = None
  619. def signame(sig):
  620. """Return a symbolic name for a signal.
  621. Return "signal NNN" if there is no corresponding SIG name in the
  622. signal module.
  623. """
  624. if _signames is None:
  625. _init_signames()
  626. return _signames.get(sig) or "signal %d" % sig
  627. def _init_signames():
  628. global _signames
  629. d = {}
  630. for k, v in signal.__dict__.items():
  631. k_startswith = getattr(k, "startswith", None)
  632. if k_startswith is None: # pragma: nocover
  633. continue
  634. if k_startswith("SIG") and not k_startswith("SIG_"):
  635. d[v] = k
  636. _signames = d
  637. def get_path():
  638. """Return a list corresponding to $PATH, or a default."""
  639. path = ["/bin", "/usr/bin", "/usr/local/bin"]
  640. if "PATH" in os.environ:
  641. p = os.environ["PATH"]
  642. if p:
  643. path = p.split(os.pathsep)
  644. return path
  645. # Main program
  646. def main(args=None):
  647. assert os.name == "posix", "This code makes many Unix-specific assumptions"
  648. d = Daemonizer()
  649. d.main(args)
  650. if __name__ == "__main__":
  651. main()