Add Ability for FileManager to Create Permanent Files

Atomically create permanent files in the file system.

Only using non-throwing APIs, even if the remove fails, its not too big of a deal.

#tlbmc
#bmc-bloom

PiperOrigin-RevId: 823707810
Change-Id: Ie4d3c284dfc45f95e7210aa62e5fd1e6a1b38e6a
diff --git a/tlbmc/redfish/routes/action_managers/file_manager.cc b/tlbmc/redfish/routes/action_managers/file_manager.cc
index 8ed29c3..6efd2e4 100644
--- a/tlbmc/redfish/routes/action_managers/file_manager.cc
+++ b/tlbmc/redfish/routes/action_managers/file_manager.cc
@@ -6,14 +6,19 @@
 #include <unistd.h>
 
 #include <cstddef>
+#include <filesystem>  // NOLINT
+#include <fstream>
 #include <string>
 #include <string_view>
+#include <system_error>  // NOLINT
 #include <utility>
 #include <vector>
 
 #include "absl/cleanup/cleanup.h"
+#include "absl/log/log.h"
 #include "absl/status/status.h"
 #include "absl/status/statusor.h"
+#include "absl/strings/str_cat.h"
 
 namespace milotic_tlbmc {
 
@@ -73,4 +78,44 @@
   return StagedTempFile::Create(content, temp_name_template);
 }
 
+absl::Status FileManager::WriteToFile(std::string_view content,
+                                      std::string_view path) {
+  // Create the parent directory if it doesn't exist.
+  std::filesystem::path parent_dir = std::filesystem::path(path).parent_path();
+  std::error_code create_parent_dir_ec;
+  // We don't care if the directory already exists.
+  if (std::filesystem::create_directories(parent_dir, create_parent_dir_ec);
+      create_parent_dir_ec) {
+    return absl::InternalError(
+        absl::StrCat("Failed to create parent directory for file: ", path,
+                     " with error: ", create_parent_dir_ec.message()));
+  }
+
+  std::string temp_path = absl::StrCat(path, ".tmp");
+  std::ofstream file(temp_path);
+  if (!file.is_open()) {
+    return absl::InternalError(
+        absl::StrCat("Failed creating staging file: ", temp_path));
+  }
+  file << content;
+  file.close();
+
+  // Atomically create the file by renaming the temporary file to the final name
+  std::error_code rename_ec;
+  if (std::filesystem::rename(temp_path, path, rename_ec); rename_ec) {
+    std::error_code remove_ec;
+    std::filesystem::remove(temp_path, remove_ec);
+    // Not too big of a deal if it doesn't clean up the temp file.
+    if (remove_ec) {
+      LOG(WARNING) << "Failed to delete temp file: " << temp_path
+                   << " with error: " << remove_ec.message();
+    }
+    return absl::InternalError(
+        absl::StrCat("Failed to create the final file: ", path,
+                     " with error: ", rename_ec.message()));
+  }
+
+  return absl::OkStatus();
+}
+
 }  // namespace milotic_tlbmc
diff --git a/tlbmc/redfish/routes/action_managers/file_manager.h b/tlbmc/redfish/routes/action_managers/file_manager.h
index aae3523..171eca4 100644
--- a/tlbmc/redfish/routes/action_managers/file_manager.h
+++ b/tlbmc/redfish/routes/action_managers/file_manager.h
@@ -4,6 +4,7 @@
 #include <string>
 #include <string_view>
 
+#include "absl/status/status.h"
 #include "absl/status/statusor.h"
 
 namespace milotic_tlbmc {
@@ -30,6 +31,10 @@
   // Create temporary file which is deleted as soon as the object is destroyed.
   static absl::StatusOr<StagedTempFile> CreateTempFile(
       std::string_view content, std::string_view temp_name_template);
+
+  // Create a permanent file
+  static absl::Status WriteToFile(std::string_view content,
+                                  std::string_view path);
 };
 
 }  // namespace milotic_tlbmc