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