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