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