Improve durability of schema version file writes

- call close explicitly in writeFile to prevent the close exception
  from being ignored
- fsync after writing schema file to flush data to disk
- fsync schema file parent to flush metadata to disk

https://github.com/NixOS/nix/issues/7064
This commit is contained in:
squalus 2022-09-19 11:15:31 -07:00
parent 9d860f3467
commit 1b595026e1
3 changed files with 45 additions and 7 deletions

View file

@ -158,7 +158,7 @@ void migrateCASchema(SQLite& db, Path schemaPath, AutoCloseFD& lockFd)
txn.commit(); txn.commit();
} }
writeFile(schemaPath, fmt("%d", nixCASchemaVersion)); writeFile(schemaPath, fmt("%d", nixCASchemaVersion), 0666, true);
lockFile(lockFd.get(), ltRead, true); lockFile(lockFd.get(), ltRead, true);
} }
} }
@ -281,7 +281,7 @@ LocalStore::LocalStore(const Params & params)
else if (curSchema == 0) { /* new store */ else if (curSchema == 0) { /* new store */
curSchema = nixSchemaVersion; curSchema = nixSchemaVersion;
openDB(*state, true); openDB(*state, true);
writeFile(schemaPath, (format("%1%") % nixSchemaVersion).str()); writeFile(schemaPath, (format("%1%") % nixSchemaVersion).str(), 0666, true);
} }
else if (curSchema < nixSchemaVersion) { else if (curSchema < nixSchemaVersion) {
@ -329,7 +329,7 @@ LocalStore::LocalStore(const Params & params)
txn.commit(); txn.commit();
} }
writeFile(schemaPath, (format("%1%") % nixSchemaVersion).str()); writeFile(schemaPath, (format("%1%") % nixSchemaVersion).str(), 0666, true);
lockFile(globalLock.get(), ltRead, true); lockFile(globalLock.get(), ltRead, true);
} }

View file

@ -353,7 +353,7 @@ void readFile(const Path & path, Sink & sink)
} }
void writeFile(const Path & path, std::string_view s, mode_t mode) void writeFile(const Path & path, std::string_view s, mode_t mode, bool sync)
{ {
AutoCloseFD fd = open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode); AutoCloseFD fd = open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode);
if (!fd) if (!fd)
@ -364,10 +364,16 @@ void writeFile(const Path & path, std::string_view s, mode_t mode)
e.addTrace({}, "writing file '%1%'", path); e.addTrace({}, "writing file '%1%'", path);
throw; throw;
} }
if (sync)
fd.fsync();
// Explicitly close to make sure exceptions are propagated.
fd.close();
if (sync)
syncParent(path);
} }
void writeFile(const Path & path, Source & source, mode_t mode) void writeFile(const Path & path, Source & source, mode_t mode, bool sync)
{ {
AutoCloseFD fd = open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode); AutoCloseFD fd = open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode);
if (!fd) if (!fd)
@ -386,6 +392,20 @@ void writeFile(const Path & path, Source & source, mode_t mode)
e.addTrace({}, "writing file '%1%'", path); e.addTrace({}, "writing file '%1%'", path);
throw; throw;
} }
if (sync)
fd.fsync();
// Explicitly close to make sure exceptions are propagated.
fd.close();
if (sync)
syncParent(path);
}
void syncParent(const Path & path)
{
AutoCloseFD fd = open(dirOf(path).c_str(), O_RDONLY, 0);
if (!fd)
throw SysError("opening file '%1%'", path);
fd.fsync();
} }
std::string readLine(int fd) std::string readLine(int fd)
@ -841,6 +861,20 @@ void AutoCloseFD::close()
} }
} }
void AutoCloseFD::fsync()
{
if (fd != -1) {
int result;
#if __APPLE__
result = ::fcntl(fd, F_FULLFSYNC);
#else
result = ::fsync(fd);
#endif
if (result == -1)
throw SysError("fsync file descriptor %1%", fd);
}
}
AutoCloseFD::operator bool() const AutoCloseFD::operator bool() const
{ {

View file

@ -115,9 +115,12 @@ std::string readFile(const Path & path);
void readFile(const Path & path, Sink & sink); void readFile(const Path & path, Sink & sink);
/* Write a string to a file. */ /* Write a string to a file. */
void writeFile(const Path & path, std::string_view s, mode_t mode = 0666); void writeFile(const Path & path, std::string_view s, mode_t mode = 0666, bool sync = false);
void writeFile(const Path & path, Source & source, mode_t mode = 0666); void writeFile(const Path & path, Source & source, mode_t mode = 0666, bool sync = false);
/* Flush a file's parent directory to disk */
void syncParent(const Path & path);
/* Read a line from a file descriptor. */ /* Read a line from a file descriptor. */
std::string readLine(int fd); std::string readLine(int fd);
@ -231,6 +234,7 @@ public:
explicit operator bool() const; explicit operator bool() const;
int release(); int release();
void close(); void close();
void fsync();
}; };