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