deps log: recover on truncated entry
authorEvan Martin <martine@danga.com>
Sat, 27 Apr 2013 20:39:04 +0000 (13:39 -0700)
committerEvan Martin <martine@danga.com>
Sat, 27 Apr 2013 20:39:04 +0000 (13:39 -0700)
If a read fails while reading an entry, truncate the log to the last
successfully read entry.  This prevents corruption when a subsequent
run appends another entry.

src/deps_log.cc
src/deps_log_test.cc

index ceb75ce..5e5bba9 100644 (file)
@@ -152,15 +152,23 @@ bool DepsLog::Load(const string& path, State* state, string* err) {
     return true;
   }
 
+  long offset;
+  bool read_failed = false;
   for (;;) {
+    offset = ftell(f);
+
     uint16_t size;
-    if (fread(&size, 2, 1, f) < 1)
+    if (fread(&size, 2, 1, f) < 1) {
+      read_failed = true;
       break;
+    }
     bool is_deps = (size >> 15) != 0;
     size = size & 0x7FFF;
 
-    if (fread(buf, size, 1, f) < 1)
+    if (fread(buf, size, 1, f) < 1) {
+      read_failed = true;
       break;
+    }
 
     if (is_deps) {
       assert(size % 4 == 0);
@@ -195,16 +203,37 @@ bool DepsLog::Load(const string& path, State* state, string* err) {
       nodes_.push_back(node);
     }
   }
-  if (ferror(f)) {
-    *err = strerror(ferror(f));
-    return false;
+
+  if (read_failed) {
+    // An error occurred while loading; try to recover by truncating the
+    // file to the last fully-read record.
+    if (ferror(f)) {
+      *err = strerror(ferror(f));
+    } else {
+      *err = "premature end of file";
+    }
+    fclose(f);
+
+    if (truncate(path.c_str(), offset) < 0) {
+      *err = strerror(errno);
+      return false;
+    }
+
+    // The truncate succeeded; we'll just report the load error as a
+    // warning because the build can proceed.
+    *err += "; recovering";
+    return true;
   }
+
   fclose(f);
+
   return true;
 }
 
 DepsLog::Deps* DepsLog::GetDeps(Node* node) {
-  if (node->id() < 0)
+  // Abort if the node has no id (never referenced in the deps) or if
+  // there's no deps recorded for the node.
+  if (node->id() < 0 || node->id() >= deps_.size())
     return NULL;
   return deps_[node->id()];
 }
index 40539a7..9623d17 100644 (file)
@@ -218,8 +218,7 @@ TEST_F(DepsLogTest, InvalidHeader) {
   }
 }
 
-// Simulate what happens if a write gets interrupted and the resulting
-// file is truncated.
+// Simulate what happens when loading a truncated log file.
 TEST_F(DepsLogTest, Truncated) {
   // Create a file with some entries.
   {
@@ -263,7 +262,7 @@ TEST_F(DepsLogTest, Truncated) {
       break;
     }
 
-    ASSERT_GE(node_count, log.nodes().size());
+    ASSERT_GE(node_count, (int)log.nodes().size());
     node_count = log.nodes().size();
 
     // Count how many non-NULL deps entries there are.
@@ -278,4 +277,70 @@ TEST_F(DepsLogTest, Truncated) {
   }
 }
 
+// Run the truncation-recovery logic.
+TEST_F(DepsLogTest, TruncatedRecovery) {
+  // Create a file with some entries.
+  {
+    State state;
+    DepsLog log;
+    string err;
+    EXPECT_TRUE(log.OpenForWrite(kTestFilename, &err));
+    ASSERT_EQ("", err);
+
+    vector<Node*> deps;
+    deps.push_back(state.GetNode("foo.h"));
+    deps.push_back(state.GetNode("bar.h"));
+    log.RecordDeps(state.GetNode("out.o"), 1, deps);
+
+    deps.clear();
+    deps.push_back(state.GetNode("foo.h"));
+    deps.push_back(state.GetNode("bar2.h"));
+    log.RecordDeps(state.GetNode("out2.o"), 2, deps);
+
+    log.Close();
+  }
+
+  // Shorten the file, corrupting the last record.
+  struct stat st;
+  ASSERT_EQ(0, stat(kTestFilename, &st));
+  ASSERT_EQ(0, truncate(kTestFilename, st.st_size - 2));
+
+  // Load the file again, add an entry.
+  {
+    State state;
+    DepsLog log;
+    string err;
+    EXPECT_TRUE(log.Load(kTestFilename, &state, &err));
+    ASSERT_EQ("premature end of file; recovering", err);
+    err.clear();
+
+    // The truncated entry should've been discarded.
+    EXPECT_EQ(NULL, log.GetDeps(state.GetNode("out2.o")));
+
+    EXPECT_TRUE(log.OpenForWrite(kTestFilename, &err));
+    ASSERT_EQ("", err);
+
+    // Add a new entry.
+    vector<Node*> deps;
+    deps.push_back(state.GetNode("foo.h"));
+    deps.push_back(state.GetNode("bar2.h"));
+    log.RecordDeps(state.GetNode("out2.o"), 3, deps);
+
+    log.Close();
+  }
+
+  // Load the file a third time to verify appending after a mangled
+  // entry doesn't break things.
+  {
+    State state;
+    DepsLog log;
+    string err;
+    EXPECT_TRUE(log.Load(kTestFilename, &state, &err));
+
+    // The truncated entry should exist.
+    DepsLog::Deps* deps = log.GetDeps(state.GetNode("out2.o"));
+    ASSERT_TRUE(deps);
+  }
+}
+
 }  // anonymous namespace