options.py 75 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022
  1. import ConfigParser
  2. import socket
  3. import getopt
  4. import os
  5. import sys
  6. import tempfile
  7. import errno
  8. import signal
  9. import re
  10. import xmlrpclib
  11. import pwd
  12. import grp
  13. import resource
  14. import stat
  15. import pkg_resources
  16. import select
  17. import glob
  18. import platform
  19. import warnings
  20. from fcntl import fcntl
  21. from fcntl import F_SETFL, F_GETFL
  22. from supervisor.medusa import asyncore_25 as asyncore
  23. from supervisor.datatypes import process_or_group_name
  24. from supervisor.datatypes import boolean
  25. from supervisor.datatypes import integer
  26. from supervisor.datatypes import name_to_uid
  27. from supervisor.datatypes import gid_for_uid
  28. from supervisor.datatypes import existing_dirpath
  29. from supervisor.datatypes import byte_size
  30. from supervisor.datatypes import signal_number
  31. from supervisor.datatypes import list_of_exitcodes
  32. from supervisor.datatypes import dict_of_key_value_pairs
  33. from supervisor.datatypes import logfile_name
  34. from supervisor.datatypes import list_of_strings
  35. from supervisor.datatypes import octal_type
  36. from supervisor.datatypes import existing_directory
  37. from supervisor.datatypes import logging_level
  38. from supervisor.datatypes import colon_separated_user_group
  39. from supervisor.datatypes import inet_address
  40. from supervisor.datatypes import InetStreamSocketConfig
  41. from supervisor.datatypes import UnixStreamSocketConfig
  42. from supervisor.datatypes import url
  43. from supervisor.datatypes import Automatic
  44. from supervisor.datatypes import auto_restart
  45. from supervisor.datatypes import profile_options
  46. from supervisor.datatypes import set_here
  47. from supervisor import loggers
  48. from supervisor import states
  49. from supervisor import xmlrpc
  50. mydir = os.path.abspath(os.path.dirname(__file__))
  51. version_txt = os.path.join(mydir, 'version.txt')
  52. VERSION = open(version_txt).read().strip()
  53. def normalize_path(v):
  54. return os.path.normpath(os.path.abspath(os.path.expanduser(v)))
  55. class Dummy:
  56. pass
  57. class Options:
  58. stderr = sys.stderr
  59. stdout = sys.stdout
  60. exit = sys.exit
  61. warnings = warnings
  62. uid = gid = None
  63. progname = sys.argv[0]
  64. configfile = None
  65. schemadir = None
  66. configroot = None
  67. here = None
  68. # Class variable deciding whether positional arguments are allowed.
  69. # If you want positional arguments, set this to 1 in your subclass.
  70. positional_args_allowed = 0
  71. def __init__(self, require_configfile=True):
  72. """Constructor.
  73. Params:
  74. require_configfile -- whether we should fail on no config file.
  75. """
  76. self.names_list = []
  77. self.short_options = []
  78. self.long_options = []
  79. self.options_map = {}
  80. self.default_map = {}
  81. self.required_map = {}
  82. self.environ_map = {}
  83. self.attr_priorities = {}
  84. self.require_configfile = require_configfile
  85. self.add(None, None, "h", "help", self.help)
  86. self.add("configfile", None, "c:", "configuration=")
  87. here = os.path.dirname(os.path.dirname(sys.argv[0]))
  88. searchpaths = [os.path.join(here, 'etc', 'supervisord.conf'),
  89. os.path.join(here, 'supervisord.conf'),
  90. 'supervisord.conf', 'etc/supervisord.conf',
  91. '/etc/supervisord.conf']
  92. self.searchpaths = searchpaths
  93. def default_configfile(self):
  94. """Return the name of the found config file or print usage/exit."""
  95. config = None
  96. for path in self.searchpaths:
  97. if os.path.exists(path):
  98. config = path
  99. break
  100. if config is None and self.require_configfile:
  101. self.usage('No config file found at default paths (%s); '
  102. 'use the -c option to specify a config file '
  103. 'at a different path' % ', '.join(self.searchpaths))
  104. return config
  105. def help(self, dummy):
  106. """Print a long help message to stdout and exit(0).
  107. Occurrences of "%s" in are replaced by self.progname.
  108. """
  109. help = self.doc + "\n"
  110. if help.find("%s") > 0:
  111. help = help.replace("%s", self.progname)
  112. self.stdout.write(help)
  113. self.exit(0)
  114. def usage(self, msg):
  115. """Print a brief error message to stderr and exit(2)."""
  116. self.stderr.write("Error: %s\n" % str(msg))
  117. self.stderr.write("For help, use %s -h\n" % self.progname)
  118. self.exit(2)
  119. def add(self,
  120. name=None, # attribute name on self
  121. confname=None, # dotted config path name
  122. short=None, # short option name
  123. long=None, # long option name
  124. handler=None, # handler (defaults to string)
  125. default=None, # default value
  126. required=None, # message if not provided
  127. flag=None, # if not None, flag value
  128. env=None, # if not None, environment variable
  129. ):
  130. """Add information about a configuration option.
  131. This can take several forms:
  132. add(name, confname)
  133. Configuration option 'confname' maps to attribute 'name'
  134. add(name, None, short, long)
  135. Command line option '-short' or '--long' maps to 'name'
  136. add(None, None, short, long, handler)
  137. Command line option calls handler
  138. add(name, None, short, long, handler)
  139. Assign handler return value to attribute 'name'
  140. In addition, one of the following keyword arguments may be given:
  141. default=... -- if not None, the default value
  142. required=... -- if nonempty, an error message if no value provided
  143. flag=... -- if not None, flag value for command line option
  144. env=... -- if not None, name of environment variable that
  145. overrides the configuration file or default
  146. """
  147. if flag is not None:
  148. if handler is not None:
  149. raise ValueError, "use at most one of flag= and handler="
  150. if not long and not short:
  151. raise ValueError, "flag= requires a command line flag"
  152. if short and short.endswith(":"):
  153. raise ValueError, "flag= requires a command line flag"
  154. if long and long.endswith("="):
  155. raise ValueError, "flag= requires a command line flag"
  156. handler = lambda arg, flag=flag: flag
  157. if short and long:
  158. if short.endswith(":") != long.endswith("="):
  159. raise ValueError, "inconsistent short/long options: %r %r" % (
  160. short, long)
  161. if short:
  162. if short[0] == "-":
  163. raise ValueError, "short option should not start with '-'"
  164. key, rest = short[:1], short[1:]
  165. if rest not in ("", ":"):
  166. raise ValueError, "short option should be 'x' or 'x:'"
  167. key = "-" + key
  168. if self.options_map.has_key(key):
  169. raise ValueError, "duplicate short option key '%s'" % key
  170. self.options_map[key] = (name, handler)
  171. self.short_options.append(short)
  172. if long:
  173. if long[0] == "-":
  174. raise ValueError, "long option should not start with '-'"
  175. key = long
  176. if key[-1] == "=":
  177. key = key[:-1]
  178. key = "--" + key
  179. if self.options_map.has_key(key):
  180. raise ValueError, "duplicate long option key '%s'" % key
  181. self.options_map[key] = (name, handler)
  182. self.long_options.append(long)
  183. if env:
  184. self.environ_map[env] = (name, handler)
  185. if name:
  186. if not hasattr(self, name):
  187. setattr(self, name, None)
  188. self.names_list.append((name, confname))
  189. if default is not None:
  190. self.default_map[name] = default
  191. if required:
  192. self.required_map[name] = required
  193. def _set(self, attr, value, prio):
  194. current = self.attr_priorities.get(attr, -1)
  195. if prio >= current:
  196. setattr(self, attr, value)
  197. self.attr_priorities[attr] = prio
  198. def realize(self, args=None, doc=None,
  199. progname=None, raise_getopt_errs=True):
  200. """Realize a configuration.
  201. Optional arguments:
  202. args -- the command line arguments, less the program name
  203. (default is sys.argv[1:])
  204. doc -- usage message (default is __main__.__doc__)
  205. """
  206. # Provide dynamic default method arguments
  207. if args is None:
  208. args = sys.argv[1:]
  209. if progname is None:
  210. progname = sys.argv[0]
  211. if doc is None:
  212. import __main__
  213. doc = __main__.__doc__
  214. self.progname = progname
  215. self.doc = doc
  216. self.options = []
  217. self.args = []
  218. # Call getopt
  219. try:
  220. self.options, self.args = getopt.getopt(
  221. args, "".join(self.short_options), self.long_options)
  222. except getopt.error, msg:
  223. if raise_getopt_errs:
  224. self.usage(msg)
  225. # Check for positional args
  226. if self.args and not self.positional_args_allowed:
  227. self.usage("positional arguments are not supported")
  228. # Process options returned by getopt
  229. for opt, arg in self.options:
  230. name, handler = self.options_map[opt]
  231. if handler is not None:
  232. try:
  233. arg = handler(arg)
  234. except ValueError, msg:
  235. self.usage("invalid value for %s %r: %s" % (opt, arg, msg))
  236. if name and arg is not None:
  237. if getattr(self, name) is not None:
  238. self.usage("conflicting command line option %r" % opt)
  239. self._set(name, arg, 2)
  240. # Process environment variables
  241. for envvar in self.environ_map.keys():
  242. name, handler = self.environ_map[envvar]
  243. if os.environ.has_key(envvar):
  244. value = os.environ[envvar]
  245. if handler is not None:
  246. try:
  247. value = handler(value)
  248. except ValueError, msg:
  249. self.usage("invalid environment value for %s %r: %s"
  250. % (envvar, value, msg))
  251. if name and value is not None:
  252. self._set(name, value, 1)
  253. if self.configfile is None:
  254. if os.getuid() == 0 and self.progname.find("supervisord") > -1: # pragma: no cover
  255. self.warnings.warn(
  256. 'Supervisord is running as root and it is searching '
  257. 'for its configuration file in default locations '
  258. '(including its current working directory); you '
  259. 'probably want to specify a "-c" argument specifying an '
  260. 'absolute path to a configuration file for improved '
  261. 'security.'
  262. )
  263. self.configfile = self.default_configfile()
  264. self.process_config()
  265. def process_config(self, do_usage=True):
  266. """Process configuration data structure.
  267. This includes reading config file if necessary, setting defaults etc.
  268. """
  269. if self.configfile:
  270. self.process_config_file(do_usage)
  271. # Copy config options to attributes of self. This only fills
  272. # in options that aren't already set from the command line.
  273. for name, confname in self.names_list:
  274. if confname:
  275. parts = confname.split(".")
  276. obj = self.configroot
  277. for part in parts:
  278. if obj is None:
  279. break
  280. # Here AttributeError is not a user error!
  281. obj = getattr(obj, part)
  282. self._set(name, obj, 0)
  283. # Process defaults
  284. for name, value in self.default_map.items():
  285. if getattr(self, name) is None:
  286. setattr(self, name, value)
  287. # Process required options
  288. for name, message in self.required_map.items():
  289. if getattr(self, name) is None:
  290. self.usage(message)
  291. def process_config_file(self, do_usage):
  292. # Process config file
  293. if not hasattr(self.configfile, 'read'):
  294. self.here = os.path.abspath(os.path.dirname(self.configfile))
  295. set_here(self.here)
  296. try:
  297. self.read_config(self.configfile)
  298. except ValueError, msg:
  299. if do_usage:
  300. # if this is not called from an RPC method, run usage and exit.
  301. self.usage(str(msg))
  302. else:
  303. # if this is called from an RPC method, raise an error
  304. raise ValueError(msg)
  305. def get_plugins(self, parser, factory_key, section_prefix):
  306. factories = []
  307. for section in parser.sections():
  308. if not section.startswith(section_prefix):
  309. continue
  310. name = section.split(':', 1)[1]
  311. factory_spec = parser.saneget(section, factory_key, None)
  312. if factory_spec is None:
  313. raise ValueError('section [%s] does not specify a %s' %
  314. (section, factory_key))
  315. try:
  316. factory = self.import_spec(factory_spec)
  317. except ImportError:
  318. raise ValueError('%s cannot be resolved within [%s]' % (
  319. factory_spec, section))
  320. items = parser.items(section)
  321. items.remove((factory_key, factory_spec))
  322. factories.append((name, factory, dict(items)))
  323. return factories
  324. def import_spec(self, spec):
  325. return pkg_resources.EntryPoint.parse("x="+spec).load(False)
  326. class ServerOptions(Options):
  327. user = None
  328. sockchown = None
  329. sockchmod = None
  330. logfile = None
  331. loglevel = None
  332. pidfile = None
  333. passwdfile = None
  334. nodaemon = None
  335. environment = None
  336. httpservers = ()
  337. unlink_socketfiles = True
  338. mood = states.SupervisorStates.RUNNING
  339. def __init__(self):
  340. Options.__init__(self)
  341. self.configroot = Dummy()
  342. self.configroot.supervisord = Dummy()
  343. self.add(None, None, "v", "version", self.version)
  344. self.add("nodaemon", "supervisord.nodaemon", "n", "nodaemon", flag=1,
  345. default=0)
  346. self.add("user", "supervisord.user", "u:", "user=")
  347. self.add("umask", "supervisord.umask", "m:", "umask=",
  348. octal_type, default='022')
  349. self.add("directory", "supervisord.directory", "d:", "directory=",
  350. existing_directory)
  351. self.add("logfile", "supervisord.logfile", "l:", "logfile=",
  352. existing_dirpath, default="supervisord.log")
  353. self.add("logfile_maxbytes", "supervisord.logfile_maxbytes",
  354. "y:", "logfile_maxbytes=", byte_size,
  355. default=50 * 1024 * 1024) # 50MB
  356. self.add("logfile_backups", "supervisord.logfile_backups",
  357. "z:", "logfile_backups=", integer, default=10)
  358. self.add("loglevel", "supervisord.loglevel", "e:", "loglevel=",
  359. logging_level, default="info")
  360. self.add("pidfile", "supervisord.pidfile", "j:", "pidfile=",
  361. existing_dirpath, default="supervisord.pid")
  362. self.add("identifier", "supervisord.identifier", "i:", "identifier=",
  363. str, default="supervisor")
  364. self.add("childlogdir", "supervisord.childlogdir", "q:", "childlogdir=",
  365. existing_directory, default=tempfile.gettempdir())
  366. self.add("minfds", "supervisord.minfds",
  367. "a:", "minfds=", int, default=1024)
  368. self.add("minprocs", "supervisord.minprocs",
  369. "", "minprocs=", int, default=200)
  370. self.add("nocleanup", "supervisord.nocleanup",
  371. "k", "nocleanup", flag=1, default=0)
  372. self.add("strip_ansi", "supervisord.strip_ansi",
  373. "t", "strip_ansi", flag=1, default=0)
  374. self.add("profile_options", "supervisord.profile_options",
  375. "", "profile_options=", profile_options, default=None)
  376. self.pidhistory = {}
  377. self.process_group_configs = []
  378. self.parse_warnings = []
  379. self.signal_receiver = SignalReceiver()
  380. def version(self, dummy):
  381. """Print version to stdout and exit(0).
  382. """
  383. self.stdout.write('%s\n' % VERSION)
  384. self.exit(0)
  385. def getLogger(self, filename, level, fmt, rotating=False, maxbytes=0,
  386. backups=0, stdout=False):
  387. return loggers.getLogger(filename, level, fmt, rotating, maxbytes,
  388. backups, stdout)
  389. def realize(self, *arg, **kw):
  390. Options.realize(self, *arg, **kw)
  391. section = self.configroot.supervisord
  392. # Additional checking of user option; set uid and gid
  393. if self.user is not None:
  394. try:
  395. uid = name_to_uid(self.user)
  396. except ValueError, msg:
  397. self.usage(msg) # invalid user
  398. self.uid = uid
  399. self.gid = gid_for_uid(uid)
  400. if not self.loglevel:
  401. self.loglevel = section.loglevel
  402. if self.logfile:
  403. logfile = self.logfile
  404. else:
  405. logfile = section.logfile
  406. self.logfile = normalize_path(logfile)
  407. if self.pidfile:
  408. pidfile = self.pidfile
  409. else:
  410. pidfile = section.pidfile
  411. self.pidfile = normalize_path(pidfile)
  412. self.rpcinterface_factories = section.rpcinterface_factories
  413. self.serverurl = None
  414. self.server_configs = sconfigs = section.server_configs
  415. # we need to set a fallback serverurl that process.spawn can use
  416. # prefer a unix domain socket
  417. for config in [ config for config in sconfigs if
  418. config['family'] is socket.AF_UNIX ]:
  419. path = config['file']
  420. self.serverurl = 'unix://%s' % path
  421. break
  422. # fall back to an inet socket
  423. if self.serverurl is None:
  424. for config in [ config for config in sconfigs if
  425. config['family'] is socket.AF_INET]:
  426. host = config['host']
  427. port = config['port']
  428. if not host:
  429. host = 'localhost'
  430. self.serverurl = 'http://%s:%s' % (host, port)
  431. # self.serverurl may still be None if no servers at all are
  432. # configured in the config file
  433. self.identifier = section.identifier
  434. def process_config(self, do_usage=True):
  435. Options.process_config(self, do_usage=do_usage)
  436. new = self.configroot.supervisord.process_group_configs
  437. self.process_group_configs = new
  438. def read_config(self, fp):
  439. # Clear parse warnings, since we may be re-reading the
  440. # config a second time after a reload.
  441. self.parse_warnings = []
  442. section = self.configroot.supervisord
  443. if not hasattr(fp, 'read'):
  444. if not os.path.exists(fp):
  445. raise ValueError("could not find config file %s" % fp)
  446. try:
  447. fp = open(fp, 'r')
  448. except (IOError, OSError):
  449. raise ValueError("could not read config file %s" % fp)
  450. parser = UnhosedConfigParser()
  451. try:
  452. parser.readfp(fp)
  453. except ConfigParser.ParsingError, why:
  454. raise ValueError(str(why))
  455. if parser.has_section('include'):
  456. if not parser.has_option('include', 'files'):
  457. raise ValueError(".ini file has [include] section, but no "
  458. "files setting")
  459. files = parser.get('include', 'files')
  460. files = files.split()
  461. if hasattr(fp, 'name'):
  462. base = os.path.dirname(os.path.abspath(fp.name))
  463. else:
  464. base = '.'
  465. for pattern in files:
  466. pattern = os.path.join(base, pattern)
  467. for filename in glob.glob(pattern):
  468. self.parse_warnings.append(
  469. 'Included extra file "%s" during parsing' % filename)
  470. try:
  471. parser.read(filename)
  472. except ConfigParser.ParsingError, why:
  473. raise ValueError(str(why))
  474. sections = parser.sections()
  475. if not 'supervisord' in sections:
  476. raise ValueError, '.ini file does not include supervisord section'
  477. get = parser.getdefault
  478. section.minfds = integer(get('minfds', 1024))
  479. section.minprocs = integer(get('minprocs', 200))
  480. directory = get('directory', None)
  481. if directory is None:
  482. section.directory = None
  483. else:
  484. section.directory = existing_directory(directory)
  485. section.user = get('user', None)
  486. section.umask = octal_type(get('umask', '022'))
  487. section.logfile = existing_dirpath(get('logfile', 'supervisord.log'))
  488. section.logfile_maxbytes = byte_size(get('logfile_maxbytes', '50MB'))
  489. section.logfile_backups = integer(get('logfile_backups', 10))
  490. section.loglevel = logging_level(get('loglevel', 'info'))
  491. section.pidfile = existing_dirpath(get('pidfile', 'supervisord.pid'))
  492. section.identifier = get('identifier', 'supervisor')
  493. section.nodaemon = boolean(get('nodaemon', 'false'))
  494. tempdir = tempfile.gettempdir()
  495. section.childlogdir = existing_directory(get('childlogdir', tempdir))
  496. section.nocleanup = boolean(get('nocleanup', 'false'))
  497. section.strip_ansi = boolean(get('strip_ansi', 'false'))
  498. expansions = {'here':self.here}
  499. expansions.update(environ_expansions())
  500. environ_str = get('environment', '')
  501. environ_str = expand(environ_str, expansions, 'environment')
  502. section.environment = dict_of_key_value_pairs(environ_str)
  503. # Process rpcinterface plugins before groups to allow custom events to
  504. # be registered.
  505. section.rpcinterface_factories = self.get_plugins(
  506. parser,
  507. 'supervisor.rpcinterface_factory',
  508. 'rpcinterface:'
  509. )
  510. section.process_group_configs = self.process_groups_from_parser(parser)
  511. for group in section.process_group_configs:
  512. for proc in group.process_configs:
  513. env = section.environment.copy()
  514. env.update(proc.environment)
  515. proc.environment = env
  516. section.server_configs = self.server_configs_from_parser(parser)
  517. section.profile_options = None
  518. return section
  519. def process_groups_from_parser(self, parser):
  520. groups = []
  521. all_sections = parser.sections()
  522. homogeneous_exclude = []
  523. get = parser.saneget
  524. # process heterogeneous groups
  525. for section in all_sections:
  526. if not section.startswith('group:'):
  527. continue
  528. group_name = process_or_group_name(section.split(':', 1)[1])
  529. programs = list_of_strings(get(section, 'programs', None))
  530. priority = integer(get(section, 'priority', 999))
  531. group_processes = []
  532. for program in programs:
  533. program_section = "program:%s" % program
  534. if not program_section in all_sections:
  535. raise ValueError(
  536. '[%s] names unknown program %s' % (section, program))
  537. homogeneous_exclude.append(program_section)
  538. processes = self.processes_from_section(parser, program_section,
  539. group_name,
  540. ProcessConfig)
  541. group_processes.extend(processes)
  542. groups.append(
  543. ProcessGroupConfig(self, group_name, priority, group_processes)
  544. )
  545. # process "normal" homogeneous groups
  546. for section in all_sections:
  547. if ( (not section.startswith('program:') )
  548. or section in homogeneous_exclude ):
  549. continue
  550. program_name = process_or_group_name(section.split(':', 1)[1])
  551. priority = integer(get(section, 'priority', 999))
  552. processes=self.processes_from_section(parser, section, program_name,
  553. ProcessConfig)
  554. groups.append(
  555. ProcessGroupConfig(self, program_name, priority, processes)
  556. )
  557. # process "event listener" homogeneous groups
  558. for section in all_sections:
  559. if not section.startswith('eventlistener:'):
  560. continue
  561. pool_name = section.split(':', 1)[1]
  562. # give listeners a "high" default priority so they are started first
  563. # and stopped last at mainloop exit
  564. priority = integer(get(section, 'priority', -1))
  565. buffer_size = integer(get(section, 'buffer_size', 10))
  566. result_handler = get(section, 'result_handler',
  567. 'supervisor.dispatchers:default_handler')
  568. try:
  569. result_handler = self.import_spec(result_handler)
  570. except ImportError:
  571. raise ValueError('%s cannot be resolved within [%s]' % (
  572. result_handler, section))
  573. pool_event_names = [x.upper() for x in
  574. list_of_strings(get(section, 'events', ''))]
  575. pool_event_names = set(pool_event_names)
  576. if not pool_event_names:
  577. raise ValueError('[%s] section requires an "events" line' %
  578. section)
  579. from supervisor.events import EventTypes
  580. pool_events = []
  581. for pool_event_name in pool_event_names:
  582. pool_event = getattr(EventTypes, pool_event_name, None)
  583. if pool_event is None:
  584. raise ValueError('Unknown event type %s in [%s] events' %
  585. (pool_event_name, section))
  586. pool_events.append(pool_event)
  587. processes=self.processes_from_section(parser, section, pool_name,
  588. EventListenerConfig)
  589. groups.append(
  590. EventListenerPoolConfig(self, pool_name, priority, processes,
  591. buffer_size, pool_events,
  592. result_handler)
  593. )
  594. # process fastcgi homogeneous groups
  595. for section in all_sections:
  596. if ( (not section.startswith('fcgi-program:') )
  597. or section in homogeneous_exclude ):
  598. continue
  599. program_name = process_or_group_name(section.split(':', 1)[1])
  600. priority = integer(get(section, 'priority', 999))
  601. # find proc_uid from "user" option
  602. proc_user = get(section, 'user', None)
  603. if proc_user is None:
  604. proc_uid = None
  605. else:
  606. proc_uid = name_to_uid(proc_user)
  607. socket_owner = get(section, 'socket_owner', None)
  608. if socket_owner is not None:
  609. try:
  610. socket_owner = colon_separated_user_group(socket_owner)
  611. except ValueError:
  612. raise ValueError('Invalid socket_owner value %s'
  613. % socket_owner)
  614. socket_mode = get(section, 'socket_mode', None)
  615. if socket_mode is not None:
  616. try:
  617. socket_mode = octal_type(socket_mode)
  618. except (TypeError, ValueError):
  619. raise ValueError('Invalid socket_mode value %s'
  620. % socket_mode)
  621. socket = get(section, 'socket', None)
  622. if not socket:
  623. raise ValueError('[%s] section requires a "socket" line' %
  624. section)
  625. expansions = {'here':self.here,
  626. 'program_name':program_name}
  627. expansions.update(environ_expansions())
  628. socket = expand(socket, expansions, 'socket')
  629. try:
  630. socket_config = self.parse_fcgi_socket(socket, proc_uid,
  631. socket_owner, socket_mode)
  632. except ValueError, e:
  633. raise ValueError('%s in [%s] socket' % (str(e), section))
  634. processes=self.processes_from_section(parser, section, program_name,
  635. FastCGIProcessConfig)
  636. groups.append(
  637. FastCGIGroupConfig(self, program_name, priority, processes,
  638. socket_config)
  639. )
  640. groups.sort()
  641. return groups
  642. def parse_fcgi_socket(self, sock, proc_uid, socket_owner, socket_mode):
  643. if sock.startswith('unix://'):
  644. path = sock[7:]
  645. #Check it's an absolute path
  646. if not os.path.isabs(path):
  647. raise ValueError("Unix socket path %s is not an absolute path",
  648. path)
  649. path = normalize_path(path)
  650. if socket_owner is None:
  651. uid = os.getuid()
  652. if proc_uid is not None and proc_uid != uid:
  653. socket_owner = (proc_uid, self.get_gid_for_uid(proc_uid))
  654. if socket_mode is None:
  655. socket_mode = 0700
  656. return UnixStreamSocketConfig(path, owner=socket_owner,
  657. mode=socket_mode)
  658. if socket_owner is not None or socket_mode is not None:
  659. raise ValueError("socket_owner and socket_mode params should"
  660. + " only be used with a Unix domain socket")
  661. m = re.match(r'tcp://([^\s:]+):(\d+)$', sock)
  662. if m:
  663. host = m.group(1)
  664. port = int(m.group(2))
  665. return InetStreamSocketConfig(host, port)
  666. raise ValueError("Bad socket format %s", sock)
  667. def get_gid_for_uid(self, uid):
  668. pwrec = pwd.getpwuid(uid)
  669. return pwrec[3]
  670. def processes_from_section(self, parser, section, group_name,
  671. klass=None):
  672. if klass is None:
  673. klass = ProcessConfig
  674. programs = []
  675. get = parser.saneget
  676. program_name = process_or_group_name(section.split(':', 1)[1])
  677. priority = integer(get(section, 'priority', 999))
  678. autostart = boolean(get(section, 'autostart', 'true'))
  679. autorestart = auto_restart(get(section, 'autorestart', 'unexpected'))
  680. startsecs = integer(get(section, 'startsecs', 1))
  681. startretries = integer(get(section, 'startretries', 3))
  682. stopsignal = signal_number(get(section, 'stopsignal', 'TERM'))
  683. stopwaitsecs = integer(get(section, 'stopwaitsecs', 10))
  684. stopasgroup = boolean(get(section, 'stopasgroup', 'false'))
  685. killasgroup = boolean(get(section, 'killasgroup', stopasgroup))
  686. exitcodes = list_of_exitcodes(get(section, 'exitcodes', '0,2'))
  687. redirect_stderr = boolean(get(section, 'redirect_stderr','false'))
  688. numprocs = integer(get(section, 'numprocs', 1))
  689. numprocs_start = integer(get(section, 'numprocs_start', 0))
  690. environment_str = get(section, 'environment', '')
  691. stdout_cmaxbytes = byte_size(get(section,'stdout_capture_maxbytes','0'))
  692. stdout_events = boolean(get(section, 'stdout_events_enabled','false'))
  693. stderr_cmaxbytes = byte_size(get(section,'stderr_capture_maxbytes','0'))
  694. stderr_events = boolean(get(section, 'stderr_events_enabled','false'))
  695. serverurl = get(section, 'serverurl', None)
  696. if serverurl and serverurl.strip().upper() == 'AUTO':
  697. serverurl = None
  698. # find uid from "user" option
  699. user = get(section, 'user', None)
  700. if user is None:
  701. uid = None
  702. else:
  703. uid = name_to_uid(user)
  704. umask = get(section, 'umask', None)
  705. if umask is not None:
  706. umask = octal_type(umask)
  707. command = get(section, 'command', None)
  708. if command is None:
  709. raise ValueError, (
  710. 'program section %s does not specify a command' % section)
  711. process_name = process_or_group_name(
  712. get(section, 'process_name', '%(program_name)s'))
  713. if numprocs > 1:
  714. if process_name.find('%(process_num)') == -1:
  715. # process_name needs to include process_num when we
  716. # represent a group of processes
  717. raise ValueError(
  718. '%(process_num) must be present within process_name when '
  719. 'numprocs > 1')
  720. if stopasgroup and not killasgroup:
  721. raise ValueError("Cannot set stopasgroup=true and killasgroup=false")
  722. host_node_name = platform.node()
  723. for process_num in range(numprocs_start, numprocs + numprocs_start):
  724. expansions = {'here':self.here,
  725. 'process_num':process_num,
  726. 'program_name':program_name,
  727. 'host_node_name':host_node_name,
  728. 'group_name':group_name}
  729. expansions.update(environ_expansions())
  730. environment = dict_of_key_value_pairs(
  731. expand(environment_str, expansions, 'environment'))
  732. directory = get(section, 'directory', None)
  733. if directory:
  734. directory = expand(directory, expansions, 'directory')
  735. logfiles = {}
  736. for k in ('stdout', 'stderr'):
  737. n = '%s_logfile' % k
  738. lf_val = get(section, n, Automatic)
  739. if isinstance(lf_val, basestring):
  740. lf_val = expand(lf_val, expansions, n)
  741. lf_val = logfile_name(lf_val)
  742. logfiles[n] = lf_val
  743. bu_key = '%s_logfile_backups' % k
  744. backups = integer(get(section, bu_key, 10))
  745. logfiles[bu_key] = backups
  746. mb_key = '%s_logfile_maxbytes' % k
  747. maxbytes = byte_size(get(section, mb_key, '50MB'))
  748. logfiles[mb_key] = maxbytes
  749. if lf_val is Automatic and not maxbytes:
  750. self.parse_warnings.append(
  751. 'For [%s], AUTO logging used for %s without '
  752. 'rollover, set maxbytes > 0 to avoid filling up '
  753. 'filesystem unintentionally' % (section, n))
  754. pconfig = klass(
  755. self,
  756. name=expand(process_name, expansions, 'process_name'),
  757. command=expand(command, expansions, 'command'),
  758. directory=directory,
  759. umask=umask,
  760. priority=priority,
  761. autostart=autostart,
  762. autorestart=autorestart,
  763. startsecs=startsecs,
  764. startretries=startretries,
  765. uid=uid,
  766. stdout_logfile=logfiles['stdout_logfile'],
  767. stdout_capture_maxbytes = stdout_cmaxbytes,
  768. stdout_events_enabled = stdout_events,
  769. stdout_logfile_backups=logfiles['stdout_logfile_backups'],
  770. stdout_logfile_maxbytes=logfiles['stdout_logfile_maxbytes'],
  771. stderr_logfile=logfiles['stderr_logfile'],
  772. stderr_capture_maxbytes = stderr_cmaxbytes,
  773. stderr_events_enabled = stderr_events,
  774. stderr_logfile_backups=logfiles['stderr_logfile_backups'],
  775. stderr_logfile_maxbytes=logfiles['stderr_logfile_maxbytes'],
  776. stopsignal=stopsignal,
  777. stopwaitsecs=stopwaitsecs,
  778. stopasgroup=stopasgroup,
  779. killasgroup=killasgroup,
  780. exitcodes=exitcodes,
  781. redirect_stderr=redirect_stderr,
  782. environment=environment,
  783. serverurl=serverurl)
  784. programs.append(pconfig)
  785. programs.sort() # asc by priority
  786. return programs
  787. def _parse_servernames(self, parser, stype):
  788. options = []
  789. for section in parser.sections():
  790. if section.startswith(stype):
  791. parts = section.split(':', 1)
  792. if len(parts) > 1:
  793. name = parts[1]
  794. else:
  795. name = None # default sentinel
  796. options.append((name, section))
  797. return options
  798. def _parse_username_and_password(self, parser, section):
  799. get = parser.saneget
  800. username = get(section, 'username', None)
  801. password = get(section, 'password', None)
  802. if username is None and password is not None:
  803. raise ValueError(
  804. 'Must specify username if password is specified in [%s]'
  805. % section)
  806. return {'username':username, 'password':password}
  807. def server_configs_from_parser(self, parser):
  808. configs = []
  809. inet_serverdefs = self._parse_servernames(parser, 'inet_http_server')
  810. for name, section in inet_serverdefs:
  811. config = {}
  812. get = parser.saneget
  813. config.update(self._parse_username_and_password(parser, section))
  814. config['name'] = name
  815. config['family'] = socket.AF_INET
  816. port = get(section, 'port', None)
  817. if port is None:
  818. raise ValueError('section [%s] has no port value' % section)
  819. host, port = inet_address(port)
  820. config['host'] = host
  821. config['port'] = port
  822. config['section'] = section
  823. configs.append(config)
  824. unix_serverdefs = self._parse_servernames(parser, 'unix_http_server')
  825. for name, section in unix_serverdefs:
  826. config = {}
  827. get = parser.saneget
  828. sfile = get(section, 'file', None)
  829. if sfile is None:
  830. raise ValueError('section [%s] has no file value' % section)
  831. sfile = sfile.strip()
  832. config['name'] = name
  833. config['family'] = socket.AF_UNIX
  834. sfile = expand(sfile, {'here':self.here}, 'socket file')
  835. config['file'] = normalize_path(sfile)
  836. config.update(self._parse_username_and_password(parser, section))
  837. chown = get(section, 'chown', None)
  838. if chown is not None:
  839. try:
  840. chown = colon_separated_user_group(chown)
  841. except ValueError:
  842. raise ValueError('Invalid sockchown value %s' % chown)
  843. else:
  844. chown = (-1, -1)
  845. config['chown'] = chown
  846. chmod = get(section, 'chmod', None)
  847. if chmod is not None:
  848. try:
  849. chmod = octal_type(chmod)
  850. except (TypeError, ValueError):
  851. raise ValueError('Invalid chmod value %s' % chmod)
  852. else:
  853. chmod = 0700
  854. config['chmod'] = chmod
  855. config['section'] = section
  856. configs.append(config)
  857. return configs
  858. def daemonize(self):
  859. # To daemonize, we need to become the leader of our own session
  860. # (process) group. If we do not, signals sent to our
  861. # parent process will also be sent to us. This might be bad because
  862. # signals such as SIGINT can be sent to our parent process during
  863. # normal (uninteresting) operations such as when we press Ctrl-C in the
  864. # parent terminal window to escape from a logtail command.
  865. # To disassociate ourselves from our parent's session group we use
  866. # os.setsid. It means "set session id", which has the effect of
  867. # disassociating a process from is current session and process group
  868. # and setting itself up as a new session leader.
  869. #
  870. # Unfortunately we cannot call setsid if we're already a session group
  871. # leader, so we use "fork" to make a copy of ourselves that is
  872. # guaranteed to not be a session group leader.
  873. #
  874. # We also change directories, set stderr and stdout to null, and
  875. # change our umask.
  876. #
  877. # This explanation was (gratefully) garnered from
  878. # http://www.hawklord.uklinux.net/system/daemons/d3.htm
  879. pid = os.fork()
  880. if pid != 0:
  881. # Parent
  882. self.logger.blather("supervisord forked; parent exiting")
  883. os._exit(0)
  884. # Child
  885. self.logger.info("daemonizing the supervisord process")
  886. if self.directory:
  887. try:
  888. os.chdir(self.directory)
  889. except OSError, err:
  890. self.logger.critical("can't chdir into %r: %s"
  891. % (self.directory, err))
  892. else:
  893. self.logger.info("set current directory: %r"
  894. % self.directory)
  895. os.close(0)
  896. self.stdin = sys.stdin = sys.__stdin__ = open("/dev/null")
  897. os.close(1)
  898. self.stdout = sys.stdout = sys.__stdout__ = open("/dev/null", "w")
  899. os.close(2)
  900. self.stderr = sys.stderr = sys.__stderr__ = open("/dev/null", "w")
  901. os.setsid()
  902. os.umask(self.umask)
  903. # XXX Stevens, in his Advanced Unix book, section 13.3 (page
  904. # 417) recommends calling umask(0) and closing unused
  905. # file descriptors. In his Network Programming book, he
  906. # additionally recommends ignoring SIGHUP and forking again
  907. # after the setsid() call, for obscure SVR4 reasons.
  908. def write_pidfile(self):
  909. pid = os.getpid()
  910. try:
  911. f = open(self.pidfile, 'w')
  912. f.write('%s\n' % pid)
  913. f.close()
  914. except (IOError, OSError):
  915. self.logger.critical('could not write pidfile %s' % self.pidfile)
  916. else:
  917. self.logger.info('supervisord started with pid %s' % pid)
  918. def cleanup(self):
  919. try:
  920. for config, server in self.httpservers:
  921. if config['family'] == socket.AF_UNIX:
  922. if self.unlink_socketfiles:
  923. socketname = config['file']
  924. try:
  925. os.unlink(socketname)
  926. except OSError:
  927. pass
  928. except OSError:
  929. pass
  930. try:
  931. os.unlink(self.pidfile)
  932. except OSError:
  933. pass
  934. def close_httpservers(self):
  935. for config, server in self.httpservers:
  936. server.close()
  937. map = self.get_socket_map()
  938. # server._map is a reference to the asyncore socket_map
  939. for dispatcher in map.values():
  940. # For unknown reasons, sometimes an http_channel
  941. # dispatcher in the socket map related to servers
  942. # remains open *during a reload*. If one of these
  943. # exists at this point, we need to close it by hand
  944. # (thus removing it from the asyncore.socket_map). If
  945. # we don't do this, 'cleanup_fds' will cause its file
  946. # descriptor to be closed, but it will still remain in
  947. # the socket_map, and eventually its file descriptor
  948. # will be passed to # select(), which will bomb. See
  949. # also http://www.plope.com/software/collector/253
  950. dispatcher_server = getattr(dispatcher, 'server', None)
  951. if dispatcher_server is server:
  952. dispatcher.close()
  953. def close_logger(self):
  954. self.logger.close()
  955. def setsignals(self):
  956. receive = self.signal_receiver.receive
  957. signal.signal(signal.SIGTERM, receive)
  958. signal.signal(signal.SIGINT, receive)
  959. signal.signal(signal.SIGQUIT, receive)
  960. signal.signal(signal.SIGHUP, receive)
  961. signal.signal(signal.SIGCHLD, receive)
  962. signal.signal(signal.SIGUSR2, receive)
  963. def get_signal(self):
  964. return self.signal_receiver.get_signal()
  965. def openhttpservers(self, supervisord):
  966. try:
  967. self.httpservers = self.make_http_servers(supervisord)
  968. except socket.error, why:
  969. if why[0] == errno.EADDRINUSE:
  970. self.usage('Another program is already listening on '
  971. 'a port that one of our HTTP servers is '
  972. 'configured to use. Shut this program '
  973. 'down first before starting supervisord.')
  974. else:
  975. help = 'Cannot open an HTTP server: socket.error reported'
  976. errorname = errno.errorcode.get(why[0])
  977. if errorname is None:
  978. self.usage('%s %s' % (help, why[0]))
  979. else:
  980. self.usage('%s errno.%s (%d)' %
  981. (help, errorname, why[0]))
  982. self.unlink_socketfiles = False
  983. except ValueError, why:
  984. self.usage(why[0])
  985. def get_autochildlog_name(self, name, identifier, channel):
  986. prefix='%s-%s---%s-' % (name, channel, identifier)
  987. logfile = self.mktempfile(
  988. suffix='.log',
  989. prefix=prefix,
  990. dir=self.childlogdir)
  991. return logfile
  992. def clear_autochildlogdir(self):
  993. # must be called after realize()
  994. childlogdir = self.childlogdir
  995. fnre = re.compile(r'.+?---%s-\S+\.log\.{0,1}\d{0,4}' % self.identifier)
  996. try:
  997. filenames = os.listdir(childlogdir)
  998. except (IOError, OSError):
  999. self.logger.warn('Could not clear childlog dir')
  1000. return
  1001. for filename in filenames:
  1002. if fnre.match(filename):
  1003. pathname = os.path.join(childlogdir, filename)
  1004. try:
  1005. os.remove(pathname)
  1006. except (OSError, IOError):
  1007. self.logger.warn('Failed to clean up %r' % pathname)
  1008. def get_socket_map(self):
  1009. return asyncore.socket_map
  1010. def cleanup_fds(self):
  1011. # try to close any leaked file descriptors (for reload)
  1012. start = 5
  1013. for x in range(start, self.minfds):
  1014. try:
  1015. os.close(x)
  1016. except OSError:
  1017. pass
  1018. def select(self, r, w, x, timeout):
  1019. return select.select(r, w, x, timeout)
  1020. def kill(self, pid, signal):
  1021. os.kill(pid, signal)
  1022. def set_uid(self):
  1023. if self.uid is None:
  1024. if os.getuid() == 0:
  1025. return 'Supervisor running as root (no user in config file)'
  1026. return None
  1027. msg = self.dropPrivileges(self.uid)
  1028. if msg is None:
  1029. return 'Set uid to user %s' % self.uid
  1030. return msg
  1031. def dropPrivileges(self, user):
  1032. # Drop root privileges if we have them
  1033. if user is None:
  1034. return "No user specified to setuid to!"
  1035. # get uid for user, which can be a number or username
  1036. try:
  1037. uid = int(user)
  1038. except ValueError:
  1039. try:
  1040. pwrec = pwd.getpwnam(user)
  1041. except KeyError:
  1042. return "Can't find username %r" % user
  1043. uid = pwrec[2]
  1044. else:
  1045. try:
  1046. pwrec = pwd.getpwuid(uid)
  1047. except KeyError:
  1048. return "Can't find uid %r" % uid
  1049. current_uid = os.getuid()
  1050. if current_uid == uid:
  1051. # do nothing and return successfully if the uid is already the
  1052. # current one. this allows a supervisord running as an
  1053. # unprivileged user "foo" to start a process where the config
  1054. # has "user=foo" (same user) in it.
  1055. return
  1056. if current_uid != 0:
  1057. return "Can't drop privilege as nonroot user"
  1058. gid = pwrec[3]
  1059. if hasattr(os, 'setgroups'):
  1060. user = pwrec[0]
  1061. groups = [grprec[2] for grprec in grp.getgrall() if user in
  1062. grprec[3]]
  1063. # always put our primary gid first in this list, otherwise we can
  1064. # lose group info since sometimes the first group in the setgroups
  1065. # list gets overwritten on the subsequent setgid call (at least on
  1066. # freebsd 9 with python 2.7 - this will be safe though for all unix
  1067. # /python version combos)
  1068. groups.insert(0, gid)
  1069. try:
  1070. os.setgroups(groups)
  1071. except OSError:
  1072. return 'Could not set groups of effective user'
  1073. try:
  1074. os.setgid(gid)
  1075. except OSError:
  1076. return 'Could not set group id of effective user'
  1077. os.setuid(uid)
  1078. def waitpid(self):
  1079. # need pthread_sigmask here to avoid concurrent sigchild, but
  1080. # Python doesn't offer it as it's not standard across UNIX versions.
  1081. # there is still a race condition here; we can get a sigchild while
  1082. # we're sitting in the waitpid call.
  1083. try:
  1084. pid, sts = os.waitpid(-1, os.WNOHANG)
  1085. except OSError, why:
  1086. err = why[0]
  1087. if err not in (errno.ECHILD, errno.EINTR):
  1088. self.logger.critical(
  1089. 'waitpid error; a process may not be cleaned up properly')
  1090. if err == errno.EINTR:
  1091. self.logger.blather('EINTR during reap')
  1092. pid, sts = None, None
  1093. return pid, sts
  1094. def set_rlimits(self):
  1095. limits = []
  1096. if hasattr(resource, 'RLIMIT_NOFILE'):
  1097. limits.append(
  1098. {
  1099. 'msg':('The minimum number of file descriptors required '
  1100. 'to run this process is %(min)s as per the "minfds" '
  1101. 'command-line argument or config file setting. '
  1102. 'The current environment will only allow you '
  1103. 'to open %(hard)s file descriptors. Either raise '
  1104. 'the number of usable file descriptors in your '
  1105. 'environment (see README.rst) or lower the '
  1106. 'minfds setting in the config file to allow '
  1107. 'the process to start.'),
  1108. 'min':self.minfds,
  1109. 'resource':resource.RLIMIT_NOFILE,
  1110. 'name':'RLIMIT_NOFILE',
  1111. })
  1112. if hasattr(resource, 'RLIMIT_NPROC'):
  1113. limits.append(
  1114. {
  1115. 'msg':('The minimum number of available processes required '
  1116. 'to run this program is %(min)s as per the "minprocs" '
  1117. 'command-line argument or config file setting. '
  1118. 'The current environment will only allow you '
  1119. 'to open %(hard)s processes. Either raise '
  1120. 'the number of usable processes in your '
  1121. 'environment (see README.rst) or lower the '
  1122. 'minprocs setting in the config file to allow '
  1123. 'the program to start.'),
  1124. 'min':self.minprocs,
  1125. 'resource':resource.RLIMIT_NPROC,
  1126. 'name':'RLIMIT_NPROC',
  1127. })
  1128. msgs = []
  1129. for limit in limits:
  1130. min = limit['min']
  1131. res = limit['resource']
  1132. msg = limit['msg']
  1133. name = limit['name']
  1134. soft, hard = resource.getrlimit(res)
  1135. if (soft < min) and (soft != -1): # -1 means unlimited
  1136. if (hard < min) and (hard != -1):
  1137. # setrlimit should increase the hard limit if we are
  1138. # root, if not then setrlimit raises and we print usage
  1139. hard = min
  1140. try:
  1141. resource.setrlimit(res, (min, hard))
  1142. msgs.append('Increased %(name)s limit to %(min)s' %
  1143. locals())
  1144. except (resource.error, ValueError):
  1145. self.usage(msg % locals())
  1146. return msgs
  1147. def make_logger(self, critical_messages, warn_messages, info_messages):
  1148. # must be called after realize() and after supervisor does setuid()
  1149. format = '%(asctime)s %(levelname)s %(message)s\n'
  1150. self.logger = loggers.getLogger(
  1151. self.logfile,
  1152. self.loglevel,
  1153. format,
  1154. rotating=True,
  1155. maxbytes=self.logfile_maxbytes,
  1156. backups=self.logfile_backups,
  1157. stdout = self.nodaemon,
  1158. )
  1159. for msg in critical_messages:
  1160. self.logger.critical(msg)
  1161. for msg in warn_messages:
  1162. self.logger.warn(msg)
  1163. for msg in info_messages:
  1164. self.logger.info(msg)
  1165. def make_http_servers(self, supervisord):
  1166. from supervisor.http import make_http_servers
  1167. return make_http_servers(self, supervisord)
  1168. def close_fd(self, fd):
  1169. try:
  1170. os.close(fd)
  1171. except OSError:
  1172. pass
  1173. def fork(self):
  1174. return os.fork()
  1175. def dup2(self, frm, to):
  1176. return os.dup2(frm, to)
  1177. def setpgrp(self):
  1178. return os.setpgrp()
  1179. def stat(self, filename):
  1180. return os.stat(filename)
  1181. def write(self, fd, data):
  1182. return os.write(fd, data)
  1183. def execve(self, filename, argv, env):
  1184. return os.execve(filename, argv, env)
  1185. def mktempfile(self, suffix, prefix, dir):
  1186. # set os._urandomfd as a hack around bad file descriptor bug
  1187. # seen in the wild, see
  1188. # http://www.plope.com/software/collector/252
  1189. os._urandomfd = None
  1190. fd, filename = tempfile.mkstemp(suffix, prefix, dir)
  1191. os.close(fd)
  1192. return filename
  1193. def remove(self, path):
  1194. os.remove(path)
  1195. def exists(self, path):
  1196. return os.path.exists(path)
  1197. def _exit(self, code):
  1198. os._exit(code)
  1199. def setumask(self, mask):
  1200. os.umask(mask)
  1201. def get_path(self):
  1202. """Return a list corresponding to $PATH, or a default."""
  1203. path = ["/bin", "/usr/bin", "/usr/local/bin"]
  1204. if os.environ.has_key("PATH"):
  1205. p = os.environ["PATH"]
  1206. if p:
  1207. path = p.split(os.pathsep)
  1208. return path
  1209. def get_pid(self):
  1210. return os.getpid()
  1211. def check_execv_args(self, filename, argv, st):
  1212. if st is None:
  1213. raise NotFound("can't find command %r" % filename)
  1214. elif stat.S_ISDIR(st[stat.ST_MODE]):
  1215. raise NotExecutable("command at %r is a directory" % filename)
  1216. elif not (stat.S_IMODE(st[stat.ST_MODE]) & 0111):
  1217. raise NotExecutable("command at %r is not executable" % filename)
  1218. elif not os.access(filename, os.X_OK):
  1219. raise NoPermission("no permission to run command %r" % filename)
  1220. def reopenlogs(self):
  1221. self.logger.info('supervisord logreopen')
  1222. for handler in self.logger.handlers:
  1223. if hasattr(handler, 'reopen'):
  1224. handler.reopen()
  1225. def readfd(self, fd):
  1226. try:
  1227. data = os.read(fd, 2 << 16) # 128K
  1228. except OSError, why:
  1229. if why[0] not in (errno.EWOULDBLOCK, errno.EBADF, errno.EINTR):
  1230. raise
  1231. data = ''
  1232. return data
  1233. def process_environment(self):
  1234. os.environ.update(self.environment or {})
  1235. def open(self, fn, mode='r'):
  1236. return open(fn, mode)
  1237. def chdir(self, dir):
  1238. os.chdir(dir)
  1239. def make_pipes(self, stderr=True):
  1240. """ Create pipes for parent to child stdin/stdout/stderr
  1241. communications. Open fd in nonblocking mode so we can read them
  1242. in the mainloop without blocking. If stderr is False, don't
  1243. create a pipe for stderr. """
  1244. pipes = {'child_stdin':None,
  1245. 'stdin':None,
  1246. 'stdout':None,
  1247. 'child_stdout':None,
  1248. 'stderr':None,
  1249. 'child_stderr':None}
  1250. try:
  1251. stdin, child_stdin = os.pipe()
  1252. pipes['child_stdin'], pipes['stdin'] = stdin, child_stdin
  1253. stdout, child_stdout = os.pipe()
  1254. pipes['stdout'], pipes['child_stdout'] = stdout, child_stdout
  1255. if stderr:
  1256. stderr, child_stderr = os.pipe()
  1257. pipes['stderr'], pipes['child_stderr'] = stderr, child_stderr
  1258. for fd in (pipes['stdout'], pipes['stderr'], pipes['stdin']):
  1259. if fd is not None:
  1260. fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | os.O_NDELAY)
  1261. return pipes
  1262. except OSError:
  1263. for fd in pipes.values():
  1264. if fd is not None:
  1265. self.close_fd(fd)
  1266. def close_parent_pipes(self, pipes):
  1267. for fdname in ('stdin', 'stdout', 'stderr'):
  1268. fd = pipes[fdname]
  1269. if fd is not None:
  1270. self.close_fd(fd)
  1271. def close_child_pipes(self, pipes):
  1272. for fdname in ('child_stdin', 'child_stdout', 'child_stderr'):
  1273. fd = pipes[fdname]
  1274. if fd is not None:
  1275. self.close_fd(fd)
  1276. class ClientOptions(Options):
  1277. positional_args_allowed = 1
  1278. interactive = None
  1279. prompt = None
  1280. serverurl = None
  1281. username = None
  1282. password = None
  1283. history_file = None
  1284. def __init__(self):
  1285. Options.__init__(self, require_configfile=False)
  1286. self.configroot = Dummy()
  1287. self.configroot.supervisorctl = Dummy()
  1288. self.configroot.supervisorctl.interactive = None
  1289. self.configroot.supervisorctl.prompt = 'supervisor'
  1290. self.configroot.supervisorctl.serverurl = None
  1291. self.configroot.supervisorctl.username = None
  1292. self.configroot.supervisorctl.password = None
  1293. self.configroot.supervisorctl.history_file = None
  1294. from supervisor.supervisorctl import DefaultControllerPlugin
  1295. default_factory = ('default', DefaultControllerPlugin, {})
  1296. # we always add the default factory. If you want to a supervisorctl
  1297. # without the default plugin, please write your own supervisorctl.
  1298. self.plugin_factories = [default_factory]
  1299. self.add("interactive", "supervisorctl.interactive", "i",
  1300. "interactive", flag=1, default=0)
  1301. self.add("prompt", "supervisorctl.prompt", default="supervisor")
  1302. self.add("serverurl", "supervisorctl.serverurl", "s:", "serverurl=",
  1303. url, default="http://localhost:9001")
  1304. self.add("username", "supervisorctl.username", "u:", "username=")
  1305. self.add("password", "supervisorctl.password", "p:", "password=")
  1306. self.add("history", "supervisorctl.history_file", "r:", "history_file=")
  1307. def realize(self, *arg, **kw):
  1308. Options.realize(self, *arg, **kw)
  1309. if not self.args:
  1310. self.interactive = 1
  1311. def read_config(self, fp):
  1312. section = self.configroot.supervisorctl
  1313. if not hasattr(fp, 'read'):
  1314. self.here = os.path.dirname(normalize_path(fp))
  1315. if not os.path.exists(fp):
  1316. raise ValueError("could not find config file %s" % fp)
  1317. try:
  1318. fp = open(fp, 'r')
  1319. except (IOError, OSError):
  1320. raise ValueError("could not read config file %s" % fp)
  1321. config = UnhosedConfigParser()
  1322. config.mysection = 'supervisorctl'
  1323. config.readfp(fp)
  1324. sections = config.sections()
  1325. if not 'supervisorctl' in sections:
  1326. raise ValueError,'.ini file does not include supervisorctl section'
  1327. serverurl = config.getdefault('serverurl', 'http://localhost:9001')
  1328. if serverurl.startswith('unix://'):
  1329. sf = serverurl[7:]
  1330. path = expand(sf, {'here':self.here}, 'serverurl')
  1331. path = normalize_path(path)
  1332. serverurl = 'unix://%s' % path
  1333. section.serverurl = serverurl
  1334. # The defaults used below are really set in __init__ (since
  1335. # section==self.configroot.supervisorctl)
  1336. section.prompt = config.getdefault('prompt', section.prompt)
  1337. section.username = config.getdefault('username', section.username)
  1338. section.password = config.getdefault('password', section.password)
  1339. history_file = config.getdefault('history_file', section.history_file)
  1340. if history_file:
  1341. history_file = normalize_path(history_file)
  1342. section.history_file = history_file
  1343. self.history_file = history_file
  1344. else:
  1345. section.history_file = None
  1346. self.history_file = None
  1347. self.plugin_factories += self.get_plugins(
  1348. config,
  1349. 'supervisor.ctl_factory',
  1350. 'ctlplugin:'
  1351. )
  1352. return section
  1353. def getServerProxy(self):
  1354. # mostly put here for unit testing
  1355. return xmlrpclib.ServerProxy(
  1356. # dumbass ServerProxy won't allow us to pass in a non-HTTP url,
  1357. # so we fake the url we pass into it and always use the transport's
  1358. # 'serverurl' to figure out what to attach to
  1359. 'http://127.0.0.1',
  1360. transport = xmlrpc.SupervisorTransport(self.username,
  1361. self.password,
  1362. self.serverurl)
  1363. )
  1364. _marker = []
  1365. class UnhosedConfigParser(ConfigParser.RawConfigParser):
  1366. mysection = 'supervisord'
  1367. def read_string(self, s):
  1368. from StringIO import StringIO
  1369. s = StringIO(s)
  1370. return self.readfp(s)
  1371. def getdefault(self, option, default=_marker):
  1372. try:
  1373. return self.get(self.mysection, option)
  1374. except ConfigParser.NoOptionError:
  1375. if default is _marker:
  1376. raise
  1377. else:
  1378. return default
  1379. def saneget(self, section, option, default=_marker):
  1380. try:
  1381. return self.get(section, option)
  1382. except ConfigParser.NoOptionError:
  1383. if default is _marker:
  1384. raise
  1385. else:
  1386. return default
  1387. class Config(object):
  1388. def __ne__(self, other):
  1389. return not self.__eq__(other)
  1390. def __lt__(self, other):
  1391. if self.priority == other.priority:
  1392. return self.name < other.name
  1393. return self.priority < other.priority
  1394. def __le__(self, other):
  1395. if self.priority == other.priority:
  1396. return self.name <= other.name
  1397. return self.priority <= other.priority
  1398. def __gt__(self, other):
  1399. if self.priority == other.priority:
  1400. return self.name > other.name
  1401. return self.priority > other.priority
  1402. def __ge__(self, other):
  1403. if self.priority == other.priority:
  1404. return self.name >= other.name
  1405. return self.priority >= other.priority
  1406. def __repr__(self):
  1407. return '<%s instance at %s named %s>' % (self.__class__, id(self),
  1408. self.name)
  1409. class ProcessConfig(Config):
  1410. req_param_names = [
  1411. 'name', 'uid', 'command', 'directory', 'umask', 'priority',
  1412. 'autostart', 'autorestart', 'startsecs', 'startretries',
  1413. 'stdout_logfile', 'stdout_capture_maxbytes',
  1414. 'stdout_events_enabled',
  1415. 'stdout_logfile_backups', 'stdout_logfile_maxbytes',
  1416. 'stderr_logfile', 'stderr_capture_maxbytes',
  1417. 'stderr_logfile_backups', 'stderr_logfile_maxbytes',
  1418. 'stderr_events_enabled',
  1419. 'stopsignal', 'stopwaitsecs', 'stopasgroup', 'killasgroup',
  1420. 'exitcodes', 'redirect_stderr' ]
  1421. optional_param_names = [ 'environment', 'serverurl' ]
  1422. def __init__(self, options, **params):
  1423. self.options = options
  1424. for name in self.req_param_names:
  1425. setattr(self, name, params[name])
  1426. for name in self.optional_param_names:
  1427. setattr(self, name, params.get(name, None))
  1428. def __eq__(self, other):
  1429. if not isinstance(other, ProcessConfig):
  1430. return False
  1431. for name in self.req_param_names + self.optional_param_names:
  1432. if Automatic in [getattr(self, name), getattr(other, name)] :
  1433. continue
  1434. if getattr(self, name) != getattr(other, name):
  1435. return False
  1436. return True
  1437. def create_autochildlogs(self):
  1438. # temporary logfiles which are erased at start time
  1439. get_autoname = self.options.get_autochildlog_name
  1440. sid = self.options.identifier
  1441. name = self.name
  1442. if self.stdout_logfile is Automatic:
  1443. self.stdout_logfile = get_autoname(name, sid, 'stdout')
  1444. if self.stderr_logfile is Automatic:
  1445. self.stderr_logfile = get_autoname(name, sid, 'stderr')
  1446. def make_process(self, group=None):
  1447. from supervisor.process import Subprocess
  1448. process = Subprocess(self)
  1449. process.group = group
  1450. return process
  1451. def make_dispatchers(self, proc):
  1452. use_stderr = not self.redirect_stderr
  1453. p = self.options.make_pipes(use_stderr)
  1454. stdout_fd,stderr_fd,stdin_fd = p['stdout'],p['stderr'],p['stdin']
  1455. dispatchers = {}
  1456. from supervisor.dispatchers import POutputDispatcher
  1457. from supervisor.dispatchers import PInputDispatcher
  1458. from supervisor import events
  1459. if stdout_fd is not None:
  1460. etype = events.ProcessCommunicationStdoutEvent
  1461. dispatchers[stdout_fd] = POutputDispatcher(proc, etype, stdout_fd)
  1462. if stderr_fd is not None:
  1463. etype = events.ProcessCommunicationStderrEvent
  1464. dispatchers[stderr_fd] = POutputDispatcher(proc,etype, stderr_fd)
  1465. if stdin_fd is not None:
  1466. dispatchers[stdin_fd] = PInputDispatcher(proc, 'stdin', stdin_fd)
  1467. return dispatchers, p
  1468. class EventListenerConfig(ProcessConfig):
  1469. def make_dispatchers(self, proc):
  1470. use_stderr = not self.redirect_stderr
  1471. p = self.options.make_pipes(use_stderr)
  1472. stdout_fd,stderr_fd,stdin_fd = p['stdout'],p['stderr'],p['stdin']
  1473. dispatchers = {}
  1474. from supervisor.dispatchers import PEventListenerDispatcher
  1475. from supervisor.dispatchers import PInputDispatcher
  1476. from supervisor.dispatchers import POutputDispatcher
  1477. from supervisor import events
  1478. if stdout_fd is not None:
  1479. dispatchers[stdout_fd] = PEventListenerDispatcher(proc, 'stdout',
  1480. stdout_fd)
  1481. if stderr_fd is not None:
  1482. etype = events.ProcessCommunicationStderrEvent
  1483. dispatchers[stderr_fd] = POutputDispatcher(proc, etype, stderr_fd)
  1484. if stdin_fd is not None:
  1485. dispatchers[stdin_fd] = PInputDispatcher(proc, 'stdin', stdin_fd)
  1486. return dispatchers, p
  1487. class FastCGIProcessConfig(ProcessConfig):
  1488. def make_process(self, group=None):
  1489. if group is None:
  1490. raise NotImplementedError('FastCGI programs require a group')
  1491. from supervisor.process import FastCGISubprocess
  1492. process = FastCGISubprocess(self)
  1493. process.group = group
  1494. return process
  1495. def make_dispatchers(self, proc):
  1496. dispatchers, p = ProcessConfig.make_dispatchers(self, proc)
  1497. #FastCGI child processes expect the FastCGI socket set to
  1498. #file descriptor 0, so supervisord cannot use stdin
  1499. #to communicate with the child process
  1500. stdin_fd = p['stdin']
  1501. if stdin_fd is not None:
  1502. dispatchers[stdin_fd].close()
  1503. return dispatchers, p
  1504. class ProcessGroupConfig(Config):
  1505. def __init__(self, options, name, priority, process_configs):
  1506. self.options = options
  1507. self.name = name
  1508. self.priority = priority
  1509. self.process_configs = process_configs
  1510. def __eq__(self, other):
  1511. if not isinstance(other, ProcessGroupConfig):
  1512. return False
  1513. if self.name != other.name:
  1514. return False
  1515. if self.priority != other.priority:
  1516. return False
  1517. if self.process_configs != other.process_configs:
  1518. return False
  1519. return True
  1520. def after_setuid(self):
  1521. for config in self.process_configs:
  1522. config.create_autochildlogs()
  1523. def make_group(self):
  1524. from supervisor.process import ProcessGroup
  1525. return ProcessGroup(self)
  1526. class EventListenerPoolConfig(Config):
  1527. def __init__(self, options, name, priority, process_configs, buffer_size,
  1528. pool_events, result_handler):
  1529. self.options = options
  1530. self.name = name
  1531. self.priority = priority
  1532. self.process_configs = process_configs
  1533. self.buffer_size = buffer_size
  1534. self.pool_events = pool_events
  1535. self.result_handler = result_handler
  1536. def __eq__(self, other):
  1537. if not isinstance(other, EventListenerPoolConfig):
  1538. return False
  1539. if (self.name == other.name) and (self.priority == other.priority):
  1540. return True
  1541. return False
  1542. def after_setuid(self):
  1543. for config in self.process_configs:
  1544. config.create_autochildlogs()
  1545. def make_group(self):
  1546. from supervisor.process import EventListenerPool
  1547. return EventListenerPool(self)
  1548. class FastCGIGroupConfig(ProcessGroupConfig):
  1549. def __init__(self, options, name, priority, process_configs,
  1550. socket_config):
  1551. self.options = options
  1552. self.name = name
  1553. self.priority = priority
  1554. self.process_configs = process_configs
  1555. self.socket_config = socket_config
  1556. def __eq__(self, other):
  1557. if not isinstance(other, FastCGIGroupConfig):
  1558. return False
  1559. if self.socket_config != other.socket_config:
  1560. return False
  1561. return ProcessGroupConfig.__eq__(self, other)
  1562. def make_group(self):
  1563. from supervisor.process import FastCGIProcessGroup
  1564. return FastCGIProcessGroup(self)
  1565. def readFile(filename, offset, length):
  1566. """ Read length bytes from the file named by filename starting at
  1567. offset """
  1568. absoffset = abs(offset)
  1569. abslength = abs(length)
  1570. try:
  1571. f = open(filename, 'rb')
  1572. if absoffset != offset:
  1573. # negative offset returns offset bytes from tail of the file
  1574. if length:
  1575. raise ValueError('BAD_ARGUMENTS')
  1576. f.seek(0, 2)
  1577. sz = f.tell()
  1578. pos = int(sz - absoffset)
  1579. if pos < 0:
  1580. pos = 0
  1581. f.seek(pos)
  1582. data = f.read(absoffset)
  1583. else:
  1584. if abslength != length:
  1585. raise ValueError('BAD_ARGUMENTS')
  1586. if length == 0:
  1587. f.seek(offset)
  1588. data = f.read()
  1589. else:
  1590. sz = f.seek(offset)
  1591. data = f.read(length)
  1592. except (OSError, IOError):
  1593. raise ValueError('FAILED')
  1594. return data
  1595. def tailFile(filename, offset, length):
  1596. """
  1597. Read length bytes from the file named by filename starting at
  1598. offset, automatically increasing offset and setting overflow
  1599. flag if log size has grown beyond (offset + length). If length
  1600. bytes are not available, as many bytes as are available are returned.
  1601. """
  1602. overflow = False
  1603. try:
  1604. f = open(filename, 'rb')
  1605. f.seek(0, 2)
  1606. sz = f.tell()
  1607. if sz > (offset + length):
  1608. overflow = True
  1609. offset = sz - 1
  1610. if (offset + length) > sz:
  1611. if (offset > (sz - 1)):
  1612. length = 0
  1613. offset = sz - length
  1614. if offset < 0: offset = 0
  1615. if length < 0: length = 0
  1616. if length == 0:
  1617. data = ''
  1618. else:
  1619. f.seek(offset)
  1620. data = f.read(length)
  1621. offset = sz
  1622. return [data, offset, overflow]
  1623. except (OSError, IOError):
  1624. return ['', offset, False]
  1625. # Helpers for dealing with signals and exit status
  1626. def decode_wait_status(sts):
  1627. """Decode the status returned by wait() or waitpid().
  1628. Return a tuple (exitstatus, message) where exitstatus is the exit
  1629. status, or -1 if the process was killed by a signal; and message
  1630. is a message telling what happened. It is the caller's
  1631. responsibility to display the message.
  1632. """
  1633. if os.WIFEXITED(sts):
  1634. es = os.WEXITSTATUS(sts) & 0xffff
  1635. msg = "exit status %s" % es
  1636. return es, msg
  1637. elif os.WIFSIGNALED(sts):
  1638. sig = os.WTERMSIG(sts)
  1639. msg = "terminated by %s" % signame(sig)
  1640. if hasattr(os, "WCOREDUMP"):
  1641. iscore = os.WCOREDUMP(sts)
  1642. else:
  1643. iscore = sts & 0x80
  1644. if iscore:
  1645. msg += " (core dumped)"
  1646. return -1, msg
  1647. else:
  1648. msg = "unknown termination cause 0x%04x" % sts
  1649. return -1, msg
  1650. _signames = None
  1651. def signame(sig):
  1652. """Return a symbolic name for a signal.
  1653. Return "signal NNN" if there is no corresponding SIG name in the
  1654. signal module.
  1655. """
  1656. if _signames is None:
  1657. _init_signames()
  1658. return _signames.get(sig) or "signal %d" % sig
  1659. def _init_signames():
  1660. global _signames
  1661. d = {}
  1662. for k, v in signal.__dict__.items():
  1663. k_startswith = getattr(k, "startswith", None)
  1664. if k_startswith is None:
  1665. continue
  1666. if k_startswith("SIG") and not k_startswith("SIG_"):
  1667. d[v] = k
  1668. _signames = d
  1669. class SignalReceiver:
  1670. def __init__(self):
  1671. self._signals_recvd = []
  1672. def receive(self, sig, frame):
  1673. if sig not in self._signals_recvd:
  1674. self._signals_recvd.append(sig)
  1675. def get_signal(self):
  1676. if self._signals_recvd:
  1677. sig = self._signals_recvd.pop(0)
  1678. else:
  1679. sig = None
  1680. return sig
  1681. # miscellaneous utility functions
  1682. def expand(s, expansions, name):
  1683. try:
  1684. return s % expansions
  1685. except KeyError:
  1686. raise ValueError(
  1687. 'Format string %r for %r contains names which cannot be '
  1688. 'expanded' % (s, name))
  1689. except:
  1690. raise ValueError(
  1691. 'Format string %r for %r is badly formatted' % (s, name)
  1692. )
  1693. _environ_expansions = None
  1694. def environ_expansions():
  1695. """Return dict of environment variables, suitable for use in string
  1696. expansions.
  1697. Every environment variable is prefixed by 'ENV_'.
  1698. """
  1699. global _environ_expansions
  1700. if _environ_expansions:
  1701. return _environ_expansions
  1702. _environ_expansions = {}
  1703. for key, value in os.environ.iteritems():
  1704. _environ_expansions['ENV_%s' % key] = value
  1705. return _environ_expansions
  1706. def make_namespec(group_name, process_name):
  1707. # we want to refer to the process by its "short name" (a process named
  1708. # process1 in the group process1 has a name "process1"). This is for
  1709. # backwards compatibility
  1710. if group_name == process_name:
  1711. name = process_name
  1712. else:
  1713. name = '%s:%s' % (group_name, process_name)
  1714. return name
  1715. def split_namespec(namespec):
  1716. names = namespec.split(':', 1)
  1717. if len(names) == 2:
  1718. # group and and process name differ
  1719. group_name, process_name = names
  1720. if not process_name or process_name == '*':
  1721. process_name = None
  1722. else:
  1723. # group name is same as process name
  1724. group_name, process_name = namespec, namespec
  1725. return group_name, process_name
  1726. # exceptions
  1727. class ProcessException(Exception):
  1728. """ Specialized exceptions used when attempting to start a process """
  1729. class BadCommand(ProcessException):
  1730. """ Indicates the command could not be parsed properly. """
  1731. class NotExecutable(ProcessException):
  1732. """ Indicates that the filespec cannot be executed because its path
  1733. resolves to a file which is not executable, or which is a directory. """
  1734. class NotFound(ProcessException):
  1735. """ Indicates that the filespec cannot be executed because it could not
  1736. be found """
  1737. class NoPermission(ProcessException):
  1738. """ Indicates that the file cannot be executed because the supervisor
  1739. process does not possess the appropriate UNIX filesystem permission
  1740. to execute the file. """