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