##// END OF EJS Templates
cleanup: remove compatibility code for Python < 2.7.1...
Manuel Jacob -
r45402:5258bffd default
parent child Browse files
Show More
@@ -1,683 +1,680
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 '''stdout on windows misbehaves if sent through a pipe'''
191 191
192 192 def __init__(self, fp):
193 193 self.fp = fp
194 194
195 195 def __getattr__(self, key):
196 196 return getattr(self.fp, key)
197 197
198 198 def close(self):
199 199 try:
200 200 self.fp.close()
201 201 except IOError:
202 202 pass
203 203
204 204 def write(self, s):
205 205 try:
206 206 # This is workaround for "Not enough space" error on
207 207 # writing large size of data to console.
208 208 limit = 16000
209 209 l = len(s)
210 210 start = 0
211 211 self.softspace = 0
212 212 while start < l:
213 213 end = start + limit
214 214 self.fp.write(s[start:end])
215 215 start = end
216 216 except IOError as inst:
217 217 if inst.errno != 0 and not win32.lasterrorwaspipeerror(inst):
218 218 raise
219 219 self.close()
220 220 raise IOError(errno.EPIPE, 'Broken pipe')
221 221
222 222 def flush(self):
223 223 try:
224 224 return self.fp.flush()
225 225 except IOError as inst:
226 226 if not win32.lasterrorwaspipeerror(inst):
227 227 raise
228 228 raise IOError(errno.EPIPE, 'Broken pipe')
229 229
230 230
231 231 def openhardlinks():
232 232 return True
233 233
234 234
235 235 def parsepatchoutput(output_line):
236 236 """parses the output produced by patch and returns the filename"""
237 237 pf = output_line[14:]
238 238 if pf[0] == b'`':
239 239 pf = pf[1:-1] # Remove the quotes
240 240 return pf
241 241
242 242
243 243 def sshargs(sshcmd, host, user, port):
244 244 '''Build argument list for ssh or Plink'''
245 245 pflag = b'plink' in sshcmd.lower() and b'-P' or b'-p'
246 246 args = user and (b"%s@%s" % (user, host)) or host
247 247 if args.startswith(b'-') or args.startswith(b'/'):
248 248 raise error.Abort(
249 249 _(b'illegal ssh hostname or username starting with - or /: %s')
250 250 % args
251 251 )
252 252 args = shellquote(args)
253 253 if port:
254 254 args = b'%s %s %s' % (pflag, shellquote(port), args)
255 255 return args
256 256
257 257
258 258 def setflags(f, l, x):
259 259 pass
260 260
261 261
262 262 def copymode(src, dst, mode=None, enforcewritable=False):
263 263 pass
264 264
265 265
266 266 def checkexec(path):
267 267 return False
268 268
269 269
270 270 def checklink(path):
271 271 return False
272 272
273 273
274 274 def setbinary(fd):
275 275 # When run without console, pipes may expose invalid
276 276 # fileno(), usually set to -1.
277 277 fno = getattr(fd, 'fileno', None)
278 278 if fno is not None and fno() >= 0:
279 279 msvcrt.setmode(fno(), os.O_BINARY) # pytype: disable=module-attr
280 280
281 281
282 282 def pconvert(path):
283 283 return path.replace(pycompat.ossep, b'/')
284 284
285 285
286 286 def localpath(path):
287 287 return path.replace(b'/', b'\\')
288 288
289 289
290 290 def normpath(path):
291 291 return pconvert(os.path.normpath(path))
292 292
293 293
294 294 def normcase(path):
295 295 return encoding.upper(path) # NTFS compares via upper()
296 296
297 297
298 298 # see posix.py for definitions
299 299 normcasespec = encoding.normcasespecs.upper
300 300 normcasefallback = encoding.upperfallback
301 301
302 302
303 303 def samestat(s1, s2):
304 304 return False
305 305
306 306
307 307 def shelltocmdexe(path, env):
308 308 r"""Convert shell variables in the form $var and ${var} inside ``path``
309 309 to %var% form. Existing Windows style variables are left unchanged.
310 310
311 311 The variables are limited to the given environment. Unknown variables are
312 312 left unchanged.
313 313
314 314 >>> e = {b'var1': b'v1', b'var2': b'v2', b'var3': b'v3'}
315 315 >>> # Only valid values are expanded
316 316 >>> shelltocmdexe(b'cmd $var1 ${var2} %var3% $missing ${missing} %missing%',
317 317 ... e)
318 318 'cmd %var1% %var2% %var3% $missing ${missing} %missing%'
319 319 >>> # Single quote prevents expansion, as does \$ escaping
320 320 >>> shelltocmdexe(b"cmd '$var1 ${var2} %var3%' \$var1 \${var2} \\", e)
321 321 'cmd "$var1 ${var2} %var3%" $var1 ${var2} \\'
322 322 >>> # $$ is not special. %% is not special either, but can be the end and
323 323 >>> # start of consecutive variables
324 324 >>> shelltocmdexe(b"cmd $$ %% %var1%%var2%", e)
325 325 'cmd $$ %% %var1%%var2%'
326 326 >>> # No double substitution
327 327 >>> shelltocmdexe(b"$var1 %var1%", {b'var1': b'%var2%', b'var2': b'boom'})
328 328 '%var1% %var1%'
329 329 >>> # Tilde expansion
330 330 >>> shelltocmdexe(b"~/dir ~\dir2 ~tmpfile \~/", {})
331 331 '%USERPROFILE%/dir %USERPROFILE%\\dir2 ~tmpfile ~/'
332 332 """
333 333 if not any(c in path for c in b"$'~"):
334 334 return path
335 335
336 336 varchars = pycompat.sysbytes(string.ascii_letters + string.digits) + b'_-'
337 337
338 338 res = b''
339 339 index = 0
340 340 pathlen = len(path)
341 341 while index < pathlen:
342 342 c = path[index : index + 1]
343 343 if c == b'\'': # no expansion within single quotes
344 344 path = path[index + 1 :]
345 345 pathlen = len(path)
346 346 try:
347 347 index = path.index(b'\'')
348 348 res += b'"' + path[:index] + b'"'
349 349 except ValueError:
350 350 res += c + path
351 351 index = pathlen - 1
352 352 elif c == b'%': # variable
353 353 path = path[index + 1 :]
354 354 pathlen = len(path)
355 355 try:
356 356 index = path.index(b'%')
357 357 except ValueError:
358 358 res += b'%' + path
359 359 index = pathlen - 1
360 360 else:
361 361 var = path[:index]
362 362 res += b'%' + var + b'%'
363 363 elif c == b'$': # variable
364 364 if path[index + 1 : index + 2] == b'{':
365 365 path = path[index + 2 :]
366 366 pathlen = len(path)
367 367 try:
368 368 index = path.index(b'}')
369 369 var = path[:index]
370 370
371 371 # See below for why empty variables are handled specially
372 372 if env.get(var, b'') != b'':
373 373 res += b'%' + var + b'%'
374 374 else:
375 375 res += b'${' + var + b'}'
376 376 except ValueError:
377 377 res += b'${' + path
378 378 index = pathlen - 1
379 379 else:
380 380 var = b''
381 381 index += 1
382 382 c = path[index : index + 1]
383 383 while c != b'' and c in varchars:
384 384 var += c
385 385 index += 1
386 386 c = path[index : index + 1]
387 387 # Some variables (like HG_OLDNODE) may be defined, but have an
388 388 # empty value. Those need to be skipped because when spawning
389 389 # cmd.exe to run the hook, it doesn't replace %VAR% for an empty
390 390 # VAR, and that really confuses things like revset expressions.
391 391 # OTOH, if it's left in Unix format and the hook runs sh.exe, it
392 392 # will substitute to an empty string, and everything is happy.
393 393 if env.get(var, b'') != b'':
394 394 res += b'%' + var + b'%'
395 395 else:
396 396 res += b'$' + var
397 397
398 398 if c != b'':
399 399 index -= 1
400 400 elif (
401 401 c == b'~'
402 402 and index + 1 < pathlen
403 403 and path[index + 1 : index + 2] in (b'\\', b'/')
404 404 ):
405 405 res += b"%USERPROFILE%"
406 406 elif (
407 407 c == b'\\'
408 408 and index + 1 < pathlen
409 409 and path[index + 1 : index + 2] in (b'$', b'~')
410 410 ):
411 411 # Skip '\', but only if it is escaping $ or ~
412 412 res += path[index + 1 : index + 2]
413 413 index += 1
414 414 else:
415 415 res += c
416 416
417 417 index += 1
418 418 return res
419 419
420 420
421 421 # A sequence of backslashes is special iff it precedes a double quote:
422 422 # - if there's an even number of backslashes, the double quote is not
423 423 # quoted (i.e. it ends the quoted region)
424 424 # - if there's an odd number of backslashes, the double quote is quoted
425 425 # - in both cases, every pair of backslashes is unquoted into a single
426 426 # backslash
427 427 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
428 428 # So, to quote a string, we must surround it in double quotes, double
429 429 # the number of backslashes that precede double quotes and add another
430 430 # backslash before every double quote (being careful with the double
431 431 # quote we've appended to the end)
432 432 _quotere = None
433 433 _needsshellquote = None
434 434
435 435
436 436 def shellquote(s):
437 437 r"""
438 438 >>> shellquote(br'C:\Users\xyz')
439 439 '"C:\\Users\\xyz"'
440 440 >>> shellquote(br'C:\Users\xyz/mixed')
441 441 '"C:\\Users\\xyz/mixed"'
442 442 >>> # Would be safe not to quote too, since it is all double backslashes
443 443 >>> shellquote(br'C:\\Users\\xyz')
444 444 '"C:\\\\Users\\\\xyz"'
445 445 >>> # But this must be quoted
446 446 >>> shellquote(br'C:\\Users\\xyz/abc')
447 447 '"C:\\\\Users\\\\xyz/abc"'
448 448 """
449 449 global _quotere
450 450 if _quotere is None:
451 451 _quotere = re.compile(br'(\\*)("|\\$)')
452 452 global _needsshellquote
453 453 if _needsshellquote is None:
454 454 # ":" is also treated as "safe character", because it is used as a part
455 455 # of path name on Windows. "\" is also part of a path name, but isn't
456 456 # safe because shlex.split() (kind of) treats it as an escape char and
457 457 # drops it. It will leave the next character, even if it is another
458 458 # "\".
459 459 _needsshellquote = re.compile(br'[^a-zA-Z0-9._:/-]').search
460 460 if s and not _needsshellquote(s) and not _quotere.search(s):
461 461 # "s" shouldn't have to be quoted
462 462 return s
463 463 return b'"%s"' % _quotere.sub(br'\1\1\\\2', s)
464 464
465 465
466 466 def _unquote(s):
467 467 if s.startswith(b'"') and s.endswith(b'"'):
468 468 return s[1:-1]
469 469 return s
470 470
471 471
472 472 def shellsplit(s):
473 473 """Parse a command string in cmd.exe way (best-effort)"""
474 474 return pycompat.maplist(_unquote, pycompat.shlexsplit(s, posix=False))
475 475
476 476
477 477 def quotecommand(cmd):
478 478 """Build a command string suitable for os.popen* calls."""
479 if sys.version_info < (2, 7, 1):
480 # Python versions since 2.7.1 do this extra quoting themselves
481 return b'"' + cmd + b'"'
482 479 return cmd
483 480
484 481
485 482 # if you change this stub into a real check, please try to implement the
486 483 # username and groupname functions above, too.
487 484 def isowner(st):
488 485 return True
489 486
490 487
491 488 def findexe(command):
492 489 '''Find executable for command searching like cmd.exe does.
493 490 If command is a basename then PATH is searched for command.
494 491 PATH isn't searched if command is an absolute or relative path.
495 492 An extension from PATHEXT is found and added if not present.
496 493 If command isn't found None is returned.'''
497 494 pathext = encoding.environ.get(b'PATHEXT', b'.COM;.EXE;.BAT;.CMD')
498 495 pathexts = [ext for ext in pathext.lower().split(pycompat.ospathsep)]
499 496 if os.path.splitext(command)[1].lower() in pathexts:
500 497 pathexts = [b'']
501 498
502 499 def findexisting(pathcommand):
503 500 """Will append extension (if needed) and return existing file"""
504 501 for ext in pathexts:
505 502 executable = pathcommand + ext
506 503 if os.path.exists(executable):
507 504 return executable
508 505 return None
509 506
510 507 if pycompat.ossep in command:
511 508 return findexisting(command)
512 509
513 510 for path in encoding.environ.get(b'PATH', b'').split(pycompat.ospathsep):
514 511 executable = findexisting(os.path.join(path, command))
515 512 if executable is not None:
516 513 return executable
517 514 return findexisting(os.path.expanduser(os.path.expandvars(command)))
518 515
519 516
520 517 _wantedkinds = {stat.S_IFREG, stat.S_IFLNK}
521 518
522 519
523 520 def statfiles(files):
524 521 '''Stat each file in files. Yield each stat, or None if a file
525 522 does not exist or has a type we don't care about.
526 523
527 524 Cluster and cache stat per directory to minimize number of OS stat calls.'''
528 525 dircache = {} # dirname -> filename -> status | None if file does not exist
529 526 getkind = stat.S_IFMT
530 527 for nf in files:
531 528 nf = normcase(nf)
532 529 dir, base = os.path.split(nf)
533 530 if not dir:
534 531 dir = b'.'
535 532 cache = dircache.get(dir, None)
536 533 if cache is None:
537 534 try:
538 535 dmap = {
539 536 normcase(n): s
540 537 for n, k, s in listdir(dir, True)
541 538 if getkind(s.st_mode) in _wantedkinds
542 539 }
543 540 except OSError as err:
544 541 # Python >= 2.5 returns ENOENT and adds winerror field
545 542 # EINVAL is raised if dir is not a directory.
546 543 if err.errno not in (errno.ENOENT, errno.EINVAL, errno.ENOTDIR):
547 544 raise
548 545 dmap = {}
549 546 cache = dircache.setdefault(dir, dmap)
550 547 yield cache.get(base, None)
551 548
552 549
553 550 def username(uid=None):
554 551 """Return the name of the user with the given uid.
555 552
556 553 If uid is None, return the name of the current user."""
557 554 if not uid:
558 555 return pycompat.fsencode(getpass.getuser())
559 556 return None
560 557
561 558
562 559 def groupname(gid=None):
563 560 """Return the name of the group with the given gid.
564 561
565 562 If gid is None, return the name of the current group."""
566 563 return None
567 564
568 565
569 566 def readlink(pathname):
570 567 return pycompat.fsencode(os.readlink(pycompat.fsdecode(pathname)))
571 568
572 569
573 570 def removedirs(name):
574 571 """special version of os.removedirs that does not remove symlinked
575 572 directories or junction points if they actually contain files"""
576 573 if listdir(name):
577 574 return
578 575 os.rmdir(name)
579 576 head, tail = os.path.split(name)
580 577 if not tail:
581 578 head, tail = os.path.split(head)
582 579 while head and tail:
583 580 try:
584 581 if listdir(head):
585 582 return
586 583 os.rmdir(head)
587 584 except (ValueError, OSError):
588 585 break
589 586 head, tail = os.path.split(head)
590 587
591 588
592 589 def rename(src, dst):
593 590 '''atomically rename file src to dst, replacing dst if it exists'''
594 591 try:
595 592 os.rename(src, dst)
596 593 except OSError as e:
597 594 if e.errno != errno.EEXIST:
598 595 raise
599 596 unlink(dst)
600 597 os.rename(src, dst)
601 598
602 599
603 600 def gethgcmd():
604 601 return [encoding.strtolocal(arg) for arg in [sys.executable] + sys.argv[:1]]
605 602
606 603
607 604 def groupmembers(name):
608 605 # Don't support groups on Windows for now
609 606 raise KeyError
610 607
611 608
612 609 def isexec(f):
613 610 return False
614 611
615 612
616 613 class cachestat(object):
617 614 def __init__(self, path):
618 615 pass
619 616
620 617 def cacheable(self):
621 618 return False
622 619
623 620
624 621 def lookupreg(key, valname=None, scope=None):
625 622 ''' Look up a key/value name in the Windows registry.
626 623
627 624 valname: value name. If unspecified, the default value for the key
628 625 is used.
629 626 scope: optionally specify scope for registry lookup, this can be
630 627 a sequence of scopes to look up in order. Default (CURRENT_USER,
631 628 LOCAL_MACHINE).
632 629 '''
633 630 if scope is None:
634 631 scope = (winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE)
635 632 elif not isinstance(scope, (list, tuple)):
636 633 scope = (scope,)
637 634 for s in scope:
638 635 try:
639 636 with winreg.OpenKey(s, encoding.strfromlocal(key)) as hkey:
640 637 name = valname and encoding.strfromlocal(valname) or valname
641 638 val = winreg.QueryValueEx(hkey, name)[0]
642 639 # never let a Unicode string escape into the wild
643 640 return encoding.unitolocal(val)
644 641 except EnvironmentError:
645 642 pass
646 643
647 644
648 645 expandglobs = True
649 646
650 647
651 648 def statislink(st):
652 649 '''check whether a stat result is a symlink'''
653 650 return False
654 651
655 652
656 653 def statisexec(st):
657 654 '''check whether a stat result is an executable file'''
658 655 return False
659 656
660 657
661 658 def poll(fds):
662 659 # see posix.py for description
663 660 raise NotImplementedError()
664 661
665 662
666 663 def readpipe(pipe):
667 664 """Read all available data from a pipe."""
668 665 chunks = []
669 666 while True:
670 667 size = win32.peekpipe(pipe)
671 668 if not size:
672 669 break
673 670
674 671 s = pipe.read(size)
675 672 if not s:
676 673 break
677 674 chunks.append(s)
678 675
679 676 return b''.join(chunks)
680 677
681 678
682 679 def bindunixsocket(sock, path):
683 680 raise NotImplementedError('unsupported platform')
General Comments 0
You need to be logged in to leave comments. Login now