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