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