##// END OF EJS Templates
procutil: fix error message of tempfile filter...
Yuya Nishihara -
r37479:538353b8 default
parent child Browse files
Show More
@@ -1,409 +1,409 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 2-tuple (desc, code) 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, code
86 86 return _("killed by signal %d") % -code, -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 131 def popen2(cmd, env=None, newlines=False):
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 138 universal_newlines=newlines,
139 139 env=env)
140 140 return p.stdin, p.stdout
141 141
142 142 def popen3(cmd, env=None, newlines=False):
143 143 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
144 144 return stdin, stdout, stderr
145 145
146 146 def popen4(cmd, env=None, newlines=False, bufsize=-1):
147 147 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
148 148 close_fds=closefds,
149 149 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
150 150 stderr=subprocess.PIPE,
151 151 universal_newlines=newlines,
152 152 env=env)
153 153 return p.stdin, p.stdout, p.stderr, p
154 154
155 155 def pipefilter(s, cmd):
156 156 '''filter string S through command CMD, returning its output'''
157 157 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
158 158 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
159 159 pout, perr = p.communicate(s)
160 160 return pout
161 161
162 162 def tempfilter(s, cmd):
163 163 '''filter string S through a pair of temporary files with CMD.
164 164 CMD is used as a template to create the real command to be run,
165 165 with the strings INFILE and OUTFILE replaced by the real names of
166 166 the temporary files generated.'''
167 167 inname, outname = None, None
168 168 try:
169 169 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
170 170 fp = os.fdopen(infd, r'wb')
171 171 fp.write(s)
172 172 fp.close()
173 173 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
174 174 os.close(outfd)
175 175 cmd = cmd.replace('INFILE', inname)
176 176 cmd = cmd.replace('OUTFILE', outname)
177 code = os.system(cmd)
177 code = system(cmd)
178 178 if pycompat.sysplatform == 'OpenVMS' and code & 1:
179 179 code = 0
180 180 if code:
181 181 raise error.Abort(_("command '%s' failed: %s") %
182 (cmd, explainexit(code)))
182 (cmd, explainexit(code)[0]))
183 183 with open(outname, 'rb') as fp:
184 184 return fp.read()
185 185 finally:
186 186 try:
187 187 if inname:
188 188 os.unlink(inname)
189 189 except OSError:
190 190 pass
191 191 try:
192 192 if outname:
193 193 os.unlink(outname)
194 194 except OSError:
195 195 pass
196 196
197 197 _filtertable = {
198 198 'tempfile:': tempfilter,
199 199 'pipe:': pipefilter,
200 200 }
201 201
202 202 def filter(s, cmd):
203 203 "filter a string through a command that transforms its input to its output"
204 204 for name, fn in _filtertable.iteritems():
205 205 if cmd.startswith(name):
206 206 return fn(s, cmd[len(name):].lstrip())
207 207 return pipefilter(s, cmd)
208 208
209 209 def mainfrozen():
210 210 """return True if we are a frozen executable.
211 211
212 212 The code supports py2exe (most common, Windows only) and tools/freeze
213 213 (portable, not much used).
214 214 """
215 215 return (pycompat.safehasattr(sys, "frozen") or # new py2exe
216 216 pycompat.safehasattr(sys, "importers") or # old py2exe
217 217 imp.is_frozen(u"__main__")) # tools/freeze
218 218
219 219 _hgexecutable = None
220 220
221 221 def hgexecutable():
222 222 """return location of the 'hg' executable.
223 223
224 224 Defaults to $HG or 'hg' in the search path.
225 225 """
226 226 if _hgexecutable is None:
227 227 hg = encoding.environ.get('HG')
228 228 mainmod = sys.modules[r'__main__']
229 229 if hg:
230 230 _sethgexecutable(hg)
231 231 elif mainfrozen():
232 232 if getattr(sys, 'frozen', None) == 'macosx_app':
233 233 # Env variable set by py2app
234 234 _sethgexecutable(encoding.environ['EXECUTABLEPATH'])
235 235 else:
236 236 _sethgexecutable(pycompat.sysexecutable)
237 237 elif (os.path.basename(
238 238 pycompat.fsencode(getattr(mainmod, '__file__', ''))) == 'hg'):
239 239 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
240 240 else:
241 241 exe = findexe('hg') or os.path.basename(sys.argv[0])
242 242 _sethgexecutable(exe)
243 243 return _hgexecutable
244 244
245 245 def _sethgexecutable(path):
246 246 """set location of the 'hg' executable"""
247 247 global _hgexecutable
248 248 _hgexecutable = path
249 249
250 250 def _testfileno(f, stdf):
251 251 fileno = getattr(f, 'fileno', None)
252 252 try:
253 253 return fileno and fileno() == stdf.fileno()
254 254 except io.UnsupportedOperation:
255 255 return False # fileno() raised UnsupportedOperation
256 256
257 257 def isstdin(f):
258 258 return _testfileno(f, sys.__stdin__)
259 259
260 260 def isstdout(f):
261 261 return _testfileno(f, sys.__stdout__)
262 262
263 263 def protectstdio(uin, uout):
264 264 """Duplicate streams and redirect original if (uin, uout) are stdio
265 265
266 266 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
267 267 redirected to stderr so the output is still readable.
268 268
269 269 Returns (fin, fout) which point to the original (uin, uout) fds, but
270 270 may be copy of (uin, uout). The returned streams can be considered
271 271 "owned" in that print(), exec(), etc. never reach to them.
272 272 """
273 273 uout.flush()
274 274 fin, fout = uin, uout
275 275 if uin is stdin:
276 276 newfd = os.dup(uin.fileno())
277 277 nullfd = os.open(os.devnull, os.O_RDONLY)
278 278 os.dup2(nullfd, uin.fileno())
279 279 os.close(nullfd)
280 280 fin = os.fdopen(newfd, r'rb')
281 281 if uout is stdout:
282 282 newfd = os.dup(uout.fileno())
283 283 os.dup2(stderr.fileno(), uout.fileno())
284 284 fout = os.fdopen(newfd, r'wb')
285 285 return fin, fout
286 286
287 287 def restorestdio(uin, uout, fin, fout):
288 288 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
289 289 uout.flush()
290 290 for f, uif in [(fin, uin), (fout, uout)]:
291 291 if f is not uif:
292 292 os.dup2(f.fileno(), uif.fileno())
293 293 f.close()
294 294
295 295 @contextlib.contextmanager
296 296 def protectedstdio(uin, uout):
297 297 """Run code block with protected standard streams"""
298 298 fin, fout = protectstdio(uin, uout)
299 299 try:
300 300 yield fin, fout
301 301 finally:
302 302 restorestdio(uin, uout, fin, fout)
303 303
304 304 def shellenviron(environ=None):
305 305 """return environ with optional override, useful for shelling out"""
306 306 def py2shell(val):
307 307 'convert python object into string that is useful to shell'
308 308 if val is None or val is False:
309 309 return '0'
310 310 if val is True:
311 311 return '1'
312 312 return pycompat.bytestr(val)
313 313 env = dict(encoding.environ)
314 314 if environ:
315 315 env.update((k, py2shell(v)) for k, v in environ.iteritems())
316 316 env['HG'] = hgexecutable()
317 317 return env
318 318
319 319 def system(cmd, environ=None, cwd=None, out=None):
320 320 '''enhanced shell command execution.
321 321 run with environment maybe modified, maybe in different dir.
322 322
323 323 if out is specified, it is assumed to be a file-like object that has a
324 324 write() method. stdout and stderr will be redirected to out.'''
325 325 try:
326 326 stdout.flush()
327 327 except Exception:
328 328 pass
329 329 cmd = quotecommand(cmd)
330 330 env = shellenviron(environ)
331 331 if out is None or isstdout(out):
332 332 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
333 333 env=env, cwd=cwd)
334 334 else:
335 335 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
336 336 env=env, cwd=cwd, stdout=subprocess.PIPE,
337 337 stderr=subprocess.STDOUT)
338 338 for line in iter(proc.stdout.readline, ''):
339 339 out.write(line)
340 340 proc.wait()
341 341 rc = proc.returncode
342 342 if pycompat.sysplatform == 'OpenVMS' and rc & 1:
343 343 rc = 0
344 344 return rc
345 345
346 346 def gui():
347 347 '''Are we running in a GUI?'''
348 348 if pycompat.isdarwin:
349 349 if 'SSH_CONNECTION' in encoding.environ:
350 350 # handle SSH access to a box where the user is logged in
351 351 return False
352 352 elif getattr(osutil, 'isgui', None):
353 353 # check if a CoreGraphics session is available
354 354 return osutil.isgui()
355 355 else:
356 356 # pure build; use a safe default
357 357 return True
358 358 else:
359 359 return pycompat.iswindows or encoding.environ.get("DISPLAY")
360 360
361 361 def hgcmd():
362 362 """Return the command used to execute current hg
363 363
364 364 This is different from hgexecutable() because on Windows we want
365 365 to avoid things opening new shell windows like batch files, so we
366 366 get either the python call or current executable.
367 367 """
368 368 if mainfrozen():
369 369 if getattr(sys, 'frozen', None) == 'macosx_app':
370 370 # Env variable set by py2app
371 371 return [encoding.environ['EXECUTABLEPATH']]
372 372 else:
373 373 return [pycompat.sysexecutable]
374 374 return _gethgcmd()
375 375
376 376 def rundetached(args, condfn):
377 377 """Execute the argument list in a detached process.
378 378
379 379 condfn is a callable which is called repeatedly and should return
380 380 True once the child process is known to have started successfully.
381 381 At this point, the child process PID is returned. If the child
382 382 process fails to start or finishes before condfn() evaluates to
383 383 True, return -1.
384 384 """
385 385 # Windows case is easier because the child process is either
386 386 # successfully starting and validating the condition or exiting
387 387 # on failure. We just poll on its PID. On Unix, if the child
388 388 # process fails to start, it will be left in a zombie state until
389 389 # the parent wait on it, which we cannot do since we expect a long
390 390 # running process on success. Instead we listen for SIGCHLD telling
391 391 # us our child process terminated.
392 392 terminated = set()
393 393 def handler(signum, frame):
394 394 terminated.add(os.wait())
395 395 prevhandler = None
396 396 SIGCHLD = getattr(signal, 'SIGCHLD', None)
397 397 if SIGCHLD is not None:
398 398 prevhandler = signal.signal(SIGCHLD, handler)
399 399 try:
400 400 pid = spawndetached(args)
401 401 while not condfn():
402 402 if ((pid in terminated or not testpid(pid))
403 403 and not condfn()):
404 404 return -1
405 405 time.sleep(0.1)
406 406 return pid
407 407 finally:
408 408 if prevhandler is not None:
409 409 signal.signal(signal.SIGCHLD, prevhandler)
@@ -1,63 +1,72 b''
1 1 Test encode/decode filters
2 2
3 3 $ hg init
4 4 $ cat > .hg/hgrc <<EOF
5 5 > [encode]
6 6 > not.gz = tr [:lower:] [:upper:]
7 7 > *.gz = gzip -d
8 8 > [decode]
9 9 > not.gz = tr [:upper:] [:lower:]
10 10 > *.gz = gzip
11 11 > EOF
12 12 $ echo "this is a test" | gzip > a.gz
13 13 $ echo "this is a test" > not.gz
14 14 $ hg add *
15 15 $ hg ci -m "test"
16 16
17 17 no changes
18 18
19 19 $ hg status
20 20 $ touch *
21 21
22 22 no changes
23 23
24 24 $ hg status
25 25
26 26 check contents in repo are encoded
27 27
28 28 $ hg debugdata a.gz 0
29 29 this is a test
30 30 $ hg debugdata not.gz 0
31 31 THIS IS A TEST
32 32
33 33 check committed content was decoded
34 34
35 35 $ gunzip < a.gz
36 36 this is a test
37 37 $ cat not.gz
38 38 this is a test
39 39 $ rm *
40 40 $ hg co -C
41 41 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
42 42
43 43 check decoding of our new working dir copy
44 44
45 45 $ gunzip < a.gz
46 46 this is a test
47 47 $ cat not.gz
48 48 this is a test
49 49
50 50 check hg cat operation
51 51
52 52 $ hg cat a.gz
53 53 this is a test
54 54 $ hg cat --decode a.gz | gunzip
55 55 this is a test
56 56 $ mkdir subdir
57 57 $ cd subdir
58 58 $ hg -R .. cat ../a.gz
59 59 this is a test
60 60 $ hg -R .. cat --decode ../a.gz | gunzip
61 61 this is a test
62 $ cd ..
63
64 check tempfile filter
65
66 $ hg cat a.gz --decode --config 'decode.*.gz=tempfile:gzip -c INFILE > OUTFILE' | gunzip
67 this is a test
68 $ hg cat a.gz --decode --config 'decode.*.gz=tempfile:sh -c "exit 1"'
69 abort: command '*' failed: exited with status 1 (glob)
70 [255]
62 71
63 72 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now