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