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