##// END OF EJS Templates
typing: add type hints to mercurial/win32.py...
Matt Harbison -
r50705:a9faacdc default
parent child Browse files
Show More
@@ -1,753 +1,753 b''
1 1 # posix.py - Posix utility function implementations for Mercurial
2 2 #
3 3 # Copyright 2005-2009 Olivia Mackall <olivia@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
9 9 import errno
10 10 import fcntl
11 11 import getpass
12 12 import grp
13 13 import os
14 14 import pwd
15 15 import re
16 16 import select
17 17 import stat
18 18 import sys
19 19 import tempfile
20 20 import unicodedata
21 21
22 22 from .i18n import _
23 23 from .pycompat import (
24 24 getattr,
25 25 open,
26 26 )
27 27 from . import (
28 28 encoding,
29 29 error,
30 30 policy,
31 31 pycompat,
32 32 )
33 33
34 34 osutil = policy.importmod('osutil')
35 35
36 36 normpath = os.path.normpath
37 37 samestat = os.path.samestat
38 38 abspath = os.path.abspath # re-exports
39 39
40 40 try:
41 41 oslink = os.link
42 42 except AttributeError:
43 43 # Some platforms build Python without os.link on systems that are
44 44 # vaguely unix-like but don't have hardlink support. For those
45 45 # poor souls, just say we tried and that it failed so we fall back
46 46 # to copies.
47 47 def oslink(src, dst):
48 48 raise OSError(
49 49 errno.EINVAL, b'hardlinks not supported: %s to %s' % (src, dst)
50 50 )
51 51
52 52
53 53 readlink = os.readlink
54 54 unlink = os.unlink
55 55 rename = os.rename
56 56 removedirs = os.removedirs
57 57 expandglobs = False
58 58
59 59 umask = os.umask(0)
60 60 os.umask(umask)
61 61
62 62 posixfile = open
63 63
64 64
65 65 def split(p):
66 66 """Same as posixpath.split, but faster
67 67
68 68 >>> import posixpath
69 69 >>> for f in [b'/absolute/path/to/file',
70 70 ... b'relative/path/to/file',
71 71 ... b'file_alone',
72 72 ... b'path/to/directory/',
73 73 ... b'/multiple/path//separators',
74 74 ... b'/file_at_root',
75 75 ... b'///multiple_leading_separators_at_root',
76 76 ... b'']:
77 77 ... assert split(f) == posixpath.split(f), f
78 78 """
79 79 ht = p.rsplit(b'/', 1)
80 80 if len(ht) == 1:
81 81 return b'', p
82 82 nh = ht[0].rstrip(b'/')
83 83 if nh:
84 84 return nh, ht[1]
85 85 return ht[0] + b'/', ht[1]
86 86
87 87
88 88 def openhardlinks():
89 89 '''return true if it is safe to hold open file handles to hardlinks'''
90 90 return True
91 91
92 92
93 93 def nlinks(name):
94 94 '''return number of hardlinks for the given file'''
95 95 return os.lstat(name).st_nlink
96 96
97 97
98 98 def parsepatchoutput(output_line):
99 99 """parses the output produced by patch and returns the filename"""
100 100 pf = output_line[14:]
101 101 if pycompat.sysplatform == b'OpenVMS':
102 102 if pf[0] == b'`':
103 103 pf = pf[1:-1] # Remove the quotes
104 104 else:
105 105 if pf.startswith(b"'") and pf.endswith(b"'") and b" " in pf:
106 106 pf = pf[1:-1] # Remove the quotes
107 107 return pf
108 108
109 109
110 110 def sshargs(sshcmd, host, user, port):
111 111 '''Build argument list for ssh'''
112 112 args = user and (b"%s@%s" % (user, host)) or host
113 113 if b'-' in args[:1]:
114 114 raise error.Abort(
115 115 _(b'illegal ssh hostname or username starting with -: %s') % args
116 116 )
117 117 args = shellquote(args)
118 118 if port:
119 119 args = b'-p %s %s' % (shellquote(port), args)
120 120 return args
121 121
122 122
123 123 def isexec(f):
124 124 """check whether a file is executable"""
125 125 return os.lstat(f).st_mode & 0o100 != 0
126 126
127 127
128 128 def setflags(f, l, x):
129 129 st = os.lstat(f)
130 130 s = st.st_mode
131 131 if l:
132 132 if not stat.S_ISLNK(s):
133 133 # switch file to link
134 134 with open(f, b'rb') as fp:
135 135 data = fp.read()
136 136 unlink(f)
137 137 try:
138 138 os.symlink(data, f)
139 139 except OSError:
140 140 # failed to make a link, rewrite file
141 141 with open(f, b"wb") as fp:
142 142 fp.write(data)
143 143
144 144 # no chmod needed at this point
145 145 return
146 146 if stat.S_ISLNK(s):
147 147 # switch link to file
148 148 data = os.readlink(f)
149 149 unlink(f)
150 150 with open(f, b"wb") as fp:
151 151 fp.write(data)
152 152 s = 0o666 & ~umask # avoid restatting for chmod
153 153
154 154 sx = s & 0o100
155 155 if st.st_nlink > 1 and bool(x) != bool(sx):
156 156 # the file is a hardlink, break it
157 157 with open(f, b"rb") as fp:
158 158 data = fp.read()
159 159 unlink(f)
160 160 with open(f, b"wb") as fp:
161 161 fp.write(data)
162 162
163 163 if x and not sx:
164 164 # Turn on +x for every +r bit when making a file executable
165 165 # and obey umask.
166 166 os.chmod(f, s | (s & 0o444) >> 2 & ~umask)
167 167 elif not x and sx:
168 168 # Turn off all +x bits
169 169 os.chmod(f, s & 0o666)
170 170
171 171
172 172 def copymode(src, dst, mode=None, enforcewritable=False):
173 173 """Copy the file mode from the file at path src to dst.
174 174 If src doesn't exist, we're using mode instead. If mode is None, we're
175 175 using umask."""
176 176 try:
177 177 st_mode = os.lstat(src).st_mode & 0o777
178 178 except FileNotFoundError:
179 179 st_mode = mode
180 180 if st_mode is None:
181 181 st_mode = ~umask
182 182 st_mode &= 0o666
183 183
184 184 new_mode = st_mode
185 185
186 186 if enforcewritable:
187 187 new_mode |= stat.S_IWUSR
188 188
189 189 os.chmod(dst, new_mode)
190 190
191 191
192 192 def checkexec(path):
193 193 """
194 194 Check whether the given path is on a filesystem with UNIX-like exec flags
195 195
196 196 Requires a directory (like /foo/.hg)
197 197 """
198 198
199 199 # VFAT on some Linux versions can flip mode but it doesn't persist
200 200 # a FS remount. Frequently we can detect it if files are created
201 201 # with exec bit on.
202 202
203 203 try:
204 204 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
205 205 basedir = os.path.join(path, b'.hg')
206 206 cachedir = os.path.join(basedir, b'wcache')
207 207 storedir = os.path.join(basedir, b'store')
208 208 if not os.path.exists(cachedir):
209 209 try:
210 210 # we want to create the 'cache' directory, not the '.hg' one.
211 211 # Automatically creating '.hg' directory could silently spawn
212 212 # invalid Mercurial repositories. That seems like a bad idea.
213 213 os.mkdir(cachedir)
214 214 if os.path.exists(storedir):
215 215 copymode(storedir, cachedir)
216 216 else:
217 217 copymode(basedir, cachedir)
218 218 except (IOError, OSError):
219 219 # we other fallback logic triggers
220 220 pass
221 221 if os.path.isdir(cachedir):
222 222 checkisexec = os.path.join(cachedir, b'checkisexec')
223 223 checknoexec = os.path.join(cachedir, b'checknoexec')
224 224
225 225 try:
226 226 m = os.stat(checkisexec).st_mode
227 227 except FileNotFoundError:
228 228 # checkisexec does not exist - fall through ...
229 229 pass
230 230 else:
231 231 # checkisexec exists, check if it actually is exec
232 232 if m & EXECFLAGS != 0:
233 233 # ensure checkisexec exists, check it isn't exec
234 234 try:
235 235 m = os.stat(checknoexec).st_mode
236 236 except FileNotFoundError:
237 237 open(checknoexec, b'w').close() # might fail
238 238 m = os.stat(checknoexec).st_mode
239 239 if m & EXECFLAGS == 0:
240 240 # check-exec is exec and check-no-exec is not exec
241 241 return True
242 242 # checknoexec exists but is exec - delete it
243 243 unlink(checknoexec)
244 244 # checkisexec exists but is not exec - delete it
245 245 unlink(checkisexec)
246 246
247 247 # check using one file, leave it as checkisexec
248 248 checkdir = cachedir
249 249 else:
250 250 # check directly in path and don't leave checkisexec behind
251 251 checkdir = path
252 252 checkisexec = None
253 253 fh, fn = pycompat.mkstemp(dir=checkdir, prefix=b'hg-checkexec-')
254 254 try:
255 255 os.close(fh)
256 256 m = os.stat(fn).st_mode
257 257 if m & EXECFLAGS == 0:
258 258 os.chmod(fn, m & 0o777 | EXECFLAGS)
259 259 if os.stat(fn).st_mode & EXECFLAGS != 0:
260 260 if checkisexec is not None:
261 261 os.rename(fn, checkisexec)
262 262 fn = None
263 263 return True
264 264 finally:
265 265 if fn is not None:
266 266 unlink(fn)
267 267 except (IOError, OSError):
268 268 # we don't care, the user probably won't be able to commit anyway
269 269 return False
270 270
271 271
272 272 def checklink(path):
273 273 """check whether the given path is on a symlink-capable filesystem"""
274 274 # mktemp is not racy because symlink creation will fail if the
275 275 # file already exists
276 276 while True:
277 277 cachedir = os.path.join(path, b'.hg', b'wcache')
278 278 checklink = os.path.join(cachedir, b'checklink')
279 279 # try fast path, read only
280 280 if os.path.islink(checklink):
281 281 return True
282 282 if os.path.isdir(cachedir):
283 283 checkdir = cachedir
284 284 else:
285 285 checkdir = path
286 286 cachedir = None
287 287 name = tempfile.mktemp(
288 288 dir=pycompat.fsdecode(checkdir), prefix=r'checklink-'
289 289 )
290 290 name = pycompat.fsencode(name)
291 291 try:
292 292 fd = None
293 293 if cachedir is None:
294 294 fd = pycompat.namedtempfile(
295 295 dir=checkdir, prefix=b'hg-checklink-'
296 296 )
297 297 target = os.path.basename(fd.name)
298 298 else:
299 299 # create a fixed file to link to; doesn't matter if it
300 300 # already exists.
301 301 target = b'checklink-target'
302 302 try:
303 303 fullpath = os.path.join(cachedir, target)
304 304 open(fullpath, b'w').close()
305 305 except PermissionError:
306 306 # If we can't write to cachedir, just pretend
307 307 # that the fs is readonly and by association
308 308 # that the fs won't support symlinks. This
309 309 # seems like the least dangerous way to avoid
310 310 # data loss.
311 311 return False
312 312 try:
313 313 os.symlink(target, name)
314 314 if cachedir is None:
315 315 unlink(name)
316 316 else:
317 317 try:
318 318 os.rename(name, checklink)
319 319 except OSError:
320 320 unlink(name)
321 321 return True
322 322 except FileExistsError:
323 323 # link creation might race, try again
324 324 continue
325 325 finally:
326 326 if fd is not None:
327 327 fd.close()
328 328 except AttributeError:
329 329 return False
330 330 except OSError as inst:
331 331 # sshfs might report failure while successfully creating the link
332 332 if inst.errno == errno.EIO and os.path.exists(name):
333 333 unlink(name)
334 334 return False
335 335
336 336
337 337 def checkosfilename(path):
338 338 """Check that the base-relative path is a valid filename on this platform.
339 339 Returns None if the path is ok, or a UI string describing the problem."""
340 340 return None # on posix platforms, every path is ok
341 341
342 342
343 343 def getfsmountpoint(dirpath):
344 344 """Get the filesystem mount point from a directory (best-effort)
345 345
346 346 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
347 347 """
348 348 return getattr(osutil, 'getfsmountpoint', lambda x: None)(dirpath)
349 349
350 350
351 351 def getfstype(dirpath):
352 352 """Get the filesystem type name from a directory (best-effort)
353 353
354 354 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
355 355 """
356 356 return getattr(osutil, 'getfstype', lambda x: None)(dirpath)
357 357
358 358
359 359 def get_password():
360 360 return encoding.strtolocal(getpass.getpass(''))
361 361
362 362
363 363 def setbinary(fd):
364 364 pass
365 365
366 366
367 367 def pconvert(path):
368 368 return path
369 369
370 370
371 371 def localpath(path):
372 372 return path
373 373
374 374
375 375 def samefile(fpath1, fpath2):
376 376 """Returns whether path1 and path2 refer to the same file. This is only
377 377 guaranteed to work for files, not directories."""
378 378 return os.path.samefile(fpath1, fpath2)
379 379
380 380
381 381 def samedevice(fpath1, fpath2):
382 382 """Returns whether fpath1 and fpath2 are on the same device. This is only
383 383 guaranteed to work for files, not directories."""
384 384 st1 = os.lstat(fpath1)
385 385 st2 = os.lstat(fpath2)
386 386 return st1.st_dev == st2.st_dev
387 387
388 388
389 389 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
390 390 def normcase(path):
391 391 return path.lower()
392 392
393 393
394 394 # what normcase does to ASCII strings
395 395 normcasespec = encoding.normcasespecs.lower
396 396 # fallback normcase function for non-ASCII strings
397 397 normcasefallback = normcase
398 398
399 399 if pycompat.isdarwin:
400 400
401 401 def normcase(path):
402 402 """
403 403 Normalize a filename for OS X-compatible comparison:
404 404 - escape-encode invalid characters
405 405 - decompose to NFD
406 406 - lowercase
407 407 - omit ignored characters [200c-200f, 202a-202e, 206a-206f,feff]
408 408
409 409 >>> normcase(b'UPPER')
410 410 'upper'
411 411 >>> normcase(b'Caf\\xc3\\xa9')
412 412 'cafe\\xcc\\x81'
413 413 >>> normcase(b'\\xc3\\x89')
414 414 'e\\xcc\\x81'
415 415 >>> normcase(b'\\xb8\\xca\\xc3\\xca\\xbe\\xc8.JPG') # issue3918
416 416 '%b8%ca%c3\\xca\\xbe%c8.jpg'
417 417 """
418 418
419 419 try:
420 420 return encoding.asciilower(path) # exception for non-ASCII
421 421 except UnicodeDecodeError:
422 422 return normcasefallback(path)
423 423
424 424 normcasespec = encoding.normcasespecs.lower
425 425
426 426 def normcasefallback(path):
427 427 try:
428 428 u = path.decode('utf-8')
429 429 except UnicodeDecodeError:
430 430 # OS X percent-encodes any bytes that aren't valid utf-8
431 431 s = b''
432 432 pos = 0
433 433 l = len(path)
434 434 while pos < l:
435 435 try:
436 436 c = encoding.getutf8char(path, pos)
437 437 pos += len(c)
438 438 except ValueError:
439 439 c = b'%%%02X' % ord(path[pos : pos + 1])
440 440 pos += 1
441 441 s += c
442 442
443 443 u = s.decode('utf-8')
444 444
445 445 # Decompose then lowercase (HFS+ technote specifies lower)
446 446 enc = unicodedata.normalize('NFD', u).lower().encode('utf-8')
447 447 # drop HFS+ ignored characters
448 448 return encoding.hfsignoreclean(enc)
449 449
450 450
451 451 if pycompat.sysplatform == b'cygwin':
452 452 # workaround for cygwin, in which mount point part of path is
453 453 # treated as case sensitive, even though underlying NTFS is case
454 454 # insensitive.
455 455
456 456 # default mount points
457 457 cygwinmountpoints = sorted(
458 458 [
459 459 b"/usr/bin",
460 460 b"/usr/lib",
461 461 b"/cygdrive",
462 462 ],
463 463 reverse=True,
464 464 )
465 465
466 466 # use upper-ing as normcase as same as NTFS workaround
467 467 def normcase(path):
468 468 pathlen = len(path)
469 469 if (pathlen == 0) or (path[0] != pycompat.ossep):
470 470 # treat as relative
471 471 return encoding.upper(path)
472 472
473 473 # to preserve case of mountpoint part
474 474 for mp in cygwinmountpoints:
475 475 if not path.startswith(mp):
476 476 continue
477 477
478 478 mplen = len(mp)
479 479 if mplen == pathlen: # mount point itself
480 480 return mp
481 481 if path[mplen] == pycompat.ossep:
482 482 return mp + encoding.upper(path[mplen:])
483 483
484 484 return encoding.upper(path)
485 485
486 486 normcasespec = encoding.normcasespecs.other
487 487 normcasefallback = normcase
488 488
489 489 # Cygwin translates native ACLs to POSIX permissions,
490 490 # but these translations are not supported by native
491 491 # tools, so the exec bit tends to be set erroneously.
492 492 # Therefore, disable executable bit access on Cygwin.
493 493 def checkexec(path):
494 494 return False
495 495
496 496 # Similarly, Cygwin's symlink emulation is likely to create
497 497 # problems when Mercurial is used from both Cygwin and native
498 498 # Windows, with other native tools, or on shared volumes
499 499 def checklink(path):
500 500 return False
501 501
502 502
503 503 _needsshellquote = None
504 504
505 505
506 506 def shellquote(s):
507 507 if pycompat.sysplatform == b'OpenVMS':
508 508 return b'"%s"' % s
509 509 global _needsshellquote
510 510 if _needsshellquote is None:
511 511 _needsshellquote = re.compile(br'[^a-zA-Z0-9._/+-]').search
512 512 if s and not _needsshellquote(s):
513 513 # "s" shouldn't have to be quoted
514 514 return s
515 515 else:
516 516 return b"'%s'" % s.replace(b"'", b"'\\''")
517 517
518 518
519 519 def shellsplit(s):
520 520 """Parse a command string in POSIX shell way (best-effort)"""
521 521 return pycompat.shlexsplit(s, posix=True)
522 522
523 523
524 524 def testpid(pid):
525 525 '''return False if pid dead, True if running or not sure'''
526 526 if pycompat.sysplatform == b'OpenVMS':
527 527 return True
528 528 try:
529 529 os.kill(pid, 0)
530 530 return True
531 531 except OSError as inst:
532 532 return inst.errno != errno.ESRCH
533 533
534 534
535 535 def isowner(st):
536 536 """Return True if the stat object st is from the current user."""
537 537 return st.st_uid == os.getuid()
538 538
539 539
540 540 def findexe(command):
541 541 """Find executable for command searching like which does.
542 542 If command is a basename then PATH is searched for command.
543 543 PATH isn't searched if command is an absolute or relative path.
544 544 If command isn't found None is returned."""
545 545 if pycompat.sysplatform == b'OpenVMS':
546 546 return command
547 547
548 548 def findexisting(executable):
549 549 b'Will return executable if existing file'
550 550 if os.path.isfile(executable) and os.access(executable, os.X_OK):
551 551 return executable
552 552 return None
553 553
554 554 if pycompat.ossep in command:
555 555 return findexisting(command)
556 556
557 557 if pycompat.sysplatform == b'plan9':
558 558 return findexisting(os.path.join(b'/bin', command))
559 559
560 560 for path in encoding.environ.get(b'PATH', b'').split(pycompat.ospathsep):
561 561 executable = findexisting(os.path.join(path, command))
562 562 if executable is not None:
563 563 return executable
564 564 return None
565 565
566 566
567 567 def setsignalhandler():
568 568 pass
569 569
570 570
571 571 _wantedkinds = {stat.S_IFREG, stat.S_IFLNK}
572 572
573 573
574 574 def statfiles(files):
575 575 """Stat each file in files. Yield each stat, or None if a file does not
576 576 exist or has a type we don't care about."""
577 577 lstat = os.lstat
578 578 getkind = stat.S_IFMT
579 579 for nf in files:
580 580 try:
581 581 st = lstat(nf)
582 582 if getkind(st.st_mode) not in _wantedkinds:
583 583 st = None
584 584 except (FileNotFoundError, NotADirectoryError):
585 585 st = None
586 586 yield st
587 587
588 588
589 589 def getuser():
590 590 '''return name of current user'''
591 591 return pycompat.fsencode(getpass.getuser())
592 592
593 593
594 594 def username(uid=None):
595 595 """Return the name of the user with the given uid.
596 596
597 597 If uid is None, return the name of the current user."""
598 598
599 599 if uid is None:
600 600 uid = os.getuid()
601 601 try:
602 602 return pycompat.fsencode(pwd.getpwuid(uid)[0])
603 603 except KeyError:
604 604 return b'%d' % uid
605 605
606 606
607 607 def groupname(gid=None):
608 608 """Return the name of the group with the given gid.
609 609
610 610 If gid is None, return the name of the current group."""
611 611
612 612 if gid is None:
613 613 gid = os.getgid()
614 614 try:
615 615 return pycompat.fsencode(grp.getgrgid(gid)[0])
616 616 except KeyError:
617 617 return pycompat.bytestr(gid)
618 618
619 619
620 620 def groupmembers(name):
621 621 """Return the list of members of the group with the given
622 622 name, KeyError if the group does not exist.
623 623 """
624 624 name = pycompat.fsdecode(name)
625 625 return pycompat.rapply(pycompat.fsencode, list(grp.getgrnam(name).gr_mem))
626 626
627 627
628 def spawndetached(args):
628 def spawndetached(args) -> int:
629 629 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0), args[0], args)
630 630
631 631
632 632 def gethgcmd():
633 633 return sys.argv[:1]
634 634
635 635
636 636 def makedir(path, notindexed):
637 637 os.mkdir(path)
638 638
639 639
640 640 def lookupreg(key, name=None, scope=None):
641 641 return None
642 642
643 643
644 644 def hidewindow():
645 645 """Hide current shell window.
646 646
647 647 Used to hide the window opened when starting asynchronous
648 648 child process under Windows, unneeded on other systems.
649 649 """
650 650 pass
651 651
652 652
653 653 class cachestat:
654 654 def __init__(self, path):
655 655 self.stat = os.stat(path)
656 656
657 657 def cacheable(self):
658 658 return bool(self.stat.st_ino)
659 659
660 660 __hash__ = object.__hash__
661 661
662 662 def __eq__(self, other):
663 663 try:
664 664 # Only dev, ino, size, mtime and atime are likely to change. Out
665 665 # of these, we shouldn't compare atime but should compare the
666 666 # rest. However, one of the other fields changing indicates
667 667 # something fishy going on, so return False if anything but atime
668 668 # changes.
669 669 return (
670 670 self.stat.st_mode == other.stat.st_mode
671 671 and self.stat.st_ino == other.stat.st_ino
672 672 and self.stat.st_dev == other.stat.st_dev
673 673 and self.stat.st_nlink == other.stat.st_nlink
674 674 and self.stat.st_uid == other.stat.st_uid
675 675 and self.stat.st_gid == other.stat.st_gid
676 676 and self.stat.st_size == other.stat.st_size
677 677 and self.stat[stat.ST_MTIME] == other.stat[stat.ST_MTIME]
678 678 and self.stat[stat.ST_CTIME] == other.stat[stat.ST_CTIME]
679 679 )
680 680 except AttributeError:
681 681 return False
682 682
683 683 def __ne__(self, other):
684 684 return not self == other
685 685
686 686
687 687 def statislink(st):
688 688 '''check whether a stat result is a symlink'''
689 689 return st and stat.S_ISLNK(st.st_mode)
690 690
691 691
692 692 def statisexec(st):
693 693 '''check whether a stat result is an executable file'''
694 694 return st and (st.st_mode & 0o100 != 0)
695 695
696 696
697 697 def poll(fds):
698 698 """block until something happens on any file descriptor
699 699
700 700 This is a generic helper that will check for any activity
701 701 (read, write. exception) and return the list of touched files.
702 702
703 703 In unsupported cases, it will raise a NotImplementedError"""
704 704 try:
705 705 res = select.select(fds, fds, fds)
706 706 except ValueError: # out of range file descriptor
707 707 raise NotImplementedError()
708 708 return sorted(list(set(sum(res, []))))
709 709
710 710
711 711 def readpipe(pipe):
712 712 """Read all available data from a pipe."""
713 713 # We can't fstat() a pipe because Linux will always report 0.
714 714 # So, we set the pipe to non-blocking mode and read everything
715 715 # that's available.
716 716 flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
717 717 flags |= os.O_NONBLOCK
718 718 oldflags = fcntl.fcntl(pipe, fcntl.F_SETFL, flags)
719 719
720 720 try:
721 721 chunks = []
722 722 while True:
723 723 try:
724 724 s = pipe.read()
725 725 if not s:
726 726 break
727 727 chunks.append(s)
728 728 except IOError:
729 729 break
730 730
731 731 return b''.join(chunks)
732 732 finally:
733 733 fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
734 734
735 735
736 736 def bindunixsocket(sock, path):
737 737 """Bind the UNIX domain socket to the specified path"""
738 738 # use relative path instead of full path at bind() if possible, since
739 739 # AF_UNIX path has very small length limit (107 chars) on common
740 740 # platforms (see sys/un.h)
741 741 dirname, basename = os.path.split(path)
742 742 bakwdfd = None
743 743
744 744 try:
745 745 if dirname:
746 746 bakwdfd = os.open(b'.', os.O_DIRECTORY)
747 747 os.chdir(dirname)
748 748 sock.bind(basename)
749 749 if bakwdfd:
750 750 os.fchdir(bakwdfd)
751 751 finally:
752 752 if bakwdfd:
753 753 os.close(bakwdfd)
@@ -1,795 +1,801 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 Olivia Mackall <olivia@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
11 11 import contextlib
12 12 import errno
13 13 import io
14 14 import os
15 15 import signal
16 16 import subprocess
17 17 import sys
18 18 import threading
19 19 import time
20 20
21 21 from typing import (
22 22 BinaryIO,
23 23 )
24 24
25 25 from ..i18n import _
26 26 from ..pycompat import (
27 27 getattr,
28 28 open,
29 29 )
30 30
31 31 from .. import (
32 32 encoding,
33 33 error,
34 34 policy,
35 35 pycompat,
36 36 typelib,
37 37 )
38 38
39 39 # Import like this to keep import-checker happy
40 40 from ..utils import resourceutil
41 41
42 42 osutil = policy.importmod('osutil')
43 43
44 44 if pycompat.iswindows:
45 45 from .. import windows as platform
46 46 else:
47 47 from .. import posix as platform
48 48
49 49
50 50 def isatty(fp):
51 51 try:
52 52 return fp.isatty()
53 53 except AttributeError:
54 54 return False
55 55
56 56
57 57 class BadFile(io.RawIOBase):
58 58 """Dummy file object to simulate closed stdio behavior"""
59 59
60 60 def readinto(self, b):
61 61 raise IOError(errno.EBADF, 'Bad file descriptor')
62 62
63 63 def write(self, b):
64 64 raise IOError(errno.EBADF, 'Bad file descriptor')
65 65
66 66
67 67 class LineBufferedWrapper:
68 68 def __init__(self, orig):
69 69 self.orig = orig
70 70
71 71 def __getattr__(self, attr):
72 72 return getattr(self.orig, attr)
73 73
74 74 def write(self, s):
75 75 orig = self.orig
76 76 res = orig.write(s)
77 77 if s.endswith(b'\n'):
78 78 orig.flush()
79 79 return res
80 80
81 81
82 82 # pytype: disable=attribute-error
83 83 io.BufferedIOBase.register(LineBufferedWrapper)
84 84 # pytype: enable=attribute-error
85 85
86 86
87 87 def make_line_buffered(stream):
88 88 # First, check if we need to wrap the stream.
89 89 check_stream = stream
90 90 while True:
91 91 if isinstance(check_stream, WriteAllWrapper):
92 92 check_stream = check_stream.orig
93 93 elif pycompat.iswindows and isinstance(
94 94 check_stream,
95 95 # pytype: disable=module-attr
96 96 platform.winstdout
97 97 # pytype: enable=module-attr
98 98 ):
99 99 check_stream = check_stream.fp
100 100 else:
101 101 break
102 102 if isinstance(check_stream, io.RawIOBase):
103 103 # The stream is unbuffered, we don't need to emulate line buffering.
104 104 return stream
105 105 elif isinstance(check_stream, io.BufferedIOBase):
106 106 # The stream supports some kind of buffering. We can't assume that
107 107 # lines are flushed. Fall back to wrapping the stream.
108 108 pass
109 109 else:
110 110 raise NotImplementedError(
111 111 "can't determine whether stream is buffered or not"
112 112 )
113 113
114 114 if isinstance(stream, LineBufferedWrapper):
115 115 return stream
116 116 return LineBufferedWrapper(stream)
117 117
118 118
119 119 def unwrap_line_buffered(stream):
120 120 if isinstance(stream, LineBufferedWrapper):
121 121 assert not isinstance(stream.orig, LineBufferedWrapper)
122 122 return stream.orig
123 123 return stream
124 124
125 125
126 126 class WriteAllWrapper(typelib.BinaryIO_Proxy):
127 127 def __init__(self, orig: BinaryIO):
128 128 self.orig = orig
129 129
130 130 def __getattr__(self, attr):
131 131 return getattr(self.orig, attr)
132 132
133 133 def write(self, s):
134 134 write1 = self.orig.write
135 135 m = memoryview(s)
136 136 total_to_write = len(s)
137 137 total_written = 0
138 138 while total_written < total_to_write:
139 139 c = write1(m[total_written:])
140 140 if c:
141 141 total_written += c
142 142 return total_written
143 143
144 144
145 145 # pytype: disable=attribute-error
146 146 io.IOBase.register(WriteAllWrapper)
147 147 # pytype: enable=attribute-error
148 148
149 149
150 150 def _make_write_all(stream):
151 151 if isinstance(stream, WriteAllWrapper):
152 152 return stream
153 153 if isinstance(stream, io.BufferedIOBase):
154 154 # The io.BufferedIOBase.write() contract guarantees that all data is
155 155 # written.
156 156 return stream
157 157 # In general, the write() method of streams is free to write only part of
158 158 # the data.
159 159 return WriteAllWrapper(stream)
160 160
161 161
162 162 # Python 3 implements its own I/O streams. Unlike stdio of C library,
163 163 # sys.stdin/stdout/stderr may be None if underlying fd is closed.
164 164
165 165 # TODO: .buffer might not exist if std streams were replaced; we'll need
166 166 # a silly wrapper to make a bytes stream backed by a unicode one.
167 167
168 168 if sys.stdin is None:
169 169 stdin = BadFile()
170 170 else:
171 171 stdin = sys.stdin.buffer
172 172 if sys.stdout is None:
173 173 stdout = BadFile()
174 174 else:
175 175 stdout = _make_write_all(sys.stdout.buffer)
176 176 if sys.stderr is None:
177 177 stderr = BadFile()
178 178 else:
179 179 stderr = _make_write_all(sys.stderr.buffer)
180 180
181 181 if pycompat.iswindows:
182 182 # Work around Windows bugs.
183 183 stdout = platform.winstdout(stdout) # pytype: disable=module-attr
184 184 stderr = platform.winstdout(stderr) # pytype: disable=module-attr
185 185 if isatty(stdout):
186 186 # The standard library doesn't offer line-buffered binary streams.
187 187 stdout = make_line_buffered(stdout)
188 188
189 189 findexe = platform.findexe
190 190 _gethgcmd = platform.gethgcmd
191 191 getuser = platform.getuser
192 192 getpid = os.getpid
193 193 hidewindow = platform.hidewindow
194 194 readpipe = platform.readpipe
195 195 setbinary = platform.setbinary
196 196 setsignalhandler = platform.setsignalhandler
197 197 shellquote = platform.shellquote
198 198 shellsplit = platform.shellsplit
199 199 spawndetached = platform.spawndetached
200 200 sshargs = platform.sshargs
201 201 testpid = platform.testpid
202 202
203 203 try:
204 204 setprocname = osutil.setprocname
205 205 except AttributeError:
206 206 pass
207 207 try:
208 208 unblocksignal = osutil.unblocksignal
209 209 except AttributeError:
210 210 pass
211 211
212 212 closefds = pycompat.isposix
213 213
214 214
215 215 def explainexit(code):
216 216 """return a message describing a subprocess status
217 217 (codes from kill are negative - not os.system/wait encoding)"""
218 218 if code >= 0:
219 219 return _(b"exited with status %d") % code
220 220 return _(b"killed by signal %d") % -code
221 221
222 222
223 223 class _pfile:
224 224 """File-like wrapper for a stream opened by subprocess.Popen()"""
225 225
226 226 def __init__(self, proc, fp):
227 227 self._proc = proc
228 228 self._fp = fp
229 229
230 230 def close(self):
231 231 # unlike os.popen(), this returns an integer in subprocess coding
232 232 self._fp.close()
233 233 return self._proc.wait()
234 234
235 235 def __iter__(self):
236 236 return iter(self._fp)
237 237
238 238 def __getattr__(self, attr):
239 239 return getattr(self._fp, attr)
240 240
241 241 def __enter__(self):
242 242 return self
243 243
244 244 def __exit__(self, exc_type, exc_value, exc_tb):
245 245 self.close()
246 246
247 247
248 248 def popen(cmd, mode=b'rb', bufsize=-1):
249 249 if mode == b'rb':
250 250 return _popenreader(cmd, bufsize)
251 251 elif mode == b'wb':
252 252 return _popenwriter(cmd, bufsize)
253 253 raise error.ProgrammingError(b'unsupported mode: %r' % mode)
254 254
255 255
256 256 def _popenreader(cmd, bufsize):
257 257 p = subprocess.Popen(
258 258 tonativestr(cmd),
259 259 shell=True,
260 260 bufsize=bufsize,
261 261 close_fds=closefds,
262 262 stdout=subprocess.PIPE,
263 263 )
264 264 return _pfile(p, p.stdout)
265 265
266 266
267 267 def _popenwriter(cmd, bufsize):
268 268 p = subprocess.Popen(
269 269 tonativestr(cmd),
270 270 shell=True,
271 271 bufsize=bufsize,
272 272 close_fds=closefds,
273 273 stdin=subprocess.PIPE,
274 274 )
275 275 return _pfile(p, p.stdin)
276 276
277 277
278 278 def popen2(cmd, env=None):
279 279 # Setting bufsize to -1 lets the system decide the buffer size.
280 280 # The default for bufsize is 0, meaning unbuffered. This leads to
281 281 # poor performance on Mac OS X: http://bugs.python.org/issue4194
282 282 p = subprocess.Popen(
283 283 tonativestr(cmd),
284 284 shell=True,
285 285 bufsize=-1,
286 286 close_fds=closefds,
287 287 stdin=subprocess.PIPE,
288 288 stdout=subprocess.PIPE,
289 289 env=tonativeenv(env),
290 290 )
291 291 return p.stdin, p.stdout
292 292
293 293
294 294 def popen3(cmd, env=None):
295 295 stdin, stdout, stderr, p = popen4(cmd, env)
296 296 return stdin, stdout, stderr
297 297
298 298
299 299 def popen4(cmd, env=None, bufsize=-1):
300 300 p = subprocess.Popen(
301 301 tonativestr(cmd),
302 302 shell=True,
303 303 bufsize=bufsize,
304 304 close_fds=closefds,
305 305 stdin=subprocess.PIPE,
306 306 stdout=subprocess.PIPE,
307 307 stderr=subprocess.PIPE,
308 308 env=tonativeenv(env),
309 309 )
310 310 return p.stdin, p.stdout, p.stderr, p
311 311
312 312
313 313 def pipefilter(s, cmd):
314 314 '''filter string S through command CMD, returning its output'''
315 315 p = subprocess.Popen(
316 316 tonativestr(cmd),
317 317 shell=True,
318 318 close_fds=closefds,
319 319 stdin=subprocess.PIPE,
320 320 stdout=subprocess.PIPE,
321 321 )
322 322 pout, perr = p.communicate(s)
323 323 return pout
324 324
325 325
326 326 def tempfilter(s, cmd):
327 327 """filter string S through a pair of temporary files with CMD.
328 328 CMD is used as a template to create the real command to be run,
329 329 with the strings INFILE and OUTFILE replaced by the real names of
330 330 the temporary files generated."""
331 331 inname, outname = None, None
332 332 try:
333 333 infd, inname = pycompat.mkstemp(prefix=b'hg-filter-in-')
334 334 fp = os.fdopen(infd, 'wb')
335 335 fp.write(s)
336 336 fp.close()
337 337 outfd, outname = pycompat.mkstemp(prefix=b'hg-filter-out-')
338 338 os.close(outfd)
339 339 cmd = cmd.replace(b'INFILE', inname)
340 340 cmd = cmd.replace(b'OUTFILE', outname)
341 341 code = system(cmd)
342 342 if pycompat.sysplatform == b'OpenVMS' and code & 1:
343 343 code = 0
344 344 if code:
345 345 raise error.Abort(
346 346 _(b"command '%s' failed: %s") % (cmd, explainexit(code))
347 347 )
348 348 with open(outname, b'rb') as fp:
349 349 return fp.read()
350 350 finally:
351 351 try:
352 352 if inname:
353 353 os.unlink(inname)
354 354 except OSError:
355 355 pass
356 356 try:
357 357 if outname:
358 358 os.unlink(outname)
359 359 except OSError:
360 360 pass
361 361
362 362
363 363 _filtertable = {
364 364 b'tempfile:': tempfilter,
365 365 b'pipe:': pipefilter,
366 366 }
367 367
368 368
369 369 def filter(s, cmd):
370 370 """filter a string through a command that transforms its input to its
371 371 output"""
372 372 for name, fn in _filtertable.items():
373 373 if cmd.startswith(name):
374 374 return fn(s, cmd[len(name) :].lstrip())
375 375 return pipefilter(s, cmd)
376 376
377 377
378 378 _hgexecutable = None
379 379
380 380
381 381 def hgexecutable():
382 382 """return location of the 'hg' executable.
383 383
384 384 Defaults to $HG or 'hg' in the search path.
385 385 """
386 386 if _hgexecutable is None:
387 387 hg = encoding.environ.get(b'HG')
388 388 mainmod = sys.modules['__main__']
389 389 if hg:
390 390 _sethgexecutable(hg)
391 391 elif resourceutil.mainfrozen():
392 392 if getattr(sys, 'frozen', None) == 'macosx_app':
393 393 # Env variable set by py2app
394 394 _sethgexecutable(encoding.environ[b'EXECUTABLEPATH'])
395 395 else:
396 396 _sethgexecutable(pycompat.sysexecutable)
397 397 elif (
398 398 not pycompat.iswindows
399 399 and os.path.basename(getattr(mainmod, '__file__', '')) == 'hg'
400 400 ):
401 401 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
402 402 else:
403 403 _sethgexecutable(
404 404 findexe(b'hg') or os.path.basename(pycompat.sysargv[0])
405 405 )
406 406 return _hgexecutable
407 407
408 408
409 409 def _sethgexecutable(path):
410 410 """set location of the 'hg' executable"""
411 411 global _hgexecutable
412 412 _hgexecutable = path
413 413
414 414
415 415 def _testfileno(f, stdf):
416 416 fileno = getattr(f, 'fileno', None)
417 417 try:
418 418 return fileno and fileno() == stdf.fileno()
419 419 except io.UnsupportedOperation:
420 420 return False # fileno() raised UnsupportedOperation
421 421
422 422
423 423 def isstdin(f):
424 424 return _testfileno(f, sys.__stdin__)
425 425
426 426
427 427 def isstdout(f):
428 428 return _testfileno(f, sys.__stdout__)
429 429
430 430
431 431 def protectstdio(uin, uout):
432 432 """Duplicate streams and redirect original if (uin, uout) are stdio
433 433
434 434 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
435 435 redirected to stderr so the output is still readable.
436 436
437 437 Returns (fin, fout) which point to the original (uin, uout) fds, but
438 438 may be copy of (uin, uout). The returned streams can be considered
439 439 "owned" in that print(), exec(), etc. never reach to them.
440 440 """
441 441 uout.flush()
442 442 fin, fout = uin, uout
443 443 if _testfileno(uin, stdin):
444 444 newfd = os.dup(uin.fileno())
445 445 nullfd = os.open(os.devnull, os.O_RDONLY)
446 446 os.dup2(nullfd, uin.fileno())
447 447 os.close(nullfd)
448 448 fin = os.fdopen(newfd, 'rb')
449 449 if _testfileno(uout, stdout):
450 450 newfd = os.dup(uout.fileno())
451 451 os.dup2(stderr.fileno(), uout.fileno())
452 452 fout = os.fdopen(newfd, 'wb')
453 453 return fin, fout
454 454
455 455
456 456 def restorestdio(uin, uout, fin, fout):
457 457 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
458 458 uout.flush()
459 459 for f, uif in [(fin, uin), (fout, uout)]:
460 460 if f is not uif:
461 461 os.dup2(f.fileno(), uif.fileno())
462 462 f.close()
463 463
464 464
465 465 def shellenviron(environ=None):
466 466 """return environ with optional override, useful for shelling out"""
467 467
468 468 def py2shell(val):
469 469 """convert python object into string that is useful to shell"""
470 470 if val is None or val is False:
471 471 return b'0'
472 472 if val is True:
473 473 return b'1'
474 474 return pycompat.bytestr(val)
475 475
476 476 env = dict(encoding.environ)
477 477 if environ:
478 478 env.update((k, py2shell(v)) for k, v in environ.items())
479 479 env[b'HG'] = hgexecutable()
480 480 return env
481 481
482 482
483 483 if pycompat.iswindows:
484 484
485 485 def shelltonative(cmd, env):
486 486 return platform.shelltocmdexe( # pytype: disable=module-attr
487 487 cmd, shellenviron(env)
488 488 )
489 489
490 490 tonativestr = encoding.strfromlocal
491 491 else:
492 492
493 493 def shelltonative(cmd, env):
494 494 return cmd
495 495
496 496 tonativestr = pycompat.identity
497 497
498 498
499 499 def tonativeenv(env):
500 500 """convert the environment from bytes to strings suitable for Popen(), etc."""
501 501 return pycompat.rapply(tonativestr, env)
502 502
503 503
504 504 def system(cmd, environ=None, cwd=None, out=None):
505 505 """enhanced shell command execution.
506 506 run with environment maybe modified, maybe in different dir.
507 507
508 508 if out is specified, it is assumed to be a file-like object that has a
509 509 write() method. stdout and stderr will be redirected to out."""
510 510 try:
511 511 stdout.flush()
512 512 except Exception:
513 513 pass
514 514 env = shellenviron(environ)
515 515 if out is None or isstdout(out):
516 516 rc = subprocess.call(
517 517 tonativestr(cmd),
518 518 shell=True,
519 519 close_fds=closefds,
520 520 env=tonativeenv(env),
521 521 cwd=pycompat.rapply(tonativestr, cwd),
522 522 )
523 523 else:
524 524 proc = subprocess.Popen(
525 525 tonativestr(cmd),
526 526 shell=True,
527 527 close_fds=closefds,
528 528 env=tonativeenv(env),
529 529 cwd=pycompat.rapply(tonativestr, cwd),
530 530 stdout=subprocess.PIPE,
531 531 stderr=subprocess.STDOUT,
532 532 )
533 533 for line in iter(proc.stdout.readline, b''):
534 534 out.write(line)
535 535 proc.wait()
536 536 rc = proc.returncode
537 537 if pycompat.sysplatform == b'OpenVMS' and rc & 1:
538 538 rc = 0
539 539 return rc
540 540
541 541
542 542 _is_gui = None
543 543
544 544
545 545 def _gui():
546 546 '''Are we running in a GUI?'''
547 547 if pycompat.isdarwin:
548 548 if b'SSH_CONNECTION' in encoding.environ:
549 549 # handle SSH access to a box where the user is logged in
550 550 return False
551 551 elif getattr(osutil, 'isgui', None):
552 552 # check if a CoreGraphics session is available
553 553 return osutil.isgui()
554 554 else:
555 555 # pure build; use a safe default
556 556 return True
557 557 else:
558 558 return (
559 559 pycompat.iswindows
560 560 or encoding.environ.get(b"DISPLAY")
561 561 or encoding.environ.get(b"WAYLAND_DISPLAY")
562 562 )
563 563
564 564
565 565 def gui():
566 566 global _is_gui
567 567 if _is_gui is None:
568 568 _is_gui = _gui()
569 569 return _is_gui
570 570
571 571
572 572 def hgcmd():
573 573 """Return the command used to execute current hg
574 574
575 575 This is different from hgexecutable() because on Windows we want
576 576 to avoid things opening new shell windows like batch files, so we
577 577 get either the python call or current executable.
578 578 """
579 579 if resourceutil.mainfrozen():
580 580 if getattr(sys, 'frozen', None) == 'macosx_app':
581 581 # Env variable set by py2app
582 582 return [encoding.environ[b'EXECUTABLEPATH']]
583 583 else:
584 584 return [pycompat.sysexecutable]
585 585 return _gethgcmd()
586 586
587 587
588 def rundetached(args, condfn):
588 def rundetached(args, condfn) -> int:
589 589 """Execute the argument list in a detached process.
590 590
591 591 condfn is a callable which is called repeatedly and should return
592 592 True once the child process is known to have started successfully.
593 593 At this point, the child process PID is returned. If the child
594 594 process fails to start or finishes before condfn() evaluates to
595 595 True, return -1.
596 596 """
597 597 # Windows case is easier because the child process is either
598 598 # successfully starting and validating the condition or exiting
599 599 # on failure. We just poll on its PID. On Unix, if the child
600 600 # process fails to start, it will be left in a zombie state until
601 601 # the parent wait on it, which we cannot do since we expect a long
602 602 # running process on success. Instead we listen for SIGCHLD telling
603 603 # us our child process terminated.
604 604 terminated = set()
605 605
606 606 def handler(signum, frame):
607 607 terminated.add(os.wait())
608 608
609 609 prevhandler = None
610 610 SIGCHLD = getattr(signal, 'SIGCHLD', None)
611 611 if SIGCHLD is not None:
612 612 prevhandler = signal.signal(SIGCHLD, handler)
613 613 try:
614 614 pid = spawndetached(args)
615 615 while not condfn():
616 616 if (pid in terminated or not testpid(pid)) and not condfn():
617 617 return -1
618 618 time.sleep(0.1)
619 619 return pid
620 620 finally:
621 621 if prevhandler is not None:
622 622 signal.signal(signal.SIGCHLD, prevhandler)
623 623
624 # pytype seems to get confused by not having a return in the finally
625 # block, and thinks the return value should be Optional[int] here. It
626 # appears to be https://github.com/google/pytype/issues/938, without
627 # the `with` clause.
628 pass # pytype: disable=bad-return-type
629
624 630
625 631 @contextlib.contextmanager
626 632 def uninterruptible(warn):
627 633 """Inhibit SIGINT handling on a region of code.
628 634
629 635 Note that if this is called in a non-main thread, it turns into a no-op.
630 636
631 637 Args:
632 638 warn: A callable which takes no arguments, and returns True if the
633 639 previous signal handling should be restored.
634 640 """
635 641
636 642 oldsiginthandler = [signal.getsignal(signal.SIGINT)]
637 643 shouldbail = []
638 644
639 645 def disabledsiginthandler(*args):
640 646 if warn():
641 647 signal.signal(signal.SIGINT, oldsiginthandler[0])
642 648 del oldsiginthandler[0]
643 649 shouldbail.append(True)
644 650
645 651 try:
646 652 try:
647 653 signal.signal(signal.SIGINT, disabledsiginthandler)
648 654 except ValueError:
649 655 # wrong thread, oh well, we tried
650 656 del oldsiginthandler[0]
651 657 yield
652 658 finally:
653 659 if oldsiginthandler:
654 660 signal.signal(signal.SIGINT, oldsiginthandler[0])
655 661 if shouldbail:
656 662 raise KeyboardInterrupt
657 663
658 664
659 665 if pycompat.iswindows:
660 666 # no fork on Windows, but we can create a detached process
661 667 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
662 668 # No stdlib constant exists for this value
663 669 DETACHED_PROCESS = 0x00000008
664 670 # Following creation flags might create a console GUI window.
665 671 # Using subprocess.CREATE_NEW_CONSOLE might helps.
666 672 # See https://phab.mercurial-scm.org/D1701 for discussion
667 673 _creationflags = (
668 674 DETACHED_PROCESS
669 675 | subprocess.CREATE_NEW_PROCESS_GROUP # pytype: disable=module-attr
670 676 )
671 677
672 678 def runbgcommand(
673 679 script,
674 680 env,
675 681 shell=False,
676 682 stdout=None,
677 683 stderr=None,
678 684 ensurestart=True,
679 685 record_wait=None,
680 686 stdin_bytes=None,
681 687 ):
682 688 '''Spawn a command without waiting for it to finish.'''
683 689 # we can't use close_fds *and* redirect stdin. I'm not sure that we
684 690 # need to because the detached process has no console connection.
685 691
686 692 try:
687 693 stdin = None
688 694 if stdin_bytes is not None:
689 695 stdin = pycompat.unnamedtempfile()
690 696 stdin.write(stdin_bytes)
691 697 stdin.flush()
692 698 stdin.seek(0)
693 699
694 700 p = subprocess.Popen(
695 701 pycompat.rapply(tonativestr, script),
696 702 shell=shell,
697 703 env=tonativeenv(env),
698 704 close_fds=True,
699 705 creationflags=_creationflags,
700 706 stdin=stdin,
701 707 stdout=stdout,
702 708 stderr=stderr,
703 709 )
704 710 if record_wait is not None:
705 711 record_wait(p.wait)
706 712 finally:
707 713 if stdin is not None:
708 714 stdin.close()
709 715
710 716
711 717 else:
712 718
713 719 def runbgcommand(
714 720 cmd,
715 721 env,
716 722 shell=False,
717 723 stdout=None,
718 724 stderr=None,
719 725 ensurestart=True,
720 726 record_wait=None,
721 727 stdin_bytes=None,
722 728 ):
723 729 """Spawn a command without waiting for it to finish.
724 730
725 731
726 732 When `record_wait` is not None, the spawned process will not be fully
727 733 detached and the `record_wait` argument will be called with a the
728 734 `Subprocess.wait` function for the spawned process. This is mostly
729 735 useful for developers that need to make sure the spawned process
730 736 finished before a certain point. (eg: writing test)"""
731 737 if pycompat.isdarwin:
732 738 # avoid crash in CoreFoundation in case another thread
733 739 # calls gui() while we're calling fork().
734 740 gui()
735 741
736 742 if shell:
737 743 script = cmd
738 744 else:
739 745 if isinstance(cmd, bytes):
740 746 cmd = [cmd]
741 747 script = b' '.join(shellquote(x) for x in cmd)
742 748 if record_wait is None:
743 749 # double-fork to completely detach from the parent process
744 750 script = b'( %s ) &' % script
745 751 start_new_session = True
746 752 else:
747 753 start_new_session = False
748 754 ensurestart = True
749 755
750 756 stdin = None
751 757
752 758 try:
753 759 if stdin_bytes is None:
754 760 stdin = subprocess.DEVNULL
755 761 else:
756 762 stdin = pycompat.unnamedtempfile()
757 763 stdin.write(stdin_bytes)
758 764 stdin.flush()
759 765 stdin.seek(0)
760 766 if stdout is None:
761 767 stdout = subprocess.DEVNULL
762 768 if stderr is None:
763 769 stderr = subprocess.DEVNULL
764 770
765 771 p = subprocess.Popen(
766 772 script,
767 773 shell=True,
768 774 env=env,
769 775 close_fds=True,
770 776 stdin=stdin,
771 777 stdout=stdout,
772 778 stderr=stderr,
773 779 start_new_session=start_new_session,
774 780 )
775 781 except Exception:
776 782 if record_wait is not None:
777 783 record_wait(255)
778 784 raise
779 785 finally:
780 786 if stdin_bytes is not None and stdin is not None:
781 787 assert not isinstance(stdin, int)
782 788 stdin.close()
783 789 if not ensurestart:
784 790 # Even though we're not waiting on the child process,
785 791 # we still must call waitpid() on it at some point so
786 792 # it's not a zombie/defunct. This is especially relevant for
787 793 # chg since the parent process won't die anytime soon.
788 794 # We use a thread to make the overhead tiny.
789 795 t = threading.Thread(target=lambda: p.wait)
790 796 t.daemon = True
791 797 t.start()
792 798 else:
793 799 returncode = p.wait
794 800 if record_wait is not None:
795 801 record_wait(returncode)
@@ -1,764 +1,771 b''
1 1 # win32.py - utility functions that use win32 API
2 2 #
3 3 # Copyright 2005-2009 Olivia Mackall <olivia@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
9 9 import ctypes
10 10 import ctypes.wintypes as wintypes
11 11 import errno
12 12 import msvcrt
13 13 import os
14 14 import random
15 15 import subprocess
16 16
17 from typing import (
18 List,
19 NoReturn,
20 Optional,
21 Tuple,
22 )
23
17 24 from . import (
18 25 encoding,
19 26 pycompat,
20 27 )
21 28
22 29 # pytype: disable=module-attr
23 30 _kernel32 = ctypes.windll.kernel32
24 31 _advapi32 = ctypes.windll.advapi32
25 32 _user32 = ctypes.windll.user32
26 33 _crypt32 = ctypes.windll.crypt32
27 34 # pytype: enable=module-attr
28 35
29 36 _BOOL = ctypes.c_long
30 37 _WORD = ctypes.c_ushort
31 38 _DWORD = ctypes.c_ulong
32 39 _UINT = ctypes.c_uint
33 40 _LONG = ctypes.c_long
34 41 _LPCSTR = _LPSTR = ctypes.c_char_p
35 42 _HANDLE = ctypes.c_void_p
36 43 _HWND = _HANDLE
37 44 _PCCERT_CONTEXT = ctypes.c_void_p
38 45 _MAX_PATH = wintypes.MAX_PATH
39 46
40 47 _INVALID_HANDLE_VALUE = _HANDLE(-1).value
41 48
42 49 # GetLastError
43 50 _ERROR_SUCCESS = 0
44 51 _ERROR_NO_MORE_FILES = 18
45 52 _ERROR_INVALID_PARAMETER = 87
46 53 _ERROR_BROKEN_PIPE = 109
47 54 _ERROR_INSUFFICIENT_BUFFER = 122
48 55 _ERROR_NO_DATA = 232
49 56
50 57 # WPARAM is defined as UINT_PTR (unsigned type)
51 58 # LPARAM is defined as LONG_PTR (signed type)
52 59 if ctypes.sizeof(ctypes.c_long) == ctypes.sizeof(ctypes.c_void_p):
53 60 _WPARAM = ctypes.c_ulong
54 61 _LPARAM = ctypes.c_long
55 62 elif ctypes.sizeof(ctypes.c_longlong) == ctypes.sizeof(ctypes.c_void_p):
56 63 _WPARAM = ctypes.c_ulonglong
57 64 _LPARAM = ctypes.c_longlong
58 65
59 66
60 67 class _FILETIME(ctypes.Structure):
61 68 _fields_ = [('dwLowDateTime', _DWORD), ('dwHighDateTime', _DWORD)]
62 69
63 70
64 71 class _BY_HANDLE_FILE_INFORMATION(ctypes.Structure):
65 72 _fields_ = [
66 73 ('dwFileAttributes', _DWORD),
67 74 ('ftCreationTime', _FILETIME),
68 75 ('ftLastAccessTime', _FILETIME),
69 76 ('ftLastWriteTime', _FILETIME),
70 77 ('dwVolumeSerialNumber', _DWORD),
71 78 ('nFileSizeHigh', _DWORD),
72 79 ('nFileSizeLow', _DWORD),
73 80 ('nNumberOfLinks', _DWORD),
74 81 ('nFileIndexHigh', _DWORD),
75 82 ('nFileIndexLow', _DWORD),
76 83 ]
77 84
78 85
79 86 # CreateFile
80 87 _FILE_SHARE_READ = 0x00000001
81 88 _FILE_SHARE_WRITE = 0x00000002
82 89 _FILE_SHARE_DELETE = 0x00000004
83 90
84 91 _OPEN_EXISTING = 3
85 92
86 93 _FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
87 94
88 95 # SetFileAttributes
89 96 _FILE_ATTRIBUTE_NORMAL = 0x80
90 97 _FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x2000
91 98
92 99 # Process Security and Access Rights
93 100 _PROCESS_QUERY_INFORMATION = 0x0400
94 101
95 102 # GetExitCodeProcess
96 103 _STILL_ACTIVE = 259
97 104
98 105
99 106 class _STARTUPINFO(ctypes.Structure):
100 107 _fields_ = [
101 108 ('cb', _DWORD),
102 109 ('lpReserved', _LPSTR),
103 110 ('lpDesktop', _LPSTR),
104 111 ('lpTitle', _LPSTR),
105 112 ('dwX', _DWORD),
106 113 ('dwY', _DWORD),
107 114 ('dwXSize', _DWORD),
108 115 ('dwYSize', _DWORD),
109 116 ('dwXCountChars', _DWORD),
110 117 ('dwYCountChars', _DWORD),
111 118 ('dwFillAttribute', _DWORD),
112 119 ('dwFlags', _DWORD),
113 120 ('wShowWindow', _WORD),
114 121 ('cbReserved2', _WORD),
115 122 ('lpReserved2', ctypes.c_char_p),
116 123 ('hStdInput', _HANDLE),
117 124 ('hStdOutput', _HANDLE),
118 125 ('hStdError', _HANDLE),
119 126 ]
120 127
121 128
122 129 class _PROCESS_INFORMATION(ctypes.Structure):
123 130 _fields_ = [
124 131 ('hProcess', _HANDLE),
125 132 ('hThread', _HANDLE),
126 133 ('dwProcessId', _DWORD),
127 134 ('dwThreadId', _DWORD),
128 135 ]
129 136
130 137
131 138 _CREATE_NO_WINDOW = 0x08000000
132 139 _SW_HIDE = 0
133 140
134 141
135 142 class _COORD(ctypes.Structure):
136 143 _fields_ = [('X', ctypes.c_short), ('Y', ctypes.c_short)]
137 144
138 145
139 146 class _SMALL_RECT(ctypes.Structure):
140 147 _fields_ = [
141 148 ('Left', ctypes.c_short),
142 149 ('Top', ctypes.c_short),
143 150 ('Right', ctypes.c_short),
144 151 ('Bottom', ctypes.c_short),
145 152 ]
146 153
147 154
148 155 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
149 156 _fields_ = [
150 157 ('dwSize', _COORD),
151 158 ('dwCursorPosition', _COORD),
152 159 ('wAttributes', _WORD),
153 160 ('srWindow', _SMALL_RECT),
154 161 ('dwMaximumWindowSize', _COORD),
155 162 ]
156 163
157 164
158 165 _STD_OUTPUT_HANDLE = _DWORD(-11).value
159 166 _STD_ERROR_HANDLE = _DWORD(-12).value
160 167
161 168 # CERT_TRUST_STATUS dwErrorStatus
162 169 CERT_TRUST_IS_PARTIAL_CHAIN = 0x10000
163 170
164 171 # CertCreateCertificateContext encodings
165 172 X509_ASN_ENCODING = 0x00000001
166 173 PKCS_7_ASN_ENCODING = 0x00010000
167 174
168 175 # These structs are only complete enough to achieve what we need.
169 176 class CERT_CHAIN_CONTEXT(ctypes.Structure):
170 177 _fields_ = (
171 178 ("cbSize", _DWORD),
172 179 # CERT_TRUST_STATUS struct
173 180 ("dwErrorStatus", _DWORD),
174 181 ("dwInfoStatus", _DWORD),
175 182 ("cChain", _DWORD),
176 183 ("rgpChain", ctypes.c_void_p),
177 184 ("cLowerQualityChainContext", _DWORD),
178 185 ("rgpLowerQualityChainContext", ctypes.c_void_p),
179 186 ("fHasRevocationFreshnessTime", _BOOL),
180 187 ("dwRevocationFreshnessTime", _DWORD),
181 188 )
182 189
183 190
184 191 class CERT_USAGE_MATCH(ctypes.Structure):
185 192 _fields_ = (
186 193 ("dwType", _DWORD),
187 194 # CERT_ENHKEY_USAGE struct
188 195 ("cUsageIdentifier", _DWORD),
189 196 ("rgpszUsageIdentifier", ctypes.c_void_p), # LPSTR *
190 197 )
191 198
192 199
193 200 class CERT_CHAIN_PARA(ctypes.Structure):
194 201 _fields_ = (
195 202 ("cbSize", _DWORD),
196 203 ("RequestedUsage", CERT_USAGE_MATCH),
197 204 ("RequestedIssuancePolicy", CERT_USAGE_MATCH),
198 205 ("dwUrlRetrievalTimeout", _DWORD),
199 206 ("fCheckRevocationFreshnessTime", _BOOL),
200 207 ("dwRevocationFreshnessTime", _DWORD),
201 208 ("pftCacheResync", ctypes.c_void_p), # LPFILETIME
202 209 ("pStrongSignPara", ctypes.c_void_p), # PCCERT_STRONG_SIGN_PARA
203 210 ("dwStrongSignFlags", _DWORD),
204 211 )
205 212
206 213
207 214 # types of parameters of C functions used (required by pypy)
208 215
209 216 _crypt32.CertCreateCertificateContext.argtypes = [
210 217 _DWORD, # cert encoding
211 218 ctypes.c_char_p, # cert
212 219 _DWORD,
213 220 ] # cert size
214 221 _crypt32.CertCreateCertificateContext.restype = _PCCERT_CONTEXT
215 222
216 223 _crypt32.CertGetCertificateChain.argtypes = [
217 224 ctypes.c_void_p, # HCERTCHAINENGINE
218 225 _PCCERT_CONTEXT,
219 226 ctypes.c_void_p, # LPFILETIME
220 227 ctypes.c_void_p, # HCERTSTORE
221 228 ctypes.c_void_p, # PCERT_CHAIN_PARA
222 229 _DWORD,
223 230 ctypes.c_void_p, # LPVOID
224 231 ctypes.c_void_p, # PCCERT_CHAIN_CONTEXT *
225 232 ]
226 233 _crypt32.CertGetCertificateChain.restype = _BOOL
227 234
228 235 _crypt32.CertFreeCertificateContext.argtypes = [_PCCERT_CONTEXT]
229 236 _crypt32.CertFreeCertificateContext.restype = _BOOL
230 237
231 238 _kernel32.CreateFileA.argtypes = [
232 239 _LPCSTR,
233 240 _DWORD,
234 241 _DWORD,
235 242 ctypes.c_void_p,
236 243 _DWORD,
237 244 _DWORD,
238 245 _HANDLE,
239 246 ]
240 247 _kernel32.CreateFileA.restype = _HANDLE
241 248
242 249 _kernel32.GetFileInformationByHandle.argtypes = [_HANDLE, ctypes.c_void_p]
243 250 _kernel32.GetFileInformationByHandle.restype = _BOOL
244 251
245 252 _kernel32.CloseHandle.argtypes = [_HANDLE]
246 253 _kernel32.CloseHandle.restype = _BOOL
247 254
248 255 try:
249 256 _kernel32.CreateHardLinkA.argtypes = [_LPCSTR, _LPCSTR, ctypes.c_void_p]
250 257 _kernel32.CreateHardLinkA.restype = _BOOL
251 258 except AttributeError:
252 259 pass
253 260
254 261 _kernel32.SetFileAttributesA.argtypes = [_LPCSTR, _DWORD]
255 262 _kernel32.SetFileAttributesA.restype = _BOOL
256 263
257 264 _DRIVE_UNKNOWN = 0
258 265 _DRIVE_NO_ROOT_DIR = 1
259 266 _DRIVE_REMOVABLE = 2
260 267 _DRIVE_FIXED = 3
261 268 _DRIVE_REMOTE = 4
262 269 _DRIVE_CDROM = 5
263 270 _DRIVE_RAMDISK = 6
264 271
265 272 _kernel32.GetDriveTypeA.argtypes = [_LPCSTR]
266 273 _kernel32.GetDriveTypeA.restype = _UINT
267 274
268 275 _kernel32.GetVolumeInformationA.argtypes = [
269 276 _LPCSTR,
270 277 ctypes.c_void_p,
271 278 _DWORD,
272 279 ctypes.c_void_p,
273 280 ctypes.c_void_p,
274 281 ctypes.c_void_p,
275 282 ctypes.c_void_p,
276 283 _DWORD,
277 284 ]
278 285 _kernel32.GetVolumeInformationA.restype = _BOOL
279 286
280 287 _kernel32.GetVolumePathNameA.argtypes = [_LPCSTR, ctypes.c_void_p, _DWORD]
281 288 _kernel32.GetVolumePathNameA.restype = _BOOL
282 289
283 290 _kernel32.OpenProcess.argtypes = [_DWORD, _BOOL, _DWORD]
284 291 _kernel32.OpenProcess.restype = _HANDLE
285 292
286 293 _kernel32.GetExitCodeProcess.argtypes = [_HANDLE, ctypes.c_void_p]
287 294 _kernel32.GetExitCodeProcess.restype = _BOOL
288 295
289 296 _kernel32.GetLastError.argtypes = []
290 297 _kernel32.GetLastError.restype = _DWORD
291 298
292 299 _kernel32.GetModuleFileNameA.argtypes = [_HANDLE, ctypes.c_void_p, _DWORD]
293 300 _kernel32.GetModuleFileNameA.restype = _DWORD
294 301
295 302 _kernel32.CreateProcessA.argtypes = [
296 303 _LPCSTR,
297 304 _LPCSTR,
298 305 ctypes.c_void_p,
299 306 ctypes.c_void_p,
300 307 _BOOL,
301 308 _DWORD,
302 309 ctypes.c_void_p,
303 310 _LPCSTR,
304 311 ctypes.c_void_p,
305 312 ctypes.c_void_p,
306 313 ]
307 314 _kernel32.CreateProcessA.restype = _BOOL
308 315
309 316 _kernel32.ExitProcess.argtypes = [_UINT]
310 317 _kernel32.ExitProcess.restype = None
311 318
312 319 _kernel32.GetCurrentProcessId.argtypes = []
313 320 _kernel32.GetCurrentProcessId.restype = _DWORD
314 321
315 322 # pytype: disable=module-attr
316 323 _SIGNAL_HANDLER = ctypes.WINFUNCTYPE(_BOOL, _DWORD)
317 324 # pytype: enable=module-attr
318 325 _kernel32.SetConsoleCtrlHandler.argtypes = [_SIGNAL_HANDLER, _BOOL]
319 326 _kernel32.SetConsoleCtrlHandler.restype = _BOOL
320 327
321 328 _kernel32.SetConsoleMode.argtypes = [_HANDLE, _DWORD]
322 329 _kernel32.SetConsoleMode.restype = _BOOL
323 330
324 331 _kernel32.GetConsoleMode.argtypes = [_HANDLE, ctypes.c_void_p]
325 332 _kernel32.GetConsoleMode.restype = _BOOL
326 333
327 334 _kernel32.GetStdHandle.argtypes = [_DWORD]
328 335 _kernel32.GetStdHandle.restype = _HANDLE
329 336
330 337 _kernel32.GetConsoleScreenBufferInfo.argtypes = [_HANDLE, ctypes.c_void_p]
331 338 _kernel32.GetConsoleScreenBufferInfo.restype = _BOOL
332 339
333 340 _advapi32.GetUserNameA.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
334 341 _advapi32.GetUserNameA.restype = _BOOL
335 342
336 343 _user32.GetWindowThreadProcessId.argtypes = [_HANDLE, ctypes.c_void_p]
337 344 _user32.GetWindowThreadProcessId.restype = _DWORD
338 345
339 346 _user32.ShowWindow.argtypes = [_HANDLE, ctypes.c_int]
340 347 _user32.ShowWindow.restype = _BOOL
341 348
342 349 # pytype: disable=module-attr
343 350 _WNDENUMPROC = ctypes.WINFUNCTYPE(_BOOL, _HWND, _LPARAM)
344 351 # pytype: enable=module-attr
345 352 _user32.EnumWindows.argtypes = [_WNDENUMPROC, _LPARAM]
346 353 _user32.EnumWindows.restype = _BOOL
347 354
348 355 _kernel32.PeekNamedPipe.argtypes = [
349 356 _HANDLE,
350 357 ctypes.c_void_p,
351 358 _DWORD,
352 359 ctypes.c_void_p,
353 360 ctypes.c_void_p,
354 361 ctypes.c_void_p,
355 362 ]
356 363 _kernel32.PeekNamedPipe.restype = _BOOL
357 364
358 365
359 def _raiseoserror(name):
366 def _raiseoserror(name: bytes) -> NoReturn:
360 367 # Force the code to a signed int to avoid an 'int too large' error.
361 368 # See https://bugs.python.org/issue28474
362 369 code = _kernel32.GetLastError()
363 370 if code > 0x7FFFFFFF:
364 371 code -= 2 ** 32
365 372 err = ctypes.WinError(code=code) # pytype: disable=module-attr
366 373 raise OSError(
367 374 err.errno, '%s: %s' % (encoding.strfromlocal(name), err.strerror)
368 375 )
369 376
370 377
371 def _getfileinfo(name):
378 def _getfileinfo(name: bytes) -> _BY_HANDLE_FILE_INFORMATION:
372 379 fh = _kernel32.CreateFileA(
373 380 name,
374 381 0,
375 382 _FILE_SHARE_READ | _FILE_SHARE_WRITE | _FILE_SHARE_DELETE,
376 383 None,
377 384 _OPEN_EXISTING,
378 385 _FILE_FLAG_BACKUP_SEMANTICS,
379 386 None,
380 387 )
381 388 if fh == _INVALID_HANDLE_VALUE:
382 389 _raiseoserror(name)
383 390 try:
384 391 fi = _BY_HANDLE_FILE_INFORMATION()
385 392 if not _kernel32.GetFileInformationByHandle(fh, ctypes.byref(fi)):
386 393 _raiseoserror(name)
387 394 return fi
388 395 finally:
389 396 _kernel32.CloseHandle(fh)
390 397
391 398
392 def checkcertificatechain(cert, build=True):
399 def checkcertificatechain(cert: bytes, build: bool = True) -> bool:
393 400 """Tests the given certificate to see if there is a complete chain to a
394 401 trusted root certificate. As a side effect, missing certificates are
395 402 downloaded and installed unless ``build=False``. True is returned if a
396 403 chain to a trusted root exists (even if built on the fly), otherwise
397 404 False. NB: A chain to a trusted root does NOT imply that the certificate
398 405 is valid.
399 406 """
400 407
401 408 chainctxptr = ctypes.POINTER(CERT_CHAIN_CONTEXT)
402 409
403 410 pchainctx = chainctxptr()
404 411 chainpara = CERT_CHAIN_PARA(
405 412 cbSize=ctypes.sizeof(CERT_CHAIN_PARA), RequestedUsage=CERT_USAGE_MATCH()
406 413 )
407 414
408 415 certctx = _crypt32.CertCreateCertificateContext(
409 416 X509_ASN_ENCODING, cert, len(cert)
410 417 )
411 418 if certctx is None:
412 419 _raiseoserror(b'CertCreateCertificateContext')
413 420
414 421 flags = 0
415 422
416 423 if not build:
417 424 flags |= 0x100 # CERT_CHAIN_DISABLE_AUTH_ROOT_AUTO_UPDATE
418 425
419 426 try:
420 427 # Building the certificate chain will update root certs as necessary.
421 428 if not _crypt32.CertGetCertificateChain(
422 429 None, # hChainEngine
423 430 certctx, # pCertContext
424 431 None, # pTime
425 432 None, # hAdditionalStore
426 433 ctypes.byref(chainpara),
427 434 flags,
428 435 None, # pvReserved
429 436 ctypes.byref(pchainctx),
430 437 ):
431 438 _raiseoserror(b'CertGetCertificateChain')
432 439
433 440 chainctx = pchainctx.contents
434 441
435 442 return chainctx.dwErrorStatus & CERT_TRUST_IS_PARTIAL_CHAIN == 0
436 443 finally:
437 444 if pchainctx:
438 445 _crypt32.CertFreeCertificateChain(pchainctx)
439 446 _crypt32.CertFreeCertificateContext(certctx)
440 447
441 448
442 def oslink(src, dst):
449 def oslink(src: bytes, dst: bytes) -> None:
443 450 try:
444 451 if not _kernel32.CreateHardLinkA(dst, src, None):
445 452 _raiseoserror(src)
446 453 except AttributeError: # Wine doesn't support this function
447 454 _raiseoserror(src)
448 455
449 456
450 def nlinks(name):
457 def nlinks(name: bytes) -> int:
451 458 '''return number of hardlinks for the given file'''
452 459 return _getfileinfo(name).nNumberOfLinks
453 460
454 461
455 def samefile(path1, path2):
462 def samefile(path1: bytes, path2: bytes) -> bool:
456 463 '''Returns whether path1 and path2 refer to the same file or directory.'''
457 464 res1 = _getfileinfo(path1)
458 465 res2 = _getfileinfo(path2)
459 466 return (
460 467 res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber
461 468 and res1.nFileIndexHigh == res2.nFileIndexHigh
462 469 and res1.nFileIndexLow == res2.nFileIndexLow
463 470 )
464 471
465 472
466 def samedevice(path1, path2):
473 def samedevice(path1: bytes, path2: bytes) -> bool:
467 474 '''Returns whether path1 and path2 are on the same device.'''
468 475 res1 = _getfileinfo(path1)
469 476 res2 = _getfileinfo(path2)
470 477 return res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber
471 478
472 479
473 def peekpipe(pipe):
480 def peekpipe(pipe) -> int:
474 481 handle = msvcrt.get_osfhandle(pipe.fileno()) # pytype: disable=module-attr
475 482 avail = _DWORD()
476 483
477 484 if not _kernel32.PeekNamedPipe(
478 485 handle, None, 0, None, ctypes.byref(avail), None
479 486 ):
480 487 err = _kernel32.GetLastError()
481 488 if err == _ERROR_BROKEN_PIPE:
482 489 return 0
483 490 raise ctypes.WinError(err) # pytype: disable=module-attr
484 491
485 492 return avail.value
486 493
487 494
488 def lasterrorwaspipeerror(err):
495 def lasterrorwaspipeerror(err) -> bool:
489 496 if err.errno != errno.EINVAL:
490 497 return False
491 498 err = _kernel32.GetLastError()
492 499 return err == _ERROR_BROKEN_PIPE or err == _ERROR_NO_DATA
493 500
494 501
495 def testpid(pid):
502 def testpid(pid: int) -> bool:
496 503 """return True if pid is still running or unable to
497 504 determine, False otherwise"""
498 505 h = _kernel32.OpenProcess(_PROCESS_QUERY_INFORMATION, False, pid)
499 506 if h:
500 507 try:
501 508 status = _DWORD()
502 509 if _kernel32.GetExitCodeProcess(h, ctypes.byref(status)):
503 510 return status.value == _STILL_ACTIVE
504 511 finally:
505 512 _kernel32.CloseHandle(h)
506 513 return _kernel32.GetLastError() != _ERROR_INVALID_PARAMETER
507 514
508 515
509 def executablepath():
516 def executablepath() -> bytes:
510 517 '''return full path of hg.exe'''
511 518 size = 600
512 519 buf = ctypes.create_string_buffer(size + 1)
513 520 len = _kernel32.GetModuleFileNameA(None, ctypes.byref(buf), size)
514 521 # pytype: disable=module-attr
515 522 if len == 0:
516 523 raise ctypes.WinError() # Note: WinError is a function
517 524 elif len == size:
518 525 raise ctypes.WinError(_ERROR_INSUFFICIENT_BUFFER)
519 526 # pytype: enable=module-attr
520 527 return buf.value
521 528
522 529
523 def getvolumename(path):
530 def getvolumename(path: bytes) -> Optional[bytes]:
524 531 """Get the mount point of the filesystem from a directory or file
525 532 (best-effort)
526 533
527 534 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
528 535 """
529 536 # realpath() calls GetFullPathName()
530 537 realpath = os.path.realpath(path)
531 538
532 539 # allocate at least MAX_PATH long since GetVolumePathName('c:\\', buf, 4)
533 540 # somehow fails on Windows XP
534 541 size = max(len(realpath), _MAX_PATH) + 1
535 542 buf = ctypes.create_string_buffer(size)
536 543
537 544 if not _kernel32.GetVolumePathNameA(realpath, ctypes.byref(buf), size):
538 545 # Note: WinError is a function
539 546 raise ctypes.WinError() # pytype: disable=module-attr
540 547
541 548 return buf.value
542 549
543 550
544 def getfstype(path):
551 def getfstype(path: bytes) -> Optional[bytes]:
545 552 """Get the filesystem type name from a directory or file (best-effort)
546 553
547 554 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
548 555 """
549 556 volume = getvolumename(path)
550 557
551 558 t = _kernel32.GetDriveTypeA(volume)
552 559
553 560 if t == _DRIVE_REMOTE:
554 561 return b'cifs'
555 562 elif t not in (
556 563 _DRIVE_REMOVABLE,
557 564 _DRIVE_FIXED,
558 565 _DRIVE_CDROM,
559 566 _DRIVE_RAMDISK,
560 567 ):
561 568 return None
562 569
563 570 size = _MAX_PATH + 1
564 571 name = ctypes.create_string_buffer(size)
565 572
566 573 if not _kernel32.GetVolumeInformationA(
567 574 volume, None, 0, None, None, None, ctypes.byref(name), size
568 575 ):
569 576 # Note: WinError is a function
570 577 raise ctypes.WinError() # pytype: disable=module-attr
571 578
572 579 return name.value
573 580
574 581
575 def getuser():
582 def getuser() -> bytes:
576 583 '''return name of current user'''
577 584 size = _DWORD(300)
578 585 buf = ctypes.create_string_buffer(size.value + 1)
579 586 if not _advapi32.GetUserNameA(ctypes.byref(buf), ctypes.byref(size)):
580 587 raise ctypes.WinError() # pytype: disable=module-attr
581 588 return buf.value
582 589
583 590
584 _signalhandler = []
591 _signalhandler: List[_SIGNAL_HANDLER] = []
585 592
586 593
587 def setsignalhandler():
594 def setsignalhandler() -> None:
588 595 """Register a termination handler for console events including
589 596 CTRL+C. python signal handlers do not work well with socket
590 597 operations.
591 598 """
592 599
593 600 def handler(event):
594 601 _kernel32.ExitProcess(1)
595 602
596 603 if _signalhandler:
597 604 return # already registered
598 605 h = _SIGNAL_HANDLER(handler)
599 606 _signalhandler.append(h) # needed to prevent garbage collection
600 607 if not _kernel32.SetConsoleCtrlHandler(h, True):
601 608 raise ctypes.WinError() # pytype: disable=module-attr
602 609
603 610
604 def hidewindow():
611 def hidewindow() -> None:
605 612 def callback(hwnd, pid):
606 613 wpid = _DWORD()
607 614 _user32.GetWindowThreadProcessId(hwnd, ctypes.byref(wpid))
608 615 if pid == wpid.value:
609 616 _user32.ShowWindow(hwnd, _SW_HIDE)
610 617 return False # stop enumerating windows
611 618 return True
612 619
613 620 pid = _kernel32.GetCurrentProcessId()
614 621 _user32.EnumWindows(_WNDENUMPROC(callback), pid)
615 622
616 623
617 def termsize():
624 def termsize() -> Tuple[int, int]:
618 625 # cmd.exe does not handle CR like a unix console, the CR is
619 626 # counted in the line length. On 80 columns consoles, if 80
620 627 # characters are written, the following CR won't apply on the
621 628 # current line but on the new one. Keep room for it.
622 629 width = 80 - 1
623 630 height = 25
624 631 # Query stderr to avoid problems with redirections
625 632 screenbuf = _kernel32.GetStdHandle(
626 633 _STD_ERROR_HANDLE
627 634 ) # don't close the handle returned
628 635 if screenbuf is None or screenbuf == _INVALID_HANDLE_VALUE:
629 636 return width, height
630 637 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
631 638 if not _kernel32.GetConsoleScreenBufferInfo(screenbuf, ctypes.byref(csbi)):
632 639 return width, height
633 640 width = csbi.srWindow.Right - csbi.srWindow.Left # don't '+ 1'
634 641 height = csbi.srWindow.Bottom - csbi.srWindow.Top + 1
635 642 return width, height
636 643
637 644
638 def enablevtmode():
645 def enablevtmode() -> bool:
639 646 """Enable virtual terminal mode for the associated console. Return True if
640 647 enabled, else False."""
641 648
642 649 ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4
643 650
644 651 handle = _kernel32.GetStdHandle(
645 652 _STD_OUTPUT_HANDLE
646 653 ) # don't close the handle
647 654 if handle == _INVALID_HANDLE_VALUE:
648 655 return False
649 656
650 657 mode = _DWORD(0)
651 658
652 659 if not _kernel32.GetConsoleMode(handle, ctypes.byref(mode)):
653 660 return False
654 661
655 662 if (mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0:
656 663 mode.value |= ENABLE_VIRTUAL_TERMINAL_PROCESSING
657 664
658 665 if not _kernel32.SetConsoleMode(handle, mode):
659 666 return False
660 667
661 668 return True
662 669
663 670
664 def spawndetached(args):
671 def spawndetached(args: List[bytes]) -> int:
665 672 # No standard library function really spawns a fully detached
666 673 # process under win32 because they allocate pipes or other objects
667 674 # to handle standard streams communications. Passing these objects
668 675 # to the child process requires handle inheritance to be enabled
669 676 # which makes really detached processes impossible.
670 677 si = _STARTUPINFO()
671 678 si.cb = ctypes.sizeof(_STARTUPINFO)
672 679
673 680 pi = _PROCESS_INFORMATION()
674 681
675 682 env = b''
676 683 for k in encoding.environ:
677 684 env += b"%s=%s\0" % (k, encoding.environ[k])
678 685 if not env:
679 686 env = b'\0'
680 687 env += b'\0'
681 688
682 689 args = subprocess.list2cmdline(pycompat.rapply(encoding.strfromlocal, args))
683 690
684 691 # TODO: CreateProcessW on py3?
685 692 res = _kernel32.CreateProcessA(
686 693 None,
687 694 encoding.strtolocal(args),
688 695 None,
689 696 None,
690 697 False,
691 698 _CREATE_NO_WINDOW,
692 699 env,
693 700 encoding.getcwd(),
694 701 ctypes.byref(si),
695 702 ctypes.byref(pi),
696 703 )
697 704 if not res:
698 705 raise ctypes.WinError() # pytype: disable=module-attr
699 706
700 707 _kernel32.CloseHandle(pi.hProcess)
701 708 _kernel32.CloseHandle(pi.hThread)
702 709
703 710 return pi.dwProcessId
704 711
705 712
706 def unlink(f):
713 def unlink(f: bytes) -> None:
707 714 '''try to implement POSIX' unlink semantics on Windows'''
708 715
709 716 if os.path.isdir(f):
710 717 # use EPERM because it is POSIX prescribed value, even though
711 718 # unlink(2) on directories returns EISDIR on Linux
712 719 raise IOError(
713 720 errno.EPERM,
714 721 r"Unlinking directory not permitted: '%s'"
715 722 % encoding.strfromlocal(f),
716 723 )
717 724
718 725 # POSIX allows to unlink and rename open files. Windows has serious
719 726 # problems with doing that:
720 727 # - Calling os.unlink (or os.rename) on a file f fails if f or any
721 728 # hardlinked copy of f has been opened with Python's open(). There is no
722 729 # way such a file can be deleted or renamed on Windows (other than
723 730 # scheduling the delete or rename for the next reboot).
724 731 # - Calling os.unlink on a file that has been opened with Mercurial's
725 732 # posixfile (or comparable methods) will delay the actual deletion of
726 733 # the file for as long as the file is held open. The filename is blocked
727 734 # during that time and cannot be used for recreating a new file under
728 735 # that same name ("zombie file"). Directories containing such zombie files
729 736 # cannot be removed or moved.
730 737 # A file that has been opened with posixfile can be renamed, so we rename
731 738 # f to a random temporary name before calling os.unlink on it. This allows
732 739 # callers to recreate f immediately while having other readers do their
733 740 # implicit zombie filename blocking on a temporary name.
734 741
735 742 for tries in range(10):
736 743 temp = b'%s-%08x' % (f, random.randint(0, 0xFFFFFFFF))
737 744 try:
738 745 os.rename(f, temp)
739 746 break
740 747 except FileExistsError:
741 748 pass
742 749 else:
743 750 raise IOError(errno.EEXIST, "No usable temporary filename found")
744 751
745 752 try:
746 753 os.unlink(temp)
747 754 except OSError:
748 755 # The unlink might have failed because the READONLY attribute may heave
749 756 # been set on the original file. Rename works fine with READONLY set,
750 757 # but not os.unlink. Reset all attributes and try again.
751 758 _kernel32.SetFileAttributesA(temp, _FILE_ATTRIBUTE_NORMAL)
752 759 try:
753 760 os.unlink(temp)
754 761 except OSError:
755 762 # The unlink might have failed due to some very rude AV-Scanners.
756 763 # Leaking a tempfile is the lesser evil than aborting here and
757 764 # leaving some potentially serious inconsistencies.
758 765 pass
759 766
760 767
761 def makedir(path, notindexed):
768 def makedir(path: bytes, notindexed: bool) -> None:
762 769 os.mkdir(path)
763 770 if notindexed:
764 771 _kernel32.SetFileAttributesA(path, _FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)
General Comments 0
You need to be logged in to leave comments. Login now