Skip to content
This repository was archived by the owner on Jun 15, 2023. It is now read-only.

Commit 65ad268

Browse files
committed
Suffix depslog path records with their expected index.
ninja assumes that numbering path entries in the deps log in order produces valid dense integer ids. If two ninja processes write to the same deps log concurrently, this is not true. Store the expected indices of path records in the log and treat the rest of a deps log as invalid if the dense id of a path record doesn't match the expected id stored in the log. Addresses the rest of issue ninja-build#595. This requires bumping the deps log file format version. Do not migrate the old version to the new, as the old format did not have this protection, and might hence contain invalid data. (Unlikely, but possible.) Also store the record id as one's complement, to make them look less like regular deps record values. Since each record is written atomically this isn't really necessary, but it makes the format somewhat more robust (and easier to read in a hex editor).
1 parent 8c782f1 commit 65ad268

File tree

2 files changed

+34
-7
lines changed

2 files changed

+34
-7
lines changed

src/deps_log.cc

+31-6
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
// The version is stored as 4 bytes after the signature and also serves as a
3131
// byte order mark. Signature and version combined are 16 bytes long.
3232
const char kFileSignature[] = "# ninjadeps\n";
33-
const int kCurrentVersion = 1;
33+
const int kCurrentVersion = 2;
3434

3535
// Since the size field is 2 bytes and the top bit marks deps entries, a single
3636
// record can be at most 32 kB. Set the buffer size to this and flush the file
@@ -179,9 +179,15 @@ bool DepsLog::Load(const string& path, State* state, string* err) {
179179
int version = 0;
180180
if (!fgets(buf, sizeof(buf), f) || fread(&version, 4, 1, f) < 1)
181181
valid_header = false;
182+
// Note: For version differences, this should migrate to the new format.
183+
// But the v1 format could sometimes (rarely) end up with invalid data, so
184+
// don't migrate v1 to v2 to force a rebuild.
182185
if (!valid_header || strcmp(buf, kFileSignature) != 0 ||
183186
version != kCurrentVersion) {
184-
*err = "bad deps log signature or version; starting over";
187+
if (version == 1)
188+
*err = "deps log potentially corrupt; rebuilding";
189+
else
190+
*err = "bad deps log signature or version; starting over";
185191
fclose(f);
186192
unlink(path.c_str());
187193
// Don't report this as a failure. An empty deps log will cause
@@ -229,10 +235,25 @@ bool DepsLog::Load(const string& path, State* state, string* err) {
229235
if (!UpdateDeps(out_id, deps))
230236
++unique_dep_record_count;
231237
} else {
232-
StringPiece path(buf, size);
238+
int path_size = size - 4;
239+
StringPiece path(buf, path_size);
233240
Node* node = state->GetNode(path);
241+
242+
// Check that the expected index matches the actual index. This can only
243+
// happen if two ninja processes write to the same deps log concurrently.
244+
// (This uses unary complement to make the checksum look less like a
245+
// dependency record entry.)
246+
unsigned checksum;
247+
memcpy(&checksum, buf + path_size, sizeof checksum);
248+
int expected_id = ~checksum;
249+
int id = nodes_.size();
250+
if (id != expected_id) {
251+
read_failed = true;
252+
break;
253+
}
254+
234255
assert(node->id() < 0);
235-
node->set_id(nodes_.size());
256+
node->set_id(id);
236257
nodes_.push_back(node);
237258
}
238259
}
@@ -340,7 +361,7 @@ bool DepsLog::UpdateDeps(int out_id, Deps* deps) {
340361
}
341362

342363
bool DepsLog::RecordId(Node* node) {
343-
size_t size = node->path().size();
364+
size_t size = node->path().size() + 4;
344365
if (size > kMaxRecordSize) {
345366
errno = ERANGE;
346367
return false;
@@ -352,10 +373,14 @@ bool DepsLog::RecordId(Node* node) {
352373
assert(node->path().size() > 0);
353374
return false;
354375
}
376+
int id = nodes_.size();
377+
unsigned checksum = ~(unsigned)id;
378+
if (fwrite(&checksum, 4, 1, file_) < 1)
379+
return false;
355380
if (fflush(file_) != 0)
356381
return false;
357382

358-
node->set_id(nodes_.size());
383+
node->set_id(id);
359384
nodes_.push_back(node);
360385

361386
return true;

src/deps_log.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ struct State;
5252
/// Concretely, a record is:
5353
/// two bytes record length, high bit indicates record type
5454
/// (implies max record length 32k)
55-
/// path records contain just the string name of the path
55+
/// path records contain the string name of the path, followed by the
56+
/// one's complement of the expected index of the record (to detect
57+
/// concurrent writes of multiple ninja processes to the log).
5658
/// dependency records are an array of 4-byte integers
5759
/// [output path id, output path mtime, input path id, input path id...]
5860
/// (The mtime is compared against the on-disk output path mtime

0 commit comments

Comments
 (0)