From 1b2a20c395736799f1e9a1ff3b13a62a6b000c0b Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Thu, 16 Feb 2023 05:08:13 +0100 Subject: [PATCH] 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 --- packages/lib/file_ops.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/lib/file_ops.js b/packages/lib/file_ops.js index 0e14b39d..2d869e41 100644 --- a/packages/lib/file_ops.js +++ b/packages/lib/file_ops.js @@ -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); }