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