##// END OF EJS Templates
py3: catch PermissionError instead of checking errno == EACCES
Manuel Jacob -
r50202:6f2a57ba default
parent child Browse files
Show More
@@ -1,760 +1,755 b''
1 # posix.py - Posix utility function implementations for Mercurial
1 # posix.py - Posix 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 fcntl
10 import fcntl
11 import getpass
11 import getpass
12 import grp
12 import grp
13 import os
13 import os
14 import pwd
14 import pwd
15 import re
15 import re
16 import select
16 import select
17 import stat
17 import stat
18 import sys
18 import sys
19 import tempfile
19 import tempfile
20 import unicodedata
20 import unicodedata
21
21
22 from .i18n import _
22 from .i18n import _
23 from .pycompat import (
23 from .pycompat import (
24 getattr,
24 getattr,
25 open,
25 open,
26 )
26 )
27 from . import (
27 from . import (
28 encoding,
28 encoding,
29 error,
29 error,
30 policy,
30 policy,
31 pycompat,
31 pycompat,
32 )
32 )
33
33
34 osutil = policy.importmod('osutil')
34 osutil = policy.importmod('osutil')
35
35
36 normpath = os.path.normpath
36 normpath = os.path.normpath
37 samestat = os.path.samestat
37 samestat = os.path.samestat
38 abspath = os.path.abspath # re-exports
38 abspath = os.path.abspath # re-exports
39
39
40 try:
40 try:
41 oslink = os.link
41 oslink = os.link
42 except AttributeError:
42 except AttributeError:
43 # Some platforms build Python without os.link on systems that are
43 # Some platforms build Python without os.link on systems that are
44 # vaguely unix-like but don't have hardlink support. For those
44 # vaguely unix-like but don't have hardlink support. For those
45 # poor souls, just say we tried and that it failed so we fall back
45 # poor souls, just say we tried and that it failed so we fall back
46 # to copies.
46 # to copies.
47 def oslink(src, dst):
47 def oslink(src, dst):
48 raise OSError(
48 raise OSError(
49 errno.EINVAL, b'hardlinks not supported: %s to %s' % (src, dst)
49 errno.EINVAL, b'hardlinks not supported: %s to %s' % (src, dst)
50 )
50 )
51
51
52
52
53 readlink = os.readlink
53 readlink = os.readlink
54 unlink = os.unlink
54 unlink = os.unlink
55 rename = os.rename
55 rename = os.rename
56 removedirs = os.removedirs
56 removedirs = os.removedirs
57 expandglobs = False
57 expandglobs = False
58
58
59 umask = os.umask(0)
59 umask = os.umask(0)
60 os.umask(umask)
60 os.umask(umask)
61
61
62 posixfile = open
62 posixfile = open
63
63
64
64
65 def split(p):
65 def split(p):
66 """Same as posixpath.split, but faster
66 """Same as posixpath.split, but faster
67
67
68 >>> import posixpath
68 >>> import posixpath
69 >>> for f in [b'/absolute/path/to/file',
69 >>> for f in [b'/absolute/path/to/file',
70 ... b'relative/path/to/file',
70 ... b'relative/path/to/file',
71 ... b'file_alone',
71 ... b'file_alone',
72 ... b'path/to/directory/',
72 ... b'path/to/directory/',
73 ... b'/multiple/path//separators',
73 ... b'/multiple/path//separators',
74 ... b'/file_at_root',
74 ... b'/file_at_root',
75 ... b'///multiple_leading_separators_at_root',
75 ... b'///multiple_leading_separators_at_root',
76 ... b'']:
76 ... b'']:
77 ... assert split(f) == posixpath.split(f), f
77 ... assert split(f) == posixpath.split(f), f
78 """
78 """
79 ht = p.rsplit(b'/', 1)
79 ht = p.rsplit(b'/', 1)
80 if len(ht) == 1:
80 if len(ht) == 1:
81 return b'', p
81 return b'', p
82 nh = ht[0].rstrip(b'/')
82 nh = ht[0].rstrip(b'/')
83 if nh:
83 if nh:
84 return nh, ht[1]
84 return nh, ht[1]
85 return ht[0] + b'/', ht[1]
85 return ht[0] + b'/', ht[1]
86
86
87
87
88 def openhardlinks():
88 def openhardlinks():
89 '''return true if it is safe to hold open file handles to hardlinks'''
89 '''return true if it is safe to hold open file handles to hardlinks'''
90 return True
90 return True
91
91
92
92
93 def nlinks(name):
93 def nlinks(name):
94 '''return number of hardlinks for the given file'''
94 '''return number of hardlinks for the given file'''
95 return os.lstat(name).st_nlink
95 return os.lstat(name).st_nlink
96
96
97
97
98 def parsepatchoutput(output_line):
98 def parsepatchoutput(output_line):
99 """parses the output produced by patch and returns the filename"""
99 """parses the output produced by patch and returns the filename"""
100 pf = output_line[14:]
100 pf = output_line[14:]
101 if pycompat.sysplatform == b'OpenVMS':
101 if pycompat.sysplatform == b'OpenVMS':
102 if pf[0] == b'`':
102 if pf[0] == b'`':
103 pf = pf[1:-1] # Remove the quotes
103 pf = pf[1:-1] # Remove the quotes
104 else:
104 else:
105 if pf.startswith(b"'") and pf.endswith(b"'") and b" " in pf:
105 if pf.startswith(b"'") and pf.endswith(b"'") and b" " in pf:
106 pf = pf[1:-1] # Remove the quotes
106 pf = pf[1:-1] # Remove the quotes
107 return pf
107 return pf
108
108
109
109
110 def sshargs(sshcmd, host, user, port):
110 def sshargs(sshcmd, host, user, port):
111 '''Build argument list for ssh'''
111 '''Build argument list for ssh'''
112 args = user and (b"%s@%s" % (user, host)) or host
112 args = user and (b"%s@%s" % (user, host)) or host
113 if b'-' in args[:1]:
113 if b'-' in args[:1]:
114 raise error.Abort(
114 raise error.Abort(
115 _(b'illegal ssh hostname or username starting with -: %s') % args
115 _(b'illegal ssh hostname or username starting with -: %s') % args
116 )
116 )
117 args = shellquote(args)
117 args = shellquote(args)
118 if port:
118 if port:
119 args = b'-p %s %s' % (shellquote(port), args)
119 args = b'-p %s %s' % (shellquote(port), args)
120 return args
120 return args
121
121
122
122
123 def isexec(f):
123 def isexec(f):
124 """check whether a file is executable"""
124 """check whether a file is executable"""
125 return os.lstat(f).st_mode & 0o100 != 0
125 return os.lstat(f).st_mode & 0o100 != 0
126
126
127
127
128 def setflags(f, l, x):
128 def setflags(f, l, x):
129 st = os.lstat(f)
129 st = os.lstat(f)
130 s = st.st_mode
130 s = st.st_mode
131 if l:
131 if l:
132 if not stat.S_ISLNK(s):
132 if not stat.S_ISLNK(s):
133 # switch file to link
133 # switch file to link
134 with open(f, b'rb') as fp:
134 with open(f, b'rb') as fp:
135 data = fp.read()
135 data = fp.read()
136 unlink(f)
136 unlink(f)
137 try:
137 try:
138 os.symlink(data, f)
138 os.symlink(data, f)
139 except OSError:
139 except OSError:
140 # failed to make a link, rewrite file
140 # failed to make a link, rewrite file
141 with open(f, b"wb") as fp:
141 with open(f, b"wb") as fp:
142 fp.write(data)
142 fp.write(data)
143
143
144 # no chmod needed at this point
144 # no chmod needed at this point
145 return
145 return
146 if stat.S_ISLNK(s):
146 if stat.S_ISLNK(s):
147 # switch link to file
147 # switch link to file
148 data = os.readlink(f)
148 data = os.readlink(f)
149 unlink(f)
149 unlink(f)
150 with open(f, b"wb") as fp:
150 with open(f, b"wb") as fp:
151 fp.write(data)
151 fp.write(data)
152 s = 0o666 & ~umask # avoid restatting for chmod
152 s = 0o666 & ~umask # avoid restatting for chmod
153
153
154 sx = s & 0o100
154 sx = s & 0o100
155 if st.st_nlink > 1 and bool(x) != bool(sx):
155 if st.st_nlink > 1 and bool(x) != bool(sx):
156 # the file is a hardlink, break it
156 # the file is a hardlink, break it
157 with open(f, b"rb") as fp:
157 with open(f, b"rb") as fp:
158 data = fp.read()
158 data = fp.read()
159 unlink(f)
159 unlink(f)
160 with open(f, b"wb") as fp:
160 with open(f, b"wb") as fp:
161 fp.write(data)
161 fp.write(data)
162
162
163 if x and not sx:
163 if x and not sx:
164 # Turn on +x for every +r bit when making a file executable
164 # Turn on +x for every +r bit when making a file executable
165 # and obey umask.
165 # and obey umask.
166 os.chmod(f, s | (s & 0o444) >> 2 & ~umask)
166 os.chmod(f, s | (s & 0o444) >> 2 & ~umask)
167 elif not x and sx:
167 elif not x and sx:
168 # Turn off all +x bits
168 # Turn off all +x bits
169 os.chmod(f, s & 0o666)
169 os.chmod(f, s & 0o666)
170
170
171
171
172 def copymode(src, dst, mode=None, enforcewritable=False):
172 def copymode(src, dst, mode=None, enforcewritable=False):
173 """Copy the file mode from the file at path src to dst.
173 """Copy the file mode from the file at path src to dst.
174 If src doesn't exist, we're using mode instead. If mode is None, we're
174 If src doesn't exist, we're using mode instead. If mode is None, we're
175 using umask."""
175 using umask."""
176 try:
176 try:
177 st_mode = os.lstat(src).st_mode & 0o777
177 st_mode = os.lstat(src).st_mode & 0o777
178 except FileNotFoundError:
178 except FileNotFoundError:
179 st_mode = mode
179 st_mode = mode
180 if st_mode is None:
180 if st_mode is None:
181 st_mode = ~umask
181 st_mode = ~umask
182 st_mode &= 0o666
182 st_mode &= 0o666
183
183
184 new_mode = st_mode
184 new_mode = st_mode
185
185
186 if enforcewritable:
186 if enforcewritable:
187 new_mode |= stat.S_IWUSR
187 new_mode |= stat.S_IWUSR
188
188
189 os.chmod(dst, new_mode)
189 os.chmod(dst, new_mode)
190
190
191
191
192 def checkexec(path):
192 def checkexec(path):
193 """
193 """
194 Check whether the given path is on a filesystem with UNIX-like exec flags
194 Check whether the given path is on a filesystem with UNIX-like exec flags
195
195
196 Requires a directory (like /foo/.hg)
196 Requires a directory (like /foo/.hg)
197 """
197 """
198
198
199 # VFAT on some Linux versions can flip mode but it doesn't persist
199 # VFAT on some Linux versions can flip mode but it doesn't persist
200 # a FS remount. Frequently we can detect it if files are created
200 # a FS remount. Frequently we can detect it if files are created
201 # with exec bit on.
201 # with exec bit on.
202
202
203 try:
203 try:
204 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
204 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
205 basedir = os.path.join(path, b'.hg')
205 basedir = os.path.join(path, b'.hg')
206 cachedir = os.path.join(basedir, b'wcache')
206 cachedir = os.path.join(basedir, b'wcache')
207 storedir = os.path.join(basedir, b'store')
207 storedir = os.path.join(basedir, b'store')
208 if not os.path.exists(cachedir):
208 if not os.path.exists(cachedir):
209 try:
209 try:
210 # we want to create the 'cache' directory, not the '.hg' one.
210 # we want to create the 'cache' directory, not the '.hg' one.
211 # Automatically creating '.hg' directory could silently spawn
211 # Automatically creating '.hg' directory could silently spawn
212 # invalid Mercurial repositories. That seems like a bad idea.
212 # invalid Mercurial repositories. That seems like a bad idea.
213 os.mkdir(cachedir)
213 os.mkdir(cachedir)
214 if os.path.exists(storedir):
214 if os.path.exists(storedir):
215 copymode(storedir, cachedir)
215 copymode(storedir, cachedir)
216 else:
216 else:
217 copymode(basedir, cachedir)
217 copymode(basedir, cachedir)
218 except (IOError, OSError):
218 except (IOError, OSError):
219 # we other fallback logic triggers
219 # we other fallback logic triggers
220 pass
220 pass
221 if os.path.isdir(cachedir):
221 if os.path.isdir(cachedir):
222 checkisexec = os.path.join(cachedir, b'checkisexec')
222 checkisexec = os.path.join(cachedir, b'checkisexec')
223 checknoexec = os.path.join(cachedir, b'checknoexec')
223 checknoexec = os.path.join(cachedir, b'checknoexec')
224
224
225 try:
225 try:
226 m = os.stat(checkisexec).st_mode
226 m = os.stat(checkisexec).st_mode
227 except FileNotFoundError:
227 except FileNotFoundError:
228 # checkisexec does not exist - fall through ...
228 # checkisexec does not exist - fall through ...
229 pass
229 pass
230 else:
230 else:
231 # checkisexec exists, check if it actually is exec
231 # checkisexec exists, check if it actually is exec
232 if m & EXECFLAGS != 0:
232 if m & EXECFLAGS != 0:
233 # ensure checkisexec exists, check it isn't exec
233 # ensure checkisexec exists, check it isn't exec
234 try:
234 try:
235 m = os.stat(checknoexec).st_mode
235 m = os.stat(checknoexec).st_mode
236 except FileNotFoundError:
236 except FileNotFoundError:
237 open(checknoexec, b'w').close() # might fail
237 open(checknoexec, b'w').close() # might fail
238 m = os.stat(checknoexec).st_mode
238 m = os.stat(checknoexec).st_mode
239 if m & EXECFLAGS == 0:
239 if m & EXECFLAGS == 0:
240 # check-exec is exec and check-no-exec is not exec
240 # check-exec is exec and check-no-exec is not exec
241 return True
241 return True
242 # checknoexec exists but is exec - delete it
242 # checknoexec exists but is exec - delete it
243 unlink(checknoexec)
243 unlink(checknoexec)
244 # checkisexec exists but is not exec - delete it
244 # checkisexec exists but is not exec - delete it
245 unlink(checkisexec)
245 unlink(checkisexec)
246
246
247 # check using one file, leave it as checkisexec
247 # check using one file, leave it as checkisexec
248 checkdir = cachedir
248 checkdir = cachedir
249 else:
249 else:
250 # check directly in path and don't leave checkisexec behind
250 # check directly in path and don't leave checkisexec behind
251 checkdir = path
251 checkdir = path
252 checkisexec = None
252 checkisexec = None
253 fh, fn = pycompat.mkstemp(dir=checkdir, prefix=b'hg-checkexec-')
253 fh, fn = pycompat.mkstemp(dir=checkdir, prefix=b'hg-checkexec-')
254 try:
254 try:
255 os.close(fh)
255 os.close(fh)
256 m = os.stat(fn).st_mode
256 m = os.stat(fn).st_mode
257 if m & EXECFLAGS == 0:
257 if m & EXECFLAGS == 0:
258 os.chmod(fn, m & 0o777 | EXECFLAGS)
258 os.chmod(fn, m & 0o777 | EXECFLAGS)
259 if os.stat(fn).st_mode & EXECFLAGS != 0:
259 if os.stat(fn).st_mode & EXECFLAGS != 0:
260 if checkisexec is not None:
260 if checkisexec is not None:
261 os.rename(fn, checkisexec)
261 os.rename(fn, checkisexec)
262 fn = None
262 fn = None
263 return True
263 return True
264 finally:
264 finally:
265 if fn is not None:
265 if fn is not None:
266 unlink(fn)
266 unlink(fn)
267 except (IOError, OSError):
267 except (IOError, OSError):
268 # we don't care, the user probably won't be able to commit anyway
268 # we don't care, the user probably won't be able to commit anyway
269 return False
269 return False
270
270
271
271
272 def checklink(path):
272 def checklink(path):
273 """check whether the given path is on a symlink-capable filesystem"""
273 """check whether the given path is on a symlink-capable filesystem"""
274 # mktemp is not racy because symlink creation will fail if the
274 # mktemp is not racy because symlink creation will fail if the
275 # file already exists
275 # file already exists
276 while True:
276 while True:
277 cachedir = os.path.join(path, b'.hg', b'wcache')
277 cachedir = os.path.join(path, b'.hg', b'wcache')
278 checklink = os.path.join(cachedir, b'checklink')
278 checklink = os.path.join(cachedir, b'checklink')
279 # try fast path, read only
279 # try fast path, read only
280 if os.path.islink(checklink):
280 if os.path.islink(checklink):
281 return True
281 return True
282 if os.path.isdir(cachedir):
282 if os.path.isdir(cachedir):
283 checkdir = cachedir
283 checkdir = cachedir
284 else:
284 else:
285 checkdir = path
285 checkdir = path
286 cachedir = None
286 cachedir = None
287 name = tempfile.mktemp(
287 name = tempfile.mktemp(
288 dir=pycompat.fsdecode(checkdir), prefix=r'checklink-'
288 dir=pycompat.fsdecode(checkdir), prefix=r'checklink-'
289 )
289 )
290 name = pycompat.fsencode(name)
290 name = pycompat.fsencode(name)
291 try:
291 try:
292 fd = None
292 fd = None
293 if cachedir is None:
293 if cachedir is None:
294 fd = pycompat.namedtempfile(
294 fd = pycompat.namedtempfile(
295 dir=checkdir, prefix=b'hg-checklink-'
295 dir=checkdir, prefix=b'hg-checklink-'
296 )
296 )
297 target = os.path.basename(fd.name)
297 target = os.path.basename(fd.name)
298 else:
298 else:
299 # create a fixed file to link to; doesn't matter if it
299 # create a fixed file to link to; doesn't matter if it
300 # already exists.
300 # already exists.
301 target = b'checklink-target'
301 target = b'checklink-target'
302 try:
302 try:
303 fullpath = os.path.join(cachedir, target)
303 fullpath = os.path.join(cachedir, target)
304 open(fullpath, b'w').close()
304 open(fullpath, b'w').close()
305 except IOError as inst:
305 except PermissionError:
306 # pytype: disable=unsupported-operands
306 # If we can't write to cachedir, just pretend
307 if inst[0] == errno.EACCES:
307 # that the fs is readonly and by association
308 # pytype: enable=unsupported-operands
308 # that the fs won't support symlinks. This
309
309 # seems like the least dangerous way to avoid
310 # If we can't write to cachedir, just pretend
310 # data loss.
311 # that the fs is readonly and by association
311 return False
312 # that the fs won't support symlinks. This
313 # seems like the least dangerous way to avoid
314 # data loss.
315 return False
316 raise
317 try:
312 try:
318 os.symlink(target, name)
313 os.symlink(target, name)
319 if cachedir is None:
314 if cachedir is None:
320 unlink(name)
315 unlink(name)
321 else:
316 else:
322 try:
317 try:
323 os.rename(name, checklink)
318 os.rename(name, checklink)
324 except OSError:
319 except OSError:
325 unlink(name)
320 unlink(name)
326 return True
321 return True
327 except FileExistsError:
322 except FileExistsError:
328 # link creation might race, try again
323 # link creation might race, try again
329 continue
324 continue
330 finally:
325 finally:
331 if fd is not None:
326 if fd is not None:
332 fd.close()
327 fd.close()
333 except AttributeError:
328 except AttributeError:
334 return False
329 return False
335 except OSError as inst:
330 except OSError as inst:
336 # sshfs might report failure while successfully creating the link
331 # sshfs might report failure while successfully creating the link
337 if inst.errno == errno.EIO and os.path.exists(name):
332 if inst.errno == errno.EIO and os.path.exists(name):
338 unlink(name)
333 unlink(name)
339 return False
334 return False
340
335
341
336
342 def checkosfilename(path):
337 def checkosfilename(path):
343 """Check that the base-relative path is a valid filename on this platform.
338 """Check that the base-relative path is a valid filename on this platform.
344 Returns None if the path is ok, or a UI string describing the problem."""
339 Returns None if the path is ok, or a UI string describing the problem."""
345 return None # on posix platforms, every path is ok
340 return None # on posix platforms, every path is ok
346
341
347
342
348 def getfsmountpoint(dirpath):
343 def getfsmountpoint(dirpath):
349 """Get the filesystem mount point from a directory (best-effort)
344 """Get the filesystem mount point from a directory (best-effort)
350
345
351 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
346 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
352 """
347 """
353 return getattr(osutil, 'getfsmountpoint', lambda x: None)(dirpath)
348 return getattr(osutil, 'getfsmountpoint', lambda x: None)(dirpath)
354
349
355
350
356 def getfstype(dirpath):
351 def getfstype(dirpath):
357 """Get the filesystem type name from a directory (best-effort)
352 """Get the filesystem type name from a directory (best-effort)
358
353
359 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
354 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
360 """
355 """
361 return getattr(osutil, 'getfstype', lambda x: None)(dirpath)
356 return getattr(osutil, 'getfstype', lambda x: None)(dirpath)
362
357
363
358
364 def get_password():
359 def get_password():
365 return encoding.strtolocal(getpass.getpass(''))
360 return encoding.strtolocal(getpass.getpass(''))
366
361
367
362
368 def setbinary(fd):
363 def setbinary(fd):
369 pass
364 pass
370
365
371
366
372 def pconvert(path):
367 def pconvert(path):
373 return path
368 return path
374
369
375
370
376 def localpath(path):
371 def localpath(path):
377 return path
372 return path
378
373
379
374
380 def samefile(fpath1, fpath2):
375 def samefile(fpath1, fpath2):
381 """Returns whether path1 and path2 refer to the same file. This is only
376 """Returns whether path1 and path2 refer to the same file. This is only
382 guaranteed to work for files, not directories."""
377 guaranteed to work for files, not directories."""
383 return os.path.samefile(fpath1, fpath2)
378 return os.path.samefile(fpath1, fpath2)
384
379
385
380
386 def samedevice(fpath1, fpath2):
381 def samedevice(fpath1, fpath2):
387 """Returns whether fpath1 and fpath2 are on the same device. This is only
382 """Returns whether fpath1 and fpath2 are on the same device. This is only
388 guaranteed to work for files, not directories."""
383 guaranteed to work for files, not directories."""
389 st1 = os.lstat(fpath1)
384 st1 = os.lstat(fpath1)
390 st2 = os.lstat(fpath2)
385 st2 = os.lstat(fpath2)
391 return st1.st_dev == st2.st_dev
386 return st1.st_dev == st2.st_dev
392
387
393
388
394 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
389 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
395 def normcase(path):
390 def normcase(path):
396 return path.lower()
391 return path.lower()
397
392
398
393
399 # what normcase does to ASCII strings
394 # what normcase does to ASCII strings
400 normcasespec = encoding.normcasespecs.lower
395 normcasespec = encoding.normcasespecs.lower
401 # fallback normcase function for non-ASCII strings
396 # fallback normcase function for non-ASCII strings
402 normcasefallback = normcase
397 normcasefallback = normcase
403
398
404 if pycompat.isdarwin:
399 if pycompat.isdarwin:
405
400
406 def normcase(path):
401 def normcase(path):
407 """
402 """
408 Normalize a filename for OS X-compatible comparison:
403 Normalize a filename for OS X-compatible comparison:
409 - escape-encode invalid characters
404 - escape-encode invalid characters
410 - decompose to NFD
405 - decompose to NFD
411 - lowercase
406 - lowercase
412 - omit ignored characters [200c-200f, 202a-202e, 206a-206f,feff]
407 - omit ignored characters [200c-200f, 202a-202e, 206a-206f,feff]
413
408
414 >>> normcase(b'UPPER')
409 >>> normcase(b'UPPER')
415 'upper'
410 'upper'
416 >>> normcase(b'Caf\\xc3\\xa9')
411 >>> normcase(b'Caf\\xc3\\xa9')
417 'cafe\\xcc\\x81'
412 'cafe\\xcc\\x81'
418 >>> normcase(b'\\xc3\\x89')
413 >>> normcase(b'\\xc3\\x89')
419 'e\\xcc\\x81'
414 'e\\xcc\\x81'
420 >>> normcase(b'\\xb8\\xca\\xc3\\xca\\xbe\\xc8.JPG') # issue3918
415 >>> normcase(b'\\xb8\\xca\\xc3\\xca\\xbe\\xc8.JPG') # issue3918
421 '%b8%ca%c3\\xca\\xbe%c8.jpg'
416 '%b8%ca%c3\\xca\\xbe%c8.jpg'
422 """
417 """
423
418
424 try:
419 try:
425 return encoding.asciilower(path) # exception for non-ASCII
420 return encoding.asciilower(path) # exception for non-ASCII
426 except UnicodeDecodeError:
421 except UnicodeDecodeError:
427 return normcasefallback(path)
422 return normcasefallback(path)
428
423
429 normcasespec = encoding.normcasespecs.lower
424 normcasespec = encoding.normcasespecs.lower
430
425
431 def normcasefallback(path):
426 def normcasefallback(path):
432 try:
427 try:
433 u = path.decode('utf-8')
428 u = path.decode('utf-8')
434 except UnicodeDecodeError:
429 except UnicodeDecodeError:
435 # OS X percent-encodes any bytes that aren't valid utf-8
430 # OS X percent-encodes any bytes that aren't valid utf-8
436 s = b''
431 s = b''
437 pos = 0
432 pos = 0
438 l = len(path)
433 l = len(path)
439 while pos < l:
434 while pos < l:
440 try:
435 try:
441 c = encoding.getutf8char(path, pos)
436 c = encoding.getutf8char(path, pos)
442 pos += len(c)
437 pos += len(c)
443 except ValueError:
438 except ValueError:
444 c = b'%%%02X' % ord(path[pos : pos + 1])
439 c = b'%%%02X' % ord(path[pos : pos + 1])
445 pos += 1
440 pos += 1
446 s += c
441 s += c
447
442
448 u = s.decode('utf-8')
443 u = s.decode('utf-8')
449
444
450 # Decompose then lowercase (HFS+ technote specifies lower)
445 # Decompose then lowercase (HFS+ technote specifies lower)
451 enc = unicodedata.normalize('NFD', u).lower().encode('utf-8')
446 enc = unicodedata.normalize('NFD', u).lower().encode('utf-8')
452 # drop HFS+ ignored characters
447 # drop HFS+ ignored characters
453 return encoding.hfsignoreclean(enc)
448 return encoding.hfsignoreclean(enc)
454
449
455
450
456 if pycompat.sysplatform == b'cygwin':
451 if pycompat.sysplatform == b'cygwin':
457 # workaround for cygwin, in which mount point part of path is
452 # workaround for cygwin, in which mount point part of path is
458 # treated as case sensitive, even though underlying NTFS is case
453 # treated as case sensitive, even though underlying NTFS is case
459 # insensitive.
454 # insensitive.
460
455
461 # default mount points
456 # default mount points
462 cygwinmountpoints = sorted(
457 cygwinmountpoints = sorted(
463 [
458 [
464 b"/usr/bin",
459 b"/usr/bin",
465 b"/usr/lib",
460 b"/usr/lib",
466 b"/cygdrive",
461 b"/cygdrive",
467 ],
462 ],
468 reverse=True,
463 reverse=True,
469 )
464 )
470
465
471 # use upper-ing as normcase as same as NTFS workaround
466 # use upper-ing as normcase as same as NTFS workaround
472 def normcase(path):
467 def normcase(path):
473 pathlen = len(path)
468 pathlen = len(path)
474 if (pathlen == 0) or (path[0] != pycompat.ossep):
469 if (pathlen == 0) or (path[0] != pycompat.ossep):
475 # treat as relative
470 # treat as relative
476 return encoding.upper(path)
471 return encoding.upper(path)
477
472
478 # to preserve case of mountpoint part
473 # to preserve case of mountpoint part
479 for mp in cygwinmountpoints:
474 for mp in cygwinmountpoints:
480 if not path.startswith(mp):
475 if not path.startswith(mp):
481 continue
476 continue
482
477
483 mplen = len(mp)
478 mplen = len(mp)
484 if mplen == pathlen: # mount point itself
479 if mplen == pathlen: # mount point itself
485 return mp
480 return mp
486 if path[mplen] == pycompat.ossep:
481 if path[mplen] == pycompat.ossep:
487 return mp + encoding.upper(path[mplen:])
482 return mp + encoding.upper(path[mplen:])
488
483
489 return encoding.upper(path)
484 return encoding.upper(path)
490
485
491 normcasespec = encoding.normcasespecs.other
486 normcasespec = encoding.normcasespecs.other
492 normcasefallback = normcase
487 normcasefallback = normcase
493
488
494 # Cygwin translates native ACLs to POSIX permissions,
489 # Cygwin translates native ACLs to POSIX permissions,
495 # but these translations are not supported by native
490 # but these translations are not supported by native
496 # tools, so the exec bit tends to be set erroneously.
491 # tools, so the exec bit tends to be set erroneously.
497 # Therefore, disable executable bit access on Cygwin.
492 # Therefore, disable executable bit access on Cygwin.
498 def checkexec(path):
493 def checkexec(path):
499 return False
494 return False
500
495
501 # Similarly, Cygwin's symlink emulation is likely to create
496 # Similarly, Cygwin's symlink emulation is likely to create
502 # problems when Mercurial is used from both Cygwin and native
497 # problems when Mercurial is used from both Cygwin and native
503 # Windows, with other native tools, or on shared volumes
498 # Windows, with other native tools, or on shared volumes
504 def checklink(path):
499 def checklink(path):
505 return False
500 return False
506
501
507
502
508 _needsshellquote = None
503 _needsshellquote = None
509
504
510
505
511 def shellquote(s):
506 def shellquote(s):
512 if pycompat.sysplatform == b'OpenVMS':
507 if pycompat.sysplatform == b'OpenVMS':
513 return b'"%s"' % s
508 return b'"%s"' % s
514 global _needsshellquote
509 global _needsshellquote
515 if _needsshellquote is None:
510 if _needsshellquote is None:
516 _needsshellquote = re.compile(br'[^a-zA-Z0-9._/+-]').search
511 _needsshellquote = re.compile(br'[^a-zA-Z0-9._/+-]').search
517 if s and not _needsshellquote(s):
512 if s and not _needsshellquote(s):
518 # "s" shouldn't have to be quoted
513 # "s" shouldn't have to be quoted
519 return s
514 return s
520 else:
515 else:
521 return b"'%s'" % s.replace(b"'", b"'\\''")
516 return b"'%s'" % s.replace(b"'", b"'\\''")
522
517
523
518
524 def shellsplit(s):
519 def shellsplit(s):
525 """Parse a command string in POSIX shell way (best-effort)"""
520 """Parse a command string in POSIX shell way (best-effort)"""
526 return pycompat.shlexsplit(s, posix=True)
521 return pycompat.shlexsplit(s, posix=True)
527
522
528
523
529 def testpid(pid):
524 def testpid(pid):
530 '''return False if pid dead, True if running or not sure'''
525 '''return False if pid dead, True if running or not sure'''
531 if pycompat.sysplatform == b'OpenVMS':
526 if pycompat.sysplatform == b'OpenVMS':
532 return True
527 return True
533 try:
528 try:
534 os.kill(pid, 0)
529 os.kill(pid, 0)
535 return True
530 return True
536 except OSError as inst:
531 except OSError as inst:
537 return inst.errno != errno.ESRCH
532 return inst.errno != errno.ESRCH
538
533
539
534
540 def isowner(st):
535 def isowner(st):
541 """Return True if the stat object st is from the current user."""
536 """Return True if the stat object st is from the current user."""
542 return st.st_uid == os.getuid()
537 return st.st_uid == os.getuid()
543
538
544
539
545 def findexe(command):
540 def findexe(command):
546 """Find executable for command searching like which does.
541 """Find executable for command searching like which does.
547 If command is a basename then PATH is searched for command.
542 If command is a basename then PATH is searched for command.
548 PATH isn't searched if command is an absolute or relative path.
543 PATH isn't searched if command is an absolute or relative path.
549 If command isn't found None is returned."""
544 If command isn't found None is returned."""
550 if pycompat.sysplatform == b'OpenVMS':
545 if pycompat.sysplatform == b'OpenVMS':
551 return command
546 return command
552
547
553 def findexisting(executable):
548 def findexisting(executable):
554 b'Will return executable if existing file'
549 b'Will return executable if existing file'
555 if os.path.isfile(executable) and os.access(executable, os.X_OK):
550 if os.path.isfile(executable) and os.access(executable, os.X_OK):
556 return executable
551 return executable
557 return None
552 return None
558
553
559 if pycompat.ossep in command:
554 if pycompat.ossep in command:
560 return findexisting(command)
555 return findexisting(command)
561
556
562 if pycompat.sysplatform == b'plan9':
557 if pycompat.sysplatform == b'plan9':
563 return findexisting(os.path.join(b'/bin', command))
558 return findexisting(os.path.join(b'/bin', command))
564
559
565 for path in encoding.environ.get(b'PATH', b'').split(pycompat.ospathsep):
560 for path in encoding.environ.get(b'PATH', b'').split(pycompat.ospathsep):
566 executable = findexisting(os.path.join(path, command))
561 executable = findexisting(os.path.join(path, command))
567 if executable is not None:
562 if executable is not None:
568 return executable
563 return executable
569 return None
564 return None
570
565
571
566
572 def setsignalhandler():
567 def setsignalhandler():
573 pass
568 pass
574
569
575
570
576 _wantedkinds = {stat.S_IFREG, stat.S_IFLNK}
571 _wantedkinds = {stat.S_IFREG, stat.S_IFLNK}
577
572
578
573
579 def statfiles(files):
574 def statfiles(files):
580 """Stat each file in files. Yield each stat, or None if a file does not
575 """Stat each file in files. Yield each stat, or None if a file does not
581 exist or has a type we don't care about."""
576 exist or has a type we don't care about."""
582 lstat = os.lstat
577 lstat = os.lstat
583 getkind = stat.S_IFMT
578 getkind = stat.S_IFMT
584 for nf in files:
579 for nf in files:
585 try:
580 try:
586 st = lstat(nf)
581 st = lstat(nf)
587 if getkind(st.st_mode) not in _wantedkinds:
582 if getkind(st.st_mode) not in _wantedkinds:
588 st = None
583 st = None
589 except OSError as err:
584 except OSError as err:
590 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
585 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
591 raise
586 raise
592 st = None
587 st = None
593 yield st
588 yield st
594
589
595
590
596 def getuser():
591 def getuser():
597 '''return name of current user'''
592 '''return name of current user'''
598 return pycompat.fsencode(getpass.getuser())
593 return pycompat.fsencode(getpass.getuser())
599
594
600
595
601 def username(uid=None):
596 def username(uid=None):
602 """Return the name of the user with the given uid.
597 """Return the name of the user with the given uid.
603
598
604 If uid is None, return the name of the current user."""
599 If uid is None, return the name of the current user."""
605
600
606 if uid is None:
601 if uid is None:
607 uid = os.getuid()
602 uid = os.getuid()
608 try:
603 try:
609 return pycompat.fsencode(pwd.getpwuid(uid)[0])
604 return pycompat.fsencode(pwd.getpwuid(uid)[0])
610 except KeyError:
605 except KeyError:
611 return b'%d' % uid
606 return b'%d' % uid
612
607
613
608
614 def groupname(gid=None):
609 def groupname(gid=None):
615 """Return the name of the group with the given gid.
610 """Return the name of the group with the given gid.
616
611
617 If gid is None, return the name of the current group."""
612 If gid is None, return the name of the current group."""
618
613
619 if gid is None:
614 if gid is None:
620 gid = os.getgid()
615 gid = os.getgid()
621 try:
616 try:
622 return pycompat.fsencode(grp.getgrgid(gid)[0])
617 return pycompat.fsencode(grp.getgrgid(gid)[0])
623 except KeyError:
618 except KeyError:
624 return pycompat.bytestr(gid)
619 return pycompat.bytestr(gid)
625
620
626
621
627 def groupmembers(name):
622 def groupmembers(name):
628 """Return the list of members of the group with the given
623 """Return the list of members of the group with the given
629 name, KeyError if the group does not exist.
624 name, KeyError if the group does not exist.
630 """
625 """
631 name = pycompat.fsdecode(name)
626 name = pycompat.fsdecode(name)
632 return pycompat.rapply(pycompat.fsencode, list(grp.getgrnam(name).gr_mem))
627 return pycompat.rapply(pycompat.fsencode, list(grp.getgrnam(name).gr_mem))
633
628
634
629
635 def spawndetached(args):
630 def spawndetached(args):
636 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0), args[0], args)
631 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0), args[0], args)
637
632
638
633
639 def gethgcmd():
634 def gethgcmd():
640 return sys.argv[:1]
635 return sys.argv[:1]
641
636
642
637
643 def makedir(path, notindexed):
638 def makedir(path, notindexed):
644 os.mkdir(path)
639 os.mkdir(path)
645
640
646
641
647 def lookupreg(key, name=None, scope=None):
642 def lookupreg(key, name=None, scope=None):
648 return None
643 return None
649
644
650
645
651 def hidewindow():
646 def hidewindow():
652 """Hide current shell window.
647 """Hide current shell window.
653
648
654 Used to hide the window opened when starting asynchronous
649 Used to hide the window opened when starting asynchronous
655 child process under Windows, unneeded on other systems.
650 child process under Windows, unneeded on other systems.
656 """
651 """
657 pass
652 pass
658
653
659
654
660 class cachestat:
655 class cachestat:
661 def __init__(self, path):
656 def __init__(self, path):
662 self.stat = os.stat(path)
657 self.stat = os.stat(path)
663
658
664 def cacheable(self):
659 def cacheable(self):
665 return bool(self.stat.st_ino)
660 return bool(self.stat.st_ino)
666
661
667 __hash__ = object.__hash__
662 __hash__ = object.__hash__
668
663
669 def __eq__(self, other):
664 def __eq__(self, other):
670 try:
665 try:
671 # Only dev, ino, size, mtime and atime are likely to change. Out
666 # Only dev, ino, size, mtime and atime are likely to change. Out
672 # of these, we shouldn't compare atime but should compare the
667 # of these, we shouldn't compare atime but should compare the
673 # rest. However, one of the other fields changing indicates
668 # rest. However, one of the other fields changing indicates
674 # something fishy going on, so return False if anything but atime
669 # something fishy going on, so return False if anything but atime
675 # changes.
670 # changes.
676 return (
671 return (
677 self.stat.st_mode == other.stat.st_mode
672 self.stat.st_mode == other.stat.st_mode
678 and self.stat.st_ino == other.stat.st_ino
673 and self.stat.st_ino == other.stat.st_ino
679 and self.stat.st_dev == other.stat.st_dev
674 and self.stat.st_dev == other.stat.st_dev
680 and self.stat.st_nlink == other.stat.st_nlink
675 and self.stat.st_nlink == other.stat.st_nlink
681 and self.stat.st_uid == other.stat.st_uid
676 and self.stat.st_uid == other.stat.st_uid
682 and self.stat.st_gid == other.stat.st_gid
677 and self.stat.st_gid == other.stat.st_gid
683 and self.stat.st_size == other.stat.st_size
678 and self.stat.st_size == other.stat.st_size
684 and self.stat[stat.ST_MTIME] == other.stat[stat.ST_MTIME]
679 and self.stat[stat.ST_MTIME] == other.stat[stat.ST_MTIME]
685 and self.stat[stat.ST_CTIME] == other.stat[stat.ST_CTIME]
680 and self.stat[stat.ST_CTIME] == other.stat[stat.ST_CTIME]
686 )
681 )
687 except AttributeError:
682 except AttributeError:
688 return False
683 return False
689
684
690 def __ne__(self, other):
685 def __ne__(self, other):
691 return not self == other
686 return not self == other
692
687
693
688
694 def statislink(st):
689 def statislink(st):
695 '''check whether a stat result is a symlink'''
690 '''check whether a stat result is a symlink'''
696 return st and stat.S_ISLNK(st.st_mode)
691 return st and stat.S_ISLNK(st.st_mode)
697
692
698
693
699 def statisexec(st):
694 def statisexec(st):
700 '''check whether a stat result is an executable file'''
695 '''check whether a stat result is an executable file'''
701 return st and (st.st_mode & 0o100 != 0)
696 return st and (st.st_mode & 0o100 != 0)
702
697
703
698
704 def poll(fds):
699 def poll(fds):
705 """block until something happens on any file descriptor
700 """block until something happens on any file descriptor
706
701
707 This is a generic helper that will check for any activity
702 This is a generic helper that will check for any activity
708 (read, write. exception) and return the list of touched files.
703 (read, write. exception) and return the list of touched files.
709
704
710 In unsupported cases, it will raise a NotImplementedError"""
705 In unsupported cases, it will raise a NotImplementedError"""
711 try:
706 try:
712 res = select.select(fds, fds, fds)
707 res = select.select(fds, fds, fds)
713 except ValueError: # out of range file descriptor
708 except ValueError: # out of range file descriptor
714 raise NotImplementedError()
709 raise NotImplementedError()
715 return sorted(list(set(sum(res, []))))
710 return sorted(list(set(sum(res, []))))
716
711
717
712
718 def readpipe(pipe):
713 def readpipe(pipe):
719 """Read all available data from a pipe."""
714 """Read all available data from a pipe."""
720 # We can't fstat() a pipe because Linux will always report 0.
715 # We can't fstat() a pipe because Linux will always report 0.
721 # So, we set the pipe to non-blocking mode and read everything
716 # So, we set the pipe to non-blocking mode and read everything
722 # that's available.
717 # that's available.
723 flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
718 flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
724 flags |= os.O_NONBLOCK
719 flags |= os.O_NONBLOCK
725 oldflags = fcntl.fcntl(pipe, fcntl.F_SETFL, flags)
720 oldflags = fcntl.fcntl(pipe, fcntl.F_SETFL, flags)
726
721
727 try:
722 try:
728 chunks = []
723 chunks = []
729 while True:
724 while True:
730 try:
725 try:
731 s = pipe.read()
726 s = pipe.read()
732 if not s:
727 if not s:
733 break
728 break
734 chunks.append(s)
729 chunks.append(s)
735 except IOError:
730 except IOError:
736 break
731 break
737
732
738 return b''.join(chunks)
733 return b''.join(chunks)
739 finally:
734 finally:
740 fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
735 fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
741
736
742
737
743 def bindunixsocket(sock, path):
738 def bindunixsocket(sock, path):
744 """Bind the UNIX domain socket to the specified path"""
739 """Bind the UNIX domain socket to the specified path"""
745 # use relative path instead of full path at bind() if possible, since
740 # use relative path instead of full path at bind() if possible, since
746 # AF_UNIX path has very small length limit (107 chars) on common
741 # AF_UNIX path has very small length limit (107 chars) on common
747 # platforms (see sys/un.h)
742 # platforms (see sys/un.h)
748 dirname, basename = os.path.split(path)
743 dirname, basename = os.path.split(path)
749 bakwdfd = None
744 bakwdfd = None
750
745
751 try:
746 try:
752 if dirname:
747 if dirname:
753 bakwdfd = os.open(b'.', os.O_DIRECTORY)
748 bakwdfd = os.open(b'.', os.O_DIRECTORY)
754 os.chdir(dirname)
749 os.chdir(dirname)
755 sock.bind(basename)
750 sock.bind(basename)
756 if bakwdfd:
751 if bakwdfd:
757 os.fchdir(bakwdfd)
752 os.fchdir(bakwdfd)
758 finally:
753 finally:
759 if bakwdfd:
754 if bakwdfd:
760 os.close(bakwdfd)
755 os.close(bakwdfd)
General Comments 0
You need to be logged in to leave comments. Login now