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