##// END OF EJS Templates
posix: add extended support for OS X path folding...
Matt Mackall -
r15551:1fa41d1f stable
parent child Browse files
Show More
@@ -1,396 +1,414 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 i18n import _
8 from i18n import _
9 import os, sys, errno, stat, getpass, pwd, grp, tempfile
9 import os, sys, errno, stat, getpass, pwd, grp, tempfile, unicodedata
10
10
11 posixfile = open
11 posixfile = open
12 nulldev = '/dev/null'
12 nulldev = '/dev/null'
13 normpath = os.path.normpath
13 normpath = os.path.normpath
14 samestat = os.path.samestat
14 samestat = os.path.samestat
15 oslink = os.link
15 oslink = os.link
16 unlink = os.unlink
16 unlink = os.unlink
17 rename = os.rename
17 rename = os.rename
18 expandglobs = False
18 expandglobs = False
19
19
20 umask = os.umask(0)
20 umask = os.umask(0)
21 os.umask(umask)
21 os.umask(umask)
22
22
23 def openhardlinks():
23 def openhardlinks():
24 '''return true if it is safe to hold open file handles to hardlinks'''
24 '''return true if it is safe to hold open file handles to hardlinks'''
25 return True
25 return True
26
26
27 def nlinks(name):
27 def nlinks(name):
28 '''return number of hardlinks for the given file'''
28 '''return number of hardlinks for the given file'''
29 return os.lstat(name).st_nlink
29 return os.lstat(name).st_nlink
30
30
31 def parsepatchoutput(output_line):
31 def parsepatchoutput(output_line):
32 """parses the output produced by patch and returns the filename"""
32 """parses the output produced by patch and returns the filename"""
33 pf = output_line[14:]
33 pf = output_line[14:]
34 if os.sys.platform == 'OpenVMS':
34 if os.sys.platform == 'OpenVMS':
35 if pf[0] == '`':
35 if pf[0] == '`':
36 pf = pf[1:-1] # Remove the quotes
36 pf = pf[1:-1] # Remove the quotes
37 else:
37 else:
38 if pf.startswith("'") and pf.endswith("'") and " " in pf:
38 if pf.startswith("'") and pf.endswith("'") and " " in pf:
39 pf = pf[1:-1] # Remove the quotes
39 pf = pf[1:-1] # Remove the quotes
40 return pf
40 return pf
41
41
42 def sshargs(sshcmd, host, user, port):
42 def sshargs(sshcmd, host, user, port):
43 '''Build argument list for ssh'''
43 '''Build argument list for ssh'''
44 args = user and ("%s@%s" % (user, host)) or host
44 args = user and ("%s@%s" % (user, host)) or host
45 return port and ("%s -p %s" % (args, port)) or args
45 return port and ("%s -p %s" % (args, port)) or args
46
46
47 def isexec(f):
47 def isexec(f):
48 """check whether a file is executable"""
48 """check whether a file is executable"""
49 return (os.lstat(f).st_mode & 0100 != 0)
49 return (os.lstat(f).st_mode & 0100 != 0)
50
50
51 def setflags(f, l, x):
51 def setflags(f, l, x):
52 s = os.lstat(f).st_mode
52 s = os.lstat(f).st_mode
53 if l:
53 if l:
54 if not stat.S_ISLNK(s):
54 if not stat.S_ISLNK(s):
55 # switch file to link
55 # switch file to link
56 fp = open(f)
56 fp = open(f)
57 data = fp.read()
57 data = fp.read()
58 fp.close()
58 fp.close()
59 os.unlink(f)
59 os.unlink(f)
60 try:
60 try:
61 os.symlink(data, f)
61 os.symlink(data, f)
62 except OSError:
62 except OSError:
63 # failed to make a link, rewrite file
63 # failed to make a link, rewrite file
64 fp = open(f, "w")
64 fp = open(f, "w")
65 fp.write(data)
65 fp.write(data)
66 fp.close()
66 fp.close()
67 # no chmod needed at this point
67 # no chmod needed at this point
68 return
68 return
69 if stat.S_ISLNK(s):
69 if stat.S_ISLNK(s):
70 # switch link to file
70 # switch link to file
71 data = os.readlink(f)
71 data = os.readlink(f)
72 os.unlink(f)
72 os.unlink(f)
73 fp = open(f, "w")
73 fp = open(f, "w")
74 fp.write(data)
74 fp.write(data)
75 fp.close()
75 fp.close()
76 s = 0666 & ~umask # avoid restatting for chmod
76 s = 0666 & ~umask # avoid restatting for chmod
77
77
78 sx = s & 0100
78 sx = s & 0100
79 if x and not sx:
79 if x and not sx:
80 # Turn on +x for every +r bit when making a file executable
80 # Turn on +x for every +r bit when making a file executable
81 # and obey umask.
81 # and obey umask.
82 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
82 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
83 elif not x and sx:
83 elif not x and sx:
84 # Turn off all +x bits
84 # Turn off all +x bits
85 os.chmod(f, s & 0666)
85 os.chmod(f, s & 0666)
86
86
87 def copymode(src, dst, mode=None):
87 def copymode(src, dst, mode=None):
88 '''Copy the file mode from the file at path src to dst.
88 '''Copy the file mode from the file at path src to dst.
89 If src doesn't exist, we're using mode instead. If mode is None, we're
89 If src doesn't exist, we're using mode instead. If mode is None, we're
90 using umask.'''
90 using umask.'''
91 try:
91 try:
92 st_mode = os.lstat(src).st_mode & 0777
92 st_mode = os.lstat(src).st_mode & 0777
93 except OSError, inst:
93 except OSError, inst:
94 if inst.errno != errno.ENOENT:
94 if inst.errno != errno.ENOENT:
95 raise
95 raise
96 st_mode = mode
96 st_mode = mode
97 if st_mode is None:
97 if st_mode is None:
98 st_mode = ~umask
98 st_mode = ~umask
99 st_mode &= 0666
99 st_mode &= 0666
100 os.chmod(dst, st_mode)
100 os.chmod(dst, st_mode)
101
101
102 def checkexec(path):
102 def checkexec(path):
103 """
103 """
104 Check whether the given path is on a filesystem with UNIX-like exec flags
104 Check whether the given path is on a filesystem with UNIX-like exec flags
105
105
106 Requires a directory (like /foo/.hg)
106 Requires a directory (like /foo/.hg)
107 """
107 """
108
108
109 # VFAT on some Linux versions can flip mode but it doesn't persist
109 # VFAT on some Linux versions can flip mode but it doesn't persist
110 # a FS remount. Frequently we can detect it if files are created
110 # a FS remount. Frequently we can detect it if files are created
111 # with exec bit on.
111 # with exec bit on.
112
112
113 try:
113 try:
114 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
114 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
115 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
115 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
116 try:
116 try:
117 os.close(fh)
117 os.close(fh)
118 m = os.stat(fn).st_mode & 0777
118 m = os.stat(fn).st_mode & 0777
119 new_file_has_exec = m & EXECFLAGS
119 new_file_has_exec = m & EXECFLAGS
120 os.chmod(fn, m ^ EXECFLAGS)
120 os.chmod(fn, m ^ EXECFLAGS)
121 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
121 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
122 finally:
122 finally:
123 os.unlink(fn)
123 os.unlink(fn)
124 except (IOError, OSError):
124 except (IOError, OSError):
125 # we don't care, the user probably won't be able to commit anyway
125 # we don't care, the user probably won't be able to commit anyway
126 return False
126 return False
127 return not (new_file_has_exec or exec_flags_cannot_flip)
127 return not (new_file_has_exec or exec_flags_cannot_flip)
128
128
129 def checklink(path):
129 def checklink(path):
130 """check whether the given path is on a symlink-capable filesystem"""
130 """check whether the given path is on a symlink-capable filesystem"""
131 # mktemp is not racy because symlink creation will fail if the
131 # mktemp is not racy because symlink creation will fail if the
132 # file already exists
132 # file already exists
133 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
133 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
134 try:
134 try:
135 os.symlink(".", name)
135 os.symlink(".", name)
136 os.unlink(name)
136 os.unlink(name)
137 return True
137 return True
138 except (OSError, AttributeError):
138 except (OSError, AttributeError):
139 return False
139 return False
140
140
141 def checkosfilename(path):
141 def checkosfilename(path):
142 '''Check that the base-relative path is a valid filename on this platform.
142 '''Check that the base-relative path is a valid filename on this platform.
143 Returns None if the path is ok, or a UI string describing the problem.'''
143 Returns None if the path is ok, or a UI string describing the problem.'''
144 pass # on posix platforms, every path is ok
144 pass # on posix platforms, every path is ok
145
145
146 def setbinary(fd):
146 def setbinary(fd):
147 pass
147 pass
148
148
149 def pconvert(path):
149 def pconvert(path):
150 return path
150 return path
151
151
152 def localpath(path):
152 def localpath(path):
153 return path
153 return path
154
154
155 def samefile(fpath1, fpath2):
155 def samefile(fpath1, fpath2):
156 """Returns whether path1 and path2 refer to the same file. This is only
156 """Returns whether path1 and path2 refer to the same file. This is only
157 guaranteed to work for files, not directories."""
157 guaranteed to work for files, not directories."""
158 return os.path.samefile(fpath1, fpath2)
158 return os.path.samefile(fpath1, fpath2)
159
159
160 def samedevice(fpath1, fpath2):
160 def samedevice(fpath1, fpath2):
161 """Returns whether fpath1 and fpath2 are on the same device. This is only
161 """Returns whether fpath1 and fpath2 are on the same device. This is only
162 guaranteed to work for files, not directories."""
162 guaranteed to work for files, not directories."""
163 st1 = os.lstat(fpath1)
163 st1 = os.lstat(fpath1)
164 st2 = os.lstat(fpath2)
164 st2 = os.lstat(fpath2)
165 return st1.st_dev == st2.st_dev
165 return st1.st_dev == st2.st_dev
166
166
167 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
167 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
168 def normcase(path):
168 def normcase(path):
169 return path.lower()
169 return path.lower()
170
170
171 if sys.platform == 'darwin':
171 if sys.platform == 'darwin':
172 import fcntl # only needed on darwin, missing on jython
172 import fcntl # only needed on darwin, missing on jython
173
174 def normcase(path):
175 try:
176 u = path.decode('utf-8')
177 except UnicodeDecodeError:
178 # percent-encode any characters that don't round-trip
179 p2 = path.decode('utf-8', 'replace').encode('utf-8')
180 s = ""
181 for a, b in zip(path, p2):
182 if a != b:
183 s += "%%%02X" % ord(a)
184 else:
185 s += a
186 u = s.decode('utf-8')
187
188 # Decompose then lowercase (HFS+ technote specifies lower)
189 return unicodedata.normalize('NFD', u).lower().encode('utf-8')
190
173 def realpath(path):
191 def realpath(path):
174 '''
192 '''
175 Returns the true, canonical file system path equivalent to the given
193 Returns the true, canonical file system path equivalent to the given
176 path.
194 path.
177
195
178 Equivalent means, in this case, resulting in the same, unique
196 Equivalent means, in this case, resulting in the same, unique
179 file system link to the path. Every file system entry, whether a file,
197 file system link to the path. Every file system entry, whether a file,
180 directory, hard link or symbolic link or special, will have a single
198 directory, hard link or symbolic link or special, will have a single
181 path preferred by the system, but may allow multiple, differing path
199 path preferred by the system, but may allow multiple, differing path
182 lookups to point to it.
200 lookups to point to it.
183
201
184 Most regular UNIX file systems only allow a file system entry to be
202 Most regular UNIX file systems only allow a file system entry to be
185 looked up by its distinct path. Obviously, this does not apply to case
203 looked up by its distinct path. Obviously, this does not apply to case
186 insensitive file systems, whether case preserving or not. The most
204 insensitive file systems, whether case preserving or not. The most
187 complex issue to deal with is file systems transparently reencoding the
205 complex issue to deal with is file systems transparently reencoding the
188 path, such as the non-standard Unicode normalisation required for HFS+
206 path, such as the non-standard Unicode normalisation required for HFS+
189 and HFSX.
207 and HFSX.
190 '''
208 '''
191 # Constants copied from /usr/include/sys/fcntl.h
209 # Constants copied from /usr/include/sys/fcntl.h
192 F_GETPATH = 50
210 F_GETPATH = 50
193 O_SYMLINK = 0x200000
211 O_SYMLINK = 0x200000
194
212
195 try:
213 try:
196 fd = os.open(path, O_SYMLINK)
214 fd = os.open(path, O_SYMLINK)
197 except OSError, err:
215 except OSError, err:
198 if err.errno == errno.ENOENT:
216 if err.errno == errno.ENOENT:
199 return path
217 return path
200 raise
218 raise
201
219
202 try:
220 try:
203 return fcntl.fcntl(fd, F_GETPATH, '\0' * 1024).rstrip('\0')
221 return fcntl.fcntl(fd, F_GETPATH, '\0' * 1024).rstrip('\0')
204 finally:
222 finally:
205 os.close(fd)
223 os.close(fd)
206 elif sys.version_info < (2, 4, 2, 'final'):
224 elif sys.version_info < (2, 4, 2, 'final'):
207 # Workaround for http://bugs.python.org/issue1213894 (os.path.realpath
225 # Workaround for http://bugs.python.org/issue1213894 (os.path.realpath
208 # didn't resolve symlinks that were the first component of the path.)
226 # didn't resolve symlinks that were the first component of the path.)
209 def realpath(path):
227 def realpath(path):
210 if os.path.isabs(path):
228 if os.path.isabs(path):
211 return os.path.realpath(path)
229 return os.path.realpath(path)
212 else:
230 else:
213 return os.path.realpath('./' + path)
231 return os.path.realpath('./' + path)
214 else:
232 else:
215 # Fallback to the likely inadequate Python builtin function.
233 # Fallback to the likely inadequate Python builtin function.
216 realpath = os.path.realpath
234 realpath = os.path.realpath
217
235
218 def shellquote(s):
236 def shellquote(s):
219 if os.sys.platform == 'OpenVMS':
237 if os.sys.platform == 'OpenVMS':
220 return '"%s"' % s
238 return '"%s"' % s
221 else:
239 else:
222 return "'%s'" % s.replace("'", "'\\''")
240 return "'%s'" % s.replace("'", "'\\''")
223
241
224 def quotecommand(cmd):
242 def quotecommand(cmd):
225 return cmd
243 return cmd
226
244
227 def popen(command, mode='r'):
245 def popen(command, mode='r'):
228 return os.popen(command, mode)
246 return os.popen(command, mode)
229
247
230 def testpid(pid):
248 def testpid(pid):
231 '''return False if pid dead, True if running or not sure'''
249 '''return False if pid dead, True if running or not sure'''
232 if os.sys.platform == 'OpenVMS':
250 if os.sys.platform == 'OpenVMS':
233 return True
251 return True
234 try:
252 try:
235 os.kill(pid, 0)
253 os.kill(pid, 0)
236 return True
254 return True
237 except OSError, inst:
255 except OSError, inst:
238 return inst.errno != errno.ESRCH
256 return inst.errno != errno.ESRCH
239
257
240 def explainexit(code):
258 def explainexit(code):
241 """return a 2-tuple (desc, code) describing a subprocess status
259 """return a 2-tuple (desc, code) describing a subprocess status
242 (codes from kill are negative - not os.system/wait encoding)"""
260 (codes from kill are negative - not os.system/wait encoding)"""
243 if code >= 0:
261 if code >= 0:
244 return _("exited with status %d") % code, code
262 return _("exited with status %d") % code, code
245 return _("killed by signal %d") % -code, -code
263 return _("killed by signal %d") % -code, -code
246
264
247 def isowner(st):
265 def isowner(st):
248 """Return True if the stat object st is from the current user."""
266 """Return True if the stat object st is from the current user."""
249 return st.st_uid == os.getuid()
267 return st.st_uid == os.getuid()
250
268
251 def findexe(command):
269 def findexe(command):
252 '''Find executable for command searching like which does.
270 '''Find executable for command searching like which does.
253 If command is a basename then PATH is searched for command.
271 If command is a basename then PATH is searched for command.
254 PATH isn't searched if command is an absolute or relative path.
272 PATH isn't searched if command is an absolute or relative path.
255 If command isn't found None is returned.'''
273 If command isn't found None is returned.'''
256 if sys.platform == 'OpenVMS':
274 if sys.platform == 'OpenVMS':
257 return command
275 return command
258
276
259 def findexisting(executable):
277 def findexisting(executable):
260 'Will return executable if existing file'
278 'Will return executable if existing file'
261 if os.path.isfile(executable) and os.access(executable, os.X_OK):
279 if os.path.isfile(executable) and os.access(executable, os.X_OK):
262 return executable
280 return executable
263 return None
281 return None
264
282
265 if os.sep in command:
283 if os.sep in command:
266 return findexisting(command)
284 return findexisting(command)
267
285
268 for path in os.environ.get('PATH', '').split(os.pathsep):
286 for path in os.environ.get('PATH', '').split(os.pathsep):
269 executable = findexisting(os.path.join(path, command))
287 executable = findexisting(os.path.join(path, command))
270 if executable is not None:
288 if executable is not None:
271 return executable
289 return executable
272 return None
290 return None
273
291
274 def setsignalhandler():
292 def setsignalhandler():
275 pass
293 pass
276
294
277 def statfiles(files):
295 def statfiles(files):
278 'Stat each file in files and yield stat or None if file does not exist.'
296 'Stat each file in files and yield stat or None if file does not exist.'
279 lstat = os.lstat
297 lstat = os.lstat
280 for nf in files:
298 for nf in files:
281 try:
299 try:
282 st = lstat(nf)
300 st = lstat(nf)
283 except OSError, err:
301 except OSError, err:
284 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
302 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
285 raise
303 raise
286 st = None
304 st = None
287 yield st
305 yield st
288
306
289 def getuser():
307 def getuser():
290 '''return name of current user'''
308 '''return name of current user'''
291 return getpass.getuser()
309 return getpass.getuser()
292
310
293 def username(uid=None):
311 def username(uid=None):
294 """Return the name of the user with the given uid.
312 """Return the name of the user with the given uid.
295
313
296 If uid is None, return the name of the current user."""
314 If uid is None, return the name of the current user."""
297
315
298 if uid is None:
316 if uid is None:
299 uid = os.getuid()
317 uid = os.getuid()
300 try:
318 try:
301 return pwd.getpwuid(uid)[0]
319 return pwd.getpwuid(uid)[0]
302 except KeyError:
320 except KeyError:
303 return str(uid)
321 return str(uid)
304
322
305 def groupname(gid=None):
323 def groupname(gid=None):
306 """Return the name of the group with the given gid.
324 """Return the name of the group with the given gid.
307
325
308 If gid is None, return the name of the current group."""
326 If gid is None, return the name of the current group."""
309
327
310 if gid is None:
328 if gid is None:
311 gid = os.getgid()
329 gid = os.getgid()
312 try:
330 try:
313 return grp.getgrgid(gid)[0]
331 return grp.getgrgid(gid)[0]
314 except KeyError:
332 except KeyError:
315 return str(gid)
333 return str(gid)
316
334
317 def groupmembers(name):
335 def groupmembers(name):
318 """Return the list of members of the group with the given
336 """Return the list of members of the group with the given
319 name, KeyError if the group does not exist.
337 name, KeyError if the group does not exist.
320 """
338 """
321 return list(grp.getgrnam(name).gr_mem)
339 return list(grp.getgrnam(name).gr_mem)
322
340
323 def spawndetached(args):
341 def spawndetached(args):
324 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
342 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
325 args[0], args)
343 args[0], args)
326
344
327 def gethgcmd():
345 def gethgcmd():
328 return sys.argv[:1]
346 return sys.argv[:1]
329
347
330 def termwidth():
348 def termwidth():
331 try:
349 try:
332 import termios, array, fcntl
350 import termios, array, fcntl
333 for dev in (sys.stderr, sys.stdout, sys.stdin):
351 for dev in (sys.stderr, sys.stdout, sys.stdin):
334 try:
352 try:
335 try:
353 try:
336 fd = dev.fileno()
354 fd = dev.fileno()
337 except AttributeError:
355 except AttributeError:
338 continue
356 continue
339 if not os.isatty(fd):
357 if not os.isatty(fd):
340 continue
358 continue
341 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
359 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
342 width = array.array('h', arri)[1]
360 width = array.array('h', arri)[1]
343 if width > 0:
361 if width > 0:
344 return width
362 return width
345 except ValueError:
363 except ValueError:
346 pass
364 pass
347 except IOError, e:
365 except IOError, e:
348 if e[0] == errno.EINVAL:
366 if e[0] == errno.EINVAL:
349 pass
367 pass
350 else:
368 else:
351 raise
369 raise
352 except ImportError:
370 except ImportError:
353 pass
371 pass
354 return 80
372 return 80
355
373
356 def makedir(path, notindexed):
374 def makedir(path, notindexed):
357 os.mkdir(path)
375 os.mkdir(path)
358
376
359 def unlinkpath(f):
377 def unlinkpath(f):
360 """unlink and remove the directory if it is empty"""
378 """unlink and remove the directory if it is empty"""
361 os.unlink(f)
379 os.unlink(f)
362 # try removing directories that might now be empty
380 # try removing directories that might now be empty
363 try:
381 try:
364 os.removedirs(os.path.dirname(f))
382 os.removedirs(os.path.dirname(f))
365 except OSError:
383 except OSError:
366 pass
384 pass
367
385
368 def lookupreg(key, name=None, scope=None):
386 def lookupreg(key, name=None, scope=None):
369 return None
387 return None
370
388
371 def hidewindow():
389 def hidewindow():
372 """Hide current shell window.
390 """Hide current shell window.
373
391
374 Used to hide the window opened when starting asynchronous
392 Used to hide the window opened when starting asynchronous
375 child process under Windows, unneeded on other systems.
393 child process under Windows, unneeded on other systems.
376 """
394 """
377 pass
395 pass
378
396
379 class cachestat(object):
397 class cachestat(object):
380 def __init__(self, path):
398 def __init__(self, path):
381 self.stat = os.stat(path)
399 self.stat = os.stat(path)
382
400
383 def cacheable(self):
401 def cacheable(self):
384 return bool(self.stat.st_ino)
402 return bool(self.stat.st_ino)
385
403
386 def __eq__(self, other):
404 def __eq__(self, other):
387 try:
405 try:
388 return self.stat == other.stat
406 return self.stat == other.stat
389 except AttributeError:
407 except AttributeError:
390 return False
408 return False
391
409
392 def __ne__(self, other):
410 def __ne__(self, other):
393 return not self == other
411 return not self == other
394
412
395 def executablepath():
413 def executablepath():
396 return None # available on Windows only
414 return None # available on Windows only
General Comments 0
You need to be logged in to leave comments. Login now