##// END OF EJS Templates
posix: retry on symlink race in checklink...
Matt Mackall -
r26883:c750ed59 stable
parent child Browse files
Show More
@@ -1,649 +1,653 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 while True:
174 try:
174 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
175 fd = tempfile.NamedTemporaryFile(dir=path, prefix='hg-checklink-')
176 try:
175 try:
177 os.symlink(os.path.basename(fd.name), name)
176 fd = tempfile.NamedTemporaryFile(dir=path, prefix='hg-checklink-')
178 os.unlink(name)
177 try:
179 return True
178 os.symlink(os.path.basename(fd.name), name)
180 finally:
179 os.unlink(name)
181 fd.close()
180 return True
182 except AttributeError:
181 except OSError as inst:
183 return False
182 # link creation might race, try again
184 except OSError as inst:
183 if inst[0] == errno.EEXIST:
185 # sshfs might report failure while successfully creating the link
184 continue
186 if inst[0] == errno.EIO and os.path.exists(name):
185 # sshfs might report failure while successfully creating the link
187 os.unlink(name)
186 if inst[0] == errno.EIO and os.path.exists(name):
188 return False
187 os.unlink(name)
188 return False
189 finally:
190 fd.close()
191 except AttributeError:
192 return False
189
193
190 def checkosfilename(path):
194 def checkosfilename(path):
191 '''Check that the base-relative path is a valid filename on this platform.
195 '''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.'''
196 Returns None if the path is ok, or a UI string describing the problem.'''
193 pass # on posix platforms, every path is ok
197 pass # on posix platforms, every path is ok
194
198
195 def setbinary(fd):
199 def setbinary(fd):
196 pass
200 pass
197
201
198 def pconvert(path):
202 def pconvert(path):
199 return path
203 return path
200
204
201 def localpath(path):
205 def localpath(path):
202 return path
206 return path
203
207
204 def samefile(fpath1, fpath2):
208 def samefile(fpath1, fpath2):
205 """Returns whether path1 and path2 refer to the same file. This is only
209 """Returns whether path1 and path2 refer to the same file. This is only
206 guaranteed to work for files, not directories."""
210 guaranteed to work for files, not directories."""
207 return os.path.samefile(fpath1, fpath2)
211 return os.path.samefile(fpath1, fpath2)
208
212
209 def samedevice(fpath1, fpath2):
213 def samedevice(fpath1, fpath2):
210 """Returns whether fpath1 and fpath2 are on the same device. This is only
214 """Returns whether fpath1 and fpath2 are on the same device. This is only
211 guaranteed to work for files, not directories."""
215 guaranteed to work for files, not directories."""
212 st1 = os.lstat(fpath1)
216 st1 = os.lstat(fpath1)
213 st2 = os.lstat(fpath2)
217 st2 = os.lstat(fpath2)
214 return st1.st_dev == st2.st_dev
218 return st1.st_dev == st2.st_dev
215
219
216 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
220 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
217 def normcase(path):
221 def normcase(path):
218 return path.lower()
222 return path.lower()
219
223
220 # what normcase does to ASCII strings
224 # what normcase does to ASCII strings
221 normcasespec = encoding.normcasespecs.lower
225 normcasespec = encoding.normcasespecs.lower
222 # fallback normcase function for non-ASCII strings
226 # fallback normcase function for non-ASCII strings
223 normcasefallback = normcase
227 normcasefallback = normcase
224
228
225 if sys.platform == 'darwin':
229 if sys.platform == 'darwin':
226
230
227 def normcase(path):
231 def normcase(path):
228 '''
232 '''
229 Normalize a filename for OS X-compatible comparison:
233 Normalize a filename for OS X-compatible comparison:
230 - escape-encode invalid characters
234 - escape-encode invalid characters
231 - decompose to NFD
235 - decompose to NFD
232 - lowercase
236 - lowercase
233 - omit ignored characters [200c-200f, 202a-202e, 206a-206f,feff]
237 - omit ignored characters [200c-200f, 202a-202e, 206a-206f,feff]
234
238
235 >>> normcase('UPPER')
239 >>> normcase('UPPER')
236 'upper'
240 'upper'
237 >>> normcase('Caf\xc3\xa9')
241 >>> normcase('Caf\xc3\xa9')
238 'cafe\\xcc\\x81'
242 'cafe\\xcc\\x81'
239 >>> normcase('\xc3\x89')
243 >>> normcase('\xc3\x89')
240 'e\\xcc\\x81'
244 'e\\xcc\\x81'
241 >>> normcase('\xb8\xca\xc3\xca\xbe\xc8.JPG') # issue3918
245 >>> normcase('\xb8\xca\xc3\xca\xbe\xc8.JPG') # issue3918
242 '%b8%ca%c3\\xca\\xbe%c8.jpg'
246 '%b8%ca%c3\\xca\\xbe%c8.jpg'
243 '''
247 '''
244
248
245 try:
249 try:
246 return encoding.asciilower(path) # exception for non-ASCII
250 return encoding.asciilower(path) # exception for non-ASCII
247 except UnicodeDecodeError:
251 except UnicodeDecodeError:
248 return normcasefallback(path)
252 return normcasefallback(path)
249
253
250 normcasespec = encoding.normcasespecs.lower
254 normcasespec = encoding.normcasespecs.lower
251
255
252 def normcasefallback(path):
256 def normcasefallback(path):
253 try:
257 try:
254 u = path.decode('utf-8')
258 u = path.decode('utf-8')
255 except UnicodeDecodeError:
259 except UnicodeDecodeError:
256 # OS X percent-encodes any bytes that aren't valid utf-8
260 # OS X percent-encodes any bytes that aren't valid utf-8
257 s = ''
261 s = ''
258 g = ''
262 g = ''
259 l = 0
263 l = 0
260 for c in path:
264 for c in path:
261 o = ord(c)
265 o = ord(c)
262 if l and o < 128 or o >= 192:
266 if l and o < 128 or o >= 192:
263 # we want a continuation byte, but didn't get one
267 # we want a continuation byte, but didn't get one
264 s += ''.join(["%%%02X" % ord(x) for x in g])
268 s += ''.join(["%%%02X" % ord(x) for x in g])
265 g = ''
269 g = ''
266 l = 0
270 l = 0
267 if l == 0 and o < 128:
271 if l == 0 and o < 128:
268 # ascii
272 # ascii
269 s += c
273 s += c
270 elif l == 0 and 194 <= o < 245:
274 elif l == 0 and 194 <= o < 245:
271 # valid leading bytes
275 # valid leading bytes
272 if o < 224:
276 if o < 224:
273 l = 1
277 l = 1
274 elif o < 240:
278 elif o < 240:
275 l = 2
279 l = 2
276 else:
280 else:
277 l = 3
281 l = 3
278 g = c
282 g = c
279 elif l > 0 and 128 <= o < 192:
283 elif l > 0 and 128 <= o < 192:
280 # valid continuations
284 # valid continuations
281 g += c
285 g += c
282 l -= 1
286 l -= 1
283 if not l:
287 if not l:
284 s += g
288 s += g
285 g = ''
289 g = ''
286 else:
290 else:
287 # invalid
291 # invalid
288 s += "%%%02X" % o
292 s += "%%%02X" % o
289
293
290 # any remaining partial characters
294 # any remaining partial characters
291 s += ''.join(["%%%02X" % ord(x) for x in g])
295 s += ''.join(["%%%02X" % ord(x) for x in g])
292 u = s.decode('utf-8')
296 u = s.decode('utf-8')
293
297
294 # Decompose then lowercase (HFS+ technote specifies lower)
298 # Decompose then lowercase (HFS+ technote specifies lower)
295 enc = unicodedata.normalize('NFD', u).lower().encode('utf-8')
299 enc = unicodedata.normalize('NFD', u).lower().encode('utf-8')
296 # drop HFS+ ignored characters
300 # drop HFS+ ignored characters
297 return encoding.hfsignoreclean(enc)
301 return encoding.hfsignoreclean(enc)
298
302
299 if sys.platform == 'cygwin':
303 if sys.platform == 'cygwin':
300 # workaround for cygwin, in which mount point part of path is
304 # workaround for cygwin, in which mount point part of path is
301 # treated as case sensitive, even though underlying NTFS is case
305 # treated as case sensitive, even though underlying NTFS is case
302 # insensitive.
306 # insensitive.
303
307
304 # default mount points
308 # default mount points
305 cygwinmountpoints = sorted([
309 cygwinmountpoints = sorted([
306 "/usr/bin",
310 "/usr/bin",
307 "/usr/lib",
311 "/usr/lib",
308 "/cygdrive",
312 "/cygdrive",
309 ], reverse=True)
313 ], reverse=True)
310
314
311 # use upper-ing as normcase as same as NTFS workaround
315 # use upper-ing as normcase as same as NTFS workaround
312 def normcase(path):
316 def normcase(path):
313 pathlen = len(path)
317 pathlen = len(path)
314 if (pathlen == 0) or (path[0] != os.sep):
318 if (pathlen == 0) or (path[0] != os.sep):
315 # treat as relative
319 # treat as relative
316 return encoding.upper(path)
320 return encoding.upper(path)
317
321
318 # to preserve case of mountpoint part
322 # to preserve case of mountpoint part
319 for mp in cygwinmountpoints:
323 for mp in cygwinmountpoints:
320 if not path.startswith(mp):
324 if not path.startswith(mp):
321 continue
325 continue
322
326
323 mplen = len(mp)
327 mplen = len(mp)
324 if mplen == pathlen: # mount point itself
328 if mplen == pathlen: # mount point itself
325 return mp
329 return mp
326 if path[mplen] == os.sep:
330 if path[mplen] == os.sep:
327 return mp + encoding.upper(path[mplen:])
331 return mp + encoding.upper(path[mplen:])
328
332
329 return encoding.upper(path)
333 return encoding.upper(path)
330
334
331 normcasespec = encoding.normcasespecs.other
335 normcasespec = encoding.normcasespecs.other
332 normcasefallback = normcase
336 normcasefallback = normcase
333
337
334 # Cygwin translates native ACLs to POSIX permissions,
338 # Cygwin translates native ACLs to POSIX permissions,
335 # but these translations are not supported by native
339 # but these translations are not supported by native
336 # tools, so the exec bit tends to be set erroneously.
340 # tools, so the exec bit tends to be set erroneously.
337 # Therefore, disable executable bit access on Cygwin.
341 # Therefore, disable executable bit access on Cygwin.
338 def checkexec(path):
342 def checkexec(path):
339 return False
343 return False
340
344
341 # Similarly, Cygwin's symlink emulation is likely to create
345 # Similarly, Cygwin's symlink emulation is likely to create
342 # problems when Mercurial is used from both Cygwin and native
346 # problems when Mercurial is used from both Cygwin and native
343 # Windows, with other native tools, or on shared volumes
347 # Windows, with other native tools, or on shared volumes
344 def checklink(path):
348 def checklink(path):
345 return False
349 return False
346
350
347 _needsshellquote = None
351 _needsshellquote = None
348 def shellquote(s):
352 def shellquote(s):
349 if os.sys.platform == 'OpenVMS':
353 if os.sys.platform == 'OpenVMS':
350 return '"%s"' % s
354 return '"%s"' % s
351 global _needsshellquote
355 global _needsshellquote
352 if _needsshellquote is None:
356 if _needsshellquote is None:
353 _needsshellquote = re.compile(r'[^a-zA-Z0-9._/+-]').search
357 _needsshellquote = re.compile(r'[^a-zA-Z0-9._/+-]').search
354 if s and not _needsshellquote(s):
358 if s and not _needsshellquote(s):
355 # "s" shouldn't have to be quoted
359 # "s" shouldn't have to be quoted
356 return s
360 return s
357 else:
361 else:
358 return "'%s'" % s.replace("'", "'\\''")
362 return "'%s'" % s.replace("'", "'\\''")
359
363
360 def quotecommand(cmd):
364 def quotecommand(cmd):
361 return cmd
365 return cmd
362
366
363 def popen(command, mode='r'):
367 def popen(command, mode='r'):
364 return os.popen(command, mode)
368 return os.popen(command, mode)
365
369
366 def testpid(pid):
370 def testpid(pid):
367 '''return False if pid dead, True if running or not sure'''
371 '''return False if pid dead, True if running or not sure'''
368 if os.sys.platform == 'OpenVMS':
372 if os.sys.platform == 'OpenVMS':
369 return True
373 return True
370 try:
374 try:
371 os.kill(pid, 0)
375 os.kill(pid, 0)
372 return True
376 return True
373 except OSError as inst:
377 except OSError as inst:
374 return inst.errno != errno.ESRCH
378 return inst.errno != errno.ESRCH
375
379
376 def explainexit(code):
380 def explainexit(code):
377 """return a 2-tuple (desc, code) describing a subprocess status
381 """return a 2-tuple (desc, code) describing a subprocess status
378 (codes from kill are negative - not os.system/wait encoding)"""
382 (codes from kill are negative - not os.system/wait encoding)"""
379 if code >= 0:
383 if code >= 0:
380 return _("exited with status %d") % code, code
384 return _("exited with status %d") % code, code
381 return _("killed by signal %d") % -code, -code
385 return _("killed by signal %d") % -code, -code
382
386
383 def isowner(st):
387 def isowner(st):
384 """Return True if the stat object st is from the current user."""
388 """Return True if the stat object st is from the current user."""
385 return st.st_uid == os.getuid()
389 return st.st_uid == os.getuid()
386
390
387 def findexe(command):
391 def findexe(command):
388 '''Find executable for command searching like which does.
392 '''Find executable for command searching like which does.
389 If command is a basename then PATH is searched for command.
393 If command is a basename then PATH is searched for command.
390 PATH isn't searched if command is an absolute or relative path.
394 PATH isn't searched if command is an absolute or relative path.
391 If command isn't found None is returned.'''
395 If command isn't found None is returned.'''
392 if sys.platform == 'OpenVMS':
396 if sys.platform == 'OpenVMS':
393 return command
397 return command
394
398
395 def findexisting(executable):
399 def findexisting(executable):
396 'Will return executable if existing file'
400 'Will return executable if existing file'
397 if os.path.isfile(executable) and os.access(executable, os.X_OK):
401 if os.path.isfile(executable) and os.access(executable, os.X_OK):
398 return executable
402 return executable
399 return None
403 return None
400
404
401 if os.sep in command:
405 if os.sep in command:
402 return findexisting(command)
406 return findexisting(command)
403
407
404 if sys.platform == 'plan9':
408 if sys.platform == 'plan9':
405 return findexisting(os.path.join('/bin', command))
409 return findexisting(os.path.join('/bin', command))
406
410
407 for path in os.environ.get('PATH', '').split(os.pathsep):
411 for path in os.environ.get('PATH', '').split(os.pathsep):
408 executable = findexisting(os.path.join(path, command))
412 executable = findexisting(os.path.join(path, command))
409 if executable is not None:
413 if executable is not None:
410 return executable
414 return executable
411 return None
415 return None
412
416
413 def setsignalhandler():
417 def setsignalhandler():
414 pass
418 pass
415
419
416 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
420 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
417
421
418 def statfiles(files):
422 def statfiles(files):
419 '''Stat each file in files. Yield each stat, or None if a file does not
423 '''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.'''
424 exist or has a type we don't care about.'''
421 lstat = os.lstat
425 lstat = os.lstat
422 getkind = stat.S_IFMT
426 getkind = stat.S_IFMT
423 for nf in files:
427 for nf in files:
424 try:
428 try:
425 st = lstat(nf)
429 st = lstat(nf)
426 if getkind(st.st_mode) not in _wantedkinds:
430 if getkind(st.st_mode) not in _wantedkinds:
427 st = None
431 st = None
428 except OSError as err:
432 except OSError as err:
429 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
433 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
430 raise
434 raise
431 st = None
435 st = None
432 yield st
436 yield st
433
437
434 def getuser():
438 def getuser():
435 '''return name of current user'''
439 '''return name of current user'''
436 return getpass.getuser()
440 return getpass.getuser()
437
441
438 def username(uid=None):
442 def username(uid=None):
439 """Return the name of the user with the given uid.
443 """Return the name of the user with the given uid.
440
444
441 If uid is None, return the name of the current user."""
445 If uid is None, return the name of the current user."""
442
446
443 if uid is None:
447 if uid is None:
444 uid = os.getuid()
448 uid = os.getuid()
445 try:
449 try:
446 return pwd.getpwuid(uid)[0]
450 return pwd.getpwuid(uid)[0]
447 except KeyError:
451 except KeyError:
448 return str(uid)
452 return str(uid)
449
453
450 def groupname(gid=None):
454 def groupname(gid=None):
451 """Return the name of the group with the given gid.
455 """Return the name of the group with the given gid.
452
456
453 If gid is None, return the name of the current group."""
457 If gid is None, return the name of the current group."""
454
458
455 if gid is None:
459 if gid is None:
456 gid = os.getgid()
460 gid = os.getgid()
457 try:
461 try:
458 return grp.getgrgid(gid)[0]
462 return grp.getgrgid(gid)[0]
459 except KeyError:
463 except KeyError:
460 return str(gid)
464 return str(gid)
461
465
462 def groupmembers(name):
466 def groupmembers(name):
463 """Return the list of members of the group with the given
467 """Return the list of members of the group with the given
464 name, KeyError if the group does not exist.
468 name, KeyError if the group does not exist.
465 """
469 """
466 return list(grp.getgrnam(name).gr_mem)
470 return list(grp.getgrnam(name).gr_mem)
467
471
468 def spawndetached(args):
472 def spawndetached(args):
469 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
473 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
470 args[0], args)
474 args[0], args)
471
475
472 def gethgcmd():
476 def gethgcmd():
473 return sys.argv[:1]
477 return sys.argv[:1]
474
478
475 def termwidth():
479 def termwidth():
476 try:
480 try:
477 import array
481 import array
478 import termios
482 import termios
479 for dev in (sys.stderr, sys.stdout, sys.stdin):
483 for dev in (sys.stderr, sys.stdout, sys.stdin):
480 try:
484 try:
481 try:
485 try:
482 fd = dev.fileno()
486 fd = dev.fileno()
483 except AttributeError:
487 except AttributeError:
484 continue
488 continue
485 if not os.isatty(fd):
489 if not os.isatty(fd):
486 continue
490 continue
487 try:
491 try:
488 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
492 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
489 width = array.array('h', arri)[1]
493 width = array.array('h', arri)[1]
490 if width > 0:
494 if width > 0:
491 return width
495 return width
492 except AttributeError:
496 except AttributeError:
493 pass
497 pass
494 except ValueError:
498 except ValueError:
495 pass
499 pass
496 except IOError as e:
500 except IOError as e:
497 if e[0] == errno.EINVAL:
501 if e[0] == errno.EINVAL:
498 pass
502 pass
499 else:
503 else:
500 raise
504 raise
501 except ImportError:
505 except ImportError:
502 pass
506 pass
503 return 80
507 return 80
504
508
505 def makedir(path, notindexed):
509 def makedir(path, notindexed):
506 os.mkdir(path)
510 os.mkdir(path)
507
511
508 def unlinkpath(f, ignoremissing=False):
512 def unlinkpath(f, ignoremissing=False):
509 """unlink and remove the directory if it is empty"""
513 """unlink and remove the directory if it is empty"""
510 try:
514 try:
511 os.unlink(f)
515 os.unlink(f)
512 except OSError as e:
516 except OSError as e:
513 if not (ignoremissing and e.errno == errno.ENOENT):
517 if not (ignoremissing and e.errno == errno.ENOENT):
514 raise
518 raise
515 # try removing directories that might now be empty
519 # try removing directories that might now be empty
516 try:
520 try:
517 os.removedirs(os.path.dirname(f))
521 os.removedirs(os.path.dirname(f))
518 except OSError:
522 except OSError:
519 pass
523 pass
520
524
521 def lookupreg(key, name=None, scope=None):
525 def lookupreg(key, name=None, scope=None):
522 return None
526 return None
523
527
524 def hidewindow():
528 def hidewindow():
525 """Hide current shell window.
529 """Hide current shell window.
526
530
527 Used to hide the window opened when starting asynchronous
531 Used to hide the window opened when starting asynchronous
528 child process under Windows, unneeded on other systems.
532 child process under Windows, unneeded on other systems.
529 """
533 """
530 pass
534 pass
531
535
532 class cachestat(object):
536 class cachestat(object):
533 def __init__(self, path):
537 def __init__(self, path):
534 self.stat = os.stat(path)
538 self.stat = os.stat(path)
535
539
536 def cacheable(self):
540 def cacheable(self):
537 return bool(self.stat.st_ino)
541 return bool(self.stat.st_ino)
538
542
539 __hash__ = object.__hash__
543 __hash__ = object.__hash__
540
544
541 def __eq__(self, other):
545 def __eq__(self, other):
542 try:
546 try:
543 # Only dev, ino, size, mtime and atime are likely to change. Out
547 # Only dev, ino, size, mtime and atime are likely to change. Out
544 # of these, we shouldn't compare atime but should compare the
548 # of these, we shouldn't compare atime but should compare the
545 # rest. However, one of the other fields changing indicates
549 # rest. However, one of the other fields changing indicates
546 # something fishy going on, so return False if anything but atime
550 # something fishy going on, so return False if anything but atime
547 # changes.
551 # changes.
548 return (self.stat.st_mode == other.stat.st_mode and
552 return (self.stat.st_mode == other.stat.st_mode and
549 self.stat.st_ino == other.stat.st_ino and
553 self.stat.st_ino == other.stat.st_ino and
550 self.stat.st_dev == other.stat.st_dev and
554 self.stat.st_dev == other.stat.st_dev and
551 self.stat.st_nlink == other.stat.st_nlink and
555 self.stat.st_nlink == other.stat.st_nlink and
552 self.stat.st_uid == other.stat.st_uid and
556 self.stat.st_uid == other.stat.st_uid and
553 self.stat.st_gid == other.stat.st_gid and
557 self.stat.st_gid == other.stat.st_gid and
554 self.stat.st_size == other.stat.st_size and
558 self.stat.st_size == other.stat.st_size and
555 self.stat.st_mtime == other.stat.st_mtime and
559 self.stat.st_mtime == other.stat.st_mtime and
556 self.stat.st_ctime == other.stat.st_ctime)
560 self.stat.st_ctime == other.stat.st_ctime)
557 except AttributeError:
561 except AttributeError:
558 return False
562 return False
559
563
560 def __ne__(self, other):
564 def __ne__(self, other):
561 return not self == other
565 return not self == other
562
566
563 def executablepath():
567 def executablepath():
564 return None # available on Windows only
568 return None # available on Windows only
565
569
566 class unixdomainserver(socket.socket):
570 class unixdomainserver(socket.socket):
567 def __init__(self, join, subsystem):
571 def __init__(self, join, subsystem):
568 '''Create a unix domain socket with the given prefix.'''
572 '''Create a unix domain socket with the given prefix.'''
569 super(unixdomainserver, self).__init__(socket.AF_UNIX)
573 super(unixdomainserver, self).__init__(socket.AF_UNIX)
570 sockname = subsystem + '.sock'
574 sockname = subsystem + '.sock'
571 self.realpath = self.path = join(sockname)
575 self.realpath = self.path = join(sockname)
572 if os.path.islink(self.path):
576 if os.path.islink(self.path):
573 if os.path.exists(self.path):
577 if os.path.exists(self.path):
574 self.realpath = os.readlink(self.path)
578 self.realpath = os.readlink(self.path)
575 else:
579 else:
576 os.unlink(self.path)
580 os.unlink(self.path)
577 try:
581 try:
578 self.bind(self.realpath)
582 self.bind(self.realpath)
579 except socket.error as err:
583 except socket.error as err:
580 if err.args[0] == 'AF_UNIX path too long':
584 if err.args[0] == 'AF_UNIX path too long':
581 tmpdir = tempfile.mkdtemp(prefix='hg-%s-' % subsystem)
585 tmpdir = tempfile.mkdtemp(prefix='hg-%s-' % subsystem)
582 self.realpath = os.path.join(tmpdir, sockname)
586 self.realpath = os.path.join(tmpdir, sockname)
583 try:
587 try:
584 self.bind(self.realpath)
588 self.bind(self.realpath)
585 os.symlink(self.realpath, self.path)
589 os.symlink(self.realpath, self.path)
586 except (OSError, socket.error):
590 except (OSError, socket.error):
587 self.cleanup()
591 self.cleanup()
588 raise
592 raise
589 else:
593 else:
590 raise
594 raise
591 self.listen(5)
595 self.listen(5)
592
596
593 def cleanup(self):
597 def cleanup(self):
594 def okayifmissing(f, path):
598 def okayifmissing(f, path):
595 try:
599 try:
596 f(path)
600 f(path)
597 except OSError as err:
601 except OSError as err:
598 if err.errno != errno.ENOENT:
602 if err.errno != errno.ENOENT:
599 raise
603 raise
600
604
601 okayifmissing(os.unlink, self.path)
605 okayifmissing(os.unlink, self.path)
602 if self.realpath != self.path:
606 if self.realpath != self.path:
603 okayifmissing(os.unlink, self.realpath)
607 okayifmissing(os.unlink, self.realpath)
604 okayifmissing(os.rmdir, os.path.dirname(self.realpath))
608 okayifmissing(os.rmdir, os.path.dirname(self.realpath))
605
609
606 def statislink(st):
610 def statislink(st):
607 '''check whether a stat result is a symlink'''
611 '''check whether a stat result is a symlink'''
608 return st and stat.S_ISLNK(st.st_mode)
612 return st and stat.S_ISLNK(st.st_mode)
609
613
610 def statisexec(st):
614 def statisexec(st):
611 '''check whether a stat result is an executable file'''
615 '''check whether a stat result is an executable file'''
612 return st and (st.st_mode & 0o100 != 0)
616 return st and (st.st_mode & 0o100 != 0)
613
617
614 def poll(fds):
618 def poll(fds):
615 """block until something happens on any file descriptor
619 """block until something happens on any file descriptor
616
620
617 This is a generic helper that will check for any activity
621 This is a generic helper that will check for any activity
618 (read, write. exception) and return the list of touched files.
622 (read, write. exception) and return the list of touched files.
619
623
620 In unsupported cases, it will raise a NotImplementedError"""
624 In unsupported cases, it will raise a NotImplementedError"""
621 try:
625 try:
622 res = select.select(fds, fds, fds)
626 res = select.select(fds, fds, fds)
623 except ValueError: # out of range file descriptor
627 except ValueError: # out of range file descriptor
624 raise NotImplementedError()
628 raise NotImplementedError()
625 return sorted(list(set(sum(res, []))))
629 return sorted(list(set(sum(res, []))))
626
630
627 def readpipe(pipe):
631 def readpipe(pipe):
628 """Read all available data from a pipe."""
632 """Read all available data from a pipe."""
629 # We can't fstat() a pipe because Linux will always report 0.
633 # 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
634 # So, we set the pipe to non-blocking mode and read everything
631 # that's available.
635 # that's available.
632 flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
636 flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
633 flags |= os.O_NONBLOCK
637 flags |= os.O_NONBLOCK
634 oldflags = fcntl.fcntl(pipe, fcntl.F_SETFL, flags)
638 oldflags = fcntl.fcntl(pipe, fcntl.F_SETFL, flags)
635
639
636 try:
640 try:
637 chunks = []
641 chunks = []
638 while True:
642 while True:
639 try:
643 try:
640 s = pipe.read()
644 s = pipe.read()
641 if not s:
645 if not s:
642 break
646 break
643 chunks.append(s)
647 chunks.append(s)
644 except IOError:
648 except IOError:
645 break
649 break
646
650
647 return ''.join(chunks)
651 return ''.join(chunks)
648 finally:
652 finally:
649 fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
653 fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
General Comments 0
You need to be logged in to leave comments. Login now