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