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