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