comparison mercurial/wireprotoserver.py @ 43076:2372284d9457

formatting: blacken the codebase This is using my patch to black (https://github.com/psf/black/pull/826) so we don't un-wrap collection literals. Done with: hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**"' | xargs black -S # skip-blame mass-reformatting only # no-check-commit reformats foo_bar functions Differential Revision: https://phab.mercurial-scm.org/D6971
author Augie Fackler <augie@google.com>
date Sun, 06 Oct 2019 09:45:02 -0400
parents 7e19b640c53e
children 687b865b95ad
comparison
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
19 util, 19 util,
20 wireprototypes, 20 wireprototypes,
21 wireprotov1server, 21 wireprotov1server,
22 wireprotov2server, 22 wireprotov2server,
23 ) 23 )
24 from .interfaces import ( 24 from .interfaces import util as interfaceutil
25 util as interfaceutil,
26 )
27 from .utils import ( 25 from .utils import (
28 cborutil, 26 cborutil,
29 compression, 27 compression,
30 ) 28 )
31 29
40 HGTYPE2 = 'application/mercurial-0.2' 38 HGTYPE2 = 'application/mercurial-0.2'
41 HGERRTYPE = 'application/hg-error' 39 HGERRTYPE = 'application/hg-error'
42 40
43 SSHV1 = wireprototypes.SSHV1 41 SSHV1 = wireprototypes.SSHV1
44 SSHV2 = wireprototypes.SSHV2 42 SSHV2 = wireprototypes.SSHV2
43
45 44
46 def decodevaluefromheaders(req, headerprefix): 45 def decodevaluefromheaders(req, headerprefix):
47 """Decode a long value from multiple HTTP request headers. 46 """Decode a long value from multiple HTTP request headers.
48 47
49 Returns the value as a bytes, not a str. 48 Returns the value as a bytes, not a str.
56 break 55 break
57 chunks.append(pycompat.bytesurl(v)) 56 chunks.append(pycompat.bytesurl(v))
58 i += 1 57 i += 1
59 58
60 return ''.join(chunks) 59 return ''.join(chunks)
60
61 61
62 @interfaceutil.implementer(wireprototypes.baseprotocolhandler) 62 @interfaceutil.implementer(wireprototypes.baseprotocolhandler)
63 class httpv1protocolhandler(object): 63 class httpv1protocolhandler(object):
64 def __init__(self, req, ui, checkperm): 64 def __init__(self, req, ui, checkperm):
65 self._req = req 65 self._req = req
88 88
89 def _args(self): 89 def _args(self):
90 args = self._req.qsparams.asdictoflists() 90 args = self._req.qsparams.asdictoflists()
91 postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0)) 91 postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0))
92 if postlen: 92 if postlen:
93 args.update(urlreq.parseqs( 93 args.update(
94 self._req.bodyfh.read(postlen), keep_blank_values=True)) 94 urlreq.parseqs(
95 self._req.bodyfh.read(postlen), keep_blank_values=True
96 )
97 )
95 return args 98 return args
96 99
97 argvalue = decodevaluefromheaders(self._req, b'X-HgArg') 100 argvalue = decodevaluefromheaders(self._req, b'X-HgArg')
98 args.update(urlreq.parseqs(argvalue, keep_blank_values=True)) 101 args.update(urlreq.parseqs(argvalue, keep_blank_values=True))
99 return args 102 return args
130 133
131 def client(self): 134 def client(self):
132 return 'remote:%s:%s:%s' % ( 135 return 'remote:%s:%s:%s' % (
133 self._req.urlscheme, 136 self._req.urlscheme,
134 urlreq.quote(self._req.remotehost or ''), 137 urlreq.quote(self._req.remotehost or ''),
135 urlreq.quote(self._req.remoteuser or '')) 138 urlreq.quote(self._req.remoteuser or ''),
139 )
136 140
137 def addcapabilities(self, repo, caps): 141 def addcapabilities(self, repo, caps):
138 caps.append(b'batch') 142 caps.append(b'batch')
139 143
140 caps.append('httpheader=%d' % 144 caps.append(
141 repo.ui.configint('server', 'maxhttpheaderlen')) 145 'httpheader=%d' % repo.ui.configint('server', 'maxhttpheaderlen')
146 )
142 if repo.ui.configbool('experimental', 'httppostargs'): 147 if repo.ui.configbool('experimental', 'httppostargs'):
143 caps.append('httppostargs') 148 caps.append('httppostargs')
144 149
145 # FUTURE advertise 0.2rx once support is implemented 150 # FUTURE advertise 0.2rx once support is implemented
146 # FUTURE advertise minrx and mintx after consulting config option 151 # FUTURE advertise minrx and mintx after consulting config option
147 caps.append('httpmediatype=0.1rx,0.1tx,0.2tx') 152 caps.append('httpmediatype=0.1rx,0.1tx,0.2tx')
148 153
149 compengines = wireprototypes.supportedcompengines(repo.ui, 154 compengines = wireprototypes.supportedcompengines(
150 compression.SERVERROLE) 155 repo.ui, compression.SERVERROLE
156 )
151 if compengines: 157 if compengines:
152 comptypes = ','.join(urlreq.quote(e.wireprotosupport().name) 158 comptypes = ','.join(
153 for e in compengines) 159 urlreq.quote(e.wireprotosupport().name) for e in compengines
160 )
154 caps.append('compression=%s' % comptypes) 161 caps.append('compression=%s' % comptypes)
155 162
156 return caps 163 return caps
157 164
158 def checkperm(self, perm): 165 def checkperm(self, perm):
159 return self._checkperm(perm) 166 return self._checkperm(perm)
167
160 168
161 # This method exists mostly so that extensions like remotefilelog can 169 # This method exists mostly so that extensions like remotefilelog can
162 # disable a kludgey legacy method only over http. As of early 2018, 170 # disable a kludgey legacy method only over http. As of early 2018,
163 # there are no other known users, so with any luck we can discard this 171 # there are no other known users, so with any luck we can discard this
164 # hook if remotefilelog becomes a first-party extension. 172 # hook if remotefilelog becomes a first-party extension.
165 def iscmd(cmd): 173 def iscmd(cmd):
166 return cmd in wireprotov1server.commands 174 return cmd in wireprotov1server.commands
175
167 176
168 def handlewsgirequest(rctx, req, res, checkperm): 177 def handlewsgirequest(rctx, req, res, checkperm):
169 """Possibly process a wire protocol request. 178 """Possibly process a wire protocol request.
170 179
171 If the current request is a wire protocol request, the request is 180 If the current request is a wire protocol request, the request is
210 # TODO This is not a good response to issue for this request. This 219 # TODO This is not a good response to issue for this request. This
211 # is mostly for BC for now. 220 # is mostly for BC for now.
212 res.setbodybytes('0\n%s\n' % b'Not Found') 221 res.setbodybytes('0\n%s\n' % b'Not Found')
213 return True 222 return True
214 223
215 proto = httpv1protocolhandler(req, repo.ui, 224 proto = httpv1protocolhandler(
216 lambda perm: checkperm(rctx, req, perm)) 225 req, repo.ui, lambda perm: checkperm(rctx, req, perm)
226 )
217 227
218 # The permissions checker should be the only thing that can raise an 228 # The permissions checker should be the only thing that can raise an
219 # ErrorResponse. It is kind of a layer violation to catch an hgweb 229 # ErrorResponse. It is kind of a layer violation to catch an hgweb
220 # exception here. So consider refactoring into a exception type that 230 # exception here. So consider refactoring into a exception type that
221 # is associated with the wire protocol. 231 # is associated with the wire protocol.
229 # "unbundle." That assumption is not always valid. 239 # "unbundle." That assumption is not always valid.
230 res.setbodybytes('0\n%s\n' % pycompat.bytestr(e)) 240 res.setbodybytes('0\n%s\n' % pycompat.bytestr(e))
231 241
232 return True 242 return True
233 243
244
234 def _availableapis(repo): 245 def _availableapis(repo):
235 apis = set() 246 apis = set()
236 247
237 # Registered APIs are made available via config options of the name of 248 # Registered APIs are made available via config options of the name of
238 # the protocol. 249 # the protocol.
240 section, option = v['config'] 251 section, option = v['config']
241 if repo.ui.configbool(section, option): 252 if repo.ui.configbool(section, option):
242 apis.add(k) 253 apis.add(k)
243 254
244 return apis 255 return apis
256
245 257
246 def handlewsgiapirequest(rctx, req, res, checkperm): 258 def handlewsgiapirequest(rctx, req, res, checkperm):
247 """Handle requests to /api/*.""" 259 """Handle requests to /api/*."""
248 assert req.dispatchparts[0] == b'api' 260 assert req.dispatchparts[0] == b'api'
249 261
264 276
265 # Requests to /api/ list available APIs. 277 # Requests to /api/ list available APIs.
266 if req.dispatchparts == [b'api']: 278 if req.dispatchparts == [b'api']:
267 res.status = b'200 OK' 279 res.status = b'200 OK'
268 res.headers[b'Content-Type'] = b'text/plain' 280 res.headers[b'Content-Type'] = b'text/plain'
269 lines = [_('APIs can be accessed at /api/<name>, where <name> can be ' 281 lines = [
270 'one of the following:\n')] 282 _(
283 'APIs can be accessed at /api/<name>, where <name> can be '
284 'one of the following:\n'
285 )
286 ]
271 if availableapis: 287 if availableapis:
272 lines.extend(sorted(availableapis)) 288 lines.extend(sorted(availableapis))
273 else: 289 else:
274 lines.append(_('(no available APIs)\n')) 290 lines.append(_('(no available APIs)\n'))
275 res.setbodybytes(b'\n'.join(lines)) 291 res.setbodybytes(b'\n'.join(lines))
278 proto = req.dispatchparts[1] 294 proto = req.dispatchparts[1]
279 295
280 if proto not in API_HANDLERS: 296 if proto not in API_HANDLERS:
281 res.status = b'404 Not Found' 297 res.status = b'404 Not Found'
282 res.headers[b'Content-Type'] = b'text/plain' 298 res.headers[b'Content-Type'] = b'text/plain'
283 res.setbodybytes(_('Unknown API: %s\nKnown APIs: %s') % ( 299 res.setbodybytes(
284 proto, b', '.join(sorted(availableapis)))) 300 _('Unknown API: %s\nKnown APIs: %s')
301 % (proto, b', '.join(sorted(availableapis)))
302 )
285 return 303 return
286 304
287 if proto not in availableapis: 305 if proto not in availableapis:
288 res.status = b'404 Not Found' 306 res.status = b'404 Not Found'
289 res.headers[b'Content-Type'] = b'text/plain' 307 res.headers[b'Content-Type'] = b'text/plain'
290 res.setbodybytes(_('API %s not enabled\n') % proto) 308 res.setbodybytes(_('API %s not enabled\n') % proto)
291 return 309 return
292 310
293 API_HANDLERS[proto]['handler'](rctx, req, res, checkperm, 311 API_HANDLERS[proto]['handler'](
294 req.dispatchparts[2:]) 312 rctx, req, res, checkperm, req.dispatchparts[2:]
313 )
314
295 315
296 # Maps API name to metadata so custom API can be registered. 316 # Maps API name to metadata so custom API can be registered.
297 # Keys are: 317 # Keys are:
298 # 318 #
299 # config 319 # config
310 'handler': wireprotov2server.handlehttpv2request, 330 'handler': wireprotov2server.handlehttpv2request,
311 'apidescriptor': wireprotov2server.httpv2apidescriptor, 331 'apidescriptor': wireprotov2server.httpv2apidescriptor,
312 }, 332 },
313 } 333 }
314 334
335
315 def _httpresponsetype(ui, proto, prefer_uncompressed): 336 def _httpresponsetype(ui, proto, prefer_uncompressed):
316 """Determine the appropriate response type and compression settings. 337 """Determine the appropriate response type and compression settings.
317 338
318 Returns a tuple of (mediatype, compengine, engineopts). 339 Returns a tuple of (mediatype, compengine, engineopts).
319 """ 340 """
325 if prefer_uncompressed: 346 if prefer_uncompressed:
326 return HGTYPE2, compression._noopengine(), {} 347 return HGTYPE2, compression._noopengine(), {}
327 348
328 # Now find an agreed upon compression format. 349 # Now find an agreed upon compression format.
329 compformats = wireprotov1server.clientcompressionsupport(proto) 350 compformats = wireprotov1server.clientcompressionsupport(proto)
330 for engine in wireprototypes.supportedcompengines(ui, 351 for engine in wireprototypes.supportedcompengines(
331 compression.SERVERROLE): 352 ui, compression.SERVERROLE
353 ):
332 if engine.wireprotosupport().name in compformats: 354 if engine.wireprotosupport().name in compformats:
333 opts = {} 355 opts = {}
334 level = ui.configint('server', '%slevel' % engine.name()) 356 level = ui.configint('server', '%slevel' % engine.name())
335 if level is not None: 357 if level is not None:
336 opts['level'] = level 358 opts['level'] = level
343 # Don't allow untrusted settings because disabling compression or 365 # Don't allow untrusted settings because disabling compression or
344 # setting a very high compression level could lead to flooding 366 # setting a very high compression level could lead to flooding
345 # the server's network or CPU. 367 # the server's network or CPU.
346 opts = {'level': ui.configint('server', 'zliblevel')} 368 opts = {'level': ui.configint('server', 'zliblevel')}
347 return HGTYPE, util.compengines['zlib'], opts 369 return HGTYPE, util.compengines['zlib'], opts
370
348 371
349 def processcapabilitieshandshake(repo, req, res, proto): 372 def processcapabilitieshandshake(repo, req, res, proto):
350 """Called during a ?cmd=capabilities request. 373 """Called during a ?cmd=capabilities request.
351 374
352 If the client is advertising support for a newer protocol, we send 375 If the client is advertising support for a newer protocol, we send
392 res.headers[b'Content-Type'] = b'application/mercurial-cbor' 415 res.headers[b'Content-Type'] = b'application/mercurial-cbor'
393 res.setbodybytes(b''.join(cborutil.streamencode(m))) 416 res.setbodybytes(b''.join(cborutil.streamencode(m)))
394 417
395 return True 418 return True
396 419
420
397 def _callhttp(repo, req, res, proto, cmd): 421 def _callhttp(repo, req, res, proto, cmd):
398 # Avoid cycle involving hg module. 422 # Avoid cycle involving hg module.
399 from .hgweb import common as hgwebcommon 423 from .hgweb import common as hgwebcommon
400 424
401 def genversion2(gen, engine, engineopts): 425 def genversion2(gen, engine, engineopts):
421 res.setbodybytes(bodybytes) 445 res.setbodybytes(bodybytes)
422 if bodygen is not None: 446 if bodygen is not None:
423 res.setbodygen(bodygen) 447 res.setbodygen(bodygen)
424 448
425 if not wireprotov1server.commands.commandavailable(cmd, proto): 449 if not wireprotov1server.commands.commandavailable(cmd, proto):
426 setresponse(HTTP_OK, HGERRTYPE, 450 setresponse(
427 _('requested wire protocol command is not available over ' 451 HTTP_OK,
428 'HTTP')) 452 HGERRTYPE,
453 _('requested wire protocol command is not available over ' 'HTTP'),
454 )
429 return 455 return
430 456
431 proto.checkperm(wireprotov1server.commands[cmd].permission) 457 proto.checkperm(wireprotov1server.commands[cmd].permission)
432 458
433 # Possibly handle a modern client wanting to switch protocols. 459 # Possibly handle a modern client wanting to switch protocols.
434 if (cmd == 'capabilities' and 460 if cmd == 'capabilities' and processcapabilitieshandshake(
435 processcapabilitieshandshake(repo, req, res, proto)): 461 repo, req, res, proto
462 ):
436 463
437 return 464 return
438 465
439 rsp = wireprotov1server.dispatch(repo, proto, cmd) 466 rsp = wireprotov1server.dispatch(repo, proto, cmd)
440 467
448 gen = rsp.gen 475 gen = rsp.gen
449 476
450 # This code for compression should not be streamres specific. It 477 # This code for compression should not be streamres specific. It
451 # is here because we only compress streamres at the moment. 478 # is here because we only compress streamres at the moment.
452 mediatype, engine, engineopts = _httpresponsetype( 479 mediatype, engine, engineopts = _httpresponsetype(
453 repo.ui, proto, rsp.prefer_uncompressed) 480 repo.ui, proto, rsp.prefer_uncompressed
481 )
454 gen = engine.compressstream(gen, engineopts) 482 gen = engine.compressstream(gen, engineopts)
455 483
456 if mediatype == HGTYPE2: 484 if mediatype == HGTYPE2:
457 gen = genversion2(gen, engine, engineopts) 485 gen = genversion2(gen, engine, engineopts)
458 486
467 elif isinstance(rsp, wireprototypes.ooberror): 495 elif isinstance(rsp, wireprototypes.ooberror):
468 setresponse(HTTP_OK, HGERRTYPE, bodybytes=rsp.message) 496 setresponse(HTTP_OK, HGERRTYPE, bodybytes=rsp.message)
469 else: 497 else:
470 raise error.ProgrammingError('hgweb.protocol internal failure', rsp) 498 raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
471 499
500
472 def _sshv1respondbytes(fout, value): 501 def _sshv1respondbytes(fout, value):
473 """Send a bytes response for protocol version 1.""" 502 """Send a bytes response for protocol version 1."""
474 fout.write('%d\n' % len(value)) 503 fout.write('%d\n' % len(value))
475 fout.write(value) 504 fout.write(value)
476 fout.flush() 505 fout.flush()
477 506
507
478 def _sshv1respondstream(fout, source): 508 def _sshv1respondstream(fout, source):
479 write = fout.write 509 write = fout.write
480 for chunk in source.gen: 510 for chunk in source.gen:
481 write(chunk) 511 write(chunk)
482 fout.flush() 512 fout.flush()
483 513
514
484 def _sshv1respondooberror(fout, ferr, rsp): 515 def _sshv1respondooberror(fout, ferr, rsp):
485 ferr.write(b'%s\n-\n' % rsp) 516 ferr.write(b'%s\n-\n' % rsp)
486 ferr.flush() 517 ferr.flush()
487 fout.write(b'\n') 518 fout.write(b'\n')
488 fout.flush() 519 fout.flush()
489 520
521
490 @interfaceutil.implementer(wireprototypes.baseprotocolhandler) 522 @interfaceutil.implementer(wireprototypes.baseprotocolhandler)
491 class sshv1protocolhandler(object): 523 class sshv1protocolhandler(object):
492 """Handler for requests services via version 1 of SSH protocol.""" 524 """Handler for requests services via version 1 of SSH protocol."""
525
493 def __init__(self, ui, fin, fout): 526 def __init__(self, ui, fin, fout):
494 self._ui = ui 527 self._ui = ui
495 self._fin = fin 528 self._fin = fin
496 self._fout = fout 529 self._fout = fout
497 self._protocaps = set() 530 self._protocaps = set()
555 return caps 588 return caps
556 589
557 def checkperm(self, perm): 590 def checkperm(self, perm):
558 pass 591 pass
559 592
593
560 class sshv2protocolhandler(sshv1protocolhandler): 594 class sshv2protocolhandler(sshv1protocolhandler):
561 """Protocol handler for version 2 of the SSH protocol.""" 595 """Protocol handler for version 2 of the SSH protocol."""
562 596
563 @property 597 @property
564 def name(self): 598 def name(self):
565 return wireprototypes.SSHV2 599 return wireprototypes.SSHV2
566 600
567 def addcapabilities(self, repo, caps): 601 def addcapabilities(self, repo, caps):
568 return caps 602 return caps
603
569 604
570 def _runsshserver(ui, repo, fin, fout, ev): 605 def _runsshserver(ui, repo, fin, fout, ev):
571 # This function operates like a state machine of sorts. The following 606 # This function operates like a state machine of sorts. The following
572 # states are defined: 607 # states are defined:
573 # 608 #
636 671
637 # It looks like a protocol upgrade request. Transition state to 672 # It looks like a protocol upgrade request. Transition state to
638 # handle it. 673 # handle it.
639 if request.startswith(b'upgrade '): 674 if request.startswith(b'upgrade '):
640 if protoswitched: 675 if protoswitched:
641 _sshv1respondooberror(fout, ui.ferr, 676 _sshv1respondooberror(
642 b'cannot upgrade protocols multiple ' 677 fout,
643 b'times') 678 ui.ferr,
679 b'cannot upgrade protocols multiple ' b'times',
680 )
644 state = 'shutdown' 681 state = 'shutdown'
645 continue 682 continue
646 683
647 state = 'upgrade-initial' 684 state = 'upgrade-initial'
648 continue 685 continue
649 686
650 available = wireprotov1server.commands.commandavailable( 687 available = wireprotov1server.commands.commandavailable(
651 request, proto) 688 request, proto
689 )
652 690
653 # This command isn't available. Send an empty response and go 691 # This command isn't available. Send an empty response and go
654 # back to waiting for a new command. 692 # back to waiting for a new command.
655 if not available: 693 if not available:
656 _sshv1respondbytes(fout, b'') 694 _sshv1respondbytes(fout, b'')
674 elif isinstance(rsp, wireprototypes.pusherr): 712 elif isinstance(rsp, wireprototypes.pusherr):
675 _sshv1respondbytes(fout, rsp.res) 713 _sshv1respondbytes(fout, rsp.res)
676 elif isinstance(rsp, wireprototypes.ooberror): 714 elif isinstance(rsp, wireprototypes.ooberror):
677 _sshv1respondooberror(fout, ui.ferr, rsp.message) 715 _sshv1respondooberror(fout, ui.ferr, rsp.message)
678 else: 716 else:
679 raise error.ProgrammingError('unhandled response type from ' 717 raise error.ProgrammingError(
680 'wire protocol command: %s' % rsp) 718 'unhandled response type from '
719 'wire protocol command: %s' % rsp
720 )
681 721
682 # For now, protocol version 2 serving just goes back to version 1. 722 # For now, protocol version 2 serving just goes back to version 1.
683 elif state == 'protov2-serving': 723 elif state == 'protov2-serving':
684 state = 'protov1-serving' 724 state = 'protov1-serving'
685 continue 725 continue
739 ok = True 779 ok = True
740 for line in (b'hello', b'between', b'pairs 81'): 780 for line in (b'hello', b'between', b'pairs 81'):
741 request = fin.readline()[:-1] 781 request = fin.readline()[:-1]
742 782
743 if request != line: 783 if request != line:
744 _sshv1respondooberror(fout, ui.ferr, 784 _sshv1respondooberror(
745 b'malformed handshake protocol: ' 785 fout,
746 b'missing %s' % line) 786 ui.ferr,
787 b'malformed handshake protocol: ' b'missing %s' % line,
788 )
747 ok = False 789 ok = False
748 state = 'shutdown' 790 state = 'shutdown'
749 break 791 break
750 792
751 if not ok: 793 if not ok:
752 continue 794 continue
753 795
754 request = fin.read(81) 796 request = fin.read(81)
755 if request != b'%s-%s' % (b'0' * 40, b'0' * 40): 797 if request != b'%s-%s' % (b'0' * 40, b'0' * 40):
756 _sshv1respondooberror(fout, ui.ferr, 798 _sshv1respondooberror(
757 b'malformed handshake protocol: ' 799 fout,
758 b'missing between argument value') 800 ui.ferr,
801 b'malformed handshake protocol: '
802 b'missing between argument value',
803 )
759 state = 'shutdown' 804 state = 'shutdown'
760 continue 805 continue
761 806
762 state = 'upgrade-v2-finish' 807 state = 'upgrade-v2-finish'
763 continue 808 continue
778 823
779 elif state == 'shutdown': 824 elif state == 'shutdown':
780 break 825 break
781 826
782 else: 827 else:
783 raise error.ProgrammingError('unhandled ssh server state: %s' % 828 raise error.ProgrammingError(
784 state) 829 'unhandled ssh server state: %s' % state
830 )
831
785 832
786 class sshserver(object): 833 class sshserver(object):
787 def __init__(self, ui, repo, logfh=None): 834 def __init__(self, ui, repo, logfh=None):
788 self._ui = ui 835 self._ui = ui
789 self._repo = repo 836 self._repo = repo
790 self._fin, self._fout = ui.protectfinout() 837 self._fin, self._fout = ui.protectfinout()
791 838
792 # Log write I/O to stdout and stderr if configured. 839 # Log write I/O to stdout and stderr if configured.
793 if logfh: 840 if logfh:
794 self._fout = util.makeloggingfileobject( 841 self._fout = util.makeloggingfileobject(
795 logfh, self._fout, 'o', logdata=True) 842 logfh, self._fout, 'o', logdata=True
843 )
796 ui.ferr = util.makeloggingfileobject( 844 ui.ferr = util.makeloggingfileobject(
797 logfh, ui.ferr, 'e', logdata=True) 845 logfh, ui.ferr, 'e', logdata=True
846 )
798 847
799 def serve_forever(self): 848 def serve_forever(self):
800 self.serveuntil(threading.Event()) 849 self.serveuntil(threading.Event())
801 self._ui.restorefinout(self._fin, self._fout) 850 self._ui.restorefinout(self._fin, self._fout)
802 sys.exit(0) 851 sys.exit(0)