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