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