##// END OF EJS Templates
posix: move checklink test file to .hg/cache...
Mads Kiilerich -
r30447:0d87b1ca default
parent child Browse files
Show More
@@ -1,625 +1,632
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 cachedir = os.path.join(path, '.hg', 'cache')
164 164 if os.path.isdir(cachedir):
165 165 checkisexec = os.path.join(cachedir, 'checkisexec')
166 166 checknoexec = os.path.join(cachedir, 'checknoexec')
167 167
168 168 try:
169 169 m = os.stat(checkisexec).st_mode
170 170 except OSError as e:
171 171 if e.errno != errno.ENOENT:
172 172 raise
173 173 # checkisexec does not exist - fall through ...
174 174 else:
175 175 # checkisexec exists, check if it actually is exec
176 176 if m & EXECFLAGS != 0:
177 177 # ensure checkisexec exists, check it isn't exec
178 178 try:
179 179 m = os.stat(checknoexec).st_mode
180 180 except OSError as e:
181 181 if e.errno != errno.ENOENT:
182 182 raise
183 183 file(checknoexec, 'w').close() # might fail
184 184 m = os.stat(checknoexec).st_mode
185 185 if m & EXECFLAGS == 0:
186 186 # check-exec is exec and check-no-exec is not exec
187 187 return True
188 188 # checknoexec exists but is exec - delete it
189 189 os.unlink(checknoexec)
190 190 # checkisexec exists but is not exec - delete it
191 191 os.unlink(checkisexec)
192 192
193 193 # check using one file, leave it as checkisexec
194 194 checkdir = cachedir
195 195 else:
196 196 # check directly in path and don't leave checkisexec behind
197 197 checkdir = path
198 198 checkisexec = None
199 199 fh, fn = tempfile.mkstemp(dir=checkdir, prefix='hg-checkexec-')
200 200 try:
201 201 os.close(fh)
202 202 m = os.stat(fn).st_mode
203 203 if m & EXECFLAGS == 0:
204 204 os.chmod(fn, m & 0o777 | EXECFLAGS)
205 205 if os.stat(fn).st_mode & EXECFLAGS != 0:
206 206 if checkisexec is not None:
207 207 os.rename(fn, checkisexec)
208 208 fn = None
209 209 return True
210 210 finally:
211 211 if fn is not None:
212 212 os.unlink(fn)
213 213 except (IOError, OSError):
214 214 # we don't care, the user probably won't be able to commit anyway
215 215 return False
216 216
217 217 def checklink(path):
218 218 """check whether the given path is on a symlink-capable filesystem"""
219 219 # mktemp is not racy because symlink creation will fail if the
220 220 # file already exists
221 221 while True:
222 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
222 cachedir = os.path.join(path, '.hg', 'cache')
223 if os.path.isdir(cachedir):
224 checkdir = cachedir
225 else:
226 checkdir = path
227 cachedir = None
228 name = tempfile.mktemp(dir=checkdir, prefix='checklink-')
223 229 try:
224 fd = tempfile.NamedTemporaryFile(dir=path, prefix='hg-checklink-')
230 fd = tempfile.NamedTemporaryFile(dir=checkdir,
231 prefix='hg-checklink-')
225 232 try:
226 233 os.symlink(os.path.basename(fd.name), name)
227 234 os.unlink(name)
228 235 return True
229 236 except OSError as inst:
230 237 # link creation might race, try again
231 238 if inst[0] == errno.EEXIST:
232 239 continue
233 240 raise
234 241 finally:
235 242 fd.close()
236 243 except AttributeError:
237 244 return False
238 245 except OSError as inst:
239 246 # sshfs might report failure while successfully creating the link
240 247 if inst[0] == errno.EIO and os.path.exists(name):
241 248 os.unlink(name)
242 249 return False
243 250
244 251 def checkosfilename(path):
245 252 '''Check that the base-relative path is a valid filename on this platform.
246 253 Returns None if the path is ok, or a UI string describing the problem.'''
247 254 pass # on posix platforms, every path is ok
248 255
249 256 def setbinary(fd):
250 257 pass
251 258
252 259 def pconvert(path):
253 260 return path
254 261
255 262 def localpath(path):
256 263 return path
257 264
258 265 def samefile(fpath1, fpath2):
259 266 """Returns whether path1 and path2 refer to the same file. This is only
260 267 guaranteed to work for files, not directories."""
261 268 return os.path.samefile(fpath1, fpath2)
262 269
263 270 def samedevice(fpath1, fpath2):
264 271 """Returns whether fpath1 and fpath2 are on the same device. This is only
265 272 guaranteed to work for files, not directories."""
266 273 st1 = os.lstat(fpath1)
267 274 st2 = os.lstat(fpath2)
268 275 return st1.st_dev == st2.st_dev
269 276
270 277 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
271 278 def normcase(path):
272 279 return path.lower()
273 280
274 281 # what normcase does to ASCII strings
275 282 normcasespec = encoding.normcasespecs.lower
276 283 # fallback normcase function for non-ASCII strings
277 284 normcasefallback = normcase
278 285
279 286 if sys.platform == 'darwin':
280 287
281 288 def normcase(path):
282 289 '''
283 290 Normalize a filename for OS X-compatible comparison:
284 291 - escape-encode invalid characters
285 292 - decompose to NFD
286 293 - lowercase
287 294 - omit ignored characters [200c-200f, 202a-202e, 206a-206f,feff]
288 295
289 296 >>> normcase('UPPER')
290 297 'upper'
291 298 >>> normcase('Caf\xc3\xa9')
292 299 'cafe\\xcc\\x81'
293 300 >>> normcase('\xc3\x89')
294 301 'e\\xcc\\x81'
295 302 >>> normcase('\xb8\xca\xc3\xca\xbe\xc8.JPG') # issue3918
296 303 '%b8%ca%c3\\xca\\xbe%c8.jpg'
297 304 '''
298 305
299 306 try:
300 307 return encoding.asciilower(path) # exception for non-ASCII
301 308 except UnicodeDecodeError:
302 309 return normcasefallback(path)
303 310
304 311 normcasespec = encoding.normcasespecs.lower
305 312
306 313 def normcasefallback(path):
307 314 try:
308 315 u = path.decode('utf-8')
309 316 except UnicodeDecodeError:
310 317 # OS X percent-encodes any bytes that aren't valid utf-8
311 318 s = ''
312 319 pos = 0
313 320 l = len(path)
314 321 while pos < l:
315 322 try:
316 323 c = encoding.getutf8char(path, pos)
317 324 pos += len(c)
318 325 except ValueError:
319 326 c = '%%%02X' % ord(path[pos])
320 327 pos += 1
321 328 s += c
322 329
323 330 u = s.decode('utf-8')
324 331
325 332 # Decompose then lowercase (HFS+ technote specifies lower)
326 333 enc = unicodedata.normalize('NFD', u).lower().encode('utf-8')
327 334 # drop HFS+ ignored characters
328 335 return encoding.hfsignoreclean(enc)
329 336
330 337 if sys.platform == 'cygwin':
331 338 # workaround for cygwin, in which mount point part of path is
332 339 # treated as case sensitive, even though underlying NTFS is case
333 340 # insensitive.
334 341
335 342 # default mount points
336 343 cygwinmountpoints = sorted([
337 344 "/usr/bin",
338 345 "/usr/lib",
339 346 "/cygdrive",
340 347 ], reverse=True)
341 348
342 349 # use upper-ing as normcase as same as NTFS workaround
343 350 def normcase(path):
344 351 pathlen = len(path)
345 352 if (pathlen == 0) or (path[0] != os.sep):
346 353 # treat as relative
347 354 return encoding.upper(path)
348 355
349 356 # to preserve case of mountpoint part
350 357 for mp in cygwinmountpoints:
351 358 if not path.startswith(mp):
352 359 continue
353 360
354 361 mplen = len(mp)
355 362 if mplen == pathlen: # mount point itself
356 363 return mp
357 364 if path[mplen] == os.sep:
358 365 return mp + encoding.upper(path[mplen:])
359 366
360 367 return encoding.upper(path)
361 368
362 369 normcasespec = encoding.normcasespecs.other
363 370 normcasefallback = normcase
364 371
365 372 # Cygwin translates native ACLs to POSIX permissions,
366 373 # but these translations are not supported by native
367 374 # tools, so the exec bit tends to be set erroneously.
368 375 # Therefore, disable executable bit access on Cygwin.
369 376 def checkexec(path):
370 377 return False
371 378
372 379 # Similarly, Cygwin's symlink emulation is likely to create
373 380 # problems when Mercurial is used from both Cygwin and native
374 381 # Windows, with other native tools, or on shared volumes
375 382 def checklink(path):
376 383 return False
377 384
378 385 _needsshellquote = None
379 386 def shellquote(s):
380 387 if os.sys.platform == 'OpenVMS':
381 388 return '"%s"' % s
382 389 global _needsshellquote
383 390 if _needsshellquote is None:
384 391 _needsshellquote = re.compile(r'[^a-zA-Z0-9._/+-]').search
385 392 if s and not _needsshellquote(s):
386 393 # "s" shouldn't have to be quoted
387 394 return s
388 395 else:
389 396 return "'%s'" % s.replace("'", "'\\''")
390 397
391 398 def quotecommand(cmd):
392 399 return cmd
393 400
394 401 def popen(command, mode='r'):
395 402 return os.popen(command, mode)
396 403
397 404 def testpid(pid):
398 405 '''return False if pid dead, True if running or not sure'''
399 406 if os.sys.platform == 'OpenVMS':
400 407 return True
401 408 try:
402 409 os.kill(pid, 0)
403 410 return True
404 411 except OSError as inst:
405 412 return inst.errno != errno.ESRCH
406 413
407 414 def explainexit(code):
408 415 """return a 2-tuple (desc, code) describing a subprocess status
409 416 (codes from kill are negative - not os.system/wait encoding)"""
410 417 if code >= 0:
411 418 return _("exited with status %d") % code, code
412 419 return _("killed by signal %d") % -code, -code
413 420
414 421 def isowner(st):
415 422 """Return True if the stat object st is from the current user."""
416 423 return st.st_uid == os.getuid()
417 424
418 425 def findexe(command):
419 426 '''Find executable for command searching like which does.
420 427 If command is a basename then PATH is searched for command.
421 428 PATH isn't searched if command is an absolute or relative path.
422 429 If command isn't found None is returned.'''
423 430 if sys.platform == 'OpenVMS':
424 431 return command
425 432
426 433 def findexisting(executable):
427 434 'Will return executable if existing file'
428 435 if os.path.isfile(executable) and os.access(executable, os.X_OK):
429 436 return executable
430 437 return None
431 438
432 439 if os.sep in command:
433 440 return findexisting(command)
434 441
435 442 if sys.platform == 'plan9':
436 443 return findexisting(os.path.join('/bin', command))
437 444
438 445 for path in os.environ.get('PATH', '').split(os.pathsep):
439 446 executable = findexisting(os.path.join(path, command))
440 447 if executable is not None:
441 448 return executable
442 449 return None
443 450
444 451 def setsignalhandler():
445 452 pass
446 453
447 454 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
448 455
449 456 def statfiles(files):
450 457 '''Stat each file in files. Yield each stat, or None if a file does not
451 458 exist or has a type we don't care about.'''
452 459 lstat = os.lstat
453 460 getkind = stat.S_IFMT
454 461 for nf in files:
455 462 try:
456 463 st = lstat(nf)
457 464 if getkind(st.st_mode) not in _wantedkinds:
458 465 st = None
459 466 except OSError as err:
460 467 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
461 468 raise
462 469 st = None
463 470 yield st
464 471
465 472 def getuser():
466 473 '''return name of current user'''
467 474 return getpass.getuser()
468 475
469 476 def username(uid=None):
470 477 """Return the name of the user with the given uid.
471 478
472 479 If uid is None, return the name of the current user."""
473 480
474 481 if uid is None:
475 482 uid = os.getuid()
476 483 try:
477 484 return pwd.getpwuid(uid)[0]
478 485 except KeyError:
479 486 return str(uid)
480 487
481 488 def groupname(gid=None):
482 489 """Return the name of the group with the given gid.
483 490
484 491 If gid is None, return the name of the current group."""
485 492
486 493 if gid is None:
487 494 gid = os.getgid()
488 495 try:
489 496 return grp.getgrgid(gid)[0]
490 497 except KeyError:
491 498 return str(gid)
492 499
493 500 def groupmembers(name):
494 501 """Return the list of members of the group with the given
495 502 name, KeyError if the group does not exist.
496 503 """
497 504 return list(grp.getgrnam(name).gr_mem)
498 505
499 506 def spawndetached(args):
500 507 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
501 508 args[0], args)
502 509
503 510 def gethgcmd():
504 511 return sys.argv[:1]
505 512
506 513 def makedir(path, notindexed):
507 514 os.mkdir(path)
508 515
509 516 def unlinkpath(f, ignoremissing=False):
510 517 """unlink and remove the directory if it is empty"""
511 518 try:
512 519 os.unlink(f)
513 520 except OSError as e:
514 521 if not (ignoremissing and e.errno == errno.ENOENT):
515 522 raise
516 523 # try removing directories that might now be empty
517 524 try:
518 525 os.removedirs(os.path.dirname(f))
519 526 except OSError:
520 527 pass
521 528
522 529 def lookupreg(key, name=None, scope=None):
523 530 return None
524 531
525 532 def hidewindow():
526 533 """Hide current shell window.
527 534
528 535 Used to hide the window opened when starting asynchronous
529 536 child process under Windows, unneeded on other systems.
530 537 """
531 538 pass
532 539
533 540 class cachestat(object):
534 541 def __init__(self, path):
535 542 self.stat = os.stat(path)
536 543
537 544 def cacheable(self):
538 545 return bool(self.stat.st_ino)
539 546
540 547 __hash__ = object.__hash__
541 548
542 549 def __eq__(self, other):
543 550 try:
544 551 # Only dev, ino, size, mtime and atime are likely to change. Out
545 552 # of these, we shouldn't compare atime but should compare the
546 553 # rest. However, one of the other fields changing indicates
547 554 # something fishy going on, so return False if anything but atime
548 555 # changes.
549 556 return (self.stat.st_mode == other.stat.st_mode and
550 557 self.stat.st_ino == other.stat.st_ino and
551 558 self.stat.st_dev == other.stat.st_dev and
552 559 self.stat.st_nlink == other.stat.st_nlink and
553 560 self.stat.st_uid == other.stat.st_uid and
554 561 self.stat.st_gid == other.stat.st_gid and
555 562 self.stat.st_size == other.stat.st_size and
556 563 self.stat.st_mtime == other.stat.st_mtime and
557 564 self.stat.st_ctime == other.stat.st_ctime)
558 565 except AttributeError:
559 566 return False
560 567
561 568 def __ne__(self, other):
562 569 return not self == other
563 570
564 571 def executablepath():
565 572 return None # available on Windows only
566 573
567 574 def statislink(st):
568 575 '''check whether a stat result is a symlink'''
569 576 return st and stat.S_ISLNK(st.st_mode)
570 577
571 578 def statisexec(st):
572 579 '''check whether a stat result is an executable file'''
573 580 return st and (st.st_mode & 0o100 != 0)
574 581
575 582 def poll(fds):
576 583 """block until something happens on any file descriptor
577 584
578 585 This is a generic helper that will check for any activity
579 586 (read, write. exception) and return the list of touched files.
580 587
581 588 In unsupported cases, it will raise a NotImplementedError"""
582 589 try:
583 590 res = select.select(fds, fds, fds)
584 591 except ValueError: # out of range file descriptor
585 592 raise NotImplementedError()
586 593 return sorted(list(set(sum(res, []))))
587 594
588 595 def readpipe(pipe):
589 596 """Read all available data from a pipe."""
590 597 # We can't fstat() a pipe because Linux will always report 0.
591 598 # So, we set the pipe to non-blocking mode and read everything
592 599 # that's available.
593 600 flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
594 601 flags |= os.O_NONBLOCK
595 602 oldflags = fcntl.fcntl(pipe, fcntl.F_SETFL, flags)
596 603
597 604 try:
598 605 chunks = []
599 606 while True:
600 607 try:
601 608 s = pipe.read()
602 609 if not s:
603 610 break
604 611 chunks.append(s)
605 612 except IOError:
606 613 break
607 614
608 615 return ''.join(chunks)
609 616 finally:
610 617 fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
611 618
612 619 def bindunixsocket(sock, path):
613 620 """Bind the UNIX domain socket to the specified path"""
614 621 # use relative path instead of full path at bind() if possible, since
615 622 # AF_UNIX path has very small length limit (107 chars) on common
616 623 # platforms (see sys/un.h)
617 624 dirname, basename = os.path.split(path)
618 625 bakwdfd = None
619 626 if dirname:
620 627 bakwdfd = os.open('.', os.O_DIRECTORY)
621 628 os.chdir(dirname)
622 629 sock.bind(basename)
623 630 if bakwdfd:
624 631 os.fchdir(bakwdfd)
625 632 os.close(bakwdfd)
General Comments 0
You need to be logged in to leave comments. Login now