65 from .utils import ( |
65 from .utils import ( |
66 procutil, |
66 procutil, |
67 stringutil, |
67 stringutil, |
68 ) |
68 ) |
69 |
69 |
|
70 |
70 def _hashlist(items): |
71 def _hashlist(items): |
71 """return sha1 hexdigest for a list""" |
72 """return sha1 hexdigest for a list""" |
72 return node.hex(hashlib.sha1(stringutil.pprint(items)).digest()) |
73 return node.hex(hashlib.sha1(stringutil.pprint(items)).digest()) |
73 |
74 |
|
75 |
74 # sensitive config sections affecting confighash |
76 # sensitive config sections affecting confighash |
75 _configsections = [ |
77 _configsections = [ |
76 'alias', # affects global state commands.table |
78 'alias', # affects global state commands.table |
77 'eol', # uses setconfig('eol', ...) |
79 'eol', # uses setconfig('eol', ...) |
78 'extdiff', # uisetup will register new commands |
80 'extdiff', # uisetup will register new commands |
79 'extensions', |
81 'extensions', |
80 ] |
82 ] |
81 |
83 |
82 _configsectionitems = [ |
84 _configsectionitems = [ |
83 ('commands', 'show.aliasprefix'), # show.py reads it in extsetup |
85 ('commands', 'show.aliasprefix'), # show.py reads it in extsetup |
84 ] |
86 ] |
85 |
87 |
86 # sensitive environment variables affecting confighash |
88 # sensitive environment variables affecting confighash |
87 _envre = re.compile(br'''\A(?: |
89 _envre = re.compile( |
|
90 br'''\A(?: |
88 CHGHG |
91 CHGHG |
89 |HG(?:DEMANDIMPORT|EMITWARNINGS|MODULEPOLICY|PROF|RCPATH)? |
92 |HG(?:DEMANDIMPORT|EMITWARNINGS|MODULEPOLICY|PROF|RCPATH)? |
90 |HG(?:ENCODING|PLAIN).* |
93 |HG(?:ENCODING|PLAIN).* |
91 |LANG(?:UAGE)? |
94 |LANG(?:UAGE)? |
92 |LC_.* |
95 |LC_.* |
93 |LD_.* |
96 |LD_.* |
94 |PATH |
97 |PATH |
95 |PYTHON.* |
98 |PYTHON.* |
96 |TERM(?:INFO)? |
99 |TERM(?:INFO)? |
97 |TZ |
100 |TZ |
98 )\Z''', re.X) |
101 )\Z''', |
|
102 re.X, |
|
103 ) |
|
104 |
99 |
105 |
100 def _confighash(ui): |
106 def _confighash(ui): |
101 """return a quick hash for detecting config/env changes |
107 """return a quick hash for detecting config/env changes |
102 |
108 |
103 confighash is the hash of sensitive config items and environment variables. |
109 confighash is the hash of sensitive config items and environment variables. |
117 # If $CHGHG is set, the change to $HG should not trigger a new chg server |
123 # If $CHGHG is set, the change to $HG should not trigger a new chg server |
118 if 'CHGHG' in encoding.environ: |
124 if 'CHGHG' in encoding.environ: |
119 ignored = {'HG'} |
125 ignored = {'HG'} |
120 else: |
126 else: |
121 ignored = set() |
127 ignored = set() |
122 envitems = [(k, v) for k, v in encoding.environ.iteritems() |
128 envitems = [ |
123 if _envre.match(k) and k not in ignored] |
129 (k, v) |
|
130 for k, v in encoding.environ.iteritems() |
|
131 if _envre.match(k) and k not in ignored |
|
132 ] |
124 envhash = _hashlist(sorted(envitems)) |
133 envhash = _hashlist(sorted(envitems)) |
125 return sectionhash[:6] + envhash[:6] |
134 return sectionhash[:6] + envhash[:6] |
|
135 |
126 |
136 |
127 def _getmtimepaths(ui): |
137 def _getmtimepaths(ui): |
128 """get a list of paths that should be checked to detect change |
138 """get a list of paths that should be checked to detect change |
129 |
139 |
130 The list will include: |
140 The list will include: |
163 |
175 |
164 mtimehash is not included in confighash because we only know the paths of |
176 mtimehash is not included in confighash because we only know the paths of |
165 extensions after importing them (there is imp.find_module but that faces |
177 extensions after importing them (there is imp.find_module but that faces |
166 race conditions). We need to calculate confighash without importing. |
178 race conditions). We need to calculate confighash without importing. |
167 """ |
179 """ |
|
180 |
168 def trystat(path): |
181 def trystat(path): |
169 try: |
182 try: |
170 st = os.stat(path) |
183 st = os.stat(path) |
171 return (st[stat.ST_MTIME], st.st_size) |
184 return (st[stat.ST_MTIME], st.st_size) |
172 except OSError: |
185 except OSError: |
173 # could be ENOENT, EPERM etc. not fatal in any case |
186 # could be ENOENT, EPERM etc. not fatal in any case |
174 pass |
187 pass |
|
188 |
175 return _hashlist(pycompat.maplist(trystat, paths))[:12] |
189 return _hashlist(pycompat.maplist(trystat, paths))[:12] |
|
190 |
176 |
191 |
177 class hashstate(object): |
192 class hashstate(object): |
178 """a structure storing confighash, mtimehash, paths used for mtimehash""" |
193 """a structure storing confighash, mtimehash, paths used for mtimehash""" |
|
194 |
179 def __init__(self, confighash, mtimehash, mtimepaths): |
195 def __init__(self, confighash, mtimehash, mtimepaths): |
180 self.confighash = confighash |
196 self.confighash = confighash |
181 self.mtimehash = mtimehash |
197 self.mtimehash = mtimehash |
182 self.mtimepaths = mtimepaths |
198 self.mtimepaths = mtimepaths |
183 |
199 |
185 def fromui(ui, mtimepaths=None): |
201 def fromui(ui, mtimepaths=None): |
186 if mtimepaths is None: |
202 if mtimepaths is None: |
187 mtimepaths = _getmtimepaths(ui) |
203 mtimepaths = _getmtimepaths(ui) |
188 confighash = _confighash(ui) |
204 confighash = _confighash(ui) |
189 mtimehash = _mtimehash(mtimepaths) |
205 mtimehash = _mtimehash(mtimepaths) |
190 ui.log('cmdserver', 'confighash = %s mtimehash = %s\n', |
206 ui.log( |
191 confighash, mtimehash) |
207 'cmdserver', |
|
208 'confighash = %s mtimehash = %s\n', |
|
209 confighash, |
|
210 mtimehash, |
|
211 ) |
192 return hashstate(confighash, mtimehash, mtimepaths) |
212 return hashstate(confighash, mtimehash, mtimepaths) |
|
213 |
193 |
214 |
194 def _newchgui(srcui, csystem, attachio): |
215 def _newchgui(srcui, csystem, attachio): |
195 class chgui(srcui.__class__): |
216 class chgui(srcui.__class__): |
196 def __init__(self, src=None): |
217 def __init__(self, src=None): |
197 super(chgui, self).__init__(src) |
218 super(chgui, self).__init__(src) |
204 # fallback to the original system method if |
225 # fallback to the original system method if |
205 # a. the output stream is not stdout (e.g. stderr, cStringIO), |
226 # a. the output stream is not stdout (e.g. stderr, cStringIO), |
206 # b. or stdout is redirected by protectfinout(), |
227 # b. or stdout is redirected by protectfinout(), |
207 # because the chg client is not aware of these situations and |
228 # because the chg client is not aware of these situations and |
208 # will behave differently (i.e. write to stdout). |
229 # will behave differently (i.e. write to stdout). |
209 if (out is not self.fout |
230 if ( |
|
231 out is not self.fout |
210 or not util.safehasattr(self.fout, 'fileno') |
232 or not util.safehasattr(self.fout, 'fileno') |
211 or self.fout.fileno() != procutil.stdout.fileno() |
233 or self.fout.fileno() != procutil.stdout.fileno() |
212 or self._finoutredirected): |
234 or self._finoutredirected |
|
235 ): |
213 return procutil.system(cmd, environ=environ, cwd=cwd, out=out) |
236 return procutil.system(cmd, environ=environ, cwd=cwd, out=out) |
214 self.flush() |
237 self.flush() |
215 return self._csystem(cmd, procutil.shellenviron(environ), cwd) |
238 return self._csystem(cmd, procutil.shellenviron(environ), cwd) |
216 |
239 |
217 def _runpager(self, cmd, env=None): |
240 def _runpager(self, cmd, env=None): |
218 self._csystem(cmd, procutil.shellenviron(env), type='pager', |
241 self._csystem( |
219 cmdtable={'attachio': attachio}) |
242 cmd, |
|
243 procutil.shellenviron(env), |
|
244 type='pager', |
|
245 cmdtable={'attachio': attachio}, |
|
246 ) |
220 return True |
247 return True |
221 |
248 |
222 return chgui(srcui) |
249 return chgui(srcui) |
|
250 |
223 |
251 |
224 def _loadnewui(srcui, args, cdebug): |
252 def _loadnewui(srcui, args, cdebug): |
225 from . import dispatch # avoid cycle |
253 from . import dispatch # avoid cycle |
226 |
254 |
227 newui = srcui.__class__.load() |
255 newui = srcui.__class__.load() |
254 extensions.populateui(newlui) |
282 extensions.populateui(newlui) |
255 commandserver.setuplogging(newlui, fp=cdebug) |
283 commandserver.setuplogging(newlui, fp=cdebug) |
256 |
284 |
257 return (newui, newlui) |
285 return (newui, newlui) |
258 |
286 |
|
287 |
259 class channeledsystem(object): |
288 class channeledsystem(object): |
260 """Propagate ui.system() request in the following format: |
289 """Propagate ui.system() request in the following format: |
261 |
290 |
262 payload length (unsigned int), |
291 payload length (unsigned int), |
263 type, '\0', |
292 type, '\0', |
289 self.out.write(data) |
319 self.out.write(data) |
290 self.out.flush() |
320 self.out.flush() |
291 |
321 |
292 if type == 'system': |
322 if type == 'system': |
293 length = self.in_.read(4) |
323 length = self.in_.read(4) |
294 length, = struct.unpack('>I', length) |
324 (length,) = struct.unpack('>I', length) |
295 if length != 4: |
325 if length != 4: |
296 raise error.Abort(_('invalid response')) |
326 raise error.Abort(_('invalid response')) |
297 rc, = struct.unpack('>i', self.in_.read(4)) |
327 (rc,) = struct.unpack('>i', self.in_.read(4)) |
298 return rc |
328 return rc |
299 elif type == 'pager': |
329 elif type == 'pager': |
300 while True: |
330 while True: |
301 cmd = self.in_.readline()[:-1] |
331 cmd = self.in_.readline()[:-1] |
302 if not cmd: |
332 if not cmd: |
306 else: |
336 else: |
307 raise error.Abort(_('unexpected command: %s') % cmd) |
337 raise error.Abort(_('unexpected command: %s') % cmd) |
308 else: |
338 else: |
309 raise error.ProgrammingError('invalid S channel type: %s' % type) |
339 raise error.ProgrammingError('invalid S channel type: %s' % type) |
310 |
340 |
|
341 |
311 _iochannels = [ |
342 _iochannels = [ |
312 # server.ch, ui.fp, mode |
343 # server.ch, ui.fp, mode |
313 ('cin', 'fin', r'rb'), |
344 ('cin', 'fin', r'rb'), |
314 ('cout', 'fout', r'wb'), |
345 ('cout', 'fout', r'wb'), |
315 ('cerr', 'ferr', r'wb'), |
346 ('cerr', 'ferr', r'wb'), |
316 ] |
347 ] |
317 |
348 |
|
349 |
318 class chgcmdserver(commandserver.server): |
350 class chgcmdserver(commandserver.server): |
319 def __init__(self, ui, repo, fin, fout, sock, prereposetups, |
351 def __init__( |
320 hashstate, baseaddress): |
352 self, ui, repo, fin, fout, sock, prereposetups, hashstate, baseaddress |
|
353 ): |
321 super(chgcmdserver, self).__init__( |
354 super(chgcmdserver, self).__init__( |
322 _newchgui(ui, channeledsystem(fin, fout, 'S'), self.attachio), |
355 _newchgui(ui, channeledsystem(fin, fout, 'S'), self.attachio), |
323 repo, fin, fout, prereposetups) |
356 repo, |
|
357 fin, |
|
358 fout, |
|
359 prereposetups, |
|
360 ) |
324 self.clientsock = sock |
361 self.clientsock = sock |
325 self._ioattached = False |
362 self._ioattached = False |
326 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio" |
363 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio" |
327 self.hashstate = hashstate |
364 self.hashstate = hashstate |
328 self.baseaddress = baseaddress |
365 self.baseaddress = baseaddress |
510 self.ui.log('chgserver', 'setenv: %r\n', sorted(newenv.keys())) |
547 self.ui.log('chgserver', 'setenv: %r\n', sorted(newenv.keys())) |
511 encoding.environ.clear() |
548 encoding.environ.clear() |
512 encoding.environ.update(newenv) |
549 encoding.environ.update(newenv) |
513 |
550 |
514 capabilities = commandserver.server.capabilities.copy() |
551 capabilities = commandserver.server.capabilities.copy() |
515 capabilities.update({'attachio': attachio, |
552 capabilities.update( |
516 'chdir': chdir, |
553 { |
517 'runcommand': runcommand, |
554 'attachio': attachio, |
518 'setenv': setenv, |
555 'chdir': chdir, |
519 'setumask': setumask, |
556 'runcommand': runcommand, |
520 'setumask2': setumask2}) |
557 'setenv': setenv, |
|
558 'setumask': setumask, |
|
559 'setumask2': setumask2, |
|
560 } |
|
561 ) |
521 |
562 |
522 if util.safehasattr(procutil, 'setprocname'): |
563 if util.safehasattr(procutil, 'setprocname'): |
|
564 |
523 def setprocname(self): |
565 def setprocname(self): |
524 """Change process title""" |
566 """Change process title""" |
525 name = self._readstr() |
567 name = self._readstr() |
526 self.ui.log('chgserver', 'setprocname: %r\n', name) |
568 self.ui.log('chgserver', 'setprocname: %r\n', name) |
527 procutil.setprocname(name) |
569 procutil.setprocname(name) |
|
570 |
528 capabilities['setprocname'] = setprocname |
571 capabilities['setprocname'] = setprocname |
|
572 |
529 |
573 |
530 def _tempaddress(address): |
574 def _tempaddress(address): |
531 return '%s.%d.tmp' % (address, os.getpid()) |
575 return '%s.%d.tmp' % (address, os.getpid()) |
|
576 |
532 |
577 |
533 def _hashaddress(address, hashstr): |
578 def _hashaddress(address, hashstr): |
534 # if the basename of address contains '.', use only the left part. this |
579 # if the basename of address contains '.', use only the left part. this |
535 # makes it possible for the client to pass 'server.tmp$PID' and follow by |
580 # makes it possible for the client to pass 'server.tmp$PID' and follow by |
536 # an atomic rename to avoid locking when spawning new servers. |
581 # an atomic rename to avoid locking when spawning new servers. |
537 dirname, basename = os.path.split(address) |
582 dirname, basename = os.path.split(address) |
538 basename = basename.split('.', 1)[0] |
583 basename = basename.split('.', 1)[0] |
539 return '%s-%s' % (os.path.join(dirname, basename), hashstr) |
584 return '%s-%s' % (os.path.join(dirname, basename), hashstr) |
|
585 |
540 |
586 |
541 class chgunixservicehandler(object): |
587 class chgunixservicehandler(object): |
542 """Set of operations for chg services""" |
588 """Set of operations for chg services""" |
543 |
589 |
544 pollinterval = 1 # [sec] |
590 pollinterval = 1 # [sec] |
592 util.rename(tempaddress, self._baseaddress) |
638 util.rename(tempaddress, self._baseaddress) |
593 |
639 |
594 def _issocketowner(self): |
640 def _issocketowner(self): |
595 try: |
641 try: |
596 st = os.stat(self._realaddress) |
642 st = os.stat(self._realaddress) |
597 return (st.st_ino == self._socketstat.st_ino and |
643 return ( |
598 st[stat.ST_MTIME] == self._socketstat[stat.ST_MTIME]) |
644 st.st_ino == self._socketstat.st_ino |
|
645 and st[stat.ST_MTIME] == self._socketstat[stat.ST_MTIME] |
|
646 ) |
599 except OSError: |
647 except OSError: |
600 return False |
648 return False |
601 |
649 |
602 def unlinksocket(self, address): |
650 def unlinksocket(self, address): |
603 if not self._issocketowner(): |
651 if not self._issocketowner(): |
608 # the client will start a new server on demand. |
656 # the client will start a new server on demand. |
609 util.tryunlink(self._realaddress) |
657 util.tryunlink(self._realaddress) |
610 |
658 |
611 def shouldexit(self): |
659 def shouldexit(self): |
612 if not self._issocketowner(): |
660 if not self._issocketowner(): |
613 self.ui.log(b'chgserver', b'%s is not owned, exiting.\n', |
661 self.ui.log( |
614 self._realaddress) |
662 b'chgserver', b'%s is not owned, exiting.\n', self._realaddress |
|
663 ) |
615 return True |
664 return True |
616 if time.time() - self._lastactive > self._idletimeout: |
665 if time.time() - self._lastactive > self._idletimeout: |
617 self.ui.log(b'chgserver', b'being idle too long. exiting.\n') |
666 self.ui.log(b'chgserver', b'being idle too long. exiting.\n') |
618 return True |
667 return True |
619 return False |
668 return False |
620 |
669 |
621 def newconnection(self): |
670 def newconnection(self): |
622 self._lastactive = time.time() |
671 self._lastactive = time.time() |
623 |
672 |
624 def createcmdserver(self, repo, conn, fin, fout, prereposetups): |
673 def createcmdserver(self, repo, conn, fin, fout, prereposetups): |
625 return chgcmdserver(self.ui, repo, fin, fout, conn, prereposetups, |
674 return chgcmdserver( |
626 self._hashstate, self._baseaddress) |
675 self.ui, |
|
676 repo, |
|
677 fin, |
|
678 fout, |
|
679 conn, |
|
680 prereposetups, |
|
681 self._hashstate, |
|
682 self._baseaddress, |
|
683 ) |
|
684 |
627 |
685 |
628 def chgunixservice(ui, repo, opts): |
686 def chgunixservice(ui, repo, opts): |
629 # CHGINTERNALMARK is set by chg client. It is an indication of things are |
687 # CHGINTERNALMARK is set by chg client. It is an indication of things are |
630 # started by chg so other code can do things accordingly, like disabling |
688 # started by chg so other code can do things accordingly, like disabling |
631 # demandimport or detecting chg client started by chg client. When executed |
689 # demandimport or detecting chg client started by chg client. When executed |