[PATCH 3 of 5 rfc] posix: give checkexec a fast path; keep the check files and test read only
Mads Kiilerich
mads at kiilerich.com
Sat Oct 24 11:32:01 CDT 2015
# HG changeset patch
# User Mads Kiilerich <madski at unity3d.com>
# Date 1421194526 -3600
# Wed Jan 14 01:15:26 2015 +0100
# Branch stable
# Node ID 5921bcc389073dfdb4a44284db5ee00bcbb0c499
# Parent 489575343604ce4558ee18007714844f42b0fff5
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. These temporary files were created in the working directory and could
cause confusion for other tools monitoring the file system.
Instead, try to create .hg/cache/checkisexec and .hg/cache/checknoexec if they
don't exist. Best case, the exec check can be done with two stat calls. Worst
case is on file systems without support for the exec bit where there will be
one extra stat.
It is possible that this different test algorithm in some cases 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 --git a/mercurial/posix.py b/mercurial/posix.py
--- a/mercurial/posix.py
+++ b/mercurial/posix.py
@@ -153,18 +153,59 @@ def checkexec(path):
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 to create and check
+ else:
+ if not m & EXECFLAGS:
+ # checkisexec exists but is not exec
+ return False
+ # ensure checkisexec exists, check if it actually is exec
+ try:
+ m = os.stat(checknoexec).st_mode
+ except OSError as e:
+ if e.errno != errno.ENOENT:
+ raise
+ file(checknoexec, 'w').close() # might raise
+ m = os.stat(checknoexec).st_mode
+ if m & EXECFLAGS:
+ # checknoexec exists but is exec
+ os.unlink(checknoexec)
+ return False
+ # check-no-exec exists and is not exec
+ return True
+
+ # check using one file, leave it as checkisexec
+ checkdir = cachedir
+ else:
+ 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 os.stat(fn).st_mode & EXECFLAGS:
+ if checkisexec is not None:
+ try:
+ os.rename(fn, checkisexec)
+ fn = None
+ except OSError:
+ pass # probably because .hg/cache doesn't exist (yet)
+ return True
+ return False
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 --git a/tests/test-clone.t b/tests/test-clone.t
--- a/tests/test-clone.t
+++ b/tests/test-clone.t
@@ -31,6 +31,8 @@ Trigger branchcache creation:
default 10:a7949464abda
$ ls .hg/cache
branch2-served
+ checkisexec
+ checknoexec
rbc-names-v1
rbc-revs-v1
@@ -45,6 +47,7 @@ Ensure branchcache got copied over:
$ ls .hg/cache
branch2-served
+ checkisexec
$ cat a
a
diff --git a/tests/test-hardlinks.t b/tests/test-hardlinks.t
--- a/tests/test-hardlinks.t
+++ b/tests/test-hardlinks.t
@@ -211,6 +211,8 @@ r4 has hardlinks in the working dir (not
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
@@ -246,6 +248,8 @@ Update back to revision 11 in r4 should
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 --git a/tests/test-tags.t b/tests/test-tags.t
--- a/tests/test-tags.t
+++ b/tests/test-tags.t
@@ -665,6 +665,7 @@ Missing tags2* files means the cache was
$ ls tagsclient/.hg/cache
branch2-served
+ checkisexec
hgtagsfnodes1
rbc-names-v1
rbc-revs-v1
@@ -689,6 +690,7 @@ Running hg tags should produce tags2* fi
$ ls tagsclient/.hg/cache
branch2-served
+ checkisexec
hgtagsfnodes1
rbc-names-v1
rbc-revs-v1
More information about the Mercurial-devel
mailing list