changeset 15571:809788118aa2 stable

largefiles: write .hg/largefiles/ files atomically Before, it was possible to create a .hg/largefiles/hash file with truncated content, i.e., content where SHA-1(content) != hash This breaks the fundamental invariant in largefiles that the file content for files in .hg/largefiles hash to the filename.
author Martin Geisler <mg@aragost.com>
date Thu, 24 Nov 2011 18:12:13 +0100
parents 0f208626d503
children 926bc23d0b6a
files hgext/largefiles/lfutil.py tests/test-largefiles-small-disk.t
diffstat 2 files changed, 44 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/hgext/largefiles/lfutil.py	Thu Nov 24 18:11:43 2011 +0100
+++ b/hgext/largefiles/lfutil.py	Thu Nov 24 18:12:13 2011 +0100
@@ -228,8 +228,11 @@
     if inusercache(repo.ui, hash):
         link(usercachepath(repo.ui, hash), storepath(repo, hash))
     else:
-        shutil.copyfile(file, storepath(repo, hash))
-        os.chmod(storepath(repo, hash), os.stat(file).st_mode)
+        dst = util.atomictempfile(storepath(repo, hash))
+        for chunk in util.filechunkiter(open(file)):
+            dst.write(chunk)
+        dst.close()
+        util.copymode(file, storepath(repo, hash))
         linktousercache(repo, hash)
 
 def linktousercache(repo, hash):
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-largefiles-small-disk.t	Thu Nov 24 18:12:13 2011 +0100
@@ -0,0 +1,39 @@
+Test how largefiles abort in case the disk runs full
+
+  $ cat > criple.py <<EOF
+  > import os, errno, shutil
+  > from mercurial import util
+  > #
+  > # this makes the original largefiles code abort:
+  > def copyfileobj(fsrc, fdst, length=16*1024):
+  >     fdst.write(fsrc.read(4))
+  >     raise IOError(errno.ENOSPC, os.strerror(errno.ENOSPC))
+  > shutil.copyfileobj = copyfileobj
+  > #
+  > # this makes the rewritten code abort:
+  > def filechunkiter(f, size=65536, limit=None):
+  >     yield f.read(4)
+  >     raise IOError(errno.ENOSPC, os.strerror(errno.ENOSPC))
+  > util.filechunkiter = filechunkiter
+  > EOF
+
+  $ echo "[extensions]" >> $HGRCPATH
+  $ echo "largefiles =" >> $HGRCPATH
+
+  $ hg init alice
+  $ cd alice
+  $ echo "this is a very big file" > big
+  $ hg add --large big
+  $ hg commit --config extensions.criple=$TESTTMP/criple.py -m big
+  abort: No space left on device
+  [255]
+
+The largefile is not created in .hg/largefiles:
+
+  $ ls .hg/largefiles
+  dirstate
+
+The user cache is not even created:
+
+  >>> import os; os.path.exists("$HOME/.cache/largefiles/")
+  False