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