From 6b9c8bced66853ed36379204a70b30ac5be223d2 Mon Sep 17 00:00:00 2001
From: Jakob Ackermann <jakob.ackermann@overleaf.com>
Date: Mon, 17 May 2021 14:18:07 +0100
Subject: [PATCH] [ContentCacheManager] write streams to disk atomically

Use an intermediate file for writing to disk, then rename to the target.
---
 services/clsi/app/js/ContentCacheManager.js | 20 +++++++++++++++-----
 1 file changed, 15 insertions(+), 5 deletions(-)

diff --git a/services/clsi/app/js/ContentCacheManager.js b/services/clsi/app/js/ContentCacheManager.js
index 94ed9ebc00..a32a952ca5 100644
--- a/services/clsi/app/js/ContentCacheManager.js
+++ b/services/clsi/app/js/ContentCacheManager.js
@@ -100,17 +100,27 @@ async function writePdfStream(dir, hash, buffers) {
     //  ETags used for client side caching via browser internals.
     return false
   } catch (e) {}
-  const file = await fs.promises.open(filename, 'w')
+  const atomicWriteFilename = filename + '~'
+  const file = await fs.promises.open(atomicWriteFilename, 'w')
   if (Settings.enablePdfCachingDark) {
     // Write an empty file in dark mode.
     buffers = []
   }
   try {
-    for (const buffer of buffers) {
-      await file.write(buffer)
+    try {
+      for (const buffer of buffers) {
+        await file.write(buffer)
+      }
+    } finally {
+      await file.close()
+    }
+    await fs.promises.rename(atomicWriteFilename, filename)
+  } catch (err) {
+    try {
+      await fs.promises.unlink(atomicWriteFilename)
+    } catch (_) {
+      throw err
     }
-  } finally {
-    await file.close()
   }
   return true
 }