##// END OF EJS Templates
windows: drop detection of Windows 95/98/ME...
Matt Harbison -
r44374:70abcb61 default
parent child Browse files
Show More
@@ -1,693 +1,685 b''
1 1 # windows.py - Windows utility function implementations for Mercurial
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import getpass
12 12 import msvcrt
13 13 import os
14 14 import re
15 15 import stat
16 16 import string
17 17 import sys
18 18
19 19 from .i18n import _
20 20 from .pycompat import getattr
21 21 from . import (
22 22 encoding,
23 23 error,
24 24 policy,
25 25 pycompat,
26 26 win32,
27 27 )
28 28
29 29 try:
30 30 import _winreg as winreg # pytype: disable=import-error
31 31
32 32 winreg.CloseKey
33 33 except ImportError:
34 34 # py2 only
35 35 import winreg # pytype: disable=import-error
36 36
37 37 osutil = policy.importmod('osutil')
38 38
39 39 getfsmountpoint = win32.getvolumename
40 40 getfstype = win32.getfstype
41 41 getuser = win32.getuser
42 42 hidewindow = win32.hidewindow
43 43 makedir = win32.makedir
44 44 nlinks = win32.nlinks
45 45 oslink = win32.oslink
46 46 samedevice = win32.samedevice
47 47 samefile = win32.samefile
48 48 setsignalhandler = win32.setsignalhandler
49 49 spawndetached = win32.spawndetached
50 50 split = os.path.split
51 51 testpid = win32.testpid
52 52 unlink = win32.unlink
53 53
54 54 umask = 0o022
55 55
56 56
57 57 class mixedfilemodewrapper(object):
58 58 """Wraps a file handle when it is opened in read/write mode.
59 59
60 60 fopen() and fdopen() on Windows have a specific-to-Windows requirement
61 61 that files opened with mode r+, w+, or a+ make a call to a file positioning
62 62 function when switching between reads and writes. Without this extra call,
63 63 Python will raise a not very intuitive "IOError: [Errno 0] Error."
64 64
65 65 This class wraps posixfile instances when the file is opened in read/write
66 66 mode and automatically adds checks or inserts appropriate file positioning
67 67 calls when necessary.
68 68 """
69 69
70 70 OPNONE = 0
71 71 OPREAD = 1
72 72 OPWRITE = 2
73 73
74 74 def __init__(self, fp):
75 75 object.__setattr__(self, '_fp', fp)
76 76 object.__setattr__(self, '_lastop', 0)
77 77
78 78 def __enter__(self):
79 79 self._fp.__enter__()
80 80 return self
81 81
82 82 def __exit__(self, exc_type, exc_val, exc_tb):
83 83 self._fp.__exit__(exc_type, exc_val, exc_tb)
84 84
85 85 def __getattr__(self, name):
86 86 return getattr(self._fp, name)
87 87
88 88 def __setattr__(self, name, value):
89 89 return self._fp.__setattr__(name, value)
90 90
91 91 def _noopseek(self):
92 92 self._fp.seek(0, os.SEEK_CUR)
93 93
94 94 def seek(self, *args, **kwargs):
95 95 object.__setattr__(self, '_lastop', self.OPNONE)
96 96 return self._fp.seek(*args, **kwargs)
97 97
98 98 def write(self, d):
99 99 if self._lastop == self.OPREAD:
100 100 self._noopseek()
101 101
102 102 object.__setattr__(self, '_lastop', self.OPWRITE)
103 103 return self._fp.write(d)
104 104
105 105 def writelines(self, *args, **kwargs):
106 106 if self._lastop == self.OPREAD:
107 107 self._noopeseek()
108 108
109 109 object.__setattr__(self, '_lastop', self.OPWRITE)
110 110 return self._fp.writelines(*args, **kwargs)
111 111
112 112 def read(self, *args, **kwargs):
113 113 if self._lastop == self.OPWRITE:
114 114 self._noopseek()
115 115
116 116 object.__setattr__(self, '_lastop', self.OPREAD)
117 117 return self._fp.read(*args, **kwargs)
118 118
119 119 def readline(self, *args, **kwargs):
120 120 if self._lastop == self.OPWRITE:
121 121 self._noopseek()
122 122
123 123 object.__setattr__(self, '_lastop', self.OPREAD)
124 124 return self._fp.readline(*args, **kwargs)
125 125
126 126 def readlines(self, *args, **kwargs):
127 127 if self._lastop == self.OPWRITE:
128 128 self._noopseek()
129 129
130 130 object.__setattr__(self, '_lastop', self.OPREAD)
131 131 return self._fp.readlines(*args, **kwargs)
132 132
133 133
134 134 class fdproxy(object):
135 135 """Wraps osutil.posixfile() to override the name attribute to reflect the
136 136 underlying file name.
137 137 """
138 138
139 139 def __init__(self, name, fp):
140 140 self.name = name
141 141 self._fp = fp
142 142
143 143 def __enter__(self):
144 144 self._fp.__enter__()
145 145 # Return this wrapper for the context manager so that the name is
146 146 # still available.
147 147 return self
148 148
149 149 def __exit__(self, exc_type, exc_value, traceback):
150 150 self._fp.__exit__(exc_type, exc_value, traceback)
151 151
152 152 def __iter__(self):
153 153 return iter(self._fp)
154 154
155 155 def __getattr__(self, name):
156 156 return getattr(self._fp, name)
157 157
158 158
159 159 def posixfile(name, mode=b'r', buffering=-1):
160 160 '''Open a file with even more POSIX-like semantics'''
161 161 try:
162 162 fp = osutil.posixfile(name, mode, buffering) # may raise WindowsError
163 163
164 164 # PyFile_FromFd() ignores the name, and seems to report fp.name as the
165 165 # underlying file descriptor.
166 166 if pycompat.ispy3:
167 167 fp = fdproxy(name, fp)
168 168
169 169 # The position when opening in append mode is implementation defined, so
170 170 # make it consistent with other platforms, which position at EOF.
171 171 if b'a' in mode:
172 172 fp.seek(0, os.SEEK_END)
173 173
174 174 if b'+' in mode:
175 175 return mixedfilemodewrapper(fp)
176 176
177 177 return fp
178 178 except WindowsError as err:
179 179 # convert to a friendlier exception
180 180 raise IOError(
181 181 err.errno, '%s: %s' % (encoding.strfromlocal(name), err.strerror)
182 182 )
183 183
184 184
185 185 # may be wrapped by win32mbcs extension
186 186 listdir = osutil.listdir
187 187
188 188
189 189 class winstdout(object):
190 190 '''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 def _is_win_9x():
232 '''return true if run on windows 95, 98 or me.'''
233 try:
234 return sys.getwindowsversion()[3] == 1
235 except AttributeError:
236 return b'command' in encoding.environ.get(b'comspec', b'')
237
238
239 231 def openhardlinks():
240 return not _is_win_9x()
232 return True
241 233
242 234
243 235 def parsepatchoutput(output_line):
244 236 """parses the output produced by patch and returns the filename"""
245 237 pf = output_line[14:]
246 238 if pf[0] == b'`':
247 239 pf = pf[1:-1] # Remove the quotes
248 240 return pf
249 241
250 242
251 243 def sshargs(sshcmd, host, user, port):
252 244 '''Build argument list for ssh or Plink'''
253 245 pflag = b'plink' in sshcmd.lower() and b'-P' or b'-p'
254 246 args = user and (b"%s@%s" % (user, host)) or host
255 247 if args.startswith(b'-') or args.startswith(b'/'):
256 248 raise error.Abort(
257 249 _(b'illegal ssh hostname or username starting with - or /: %s')
258 250 % args
259 251 )
260 252 args = shellquote(args)
261 253 if port:
262 254 args = b'%s %s %s' % (pflag, shellquote(port), args)
263 255 return args
264 256
265 257
266 258 def setflags(f, l, x):
267 259 pass
268 260
269 261
270 262 def copymode(src, dst, mode=None, enforcewritable=False):
271 263 pass
272 264
273 265
274 266 def checkexec(path):
275 267 return False
276 268
277 269
278 270 def checklink(path):
279 271 return False
280 272
281 273
282 274 def setbinary(fd):
283 275 # When run without console, pipes may expose invalid
284 276 # fileno(), usually set to -1.
285 277 fno = getattr(fd, 'fileno', None)
286 278 if fno is not None and fno() >= 0:
287 279 msvcrt.setmode(fno(), os.O_BINARY) # pytype: disable=module-attr
288 280
289 281
290 282 def pconvert(path):
291 283 return path.replace(pycompat.ossep, b'/')
292 284
293 285
294 286 def localpath(path):
295 287 return path.replace(b'/', b'\\')
296 288
297 289
298 290 def normpath(path):
299 291 return pconvert(os.path.normpath(path))
300 292
301 293
302 294 def normcase(path):
303 295 return encoding.upper(path) # NTFS compares via upper()
304 296
305 297
306 298 # see posix.py for definitions
307 299 normcasespec = encoding.normcasespecs.upper
308 300 normcasefallback = encoding.upperfallback
309 301
310 302
311 303 def samestat(s1, s2):
312 304 return False
313 305
314 306
315 307 def shelltocmdexe(path, env):
316 308 r"""Convert shell variables in the form $var and ${var} inside ``path``
317 309 to %var% form. Existing Windows style variables are left unchanged.
318 310
319 311 The variables are limited to the given environment. Unknown variables are
320 312 left unchanged.
321 313
322 314 >>> e = {b'var1': b'v1', b'var2': b'v2', b'var3': b'v3'}
323 315 >>> # Only valid values are expanded
324 316 >>> shelltocmdexe(b'cmd $var1 ${var2} %var3% $missing ${missing} %missing%',
325 317 ... e)
326 318 'cmd %var1% %var2% %var3% $missing ${missing} %missing%'
327 319 >>> # Single quote prevents expansion, as does \$ escaping
328 320 >>> shelltocmdexe(b"cmd '$var1 ${var2} %var3%' \$var1 \${var2} \\", e)
329 321 'cmd "$var1 ${var2} %var3%" $var1 ${var2} \\'
330 322 >>> # $$ is not special. %% is not special either, but can be the end and
331 323 >>> # start of consecutive variables
332 324 >>> shelltocmdexe(b"cmd $$ %% %var1%%var2%", e)
333 325 'cmd $$ %% %var1%%var2%'
334 326 >>> # No double substitution
335 327 >>> shelltocmdexe(b"$var1 %var1%", {b'var1': b'%var2%', b'var2': b'boom'})
336 328 '%var1% %var1%'
337 329 >>> # Tilde expansion
338 330 >>> shelltocmdexe(b"~/dir ~\dir2 ~tmpfile \~/", {})
339 331 '%USERPROFILE%/dir %USERPROFILE%\\dir2 ~tmpfile ~/'
340 332 """
341 333 if not any(c in path for c in b"$'~"):
342 334 return path
343 335
344 336 varchars = pycompat.sysbytes(string.ascii_letters + string.digits) + b'_-'
345 337
346 338 res = b''
347 339 index = 0
348 340 pathlen = len(path)
349 341 while index < pathlen:
350 342 c = path[index : index + 1]
351 343 if c == b'\'': # no expansion within single quotes
352 344 path = path[index + 1 :]
353 345 pathlen = len(path)
354 346 try:
355 347 index = path.index(b'\'')
356 348 res += b'"' + path[:index] + b'"'
357 349 except ValueError:
358 350 res += c + path
359 351 index = pathlen - 1
360 352 elif c == b'%': # variable
361 353 path = path[index + 1 :]
362 354 pathlen = len(path)
363 355 try:
364 356 index = path.index(b'%')
365 357 except ValueError:
366 358 res += b'%' + path
367 359 index = pathlen - 1
368 360 else:
369 361 var = path[:index]
370 362 res += b'%' + var + b'%'
371 363 elif c == b'$': # variable
372 364 if path[index + 1 : index + 2] == b'{':
373 365 path = path[index + 2 :]
374 366 pathlen = len(path)
375 367 try:
376 368 index = path.index(b'}')
377 369 var = path[:index]
378 370
379 371 # See below for why empty variables are handled specially
380 372 if env.get(var, b'') != b'':
381 373 res += b'%' + var + b'%'
382 374 else:
383 375 res += b'${' + var + b'}'
384 376 except ValueError:
385 377 res += b'${' + path
386 378 index = pathlen - 1
387 379 else:
388 380 var = b''
389 381 index += 1
390 382 c = path[index : index + 1]
391 383 while c != b'' and c in varchars:
392 384 var += c
393 385 index += 1
394 386 c = path[index : index + 1]
395 387 # Some variables (like HG_OLDNODE) may be defined, but have an
396 388 # empty value. Those need to be skipped because when spawning
397 389 # cmd.exe to run the hook, it doesn't replace %VAR% for an empty
398 390 # VAR, and that really confuses things like revset expressions.
399 391 # OTOH, if it's left in Unix format and the hook runs sh.exe, it
400 392 # will substitute to an empty string, and everything is happy.
401 393 if env.get(var, b'') != b'':
402 394 res += b'%' + var + b'%'
403 395 else:
404 396 res += b'$' + var
405 397
406 398 if c != b'':
407 399 index -= 1
408 400 elif (
409 401 c == b'~'
410 402 and index + 1 < pathlen
411 403 and path[index + 1 : index + 2] in (b'\\', b'/')
412 404 ):
413 405 res += b"%USERPROFILE%"
414 406 elif (
415 407 c == b'\\'
416 408 and index + 1 < pathlen
417 409 and path[index + 1 : index + 2] in (b'$', b'~')
418 410 ):
419 411 # Skip '\', but only if it is escaping $ or ~
420 412 res += path[index + 1 : index + 2]
421 413 index += 1
422 414 else:
423 415 res += c
424 416
425 417 index += 1
426 418 return res
427 419
428 420
429 421 # A sequence of backslashes is special iff it precedes a double quote:
430 422 # - if there's an even number of backslashes, the double quote is not
431 423 # quoted (i.e. it ends the quoted region)
432 424 # - if there's an odd number of backslashes, the double quote is quoted
433 425 # - in both cases, every pair of backslashes is unquoted into a single
434 426 # backslash
435 427 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
436 428 # So, to quote a string, we must surround it in double quotes, double
437 429 # the number of backslashes that precede double quotes and add another
438 430 # backslash before every double quote (being careful with the double
439 431 # quote we've appended to the end)
440 432 _quotere = None
441 433 _needsshellquote = None
442 434
443 435
444 436 def shellquote(s):
445 437 r"""
446 438 >>> shellquote(br'C:\Users\xyz')
447 439 '"C:\\Users\\xyz"'
448 440 >>> shellquote(br'C:\Users\xyz/mixed')
449 441 '"C:\\Users\\xyz/mixed"'
450 442 >>> # Would be safe not to quote too, since it is all double backslashes
451 443 >>> shellquote(br'C:\\Users\\xyz')
452 444 '"C:\\\\Users\\\\xyz"'
453 445 >>> # But this must be quoted
454 446 >>> shellquote(br'C:\\Users\\xyz/abc')
455 447 '"C:\\\\Users\\\\xyz/abc"'
456 448 """
457 449 global _quotere
458 450 if _quotere is None:
459 451 _quotere = re.compile(br'(\\*)("|\\$)')
460 452 global _needsshellquote
461 453 if _needsshellquote is None:
462 454 # ":" is also treated as "safe character", because it is used as a part
463 455 # of path name on Windows. "\" is also part of a path name, but isn't
464 456 # safe because shlex.split() (kind of) treats it as an escape char and
465 457 # drops it. It will leave the next character, even if it is another
466 458 # "\".
467 459 _needsshellquote = re.compile(br'[^a-zA-Z0-9._:/-]').search
468 460 if s and not _needsshellquote(s) and not _quotere.search(s):
469 461 # "s" shouldn't have to be quoted
470 462 return s
471 463 return b'"%s"' % _quotere.sub(br'\1\1\\\2', s)
472 464
473 465
474 466 def _unquote(s):
475 467 if s.startswith(b'"') and s.endswith(b'"'):
476 468 return s[1:-1]
477 469 return s
478 470
479 471
480 472 def shellsplit(s):
481 473 """Parse a command string in cmd.exe way (best-effort)"""
482 474 return pycompat.maplist(_unquote, pycompat.shlexsplit(s, posix=False))
483 475
484 476
485 477 def quotecommand(cmd):
486 478 """Build a command string suitable for os.popen* calls."""
487 479 if sys.version_info < (2, 7, 1):
488 480 # Python versions since 2.7.1 do this extra quoting themselves
489 481 return b'"' + cmd + b'"'
490 482 return cmd
491 483
492 484
493 485 # if you change this stub into a real check, please try to implement the
494 486 # username and groupname functions above, too.
495 487 def isowner(st):
496 488 return True
497 489
498 490
499 491 def findexe(command):
500 492 '''Find executable for command searching like cmd.exe does.
501 493 If command is a basename then PATH is searched for command.
502 494 PATH isn't searched if command is an absolute or relative path.
503 495 An extension from PATHEXT is found and added if not present.
504 496 If command isn't found None is returned.'''
505 497 pathext = encoding.environ.get(b'PATHEXT', b'.COM;.EXE;.BAT;.CMD')
506 498 pathexts = [ext for ext in pathext.lower().split(pycompat.ospathsep)]
507 499 if os.path.splitext(command)[1].lower() in pathexts:
508 500 pathexts = [b'']
509 501
510 502 def findexisting(pathcommand):
511 503 """Will append extension (if needed) and return existing file"""
512 504 for ext in pathexts:
513 505 executable = pathcommand + ext
514 506 if os.path.exists(executable):
515 507 return executable
516 508 return None
517 509
518 510 if pycompat.ossep in command:
519 511 return findexisting(command)
520 512
521 513 for path in encoding.environ.get(b'PATH', b'').split(pycompat.ospathsep):
522 514 executable = findexisting(os.path.join(path, command))
523 515 if executable is not None:
524 516 return executable
525 517 return findexisting(os.path.expanduser(os.path.expandvars(command)))
526 518
527 519
528 520 _wantedkinds = {stat.S_IFREG, stat.S_IFLNK}
529 521
530 522
531 523 def statfiles(files):
532 524 '''Stat each file in files. Yield each stat, or None if a file
533 525 does not exist or has a type we don't care about.
534 526
535 527 Cluster and cache stat per directory to minimize number of OS stat calls.'''
536 528 dircache = {} # dirname -> filename -> status | None if file does not exist
537 529 getkind = stat.S_IFMT
538 530 for nf in files:
539 531 nf = normcase(nf)
540 532 dir, base = os.path.split(nf)
541 533 if not dir:
542 534 dir = b'.'
543 535 cache = dircache.get(dir, None)
544 536 if cache is None:
545 537 try:
546 538 dmap = dict(
547 539 [
548 540 (normcase(n), s)
549 541 for n, k, s in listdir(dir, True)
550 542 if getkind(s.st_mode) in _wantedkinds
551 543 ]
552 544 )
553 545 except OSError as err:
554 546 # Python >= 2.5 returns ENOENT and adds winerror field
555 547 # EINVAL is raised if dir is not a directory.
556 548 if err.errno not in (errno.ENOENT, errno.EINVAL, errno.ENOTDIR):
557 549 raise
558 550 dmap = {}
559 551 cache = dircache.setdefault(dir, dmap)
560 552 yield cache.get(base, None)
561 553
562 554
563 555 def username(uid=None):
564 556 """Return the name of the user with the given uid.
565 557
566 558 If uid is None, return the name of the current user."""
567 559 if not uid:
568 560 return pycompat.fsencode(getpass.getuser())
569 561 return None
570 562
571 563
572 564 def groupname(gid=None):
573 565 """Return the name of the group with the given gid.
574 566
575 567 If gid is None, return the name of the current group."""
576 568 return None
577 569
578 570
579 571 def readlink(pathname):
580 572 return pycompat.fsencode(os.readlink(pycompat.fsdecode(pathname)))
581 573
582 574
583 575 def removedirs(name):
584 576 """special version of os.removedirs that does not remove symlinked
585 577 directories or junction points if they actually contain files"""
586 578 if listdir(name):
587 579 return
588 580 os.rmdir(name)
589 581 head, tail = os.path.split(name)
590 582 if not tail:
591 583 head, tail = os.path.split(head)
592 584 while head and tail:
593 585 try:
594 586 if listdir(head):
595 587 return
596 588 os.rmdir(head)
597 589 except (ValueError, OSError):
598 590 break
599 591 head, tail = os.path.split(head)
600 592
601 593
602 594 def rename(src, dst):
603 595 '''atomically rename file src to dst, replacing dst if it exists'''
604 596 try:
605 597 os.rename(src, dst)
606 598 except OSError as e:
607 599 if e.errno != errno.EEXIST:
608 600 raise
609 601 unlink(dst)
610 602 os.rename(src, dst)
611 603
612 604
613 605 def gethgcmd():
614 606 return [encoding.strtolocal(arg) for arg in [sys.executable] + sys.argv[:1]]
615 607
616 608
617 609 def groupmembers(name):
618 610 # Don't support groups on Windows for now
619 611 raise KeyError
620 612
621 613
622 614 def isexec(f):
623 615 return False
624 616
625 617
626 618 class cachestat(object):
627 619 def __init__(self, path):
628 620 pass
629 621
630 622 def cacheable(self):
631 623 return False
632 624
633 625
634 626 def lookupreg(key, valname=None, scope=None):
635 627 ''' Look up a key/value name in the Windows registry.
636 628
637 629 valname: value name. If unspecified, the default value for the key
638 630 is used.
639 631 scope: optionally specify scope for registry lookup, this can be
640 632 a sequence of scopes to look up in order. Default (CURRENT_USER,
641 633 LOCAL_MACHINE).
642 634 '''
643 635 if scope is None:
644 636 scope = (winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE)
645 637 elif not isinstance(scope, (list, tuple)):
646 638 scope = (scope,)
647 639 for s in scope:
648 640 try:
649 641 with winreg.OpenKey(s, encoding.strfromlocal(key)) as hkey:
650 642 name = valname and encoding.strfromlocal(valname) or valname
651 643 val = winreg.QueryValueEx(hkey, name)[0]
652 644 # never let a Unicode string escape into the wild
653 645 return encoding.unitolocal(val)
654 646 except EnvironmentError:
655 647 pass
656 648
657 649
658 650 expandglobs = True
659 651
660 652
661 653 def statislink(st):
662 654 '''check whether a stat result is a symlink'''
663 655 return False
664 656
665 657
666 658 def statisexec(st):
667 659 '''check whether a stat result is an executable file'''
668 660 return False
669 661
670 662
671 663 def poll(fds):
672 664 # see posix.py for description
673 665 raise NotImplementedError()
674 666
675 667
676 668 def readpipe(pipe):
677 669 """Read all available data from a pipe."""
678 670 chunks = []
679 671 while True:
680 672 size = win32.peekpipe(pipe)
681 673 if not size:
682 674 break
683 675
684 676 s = pipe.read(size)
685 677 if not s:
686 678 break
687 679 chunks.append(s)
688 680
689 681 return b''.join(chunks)
690 682
691 683
692 684 def bindunixsocket(sock, path):
693 685 raise NotImplementedError('unsupported platform')
General Comments 0
You need to be logged in to leave comments. Login now