comparison planemo/lib/python3.7/site-packages/galaxy/util/pastescript/serve.py @ 1:56ad4e20f292 draft

"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
author guerler
date Fri, 31 Jul 2020 00:32:28 -0400
parents
children
comparison
equal deleted inserted replaced
0:d30785e31577 1:56ad4e20f292
1 # Most of this code is:
2
3 # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
4 # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
5
6 # The server command includes the additional header:
7
8 # For discussion of daemonizing:
9 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/278731
10 # Code taken also from QP:
11 # http://www.mems-exchange.org/software/qp/
12 # From lib/site.py
13
14 # Galaxy originally used PasteScript and PasteDeploy for application
15 # loading, to maintain compatibility we've internalized some of that
16 # code here, stripping out uneeded functionality.
17
18 # All top level imports from each package moved here and organized
19 from __future__ import absolute_import
20 from __future__ import print_function
21
22 import atexit
23 import errno
24 import grp
25 import logging
26 import optparse
27 import os
28 import pwd
29 import re
30 import resource
31 import signal
32 import socket
33 import subprocess
34 import sys
35 import textwrap
36 import threading
37 import time
38 from logging.config import fileConfig
39
40 from six.moves import configparser
41
42 from .loadwsgi import loadapp, loadserver
43
44
45 difflib = None
46
47 # ---- from paste.script.bool_optparse --------------------------------
48
49 """
50 A subclass of ``optparse.OptionParser`` that allows boolean long
51 options (like ``--verbose``) to also take arguments (like
52 ``--verbose=true``). Arguments *must* use ``=``.
53 """
54
55 try:
56 _ = optparse._
57 except AttributeError:
58 from gettext import gettext as _
59
60
61 class BoolOptionParser(optparse.OptionParser):
62
63 def _process_long_opt(self, rargs, values):
64 arg = rargs.pop(0)
65
66 # Value explicitly attached to arg? Pretend it's the next
67 # argument.
68 if "=" in arg:
69 (opt, next_arg) = arg.split("=", 1)
70 rargs.insert(0, next_arg)
71 had_explicit_value = True
72 else:
73 opt = arg
74 had_explicit_value = False
75
76 opt = self._match_long_opt(opt)
77 option = self._long_opt[opt]
78 if option.takes_value():
79 nargs = option.nargs
80 if len(rargs) < nargs:
81 if nargs == 1:
82 self.error(_("%s option requires an argument") % opt)
83 else:
84 self.error(_("%s option requires %d arguments")
85 % (opt, nargs))
86 elif nargs == 1:
87 value = rargs.pop(0)
88 else:
89 value = tuple(rargs[0:nargs])
90 del rargs[0:nargs]
91
92 elif had_explicit_value:
93 value = rargs[0].lower().strip()
94 del rargs[0:1]
95 if value in ('true', 'yes', 'on', '1', 'y', 't'):
96 value = None
97 elif value in ('false', 'no', 'off', '0', 'n', 'f'):
98 # Don't process
99 return
100 else:
101 self.error(_('%s option takes a boolean value only (true/false)') % opt)
102
103 else:
104 value = None
105
106 option.process(opt, value, values, self)
107
108 # ---- from paste.script.command --------------------------------------
109
110 # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
111 # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
112
113
114 class BadCommand(Exception):
115
116 def __init__(self, message, exit_code=2):
117 self.message = message
118 self.exit_code = exit_code
119 Exception.__init__(self, message)
120
121 def _get_message(self):
122 """Getter for 'message'; needed only to override deprecation
123 in BaseException."""
124 return self.__message
125
126 def _set_message(self, value):
127 """Setter for 'message'; needed only to override deprecation
128 in BaseException."""
129 self.__message = value
130
131 # BaseException.message has been deprecated since Python 2.6.
132 # To prevent DeprecationWarning from popping up over this
133 # pre-existing attribute, use a new property that takes lookup
134 # precedence.
135 message = property(_get_message, _set_message)
136
137
138 class NoDefault(object):
139 pass
140
141
142 # run and invoke methods moved below ServeCommand
143 class Command(object):
144
145 def __init__(self, name):
146 self.command_name = name
147
148 max_args = None
149 max_args_error = 'You must provide no more than %(max_args)s arguments'
150 min_args = None
151 min_args_error = 'You must provide at least %(min_args)s arguments'
152 required_args = None
153 # If this command takes a configuration file, set this to 1 or -1
154 # Then if invoked through #! the config file will be put into the positional
155 # arguments -- at the beginning with 1, at the end with -1
156 takes_config_file = None
157
158 # Grouped in help messages by this:
159 group_name = ''
160
161 required_args = ()
162 description = None
163 usage = ''
164 hidden = False
165 # This is the default verbosity level; --quiet subtracts,
166 # --verbose adds:
167 default_verbosity = 0
168 # This is the default interactive state:
169 default_interactive = 0
170 return_code = 0
171
172 BadCommand = BadCommand
173
174 # Must define:
175 # parser
176 # summary
177 # command()
178
179 def run(self, args):
180 self.parse_args(args)
181
182 # Setup defaults:
183 for name, default in [('verbose', 0),
184 ('quiet', 0),
185 ('interactive', False),
186 ('overwrite', False)]:
187 if not hasattr(self.options, name):
188 setattr(self.options, name, default)
189 if getattr(self.options, 'simulate', False):
190 self.options.verbose = max(self.options.verbose, 1)
191 self.interactive = self.default_interactive
192 if getattr(self.options, 'interactive', False):
193 self.interactive += self.options.interactive
194 if getattr(self.options, 'no_interactive', False):
195 self.interactive = False
196 self.verbose = self.default_verbosity
197 self.verbose += self.options.verbose
198 self.verbose -= self.options.quiet
199 self.simulate = getattr(self.options, 'simulate', False)
200
201 # For #! situations:
202 if os.environ.get('PASTE_CONFIG_FILE') and self.takes_config_file is not None:
203 take = self.takes_config_file
204 filename = os.environ.get('PASTE_CONFIG_FILE')
205 if take == 1:
206 self.args.insert(0, filename)
207 elif take == -1:
208 self.args.append(filename)
209 else:
210 assert 0, (
211 "Value takes_config_file must be None, 1, or -1 (not %r)"
212 % take)
213
214 if os.environ.get('PASTE_DEFAULT_QUIET'):
215 self.verbose = 0
216
217 # Validate:
218 if self.min_args is not None and len(self.args) < self.min_args:
219 raise BadCommand(
220 self.min_args_error % {'min_args': self.min_args,
221 'actual_args': len(self.args)})
222 if self.max_args is not None and len(self.args) > self.max_args:
223 raise BadCommand(
224 self.max_args_error % {'max_args': self.max_args,
225 'actual_args': len(self.args)})
226 for var_name, option_name in self.required_args:
227 if not getattr(self.options, var_name, None):
228 raise BadCommand(
229 'You must provide the option %s' % option_name)
230 result = self.command()
231 if result is None:
232 return self.return_code
233 else:
234 return result
235
236 def parse_args(self, args):
237 if self.usage:
238 usage = ' ' + self.usage
239 else:
240 usage = ''
241 self.parser.usage = "%%prog [options]%s\n%s" % (
242 usage, self.summary)
243 self.parser.prog = self._prog_name()
244 if self.description:
245 desc = self.description
246 desc = textwrap.dedent(desc)
247 self.parser.description = desc
248 self.options, self.args = self.parser.parse_args(args)
249
250 def _prog_name(self):
251 return '%s %s' % (os.path.basename(sys.argv[0]), self.command_name)
252
253 ########################################
254 # Utility methods
255 ########################################
256
257 def pad(self, s, length, dir='left'):
258 if len(s) >= length:
259 return s
260 if dir == 'left':
261 return s + ' ' * (length - len(s))
262 else:
263 return ' ' * (length - len(s)) + s
264
265 def standard_parser(cls, verbose=True,
266 interactive=False,
267 no_interactive=False,
268 simulate=False,
269 quiet=False,
270 overwrite=False):
271 """
272 Create a standard ``OptionParser`` instance.
273
274 Typically used like::
275
276 class MyCommand(Command):
277 parser = Command.standard_parser()
278
279 Subclasses may redefine ``standard_parser``, so use the
280 nearest superclass's class method.
281 """
282 parser = BoolOptionParser()
283 if verbose:
284 parser.add_option('-v', '--verbose',
285 action='count',
286 dest='verbose',
287 default=0)
288 if quiet:
289 parser.add_option('-q', '--quiet',
290 action='count',
291 dest='quiet',
292 default=0)
293 if no_interactive:
294 parser.add_option('--no-interactive',
295 action="count",
296 dest="no_interactive",
297 default=0)
298 if interactive:
299 parser.add_option('-i', '--interactive',
300 action='count',
301 dest='interactive',
302 default=0)
303 if simulate:
304 parser.add_option('-n', '--simulate',
305 action='store_true',
306 dest='simulate',
307 default=False)
308 if overwrite:
309 parser.add_option('-f', '--overwrite',
310 dest="overwrite",
311 action="store_true",
312 help="Overwrite files (warnings will be emitted for non-matching files otherwise)")
313 return parser
314
315 standard_parser = classmethod(standard_parser)
316
317 def quote_first_command_arg(self, arg):
318 """
319 There's a bug in Windows when running an executable that's
320 located inside a path with a space in it. This method handles
321 that case, or on non-Windows systems or an executable with no
322 spaces, it just leaves well enough alone.
323 """
324 if sys.platform != 'win32' or ' ' not in arg:
325 # Problem does not apply:
326 return arg
327 try:
328 import win32api
329 except ImportError:
330 raise ValueError(
331 "The executable %r contains a space, and in order to "
332 "handle this issue you must have the win32api module "
333 "installed" % arg)
334 arg = win32api.GetShortPathName(arg)
335 return arg
336
337 def parse_vars(self, args):
338 """
339 Given variables like ``['a=b', 'c=d']`` turns it into ``{'a':
340 'b', 'c': 'd'}``
341 """
342 result = {}
343 for arg in args:
344 if '=' not in arg:
345 raise BadCommand(
346 'Variable assignment %r invalid (no "=")'
347 % arg)
348 name, value = arg.split('=', 1)
349 result[name] = value
350 return result
351
352 def logging_file_config(self, config_file):
353 """
354 Setup logging via the logging module's fileConfig function with the
355 specified ``config_file``, if applicable.
356
357 ConfigParser defaults are specified for the special ``__file__``
358 and ``here`` variables, similar to PasteDeploy config loading.
359 """
360 parser = configparser.ConfigParser()
361 parser.read([config_file])
362 if parser.has_section('loggers'):
363 config_file = os.path.abspath(config_file)
364 fileConfig(config_file, dict(__file__=config_file,
365 here=os.path.dirname(config_file)))
366
367
368 class NotFoundCommand(Command):
369
370 def run(self, args):
371 print('Command %r not known (you may need to run setup.py egg_info)'
372 % self.command_name)
373 commands = list()
374 commands.sort()
375 if not commands:
376 print('No commands registered.')
377 print('Have you installed Paste Script?')
378 print('(try running python setup.py develop)')
379 return 2
380 print('Known commands:')
381 longest = max([len(n) for n, c in commands])
382 for name, command in commands:
383 print(' %s %s' % (self.pad(name, length=longest),
384 command.load().summary))
385 return 2
386
387
388 # ---- From paste.script.serve ----------------------------------------
389
390 MAXFD = 1024
391
392 jython = sys.platform.startswith('java')
393
394
395 class DaemonizeException(Exception):
396 pass
397
398
399 class ServeCommand(Command):
400
401 min_args = 0
402 usage = 'CONFIG_FILE [start|stop|restart|status] [var=value]'
403 takes_config_file = 1
404 summary = "Serve the described application"
405 description = """\
406 This command serves a web application that uses a paste.deploy
407 configuration file for the server and application.
408
409 If start/stop/restart is given, then --daemon is implied, and it will
410 start (normal operation), stop (--stop-daemon), or do both.
411
412 You can also include variable assignments like 'http_port=8080'
413 and then use %(http_port)s in your config files.
414 """
415
416 # used by subclasses that configure apps and servers differently
417 requires_config_file = True
418
419 parser = Command.standard_parser(quiet=True)
420 parser.add_option('-n', '--app-name',
421 dest='app_name',
422 metavar='NAME',
423 help="Load the named application (default main)")
424 parser.add_option('-s', '--server',
425 dest='server',
426 metavar='SERVER_TYPE',
427 help="Use the named server.")
428 parser.add_option('--server-name',
429 dest='server_name',
430 metavar='SECTION_NAME',
431 help="Use the named server as defined in the configuration file (default: main)")
432 if hasattr(os, 'fork'):
433 parser.add_option('--daemon',
434 dest="daemon",
435 action="store_true",
436 help="Run in daemon (background) mode")
437 parser.add_option('--pid-file',
438 dest='pid_file',
439 metavar='FILENAME',
440 help="Save PID to file (default to paster.pid if running in daemon mode)")
441 parser.add_option('--log-file',
442 dest='log_file',
443 metavar='LOG_FILE',
444 help="Save output to the given log file (redirects stdout)")
445 parser.add_option('--reload',
446 dest='reload',
447 action='store_true',
448 help="Use auto-restart file monitor")
449 parser.add_option('--reload-interval',
450 dest='reload_interval',
451 default=1,
452 help="Seconds between checking files (low number can cause significant CPU usage)")
453 parser.add_option('--monitor-restart',
454 dest='monitor_restart',
455 action='store_true',
456 help="Auto-restart server if it dies")
457 parser.add_option('--status',
458 action='store_true',
459 dest='show_status',
460 help="Show the status of the (presumably daemonized) server")
461
462 if hasattr(os, 'setuid'):
463 # I don't think these are available on Windows
464 parser.add_option('--user',
465 dest='set_user',
466 metavar="USERNAME",
467 help="Set the user (usually only possible when run as root)")
468 parser.add_option('--group',
469 dest='set_group',
470 metavar="GROUP",
471 help="Set the group (usually only possible when run as root)")
472
473 parser.add_option('--stop-daemon',
474 dest='stop_daemon',
475 action='store_true',
476 help='Stop a daemonized server (given a PID file, or default paster.pid file)')
477
478 if jython:
479 parser.add_option('--disable-jython-reloader',
480 action='store_true',
481 dest='disable_jython_reloader',
482 help="Disable the Jython reloader")
483
484 _scheme_re = re.compile(r'^[a-z][a-z]+:', re.I)
485
486 default_verbosity = 1
487
488 _reloader_environ_key = 'PYTHON_RELOADER_SHOULD_RUN'
489 _monitor_environ_key = 'PASTE_MONITOR_SHOULD_RUN'
490
491 possible_subcommands = ('start', 'stop', 'restart', 'status')
492
493 def command(self):
494 if self.options.stop_daemon:
495 return self.stop_daemon()
496
497 if not hasattr(self.options, 'set_user'):
498 # Windows case:
499 self.options.set_user = self.options.set_group = None
500 # @@: Is this the right stage to set the user at?
501 self.change_user_group(
502 self.options.set_user, self.options.set_group)
503
504 if self.requires_config_file:
505 if not self.args:
506 raise BadCommand('You must give a config file')
507 app_spec = self.args[0]
508 if len(self.args) > 1 and self.args[1] in self.possible_subcommands:
509 cmd = self.args[1]
510 restvars = self.args[2:]
511 else:
512 cmd = None
513 restvars = self.args[1:]
514 else:
515 app_spec = ""
516 if self.args and self.args[0] in self.possible_subcommands:
517 cmd = self.args[0]
518 restvars = self.args[1:]
519 else:
520 cmd = None
521 restvars = self.args[:]
522
523 if (getattr(self.options, 'daemon', False) and
524 getattr(self.options, 'reload', False)):
525 raise BadCommand('The --daemon and --reload options may not be used together')
526
527 jython_monitor = False
528 if self.options.reload:
529 if jython and not self.options.disable_jython_reloader:
530 # JythonMonitor raises the special SystemRestart
531 # exception that'll cause the Jython interpreter to
532 # reload in the existing Java process (avoiding
533 # subprocess startup time)
534 try:
535 from paste.reloader import JythonMonitor
536 except ImportError:
537 pass
538 else:
539 jython_monitor = JythonMonitor(poll_interval=int(
540 self.options.reload_interval))
541 if self.requires_config_file:
542 jython_monitor.watch_file(self.args[0])
543
544 if not jython_monitor:
545 if os.environ.get(self._reloader_environ_key):
546 from paste import reloader
547 if self.verbose > 1:
548 print('Running reloading file monitor')
549 reloader.install(int(self.options.reload_interval))
550 if self.requires_config_file:
551 reloader.watch_file(self.args[0])
552 else:
553 return self.restart_with_reloader()
554
555 if cmd not in (None, 'start', 'stop', 'restart', 'status'):
556 raise BadCommand(
557 'Error: must give start|stop|restart (not %s)' % cmd)
558
559 if cmd == 'status' or self.options.show_status:
560 return self.show_status()
561
562 if cmd == 'restart' or cmd == 'stop':
563 result = self.stop_daemon()
564 if result:
565 print("Could not stop daemon")
566 # It's ok to continue trying to restart if stop_daemon returns
567 # a 1, otherwise shortcut and return.
568 if cmd == 'restart' and result != 1:
569 return result
570 if cmd == 'stop':
571 return result
572 self.options.daemon = True
573
574 if cmd == 'start':
575 self.options.daemon = True
576
577 app_name = self.options.app_name
578 vars = self.parse_vars(restvars)
579 if not self._scheme_re.search(app_spec):
580 app_spec = 'config:' + app_spec
581 server_name = self.options.server_name
582 if self.options.server:
583 server_spec = 'egg:PasteScript'
584 assert server_name is None
585 server_name = self.options.server
586 else:
587 server_spec = app_spec
588 base = os.getcwd()
589
590 if getattr(self.options, 'daemon', False):
591 if not self.options.pid_file:
592 self.options.pid_file = 'paster.pid'
593 if not self.options.log_file:
594 self.options.log_file = 'paster.log'
595
596 # Ensure the log file is writeable
597 if self.options.log_file:
598 try:
599 writeable_log_file = open(self.options.log_file, 'a')
600 except IOError as ioe:
601 msg = 'Error: Unable to write to log file: %s' % ioe
602 raise BadCommand(msg)
603 writeable_log_file.close()
604
605 # Ensure the pid file is writeable
606 if self.options.pid_file:
607 try:
608 writeable_pid_file = open(self.options.pid_file, 'a')
609 except IOError as ioe:
610 msg = 'Error: Unable to write to pid file: %s' % ioe
611 raise BadCommand(msg)
612 writeable_pid_file.close()
613
614 if getattr(self.options, 'daemon', False):
615 try:
616 self.daemonize()
617 except DaemonizeException as ex:
618 if self.verbose > 0:
619 print(str(ex))
620 return
621
622 if (self.options.monitor_restart and not
623 os.environ.get(self._monitor_environ_key)):
624 return self.restart_with_monitor()
625
626 if self.options.pid_file:
627 self.record_pid(self.options.pid_file)
628
629 if self.options.log_file:
630 stdout_log = LazyWriter(self.options.log_file, 'a')
631 sys.stdout = stdout_log
632 sys.stderr = stdout_log
633 logging.basicConfig(stream=stdout_log)
634
635 log_fn = app_spec
636 if log_fn.startswith('config:'):
637 log_fn = app_spec[len('config:'):]
638 elif log_fn.startswith('egg:'):
639 log_fn = None
640 if log_fn:
641 log_fn = os.path.join(base, log_fn)
642 self.logging_file_config(log_fn)
643
644 server = loadserver(server_spec, name=server_name, relative_to=base, global_conf=vars)
645
646 app = loadapp(app_spec, name=app_name, relative_to=base, global_conf=vars)
647
648 if self.verbose > 0:
649 if hasattr(os, 'getpid'):
650 msg = 'Starting server in PID %i.' % os.getpid()
651 else:
652 msg = 'Starting server.'
653 print(msg)
654
655 def serve():
656 try:
657 server(app)
658 except (SystemExit, KeyboardInterrupt) as e:
659 if self.verbose > 1:
660 raise
661 if str(e):
662 msg = ' ' + str(e)
663 else:
664 msg = ''
665 print('Exiting%s (-v to see traceback)' % msg)
666 except AttributeError as e:
667 # Capturing bad error response from paste
668 if str(e) == "'WSGIThreadPoolServer' object has no attribute 'thread_pool'":
669 raise socket.error(98, 'Address already in use')
670 else:
671 raise AttributeError(e)
672
673 if jython_monitor:
674 # JythonMonitor has to be ran from the main thread
675 threading.Thread(target=serve).start()
676 print('Starting Jython file monitor')
677 jython_monitor.periodic_reload()
678 else:
679 serve()
680
681 def daemonize(self):
682 pid = live_pidfile(self.options.pid_file)
683 if pid:
684 raise DaemonizeException(
685 "Daemon is already running (PID: %s from PID file %s)"
686 % (pid, self.options.pid_file))
687
688 if self.verbose > 0:
689 print('Entering daemon mode')
690 pid = os.fork()
691 if pid:
692 # The forked process also has a handle on resources, so we
693 # *don't* want proper termination of the process, we just
694 # want to exit quick (which os._exit() does)
695 os._exit(0)
696 # Make this the session leader
697 os.setsid()
698 # Fork again for good measure!
699 pid = os.fork()
700 if pid:
701 os._exit(0)
702
703 # @@: Should we set the umask and cwd now?
704
705 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
706 if maxfd == resource.RLIM_INFINITY:
707 maxfd = MAXFD
708 # Iterate through and close all file descriptors.
709 for fd in range(0, maxfd):
710 try:
711 os.close(fd)
712 except OSError: # ERROR, fd wasn't open to begin with (ignored)
713 pass
714
715 if hasattr(os, "devnull"):
716 REDIRECT_TO = os.devnull
717 else:
718 REDIRECT_TO = "/dev/null"
719 os.open(REDIRECT_TO, os.O_RDWR) # standard input (0)
720 # Duplicate standard input to standard output and standard error.
721 os.dup2(0, 1) # standard output (1)
722 os.dup2(0, 2) # standard error (2)
723
724 def record_pid(self, pid_file):
725 pid = os.getpid()
726 if self.verbose > 1:
727 print('Writing PID %s to %s' % (pid, pid_file))
728 f = open(pid_file, 'w')
729 f.write(str(pid))
730 f.close()
731 atexit.register(_remove_pid_file, pid, pid_file, self.verbose)
732
733 def stop_daemon(self):
734 pid_file = self.options.pid_file or 'paster.pid'
735 if not os.path.exists(pid_file):
736 print('No PID file exists in %s' % pid_file)
737 return 1
738 pid = read_pidfile(pid_file)
739 if not pid:
740 print("Not a valid PID file in %s" % pid_file)
741 return 1
742 pid = live_pidfile(pid_file)
743 if not pid:
744 print("PID in %s is not valid (deleting)" % pid_file)
745 try:
746 os.unlink(pid_file)
747 except (OSError, IOError) as e:
748 print("Could not delete: %s" % e)
749 return 2
750 return 1
751 for _i in range(10):
752 if not live_pidfile(pid_file):
753 break
754 os.kill(pid, signal.SIGTERM)
755 time.sleep(1)
756 else:
757 print("failed to kill web process %s" % pid)
758 return 3
759 if os.path.exists(pid_file):
760 os.unlink(pid_file)
761 return 0
762
763 def show_status(self):
764 pid_file = self.options.pid_file or 'paster.pid'
765 if not os.path.exists(pid_file):
766 print('No PID file %s' % pid_file)
767 return 1
768 pid = read_pidfile(pid_file)
769 if not pid:
770 print('No PID in file %s' % pid_file)
771 return 1
772 pid = live_pidfile(pid_file)
773 if not pid:
774 print('PID %s in %s is not running' % (pid, pid_file))
775 return 1
776 print('Server running in PID %s' % pid)
777 return 0
778
779 def restart_with_reloader(self):
780 self.restart_with_monitor(reloader=True)
781
782 def restart_with_monitor(self, reloader=False):
783 if self.verbose > 0:
784 if reloader:
785 print('Starting subprocess with file monitor')
786 else:
787 print('Starting subprocess with monitor parent')
788 while 1:
789 args = [self.quote_first_command_arg(sys.executable)] + sys.argv
790 new_environ = os.environ.copy()
791 if reloader:
792 new_environ[self._reloader_environ_key] = 'true'
793 else:
794 new_environ[self._monitor_environ_key] = 'true'
795 proc = None
796 try:
797 try:
798 _turn_sigterm_into_systemexit()
799 proc = subprocess.Popen(args, env=new_environ)
800 exit_code = proc.wait()
801 proc = None
802 except KeyboardInterrupt:
803 print('^C caught in monitor process')
804 if self.verbose > 1:
805 raise
806 return 1
807 finally:
808 if proc is not None and hasattr(os, 'kill'):
809 try:
810 os.kill(proc.pid, signal.SIGTERM)
811 except (OSError, IOError):
812 pass
813
814 if reloader:
815 # Reloader always exits with code 3; but if we are
816 # a monitor, any exit code will restart
817 if exit_code != 3:
818 return exit_code
819 if self.verbose > 0:
820 print('-' * 20, 'Restarting', '-' * 20)
821
822 def change_user_group(self, user, group):
823 if not user and not group:
824 return
825 uid = gid = None
826 if group:
827 try:
828 gid = int(group)
829 group = grp.getgrgid(gid).gr_name
830 except ValueError:
831 try:
832 entry = grp.getgrnam(group)
833 except KeyError:
834 raise BadCommand(
835 "Bad group: %r; no such group exists" % group)
836 gid = entry.gr_gid
837 try:
838 uid = int(user)
839 user = pwd.getpwuid(uid).pw_name
840 except ValueError:
841 try:
842 entry = pwd.getpwnam(user)
843 except KeyError:
844 raise BadCommand(
845 "Bad username: %r; no such user exists" % user)
846 if not gid:
847 gid = entry.pw_gid
848 uid = entry.pw_uid
849 if self.verbose > 0:
850 print('Changing user to %s:%s (%s:%s)' % (
851 user, group or '(unknown)', uid, gid))
852 if hasattr(os, 'initgroups'):
853 os.initgroups(user, gid)
854 else:
855 os.setgroups([e.gr_gid for e in grp.getgrall()
856 if user in e.gr_mem] + [gid])
857 if gid:
858 os.setgid(gid)
859 if uid:
860 os.setuid(uid)
861
862
863 class LazyWriter(object):
864
865 """
866 File-like object that opens a file lazily when it is first written
867 to.
868 """
869
870 def __init__(self, filename, mode='w'):
871 self.filename = filename
872 self.fileobj = None
873 self.lock = threading.Lock()
874 self.mode = mode
875
876 def open(self):
877 if self.fileobj is None:
878 self.lock.acquire()
879 try:
880 if self.fileobj is None:
881 self.fileobj = open(self.filename, self.mode)
882 finally:
883 self.lock.release()
884 return self.fileobj
885
886 def write(self, text):
887 fileobj = self.open()
888 fileobj.write(text)
889 fileobj.flush()
890
891 def writelines(self, text):
892 fileobj = self.open()
893 fileobj.writelines(text)
894 fileobj.flush()
895
896 def flush(self):
897 self.open().flush()
898
899
900 def live_pidfile(pidfile):
901 """(pidfile:str) -> int | None
902 Returns an int found in the named file, if there is one,
903 and if there is a running process with that process id.
904 Return None if no such process exists.
905 """
906 pid = read_pidfile(pidfile)
907 if pid:
908 try:
909 os.kill(int(pid), 0)
910 return pid
911 except OSError as e:
912 if e.errno == errno.EPERM:
913 return pid
914 return None
915
916
917 def read_pidfile(filename):
918 if os.path.exists(filename):
919 try:
920 f = open(filename)
921 content = f.read()
922 f.close()
923 return int(content.strip())
924 except (ValueError, IOError):
925 return None
926 else:
927 return None
928
929
930 def _remove_pid_file(written_pid, filename, verbosity):
931 current_pid = os.getpid()
932 if written_pid != current_pid:
933 # A forked process must be exiting, not the process that
934 # wrote the PID file
935 return
936 if not os.path.exists(filename):
937 return
938 f = open(filename)
939 content = f.read().strip()
940 f.close()
941 try:
942 pid_in_file = int(content)
943 except ValueError:
944 pass
945 else:
946 if pid_in_file != current_pid:
947 print("PID file %s contains %s, not expected PID %s" % (
948 filename, pid_in_file, current_pid))
949 return
950 if verbosity > 0:
951 print("Removing PID file %s" % filename)
952 try:
953 os.unlink(filename)
954 return
955 except OSError as e:
956 # Record, but don't give traceback
957 print("Cannot remove PID file: %s" % e)
958 # well, at least lets not leave the invalid PID around...
959 try:
960 f = open(filename, 'w')
961 f.write('')
962 f.close()
963 except OSError as e:
964 print('Stale PID left in file: %s (%e)' % (filename, e))
965 else:
966 print('Stale PID removed')
967
968
969 def ensure_port_cleanup(bound_addresses, maxtries=30, sleeptime=2):
970 """
971 This makes sure any open ports are closed.
972
973 Does this by connecting to them until they give connection
974 refused. Servers should call like::
975
976 import paste.script
977 ensure_port_cleanup([80, 443])
978 """
979 atexit.register(_cleanup_ports, bound_addresses, maxtries=maxtries,
980 sleeptime=sleeptime)
981
982
983 def _cleanup_ports(bound_addresses, maxtries=30, sleeptime=2):
984 # Wait for the server to bind to the port.
985 for bound_address in bound_addresses:
986 for _i in range(maxtries):
987 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
988 try:
989 sock.connect(bound_address)
990 except socket.error as e:
991 if e.errno != errno.ECONNREFUSED:
992 raise
993 break
994 else:
995 time.sleep(sleeptime)
996 else:
997 raise SystemExit('Timeout waiting for port.')
998 sock.close()
999
1000
1001 def _turn_sigterm_into_systemexit():
1002 """
1003 Attempts to turn a SIGTERM exception into a SystemExit exception.
1004 """
1005
1006 def handle_term(signo, frame):
1007 raise SystemExit
1008 signal.signal(signal.SIGTERM, handle_term)
1009
1010
1011 # ---- from paste.script.command --------------------------------------
1012 python_version = sys.version.splitlines()[0].strip()
1013
1014 parser = optparse.OptionParser(add_help_option=False,
1015 # version='%s from %s (python %s)'
1016 # % (dist, dist.location, python_version),
1017 usage='%prog [paster_options] COMMAND [command_options]')
1018
1019 parser.add_option(
1020 '-h', '--help',
1021 action='store_true',
1022 dest='do_help',
1023 help="Show this help message")
1024 parser.disable_interspersed_args()
1025
1026 # @@: Add an option to run this in another Python interpreter
1027
1028 commands = {
1029 'serve': ServeCommand
1030 }
1031
1032
1033 def run(args=None):
1034 if (not args and len(sys.argv) >= 2 and os.environ.get('_') and
1035 sys.argv[0] != os.environ['_'] and os.environ['_'] == sys.argv[1]):
1036 # probably it's an exe execution
1037 args = ['exe', os.environ['_']] + sys.argv[2:]
1038 if args is None:
1039 args = sys.argv[1:]
1040 options, args = parser.parse_args(args)
1041 options.base_parser = parser
1042 if options.do_help:
1043 args = ['help'] + args
1044 if not args:
1045 print('Usage: %s COMMAND' % sys.argv[0])
1046 args = ['help']
1047 command_name = args[0]
1048 if command_name not in commands:
1049 command = NotFoundCommand
1050 else:
1051 command = commands[command_name]
1052 invoke(command, command_name, options, args[1:])
1053
1054
1055 def invoke(command, command_name, options, args):
1056 try:
1057 runner = command(command_name)
1058 exit_code = runner.run(args)
1059 except BadCommand as e:
1060 print(e)
1061 exit_code = e.exit_code
1062 sys.exit(exit_code)