##// END OF EJS Templates
formatting: add missing newline...
Raphaël Gomès -
r45330:79f6f856 stable
parent child Browse files
Show More
@@ -1,678 +1,679 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 contextlib
13 13 import errno
14 14 import io
15 15 import os
16 16 import signal
17 17 import subprocess
18 18 import sys
19 19 import threading
20 20 import time
21 21
22 22 from ..i18n import _
23 23 from ..pycompat import (
24 24 getattr,
25 25 open,
26 26 )
27 27
28 28 from .. import (
29 29 encoding,
30 30 error,
31 31 policy,
32 32 pycompat,
33 33 )
34 34
35 35 # Import like this to keep import-checker happy
36 36 from ..utils import resourceutil
37 37
38 38 osutil = policy.importmod('osutil')
39 39
40 40 stderr = pycompat.stderr
41 41 stdin = pycompat.stdin
42 42 stdout = pycompat.stdout
43 43
44 44
45 45 def isatty(fp):
46 46 try:
47 47 return fp.isatty()
48 48 except AttributeError:
49 49 return False
50 50
51 51
52 52 # glibc determines buffering on first write to stdout - if we replace a TTY
53 53 # destined stdout with a pipe destined stdout (e.g. pager), we want line
54 54 # buffering (or unbuffered, on Windows)
55 55 if isatty(stdout):
56 56 if pycompat.iswindows:
57 57 # Windows doesn't support line buffering
58 58 stdout = os.fdopen(stdout.fileno(), 'wb', 0)
59 59 elif not pycompat.ispy3:
60 60 # on Python 3, stdout (sys.stdout.buffer) is already line buffered and
61 61 # buffering=1 is not handled in binary mode
62 62 stdout = os.fdopen(stdout.fileno(), 'wb', 1)
63 63
64 64 if pycompat.iswindows:
65 65 from .. import windows as platform
66 66
67 67 stdout = platform.winstdout(stdout)
68 68 else:
69 69 from .. import posix as platform
70 70
71 71 findexe = platform.findexe
72 72 _gethgcmd = platform.gethgcmd
73 73 getuser = platform.getuser
74 74 getpid = os.getpid
75 75 hidewindow = platform.hidewindow
76 76 quotecommand = platform.quotecommand
77 77 readpipe = platform.readpipe
78 78 setbinary = platform.setbinary
79 79 setsignalhandler = platform.setsignalhandler
80 80 shellquote = platform.shellquote
81 81 shellsplit = platform.shellsplit
82 82 spawndetached = platform.spawndetached
83 83 sshargs = platform.sshargs
84 84 testpid = platform.testpid
85 85
86 86 try:
87 87 setprocname = osutil.setprocname
88 88 except AttributeError:
89 89 pass
90 90 try:
91 91 unblocksignal = osutil.unblocksignal
92 92 except AttributeError:
93 93 pass
94 94
95 95 closefds = pycompat.isposix
96 96
97 97
98 98 def explainexit(code):
99 99 """return a message describing a subprocess status
100 100 (codes from kill are negative - not os.system/wait encoding)"""
101 101 if code >= 0:
102 102 return _(b"exited with status %d") % code
103 103 return _(b"killed by signal %d") % -code
104 104
105 105
106 106 class _pfile(object):
107 107 """File-like wrapper for a stream opened by subprocess.Popen()"""
108 108
109 109 def __init__(self, proc, fp):
110 110 self._proc = proc
111 111 self._fp = fp
112 112
113 113 def close(self):
114 114 # unlike os.popen(), this returns an integer in subprocess coding
115 115 self._fp.close()
116 116 return self._proc.wait()
117 117
118 118 def __iter__(self):
119 119 return iter(self._fp)
120 120
121 121 def __getattr__(self, attr):
122 122 return getattr(self._fp, attr)
123 123
124 124 def __enter__(self):
125 125 return self
126 126
127 127 def __exit__(self, exc_type, exc_value, exc_tb):
128 128 self.close()
129 129
130 130
131 131 def popen(cmd, mode=b'rb', bufsize=-1):
132 132 if mode == b'rb':
133 133 return _popenreader(cmd, bufsize)
134 134 elif mode == b'wb':
135 135 return _popenwriter(cmd, bufsize)
136 136 raise error.ProgrammingError(b'unsupported mode: %r' % mode)
137 137
138 138
139 139 def _popenreader(cmd, bufsize):
140 140 p = subprocess.Popen(
141 141 tonativestr(quotecommand(cmd)),
142 142 shell=True,
143 143 bufsize=bufsize,
144 144 close_fds=closefds,
145 145 stdout=subprocess.PIPE,
146 146 )
147 147 return _pfile(p, p.stdout)
148 148
149 149
150 150 def _popenwriter(cmd, bufsize):
151 151 p = subprocess.Popen(
152 152 tonativestr(quotecommand(cmd)),
153 153 shell=True,
154 154 bufsize=bufsize,
155 155 close_fds=closefds,
156 156 stdin=subprocess.PIPE,
157 157 )
158 158 return _pfile(p, p.stdin)
159 159
160 160
161 161 def popen2(cmd, env=None):
162 162 # Setting bufsize to -1 lets the system decide the buffer size.
163 163 # The default for bufsize is 0, meaning unbuffered. This leads to
164 164 # poor performance on Mac OS X: http://bugs.python.org/issue4194
165 165 p = subprocess.Popen(
166 166 tonativestr(cmd),
167 167 shell=True,
168 168 bufsize=-1,
169 169 close_fds=closefds,
170 170 stdin=subprocess.PIPE,
171 171 stdout=subprocess.PIPE,
172 172 env=tonativeenv(env),
173 173 )
174 174 return p.stdin, p.stdout
175 175
176 176
177 177 def popen3(cmd, env=None):
178 178 stdin, stdout, stderr, p = popen4(cmd, env)
179 179 return stdin, stdout, stderr
180 180
181 181
182 182 def popen4(cmd, env=None, bufsize=-1):
183 183 p = subprocess.Popen(
184 184 tonativestr(cmd),
185 185 shell=True,
186 186 bufsize=bufsize,
187 187 close_fds=closefds,
188 188 stdin=subprocess.PIPE,
189 189 stdout=subprocess.PIPE,
190 190 stderr=subprocess.PIPE,
191 191 env=tonativeenv(env),
192 192 )
193 193 return p.stdin, p.stdout, p.stderr, p
194 194
195 195
196 196 def pipefilter(s, cmd):
197 197 '''filter string S through command CMD, returning its output'''
198 198 p = subprocess.Popen(
199 199 tonativestr(cmd),
200 200 shell=True,
201 201 close_fds=closefds,
202 202 stdin=subprocess.PIPE,
203 203 stdout=subprocess.PIPE,
204 204 )
205 205 pout, perr = p.communicate(s)
206 206 return pout
207 207
208 208
209 209 def tempfilter(s, cmd):
210 210 '''filter string S through a pair of temporary files with CMD.
211 211 CMD is used as a template to create the real command to be run,
212 212 with the strings INFILE and OUTFILE replaced by the real names of
213 213 the temporary files generated.'''
214 214 inname, outname = None, None
215 215 try:
216 216 infd, inname = pycompat.mkstemp(prefix=b'hg-filter-in-')
217 217 fp = os.fdopen(infd, 'wb')
218 218 fp.write(s)
219 219 fp.close()
220 220 outfd, outname = pycompat.mkstemp(prefix=b'hg-filter-out-')
221 221 os.close(outfd)
222 222 cmd = cmd.replace(b'INFILE', inname)
223 223 cmd = cmd.replace(b'OUTFILE', outname)
224 224 code = system(cmd)
225 225 if pycompat.sysplatform == b'OpenVMS' and code & 1:
226 226 code = 0
227 227 if code:
228 228 raise error.Abort(
229 229 _(b"command '%s' failed: %s") % (cmd, explainexit(code))
230 230 )
231 231 with open(outname, b'rb') as fp:
232 232 return fp.read()
233 233 finally:
234 234 try:
235 235 if inname:
236 236 os.unlink(inname)
237 237 except OSError:
238 238 pass
239 239 try:
240 240 if outname:
241 241 os.unlink(outname)
242 242 except OSError:
243 243 pass
244 244
245 245
246 246 _filtertable = {
247 247 b'tempfile:': tempfilter,
248 248 b'pipe:': pipefilter,
249 249 }
250 250
251 251
252 252 def filter(s, cmd):
253 253 """filter a string through a command that transforms its input to its
254 254 output"""
255 255 for name, fn in pycompat.iteritems(_filtertable):
256 256 if cmd.startswith(name):
257 257 return fn(s, cmd[len(name) :].lstrip())
258 258 return pipefilter(s, cmd)
259 259
260 260
261 261 _hgexecutable = None
262 262
263 263
264 264 def hgexecutable():
265 265 """return location of the 'hg' executable.
266 266
267 267 Defaults to $HG or 'hg' in the search path.
268 268 """
269 269 if _hgexecutable is None:
270 270 hg = encoding.environ.get(b'HG')
271 271 mainmod = sys.modules['__main__']
272 272 if hg:
273 273 _sethgexecutable(hg)
274 274 elif resourceutil.mainfrozen():
275 275 if getattr(sys, 'frozen', None) == 'macosx_app':
276 276 # Env variable set by py2app
277 277 _sethgexecutable(encoding.environ[b'EXECUTABLEPATH'])
278 278 else:
279 279 _sethgexecutable(pycompat.sysexecutable)
280 280 elif (
281 281 not pycompat.iswindows
282 282 and os.path.basename(getattr(mainmod, '__file__', '')) == 'hg'
283 283 ):
284 284 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
285 285 else:
286 286 _sethgexecutable(
287 287 findexe(b'hg') or os.path.basename(pycompat.sysargv[0])
288 288 )
289 289 return _hgexecutable
290 290
291 291
292 292 def _sethgexecutable(path):
293 293 """set location of the 'hg' executable"""
294 294 global _hgexecutable
295 295 _hgexecutable = path
296 296
297 297
298 298 def _testfileno(f, stdf):
299 299 fileno = getattr(f, 'fileno', None)
300 300 try:
301 301 return fileno and fileno() == stdf.fileno()
302 302 except io.UnsupportedOperation:
303 303 return False # fileno() raised UnsupportedOperation
304 304
305 305
306 306 def isstdin(f):
307 307 return _testfileno(f, sys.__stdin__)
308 308
309 309
310 310 def isstdout(f):
311 311 return _testfileno(f, sys.__stdout__)
312 312
313 313
314 314 def protectstdio(uin, uout):
315 315 """Duplicate streams and redirect original if (uin, uout) are stdio
316 316
317 317 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
318 318 redirected to stderr so the output is still readable.
319 319
320 320 Returns (fin, fout) which point to the original (uin, uout) fds, but
321 321 may be copy of (uin, uout). The returned streams can be considered
322 322 "owned" in that print(), exec(), etc. never reach to them.
323 323 """
324 324 uout.flush()
325 325 fin, fout = uin, uout
326 326 if _testfileno(uin, stdin):
327 327 newfd = os.dup(uin.fileno())
328 328 nullfd = os.open(os.devnull, os.O_RDONLY)
329 329 os.dup2(nullfd, uin.fileno())
330 330 os.close(nullfd)
331 331 fin = os.fdopen(newfd, 'rb')
332 332 if _testfileno(uout, stdout):
333 333 newfd = os.dup(uout.fileno())
334 334 os.dup2(stderr.fileno(), uout.fileno())
335 335 fout = os.fdopen(newfd, 'wb')
336 336 return fin, fout
337 337
338 338
339 339 def restorestdio(uin, uout, fin, fout):
340 340 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
341 341 uout.flush()
342 342 for f, uif in [(fin, uin), (fout, uout)]:
343 343 if f is not uif:
344 344 os.dup2(f.fileno(), uif.fileno())
345 345 f.close()
346 346
347 347
348 348 def shellenviron(environ=None):
349 349 """return environ with optional override, useful for shelling out"""
350 350
351 351 def py2shell(val):
352 352 """convert python object into string that is useful to shell"""
353 353 if val is None or val is False:
354 354 return b'0'
355 355 if val is True:
356 356 return b'1'
357 357 return pycompat.bytestr(val)
358 358
359 359 env = dict(encoding.environ)
360 360 if environ:
361 361 env.update((k, py2shell(v)) for k, v in pycompat.iteritems(environ))
362 362 env[b'HG'] = hgexecutable()
363 363 return env
364 364
365 365
366 366 if pycompat.iswindows:
367 367
368 368 def shelltonative(cmd, env):
369 369 return platform.shelltocmdexe( # pytype: disable=module-attr
370 370 cmd, shellenviron(env)
371 371 )
372 372
373 373 tonativestr = encoding.strfromlocal
374 374 else:
375 375
376 376 def shelltonative(cmd, env):
377 377 return cmd
378 378
379 379 tonativestr = pycompat.identity
380 380
381 381
382 382 def tonativeenv(env):
383 383 '''convert the environment from bytes to strings suitable for Popen(), etc.
384 384 '''
385 385 return pycompat.rapply(tonativestr, env)
386 386
387 387
388 388 def system(cmd, environ=None, cwd=None, out=None):
389 389 '''enhanced shell command execution.
390 390 run with environment maybe modified, maybe in different dir.
391 391
392 392 if out is specified, it is assumed to be a file-like object that has a
393 393 write() method. stdout and stderr will be redirected to out.'''
394 394 try:
395 395 stdout.flush()
396 396 except Exception:
397 397 pass
398 398 cmd = quotecommand(cmd)
399 399 env = shellenviron(environ)
400 400 if out is None or isstdout(out):
401 401 rc = subprocess.call(
402 402 tonativestr(cmd),
403 403 shell=True,
404 404 close_fds=closefds,
405 405 env=tonativeenv(env),
406 406 cwd=pycompat.rapply(tonativestr, cwd),
407 407 )
408 408 else:
409 409 proc = subprocess.Popen(
410 410 tonativestr(cmd),
411 411 shell=True,
412 412 close_fds=closefds,
413 413 env=tonativeenv(env),
414 414 cwd=pycompat.rapply(tonativestr, cwd),
415 415 stdout=subprocess.PIPE,
416 416 stderr=subprocess.STDOUT,
417 417 )
418 418 for line in iter(proc.stdout.readline, b''):
419 419 out.write(line)
420 420 proc.wait()
421 421 rc = proc.returncode
422 422 if pycompat.sysplatform == b'OpenVMS' and rc & 1:
423 423 rc = 0
424 424 return rc
425 425
426 426
427 427 _is_gui = None
428 428
429 429
430 430 def _gui():
431 431 '''Are we running in a GUI?'''
432 432 if pycompat.isdarwin:
433 433 if b'SSH_CONNECTION' in encoding.environ:
434 434 # handle SSH access to a box where the user is logged in
435 435 return False
436 436 elif getattr(osutil, 'isgui', None):
437 437 # check if a CoreGraphics session is available
438 438 return osutil.isgui()
439 439 else:
440 440 # pure build; use a safe default
441 441 return True
442 442 else:
443 443 return pycompat.iswindows or encoding.environ.get(b"DISPLAY")
444 444
445 445
446 446 def gui():
447 447 global _is_gui
448 448 if _is_gui is None:
449 449 _is_gui = _gui()
450 450 return _is_gui
451 451
452 452
453 453 def hgcmd():
454 454 """Return the command used to execute current hg
455 455
456 456 This is different from hgexecutable() because on Windows we want
457 457 to avoid things opening new shell windows like batch files, so we
458 458 get either the python call or current executable.
459 459 """
460 460 if resourceutil.mainfrozen():
461 461 if getattr(sys, 'frozen', None) == 'macosx_app':
462 462 # Env variable set by py2app
463 463 return [encoding.environ[b'EXECUTABLEPATH']]
464 464 else:
465 465 return [pycompat.sysexecutable]
466 466 return _gethgcmd()
467 467
468 468
469 469 def rundetached(args, condfn):
470 470 """Execute the argument list in a detached process.
471 471
472 472 condfn is a callable which is called repeatedly and should return
473 473 True once the child process is known to have started successfully.
474 474 At this point, the child process PID is returned. If the child
475 475 process fails to start or finishes before condfn() evaluates to
476 476 True, return -1.
477 477 """
478 478 # Windows case is easier because the child process is either
479 479 # successfully starting and validating the condition or exiting
480 480 # on failure. We just poll on its PID. On Unix, if the child
481 481 # process fails to start, it will be left in a zombie state until
482 482 # the parent wait on it, which we cannot do since we expect a long
483 483 # running process on success. Instead we listen for SIGCHLD telling
484 484 # us our child process terminated.
485 485 terminated = set()
486 486
487 487 def handler(signum, frame):
488 488 terminated.add(os.wait())
489 489
490 490 prevhandler = None
491 491 SIGCHLD = getattr(signal, 'SIGCHLD', None)
492 492 if SIGCHLD is not None:
493 493 prevhandler = signal.signal(SIGCHLD, handler)
494 494 try:
495 495 pid = spawndetached(args)
496 496 while not condfn():
497 497 if (pid in terminated or not testpid(pid)) and not condfn():
498 498 return -1
499 499 time.sleep(0.1)
500 500 return pid
501 501 finally:
502 502 if prevhandler is not None:
503 503 signal.signal(signal.SIGCHLD, prevhandler)
504 504
505 505
506 506 @contextlib.contextmanager
507 507 def uninterruptible(warn):
508 508 """Inhibit SIGINT handling on a region of code.
509 509
510 510 Note that if this is called in a non-main thread, it turns into a no-op.
511 511
512 512 Args:
513 513 warn: A callable which takes no arguments, and returns True if the
514 514 previous signal handling should be restored.
515 515 """
516 516
517 517 oldsiginthandler = [signal.getsignal(signal.SIGINT)]
518 518 shouldbail = []
519 519
520 520 def disabledsiginthandler(*args):
521 521 if warn():
522 522 signal.signal(signal.SIGINT, oldsiginthandler[0])
523 523 del oldsiginthandler[0]
524 524 shouldbail.append(True)
525 525
526 526 try:
527 527 try:
528 528 signal.signal(signal.SIGINT, disabledsiginthandler)
529 529 except ValueError:
530 530 # wrong thread, oh well, we tried
531 531 del oldsiginthandler[0]
532 532 yield
533 533 finally:
534 534 if oldsiginthandler:
535 535 signal.signal(signal.SIGINT, oldsiginthandler[0])
536 536 if shouldbail:
537 537 raise KeyboardInterrupt
538 538
539 539
540 540 if pycompat.iswindows:
541 541 # no fork on Windows, but we can create a detached process
542 542 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
543 543 # No stdlib constant exists for this value
544 544 DETACHED_PROCESS = 0x00000008
545 545 # Following creation flags might create a console GUI window.
546 546 # Using subprocess.CREATE_NEW_CONSOLE might helps.
547 547 # See https://phab.mercurial-scm.org/D1701 for discussion
548 548 _creationflags = (
549 549 DETACHED_PROCESS
550 550 | subprocess.CREATE_NEW_PROCESS_GROUP # pytype: disable=module-attr
551 551 )
552 552
553 553 def runbgcommand(
554 554 script,
555 555 env,
556 556 shell=False,
557 557 stdout=None,
558 558 stderr=None,
559 559 ensurestart=True,
560 560 record_wait=None,
561 561 ):
562 562 '''Spawn a command without waiting for it to finish.'''
563 563 # we can't use close_fds *and* redirect stdin. I'm not sure that we
564 564 # need to because the detached process has no console connection.
565 565 p = subprocess.Popen(
566 566 tonativestr(script),
567 567 shell=shell,
568 568 env=tonativeenv(env),
569 569 close_fds=True,
570 570 creationflags=_creationflags,
571 571 stdout=stdout,
572 572 stderr=stderr,
573 573 )
574 574 if record_wait is not None:
575 575 record_wait(p.wait)
576 576
577 577
578 578 else:
579 579
580 580 def runbgcommand(
581 581 cmd,
582 582 env,
583 583 shell=False,
584 584 stdout=None,
585 585 stderr=None,
586 586 ensurestart=True,
587 587 record_wait=None,
588 588 ):
589 589 '''Spawn a command without waiting for it to finish.
590 590
591 591
592 592 When `record_wait` is not None, the spawned process will not be fully
593 593 detached and the `record_wait` argument will be called with a the
594 594 `Subprocess.wait` function for the spawned process. This is mostly
595 595 useful for developers that need to make sure the spawned process
596 596 finished before a certain point. (eg: writing test)'''
597 597 if pycompat.isdarwin:
598 598 # avoid crash in CoreFoundation in case another thread
599 599 # calls gui() while we're calling fork().
600 600 gui()
601 601
602 602 # double-fork to completely detach from the parent process
603 603 # based on http://code.activestate.com/recipes/278731
604 604 if record_wait is None:
605 605 pid = os.fork()
606 606 if pid:
607 607 if not ensurestart:
608 608 # Even though we're not waiting on the child process,
609 609 # we still must call waitpid() on it at some point so
610 610 # it's not a zombie/defunct. This is especially relevant for
611 611 # chg since the parent process won't die anytime soon.
612 612 # We use a thread to make the overhead tiny.
613 613 def _do_wait():
614 614 os.waitpid(pid, 0)
615
615 616 threading.Thread(target=_do_wait, daemon=True).start()
616 617 return
617 618 # Parent process
618 619 (_pid, status) = os.waitpid(pid, 0)
619 620 if os.WIFEXITED(status):
620 621 returncode = os.WEXITSTATUS(status)
621 622 else:
622 623 returncode = -(os.WTERMSIG(status))
623 624 if returncode != 0:
624 625 # The child process's return code is 0 on success, an errno
625 626 # value on failure, or 255 if we don't have a valid errno
626 627 # value.
627 628 #
628 629 # (It would be slightly nicer to return the full exception info
629 630 # over a pipe as the subprocess module does. For now it
630 631 # doesn't seem worth adding that complexity here, though.)
631 632 if returncode == 255:
632 633 returncode = errno.EINVAL
633 634 raise OSError(
634 635 returncode,
635 636 b'error running %r: %s'
636 637 % (cmd, os.strerror(returncode)),
637 638 )
638 639 return
639 640
640 641 returncode = 255
641 642 try:
642 643 if record_wait is None:
643 644 # Start a new session
644 645 os.setsid()
645 646
646 647 stdin = open(os.devnull, b'r')
647 648 if stdout is None:
648 649 stdout = open(os.devnull, b'w')
649 650 if stderr is None:
650 651 stderr = open(os.devnull, b'w')
651 652
652 653 # connect stdin to devnull to make sure the subprocess can't
653 654 # muck up that stream for mercurial.
654 655 p = subprocess.Popen(
655 656 cmd,
656 657 shell=shell,
657 658 env=env,
658 659 close_fds=True,
659 660 stdin=stdin,
660 661 stdout=stdout,
661 662 stderr=stderr,
662 663 )
663 664 if record_wait is not None:
664 665 record_wait(p.wait)
665 666 returncode = 0
666 667 except EnvironmentError as ex:
667 668 returncode = ex.errno & 0xFF
668 669 if returncode == 0:
669 670 # This shouldn't happen, but just in case make sure the
670 671 # return code is never 0 here.
671 672 returncode = 255
672 673 except Exception:
673 674 returncode = 255
674 675 finally:
675 676 # mission accomplished, this child needs to exit and not
676 677 # continue the hg process here.
677 678 if record_wait is None:
678 679 os._exit(returncode)
General Comments 0
You need to be logged in to leave comments. Login now