comparison roundup/scripts/roundup_server.py @ 6040:d5c51d1ef09c

flake8 whitespace format fixes.
author John Rouillard <rouilj@ieee.org>
date Tue, 07 Jan 2020 21:26:43 -0500
parents f822a91b3778
children 5f275158cfa9
comparison
equal deleted inserted replaced
6039:0dc1e0222353 6040:d5c51d1ef09c
93 # Note: the order is important. Preferred multiprocess type 93 # Note: the order is important. Preferred multiprocess type
94 # is the last element of this list. 94 # is the last element of this list.
95 # "debug" means "none" + no tracker/template cache 95 # "debug" means "none" + no tracker/template cache
96 MULTIPROCESS_TYPES = ["debug", "none"] 96 MULTIPROCESS_TYPES = ["debug", "none"]
97 try: 97 try:
98 import thread 98 import thread # nosrc: F401
99 except ImportError: 99 except ImportError:
100 pass 100 pass
101 else: 101 else:
102 MULTIPROCESS_TYPES.append("thread") 102 MULTIPROCESS_TYPES.append("thread")
103 if hasattr(os, 'fork'): 103 if hasattr(os, 'fork'):
104 MULTIPROCESS_TYPES.append("fork") 104 MULTIPROCESS_TYPES.append("fork")
105 DEFAULT_MULTIPROCESS = MULTIPROCESS_TYPES[-1] 105 DEFAULT_MULTIPROCESS = MULTIPROCESS_TYPES[-1]
106
106 107
107 def auto_ssl(): 108 def auto_ssl():
108 print(_('WARNING: generating temporary SSL certificate')) 109 print(_('WARNING: generating temporary SSL certificate'))
109 import OpenSSL, random 110 import OpenSSL, random
110 pkey = OpenSSL.crypto.PKey() 111 pkey = OpenSSL.crypto.PKey()
111 pkey.generate_key(OpenSSL.crypto.TYPE_RSA, 768) 112 pkey.generate_key(OpenSSL.crypto.TYPE_RSA, 768)
112 cert = OpenSSL.crypto.X509() 113 cert = OpenSSL.crypto.X509()
113 cert.set_serial_number(random.randint(0, sys.maxsize)) 114 cert.set_serial_number(random.randint(0, sys.maxsize))
114 cert.gmtime_adj_notBefore(0) 115 cert.gmtime_adj_notBefore(0)
115 cert.gmtime_adj_notAfter(60 * 60 * 24 * 365) # one year 116 cert.gmtime_adj_notAfter(60 * 60 * 24 * 365) # one year
116 cert.get_subject().CN = '*' 117 cert.get_subject().CN = '*'
117 cert.get_subject().O = 'Roundup Dummy Certificate' 118 cert.get_subject().O = 'Roundup Dummy Certificate'
118 cert.get_issuer().CN = 'Roundup Dummy Certificate Authority' 119 cert.get_issuer().CN = 'Roundup Dummy Certificate Authority'
119 cert.get_issuer().O = 'Self-Signed' 120 cert.get_issuer().O = 'Self-Signed'
120 cert.set_pubkey(pkey) 121 cert.set_pubkey(pkey)
123 ctx.use_privatekey(pkey) 124 ctx.use_privatekey(pkey)
124 ctx.use_certificate(cert) 125 ctx.use_certificate(cert)
125 126
126 return ctx 127 return ctx
127 128
129
128 class SecureHTTPServer(http_.server.HTTPServer): 130 class SecureHTTPServer(http_.server.HTTPServer):
129 def __init__(self, server_address, HandlerClass, ssl_pem=None): 131 def __init__(self, server_address, HandlerClass, ssl_pem=None):
130 assert SSL, "pyopenssl not installed" 132 assert SSL, "pyopenssl not installed"
131 http_.server.HTTPServer.__init__(self, server_address, HandlerClass) 133 http_.server.HTTPServer.__init__(self, server_address, HandlerClass)
132 self.socket = socket.socket(self.address_family, self.socket_type) 134 self.socket = socket.socket(self.address_family, self.socket_type)
174 class ConnFixer(object): 176 class ConnFixer(object):
175 """ wraps an SSL socket so that it implements makefile 177 """ wraps an SSL socket so that it implements makefile
176 which the HTTP handlers require """ 178 which the HTTP handlers require """
177 def __init__(self, conn): 179 def __init__(self, conn):
178 self.__conn = conn 180 self.__conn = conn
181
179 def makefile(self, mode, bufsize): 182 def makefile(self, mode, bufsize):
180 fo = socket._fileobject(self.__conn, mode, bufsize) 183 fo = socket._fileobject(self.__conn, mode, bufsize)
181 return RetryingFile(fo) 184 return RetryingFile(fo)
182 185
183 def __getattr__(self, attrib): 186 def __getattr__(self, attrib):
184 return getattr(self.__conn, attrib) 187 return getattr(self.__conn, attrib)
185 188
186 conn = ConnFixer(conn) 189 conn = ConnFixer(conn)
187 return (conn, info) 190 return (conn, info)
191
188 192
189 class RoundupRequestHandler(http_.server.BaseHTTPRequestHandler): 193 class RoundupRequestHandler(http_.server.BaseHTTPRequestHandler):
190 TRACKER_HOMES = {} 194 TRACKER_HOMES = {}
191 TRACKERS = None 195 TRACKERS = None
192 LOG_IPADDRESS = 1 196 LOG_IPADDRESS = 1
223 try: 227 try:
224 self.inner_run_cgi() 228 self.inner_run_cgi()
225 except client.NotFound: 229 except client.NotFound:
226 self.send_error(404, self.path) 230 self.send_error(404, self.path)
227 except client.Unauthorised as message: 231 except client.Unauthorised as message:
228 self.send_error(403, '%s (%s)'%(self.path, message)) 232 self.send_error(403, '%s (%s)' % (self.path, message))
229 except: 233 except:
230 exc, val, tb = sys.exc_info() 234 exc, val, tb = sys.exc_info()
231 if hasattr(socket, 'timeout') and isinstance(val, socket.timeout): 235 if hasattr(socket, 'timeout') and isinstance(val, socket.timeout):
232 self.log_error('timeout') 236 self.log_error('timeout')
233 else: 237 else:
250 else: 254 else:
251 # user feedback 255 # user feedback
252 self.wfile.write(s2b(cgitb.breaker())) 256 self.wfile.write(s2b(cgitb.breaker()))
253 ts = time.ctime() 257 ts = time.ctime()
254 self.wfile.write(s2b('''<p>%s: An error occurred. Please check 258 self.wfile.write(s2b('''<p>%s: An error occurred. Please check
255 the server log for more information.</p>'''%ts)) 259 the server log for more information.</p>''' % ts))
256 # out to the logfile 260 # out to the logfile
257 print('EXCEPTION AT', ts) 261 print('EXCEPTION AT', ts)
258 traceback.print_exc() 262 traceback.print_exc()
259 263
260 do_GET = do_POST = do_HEAD = do_PUT = do_DELETE = do_PATCH = do_OPTIONS = run_cgi 264 do_GET = do_POST = do_HEAD = do_PUT = do_DELETE = \
265 do_PATCH = do_OPTIONS = run_cgi
261 266
262 def index(self): 267 def index(self):
263 ''' Print up an index of the available trackers 268 ''' Print up an index of the available trackers
264 ''' 269 '''
265 keys = list(self.TRACKER_HOMES.keys()) 270 keys = list(self.TRACKER_HOMES.keys())
276 281
277 if self.CONFIG and self.CONFIG['TEMPLATE']: 282 if self.CONFIG and self.CONFIG['TEMPLATE']:
278 template = open(self.CONFIG['TEMPLATE']).read() 283 template = open(self.CONFIG['TEMPLATE']).read()
279 pt = PageTemplate() 284 pt = PageTemplate()
280 pt.write(template) 285 pt.write(template)
281 extra = { 'trackers': self.TRACKERS, 286 extra = {'trackers': self.TRACKERS,
282 'nothing' : None, 287 'nothing': None,
283 'true' : 1, 288 'true': 1,
284 'false' : 0, 289 'false': 0,
285 } 290 }
286 w(s2b(pt.pt_render(extra_context=extra))) 291 w(s2b(pt.pt_render(extra_context=extra)))
287 else: 292 else:
288 w(s2b(_('<html><head><title>Roundup trackers index</title></head>\n' 293 w(s2b(_('<html><head><title>Roundup trackers index</title></head>\n'
289 '<body><h1>Roundup trackers index</h1><ol>\n'))) 294 '<body><h1>Roundup trackers index</h1><ol>\n')))
290 keys.sort() 295 keys.sort()
291 for tracker in keys: 296 for tracker in keys:
292 w(s2b('<li><a href="%(tracker_url)s/index">%(tracker_name)s</a>\n'%{ 297 w(s2b('<li><a href="%(tracker_url)s/index">%(tracker_name)s</a>\n' % {
293 'tracker_url': urllib_.quote(tracker), 298 'tracker_url': urllib_.quote(tracker),
294 'tracker_name': html_escape(tracker)})) 299 'tracker_name': html_escape(tracker)}))
295 w(b'</ol></body></html>') 300 w(b'</ol></body></html>')
296 301
297 def inner_run_cgi(self): 302 def inner_run_cgi(self):
309 favicon_filepath = os.path.abspath(self.CONFIG['FAVICON']) 314 favicon_filepath = os.path.abspath(self.CONFIG['FAVICON'])
310 315
311 if os.access(favicon_filepath, os.R_OK): 316 if os.access(favicon_filepath, os.R_OK):
312 favicon_fileobj = open(favicon_filepath, 'rb') 317 favicon_fileobj = open(favicon_filepath, 'rb')
313 318
314
315 if favicon_fileobj is None: 319 if favicon_fileobj is None:
316 favicon_fileobj = io.BytesIO(favico) 320 favicon_fileobj = io.BytesIO(favico)
317 321
318 self.send_response(200) 322 self.send_response(200)
319 self.send_header('Content-Type', 'image/x-icon') 323 self.send_header('Content-Type', 'image/x-icon')
320 self.end_headers() 324 self.end_headers()
321 325
322 # this bufsize is completely arbitrary, I picked 4K because it sounded good. 326 # this bufsize is completely arbitrary, I picked 4K because
323 # if someone knows of a better buffer size, feel free to plug it in. 327 # it sounded good. if someone knows of a better buffer size,
328 # feel free to plug it in.
324 bufsize = 4 * 1024 329 bufsize = 4 * 1024
325 Processing = True 330 Processing = True
326 while Processing: 331 while Processing:
327 data = favicon_fileobj.read(bufsize) 332 data = favicon_fileobj.read(bufsize)
328 if len(data) > 0: 333 if len(data) > 0:
354 # handle missing trailing '/' 359 # handle missing trailing '/'
355 if len(l_path) == 2: 360 if len(l_path) == 2:
356 self.send_response(301) 361 self.send_response(301)
357 # redirect - XXX https?? 362 # redirect - XXX https??
358 protocol = 'http' 363 protocol = 'http'
359 url = '%s://%s%s/'%(protocol, self.headers['host'], rest) 364 url = '%s://%s%s/' % (protocol, self.headers['host'], rest)
360 if query: 365 if query:
361 url += '?' + query 366 url += '?' + query
362 self.send_header('Location', url) 367 self.send_header('Location', url)
363 self.end_headers() 368 self.end_headers()
364 self.wfile.write(b'Moved Permanently') 369 self.wfile.write(b'Moved Permanently')
365 return 370 return
366 371
383 elif self.headers.typeheader is None: 388 elif self.headers.typeheader is None:
384 # Python 2. 389 # Python 2.
385 content_type = self.headers.type 390 content_type = self.headers.type
386 else: 391 else:
387 # Python 2. 392 # Python 2.
388 content_type = self.headers.typeheader 393 content_type = self.headers.typeheader
389 if content_type: 394 if content_type:
390 env['CONTENT_TYPE'] = content_type 395 env['CONTENT_TYPE'] = content_type
391 length = self.headers.get('content-length') 396 length = self.headers.get('content-length')
392 if length: 397 if length:
393 env['CONTENT_LENGTH'] = length 398 env['CONTENT_LENGTH'] = length
403 env['HTTP_AUTHORIZATION'] = self.headers.get('authorization') 408 env['HTTP_AUTHORIZATION'] = self.headers.get('authorization')
404 env['SCRIPT_NAME'] = '' 409 env['SCRIPT_NAME'] = ''
405 env['SERVER_NAME'] = self.server.server_name 410 env['SERVER_NAME'] = self.server.server_name
406 env['SERVER_PORT'] = str(self.server.server_port) 411 env['SERVER_PORT'] = str(self.server.server_port)
407 try: 412 try:
408 env['HTTP_HOST'] = self.headers ['host'] 413 env['HTTP_HOST'] = self.headers['host']
409 except KeyError: 414 except KeyError:
410 env['HTTP_HOST'] = '' 415 env['HTTP_HOST'] = ''
411 # https://tools.ietf.org/html/draft-ietf-appsawg-http-forwarded-10 416 # https://tools.ietf.org/html/draft-ietf-appsawg-http-forwarded-10
412 # headers. 417 # headers.
413 xfh = self.headers.get('X-Forwarded-Host', None) 418 xfh = self.headers.get('X-Forwarded-Host', None)
474 logger = logging.getLogger('roundup.http') 479 logger = logging.getLogger('roundup.http')
475 480
476 logger.info("%s - - [%s] %s" % 481 logger.info("%s - - [%s] %s" %
477 (self.client_address[0], 482 (self.client_address[0],
478 self.log_date_time_string(), 483 self.log_date_time_string(),
479 format%args)) 484 format % args))
480 else: 485 else:
481 try: 486 try:
482 http_.server.BaseHTTPRequestHandler.log_message(self, 487 http_.server.BaseHTTPRequestHandler.log_message(self,
483 format, *args) 488 format, *args)
484 except IOError: 489 except IOError:
489 self.send_response(response) 494 self.send_response(response)
490 for key, value in headers: 495 for key, value in headers:
491 self.send_header(key, value) 496 self.send_header(key, value)
492 self.end_headers() 497 self.end_headers()
493 498
499
494 def error(): 500 def error():
495 exc_type, exc_value = sys.exc_info()[:2] 501 exc_type, exc_value = sys.exc_info()[:2]
496 return _('Error: %s: %s' % (exc_type, exc_value)) 502 return _('Error: %s: %s' % (exc_type, exc_value))
503
497 504
498 def setgid(group): 505 def setgid(group):
499 if group is None: 506 if group is None:
500 return 507 return
501 if not hasattr(os, 'setgid'): 508 if not hasattr(os, 'setgid'):
516 except ValueError: 523 except ValueError:
517 gid = grp.getgrnam(group)[2] 524 gid = grp.getgrnam(group)[2]
518 else: 525 else:
519 grp.getgrgid(gid) 526 grp.getgrgid(gid)
520 except KeyError: 527 except KeyError:
521 raise ValueError(_("Group %(group)s doesn't exist")%locals()) 528 raise ValueError(_("Group %(group)s doesn't exist") % locals())
522 os.setgid(gid) 529 os.setgid(gid)
530
523 531
524 def setuid(user): 532 def setuid(user):
525 if not hasattr(os, 'getuid'): 533 if not hasattr(os, 'getuid'):
526 return 534 return
527 535
545 except ValueError: 553 except ValueError:
546 uid = pwd.getpwnam(user)[2] 554 uid = pwd.getpwnam(user)[2]
547 else: 555 else:
548 pwd.getpwuid(uid) 556 pwd.getpwuid(uid)
549 except KeyError: 557 except KeyError:
550 raise ValueError(_("User %(user)s doesn't exist")%locals()) 558 raise ValueError(_("User %(user)s doesn't exist") % locals())
551 os.setuid(uid) 559 os.setuid(uid)
560
552 561
553 class TrackerHomeOption(configuration.FilePathOption): 562 class TrackerHomeOption(configuration.FilePathOption):
554 563
555 # Tracker homes do not need any description strings 564 # Tracker homes do not need any description strings
556 def format(self): 565 def format(self):
557 return "%(name)s = %(value)s\n" % { 566 return "%(name)s = %(value)s\n" % {
558 "name": self.setting, 567 "name": self.setting,
559 "value": self.value2str(self._value), 568 "value": self.value2str(self._value),
560 } 569 }
570
561 571
562 class ServerConfig(configuration.Config): 572 class ServerConfig(configuration.Config):
563 573
564 SETTINGS = ( 574 SETTINGS = (
565 ("main", ( 575 ("main", (
657 for name in config.options("trackers"): 667 for name in config.options("trackers"):
658 if name not in defaults: 668 if name not in defaults:
659 self.add_option(TrackerHomeOption(self, "trackers", name)) 669 self.add_option(TrackerHomeOption(self, "trackers", name))
660 670
661 def getopt(self, args, short_options="", long_options=(), 671 def getopt(self, args, short_options="", long_options=(),
662 config_load_options=("C", "config"), **options 672 config_load_options=("C", "config"), **options):
663 ):
664 options.update(self.OPTIONS) 673 options.update(self.OPTIONS)
665 return configuration.Config.getopt(self, args, 674 return configuration.Config.getopt(self, args,
666 short_options, long_options, config_load_options, **options) 675 short_options, long_options, config_load_options, **options)
667 676
668 def _get_name(self): 677 def _get_name(self):
691 tracker_homes = self.trackers() 700 tracker_homes = self.trackers()
692 if self["MULTIPROCESS"] == "debug": 701 if self["MULTIPROCESS"] == "debug":
693 trackers = None 702 trackers = None
694 else: 703 else:
695 trackers = dict([(name, roundup.instance.open(home, optimize=1)) 704 trackers = dict([(name, roundup.instance.open(home, optimize=1))
696 for (name, home) in tracker_homes]) 705 for (name, home) in tracker_homes])
697 706
698 # build customized request handler class 707 # build customized request handler class
699 class RequestHandler(RoundupRequestHandler): 708 class RequestHandler(RoundupRequestHandler):
700 LOG_IPADDRESS = not self["LOG_HOSTNAMES"] 709 LOG_IPADDRESS = not self["LOG_HOSTNAMES"]
701 TRACKER_HOMES = dict(tracker_homes) 710 TRACKER_HOMES = dict(tracker_homes)
728 base_server = http_.server.HTTPServer 737 base_server = http_.server.HTTPServer
729 738
730 # obtain request server class 739 # obtain request server class
731 if self["MULTIPROCESS"] not in MULTIPROCESS_TYPES: 740 if self["MULTIPROCESS"] not in MULTIPROCESS_TYPES:
732 print(_("Multiprocess mode \"%s\" is not available, " 741 print(_("Multiprocess mode \"%s\" is not available, "
733 "switching to single-process") % self["MULTIPROCESS"]) 742 "switching to single-process") % self["MULTIPROCESS"])
734 self["MULTIPROCESS"] = "none" 743 self["MULTIPROCESS"] = "none"
735 server_class = base_server 744 server_class = base_server
736 elif self["MULTIPROCESS"] == "fork": 745 elif self["MULTIPROCESS"] == "fork":
737 class ForkingServer(socketserver.ForkingMixIn, 746 class ForkingServer(socketserver.ForkingMixIn,
738 base_server): 747 base_server):
739 pass 748 pass
740 server_class = ForkingServer 749 server_class = ForkingServer
741 elif self["MULTIPROCESS"] == "thread": 750 elif self["MULTIPROCESS"] == "thread":
742 class ThreadingServer(socketserver.ThreadingMixIn, 751 class ThreadingServer(socketserver.ThreadingMixIn,
743 base_server): 752 base_server):
744 pass 753 pass
745 server_class = ThreadingServer 754 server_class = ThreadingServer
746 else: 755 else:
747 server_class = base_server 756 server_class = base_server
748 757
749 # obtain server before changing user id - allows to 758 # obtain server before changing user id - allows to
754 if self["SSL"]: 763 if self["SSL"]:
755 kwargs['ssl_pem'] = self["PEM"] 764 kwargs['ssl_pem'] = self["PEM"]
756 httpd = server_class(*args, **kwargs) 765 httpd = server_class(*args, **kwargs)
757 except socket.error as e: 766 except socket.error as e:
758 if e.args[0] == errno.EADDRINUSE: 767 if e.args[0] == errno.EADDRINUSE:
759 raise socket.error(_("Unable to bind to port %s, port already in use.") \ 768 raise socket.error(_("Unable to bind to port %s, "
760 % self["PORT"]) 769 "port already in use.") % self["PORT"])
761 raise 770 raise
762 # change user and/or group 771 # change user and/or group
763 setgid(self["GROUP"]) 772 setgid(self["GROUP"])
764 setuid(self["USER"]) 773 setuid(self["USER"])
765 # return the server 774 # return the server
766 return httpd 775 return httpd
767 776
777
768 try: 778 try:
769 import win32serviceutil 779 import win32serviceutil
770 except: 780 except ImportError:
771 RoundupService = None 781 RoundupService = None
772 else: 782 else:
773 783
774 # allow the win32 784 # allow the win32
775 import win32service 785 import win32service
790 self.ReportServiceStatus(win32service.SERVICE_START_PENDING) 800 self.ReportServiceStatus(win32service.SERVICE_START_PENDING)
791 config = ServerConfig() 801 config = ServerConfig()
792 (optlist, args) = config.getopt(sys.argv[1:]) 802 (optlist, args) = config.getopt(sys.argv[1:])
793 if not config["LOGFILE"]: 803 if not config["LOGFILE"]:
794 servicemanager.LogMsg(servicemanager.EVENTLOG_ERROR_TYPE, 804 servicemanager.LogMsg(servicemanager.EVENTLOG_ERROR_TYPE,
795 servicemanager.PYS_SERVICE_STOPPED, 805 servicemanager.PYS_SERVICE_STOPPED,
796 (self._svc_display_name_, "\r\nMissing logfile option")) 806 (self._svc_display_name_, "\r\nMissing logfile option"))
797 self.ReportServiceStatus(win32service.SERVICE_STOPPED) 807 self.ReportServiceStatus(win32service.SERVICE_STOPPED)
798 return 808 return
799 config.set_logging() 809 config.set_logging()
800 self.server = config.get_server() 810 self.server = config.get_server()
801 self.running = 1 811 self.running = 1
802 self.ReportServiceStatus(win32service.SERVICE_RUNNING) 812 self.ReportServiceStatus(win32service.SERVICE_RUNNING)
803 servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, 813 servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
804 servicemanager.PYS_SERVICE_STARTED, (self._svc_display_name_, 814 servicemanager.PYS_SERVICE_STARTED,
805 " at %s:%s" % (config["HOST"], config["PORT"]))) 815 (self._svc_display_name_,
816 " at %s:%s" % (config["HOST"],
817 config["PORT"])))
806 while self.running: 818 while self.running:
807 self.server.handle_request() 819 self.server.handle_request()
808 servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, 820 servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
809 servicemanager.PYS_SERVICE_STOPPED, 821 servicemanager.PYS_SERVICE_STOPPED,
810 (self._svc_display_name_, "")) 822 (self._svc_display_name_, ""))
811 self.ReportServiceStatus(win32service.SERVICE_STOPPED) 823 self.ReportServiceStatus(win32service.SERVICE_STOPPED)
812 824
813 def SvcStop(self): 825 def SvcStop(self):
814 self.running = 0 826 self.running = 0
815 # make dummy connection to self to terminate blocking accept() 827 # make dummy connection to self to terminate blocking accept()
818 addr = ("127.0.0.1", addr[1]) 830 addr = ("127.0.0.1", addr[1])
819 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 831 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
820 sock.connect(addr) 832 sock.connect(addr)
821 sock.close() 833 sock.close()
822 self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) 834 self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
835
823 836
824 def usage(message=''): 837 def usage(message=''):
825 if RoundupService: 838 if RoundupService:
826 os_part = \ 839 os_part = \
827 ""''' -c <Command> Windows Service options. 840 ""''' -c <Command> Windows Service options.
909 if pid: 922 if pid:
910 pidfile = open(pidfile, 'w') 923 pidfile = open(pidfile, 'w')
911 pidfile.write(str(pid)) 924 pidfile.write(str(pid))
912 pidfile.close() 925 pidfile.close()
913 926
927
914 def daemonize(pidfile): 928 def daemonize(pidfile):
915 ''' Turn this process into a daemon. 929 ''' Turn this process into a daemon.
916 - make sure the sys.std(in|out|err) are completely cut off 930 - make sure the sys.std(in|out|err) are completely cut off
917 - make our parent PID 1 931 - make our parent PID 1
918 932
943 devnull = os.open('/dev/null', 0) 957 devnull = os.open('/dev/null', 0)
944 os.dup2(devnull, 0) 958 os.dup2(devnull, 0)
945 os.dup2(devnull, 1) 959 os.dup2(devnull, 1)
946 os.dup2(devnull, 2) 960 os.dup2(devnull, 2)
947 961
962
948 undefined = [] 963 undefined = []
964
965
949 def run(port=undefined, success_message=None): 966 def run(port=undefined, success_message=None):
950 ''' Script entry point - handle args and figure out what to to. 967 ''' Script entry point - handle args and figure out what to to.
951 ''' 968 '''
952 config = ServerConfig() 969 config = ServerConfig()
953 # additional options 970 # additional options
954 short_options = "hvSc" 971 short_options = "hvSc"
955 try: 972 try:
956 (optlist, args) = config.getopt(sys.argv[1:], 973 (optlist, args) = config.getopt(sys.argv[1:],
957 short_options, ("help", "version", "save-config",)) 974 short_options,
975 ("help", "version", "save-config",))
958 except (getopt.GetoptError, configuration.ConfigurationError) as e: 976 except (getopt.GetoptError, configuration.ConfigurationError) as e:
959 usage(str(e)) 977 usage(str(e))
960 return 978 return
961 979
962 # if running in windows service mode, don't do any other stuff 980 # if running in windows service mode, don't do any other stuff
993 elif opt != "-c": 1011 elif opt != "-c":
994 svc_args.extend(opt) 1012 svc_args.extend(opt)
995 RoundupService._exe_args_ = " ".join(svc_args) 1013 RoundupService._exe_args_ = " ".join(svc_args)
996 # pass the control to serviceutil 1014 # pass the control to serviceutil
997 win32serviceutil.HandleCommandLine(RoundupService, 1015 win32serviceutil.HandleCommandLine(RoundupService,
998 argv=sys.argv[:1] + args) 1016 argv=sys.argv[:1] + args)
999 return 1017 return
1000 1018
1001 # add tracker names from command line. 1019 # add tracker names from command line.
1002 # this is done early to let '--save-config' handle the trackers. 1020 # this is done early to let '--save-config' handle the trackers.
1003 if args: 1021 if args:
1009 config.add_option(TrackerHomeOption(config, "trackers", name)) 1027 config.add_option(TrackerHomeOption(config, "trackers", name))
1010 config["TRACKERS_" + name.upper()] = home 1028 config["TRACKERS_" + name.upper()] = home
1011 1029
1012 # handle remaining options 1030 # handle remaining options
1013 if optlist: 1031 if optlist:
1014 for (opt, arg) in optlist: 1032 for (opt, _arg) in optlist:
1015 if opt in ("-h", "--help"): 1033 if opt in ("-h", "--help"):
1016 usage() 1034 usage()
1017 elif opt in ("-v", "--version"): 1035 elif opt in ("-v", "--version"):
1018 print('%s (python %s)' % (roundup_version, 1036 print('%s (python %s)' % (roundup_version,
1019 sys.version.split()[0])) 1037 sys.version.split()[0]))
1020 elif opt in ("-S", "--save-config"): 1038 elif opt in ("-S", "--save-config"):
1021 config.save() 1039 config.save()
1022 print(_("Configuration saved to %s") % config.filepath) 1040 print(_("Configuration saved to %s") % config.filepath)
1023 # any of the above options prevent server from running 1041 # any of the above options prevent server from running
1024 return 1042 return
1036 1054
1037 # fork the server from our parent if a pidfile is specified 1055 # fork the server from our parent if a pidfile is specified
1038 if config["PIDFILE"]: 1056 if config["PIDFILE"]:
1039 if not hasattr(os, 'fork'): 1057 if not hasattr(os, 'fork'):
1040 print(_("Sorry, you can't run the server as a daemon" 1058 print(_("Sorry, you can't run the server as a daemon"
1041 " on this Operating System")) 1059 " on this Operating System"))
1042 sys.exit(0) 1060 sys.exit(0)
1043 else: 1061 else:
1044 if config['NODAEMON']: 1062 if config['NODAEMON']:
1045 writepidfile(config["PIDFILE"]) 1063 writepidfile(config["PIDFILE"])
1046 else: 1064 else:
1063 try: 1081 try:
1064 httpd.serve_forever() 1082 httpd.serve_forever()
1065 except KeyboardInterrupt: 1083 except KeyboardInterrupt:
1066 print('Keyboard Interrupt: exiting') 1084 print('Keyboard Interrupt: exiting')
1067 1085
1086
1068 if __name__ == '__main__': 1087 if __name__ == '__main__':
1069 run() 1088 run()
1070 1089
1071 # vim: sts=4 sw=4 et si 1090 # vim: sts=4 sw=4 et si

Roundup Issue Tracker: http://roundup-tracker.org/