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