##// END OF EJS Templates
procutil: move protectio/restoreio from commandserver...
Yuya Nishihara -
r37141:0216232f default
parent child Browse files
Show More
@@ -1,565 +1,536 b''
1 1 # commandserver.py - communicate with Mercurial's API over a pipe
2 2 #
3 3 # Copyright Matt Mackall <mpm@selenic.com>
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 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import gc
12 12 import os
13 13 import random
14 14 import signal
15 15 import socket
16 16 import struct
17 17 import traceback
18 18
19 19 try:
20 20 import selectors
21 21 selectors.BaseSelector
22 22 except ImportError:
23 23 from .thirdparty import selectors2 as selectors
24 24
25 25 from .i18n import _
26 26 from . import (
27 27 encoding,
28 28 error,
29 29 pycompat,
30 30 util,
31 31 )
32 32 from .utils import (
33 33 procutil,
34 34 )
35 35
36 36 logfile = None
37 37
38 38 def log(*args):
39 39 if not logfile:
40 40 return
41 41
42 42 for a in args:
43 43 logfile.write(str(a))
44 44
45 45 logfile.flush()
46 46
47 47 class channeledoutput(object):
48 48 """
49 49 Write data to out in the following format:
50 50
51 51 data length (unsigned int),
52 52 data
53 53 """
54 54 def __init__(self, out, channel):
55 55 self.out = out
56 56 self.channel = channel
57 57
58 58 @property
59 59 def name(self):
60 60 return '<%c-channel>' % self.channel
61 61
62 62 def write(self, data):
63 63 if not data:
64 64 return
65 65 # single write() to guarantee the same atomicity as the underlying file
66 66 self.out.write(struct.pack('>cI', self.channel, len(data)) + data)
67 67 self.out.flush()
68 68
69 69 def __getattr__(self, attr):
70 70 if attr in ('isatty', 'fileno', 'tell', 'seek'):
71 71 raise AttributeError(attr)
72 72 return getattr(self.out, attr)
73 73
74 74 class channeledinput(object):
75 75 """
76 76 Read data from in_.
77 77
78 78 Requests for input are written to out in the following format:
79 79 channel identifier - 'I' for plain input, 'L' line based (1 byte)
80 80 how many bytes to send at most (unsigned int),
81 81
82 82 The client replies with:
83 83 data length (unsigned int), 0 meaning EOF
84 84 data
85 85 """
86 86
87 87 maxchunksize = 4 * 1024
88 88
89 89 def __init__(self, in_, out, channel):
90 90 self.in_ = in_
91 91 self.out = out
92 92 self.channel = channel
93 93
94 94 @property
95 95 def name(self):
96 96 return '<%c-channel>' % self.channel
97 97
98 98 def read(self, size=-1):
99 99 if size < 0:
100 100 # if we need to consume all the clients input, ask for 4k chunks
101 101 # so the pipe doesn't fill up risking a deadlock
102 102 size = self.maxchunksize
103 103 s = self._read(size, self.channel)
104 104 buf = s
105 105 while s:
106 106 s = self._read(size, self.channel)
107 107 buf += s
108 108
109 109 return buf
110 110 else:
111 111 return self._read(size, self.channel)
112 112
113 113 def _read(self, size, channel):
114 114 if not size:
115 115 return ''
116 116 assert size > 0
117 117
118 118 # tell the client we need at most size bytes
119 119 self.out.write(struct.pack('>cI', channel, size))
120 120 self.out.flush()
121 121
122 122 length = self.in_.read(4)
123 123 length = struct.unpack('>I', length)[0]
124 124 if not length:
125 125 return ''
126 126 else:
127 127 return self.in_.read(length)
128 128
129 129 def readline(self, size=-1):
130 130 if size < 0:
131 131 size = self.maxchunksize
132 132 s = self._read(size, 'L')
133 133 buf = s
134 134 # keep asking for more until there's either no more or
135 135 # we got a full line
136 136 while s and s[-1] != '\n':
137 137 s = self._read(size, 'L')
138 138 buf += s
139 139
140 140 return buf
141 141 else:
142 142 return self._read(size, 'L')
143 143
144 144 def __iter__(self):
145 145 return self
146 146
147 147 def next(self):
148 148 l = self.readline()
149 149 if not l:
150 150 raise StopIteration
151 151 return l
152 152
153 153 def __getattr__(self, attr):
154 154 if attr in ('isatty', 'fileno', 'tell', 'seek'):
155 155 raise AttributeError(attr)
156 156 return getattr(self.in_, attr)
157 157
158 158 class server(object):
159 159 """
160 160 Listens for commands on fin, runs them and writes the output on a channel
161 161 based stream to fout.
162 162 """
163 163 def __init__(self, ui, repo, fin, fout):
164 164 self.cwd = pycompat.getcwd()
165 165
166 166 # developer config: cmdserver.log
167 167 logpath = ui.config("cmdserver", "log")
168 168 if logpath:
169 169 global logfile
170 170 if logpath == '-':
171 171 # write log on a special 'd' (debug) channel
172 172 logfile = channeledoutput(fout, 'd')
173 173 else:
174 174 logfile = open(logpath, 'a')
175 175
176 176 if repo:
177 177 # the ui here is really the repo ui so take its baseui so we don't
178 178 # end up with its local configuration
179 179 self.ui = repo.baseui
180 180 self.repo = repo
181 181 self.repoui = repo.ui
182 182 else:
183 183 self.ui = ui
184 184 self.repo = self.repoui = None
185 185
186 186 self.cerr = channeledoutput(fout, 'e')
187 187 self.cout = channeledoutput(fout, 'o')
188 188 self.cin = channeledinput(fin, fout, 'I')
189 189 self.cresult = channeledoutput(fout, 'r')
190 190
191 191 self.client = fin
192 192
193 193 def cleanup(self):
194 194 """release and restore resources taken during server session"""
195 195
196 196 def _read(self, size):
197 197 if not size:
198 198 return ''
199 199
200 200 data = self.client.read(size)
201 201
202 202 # is the other end closed?
203 203 if not data:
204 204 raise EOFError
205 205
206 206 return data
207 207
208 208 def _readstr(self):
209 209 """read a string from the channel
210 210
211 211 format:
212 212 data length (uint32), data
213 213 """
214 214 length = struct.unpack('>I', self._read(4))[0]
215 215 if not length:
216 216 return ''
217 217 return self._read(length)
218 218
219 219 def _readlist(self):
220 220 """read a list of NULL separated strings from the channel"""
221 221 s = self._readstr()
222 222 if s:
223 223 return s.split('\0')
224 224 else:
225 225 return []
226 226
227 227 def runcommand(self):
228 228 """ reads a list of \0 terminated arguments, executes
229 229 and writes the return code to the result channel """
230 230 from . import dispatch # avoid cycle
231 231
232 232 args = self._readlist()
233 233
234 234 # copy the uis so changes (e.g. --config or --verbose) don't
235 235 # persist between requests
236 236 copiedui = self.ui.copy()
237 237 uis = [copiedui]
238 238 if self.repo:
239 239 self.repo.baseui = copiedui
240 240 # clone ui without using ui.copy because this is protected
241 241 repoui = self.repoui.__class__(self.repoui)
242 242 repoui.copy = copiedui.copy # redo copy protection
243 243 uis.append(repoui)
244 244 self.repo.ui = self.repo.dirstate._ui = repoui
245 245 self.repo.invalidateall()
246 246
247 247 for ui in uis:
248 248 ui.resetstate()
249 249 # any kind of interaction must use server channels, but chg may
250 250 # replace channels by fully functional tty files. so nontty is
251 251 # enforced only if cin is a channel.
252 252 if not util.safehasattr(self.cin, 'fileno'):
253 253 ui.setconfig('ui', 'nontty', 'true', 'commandserver')
254 254
255 255 req = dispatch.request(args[:], copiedui, self.repo, self.cin,
256 256 self.cout, self.cerr)
257 257
258 258 try:
259 259 ret = (dispatch.dispatch(req) or 0) & 255 # might return None
260 260 self.cresult.write(struct.pack('>i', int(ret)))
261 261 finally:
262 262 # restore old cwd
263 263 if '--cwd' in args:
264 264 os.chdir(self.cwd)
265 265
266 266 def getencoding(self):
267 267 """ writes the current encoding to the result channel """
268 268 self.cresult.write(encoding.encoding)
269 269
270 270 def serveone(self):
271 271 cmd = self.client.readline()[:-1]
272 272 if cmd:
273 273 handler = self.capabilities.get(cmd)
274 274 if handler:
275 275 handler(self)
276 276 else:
277 277 # clients are expected to check what commands are supported by
278 278 # looking at the servers capabilities
279 279 raise error.Abort(_('unknown command %s') % cmd)
280 280
281 281 return cmd != ''
282 282
283 283 capabilities = {'runcommand': runcommand,
284 284 'getencoding': getencoding}
285 285
286 286 def serve(self):
287 287 hellomsg = 'capabilities: ' + ' '.join(sorted(self.capabilities))
288 288 hellomsg += '\n'
289 289 hellomsg += 'encoding: ' + encoding.encoding
290 290 hellomsg += '\n'
291 291 hellomsg += 'pid: %d' % procutil.getpid()
292 292 if util.safehasattr(os, 'getpgid'):
293 293 hellomsg += '\n'
294 294 hellomsg += 'pgid: %d' % os.getpgid(0)
295 295
296 296 # write the hello msg in -one- chunk
297 297 self.cout.write(hellomsg)
298 298
299 299 try:
300 300 while self.serveone():
301 301 pass
302 302 except EOFError:
303 303 # we'll get here if the client disconnected while we were reading
304 304 # its request
305 305 return 1
306 306
307 307 return 0
308 308
309 def _protectio(uin, uout):
310 """Duplicate streams and redirect original to null if (uin, uout) are
311 stdio
312
313 Returns (fin, fout) which point to the original (uin, uout) fds, but
314 may be copy of (uin, uout). The returned streams can be considered
315 "owned" in that print(), exec(), etc. never reach to them.
316 """
317 uout.flush()
318 newfiles = []
319 nullfd = os.open(os.devnull, os.O_RDWR)
320 for f, sysf, mode in [(uin, procutil.stdin, r'rb'),
321 (uout, procutil.stdout, r'wb')]:
322 if f is sysf:
323 newfd = os.dup(f.fileno())
324 os.dup2(nullfd, f.fileno())
325 f = os.fdopen(newfd, mode)
326 newfiles.append(f)
327 os.close(nullfd)
328 return tuple(newfiles)
329
330 def _restoreio(uin, uout, fin, fout):
331 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
332 uout.flush()
333 for f, uif in [(fin, uin), (fout, uout)]:
334 if f is not uif:
335 os.dup2(f.fileno(), uif.fileno())
336 f.close()
337
338 309 class pipeservice(object):
339 310 def __init__(self, ui, repo, opts):
340 311 self.ui = ui
341 312 self.repo = repo
342 313
343 314 def init(self):
344 315 pass
345 316
346 317 def run(self):
347 318 ui = self.ui
348 319 # redirect stdio to null device so that broken extensions or in-process
349 320 # hooks will never cause corruption of channel protocol.
350 fin, fout = _protectio(ui.fin, ui.fout)
321 fin, fout = procutil.protectstdio(ui.fin, ui.fout)
351 322 try:
352 323 sv = server(ui, self.repo, fin, fout)
353 324 return sv.serve()
354 325 finally:
355 326 sv.cleanup()
356 _restoreio(ui.fin, ui.fout, fin, fout)
327 procutil.restorestdio(ui.fin, ui.fout, fin, fout)
357 328
358 329 def _initworkerprocess():
359 330 # use a different process group from the master process, in order to:
360 331 # 1. make the current process group no longer "orphaned" (because the
361 332 # parent of this process is in a different process group while
362 333 # remains in a same session)
363 334 # according to POSIX 2.2.2.52, orphaned process group will ignore
364 335 # terminal-generated stop signals like SIGTSTP (Ctrl+Z), which will
365 336 # cause trouble for things like ncurses.
366 337 # 2. the client can use kill(-pgid, sig) to simulate terminal-generated
367 338 # SIGINT (Ctrl+C) and process-exit-generated SIGHUP. our child
368 339 # processes like ssh will be killed properly, without affecting
369 340 # unrelated processes.
370 341 os.setpgid(0, 0)
371 342 # change random state otherwise forked request handlers would have a
372 343 # same state inherited from parent.
373 344 random.seed()
374 345
375 346 def _serverequest(ui, repo, conn, createcmdserver):
376 347 fin = conn.makefile('rb')
377 348 fout = conn.makefile('wb')
378 349 sv = None
379 350 try:
380 351 sv = createcmdserver(repo, conn, fin, fout)
381 352 try:
382 353 sv.serve()
383 354 # handle exceptions that may be raised by command server. most of
384 355 # known exceptions are caught by dispatch.
385 356 except error.Abort as inst:
386 357 ui.warn(_('abort: %s\n') % inst)
387 358 except IOError as inst:
388 359 if inst.errno != errno.EPIPE:
389 360 raise
390 361 except KeyboardInterrupt:
391 362 pass
392 363 finally:
393 364 sv.cleanup()
394 365 except: # re-raises
395 366 # also write traceback to error channel. otherwise client cannot
396 367 # see it because it is written to server's stderr by default.
397 368 if sv:
398 369 cerr = sv.cerr
399 370 else:
400 371 cerr = channeledoutput(fout, 'e')
401 372 traceback.print_exc(file=cerr)
402 373 raise
403 374 finally:
404 375 fin.close()
405 376 try:
406 377 fout.close() # implicit flush() may cause another EPIPE
407 378 except IOError as inst:
408 379 if inst.errno != errno.EPIPE:
409 380 raise
410 381
411 382 class unixservicehandler(object):
412 383 """Set of pluggable operations for unix-mode services
413 384
414 385 Almost all methods except for createcmdserver() are called in the main
415 386 process. You can't pass mutable resource back from createcmdserver().
416 387 """
417 388
418 389 pollinterval = None
419 390
420 391 def __init__(self, ui):
421 392 self.ui = ui
422 393
423 394 def bindsocket(self, sock, address):
424 395 util.bindunixsocket(sock, address)
425 396 sock.listen(socket.SOMAXCONN)
426 397 self.ui.status(_('listening at %s\n') % address)
427 398 self.ui.flush() # avoid buffering of status message
428 399
429 400 def unlinksocket(self, address):
430 401 os.unlink(address)
431 402
432 403 def shouldexit(self):
433 404 """True if server should shut down; checked per pollinterval"""
434 405 return False
435 406
436 407 def newconnection(self):
437 408 """Called when main process notices new connection"""
438 409
439 410 def createcmdserver(self, repo, conn, fin, fout):
440 411 """Create new command server instance; called in the process that
441 412 serves for the current connection"""
442 413 return server(self.ui, repo, fin, fout)
443 414
444 415 class unixforkingservice(object):
445 416 """
446 417 Listens on unix domain socket and forks server per connection
447 418 """
448 419
449 420 def __init__(self, ui, repo, opts, handler=None):
450 421 self.ui = ui
451 422 self.repo = repo
452 423 self.address = opts['address']
453 424 if not util.safehasattr(socket, 'AF_UNIX'):
454 425 raise error.Abort(_('unsupported platform'))
455 426 if not self.address:
456 427 raise error.Abort(_('no socket path specified with --address'))
457 428 self._servicehandler = handler or unixservicehandler(ui)
458 429 self._sock = None
459 430 self._oldsigchldhandler = None
460 431 self._workerpids = set() # updated by signal handler; do not iterate
461 432 self._socketunlinked = None
462 433
463 434 def init(self):
464 435 self._sock = socket.socket(socket.AF_UNIX)
465 436 self._servicehandler.bindsocket(self._sock, self.address)
466 437 if util.safehasattr(procutil, 'unblocksignal'):
467 438 procutil.unblocksignal(signal.SIGCHLD)
468 439 o = signal.signal(signal.SIGCHLD, self._sigchldhandler)
469 440 self._oldsigchldhandler = o
470 441 self._socketunlinked = False
471 442
472 443 def _unlinksocket(self):
473 444 if not self._socketunlinked:
474 445 self._servicehandler.unlinksocket(self.address)
475 446 self._socketunlinked = True
476 447
477 448 def _cleanup(self):
478 449 signal.signal(signal.SIGCHLD, self._oldsigchldhandler)
479 450 self._sock.close()
480 451 self._unlinksocket()
481 452 # don't kill child processes as they have active clients, just wait
482 453 self._reapworkers(0)
483 454
484 455 def run(self):
485 456 try:
486 457 self._mainloop()
487 458 finally:
488 459 self._cleanup()
489 460
490 461 def _mainloop(self):
491 462 exiting = False
492 463 h = self._servicehandler
493 464 selector = selectors.DefaultSelector()
494 465 selector.register(self._sock, selectors.EVENT_READ)
495 466 while True:
496 467 if not exiting and h.shouldexit():
497 468 # clients can no longer connect() to the domain socket, so
498 469 # we stop queuing new requests.
499 470 # for requests that are queued (connect()-ed, but haven't been
500 471 # accept()-ed), handle them before exit. otherwise, clients
501 472 # waiting for recv() will receive ECONNRESET.
502 473 self._unlinksocket()
503 474 exiting = True
504 475 ready = selector.select(timeout=h.pollinterval)
505 476 if not ready:
506 477 # only exit if we completed all queued requests
507 478 if exiting:
508 479 break
509 480 continue
510 481 try:
511 482 conn, _addr = self._sock.accept()
512 483 except socket.error as inst:
513 484 if inst.args[0] == errno.EINTR:
514 485 continue
515 486 raise
516 487
517 488 pid = os.fork()
518 489 if pid:
519 490 try:
520 491 self.ui.debug('forked worker process (pid=%d)\n' % pid)
521 492 self._workerpids.add(pid)
522 493 h.newconnection()
523 494 finally:
524 495 conn.close() # release handle in parent process
525 496 else:
526 497 try:
527 498 self._runworker(conn)
528 499 conn.close()
529 500 os._exit(0)
530 501 except: # never return, hence no re-raises
531 502 try:
532 503 self.ui.traceback(force=True)
533 504 finally:
534 505 os._exit(255)
535 506 selector.close()
536 507
537 508 def _sigchldhandler(self, signal, frame):
538 509 self._reapworkers(os.WNOHANG)
539 510
540 511 def _reapworkers(self, options):
541 512 while self._workerpids:
542 513 try:
543 514 pid, _status = os.waitpid(-1, options)
544 515 except OSError as inst:
545 516 if inst.errno == errno.EINTR:
546 517 continue
547 518 if inst.errno != errno.ECHILD:
548 519 raise
549 520 # no child processes at all (reaped by other waitpid()?)
550 521 self._workerpids.clear()
551 522 return
552 523 if pid == 0:
553 524 # no waitable child processes
554 525 return
555 526 self.ui.debug('worker process exited (pid=%d)\n' % pid)
556 527 self._workerpids.discard(pid)
557 528
558 529 def _runworker(self, conn):
559 530 signal.signal(signal.SIGCHLD, self._oldsigchldhandler)
560 531 _initworkerprocess()
561 532 h = self._servicehandler
562 533 try:
563 534 _serverequest(self.ui, self.repo, conn, h.createcmdserver)
564 535 finally:
565 536 gc.collect() # trigger __del__ since worker process uses os._exit
@@ -1,319 +1,348 b''
1 1 # procutil.py - utility for managing processes and executable environment
2 2 #
3 3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 from __future__ import absolute_import
11 11
12 12 import imp
13 13 import io
14 14 import os
15 15 import signal
16 16 import subprocess
17 17 import sys
18 18 import tempfile
19 19 import time
20 20
21 21 from ..i18n import _
22 22
23 23 from .. import (
24 24 encoding,
25 25 error,
26 26 policy,
27 27 pycompat,
28 28 )
29 29
30 30 osutil = policy.importmod(r'osutil')
31 31
32 32 stderr = pycompat.stderr
33 33 stdin = pycompat.stdin
34 34 stdout = pycompat.stdout
35 35
36 36 def isatty(fp):
37 37 try:
38 38 return fp.isatty()
39 39 except AttributeError:
40 40 return False
41 41
42 42 # glibc determines buffering on first write to stdout - if we replace a TTY
43 43 # destined stdout with a pipe destined stdout (e.g. pager), we want line
44 44 # buffering
45 45 if isatty(stdout):
46 46 stdout = os.fdopen(stdout.fileno(), r'wb', 1)
47 47
48 48 if pycompat.iswindows:
49 49 from .. import windows as platform
50 50 stdout = platform.winstdout(stdout)
51 51 else:
52 52 from .. import posix as platform
53 53
54 54 explainexit = platform.explainexit
55 55 findexe = platform.findexe
56 56 _gethgcmd = platform.gethgcmd
57 57 getuser = platform.getuser
58 58 getpid = os.getpid
59 59 hidewindow = platform.hidewindow
60 60 popen = platform.popen
61 61 quotecommand = platform.quotecommand
62 62 readpipe = platform.readpipe
63 63 setbinary = platform.setbinary
64 64 setsignalhandler = platform.setsignalhandler
65 65 shellquote = platform.shellquote
66 66 shellsplit = platform.shellsplit
67 67 spawndetached = platform.spawndetached
68 68 sshargs = platform.sshargs
69 69 testpid = platform.testpid
70 70
71 71 try:
72 72 setprocname = osutil.setprocname
73 73 except AttributeError:
74 74 pass
75 75 try:
76 76 unblocksignal = osutil.unblocksignal
77 77 except AttributeError:
78 78 pass
79 79
80 80 closefds = pycompat.isposix
81 81
82 82 def popen2(cmd, env=None, newlines=False):
83 83 # Setting bufsize to -1 lets the system decide the buffer size.
84 84 # The default for bufsize is 0, meaning unbuffered. This leads to
85 85 # poor performance on Mac OS X: http://bugs.python.org/issue4194
86 86 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
87 87 close_fds=closefds,
88 88 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
89 89 universal_newlines=newlines,
90 90 env=env)
91 91 return p.stdin, p.stdout
92 92
93 93 def popen3(cmd, env=None, newlines=False):
94 94 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
95 95 return stdin, stdout, stderr
96 96
97 97 def popen4(cmd, env=None, newlines=False, bufsize=-1):
98 98 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
99 99 close_fds=closefds,
100 100 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
101 101 stderr=subprocess.PIPE,
102 102 universal_newlines=newlines,
103 103 env=env)
104 104 return p.stdin, p.stdout, p.stderr, p
105 105
106 106 def pipefilter(s, cmd):
107 107 '''filter string S through command CMD, returning its output'''
108 108 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
109 109 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
110 110 pout, perr = p.communicate(s)
111 111 return pout
112 112
113 113 def tempfilter(s, cmd):
114 114 '''filter string S through a pair of temporary files with CMD.
115 115 CMD is used as a template to create the real command to be run,
116 116 with the strings INFILE and OUTFILE replaced by the real names of
117 117 the temporary files generated.'''
118 118 inname, outname = None, None
119 119 try:
120 120 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
121 121 fp = os.fdopen(infd, r'wb')
122 122 fp.write(s)
123 123 fp.close()
124 124 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
125 125 os.close(outfd)
126 126 cmd = cmd.replace('INFILE', inname)
127 127 cmd = cmd.replace('OUTFILE', outname)
128 128 code = os.system(cmd)
129 129 if pycompat.sysplatform == 'OpenVMS' and code & 1:
130 130 code = 0
131 131 if code:
132 132 raise error.Abort(_("command '%s' failed: %s") %
133 133 (cmd, explainexit(code)))
134 134 with open(outname, 'rb') as fp:
135 135 return fp.read()
136 136 finally:
137 137 try:
138 138 if inname:
139 139 os.unlink(inname)
140 140 except OSError:
141 141 pass
142 142 try:
143 143 if outname:
144 144 os.unlink(outname)
145 145 except OSError:
146 146 pass
147 147
148 148 _filtertable = {
149 149 'tempfile:': tempfilter,
150 150 'pipe:': pipefilter,
151 151 }
152 152
153 153 def filter(s, cmd):
154 154 "filter a string through a command that transforms its input to its output"
155 155 for name, fn in _filtertable.iteritems():
156 156 if cmd.startswith(name):
157 157 return fn(s, cmd[len(name):].lstrip())
158 158 return pipefilter(s, cmd)
159 159
160 160 def mainfrozen():
161 161 """return True if we are a frozen executable.
162 162
163 163 The code supports py2exe (most common, Windows only) and tools/freeze
164 164 (portable, not much used).
165 165 """
166 166 return (pycompat.safehasattr(sys, "frozen") or # new py2exe
167 167 pycompat.safehasattr(sys, "importers") or # old py2exe
168 168 imp.is_frozen(u"__main__")) # tools/freeze
169 169
170 170 _hgexecutable = None
171 171
172 172 def hgexecutable():
173 173 """return location of the 'hg' executable.
174 174
175 175 Defaults to $HG or 'hg' in the search path.
176 176 """
177 177 if _hgexecutable is None:
178 178 hg = encoding.environ.get('HG')
179 179 mainmod = sys.modules[r'__main__']
180 180 if hg:
181 181 _sethgexecutable(hg)
182 182 elif mainfrozen():
183 183 if getattr(sys, 'frozen', None) == 'macosx_app':
184 184 # Env variable set by py2app
185 185 _sethgexecutable(encoding.environ['EXECUTABLEPATH'])
186 186 else:
187 187 _sethgexecutable(pycompat.sysexecutable)
188 188 elif (os.path.basename(
189 189 pycompat.fsencode(getattr(mainmod, '__file__', ''))) == 'hg'):
190 190 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
191 191 else:
192 192 exe = findexe('hg') or os.path.basename(sys.argv[0])
193 193 _sethgexecutable(exe)
194 194 return _hgexecutable
195 195
196 196 def _sethgexecutable(path):
197 197 """set location of the 'hg' executable"""
198 198 global _hgexecutable
199 199 _hgexecutable = path
200 200
201 201 def _testfileno(f, stdf):
202 202 fileno = getattr(f, 'fileno', None)
203 203 try:
204 204 return fileno and fileno() == stdf.fileno()
205 205 except io.UnsupportedOperation:
206 206 return False # fileno() raised UnsupportedOperation
207 207
208 208 def isstdin(f):
209 209 return _testfileno(f, sys.__stdin__)
210 210
211 211 def isstdout(f):
212 212 return _testfileno(f, sys.__stdout__)
213 213
214 def protectstdio(uin, uout):
215 """Duplicate streams and redirect original to null if (uin, uout) are
216 stdio
217
218 Returns (fin, fout) which point to the original (uin, uout) fds, but
219 may be copy of (uin, uout). The returned streams can be considered
220 "owned" in that print(), exec(), etc. never reach to them.
221 """
222 uout.flush()
223 newfiles = []
224 nullfd = os.open(os.devnull, os.O_RDWR)
225 for f, sysf, mode in [(uin, stdin, r'rb'),
226 (uout, stdout, r'wb')]:
227 if f is sysf:
228 newfd = os.dup(f.fileno())
229 os.dup2(nullfd, f.fileno())
230 f = os.fdopen(newfd, mode)
231 newfiles.append(f)
232 os.close(nullfd)
233 return tuple(newfiles)
234
235 def restorestdio(uin, uout, fin, fout):
236 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
237 uout.flush()
238 for f, uif in [(fin, uin), (fout, uout)]:
239 if f is not uif:
240 os.dup2(f.fileno(), uif.fileno())
241 f.close()
242
214 243 def shellenviron(environ=None):
215 244 """return environ with optional override, useful for shelling out"""
216 245 def py2shell(val):
217 246 'convert python object into string that is useful to shell'
218 247 if val is None or val is False:
219 248 return '0'
220 249 if val is True:
221 250 return '1'
222 251 return pycompat.bytestr(val)
223 252 env = dict(encoding.environ)
224 253 if environ:
225 254 env.update((k, py2shell(v)) for k, v in environ.iteritems())
226 255 env['HG'] = hgexecutable()
227 256 return env
228 257
229 258 def system(cmd, environ=None, cwd=None, out=None):
230 259 '''enhanced shell command execution.
231 260 run with environment maybe modified, maybe in different dir.
232 261
233 262 if out is specified, it is assumed to be a file-like object that has a
234 263 write() method. stdout and stderr will be redirected to out.'''
235 264 try:
236 265 stdout.flush()
237 266 except Exception:
238 267 pass
239 268 cmd = quotecommand(cmd)
240 269 env = shellenviron(environ)
241 270 if out is None or isstdout(out):
242 271 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
243 272 env=env, cwd=cwd)
244 273 else:
245 274 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
246 275 env=env, cwd=cwd, stdout=subprocess.PIPE,
247 276 stderr=subprocess.STDOUT)
248 277 for line in iter(proc.stdout.readline, ''):
249 278 out.write(line)
250 279 proc.wait()
251 280 rc = proc.returncode
252 281 if pycompat.sysplatform == 'OpenVMS' and rc & 1:
253 282 rc = 0
254 283 return rc
255 284
256 285 def gui():
257 286 '''Are we running in a GUI?'''
258 287 if pycompat.isdarwin:
259 288 if 'SSH_CONNECTION' in encoding.environ:
260 289 # handle SSH access to a box where the user is logged in
261 290 return False
262 291 elif getattr(osutil, 'isgui', None):
263 292 # check if a CoreGraphics session is available
264 293 return osutil.isgui()
265 294 else:
266 295 # pure build; use a safe default
267 296 return True
268 297 else:
269 298 return pycompat.iswindows or encoding.environ.get("DISPLAY")
270 299
271 300 def hgcmd():
272 301 """Return the command used to execute current hg
273 302
274 303 This is different from hgexecutable() because on Windows we want
275 304 to avoid things opening new shell windows like batch files, so we
276 305 get either the python call or current executable.
277 306 """
278 307 if mainfrozen():
279 308 if getattr(sys, 'frozen', None) == 'macosx_app':
280 309 # Env variable set by py2app
281 310 return [encoding.environ['EXECUTABLEPATH']]
282 311 else:
283 312 return [pycompat.sysexecutable]
284 313 return _gethgcmd()
285 314
286 315 def rundetached(args, condfn):
287 316 """Execute the argument list in a detached process.
288 317
289 318 condfn is a callable which is called repeatedly and should return
290 319 True once the child process is known to have started successfully.
291 320 At this point, the child process PID is returned. If the child
292 321 process fails to start or finishes before condfn() evaluates to
293 322 True, return -1.
294 323 """
295 324 # Windows case is easier because the child process is either
296 325 # successfully starting and validating the condition or exiting
297 326 # on failure. We just poll on its PID. On Unix, if the child
298 327 # process fails to start, it will be left in a zombie state until
299 328 # the parent wait on it, which we cannot do since we expect a long
300 329 # running process on success. Instead we listen for SIGCHLD telling
301 330 # us our child process terminated.
302 331 terminated = set()
303 332 def handler(signum, frame):
304 333 terminated.add(os.wait())
305 334 prevhandler = None
306 335 SIGCHLD = getattr(signal, 'SIGCHLD', None)
307 336 if SIGCHLD is not None:
308 337 prevhandler = signal.signal(SIGCHLD, handler)
309 338 try:
310 339 pid = spawndetached(args)
311 340 while not condfn():
312 341 if ((pid in terminated or not testpid(pid))
313 342 and not condfn()):
314 343 return -1
315 344 time.sleep(0.1)
316 345 return pid
317 346 finally:
318 347 if prevhandler is not None:
319 348 signal.signal(signal.SIGCHLD, prevhandler)
General Comments 0
You need to be logged in to leave comments. Login now