# HG changeset patch # User Mads Kiilerich # Date 1421194526 -3600 # Node ID b324b4e431e5c03e6d3b4859985ab5d781f74007 # Parent 1ce4c2062ab07617ea4dcd4933dc7062a05ffb36 posix: give checkexec a fast path; keep the check files and test read only Before, Mercurial would create a new temporary file every time, stat it, change its exec mode, stat it again, and delete it. Most of this dance was done to handle the rare and not-so-essential case of VFAT mounts on unix. The cost of that was paid by the much more common and important case of using normal file systems. Instead, try to create and preserve .hg/cache/checkisexec and .hg/cache/checknoexec with and without exec flag set. If the files exist and have correct exec flags set, we can conclude that that file system supports the exec flag. Best case, the whole exec check can thus be done with two stat calls. Worst case, we delete the wrong files and check as usual. That will be because temporary loss of exec bit or on file systems without support for the exec bit. In that case we check as we did before, with the additional overhead of one extra stat call. It is possible that this different test algorithm in some cases on odd file systems will give different behaviour. Again, I think it will be rare and special cases and I think it is worth the risk. test-clone.t happens to show the situation where checkisexec is left behind from the old style check, while checknoexec only will be created next time a exec check will be performed. diff -r 1ce4c2062ab0 -r b324b4e431e5 mercurial/posix.py --- a/mercurial/posix.py Wed Jan 14 01:15:26 2015 +0100 +++ b/mercurial/posix.py Wed Jan 14 01:15:26 2015 +0100 @@ -161,18 +161,55 @@ try: EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH cachedir = os.path.join(path, '.hg', 'cache') - if not os.path.isdir(cachedir): - cachedir = path - fh, fn = tempfile.mkstemp(dir=cachedir, prefix='hg-checkexec-') + if os.path.isdir(cachedir): + checkisexec = os.path.join(cachedir, 'checkisexec') + checknoexec = os.path.join(cachedir, 'checknoexec') + + try: + m = os.stat(checkisexec).st_mode + except OSError as e: + if e.errno != errno.ENOENT: + raise + # checkisexec does not exist - fall through ... + else: + # checkisexec exists, check if it actually is exec + if m & EXECFLAGS != 0: + # ensure checkisexec exists, check it isn't exec + try: + m = os.stat(checknoexec).st_mode + except OSError as e: + if e.errno != errno.ENOENT: + raise + file(checknoexec, 'w').close() # might fail + m = os.stat(checknoexec).st_mode + if m & EXECFLAGS == 0: + # check-exec is exec and check-no-exec is not exec + return True + # checknoexec exists but is exec - delete it + os.unlink(checknoexec) + # checkisexec exists but is not exec - delete it + os.unlink(checkisexec) + + # check using one file, leave it as checkisexec + checkdir = cachedir + else: + # check directly in path and don't leave checkisexec behind + checkdir = path + checkisexec = None + fh, fn = tempfile.mkstemp(dir=checkdir, prefix='hg-checkexec-') try: os.close(fh) m = os.stat(fn).st_mode - if m & EXECFLAGS: - return False - os.chmod(fn, m & 0o777 | EXECFLAGS) - return os.stat(fn).st_mode & EXECFLAGS + if m & EXECFLAGS == 0: + os.chmod(fn, m & 0o777 | EXECFLAGS) + if os.stat(fn).st_mode & EXECFLAGS != 0: + if checkisexec is not None: + os.rename(fn, checkisexec) + fn = None + return True finally: - os.unlink(fn) + if fn is not None: + os.unlink(fn) except (IOError, OSError): # we don't care, the user probably won't be able to commit anyway return False diff -r 1ce4c2062ab0 -r b324b4e431e5 tests/test-clone.t --- a/tests/test-clone.t Wed Jan 14 01:15:26 2015 +0100 +++ b/tests/test-clone.t Wed Jan 14 01:15:26 2015 +0100 @@ -31,6 +31,8 @@ default 10:a7949464abda $ ls .hg/cache branch2-served + checkisexec + checknoexec rbc-names-v1 rbc-revs-v1 @@ -45,6 +47,7 @@ $ ls .hg/cache branch2-served + checkisexec $ cat a a diff -r 1ce4c2062ab0 -r b324b4e431e5 tests/test-hardlinks.t --- a/tests/test-hardlinks.t Wed Jan 14 01:15:26 2015 +0100 +++ b/tests/test-hardlinks.t Wed Jan 14 01:15:26 2015 +0100 @@ -211,6 +211,8 @@ 2 r4/.hg/00changelog.i 2 r4/.hg/branch 2 r4/.hg/cache/branch2-served + 2 r4/.hg/cache/checkisexec + 2 r4/.hg/cache/checknoexec 2 r4/.hg/cache/rbc-names-v1 2 r4/.hg/cache/rbc-revs-v1 2 r4/.hg/dirstate @@ -247,6 +249,8 @@ 2 r4/.hg/00changelog.i 1 r4/.hg/branch 2 r4/.hg/cache/branch2-served + 2 r4/.hg/cache/checkisexec + 2 r4/.hg/cache/checknoexec 2 r4/.hg/cache/rbc-names-v1 2 r4/.hg/cache/rbc-revs-v1 1 r4/.hg/dirstate diff -r 1ce4c2062ab0 -r b324b4e431e5 tests/test-tags.t --- a/tests/test-tags.t Wed Jan 14 01:15:26 2015 +0100 +++ b/tests/test-tags.t Wed Jan 14 01:15:26 2015 +0100 @@ -672,6 +672,7 @@ $ ls tagsclient/.hg/cache branch2-served + checkisexec hgtagsfnodes1 rbc-names-v1 rbc-revs-v1 @@ -696,6 +697,7 @@ $ ls tagsclient/.hg/cache branch2-served + checkisexec hgtagsfnodes1 rbc-names-v1 rbc-revs-v1