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