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