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