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