Call fsync on safeOutputFile

If a system crash occurs after a simple write file + rename operation
the target file that was renamed over may end up being empty if the data
for the file written had not yet been committed to disk.  This is
possible because no part of the open file, write data to it, close it
and then rename it ensure the data was actually written to disk prior to
the rename happening. See this discussion on the btrfs wiki[1].

Fix this possible data loss scenario by calling fsync on the temporary
file that is written prior to calling close on it.

[1] https://btrfs.wiki.kernel.org/index.php/FAQ#What_are_the_crash_guarantees_of_overwrite-by-rename.3F
This commit is contained in:
Hornwitser committed 2023-02-16 05:19:04 +01:00
1 parent 50703f8ae6
commit 1b2a20c395
1 file changed
+12 -1
+12 -1
View File
@@ -136,8 +136,19 @@ async function getTempFile(prefix, suffix, tmpdir) {
* @param {object|string} options - see fs.writeFile, `flag` must not be set.
*/
async function safeOutputFile(file, data, options={}) {
let directory = path.dirname(file);
if (!await fs.pathExists(directory)) {
await fs.mkdirs(directory);
}
let temporary = `${file}.tmp`;
await fs.outputFile(temporary, data, options);
let fd = await fs.open(temporary, "w");
try {
await fs.writeFile(fd, data, options);
await fs.fsync(fd);
} finally {
await fs.close(fd);
}
await fs.rename(temporary, file);
}