##// END OF EJS Templates
chgserver: add fastannotate config section to sensitive list...
Pulkit Goyal -
r45102:bc592059 default
parent child Browse files
Show More
@@ -1,715 +1,716 b''
1 1 # chgserver.py - command server extension for cHg
2 2 #
3 3 # Copyright 2011 Yuya Nishihara <yuya@tcha.org>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 """command server extension for cHg
9 9
10 10 'S' channel (read/write)
11 11 propagate ui.system() request to client
12 12
13 13 'attachio' command
14 14 attach client's stdio passed by sendmsg()
15 15
16 16 'chdir' command
17 17 change current directory
18 18
19 19 'setenv' command
20 20 replace os.environ completely
21 21
22 22 'setumask' command (DEPRECATED)
23 23 'setumask2' command
24 24 set umask
25 25
26 26 'validate' command
27 27 reload the config and check if the server is up to date
28 28
29 29 Config
30 30 ------
31 31
32 32 ::
33 33
34 34 [chgserver]
35 35 # how long (in seconds) should an idle chg server exit
36 36 idletimeout = 3600
37 37
38 38 # whether to skip config or env change checks
39 39 skiphash = False
40 40 """
41 41
42 42 from __future__ import absolute_import
43 43
44 44 import inspect
45 45 import os
46 46 import re
47 47 import socket
48 48 import stat
49 49 import struct
50 50 import time
51 51
52 52 from .i18n import _
53 53 from .pycompat import (
54 54 getattr,
55 55 setattr,
56 56 )
57 57
58 58 from . import (
59 59 commandserver,
60 60 encoding,
61 61 error,
62 62 extensions,
63 63 node,
64 64 pycompat,
65 65 util,
66 66 )
67 67
68 68 from .utils import (
69 69 hashutil,
70 70 procutil,
71 71 stringutil,
72 72 )
73 73
74 74
75 75 def _hashlist(items):
76 76 """return sha1 hexdigest for a list"""
77 77 return node.hex(hashutil.sha1(stringutil.pprint(items)).digest())
78 78
79 79
80 80 # sensitive config sections affecting confighash
81 81 _configsections = [
82 82 b'alias', # affects global state commands.table
83 83 b'eol', # uses setconfig('eol', ...)
84 84 b'extdiff', # uisetup will register new commands
85 85 b'extensions',
86 b'fastannotate', # affects annotate command and adds fastannonate cmd
86 87 b'schemes', # extsetup will update global hg.schemes
87 88 ]
88 89
89 90 _configsectionitems = [
90 91 (b'commands', b'show.aliasprefix'), # show.py reads it in extsetup
91 92 ]
92 93
93 94 # sensitive environment variables affecting confighash
94 95 _envre = re.compile(
95 96 br'''\A(?:
96 97 CHGHG
97 98 |HG(?:DEMANDIMPORT|EMITWARNINGS|MODULEPOLICY|PROF|RCPATH)?
98 99 |HG(?:ENCODING|PLAIN).*
99 100 |LANG(?:UAGE)?
100 101 |LC_.*
101 102 |LD_.*
102 103 |PATH
103 104 |PYTHON.*
104 105 |TERM(?:INFO)?
105 106 |TZ
106 107 )\Z''',
107 108 re.X,
108 109 )
109 110
110 111
111 112 def _confighash(ui):
112 113 """return a quick hash for detecting config/env changes
113 114
114 115 confighash is the hash of sensitive config items and environment variables.
115 116
116 117 for chgserver, it is designed that once confighash changes, the server is
117 118 not qualified to serve its client and should redirect the client to a new
118 119 server. different from mtimehash, confighash change will not mark the
119 120 server outdated and exit since the user can have different configs at the
120 121 same time.
121 122 """
122 123 sectionitems = []
123 124 for section in _configsections:
124 125 sectionitems.append(ui.configitems(section))
125 126 for section, item in _configsectionitems:
126 127 sectionitems.append(ui.config(section, item))
127 128 sectionhash = _hashlist(sectionitems)
128 129 # If $CHGHG is set, the change to $HG should not trigger a new chg server
129 130 if b'CHGHG' in encoding.environ:
130 131 ignored = {b'HG'}
131 132 else:
132 133 ignored = set()
133 134 envitems = [
134 135 (k, v)
135 136 for k, v in pycompat.iteritems(encoding.environ)
136 137 if _envre.match(k) and k not in ignored
137 138 ]
138 139 envhash = _hashlist(sorted(envitems))
139 140 return sectionhash[:6] + envhash[:6]
140 141
141 142
142 143 def _getmtimepaths(ui):
143 144 """get a list of paths that should be checked to detect change
144 145
145 146 The list will include:
146 147 - extensions (will not cover all files for complex extensions)
147 148 - mercurial/__version__.py
148 149 - python binary
149 150 """
150 151 modules = [m for n, m in extensions.extensions(ui)]
151 152 try:
152 153 from . import __version__
153 154
154 155 modules.append(__version__)
155 156 except ImportError:
156 157 pass
157 158 files = []
158 159 if pycompat.sysexecutable:
159 160 files.append(pycompat.sysexecutable)
160 161 for m in modules:
161 162 try:
162 163 files.append(pycompat.fsencode(inspect.getabsfile(m)))
163 164 except TypeError:
164 165 pass
165 166 return sorted(set(files))
166 167
167 168
168 169 def _mtimehash(paths):
169 170 """return a quick hash for detecting file changes
170 171
171 172 mtimehash calls stat on given paths and calculate a hash based on size and
172 173 mtime of each file. mtimehash does not read file content because reading is
173 174 expensive. therefore it's not 100% reliable for detecting content changes.
174 175 it's possible to return different hashes for same file contents.
175 176 it's also possible to return a same hash for different file contents for
176 177 some carefully crafted situation.
177 178
178 179 for chgserver, it is designed that once mtimehash changes, the server is
179 180 considered outdated immediately and should no longer provide service.
180 181
181 182 mtimehash is not included in confighash because we only know the paths of
182 183 extensions after importing them (there is imp.find_module but that faces
183 184 race conditions). We need to calculate confighash without importing.
184 185 """
185 186
186 187 def trystat(path):
187 188 try:
188 189 st = os.stat(path)
189 190 return (st[stat.ST_MTIME], st.st_size)
190 191 except OSError:
191 192 # could be ENOENT, EPERM etc. not fatal in any case
192 193 pass
193 194
194 195 return _hashlist(pycompat.maplist(trystat, paths))[:12]
195 196
196 197
197 198 class hashstate(object):
198 199 """a structure storing confighash, mtimehash, paths used for mtimehash"""
199 200
200 201 def __init__(self, confighash, mtimehash, mtimepaths):
201 202 self.confighash = confighash
202 203 self.mtimehash = mtimehash
203 204 self.mtimepaths = mtimepaths
204 205
205 206 @staticmethod
206 207 def fromui(ui, mtimepaths=None):
207 208 if mtimepaths is None:
208 209 mtimepaths = _getmtimepaths(ui)
209 210 confighash = _confighash(ui)
210 211 mtimehash = _mtimehash(mtimepaths)
211 212 ui.log(
212 213 b'cmdserver',
213 214 b'confighash = %s mtimehash = %s\n',
214 215 confighash,
215 216 mtimehash,
216 217 )
217 218 return hashstate(confighash, mtimehash, mtimepaths)
218 219
219 220
220 221 def _newchgui(srcui, csystem, attachio):
221 222 class chgui(srcui.__class__):
222 223 def __init__(self, src=None):
223 224 super(chgui, self).__init__(src)
224 225 if src:
225 226 self._csystem = getattr(src, '_csystem', csystem)
226 227 else:
227 228 self._csystem = csystem
228 229
229 230 def _runsystem(self, cmd, environ, cwd, out):
230 231 # fallback to the original system method if
231 232 # a. the output stream is not stdout (e.g. stderr, cStringIO),
232 233 # b. or stdout is redirected by protectfinout(),
233 234 # because the chg client is not aware of these situations and
234 235 # will behave differently (i.e. write to stdout).
235 236 if (
236 237 out is not self.fout
237 238 or not util.safehasattr(self.fout, b'fileno')
238 239 or self.fout.fileno() != procutil.stdout.fileno()
239 240 or self._finoutredirected
240 241 ):
241 242 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
242 243 self.flush()
243 244 return self._csystem(cmd, procutil.shellenviron(environ), cwd)
244 245
245 246 def _runpager(self, cmd, env=None):
246 247 self._csystem(
247 248 cmd,
248 249 procutil.shellenviron(env),
249 250 type=b'pager',
250 251 cmdtable={b'attachio': attachio},
251 252 )
252 253 return True
253 254
254 255 return chgui(srcui)
255 256
256 257
257 258 def _loadnewui(srcui, args, cdebug):
258 259 from . import dispatch # avoid cycle
259 260
260 261 newui = srcui.__class__.load()
261 262 for a in [b'fin', b'fout', b'ferr', b'environ']:
262 263 setattr(newui, a, getattr(srcui, a))
263 264 if util.safehasattr(srcui, b'_csystem'):
264 265 newui._csystem = srcui._csystem
265 266
266 267 # command line args
267 268 options = dispatch._earlyparseopts(newui, args)
268 269 dispatch._parseconfig(newui, options[b'config'])
269 270
270 271 # stolen from tortoisehg.util.copydynamicconfig()
271 272 for section, name, value in srcui.walkconfig():
272 273 source = srcui.configsource(section, name)
273 274 if b':' in source or source == b'--config' or source.startswith(b'$'):
274 275 # path:line or command line, or environ
275 276 continue
276 277 newui.setconfig(section, name, value, source)
277 278
278 279 # load wd and repo config, copied from dispatch.py
279 280 cwd = options[b'cwd']
280 281 cwd = cwd and os.path.realpath(cwd) or None
281 282 rpath = options[b'repository']
282 283 path, newlui = dispatch._getlocal(newui, rpath, wd=cwd)
283 284
284 285 extensions.populateui(newui)
285 286 commandserver.setuplogging(newui, fp=cdebug)
286 287 if newui is not newlui:
287 288 extensions.populateui(newlui)
288 289 commandserver.setuplogging(newlui, fp=cdebug)
289 290
290 291 return (newui, newlui)
291 292
292 293
293 294 class channeledsystem(object):
294 295 """Propagate ui.system() request in the following format:
295 296
296 297 payload length (unsigned int),
297 298 type, '\0',
298 299 cmd, '\0',
299 300 cwd, '\0',
300 301 envkey, '=', val, '\0',
301 302 ...
302 303 envkey, '=', val
303 304
304 305 if type == 'system', waits for:
305 306
306 307 exitcode length (unsigned int),
307 308 exitcode (int)
308 309
309 310 if type == 'pager', repetitively waits for a command name ending with '\n'
310 311 and executes it defined by cmdtable, or exits the loop if the command name
311 312 is empty.
312 313 """
313 314
314 315 def __init__(self, in_, out, channel):
315 316 self.in_ = in_
316 317 self.out = out
317 318 self.channel = channel
318 319
319 320 def __call__(self, cmd, environ, cwd=None, type=b'system', cmdtable=None):
320 321 args = [type, procutil.quotecommand(cmd), os.path.abspath(cwd or b'.')]
321 322 args.extend(b'%s=%s' % (k, v) for k, v in pycompat.iteritems(environ))
322 323 data = b'\0'.join(args)
323 324 self.out.write(struct.pack(b'>cI', self.channel, len(data)))
324 325 self.out.write(data)
325 326 self.out.flush()
326 327
327 328 if type == b'system':
328 329 length = self.in_.read(4)
329 330 (length,) = struct.unpack(b'>I', length)
330 331 if length != 4:
331 332 raise error.Abort(_(b'invalid response'))
332 333 (rc,) = struct.unpack(b'>i', self.in_.read(4))
333 334 return rc
334 335 elif type == b'pager':
335 336 while True:
336 337 cmd = self.in_.readline()[:-1]
337 338 if not cmd:
338 339 break
339 340 if cmdtable and cmd in cmdtable:
340 341 cmdtable[cmd]()
341 342 else:
342 343 raise error.Abort(_(b'unexpected command: %s') % cmd)
343 344 else:
344 345 raise error.ProgrammingError(b'invalid S channel type: %s' % type)
345 346
346 347
347 348 _iochannels = [
348 349 # server.ch, ui.fp, mode
349 350 (b'cin', b'fin', 'rb'),
350 351 (b'cout', b'fout', 'wb'),
351 352 (b'cerr', b'ferr', 'wb'),
352 353 ]
353 354
354 355
355 356 class chgcmdserver(commandserver.server):
356 357 def __init__(
357 358 self, ui, repo, fin, fout, sock, prereposetups, hashstate, baseaddress
358 359 ):
359 360 super(chgcmdserver, self).__init__(
360 361 _newchgui(ui, channeledsystem(fin, fout, b'S'), self.attachio),
361 362 repo,
362 363 fin,
363 364 fout,
364 365 prereposetups,
365 366 )
366 367 self.clientsock = sock
367 368 self._ioattached = False
368 369 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio"
369 370 self.hashstate = hashstate
370 371 self.baseaddress = baseaddress
371 372 if hashstate is not None:
372 373 self.capabilities = self.capabilities.copy()
373 374 self.capabilities[b'validate'] = chgcmdserver.validate
374 375
375 376 def cleanup(self):
376 377 super(chgcmdserver, self).cleanup()
377 378 # dispatch._runcatch() does not flush outputs if exception is not
378 379 # handled by dispatch._dispatch()
379 380 self.ui.flush()
380 381 self._restoreio()
381 382 self._ioattached = False
382 383
383 384 def attachio(self):
384 385 """Attach to client's stdio passed via unix domain socket; all
385 386 channels except cresult will no longer be used
386 387 """
387 388 # tell client to sendmsg() with 1-byte payload, which makes it
388 389 # distinctive from "attachio\n" command consumed by client.read()
389 390 self.clientsock.sendall(struct.pack(b'>cI', b'I', 1))
390 391 clientfds = util.recvfds(self.clientsock.fileno())
391 392 self.ui.log(b'chgserver', b'received fds: %r\n', clientfds)
392 393
393 394 ui = self.ui
394 395 ui.flush()
395 396 self._saveio()
396 397 for fd, (cn, fn, mode) in zip(clientfds, _iochannels):
397 398 assert fd > 0
398 399 fp = getattr(ui, fn)
399 400 os.dup2(fd, fp.fileno())
400 401 os.close(fd)
401 402 if self._ioattached:
402 403 continue
403 404 # reset buffering mode when client is first attached. as we want
404 405 # to see output immediately on pager, the mode stays unchanged
405 406 # when client re-attached. ferr is unchanged because it should
406 407 # be unbuffered no matter if it is a tty or not.
407 408 if fn == b'ferr':
408 409 newfp = fp
409 410 else:
410 411 # make it line buffered explicitly because the default is
411 412 # decided on first write(), where fout could be a pager.
412 413 if fp.isatty():
413 414 bufsize = 1 # line buffered
414 415 else:
415 416 bufsize = -1 # system default
416 417 newfp = os.fdopen(fp.fileno(), mode, bufsize)
417 418 setattr(ui, fn, newfp)
418 419 setattr(self, cn, newfp)
419 420
420 421 self._ioattached = True
421 422 self.cresult.write(struct.pack(b'>i', len(clientfds)))
422 423
423 424 def _saveio(self):
424 425 if self._oldios:
425 426 return
426 427 ui = self.ui
427 428 for cn, fn, _mode in _iochannels:
428 429 ch = getattr(self, cn)
429 430 fp = getattr(ui, fn)
430 431 fd = os.dup(fp.fileno())
431 432 self._oldios.append((ch, fp, fd))
432 433
433 434 def _restoreio(self):
434 435 ui = self.ui
435 436 for (ch, fp, fd), (cn, fn, _mode) in zip(self._oldios, _iochannels):
436 437 newfp = getattr(ui, fn)
437 438 # close newfp while it's associated with client; otherwise it
438 439 # would be closed when newfp is deleted
439 440 if newfp is not fp:
440 441 newfp.close()
441 442 # restore original fd: fp is open again
442 443 os.dup2(fd, fp.fileno())
443 444 os.close(fd)
444 445 setattr(self, cn, ch)
445 446 setattr(ui, fn, fp)
446 447 del self._oldios[:]
447 448
448 449 def validate(self):
449 450 """Reload the config and check if the server is up to date
450 451
451 452 Read a list of '\0' separated arguments.
452 453 Write a non-empty list of '\0' separated instruction strings or '\0'
453 454 if the list is empty.
454 455 An instruction string could be either:
455 456 - "unlink $path", the client should unlink the path to stop the
456 457 outdated server.
457 458 - "redirect $path", the client should attempt to connect to $path
458 459 first. If it does not work, start a new server. It implies
459 460 "reconnect".
460 461 - "exit $n", the client should exit directly with code n.
461 462 This may happen if we cannot parse the config.
462 463 - "reconnect", the client should close the connection and
463 464 reconnect.
464 465 If neither "reconnect" nor "redirect" is included in the instruction
465 466 list, the client can continue with this server after completing all
466 467 the instructions.
467 468 """
468 469 from . import dispatch # avoid cycle
469 470
470 471 args = self._readlist()
471 472 try:
472 473 self.ui, lui = _loadnewui(self.ui, args, self.cdebug)
473 474 except error.ParseError as inst:
474 475 dispatch._formatparse(self.ui.warn, inst)
475 476 self.ui.flush()
476 477 self.cresult.write(b'exit 255')
477 478 return
478 479 except error.Abort as inst:
479 480 self.ui.error(_(b"abort: %s\n") % inst)
480 481 if inst.hint:
481 482 self.ui.error(_(b"(%s)\n") % inst.hint)
482 483 self.ui.flush()
483 484 self.cresult.write(b'exit 255')
484 485 return
485 486 newhash = hashstate.fromui(lui, self.hashstate.mtimepaths)
486 487 insts = []
487 488 if newhash.mtimehash != self.hashstate.mtimehash:
488 489 addr = _hashaddress(self.baseaddress, self.hashstate.confighash)
489 490 insts.append(b'unlink %s' % addr)
490 491 # mtimehash is empty if one or more extensions fail to load.
491 492 # to be compatible with hg, still serve the client this time.
492 493 if self.hashstate.mtimehash:
493 494 insts.append(b'reconnect')
494 495 if newhash.confighash != self.hashstate.confighash:
495 496 addr = _hashaddress(self.baseaddress, newhash.confighash)
496 497 insts.append(b'redirect %s' % addr)
497 498 self.ui.log(b'chgserver', b'validate: %s\n', stringutil.pprint(insts))
498 499 self.cresult.write(b'\0'.join(insts) or b'\0')
499 500
500 501 def chdir(self):
501 502 """Change current directory
502 503
503 504 Note that the behavior of --cwd option is bit different from this.
504 505 It does not affect --config parameter.
505 506 """
506 507 path = self._readstr()
507 508 if not path:
508 509 return
509 510 self.ui.log(b'chgserver', b"chdir to '%s'\n", path)
510 511 os.chdir(path)
511 512
512 513 def setumask(self):
513 514 """Change umask (DEPRECATED)"""
514 515 # BUG: this does not follow the message frame structure, but kept for
515 516 # backward compatibility with old chg clients for some time
516 517 self._setumask(self._read(4))
517 518
518 519 def setumask2(self):
519 520 """Change umask"""
520 521 data = self._readstr()
521 522 if len(data) != 4:
522 523 raise ValueError(b'invalid mask length in setumask2 request')
523 524 self._setumask(data)
524 525
525 526 def _setumask(self, data):
526 527 mask = struct.unpack(b'>I', data)[0]
527 528 self.ui.log(b'chgserver', b'setumask %r\n', mask)
528 529 os.umask(mask)
529 530
530 531 def runcommand(self):
531 532 # pager may be attached within the runcommand session, which should
532 533 # be detached at the end of the session. otherwise the pager wouldn't
533 534 # receive EOF.
534 535 globaloldios = self._oldios
535 536 self._oldios = []
536 537 try:
537 538 return super(chgcmdserver, self).runcommand()
538 539 finally:
539 540 self._restoreio()
540 541 self._oldios = globaloldios
541 542
542 543 def setenv(self):
543 544 """Clear and update os.environ
544 545
545 546 Note that not all variables can make an effect on the running process.
546 547 """
547 548 l = self._readlist()
548 549 try:
549 550 newenv = dict(s.split(b'=', 1) for s in l)
550 551 except ValueError:
551 552 raise ValueError(b'unexpected value in setenv request')
552 553 self.ui.log(b'chgserver', b'setenv: %r\n', sorted(newenv.keys()))
553 554
554 555 encoding.environ.clear()
555 556 encoding.environ.update(newenv)
556 557
557 558 capabilities = commandserver.server.capabilities.copy()
558 559 capabilities.update(
559 560 {
560 561 b'attachio': attachio,
561 562 b'chdir': chdir,
562 563 b'runcommand': runcommand,
563 564 b'setenv': setenv,
564 565 b'setumask': setumask,
565 566 b'setumask2': setumask2,
566 567 }
567 568 )
568 569
569 570 if util.safehasattr(procutil, b'setprocname'):
570 571
571 572 def setprocname(self):
572 573 """Change process title"""
573 574 name = self._readstr()
574 575 self.ui.log(b'chgserver', b'setprocname: %r\n', name)
575 576 procutil.setprocname(name)
576 577
577 578 capabilities[b'setprocname'] = setprocname
578 579
579 580
580 581 def _tempaddress(address):
581 582 return b'%s.%d.tmp' % (address, os.getpid())
582 583
583 584
584 585 def _hashaddress(address, hashstr):
585 586 # if the basename of address contains '.', use only the left part. this
586 587 # makes it possible for the client to pass 'server.tmp$PID' and follow by
587 588 # an atomic rename to avoid locking when spawning new servers.
588 589 dirname, basename = os.path.split(address)
589 590 basename = basename.split(b'.', 1)[0]
590 591 return b'%s-%s' % (os.path.join(dirname, basename), hashstr)
591 592
592 593
593 594 class chgunixservicehandler(object):
594 595 """Set of operations for chg services"""
595 596
596 597 pollinterval = 1 # [sec]
597 598
598 599 def __init__(self, ui):
599 600 self.ui = ui
600 601 self._idletimeout = ui.configint(b'chgserver', b'idletimeout')
601 602 self._lastactive = time.time()
602 603
603 604 def bindsocket(self, sock, address):
604 605 self._inithashstate(address)
605 606 self._checkextensions()
606 607 self._bind(sock)
607 608 self._createsymlink()
608 609 # no "listening at" message should be printed to simulate hg behavior
609 610
610 611 def _inithashstate(self, address):
611 612 self._baseaddress = address
612 613 if self.ui.configbool(b'chgserver', b'skiphash'):
613 614 self._hashstate = None
614 615 self._realaddress = address
615 616 return
616 617 self._hashstate = hashstate.fromui(self.ui)
617 618 self._realaddress = _hashaddress(address, self._hashstate.confighash)
618 619
619 620 def _checkextensions(self):
620 621 if not self._hashstate:
621 622 return
622 623 if extensions.notloaded():
623 624 # one or more extensions failed to load. mtimehash becomes
624 625 # meaningless because we do not know the paths of those extensions.
625 626 # set mtimehash to an illegal hash value to invalidate the server.
626 627 self._hashstate.mtimehash = b''
627 628
628 629 def _bind(self, sock):
629 630 # use a unique temp address so we can stat the file and do ownership
630 631 # check later
631 632 tempaddress = _tempaddress(self._realaddress)
632 633 util.bindunixsocket(sock, tempaddress)
633 634 self._socketstat = os.stat(tempaddress)
634 635 sock.listen(socket.SOMAXCONN)
635 636 # rename will replace the old socket file if exists atomically. the
636 637 # old server will detect ownership change and exit.
637 638 util.rename(tempaddress, self._realaddress)
638 639
639 640 def _createsymlink(self):
640 641 if self._baseaddress == self._realaddress:
641 642 return
642 643 tempaddress = _tempaddress(self._baseaddress)
643 644 os.symlink(os.path.basename(self._realaddress), tempaddress)
644 645 util.rename(tempaddress, self._baseaddress)
645 646
646 647 def _issocketowner(self):
647 648 try:
648 649 st = os.stat(self._realaddress)
649 650 return (
650 651 st.st_ino == self._socketstat.st_ino
651 652 and st[stat.ST_MTIME] == self._socketstat[stat.ST_MTIME]
652 653 )
653 654 except OSError:
654 655 return False
655 656
656 657 def unlinksocket(self, address):
657 658 if not self._issocketowner():
658 659 return
659 660 # it is possible to have a race condition here that we may
660 661 # remove another server's socket file. but that's okay
661 662 # since that server will detect and exit automatically and
662 663 # the client will start a new server on demand.
663 664 util.tryunlink(self._realaddress)
664 665
665 666 def shouldexit(self):
666 667 if not self._issocketowner():
667 668 self.ui.log(
668 669 b'chgserver', b'%s is not owned, exiting.\n', self._realaddress
669 670 )
670 671 return True
671 672 if time.time() - self._lastactive > self._idletimeout:
672 673 self.ui.log(b'chgserver', b'being idle too long. exiting.\n')
673 674 return True
674 675 return False
675 676
676 677 def newconnection(self):
677 678 self._lastactive = time.time()
678 679
679 680 def createcmdserver(self, repo, conn, fin, fout, prereposetups):
680 681 return chgcmdserver(
681 682 self.ui,
682 683 repo,
683 684 fin,
684 685 fout,
685 686 conn,
686 687 prereposetups,
687 688 self._hashstate,
688 689 self._baseaddress,
689 690 )
690 691
691 692
692 693 def chgunixservice(ui, repo, opts):
693 694 # CHGINTERNALMARK is set by chg client. It is an indication of things are
694 695 # started by chg so other code can do things accordingly, like disabling
695 696 # demandimport or detecting chg client started by chg client. When executed
696 697 # here, CHGINTERNALMARK is no longer useful and hence dropped to make
697 698 # environ cleaner.
698 699 if b'CHGINTERNALMARK' in encoding.environ:
699 700 del encoding.environ[b'CHGINTERNALMARK']
700 701 # Python3.7+ "coerces" the LC_CTYPE environment variable to a UTF-8 one if
701 702 # it thinks the current value is "C". This breaks the hash computation and
702 703 # causes chg to restart loop.
703 704 if b'CHGORIG_LC_CTYPE' in encoding.environ:
704 705 encoding.environ[b'LC_CTYPE'] = encoding.environ[b'CHGORIG_LC_CTYPE']
705 706 del encoding.environ[b'CHGORIG_LC_CTYPE']
706 707 elif b'CHG_CLEAR_LC_CTYPE' in encoding.environ:
707 708 if b'LC_CTYPE' in encoding.environ:
708 709 del encoding.environ[b'LC_CTYPE']
709 710 del encoding.environ[b'CHG_CLEAR_LC_CTYPE']
710 711
711 712 if repo:
712 713 # one chgserver can serve multiple repos. drop repo information
713 714 ui.setconfig(b'bundle', b'mainreporoot', b'', b'repo')
714 715 h = chgunixservicehandler(ui)
715 716 return commandserver.unixforkingservice(ui, repo=None, opts=opts, handler=h)
General Comments 0
You need to be logged in to leave comments. Login now