##// END OF EJS Templates
py3: drop an unnecessary fsencode() before comparing with constant...
Martin von Zweigbergk -
r44055:47d983f0 default
parent child Browse files
Show More
@@ -1,640 +1,637
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 and os.path.basename(
292 pycompat.fsencode(getattr(mainmod, '__file__', ''))
293 )
294 == b'hg'
291 and os.path.basename(getattr(mainmod, '__file__', '')) == 'hg'
295 292 ):
296 293 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
297 294 else:
298 295 _sethgexecutable(
299 296 findexe(b'hg') or os.path.basename(pycompat.sysargv[0])
300 297 )
301 298 return _hgexecutable
302 299
303 300
304 301 def _sethgexecutable(path):
305 302 """set location of the 'hg' executable"""
306 303 global _hgexecutable
307 304 _hgexecutable = path
308 305
309 306
310 307 def _testfileno(f, stdf):
311 308 fileno = getattr(f, 'fileno', None)
312 309 try:
313 310 return fileno and fileno() == stdf.fileno()
314 311 except io.UnsupportedOperation:
315 312 return False # fileno() raised UnsupportedOperation
316 313
317 314
318 315 def isstdin(f):
319 316 return _testfileno(f, sys.__stdin__)
320 317
321 318
322 319 def isstdout(f):
323 320 return _testfileno(f, sys.__stdout__)
324 321
325 322
326 323 def protectstdio(uin, uout):
327 324 """Duplicate streams and redirect original if (uin, uout) are stdio
328 325
329 326 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
330 327 redirected to stderr so the output is still readable.
331 328
332 329 Returns (fin, fout) which point to the original (uin, uout) fds, but
333 330 may be copy of (uin, uout). The returned streams can be considered
334 331 "owned" in that print(), exec(), etc. never reach to them.
335 332 """
336 333 uout.flush()
337 334 fin, fout = uin, uout
338 335 if _testfileno(uin, stdin):
339 336 newfd = os.dup(uin.fileno())
340 337 nullfd = os.open(os.devnull, os.O_RDONLY)
341 338 os.dup2(nullfd, uin.fileno())
342 339 os.close(nullfd)
343 340 fin = os.fdopen(newfd, 'rb')
344 341 if _testfileno(uout, stdout):
345 342 newfd = os.dup(uout.fileno())
346 343 os.dup2(stderr.fileno(), uout.fileno())
347 344 fout = os.fdopen(newfd, 'wb')
348 345 return fin, fout
349 346
350 347
351 348 def restorestdio(uin, uout, fin, fout):
352 349 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
353 350 uout.flush()
354 351 for f, uif in [(fin, uin), (fout, uout)]:
355 352 if f is not uif:
356 353 os.dup2(f.fileno(), uif.fileno())
357 354 f.close()
358 355
359 356
360 357 def shellenviron(environ=None):
361 358 """return environ with optional override, useful for shelling out"""
362 359
363 360 def py2shell(val):
364 361 b'convert python object into string that is useful to shell'
365 362 if val is None or val is False:
366 363 return b'0'
367 364 if val is True:
368 365 return b'1'
369 366 return pycompat.bytestr(val)
370 367
371 368 env = dict(encoding.environ)
372 369 if environ:
373 370 env.update((k, py2shell(v)) for k, v in pycompat.iteritems(environ))
374 371 env[b'HG'] = hgexecutable()
375 372 return env
376 373
377 374
378 375 if pycompat.iswindows:
379 376
380 377 def shelltonative(cmd, env):
381 378 return platform.shelltocmdexe( # pytype: disable=module-attr
382 379 cmd, shellenviron(env)
383 380 )
384 381
385 382 tonativestr = encoding.strfromlocal
386 383 else:
387 384
388 385 def shelltonative(cmd, env):
389 386 return cmd
390 387
391 388 tonativestr = pycompat.identity
392 389
393 390
394 391 def tonativeenv(env):
395 392 '''convert the environment from bytes to strings suitable for Popen(), etc.
396 393 '''
397 394 return pycompat.rapply(tonativestr, env)
398 395
399 396
400 397 def system(cmd, environ=None, cwd=None, out=None):
401 398 '''enhanced shell command execution.
402 399 run with environment maybe modified, maybe in different dir.
403 400
404 401 if out is specified, it is assumed to be a file-like object that has a
405 402 write() method. stdout and stderr will be redirected to out.'''
406 403 try:
407 404 stdout.flush()
408 405 except Exception:
409 406 pass
410 407 cmd = quotecommand(cmd)
411 408 env = shellenviron(environ)
412 409 if out is None or isstdout(out):
413 410 rc = subprocess.call(
414 411 tonativestr(cmd),
415 412 shell=True,
416 413 close_fds=closefds,
417 414 env=tonativeenv(env),
418 415 cwd=pycompat.rapply(tonativestr, cwd),
419 416 )
420 417 else:
421 418 proc = subprocess.Popen(
422 419 tonativestr(cmd),
423 420 shell=True,
424 421 close_fds=closefds,
425 422 env=tonativeenv(env),
426 423 cwd=pycompat.rapply(tonativestr, cwd),
427 424 stdout=subprocess.PIPE,
428 425 stderr=subprocess.STDOUT,
429 426 )
430 427 for line in iter(proc.stdout.readline, b''):
431 428 out.write(line)
432 429 proc.wait()
433 430 rc = proc.returncode
434 431 if pycompat.sysplatform == b'OpenVMS' and rc & 1:
435 432 rc = 0
436 433 return rc
437 434
438 435
439 436 def gui():
440 437 '''Are we running in a GUI?'''
441 438 if pycompat.isdarwin:
442 439 if b'SSH_CONNECTION' in encoding.environ:
443 440 # handle SSH access to a box where the user is logged in
444 441 return False
445 442 elif getattr(osutil, 'isgui', None):
446 443 # check if a CoreGraphics session is available
447 444 return osutil.isgui()
448 445 else:
449 446 # pure build; use a safe default
450 447 return True
451 448 else:
452 449 return pycompat.iswindows or encoding.environ.get(b"DISPLAY")
453 450
454 451
455 452 def hgcmd():
456 453 """Return the command used to execute current hg
457 454
458 455 This is different from hgexecutable() because on Windows we want
459 456 to avoid things opening new shell windows like batch files, so we
460 457 get either the python call or current executable.
461 458 """
462 459 if mainfrozen():
463 460 if getattr(sys, 'frozen', None) == b'macosx_app':
464 461 # Env variable set by py2app
465 462 return [encoding.environ[b'EXECUTABLEPATH']]
466 463 else:
467 464 return [pycompat.sysexecutable]
468 465 return _gethgcmd()
469 466
470 467
471 468 def rundetached(args, condfn):
472 469 """Execute the argument list in a detached process.
473 470
474 471 condfn is a callable which is called repeatedly and should return
475 472 True once the child process is known to have started successfully.
476 473 At this point, the child process PID is returned. If the child
477 474 process fails to start or finishes before condfn() evaluates to
478 475 True, return -1.
479 476 """
480 477 # Windows case is easier because the child process is either
481 478 # successfully starting and validating the condition or exiting
482 479 # on failure. We just poll on its PID. On Unix, if the child
483 480 # process fails to start, it will be left in a zombie state until
484 481 # the parent wait on it, which we cannot do since we expect a long
485 482 # running process on success. Instead we listen for SIGCHLD telling
486 483 # us our child process terminated.
487 484 terminated = set()
488 485
489 486 def handler(signum, frame):
490 487 terminated.add(os.wait())
491 488
492 489 prevhandler = None
493 490 SIGCHLD = getattr(signal, 'SIGCHLD', None)
494 491 if SIGCHLD is not None:
495 492 prevhandler = signal.signal(SIGCHLD, handler)
496 493 try:
497 494 pid = spawndetached(args)
498 495 while not condfn():
499 496 if (pid in terminated or not testpid(pid)) and not condfn():
500 497 return -1
501 498 time.sleep(0.1)
502 499 return pid
503 500 finally:
504 501 if prevhandler is not None:
505 502 signal.signal(signal.SIGCHLD, prevhandler)
506 503
507 504
508 505 @contextlib.contextmanager
509 506 def uninterruptible(warn):
510 507 """Inhibit SIGINT handling on a region of code.
511 508
512 509 Note that if this is called in a non-main thread, it turns into a no-op.
513 510
514 511 Args:
515 512 warn: A callable which takes no arguments, and returns True if the
516 513 previous signal handling should be restored.
517 514 """
518 515
519 516 oldsiginthandler = [signal.getsignal(signal.SIGINT)]
520 517 shouldbail = []
521 518
522 519 def disabledsiginthandler(*args):
523 520 if warn():
524 521 signal.signal(signal.SIGINT, oldsiginthandler[0])
525 522 del oldsiginthandler[0]
526 523 shouldbail.append(True)
527 524
528 525 try:
529 526 try:
530 527 signal.signal(signal.SIGINT, disabledsiginthandler)
531 528 except ValueError:
532 529 # wrong thread, oh well, we tried
533 530 del oldsiginthandler[0]
534 531 yield
535 532 finally:
536 533 if oldsiginthandler:
537 534 signal.signal(signal.SIGINT, oldsiginthandler[0])
538 535 if shouldbail:
539 536 raise KeyboardInterrupt
540 537
541 538
542 539 if pycompat.iswindows:
543 540 # no fork on Windows, but we can create a detached process
544 541 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
545 542 # No stdlib constant exists for this value
546 543 DETACHED_PROCESS = 0x00000008
547 544 # Following creation flags might create a console GUI window.
548 545 # Using subprocess.CREATE_NEW_CONSOLE might helps.
549 546 # See https://phab.mercurial-scm.org/D1701 for discussion
550 547 _creationflags = (
551 548 DETACHED_PROCESS
552 549 | subprocess.CREATE_NEW_PROCESS_GROUP # pytype: disable=module-attr
553 550 )
554 551
555 552 def runbgcommand(
556 553 script, env, shell=False, stdout=None, stderr=None, ensurestart=True
557 554 ):
558 555 '''Spawn a command without waiting for it to finish.'''
559 556 # we can't use close_fds *and* redirect stdin. I'm not sure that we
560 557 # need to because the detached process has no console connection.
561 558 subprocess.Popen(
562 559 tonativestr(script),
563 560 shell=shell,
564 561 env=tonativeenv(env),
565 562 close_fds=True,
566 563 creationflags=_creationflags,
567 564 stdout=stdout,
568 565 stderr=stderr,
569 566 )
570 567
571 568
572 569 else:
573 570
574 571 def runbgcommand(
575 572 cmd, env, shell=False, stdout=None, stderr=None, ensurestart=True
576 573 ):
577 574 '''Spawn a command without waiting for it to finish.'''
578 575 # double-fork to completely detach from the parent process
579 576 # based on http://code.activestate.com/recipes/278731
580 577 pid = os.fork()
581 578 if pid:
582 579 if not ensurestart:
583 580 return
584 581 # Parent process
585 582 (_pid, status) = os.waitpid(pid, 0)
586 583 if os.WIFEXITED(status):
587 584 returncode = os.WEXITSTATUS(status)
588 585 else:
589 586 returncode = -(os.WTERMSIG(status))
590 587 if returncode != 0:
591 588 # The child process's return code is 0 on success, an errno
592 589 # value on failure, or 255 if we don't have a valid errno
593 590 # value.
594 591 #
595 592 # (It would be slightly nicer to return the full exception info
596 593 # over a pipe as the subprocess module does. For now it
597 594 # doesn't seem worth adding that complexity here, though.)
598 595 if returncode == 255:
599 596 returncode = errno.EINVAL
600 597 raise OSError(
601 598 returncode,
602 599 b'error running %r: %s' % (cmd, os.strerror(returncode)),
603 600 )
604 601 return
605 602
606 603 returncode = 255
607 604 try:
608 605 # Start a new session
609 606 os.setsid()
610 607
611 608 stdin = open(os.devnull, b'r')
612 609 if stdout is None:
613 610 stdout = open(os.devnull, b'w')
614 611 if stderr is None:
615 612 stderr = open(os.devnull, b'w')
616 613
617 614 # connect stdin to devnull to make sure the subprocess can't
618 615 # muck up that stream for mercurial.
619 616 subprocess.Popen(
620 617 cmd,
621 618 shell=shell,
622 619 env=env,
623 620 close_fds=True,
624 621 stdin=stdin,
625 622 stdout=stdout,
626 623 stderr=stderr,
627 624 )
628 625 returncode = 0
629 626 except EnvironmentError as ex:
630 627 returncode = ex.errno & 0xFF
631 628 if returncode == 0:
632 629 # This shouldn't happen, but just in case make sure the
633 630 # return code is never 0 here.
634 631 returncode = 255
635 632 except Exception:
636 633 returncode = 255
637 634 finally:
638 635 # mission accomplished, this child needs to exit and not
639 636 # continue the hg process here.
640 637 os._exit(returncode)
General Comments 0
You need to be logged in to leave comments. Login now