about summary refs log tree commit diff
path: root/src/libstore/optimise-store.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/libstore/optimise-store.cc')
-rw-r--r--src/libstore/optimise-store.cc74
1 files changed, 43 insertions, 31 deletions
diff --git a/src/libstore/optimise-store.cc b/src/libstore/optimise-store.cc
index 334f4f355f43..9d0242bbc857 100644
--- a/src/libstore/optimise-store.cc
+++ b/src/libstore/optimise-store.cc
@@ -102,11 +102,11 @@ void LocalStore::optimisePath_(OptimiseStats & stats, const Path & path)
         /* Nope, create a hard link in the links directory. */
         makeMutable(path);
         MakeImmutable mk1(path);
-
-        if (link(path.c_str(), linkPath.c_str()) == -1)
+        if (link(path.c_str(), linkPath.c_str()) == 0) return;
+        if (errno != EEXIST)
             throw SysError(format("cannot link `%1%' to `%2%'") % linkPath % path);
-
-        return;
+        /* Fall through if another process created ‘linkPath’ before
+           we did. */
     }
 
     /* Yes!  We've seen a file with the same contents.  Replace the
@@ -123,9 +123,6 @@ void LocalStore::optimisePath_(OptimiseStats & stats, const Path & path)
 
     printMsg(lvlTalkative, format("linking `%1%' to `%2%'") % path % linkPath);
 
-    Path tempLink = (format("%1%/.tmp-link-%2%-%3%")
-        % settings.nixStore % getpid() % rand()).str();
-
     /* Make the containing directory writable, but only if it's not
        the store itself (we don't want or need to mess with its
        permissions). */
@@ -140,40 +137,55 @@ void LocalStore::optimisePath_(OptimiseStats & stats, const Path & path)
        so make it mutable first (and make it immutable again when
        we're done).  We also have to make ‘path’ mutable, otherwise
        rename() will fail to delete it. */
-    makeMutable(linkPath);
-    MakeImmutable mk1(linkPath);
-
     makeMutable(path);
     MakeImmutable mk2(path);
 
-    if (link(linkPath.c_str(), tempLink.c_str()) == -1) {
-        if (errno == EMLINK) {
-            /* Too many links to the same file (>= 32000 on most file
-               systems).  This is likely to happen with empty files.
-               Just shrug and ignore. */
-            printMsg(lvlInfo, format("`%1%' has maximum number of links") % linkPath);
-            return;
+    /* Another process might be doing the same thing (creating a new
+       link to ‘linkPath’) and make ‘linkPath’ immutable before we're
+       done.  In that case, just retry. */
+    unsigned int retries = 1024;
+    while (--retries > 0) {
+        makeMutable(linkPath);
+        MakeImmutable mk1(linkPath);
+
+        Path tempLink = (format("%1%/.tmp-link-%2%-%3%")
+            % settings.nixStore % getpid() % rand()).str();
+
+        if (link(linkPath.c_str(), tempLink.c_str()) == -1) {
+            if (errno == EMLINK) {
+                /* Too many links to the same file (>= 32000 on most
+                   file systems).  This is likely to happen with empty
+                   files.  Just shrug and ignore. */
+                if (st.st_size)
+                    printMsg(lvlInfo, format("`%1%' has maximum number of links") % linkPath);
+                return;
+            }
+            if (errno == EPERM) continue;
+            throw SysError(format("cannot link `%1%' to `%2%'") % tempLink % linkPath);
         }
-        throw SysError(format("cannot link `%1%' to `%2%'") % tempLink % linkPath);
-    }
-
-    /* Atomically replace the old file with the new hard link. */
-    if (rename(tempLink.c_str(), path.c_str()) == -1) {
-        if (errno == EMLINK) {
-            /* Some filesystems generate too many links on the rename,
-               rather than on the original link.  (Probably it
-               temporarily increases the st_nlink field before
-               decreasing it again.) */
-            printMsg(lvlInfo, format("`%1%' has maximum number of links") % linkPath);
 
-            /* Unlink the temp link. */
+        /* Atomically replace the old file with the new hard link. */
+        if (rename(tempLink.c_str(), path.c_str()) == -1) {
             if (unlink(tempLink.c_str()) == -1)
                 printMsg(lvlError, format("unable to unlink `%1%'") % tempLink);
-            return;
+            if (errno == EMLINK) {
+                /* Some filesystems generate too many links on the
+                   rename, rather than on the original link.
+                   (Probably it temporarily increases the st_nlink
+                   field before decreasing it again.) */
+                if (st.st_size)
+                    printMsg(lvlInfo, format("`%1%' has maximum number of links") % linkPath);
+                return;
+            }
+            if (errno == EPERM) continue;
+            throw SysError(format("cannot rename `%1%' to `%2%'") % tempLink % path);
         }
-        throw SysError(format("cannot rename `%1%' to `%2%'") % tempLink % path);
+
+        break;
     }
 
+    if (retries == 0) throw Error(format("cannot link `%1%' to `%2%'") % path % linkPath);
+
     stats.filesLinked++;
     stats.bytesFreed += st.st_size;
     stats.blocksFreed += st.st_blocks;