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