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