##// END OF EJS Templates
windows: always work around EINVAL in case of broken pipe for stdout / stderr...
Manuel Jacob -
r45708:a37f290a default
parent child Browse files
Show More
@@ -1,752 +1,758 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 io
15 15 import os
16 16 import signal
17 17 import subprocess
18 18 import sys
19 19 import threading
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 # Import like this to keep import-checker happy
36 36 from ..utils import resourceutil
37 37
38 38 osutil = policy.importmod('osutil')
39 39
40 40 if pycompat.iswindows:
41 41 from .. import windows as platform
42 42 else:
43 43 from .. import posix as platform
44 44
45 45
46 46 def isatty(fp):
47 47 try:
48 48 return fp.isatty()
49 49 except AttributeError:
50 50 return False
51 51
52 52
53 53 class LineBufferedWrapper(object):
54 54 def __init__(self, orig):
55 55 self.orig = orig
56 56
57 57 def __getattr__(self, attr):
58 58 return getattr(self.orig, attr)
59 59
60 60 def write(self, s):
61 61 orig = self.orig
62 62 res = orig.write(s)
63 63 if s.endswith(b'\n'):
64 64 orig.flush()
65 65 return res
66 66
67 67
68 68 io.BufferedIOBase.register(LineBufferedWrapper)
69 69
70 70
71 71 def make_line_buffered(stream):
72 72 if pycompat.ispy3 and not isinstance(stream, io.BufferedIOBase):
73 73 # On Python 3, buffered streams can be expected to subclass
74 74 # BufferedIOBase. This is definitively the case for the streams
75 75 # initialized by the interpreter. For unbuffered streams, we don't need
76 76 # to emulate line buffering.
77 77 return stream
78 78 if isinstance(stream, LineBufferedWrapper):
79 79 return stream
80 80 return LineBufferedWrapper(stream)
81 81
82 82
83 83 class WriteAllWrapper(object):
84 84 def __init__(self, orig):
85 85 self.orig = orig
86 86
87 87 def __getattr__(self, attr):
88 88 return getattr(self.orig, attr)
89 89
90 90 def write(self, s):
91 91 write1 = self.orig.write
92 92 m = memoryview(s)
93 93 total_to_write = len(s)
94 94 total_written = 0
95 95 while total_written < total_to_write:
96 96 total_written += write1(m[total_written:])
97 97 return total_written
98 98
99 99
100 100 io.IOBase.register(WriteAllWrapper)
101 101
102 102
103 103 def _make_write_all(stream):
104 104 assert pycompat.ispy3
105 105 if isinstance(stream, WriteAllWrapper):
106 106 return stream
107 107 if isinstance(stream, io.BufferedIOBase):
108 108 # The io.BufferedIOBase.write() contract guarantees that all data is
109 109 # written.
110 110 return stream
111 111 # In general, the write() method of streams is free to write only part of
112 112 # the data.
113 113 return WriteAllWrapper(stream)
114 114
115 115
116 116 if pycompat.ispy3:
117 117 # Python 3 implements its own I/O streams.
118 118 # TODO: .buffer might not exist if std streams were replaced; we'll need
119 119 # a silly wrapper to make a bytes stream backed by a unicode one.
120 120 stdin = sys.stdin.buffer
121 121 stdout = _make_write_all(sys.stdout.buffer)
122 stderr = _make_write_all(sys.stderr.buffer)
123 if pycompat.iswindows:
124 # Work around Windows bugs.
125 stdout = platform.winstdout(stdout)
126 stderr = platform.winstdout(stderr)
122 127 if isatty(stdout):
123 128 # The standard library doesn't offer line-buffered binary streams.
124 129 stdout = make_line_buffered(stdout)
125 stderr = _make_write_all(sys.stderr.buffer)
126 130 else:
127 131 # Python 2 uses the I/O streams provided by the C library.
128 132 stdin = sys.stdin
129 133 stdout = sys.stdout
134 stderr = sys.stderr
135 if pycompat.iswindows:
136 # Work around Windows bugs.
137 stdout = platform.winstdout(stdout)
138 stderr = platform.winstdout(stderr)
130 139 if isatty(stdout):
131 140 if pycompat.iswindows:
132 # Work around size limit when writing to console.
133 stdout = platform.winstdout(stdout)
134 141 # The Windows C runtime library doesn't support line buffering.
135 142 stdout = make_line_buffered(stdout)
136 143 else:
137 144 # glibc determines buffering on first write to stdout - if we
138 145 # replace a TTY destined stdout with a pipe destined stdout (e.g.
139 146 # pager), we want line buffering.
140 147 stdout = os.fdopen(stdout.fileno(), 'wb', 1)
141 stderr = sys.stderr
142 148
143 149
144 150 findexe = platform.findexe
145 151 _gethgcmd = platform.gethgcmd
146 152 getuser = platform.getuser
147 153 getpid = os.getpid
148 154 hidewindow = platform.hidewindow
149 155 readpipe = platform.readpipe
150 156 setbinary = platform.setbinary
151 157 setsignalhandler = platform.setsignalhandler
152 158 shellquote = platform.shellquote
153 159 shellsplit = platform.shellsplit
154 160 spawndetached = platform.spawndetached
155 161 sshargs = platform.sshargs
156 162 testpid = platform.testpid
157 163
158 164 try:
159 165 setprocname = osutil.setprocname
160 166 except AttributeError:
161 167 pass
162 168 try:
163 169 unblocksignal = osutil.unblocksignal
164 170 except AttributeError:
165 171 pass
166 172
167 173 closefds = pycompat.isposix
168 174
169 175
170 176 def explainexit(code):
171 177 """return a message describing a subprocess status
172 178 (codes from kill are negative - not os.system/wait encoding)"""
173 179 if code >= 0:
174 180 return _(b"exited with status %d") % code
175 181 return _(b"killed by signal %d") % -code
176 182
177 183
178 184 class _pfile(object):
179 185 """File-like wrapper for a stream opened by subprocess.Popen()"""
180 186
181 187 def __init__(self, proc, fp):
182 188 self._proc = proc
183 189 self._fp = fp
184 190
185 191 def close(self):
186 192 # unlike os.popen(), this returns an integer in subprocess coding
187 193 self._fp.close()
188 194 return self._proc.wait()
189 195
190 196 def __iter__(self):
191 197 return iter(self._fp)
192 198
193 199 def __getattr__(self, attr):
194 200 return getattr(self._fp, attr)
195 201
196 202 def __enter__(self):
197 203 return self
198 204
199 205 def __exit__(self, exc_type, exc_value, exc_tb):
200 206 self.close()
201 207
202 208
203 209 def popen(cmd, mode=b'rb', bufsize=-1):
204 210 if mode == b'rb':
205 211 return _popenreader(cmd, bufsize)
206 212 elif mode == b'wb':
207 213 return _popenwriter(cmd, bufsize)
208 214 raise error.ProgrammingError(b'unsupported mode: %r' % mode)
209 215
210 216
211 217 def _popenreader(cmd, bufsize):
212 218 p = subprocess.Popen(
213 219 tonativestr(cmd),
214 220 shell=True,
215 221 bufsize=bufsize,
216 222 close_fds=closefds,
217 223 stdout=subprocess.PIPE,
218 224 )
219 225 return _pfile(p, p.stdout)
220 226
221 227
222 228 def _popenwriter(cmd, bufsize):
223 229 p = subprocess.Popen(
224 230 tonativestr(cmd),
225 231 shell=True,
226 232 bufsize=bufsize,
227 233 close_fds=closefds,
228 234 stdin=subprocess.PIPE,
229 235 )
230 236 return _pfile(p, p.stdin)
231 237
232 238
233 239 def popen2(cmd, env=None):
234 240 # Setting bufsize to -1 lets the system decide the buffer size.
235 241 # The default for bufsize is 0, meaning unbuffered. This leads to
236 242 # poor performance on Mac OS X: http://bugs.python.org/issue4194
237 243 p = subprocess.Popen(
238 244 tonativestr(cmd),
239 245 shell=True,
240 246 bufsize=-1,
241 247 close_fds=closefds,
242 248 stdin=subprocess.PIPE,
243 249 stdout=subprocess.PIPE,
244 250 env=tonativeenv(env),
245 251 )
246 252 return p.stdin, p.stdout
247 253
248 254
249 255 def popen3(cmd, env=None):
250 256 stdin, stdout, stderr, p = popen4(cmd, env)
251 257 return stdin, stdout, stderr
252 258
253 259
254 260 def popen4(cmd, env=None, bufsize=-1):
255 261 p = subprocess.Popen(
256 262 tonativestr(cmd),
257 263 shell=True,
258 264 bufsize=bufsize,
259 265 close_fds=closefds,
260 266 stdin=subprocess.PIPE,
261 267 stdout=subprocess.PIPE,
262 268 stderr=subprocess.PIPE,
263 269 env=tonativeenv(env),
264 270 )
265 271 return p.stdin, p.stdout, p.stderr, p
266 272
267 273
268 274 def pipefilter(s, cmd):
269 275 '''filter string S through command CMD, returning its output'''
270 276 p = subprocess.Popen(
271 277 tonativestr(cmd),
272 278 shell=True,
273 279 close_fds=closefds,
274 280 stdin=subprocess.PIPE,
275 281 stdout=subprocess.PIPE,
276 282 )
277 283 pout, perr = p.communicate(s)
278 284 return pout
279 285
280 286
281 287 def tempfilter(s, cmd):
282 288 '''filter string S through a pair of temporary files with CMD.
283 289 CMD is used as a template to create the real command to be run,
284 290 with the strings INFILE and OUTFILE replaced by the real names of
285 291 the temporary files generated.'''
286 292 inname, outname = None, None
287 293 try:
288 294 infd, inname = pycompat.mkstemp(prefix=b'hg-filter-in-')
289 295 fp = os.fdopen(infd, 'wb')
290 296 fp.write(s)
291 297 fp.close()
292 298 outfd, outname = pycompat.mkstemp(prefix=b'hg-filter-out-')
293 299 os.close(outfd)
294 300 cmd = cmd.replace(b'INFILE', inname)
295 301 cmd = cmd.replace(b'OUTFILE', outname)
296 302 code = system(cmd)
297 303 if pycompat.sysplatform == b'OpenVMS' and code & 1:
298 304 code = 0
299 305 if code:
300 306 raise error.Abort(
301 307 _(b"command '%s' failed: %s") % (cmd, explainexit(code))
302 308 )
303 309 with open(outname, b'rb') as fp:
304 310 return fp.read()
305 311 finally:
306 312 try:
307 313 if inname:
308 314 os.unlink(inname)
309 315 except OSError:
310 316 pass
311 317 try:
312 318 if outname:
313 319 os.unlink(outname)
314 320 except OSError:
315 321 pass
316 322
317 323
318 324 _filtertable = {
319 325 b'tempfile:': tempfilter,
320 326 b'pipe:': pipefilter,
321 327 }
322 328
323 329
324 330 def filter(s, cmd):
325 331 """filter a string through a command that transforms its input to its
326 332 output"""
327 333 for name, fn in pycompat.iteritems(_filtertable):
328 334 if cmd.startswith(name):
329 335 return fn(s, cmd[len(name) :].lstrip())
330 336 return pipefilter(s, cmd)
331 337
332 338
333 339 _hgexecutable = None
334 340
335 341
336 342 def hgexecutable():
337 343 """return location of the 'hg' executable.
338 344
339 345 Defaults to $HG or 'hg' in the search path.
340 346 """
341 347 if _hgexecutable is None:
342 348 hg = encoding.environ.get(b'HG')
343 349 mainmod = sys.modules['__main__']
344 350 if hg:
345 351 _sethgexecutable(hg)
346 352 elif resourceutil.mainfrozen():
347 353 if getattr(sys, 'frozen', None) == 'macosx_app':
348 354 # Env variable set by py2app
349 355 _sethgexecutable(encoding.environ[b'EXECUTABLEPATH'])
350 356 else:
351 357 _sethgexecutable(pycompat.sysexecutable)
352 358 elif (
353 359 not pycompat.iswindows
354 360 and os.path.basename(getattr(mainmod, '__file__', '')) == 'hg'
355 361 ):
356 362 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
357 363 else:
358 364 _sethgexecutable(
359 365 findexe(b'hg') or os.path.basename(pycompat.sysargv[0])
360 366 )
361 367 return _hgexecutable
362 368
363 369
364 370 def _sethgexecutable(path):
365 371 """set location of the 'hg' executable"""
366 372 global _hgexecutable
367 373 _hgexecutable = path
368 374
369 375
370 376 def _testfileno(f, stdf):
371 377 fileno = getattr(f, 'fileno', None)
372 378 try:
373 379 return fileno and fileno() == stdf.fileno()
374 380 except io.UnsupportedOperation:
375 381 return False # fileno() raised UnsupportedOperation
376 382
377 383
378 384 def isstdin(f):
379 385 return _testfileno(f, sys.__stdin__)
380 386
381 387
382 388 def isstdout(f):
383 389 return _testfileno(f, sys.__stdout__)
384 390
385 391
386 392 def protectstdio(uin, uout):
387 393 """Duplicate streams and redirect original if (uin, uout) are stdio
388 394
389 395 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
390 396 redirected to stderr so the output is still readable.
391 397
392 398 Returns (fin, fout) which point to the original (uin, uout) fds, but
393 399 may be copy of (uin, uout). The returned streams can be considered
394 400 "owned" in that print(), exec(), etc. never reach to them.
395 401 """
396 402 uout.flush()
397 403 fin, fout = uin, uout
398 404 if _testfileno(uin, stdin):
399 405 newfd = os.dup(uin.fileno())
400 406 nullfd = os.open(os.devnull, os.O_RDONLY)
401 407 os.dup2(nullfd, uin.fileno())
402 408 os.close(nullfd)
403 409 fin = os.fdopen(newfd, 'rb')
404 410 if _testfileno(uout, stdout):
405 411 newfd = os.dup(uout.fileno())
406 412 os.dup2(stderr.fileno(), uout.fileno())
407 413 fout = os.fdopen(newfd, 'wb')
408 414 return fin, fout
409 415
410 416
411 417 def restorestdio(uin, uout, fin, fout):
412 418 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
413 419 uout.flush()
414 420 for f, uif in [(fin, uin), (fout, uout)]:
415 421 if f is not uif:
416 422 os.dup2(f.fileno(), uif.fileno())
417 423 f.close()
418 424
419 425
420 426 def shellenviron(environ=None):
421 427 """return environ with optional override, useful for shelling out"""
422 428
423 429 def py2shell(val):
424 430 """convert python object into string that is useful to shell"""
425 431 if val is None or val is False:
426 432 return b'0'
427 433 if val is True:
428 434 return b'1'
429 435 return pycompat.bytestr(val)
430 436
431 437 env = dict(encoding.environ)
432 438 if environ:
433 439 env.update((k, py2shell(v)) for k, v in pycompat.iteritems(environ))
434 440 env[b'HG'] = hgexecutable()
435 441 return env
436 442
437 443
438 444 if pycompat.iswindows:
439 445
440 446 def shelltonative(cmd, env):
441 447 return platform.shelltocmdexe( # pytype: disable=module-attr
442 448 cmd, shellenviron(env)
443 449 )
444 450
445 451 tonativestr = encoding.strfromlocal
446 452 else:
447 453
448 454 def shelltonative(cmd, env):
449 455 return cmd
450 456
451 457 tonativestr = pycompat.identity
452 458
453 459
454 460 def tonativeenv(env):
455 461 '''convert the environment from bytes to strings suitable for Popen(), etc.
456 462 '''
457 463 return pycompat.rapply(tonativestr, env)
458 464
459 465
460 466 def system(cmd, environ=None, cwd=None, out=None):
461 467 '''enhanced shell command execution.
462 468 run with environment maybe modified, maybe in different dir.
463 469
464 470 if out is specified, it is assumed to be a file-like object that has a
465 471 write() method. stdout and stderr will be redirected to out.'''
466 472 try:
467 473 stdout.flush()
468 474 except Exception:
469 475 pass
470 476 env = shellenviron(environ)
471 477 if out is None or isstdout(out):
472 478 rc = subprocess.call(
473 479 tonativestr(cmd),
474 480 shell=True,
475 481 close_fds=closefds,
476 482 env=tonativeenv(env),
477 483 cwd=pycompat.rapply(tonativestr, cwd),
478 484 )
479 485 else:
480 486 proc = subprocess.Popen(
481 487 tonativestr(cmd),
482 488 shell=True,
483 489 close_fds=closefds,
484 490 env=tonativeenv(env),
485 491 cwd=pycompat.rapply(tonativestr, cwd),
486 492 stdout=subprocess.PIPE,
487 493 stderr=subprocess.STDOUT,
488 494 )
489 495 for line in iter(proc.stdout.readline, b''):
490 496 out.write(line)
491 497 proc.wait()
492 498 rc = proc.returncode
493 499 if pycompat.sysplatform == b'OpenVMS' and rc & 1:
494 500 rc = 0
495 501 return rc
496 502
497 503
498 504 _is_gui = None
499 505
500 506
501 507 def _gui():
502 508 '''Are we running in a GUI?'''
503 509 if pycompat.isdarwin:
504 510 if b'SSH_CONNECTION' in encoding.environ:
505 511 # handle SSH access to a box where the user is logged in
506 512 return False
507 513 elif getattr(osutil, 'isgui', None):
508 514 # check if a CoreGraphics session is available
509 515 return osutil.isgui()
510 516 else:
511 517 # pure build; use a safe default
512 518 return True
513 519 else:
514 520 return pycompat.iswindows or encoding.environ.get(b"DISPLAY")
515 521
516 522
517 523 def gui():
518 524 global _is_gui
519 525 if _is_gui is None:
520 526 _is_gui = _gui()
521 527 return _is_gui
522 528
523 529
524 530 def hgcmd():
525 531 """Return the command used to execute current hg
526 532
527 533 This is different from hgexecutable() because on Windows we want
528 534 to avoid things opening new shell windows like batch files, so we
529 535 get either the python call or current executable.
530 536 """
531 537 if resourceutil.mainfrozen():
532 538 if getattr(sys, 'frozen', None) == 'macosx_app':
533 539 # Env variable set by py2app
534 540 return [encoding.environ[b'EXECUTABLEPATH']]
535 541 else:
536 542 return [pycompat.sysexecutable]
537 543 return _gethgcmd()
538 544
539 545
540 546 def rundetached(args, condfn):
541 547 """Execute the argument list in a detached process.
542 548
543 549 condfn is a callable which is called repeatedly and should return
544 550 True once the child process is known to have started successfully.
545 551 At this point, the child process PID is returned. If the child
546 552 process fails to start or finishes before condfn() evaluates to
547 553 True, return -1.
548 554 """
549 555 # Windows case is easier because the child process is either
550 556 # successfully starting and validating the condition or exiting
551 557 # on failure. We just poll on its PID. On Unix, if the child
552 558 # process fails to start, it will be left in a zombie state until
553 559 # the parent wait on it, which we cannot do since we expect a long
554 560 # running process on success. Instead we listen for SIGCHLD telling
555 561 # us our child process terminated.
556 562 terminated = set()
557 563
558 564 def handler(signum, frame):
559 565 terminated.add(os.wait())
560 566
561 567 prevhandler = None
562 568 SIGCHLD = getattr(signal, 'SIGCHLD', None)
563 569 if SIGCHLD is not None:
564 570 prevhandler = signal.signal(SIGCHLD, handler)
565 571 try:
566 572 pid = spawndetached(args)
567 573 while not condfn():
568 574 if (pid in terminated or not testpid(pid)) and not condfn():
569 575 return -1
570 576 time.sleep(0.1)
571 577 return pid
572 578 finally:
573 579 if prevhandler is not None:
574 580 signal.signal(signal.SIGCHLD, prevhandler)
575 581
576 582
577 583 @contextlib.contextmanager
578 584 def uninterruptible(warn):
579 585 """Inhibit SIGINT handling on a region of code.
580 586
581 587 Note that if this is called in a non-main thread, it turns into a no-op.
582 588
583 589 Args:
584 590 warn: A callable which takes no arguments, and returns True if the
585 591 previous signal handling should be restored.
586 592 """
587 593
588 594 oldsiginthandler = [signal.getsignal(signal.SIGINT)]
589 595 shouldbail = []
590 596
591 597 def disabledsiginthandler(*args):
592 598 if warn():
593 599 signal.signal(signal.SIGINT, oldsiginthandler[0])
594 600 del oldsiginthandler[0]
595 601 shouldbail.append(True)
596 602
597 603 try:
598 604 try:
599 605 signal.signal(signal.SIGINT, disabledsiginthandler)
600 606 except ValueError:
601 607 # wrong thread, oh well, we tried
602 608 del oldsiginthandler[0]
603 609 yield
604 610 finally:
605 611 if oldsiginthandler:
606 612 signal.signal(signal.SIGINT, oldsiginthandler[0])
607 613 if shouldbail:
608 614 raise KeyboardInterrupt
609 615
610 616
611 617 if pycompat.iswindows:
612 618 # no fork on Windows, but we can create a detached process
613 619 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
614 620 # No stdlib constant exists for this value
615 621 DETACHED_PROCESS = 0x00000008
616 622 # Following creation flags might create a console GUI window.
617 623 # Using subprocess.CREATE_NEW_CONSOLE might helps.
618 624 # See https://phab.mercurial-scm.org/D1701 for discussion
619 625 _creationflags = (
620 626 DETACHED_PROCESS
621 627 | subprocess.CREATE_NEW_PROCESS_GROUP # pytype: disable=module-attr
622 628 )
623 629
624 630 def runbgcommand(
625 631 script,
626 632 env,
627 633 shell=False,
628 634 stdout=None,
629 635 stderr=None,
630 636 ensurestart=True,
631 637 record_wait=None,
632 638 ):
633 639 '''Spawn a command without waiting for it to finish.'''
634 640 # we can't use close_fds *and* redirect stdin. I'm not sure that we
635 641 # need to because the detached process has no console connection.
636 642 p = subprocess.Popen(
637 643 tonativestr(script),
638 644 shell=shell,
639 645 env=tonativeenv(env),
640 646 close_fds=True,
641 647 creationflags=_creationflags,
642 648 stdout=stdout,
643 649 stderr=stderr,
644 650 )
645 651 if record_wait is not None:
646 652 record_wait(p.wait)
647 653
648 654
649 655 else:
650 656
651 657 def runbgcommand(
652 658 cmd,
653 659 env,
654 660 shell=False,
655 661 stdout=None,
656 662 stderr=None,
657 663 ensurestart=True,
658 664 record_wait=None,
659 665 ):
660 666 '''Spawn a command without waiting for it to finish.
661 667
662 668
663 669 When `record_wait` is not None, the spawned process will not be fully
664 670 detached and the `record_wait` argument will be called with a the
665 671 `Subprocess.wait` function for the spawned process. This is mostly
666 672 useful for developers that need to make sure the spawned process
667 673 finished before a certain point. (eg: writing test)'''
668 674 if pycompat.isdarwin:
669 675 # avoid crash in CoreFoundation in case another thread
670 676 # calls gui() while we're calling fork().
671 677 gui()
672 678
673 679 # double-fork to completely detach from the parent process
674 680 # based on http://code.activestate.com/recipes/278731
675 681 if record_wait is None:
676 682 pid = os.fork()
677 683 if pid:
678 684 if not ensurestart:
679 685 # Even though we're not waiting on the child process,
680 686 # we still must call waitpid() on it at some point so
681 687 # it's not a zombie/defunct. This is especially relevant for
682 688 # chg since the parent process won't die anytime soon.
683 689 # We use a thread to make the overhead tiny.
684 690 def _do_wait():
685 691 os.waitpid(pid, 0)
686 692
687 693 t = threading.Thread(target=_do_wait)
688 694 t.daemon = True
689 695 t.start()
690 696 return
691 697 # Parent process
692 698 (_pid, status) = os.waitpid(pid, 0)
693 699 if os.WIFEXITED(status):
694 700 returncode = os.WEXITSTATUS(status)
695 701 else:
696 702 returncode = -(os.WTERMSIG(status))
697 703 if returncode != 0:
698 704 # The child process's return code is 0 on success, an errno
699 705 # value on failure, or 255 if we don't have a valid errno
700 706 # value.
701 707 #
702 708 # (It would be slightly nicer to return the full exception info
703 709 # over a pipe as the subprocess module does. For now it
704 710 # doesn't seem worth adding that complexity here, though.)
705 711 if returncode == 255:
706 712 returncode = errno.EINVAL
707 713 raise OSError(
708 714 returncode,
709 715 b'error running %r: %s'
710 716 % (cmd, os.strerror(returncode)),
711 717 )
712 718 return
713 719
714 720 returncode = 255
715 721 try:
716 722 if record_wait is None:
717 723 # Start a new session
718 724 os.setsid()
719 725
720 726 stdin = open(os.devnull, b'r')
721 727 if stdout is None:
722 728 stdout = open(os.devnull, b'w')
723 729 if stderr is None:
724 730 stderr = open(os.devnull, b'w')
725 731
726 732 # connect stdin to devnull to make sure the subprocess can't
727 733 # muck up that stream for mercurial.
728 734 p = subprocess.Popen(
729 735 cmd,
730 736 shell=shell,
731 737 env=env,
732 738 close_fds=True,
733 739 stdin=stdin,
734 740 stdout=stdout,
735 741 stderr=stderr,
736 742 )
737 743 if record_wait is not None:
738 744 record_wait(p.wait)
739 745 returncode = 0
740 746 except EnvironmentError as ex:
741 747 returncode = ex.errno & 0xFF
742 748 if returncode == 0:
743 749 # This shouldn't happen, but just in case make sure the
744 750 # return code is never 0 here.
745 751 returncode = 255
746 752 except Exception:
747 753 returncode = 255
748 754 finally:
749 755 # mission accomplished, this child needs to exit and not
750 756 # continue the hg process here.
751 757 if record_wait is None:
752 758 os._exit(returncode)
@@ -1,681 +1,685 b''
1 1 # windows.py - Windows utility function implementations for Mercurial
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import getpass
12 12 import msvcrt
13 13 import os
14 14 import re
15 15 import stat
16 16 import string
17 17 import sys
18 18
19 19 from .i18n import _
20 20 from .pycompat import getattr
21 21 from . import (
22 22 encoding,
23 23 error,
24 24 policy,
25 25 pycompat,
26 26 win32,
27 27 )
28 28
29 29 try:
30 30 import _winreg as winreg # pytype: disable=import-error
31 31
32 32 winreg.CloseKey
33 33 except ImportError:
34 34 # py2 only
35 35 import winreg # pytype: disable=import-error
36 36
37 37 osutil = policy.importmod('osutil')
38 38
39 39 getfsmountpoint = win32.getvolumename
40 40 getfstype = win32.getfstype
41 41 getuser = win32.getuser
42 42 hidewindow = win32.hidewindow
43 43 makedir = win32.makedir
44 44 nlinks = win32.nlinks
45 45 oslink = win32.oslink
46 46 samedevice = win32.samedevice
47 47 samefile = win32.samefile
48 48 setsignalhandler = win32.setsignalhandler
49 49 spawndetached = win32.spawndetached
50 50 split = os.path.split
51 51 testpid = win32.testpid
52 52 unlink = win32.unlink
53 53
54 54 umask = 0o022
55 55
56 56
57 57 class mixedfilemodewrapper(object):
58 58 """Wraps a file handle when it is opened in read/write mode.
59 59
60 60 fopen() and fdopen() on Windows have a specific-to-Windows requirement
61 61 that files opened with mode r+, w+, or a+ make a call to a file positioning
62 62 function when switching between reads and writes. Without this extra call,
63 63 Python will raise a not very intuitive "IOError: [Errno 0] Error."
64 64
65 65 This class wraps posixfile instances when the file is opened in read/write
66 66 mode and automatically adds checks or inserts appropriate file positioning
67 67 calls when necessary.
68 68 """
69 69
70 70 OPNONE = 0
71 71 OPREAD = 1
72 72 OPWRITE = 2
73 73
74 74 def __init__(self, fp):
75 75 object.__setattr__(self, '_fp', fp)
76 76 object.__setattr__(self, '_lastop', 0)
77 77
78 78 def __enter__(self):
79 79 self._fp.__enter__()
80 80 return self
81 81
82 82 def __exit__(self, exc_type, exc_val, exc_tb):
83 83 self._fp.__exit__(exc_type, exc_val, exc_tb)
84 84
85 85 def __getattr__(self, name):
86 86 return getattr(self._fp, name)
87 87
88 88 def __setattr__(self, name, value):
89 89 return self._fp.__setattr__(name, value)
90 90
91 91 def _noopseek(self):
92 92 self._fp.seek(0, os.SEEK_CUR)
93 93
94 94 def seek(self, *args, **kwargs):
95 95 object.__setattr__(self, '_lastop', self.OPNONE)
96 96 return self._fp.seek(*args, **kwargs)
97 97
98 98 def write(self, d):
99 99 if self._lastop == self.OPREAD:
100 100 self._noopseek()
101 101
102 102 object.__setattr__(self, '_lastop', self.OPWRITE)
103 103 return self._fp.write(d)
104 104
105 105 def writelines(self, *args, **kwargs):
106 106 if self._lastop == self.OPREAD:
107 107 self._noopeseek()
108 108
109 109 object.__setattr__(self, '_lastop', self.OPWRITE)
110 110 return self._fp.writelines(*args, **kwargs)
111 111
112 112 def read(self, *args, **kwargs):
113 113 if self._lastop == self.OPWRITE:
114 114 self._noopseek()
115 115
116 116 object.__setattr__(self, '_lastop', self.OPREAD)
117 117 return self._fp.read(*args, **kwargs)
118 118
119 119 def readline(self, *args, **kwargs):
120 120 if self._lastop == self.OPWRITE:
121 121 self._noopseek()
122 122
123 123 object.__setattr__(self, '_lastop', self.OPREAD)
124 124 return self._fp.readline(*args, **kwargs)
125 125
126 126 def readlines(self, *args, **kwargs):
127 127 if self._lastop == self.OPWRITE:
128 128 self._noopseek()
129 129
130 130 object.__setattr__(self, '_lastop', self.OPREAD)
131 131 return self._fp.readlines(*args, **kwargs)
132 132
133 133
134 134 class fdproxy(object):
135 135 """Wraps osutil.posixfile() to override the name attribute to reflect the
136 136 underlying file name.
137 137 """
138 138
139 139 def __init__(self, name, fp):
140 140 self.name = name
141 141 self._fp = fp
142 142
143 143 def __enter__(self):
144 144 self._fp.__enter__()
145 145 # Return this wrapper for the context manager so that the name is
146 146 # still available.
147 147 return self
148 148
149 149 def __exit__(self, exc_type, exc_value, traceback):
150 150 self._fp.__exit__(exc_type, exc_value, traceback)
151 151
152 152 def __iter__(self):
153 153 return iter(self._fp)
154 154
155 155 def __getattr__(self, name):
156 156 return getattr(self._fp, name)
157 157
158 158
159 159 def posixfile(name, mode=b'r', buffering=-1):
160 160 '''Open a file with even more POSIX-like semantics'''
161 161 try:
162 162 fp = osutil.posixfile(name, mode, buffering) # may raise WindowsError
163 163
164 164 # PyFile_FromFd() ignores the name, and seems to report fp.name as the
165 165 # underlying file descriptor.
166 166 if pycompat.ispy3:
167 167 fp = fdproxy(name, fp)
168 168
169 169 # The position when opening in append mode is implementation defined, so
170 170 # make it consistent with other platforms, which position at EOF.
171 171 if b'a' in mode:
172 172 fp.seek(0, os.SEEK_END)
173 173
174 174 if b'+' in mode:
175 175 return mixedfilemodewrapper(fp)
176 176
177 177 return fp
178 178 except WindowsError as err:
179 179 # convert to a friendlier exception
180 180 raise IOError(
181 181 err.errno, '%s: %s' % (encoding.strfromlocal(name), err.strerror)
182 182 )
183 183
184 184
185 185 # may be wrapped by win32mbcs extension
186 186 listdir = osutil.listdir
187 187
188 188
189 189 class winstdout(object):
190 190 '''Some files on Windows misbehave.
191 191
192 192 When writing to a broken pipe, EINVAL instead of EPIPE may be raised.
193 193
194 194 When writing too many bytes to a console at the same, a "Not enough space"
195 195 error may happen. Python 3 already works around that.
196 196 '''
197 197
198 198 def __init__(self, fp):
199 199 self.fp = fp
200 self.throttle = not pycompat.ispy3 and fp.isatty()
200 201
201 202 def __getattr__(self, key):
202 203 return getattr(self.fp, key)
203 204
204 205 def close(self):
205 206 try:
206 207 self.fp.close()
207 208 except IOError:
208 209 pass
209 210
210 211 def write(self, s):
212 if not pycompat.ispy3:
213 self.softspace = 0
211 214 try:
215 if not self.throttle:
216 return self.fp.write(s)
212 217 # This is workaround for "Not enough space" error on
213 218 # writing large size of data to console.
214 219 limit = 16000
215 220 l = len(s)
216 221 start = 0
217 self.softspace = 0
218 222 while start < l:
219 223 end = start + limit
220 224 self.fp.write(s[start:end])
221 225 start = end
222 226 except IOError as inst:
223 227 if inst.errno != 0 and not win32.lasterrorwaspipeerror(inst):
224 228 raise
225 229 self.close()
226 230 raise IOError(errno.EPIPE, 'Broken pipe')
227 231
228 232 def flush(self):
229 233 try:
230 234 return self.fp.flush()
231 235 except IOError as inst:
232 236 if not win32.lasterrorwaspipeerror(inst):
233 237 raise
234 238 raise IOError(errno.EPIPE, 'Broken pipe')
235 239
236 240
237 241 def openhardlinks():
238 242 return True
239 243
240 244
241 245 def parsepatchoutput(output_line):
242 246 """parses the output produced by patch and returns the filename"""
243 247 pf = output_line[14:]
244 248 if pf[0] == b'`':
245 249 pf = pf[1:-1] # Remove the quotes
246 250 return pf
247 251
248 252
249 253 def sshargs(sshcmd, host, user, port):
250 254 '''Build argument list for ssh or Plink'''
251 255 pflag = b'plink' in sshcmd.lower() and b'-P' or b'-p'
252 256 args = user and (b"%s@%s" % (user, host)) or host
253 257 if args.startswith(b'-') or args.startswith(b'/'):
254 258 raise error.Abort(
255 259 _(b'illegal ssh hostname or username starting with - or /: %s')
256 260 % args
257 261 )
258 262 args = shellquote(args)
259 263 if port:
260 264 args = b'%s %s %s' % (pflag, shellquote(port), args)
261 265 return args
262 266
263 267
264 268 def setflags(f, l, x):
265 269 pass
266 270
267 271
268 272 def copymode(src, dst, mode=None, enforcewritable=False):
269 273 pass
270 274
271 275
272 276 def checkexec(path):
273 277 return False
274 278
275 279
276 280 def checklink(path):
277 281 return False
278 282
279 283
280 284 def setbinary(fd):
281 285 # When run without console, pipes may expose invalid
282 286 # fileno(), usually set to -1.
283 287 fno = getattr(fd, 'fileno', None)
284 288 if fno is not None and fno() >= 0:
285 289 msvcrt.setmode(fno(), os.O_BINARY) # pytype: disable=module-attr
286 290
287 291
288 292 def pconvert(path):
289 293 return path.replace(pycompat.ossep, b'/')
290 294
291 295
292 296 def localpath(path):
293 297 return path.replace(b'/', b'\\')
294 298
295 299
296 300 def normpath(path):
297 301 return pconvert(os.path.normpath(path))
298 302
299 303
300 304 def normcase(path):
301 305 return encoding.upper(path) # NTFS compares via upper()
302 306
303 307
304 308 # see posix.py for definitions
305 309 normcasespec = encoding.normcasespecs.upper
306 310 normcasefallback = encoding.upperfallback
307 311
308 312
309 313 def samestat(s1, s2):
310 314 return False
311 315
312 316
313 317 def shelltocmdexe(path, env):
314 318 r"""Convert shell variables in the form $var and ${var} inside ``path``
315 319 to %var% form. Existing Windows style variables are left unchanged.
316 320
317 321 The variables are limited to the given environment. Unknown variables are
318 322 left unchanged.
319 323
320 324 >>> e = {b'var1': b'v1', b'var2': b'v2', b'var3': b'v3'}
321 325 >>> # Only valid values are expanded
322 326 >>> shelltocmdexe(b'cmd $var1 ${var2} %var3% $missing ${missing} %missing%',
323 327 ... e)
324 328 'cmd %var1% %var2% %var3% $missing ${missing} %missing%'
325 329 >>> # Single quote prevents expansion, as does \$ escaping
326 330 >>> shelltocmdexe(b"cmd '$var1 ${var2} %var3%' \$var1 \${var2} \\", e)
327 331 'cmd "$var1 ${var2} %var3%" $var1 ${var2} \\'
328 332 >>> # $$ is not special. %% is not special either, but can be the end and
329 333 >>> # start of consecutive variables
330 334 >>> shelltocmdexe(b"cmd $$ %% %var1%%var2%", e)
331 335 'cmd $$ %% %var1%%var2%'
332 336 >>> # No double substitution
333 337 >>> shelltocmdexe(b"$var1 %var1%", {b'var1': b'%var2%', b'var2': b'boom'})
334 338 '%var1% %var1%'
335 339 >>> # Tilde expansion
336 340 >>> shelltocmdexe(b"~/dir ~\dir2 ~tmpfile \~/", {})
337 341 '%USERPROFILE%/dir %USERPROFILE%\\dir2 ~tmpfile ~/'
338 342 """
339 343 if not any(c in path for c in b"$'~"):
340 344 return path
341 345
342 346 varchars = pycompat.sysbytes(string.ascii_letters + string.digits) + b'_-'
343 347
344 348 res = b''
345 349 index = 0
346 350 pathlen = len(path)
347 351 while index < pathlen:
348 352 c = path[index : index + 1]
349 353 if c == b'\'': # no expansion within single quotes
350 354 path = path[index + 1 :]
351 355 pathlen = len(path)
352 356 try:
353 357 index = path.index(b'\'')
354 358 res += b'"' + path[:index] + b'"'
355 359 except ValueError:
356 360 res += c + path
357 361 index = pathlen - 1
358 362 elif c == b'%': # variable
359 363 path = path[index + 1 :]
360 364 pathlen = len(path)
361 365 try:
362 366 index = path.index(b'%')
363 367 except ValueError:
364 368 res += b'%' + path
365 369 index = pathlen - 1
366 370 else:
367 371 var = path[:index]
368 372 res += b'%' + var + b'%'
369 373 elif c == b'$': # variable
370 374 if path[index + 1 : index + 2] == b'{':
371 375 path = path[index + 2 :]
372 376 pathlen = len(path)
373 377 try:
374 378 index = path.index(b'}')
375 379 var = path[:index]
376 380
377 381 # See below for why empty variables are handled specially
378 382 if env.get(var, b'') != b'':
379 383 res += b'%' + var + b'%'
380 384 else:
381 385 res += b'${' + var + b'}'
382 386 except ValueError:
383 387 res += b'${' + path
384 388 index = pathlen - 1
385 389 else:
386 390 var = b''
387 391 index += 1
388 392 c = path[index : index + 1]
389 393 while c != b'' and c in varchars:
390 394 var += c
391 395 index += 1
392 396 c = path[index : index + 1]
393 397 # Some variables (like HG_OLDNODE) may be defined, but have an
394 398 # empty value. Those need to be skipped because when spawning
395 399 # cmd.exe to run the hook, it doesn't replace %VAR% for an empty
396 400 # VAR, and that really confuses things like revset expressions.
397 401 # OTOH, if it's left in Unix format and the hook runs sh.exe, it
398 402 # will substitute to an empty string, and everything is happy.
399 403 if env.get(var, b'') != b'':
400 404 res += b'%' + var + b'%'
401 405 else:
402 406 res += b'$' + var
403 407
404 408 if c != b'':
405 409 index -= 1
406 410 elif (
407 411 c == b'~'
408 412 and index + 1 < pathlen
409 413 and path[index + 1 : index + 2] in (b'\\', b'/')
410 414 ):
411 415 res += b"%USERPROFILE%"
412 416 elif (
413 417 c == b'\\'
414 418 and index + 1 < pathlen
415 419 and path[index + 1 : index + 2] in (b'$', b'~')
416 420 ):
417 421 # Skip '\', but only if it is escaping $ or ~
418 422 res += path[index + 1 : index + 2]
419 423 index += 1
420 424 else:
421 425 res += c
422 426
423 427 index += 1
424 428 return res
425 429
426 430
427 431 # A sequence of backslashes is special iff it precedes a double quote:
428 432 # - if there's an even number of backslashes, the double quote is not
429 433 # quoted (i.e. it ends the quoted region)
430 434 # - if there's an odd number of backslashes, the double quote is quoted
431 435 # - in both cases, every pair of backslashes is unquoted into a single
432 436 # backslash
433 437 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
434 438 # So, to quote a string, we must surround it in double quotes, double
435 439 # the number of backslashes that precede double quotes and add another
436 440 # backslash before every double quote (being careful with the double
437 441 # quote we've appended to the end)
438 442 _quotere = None
439 443 _needsshellquote = None
440 444
441 445
442 446 def shellquote(s):
443 447 r"""
444 448 >>> shellquote(br'C:\Users\xyz')
445 449 '"C:\\Users\\xyz"'
446 450 >>> shellquote(br'C:\Users\xyz/mixed')
447 451 '"C:\\Users\\xyz/mixed"'
448 452 >>> # Would be safe not to quote too, since it is all double backslashes
449 453 >>> shellquote(br'C:\\Users\\xyz')
450 454 '"C:\\\\Users\\\\xyz"'
451 455 >>> # But this must be quoted
452 456 >>> shellquote(br'C:\\Users\\xyz/abc')
453 457 '"C:\\\\Users\\\\xyz/abc"'
454 458 """
455 459 global _quotere
456 460 if _quotere is None:
457 461 _quotere = re.compile(br'(\\*)("|\\$)')
458 462 global _needsshellquote
459 463 if _needsshellquote is None:
460 464 # ":" is also treated as "safe character", because it is used as a part
461 465 # of path name on Windows. "\" is also part of a path name, but isn't
462 466 # safe because shlex.split() (kind of) treats it as an escape char and
463 467 # drops it. It will leave the next character, even if it is another
464 468 # "\".
465 469 _needsshellquote = re.compile(br'[^a-zA-Z0-9._:/-]').search
466 470 if s and not _needsshellquote(s) and not _quotere.search(s):
467 471 # "s" shouldn't have to be quoted
468 472 return s
469 473 return b'"%s"' % _quotere.sub(br'\1\1\\\2', s)
470 474
471 475
472 476 def _unquote(s):
473 477 if s.startswith(b'"') and s.endswith(b'"'):
474 478 return s[1:-1]
475 479 return s
476 480
477 481
478 482 def shellsplit(s):
479 483 """Parse a command string in cmd.exe way (best-effort)"""
480 484 return pycompat.maplist(_unquote, pycompat.shlexsplit(s, posix=False))
481 485
482 486
483 487 # if you change this stub into a real check, please try to implement the
484 488 # username and groupname functions above, too.
485 489 def isowner(st):
486 490 return True
487 491
488 492
489 493 def findexe(command):
490 494 '''Find executable for command searching like cmd.exe does.
491 495 If command is a basename then PATH is searched for command.
492 496 PATH isn't searched if command is an absolute or relative path.
493 497 An extension from PATHEXT is found and added if not present.
494 498 If command isn't found None is returned.'''
495 499 pathext = encoding.environ.get(b'PATHEXT', b'.COM;.EXE;.BAT;.CMD')
496 500 pathexts = [ext for ext in pathext.lower().split(pycompat.ospathsep)]
497 501 if os.path.splitext(command)[1].lower() in pathexts:
498 502 pathexts = [b'']
499 503
500 504 def findexisting(pathcommand):
501 505 """Will append extension (if needed) and return existing file"""
502 506 for ext in pathexts:
503 507 executable = pathcommand + ext
504 508 if os.path.exists(executable):
505 509 return executable
506 510 return None
507 511
508 512 if pycompat.ossep in command:
509 513 return findexisting(command)
510 514
511 515 for path in encoding.environ.get(b'PATH', b'').split(pycompat.ospathsep):
512 516 executable = findexisting(os.path.join(path, command))
513 517 if executable is not None:
514 518 return executable
515 519 return findexisting(os.path.expanduser(os.path.expandvars(command)))
516 520
517 521
518 522 _wantedkinds = {stat.S_IFREG, stat.S_IFLNK}
519 523
520 524
521 525 def statfiles(files):
522 526 '''Stat each file in files. Yield each stat, or None if a file
523 527 does not exist or has a type we don't care about.
524 528
525 529 Cluster and cache stat per directory to minimize number of OS stat calls.'''
526 530 dircache = {} # dirname -> filename -> status | None if file does not exist
527 531 getkind = stat.S_IFMT
528 532 for nf in files:
529 533 nf = normcase(nf)
530 534 dir, base = os.path.split(nf)
531 535 if not dir:
532 536 dir = b'.'
533 537 cache = dircache.get(dir, None)
534 538 if cache is None:
535 539 try:
536 540 dmap = {
537 541 normcase(n): s
538 542 for n, k, s in listdir(dir, True)
539 543 if getkind(s.st_mode) in _wantedkinds
540 544 }
541 545 except OSError as err:
542 546 # Python >= 2.5 returns ENOENT and adds winerror field
543 547 # EINVAL is raised if dir is not a directory.
544 548 if err.errno not in (errno.ENOENT, errno.EINVAL, errno.ENOTDIR):
545 549 raise
546 550 dmap = {}
547 551 cache = dircache.setdefault(dir, dmap)
548 552 yield cache.get(base, None)
549 553
550 554
551 555 def username(uid=None):
552 556 """Return the name of the user with the given uid.
553 557
554 558 If uid is None, return the name of the current user."""
555 559 if not uid:
556 560 return pycompat.fsencode(getpass.getuser())
557 561 return None
558 562
559 563
560 564 def groupname(gid=None):
561 565 """Return the name of the group with the given gid.
562 566
563 567 If gid is None, return the name of the current group."""
564 568 return None
565 569
566 570
567 571 def readlink(pathname):
568 572 return pycompat.fsencode(os.readlink(pycompat.fsdecode(pathname)))
569 573
570 574
571 575 def removedirs(name):
572 576 """special version of os.removedirs that does not remove symlinked
573 577 directories or junction points if they actually contain files"""
574 578 if listdir(name):
575 579 return
576 580 os.rmdir(name)
577 581 head, tail = os.path.split(name)
578 582 if not tail:
579 583 head, tail = os.path.split(head)
580 584 while head and tail:
581 585 try:
582 586 if listdir(head):
583 587 return
584 588 os.rmdir(head)
585 589 except (ValueError, OSError):
586 590 break
587 591 head, tail = os.path.split(head)
588 592
589 593
590 594 def rename(src, dst):
591 595 '''atomically rename file src to dst, replacing dst if it exists'''
592 596 try:
593 597 os.rename(src, dst)
594 598 except OSError as e:
595 599 if e.errno != errno.EEXIST:
596 600 raise
597 601 unlink(dst)
598 602 os.rename(src, dst)
599 603
600 604
601 605 def gethgcmd():
602 606 return [encoding.strtolocal(arg) for arg in [sys.executable] + sys.argv[:1]]
603 607
604 608
605 609 def groupmembers(name):
606 610 # Don't support groups on Windows for now
607 611 raise KeyError
608 612
609 613
610 614 def isexec(f):
611 615 return False
612 616
613 617
614 618 class cachestat(object):
615 619 def __init__(self, path):
616 620 pass
617 621
618 622 def cacheable(self):
619 623 return False
620 624
621 625
622 626 def lookupreg(key, valname=None, scope=None):
623 627 ''' Look up a key/value name in the Windows registry.
624 628
625 629 valname: value name. If unspecified, the default value for the key
626 630 is used.
627 631 scope: optionally specify scope for registry lookup, this can be
628 632 a sequence of scopes to look up in order. Default (CURRENT_USER,
629 633 LOCAL_MACHINE).
630 634 '''
631 635 if scope is None:
632 636 scope = (winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE)
633 637 elif not isinstance(scope, (list, tuple)):
634 638 scope = (scope,)
635 639 for s in scope:
636 640 try:
637 641 with winreg.OpenKey(s, encoding.strfromlocal(key)) as hkey:
638 642 name = valname and encoding.strfromlocal(valname) or valname
639 643 val = winreg.QueryValueEx(hkey, name)[0]
640 644 # never let a Unicode string escape into the wild
641 645 return encoding.unitolocal(val)
642 646 except EnvironmentError:
643 647 pass
644 648
645 649
646 650 expandglobs = True
647 651
648 652
649 653 def statislink(st):
650 654 '''check whether a stat result is a symlink'''
651 655 return False
652 656
653 657
654 658 def statisexec(st):
655 659 '''check whether a stat result is an executable file'''
656 660 return False
657 661
658 662
659 663 def poll(fds):
660 664 # see posix.py for description
661 665 raise NotImplementedError()
662 666
663 667
664 668 def readpipe(pipe):
665 669 """Read all available data from a pipe."""
666 670 chunks = []
667 671 while True:
668 672 size = win32.peekpipe(pipe)
669 673 if not size:
670 674 break
671 675
672 676 s = pipe.read(size)
673 677 if not s:
674 678 break
675 679 chunks.append(s)
676 680
677 681 return b''.join(chunks)
678 682
679 683
680 684 def bindunixsocket(sock, path):
681 685 raise NotImplementedError('unsupported platform')
@@ -1,302 +1,365 b''
1 1 #!/usr/bin/env python
2 2 """
3 3 Tests the buffering behavior of stdio streams in `mercurial.utils.procutil`.
4 4 """
5 5 from __future__ import absolute_import
6 6
7 7 import contextlib
8 8 import errno
9 9 import os
10 10 import signal
11 11 import subprocess
12 12 import sys
13 13 import tempfile
14 14 import unittest
15 15
16 from mercurial import pycompat
16 from mercurial import pycompat, util
17 17
18 18
19 19 if pycompat.ispy3:
20 20
21 21 def set_noninheritable(fd):
22 22 # On Python 3, file descriptors are non-inheritable by default.
23 23 pass
24 24
25 25
26 26 else:
27 27 if pycompat.iswindows:
28 28 # unused
29 29 set_noninheritable = None
30 30 else:
31 31 import fcntl
32 32
33 33 def set_noninheritable(fd):
34 34 old = fcntl.fcntl(fd, fcntl.F_GETFD)
35 35 fcntl.fcntl(fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
36 36
37 37
38 38 TEST_BUFFERING_CHILD_SCRIPT = r'''
39 39 import os
40 40
41 41 from mercurial import dispatch
42 42 from mercurial.utils import procutil
43 43
44 44 dispatch.initstdio()
45 45 procutil.{stream}.write(b'aaa')
46 46 os.write(procutil.{stream}.fileno(), b'[written aaa]')
47 47 procutil.{stream}.write(b'bbb\n')
48 48 os.write(procutil.{stream}.fileno(), b'[written bbb\\n]')
49 49 '''
50 50 UNBUFFERED = b'aaa[written aaa]bbb\n[written bbb\\n]'
51 51 LINE_BUFFERED = b'[written aaa]aaabbb\n[written bbb\\n]'
52 52 FULLY_BUFFERED = b'[written aaa][written bbb\\n]aaabbb\n'
53 53
54 54
55 55 TEST_LARGE_WRITE_CHILD_SCRIPT = r'''
56 56 import os
57 57 import signal
58 58 import sys
59 59
60 60 from mercurial import dispatch
61 61 from mercurial.utils import procutil
62 62
63 63 signal.signal(signal.SIGINT, lambda *x: None)
64 64 dispatch.initstdio()
65 65 write_result = procutil.{stream}.write(b'x' * 1048576)
66 66 with os.fdopen(
67 67 os.open({write_result_fn!r}, os.O_WRONLY | getattr(os, 'O_TEMPORARY', 0)),
68 68 'w',
69 69 ) as write_result_f:
70 70 write_result_f.write(str(write_result))
71 71 '''
72 72
73 73
74 TEST_BROKEN_PIPE_CHILD_SCRIPT = r'''
75 import os
76 import pickle
77
78 from mercurial import dispatch
79 from mercurial.utils import procutil
80
81 dispatch.initstdio()
82 procutil.stdin.read(1) # wait until parent process closed pipe
83 try:
84 procutil.{stream}.write(b'test')
85 procutil.{stream}.flush()
86 except EnvironmentError as e:
87 with os.fdopen(
88 os.open(
89 {err_fn!r},
90 os.O_WRONLY
91 | getattr(os, 'O_BINARY', 0)
92 | getattr(os, 'O_TEMPORARY', 0),
93 ),
94 'wb',
95 ) as err_f:
96 pickle.dump(e, err_f)
97 # Exit early to suppress further broken pipe errors at interpreter shutdown.
98 os._exit(0)
99 '''
100
101
74 102 @contextlib.contextmanager
75 103 def _closing(fds):
76 104 try:
77 105 yield
78 106 finally:
79 107 for fd in fds:
80 108 try:
81 109 os.close(fd)
82 110 except EnvironmentError:
83 111 pass
84 112
85 113
86 114 # In the following, we set the FDs non-inheritable mainly to make it possible
87 115 # for tests to close the receiving end of the pipe / PTYs.
88 116
89 117
90 118 @contextlib.contextmanager
91 119 def _devnull():
92 120 devnull = os.open(os.devnull, os.O_WRONLY)
93 121 # We don't have a receiving end, so it's not worth the effort on Python 2
94 122 # on Windows to make the FD non-inheritable.
95 123 with _closing([devnull]):
96 124 yield (None, devnull)
97 125
98 126
99 127 @contextlib.contextmanager
100 128 def _pipes():
101 129 rwpair = os.pipe()
102 130 # Pipes are already non-inheritable on Windows.
103 131 if not pycompat.iswindows:
104 132 set_noninheritable(rwpair[0])
105 133 set_noninheritable(rwpair[1])
106 134 with _closing(rwpair):
107 135 yield rwpair
108 136
109 137
110 138 @contextlib.contextmanager
111 139 def _ptys():
112 140 if pycompat.iswindows:
113 141 raise unittest.SkipTest("PTYs are not supported on Windows")
114 142 import pty
115 143 import tty
116 144
117 145 rwpair = pty.openpty()
118 146 set_noninheritable(rwpair[0])
119 147 set_noninheritable(rwpair[1])
120 148 with _closing(rwpair):
121 149 tty.setraw(rwpair[0])
122 150 yield rwpair
123 151
124 152
125 153 def _readall(fd, buffer_size, initial_buf=None):
126 154 buf = initial_buf or []
127 155 while True:
128 156 try:
129 157 s = os.read(fd, buffer_size)
130 158 except OSError as e:
131 159 if e.errno == errno.EIO:
132 160 # If the child-facing PTY got closed, reading from the
133 161 # parent-facing PTY raises EIO.
134 162 break
135 163 raise
136 164 if not s:
137 165 break
138 166 buf.append(s)
139 167 return b''.join(buf)
140 168
141 169
142 170 class TestStdio(unittest.TestCase):
143 171 def _test(
144 172 self,
145 173 child_script,
146 174 stream,
147 175 rwpair_generator,
148 176 check_output,
149 177 python_args=[],
150 178 post_child_check=None,
179 stdin_generator=None,
151 180 ):
152 181 assert stream in ('stdout', 'stderr')
153 with rwpair_generator() as (stream_receiver, child_stream), open(
154 os.devnull, 'rb'
155 ) as child_stdin:
182 if stdin_generator is None:
183 stdin_generator = open(os.devnull, 'rb')
184 with rwpair_generator() as (
185 stream_receiver,
186 child_stream,
187 ), stdin_generator as child_stdin:
156 188 proc = subprocess.Popen(
157 189 [sys.executable] + python_args + ['-c', child_script],
158 190 stdin=child_stdin,
159 191 stdout=child_stream if stream == 'stdout' else None,
160 192 stderr=child_stream if stream == 'stderr' else None,
161 193 )
162 194 try:
163 195 os.close(child_stream)
164 196 if stream_receiver is not None:
165 197 check_output(stream_receiver, proc)
166 198 except: # re-raises
167 199 proc.terminate()
168 200 raise
169 201 finally:
170 202 retcode = proc.wait()
171 203 self.assertEqual(retcode, 0)
172 204 if post_child_check is not None:
173 205 post_child_check()
174 206
175 207 def _test_buffering(
176 208 self, stream, rwpair_generator, expected_output, python_args=[]
177 209 ):
178 210 def check_output(stream_receiver, proc):
179 211 self.assertEqual(_readall(stream_receiver, 1024), expected_output)
180 212
181 213 self._test(
182 214 TEST_BUFFERING_CHILD_SCRIPT.format(stream=stream),
183 215 stream,
184 216 rwpair_generator,
185 217 check_output,
186 218 python_args,
187 219 )
188 220
189 221 def test_buffering_stdout_devnull(self):
190 222 self._test_buffering('stdout', _devnull, None)
191 223
192 224 def test_buffering_stdout_pipes(self):
193 225 self._test_buffering('stdout', _pipes, FULLY_BUFFERED)
194 226
195 227 def test_buffering_stdout_ptys(self):
196 228 self._test_buffering('stdout', _ptys, LINE_BUFFERED)
197 229
198 230 def test_buffering_stdout_devnull_unbuffered(self):
199 231 self._test_buffering('stdout', _devnull, None, python_args=['-u'])
200 232
201 233 def test_buffering_stdout_pipes_unbuffered(self):
202 234 self._test_buffering('stdout', _pipes, UNBUFFERED, python_args=['-u'])
203 235
204 236 def test_buffering_stdout_ptys_unbuffered(self):
205 237 self._test_buffering('stdout', _ptys, UNBUFFERED, python_args=['-u'])
206 238
207 239 if not pycompat.ispy3 and not pycompat.iswindows:
208 240 # On Python 2 on non-Windows, we manually open stdout in line-buffered
209 241 # mode if connected to a TTY. We should check if Python was configured
210 242 # to use unbuffered stdout, but it's hard to do that.
211 243 test_buffering_stdout_ptys_unbuffered = unittest.expectedFailure(
212 244 test_buffering_stdout_ptys_unbuffered
213 245 )
214 246
215 247 def _test_large_write(self, stream, rwpair_generator, python_args=[]):
216 248 if not pycompat.ispy3 and pycompat.isdarwin:
217 249 # Python 2 doesn't always retry on EINTR, but the libc might retry.
218 250 # So far, it was observed only on macOS that EINTR is raised at the
219 251 # Python level. As Python 2 support will be dropped soon-ish, we
220 252 # won't attempt to fix it.
221 253 raise unittest.SkipTest("raises EINTR on macOS")
222 254
223 255 def check_output(stream_receiver, proc):
224 256 if not pycompat.iswindows:
225 257 # On Unix, we can provoke a partial write() by interrupting it
226 258 # by a signal handler as soon as a bit of data was written.
227 259 # We test that write() is called until all data is written.
228 260 buf = [os.read(stream_receiver, 1)]
229 261 proc.send_signal(signal.SIGINT)
230 262 else:
231 263 # On Windows, there doesn't seem to be a way to cause partial
232 264 # writes.
233 265 buf = []
234 266 self.assertEqual(
235 267 _readall(stream_receiver, 131072, buf), b'x' * 1048576
236 268 )
237 269
238 270 def post_child_check():
239 271 write_result_str = write_result_f.read()
240 272 if pycompat.ispy3:
241 273 # On Python 3, we test that the correct number of bytes is
242 274 # claimed to have been written.
243 275 expected_write_result_str = '1048576'
244 276 else:
245 277 # On Python 2, we only check that the large write does not
246 278 # crash.
247 279 expected_write_result_str = 'None'
248 280 self.assertEqual(write_result_str, expected_write_result_str)
249 281
250 282 with tempfile.NamedTemporaryFile('r') as write_result_f:
251 283 self._test(
252 284 TEST_LARGE_WRITE_CHILD_SCRIPT.format(
253 285 stream=stream, write_result_fn=write_result_f.name
254 286 ),
255 287 stream,
256 288 rwpair_generator,
257 289 check_output,
258 290 python_args,
259 291 post_child_check=post_child_check,
260 292 )
261 293
262 294 def test_large_write_stdout_devnull(self):
263 295 self._test_large_write('stdout', _devnull)
264 296
265 297 def test_large_write_stdout_pipes(self):
266 298 self._test_large_write('stdout', _pipes)
267 299
268 300 def test_large_write_stdout_ptys(self):
269 301 self._test_large_write('stdout', _ptys)
270 302
271 303 def test_large_write_stdout_devnull_unbuffered(self):
272 304 self._test_large_write('stdout', _devnull, python_args=['-u'])
273 305
274 306 def test_large_write_stdout_pipes_unbuffered(self):
275 307 self._test_large_write('stdout', _pipes, python_args=['-u'])
276 308
277 309 def test_large_write_stdout_ptys_unbuffered(self):
278 310 self._test_large_write('stdout', _ptys, python_args=['-u'])
279 311
280 312 def test_large_write_stderr_devnull(self):
281 313 self._test_large_write('stderr', _devnull)
282 314
283 315 def test_large_write_stderr_pipes(self):
284 316 self._test_large_write('stderr', _pipes)
285 317
286 318 def test_large_write_stderr_ptys(self):
287 319 self._test_large_write('stderr', _ptys)
288 320
289 321 def test_large_write_stderr_devnull_unbuffered(self):
290 322 self._test_large_write('stderr', _devnull, python_args=['-u'])
291 323
292 324 def test_large_write_stderr_pipes_unbuffered(self):
293 325 self._test_large_write('stderr', _pipes, python_args=['-u'])
294 326
295 327 def test_large_write_stderr_ptys_unbuffered(self):
296 328 self._test_large_write('stderr', _ptys, python_args=['-u'])
297 329
330 def _test_broken_pipe(self, stream):
331 assert stream in ('stdout', 'stderr')
332
333 def check_output(stream_receiver, proc):
334 os.close(stream_receiver)
335 proc.stdin.write(b'x')
336 proc.stdin.close()
337
338 def post_child_check():
339 err = util.pickle.load(err_f)
340 self.assertEqual(err.errno, errno.EPIPE)
341 self.assertEqual(err.strerror, "Broken pipe")
342
343 with tempfile.NamedTemporaryFile('rb') as err_f:
344 self._test(
345 TEST_BROKEN_PIPE_CHILD_SCRIPT.format(
346 stream=stream, err_fn=err_f.name
347 ),
348 stream,
349 _pipes,
350 check_output,
351 post_child_check=post_child_check,
352 stdin_generator=util.nullcontextmanager(subprocess.PIPE),
353 )
354
355 def test_broken_pipe_stdout(self):
356 self._test_broken_pipe('stdout')
357
358 def test_broken_pipe_stderr(self):
359 self._test_broken_pipe('stderr')
360
298 361
299 362 if __name__ == '__main__':
300 363 import silenttestrunner
301 364
302 365 silenttestrunner.main(__name__)
General Comments 0
You need to be logged in to leave comments. Login now