[clangd] Change diskbackedstorage to be atomic

Summary:
There was a chance that multiple clangd instances could try to write
same shard, in which case we would get a malformed file most likely. This patch
changes the writing mechanism to first write to a temporary file and then rename
it to fit real destination. Which is guaranteed to be atomic by POSIX.

Reviewers: ilya-biryukov

Subscribers: ioeric, MaskRay, jkorous, arphaman, jfb, cfe-commits

Differential Revision: https://reviews.llvm.org/D55417

git-svn-id: https://llvm.org/svn/llvm-project/clang-tools-extra/trunk@349348 91177308-0d34-0410-b5e6-96231b3b80d8
diff --git a/clangd/index/BackgroundIndexStorage.cpp b/clangd/index/BackgroundIndexStorage.cpp
index 4325ded..a83bec6 100644
--- a/clangd/index/BackgroundIndexStorage.cpp
+++ b/clangd/index/BackgroundIndexStorage.cpp
@@ -9,6 +9,8 @@
 
 #include "Logger.h"
 #include "index/Background.h"
+#include "llvm/ADT/ScopeExit.h"
+#include "llvm/Support/Error.h"
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/Path.h"
@@ -33,6 +35,34 @@
   return ShardRootSS.str();
 }
 
+llvm::Error
+writeAtomically(llvm::StringRef OutPath,
+                llvm::function_ref<void(llvm::raw_ostream &)> Writer) {
+  // Write to a temporary file first.
+  llvm::SmallString<128> TempPath;
+  int FD;
+  auto EC =
+      llvm::sys::fs::createUniqueFile(OutPath + ".tmp.%%%%%%%%", FD, TempPath);
+  if (EC)
+    return llvm::errorCodeToError(EC);
+  // Make sure temp file is destroyed on failure.
+  auto RemoveOnFail =
+      llvm::make_scope_exit([TempPath] { llvm::sys::fs::remove(TempPath); });
+  llvm::raw_fd_ostream OS(FD, /*shouldClose=*/true);
+  Writer(OS);
+  OS.close();
+  if (OS.has_error())
+    return llvm::errorCodeToError(OS.error());
+  // Then move to real location.
+  EC = llvm::sys::fs::rename(TempPath, OutPath);
+  if (EC)
+    return llvm::errorCodeToError(EC);
+  // If everything went well, we already moved the file to another name. So
+  // don't delete the file, as the name might be taken by another file.
+  RemoveOnFail.release();
+  return llvm::ErrorSuccess();
+}
+
 // Uses disk as a storage for index shards. Creates a directory called
 // ".clangd-index/" under the path provided during construction.
 class DiskBackedIndexStorage : public BackgroundIndexStorage {
@@ -70,14 +100,9 @@
 
   llvm::Error storeShard(llvm::StringRef ShardIdentifier,
                          IndexFileOut Shard) const override {
-    auto ShardPath = getShardPathFromFilePath(DiskShardRoot, ShardIdentifier);
-    std::error_code EC;
-    llvm::raw_fd_ostream OS(ShardPath, EC);
-    if (EC)
-      return llvm::errorCodeToError(EC);
-    OS << Shard;
-    OS.close();
-    return llvm::errorCodeToError(OS.error());
+    return writeAtomically(
+        getShardPathFromFilePath(DiskShardRoot, ShardIdentifier),
+        [&Shard](llvm::raw_ostream &OS) { OS << Shard; });
   }
 };