diff options
| -rw-r--r-- | src/rpc.cpp | 61 |
1 files changed, 58 insertions, 3 deletions
diff --git a/src/rpc.cpp b/src/rpc.cpp index d655ad5..45130ff 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp | |||
| @@ -31,6 +31,9 @@ | |||
| 31 | #include <netinet/in.h> | 31 | #include <netinet/in.h> |
| 32 | #include <arpa/inet.h> | 32 | #include <arpa/inet.h> |
| 33 | #include <unistd.h> | 33 | #include <unistd.h> |
| 34 | #include <fcntl.h> | ||
| 35 | #else | ||
| 36 | #include <io.h> | ||
| 34 | #endif | 37 | #endif |
| 35 | 38 | ||
| 36 | #include <atomic> | 39 | #include <atomic> |
| @@ -457,9 +460,50 @@ struct VersionedBlob { | |||
| 457 | uint64 seq = 0; | 460 | uint64 seq = 0; |
| 458 | }; | 461 | }; |
| 459 | 462 | ||
| 460 | // Replace `path` with `data` atomically. Caller must hold the write mutex for | 463 | // Push a file's contents out of the C library's buffer AND out of the kernel's |
| 461 | // `path`: the temp name is derived from the target, so two threads in here at | 464 | // page cache, so the bytes exist on the device before anything points at them. |
| 462 | // once for one path is precisely the race this exists to prevent. | 465 | static bool SyncFileHandle(FILE* f) |
| 466 | { | ||
| 467 | if (fflush(f) != 0) | ||
| 468 | return false; | ||
| 469 | #ifdef _WIN32 | ||
| 470 | return _commit(_fileno(f)) == 0; | ||
| 471 | #else | ||
| 472 | return fsync(fileno(f)) == 0; | ||
| 473 | #endif | ||
| 474 | } | ||
| 475 | |||
| 476 | // Make the directory entry that rename() created durable too. | ||
| 477 | // | ||
| 478 | // A tmp+rename is only atomic with respect to a crash of the process. Against | ||
| 479 | // a power cut or a hard VM reset the rename can reach the platter while the | ||
| 480 | // data it points at has not, and the file that comes back is short or empty. | ||
| 481 | // For pending_payouts.json that is not a corrupt file, it is a silent one: | ||
| 482 | // LoadPendingPayouts catches the parse error, logs a single warning, and the | ||
| 483 | // node comes up owing nobody anything. | ||
| 484 | // | ||
| 485 | // Best effort -- a filesystem that does not allow the directory to be opened | ||
| 486 | // for this is not a reason to refuse to write the ledger. | ||
| 487 | static void SyncDirectoryOf(const std::string& path) | ||
| 488 | { | ||
| 489 | #ifndef _WIN32 | ||
| 490 | size_t slash = path.find_last_of('/'); | ||
| 491 | std::string dir = (slash == std::string::npos) ? std::string(".") | ||
| 492 | : path.substr(0, slash); | ||
| 493 | if (dir.empty()) | ||
| 494 | dir = "/"; | ||
| 495 | int fd = open(dir.c_str(), O_RDONLY); | ||
| 496 | if (fd < 0) | ||
| 497 | return; | ||
| 498 | fsync(fd); | ||
| 499 | close(fd); | ||
| 500 | #endif | ||
| 501 | } | ||
| 502 | |||
| 503 | // Replace `path` with `data` atomically and durably. Caller must hold the | ||
| 504 | // write mutex for `path`: the temp name is derived from the target, so two | ||
| 505 | // threads in here at once for one path is precisely the race this exists to | ||
| 506 | // prevent. | ||
| 463 | static bool WriteFileAtomic(const std::string& path, const std::string& data, | 507 | static bool WriteFileAtomic(const std::string& path, const std::string& data, |
| 464 | const char* what) | 508 | const char* what) |
| 465 | { | 509 | { |
| @@ -476,6 +520,16 @@ static bool WriteFileAtomic(const std::string& path, const std::string& data, | |||
| 476 | fclose(f); | 520 | fclose(f); |
| 477 | return false; | 521 | return false; |
| 478 | } | 522 | } |
| 523 | // Before the rename, not after: publishing a name that points at nothing | ||
| 524 | // is the failure this ordering exists to prevent. If the data cannot be | ||
| 525 | // made durable, do not rename -- the previous file is still intact and | ||
| 526 | // still correct, and the next write will try again. | ||
| 527 | if (!SyncFileHandle(f)) { | ||
| 528 | LogPrint("payout", "[payout] WARNING: could not flush %s to disk (%s)" | ||
| 529 | " -- keeping the previous file\n", tmp.c_str(), what); | ||
| 530 | fclose(f); | ||
| 531 | return false; | ||
| 532 | } | ||
| 479 | // fclose() is where buffered data actually reaches the OS, so a failure | 533 | // fclose() is where buffered data actually reaches the OS, so a failure |
| 480 | // here means the .tmp is short -- renaming it over the real file would | 534 | // here means the .tmp is short -- renaming it over the real file would |
| 481 | // destroy the good copy, which is exactly what these files exist to | 535 | // destroy the good copy, which is exactly what these files exist to |
| @@ -494,6 +548,7 @@ static bool WriteFileAtomic(const std::string& path, const std::string& data, | |||
| 494 | path.c_str(), what); | 548 | path.c_str(), what); |
| 495 | return false; | 549 | return false; |
| 496 | } | 550 | } |
| 551 | SyncDirectoryOf(path); | ||
| 497 | return true; | 552 | return true; |
| 498 | } | 553 | } |
| 499 | 554 | ||