##// END OF EJS Templates
procutil: drop unused 'newlines' option from popen*() (API)...
Yuya Nishihara -
r37482:632b9289 default
parent child Browse files
Show More
@@ -1,409 +1,407 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 imp
14 14 import io
15 15 import os
16 16 import signal
17 17 import subprocess
18 18 import sys
19 19 import tempfile
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
46 46 if isatty(stdout):
47 47 stdout = os.fdopen(stdout.fileno(), r'wb', 1)
48 48
49 49 if pycompat.iswindows:
50 50 from .. import windows as platform
51 51 stdout = platform.winstdout(stdout)
52 52 else:
53 53 from .. import posix as platform
54 54
55 55 findexe = platform.findexe
56 56 _gethgcmd = platform.gethgcmd
57 57 getuser = platform.getuser
58 58 getpid = os.getpid
59 59 hidewindow = platform.hidewindow
60 60 quotecommand = platform.quotecommand
61 61 readpipe = platform.readpipe
62 62 setbinary = platform.setbinary
63 63 setsignalhandler = platform.setsignalhandler
64 64 shellquote = platform.shellquote
65 65 shellsplit = platform.shellsplit
66 66 spawndetached = platform.spawndetached
67 67 sshargs = platform.sshargs
68 68 testpid = platform.testpid
69 69
70 70 try:
71 71 setprocname = osutil.setprocname
72 72 except AttributeError:
73 73 pass
74 74 try:
75 75 unblocksignal = osutil.unblocksignal
76 76 except AttributeError:
77 77 pass
78 78
79 79 closefds = pycompat.isposix
80 80
81 81 def explainexit(code):
82 82 """return a message describing a subprocess status
83 83 (codes from kill are negative - not os.system/wait encoding)"""
84 84 if code >= 0:
85 85 return _("exited with status %d") % code
86 86 return _("killed by signal %d") % -code
87 87
88 88 class _pfile(object):
89 89 """File-like wrapper for a stream opened by subprocess.Popen()"""
90 90
91 91 def __init__(self, proc, fp):
92 92 self._proc = proc
93 93 self._fp = fp
94 94
95 95 def close(self):
96 96 # unlike os.popen(), this returns an integer in subprocess coding
97 97 self._fp.close()
98 98 return self._proc.wait()
99 99
100 100 def __iter__(self):
101 101 return iter(self._fp)
102 102
103 103 def __getattr__(self, attr):
104 104 return getattr(self._fp, attr)
105 105
106 106 def __enter__(self):
107 107 return self
108 108
109 109 def __exit__(self, exc_type, exc_value, exc_tb):
110 110 self.close()
111 111
112 112 def popen(cmd, mode='rb', bufsize=-1):
113 113 if mode == 'rb':
114 114 return _popenreader(cmd, bufsize)
115 115 elif mode == 'wb':
116 116 return _popenwriter(cmd, bufsize)
117 117 raise error.ProgrammingError('unsupported mode: %r' % mode)
118 118
119 119 def _popenreader(cmd, bufsize):
120 120 p = subprocess.Popen(quotecommand(cmd), shell=True, bufsize=bufsize,
121 121 close_fds=closefds,
122 122 stdout=subprocess.PIPE)
123 123 return _pfile(p, p.stdout)
124 124
125 125 def _popenwriter(cmd, bufsize):
126 126 p = subprocess.Popen(quotecommand(cmd), shell=True, bufsize=bufsize,
127 127 close_fds=closefds,
128 128 stdin=subprocess.PIPE)
129 129 return _pfile(p, p.stdin)
130 130
131 def popen2(cmd, env=None, newlines=False):
131 def popen2(cmd, env=None):
132 132 # Setting bufsize to -1 lets the system decide the buffer size.
133 133 # The default for bufsize is 0, meaning unbuffered. This leads to
134 134 # poor performance on Mac OS X: http://bugs.python.org/issue4194
135 135 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
136 136 close_fds=closefds,
137 137 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
138 universal_newlines=newlines,
139 138 env=env)
140 139 return p.stdin, p.stdout
141 140
142 def popen3(cmd, env=None, newlines=False):
143 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
141 def popen3(cmd, env=None):
142 stdin, stdout, stderr, p = popen4(cmd, env)
144 143 return stdin, stdout, stderr
145 144
146 def popen4(cmd, env=None, newlines=False, bufsize=-1):
145 def popen4(cmd, env=None, bufsize=-1):
147 146 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
148 147 close_fds=closefds,
149 148 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
150 149 stderr=subprocess.PIPE,
151 universal_newlines=newlines,
152 150 env=env)
153 151 return p.stdin, p.stdout, p.stderr, p
154 152
155 153 def pipefilter(s, cmd):
156 154 '''filter string S through command CMD, returning its output'''
157 155 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
158 156 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
159 157 pout, perr = p.communicate(s)
160 158 return pout
161 159
162 160 def tempfilter(s, cmd):
163 161 '''filter string S through a pair of temporary files with CMD.
164 162 CMD is used as a template to create the real command to be run,
165 163 with the strings INFILE and OUTFILE replaced by the real names of
166 164 the temporary files generated.'''
167 165 inname, outname = None, None
168 166 try:
169 167 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
170 168 fp = os.fdopen(infd, r'wb')
171 169 fp.write(s)
172 170 fp.close()
173 171 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
174 172 os.close(outfd)
175 173 cmd = cmd.replace('INFILE', inname)
176 174 cmd = cmd.replace('OUTFILE', outname)
177 175 code = system(cmd)
178 176 if pycompat.sysplatform == 'OpenVMS' and code & 1:
179 177 code = 0
180 178 if code:
181 179 raise error.Abort(_("command '%s' failed: %s") %
182 180 (cmd, explainexit(code)))
183 181 with open(outname, 'rb') as fp:
184 182 return fp.read()
185 183 finally:
186 184 try:
187 185 if inname:
188 186 os.unlink(inname)
189 187 except OSError:
190 188 pass
191 189 try:
192 190 if outname:
193 191 os.unlink(outname)
194 192 except OSError:
195 193 pass
196 194
197 195 _filtertable = {
198 196 'tempfile:': tempfilter,
199 197 'pipe:': pipefilter,
200 198 }
201 199
202 200 def filter(s, cmd):
203 201 "filter a string through a command that transforms its input to its output"
204 202 for name, fn in _filtertable.iteritems():
205 203 if cmd.startswith(name):
206 204 return fn(s, cmd[len(name):].lstrip())
207 205 return pipefilter(s, cmd)
208 206
209 207 def mainfrozen():
210 208 """return True if we are a frozen executable.
211 209
212 210 The code supports py2exe (most common, Windows only) and tools/freeze
213 211 (portable, not much used).
214 212 """
215 213 return (pycompat.safehasattr(sys, "frozen") or # new py2exe
216 214 pycompat.safehasattr(sys, "importers") or # old py2exe
217 215 imp.is_frozen(u"__main__")) # tools/freeze
218 216
219 217 _hgexecutable = None
220 218
221 219 def hgexecutable():
222 220 """return location of the 'hg' executable.
223 221
224 222 Defaults to $HG or 'hg' in the search path.
225 223 """
226 224 if _hgexecutable is None:
227 225 hg = encoding.environ.get('HG')
228 226 mainmod = sys.modules[r'__main__']
229 227 if hg:
230 228 _sethgexecutable(hg)
231 229 elif mainfrozen():
232 230 if getattr(sys, 'frozen', None) == 'macosx_app':
233 231 # Env variable set by py2app
234 232 _sethgexecutable(encoding.environ['EXECUTABLEPATH'])
235 233 else:
236 234 _sethgexecutable(pycompat.sysexecutable)
237 235 elif (os.path.basename(
238 236 pycompat.fsencode(getattr(mainmod, '__file__', ''))) == 'hg'):
239 237 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
240 238 else:
241 239 exe = findexe('hg') or os.path.basename(sys.argv[0])
242 240 _sethgexecutable(exe)
243 241 return _hgexecutable
244 242
245 243 def _sethgexecutable(path):
246 244 """set location of the 'hg' executable"""
247 245 global _hgexecutable
248 246 _hgexecutable = path
249 247
250 248 def _testfileno(f, stdf):
251 249 fileno = getattr(f, 'fileno', None)
252 250 try:
253 251 return fileno and fileno() == stdf.fileno()
254 252 except io.UnsupportedOperation:
255 253 return False # fileno() raised UnsupportedOperation
256 254
257 255 def isstdin(f):
258 256 return _testfileno(f, sys.__stdin__)
259 257
260 258 def isstdout(f):
261 259 return _testfileno(f, sys.__stdout__)
262 260
263 261 def protectstdio(uin, uout):
264 262 """Duplicate streams and redirect original if (uin, uout) are stdio
265 263
266 264 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
267 265 redirected to stderr so the output is still readable.
268 266
269 267 Returns (fin, fout) which point to the original (uin, uout) fds, but
270 268 may be copy of (uin, uout). The returned streams can be considered
271 269 "owned" in that print(), exec(), etc. never reach to them.
272 270 """
273 271 uout.flush()
274 272 fin, fout = uin, uout
275 273 if uin is stdin:
276 274 newfd = os.dup(uin.fileno())
277 275 nullfd = os.open(os.devnull, os.O_RDONLY)
278 276 os.dup2(nullfd, uin.fileno())
279 277 os.close(nullfd)
280 278 fin = os.fdopen(newfd, r'rb')
281 279 if uout is stdout:
282 280 newfd = os.dup(uout.fileno())
283 281 os.dup2(stderr.fileno(), uout.fileno())
284 282 fout = os.fdopen(newfd, r'wb')
285 283 return fin, fout
286 284
287 285 def restorestdio(uin, uout, fin, fout):
288 286 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
289 287 uout.flush()
290 288 for f, uif in [(fin, uin), (fout, uout)]:
291 289 if f is not uif:
292 290 os.dup2(f.fileno(), uif.fileno())
293 291 f.close()
294 292
295 293 @contextlib.contextmanager
296 294 def protectedstdio(uin, uout):
297 295 """Run code block with protected standard streams"""
298 296 fin, fout = protectstdio(uin, uout)
299 297 try:
300 298 yield fin, fout
301 299 finally:
302 300 restorestdio(uin, uout, fin, fout)
303 301
304 302 def shellenviron(environ=None):
305 303 """return environ with optional override, useful for shelling out"""
306 304 def py2shell(val):
307 305 'convert python object into string that is useful to shell'
308 306 if val is None or val is False:
309 307 return '0'
310 308 if val is True:
311 309 return '1'
312 310 return pycompat.bytestr(val)
313 311 env = dict(encoding.environ)
314 312 if environ:
315 313 env.update((k, py2shell(v)) for k, v in environ.iteritems())
316 314 env['HG'] = hgexecutable()
317 315 return env
318 316
319 317 def system(cmd, environ=None, cwd=None, out=None):
320 318 '''enhanced shell command execution.
321 319 run with environment maybe modified, maybe in different dir.
322 320
323 321 if out is specified, it is assumed to be a file-like object that has a
324 322 write() method. stdout and stderr will be redirected to out.'''
325 323 try:
326 324 stdout.flush()
327 325 except Exception:
328 326 pass
329 327 cmd = quotecommand(cmd)
330 328 env = shellenviron(environ)
331 329 if out is None or isstdout(out):
332 330 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
333 331 env=env, cwd=cwd)
334 332 else:
335 333 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
336 334 env=env, cwd=cwd, stdout=subprocess.PIPE,
337 335 stderr=subprocess.STDOUT)
338 336 for line in iter(proc.stdout.readline, ''):
339 337 out.write(line)
340 338 proc.wait()
341 339 rc = proc.returncode
342 340 if pycompat.sysplatform == 'OpenVMS' and rc & 1:
343 341 rc = 0
344 342 return rc
345 343
346 344 def gui():
347 345 '''Are we running in a GUI?'''
348 346 if pycompat.isdarwin:
349 347 if 'SSH_CONNECTION' in encoding.environ:
350 348 # handle SSH access to a box where the user is logged in
351 349 return False
352 350 elif getattr(osutil, 'isgui', None):
353 351 # check if a CoreGraphics session is available
354 352 return osutil.isgui()
355 353 else:
356 354 # pure build; use a safe default
357 355 return True
358 356 else:
359 357 return pycompat.iswindows or encoding.environ.get("DISPLAY")
360 358
361 359 def hgcmd():
362 360 """Return the command used to execute current hg
363 361
364 362 This is different from hgexecutable() because on Windows we want
365 363 to avoid things opening new shell windows like batch files, so we
366 364 get either the python call or current executable.
367 365 """
368 366 if mainfrozen():
369 367 if getattr(sys, 'frozen', None) == 'macosx_app':
370 368 # Env variable set by py2app
371 369 return [encoding.environ['EXECUTABLEPATH']]
372 370 else:
373 371 return [pycompat.sysexecutable]
374 372 return _gethgcmd()
375 373
376 374 def rundetached(args, condfn):
377 375 """Execute the argument list in a detached process.
378 376
379 377 condfn is a callable which is called repeatedly and should return
380 378 True once the child process is known to have started successfully.
381 379 At this point, the child process PID is returned. If the child
382 380 process fails to start or finishes before condfn() evaluates to
383 381 True, return -1.
384 382 """
385 383 # Windows case is easier because the child process is either
386 384 # successfully starting and validating the condition or exiting
387 385 # on failure. We just poll on its PID. On Unix, if the child
388 386 # process fails to start, it will be left in a zombie state until
389 387 # the parent wait on it, which we cannot do since we expect a long
390 388 # running process on success. Instead we listen for SIGCHLD telling
391 389 # us our child process terminated.
392 390 terminated = set()
393 391 def handler(signum, frame):
394 392 terminated.add(os.wait())
395 393 prevhandler = None
396 394 SIGCHLD = getattr(signal, 'SIGCHLD', None)
397 395 if SIGCHLD is not None:
398 396 prevhandler = signal.signal(SIGCHLD, handler)
399 397 try:
400 398 pid = spawndetached(args)
401 399 while not condfn():
402 400 if ((pid in terminated or not testpid(pid))
403 401 and not condfn()):
404 402 return -1
405 403 time.sleep(0.1)
406 404 return pid
407 405 finally:
408 406 if prevhandler is not None:
409 407 signal.signal(signal.SIGCHLD, prevhandler)
General Comments 0
You need to be logged in to leave comments. Login now