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