xref: /aosp_15_r20/external/federated-compute/fcp/client/cache/temp_files.cc (revision 14675a029014e728ec732f129a32e299b2da0601)
1 /*
2  * Copyright 2022 Google LLC
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "fcp/client/cache/temp_files.h"
18 
19 #include <sys/file.h>
20 #include <unistd.h>
21 
22 #include <cstdlib>
23 #include <filesystem>
24 #include <fstream>
25 #include <memory>
26 #include <string>
27 #include <system_error>  // NOLINT
28 
29 #include "fcp/base/monitoring.h"
30 #include "fcp/client/diag_codes.pb.h"
31 
32 namespace fcp {
33 namespace client {
34 namespace cache {
35 namespace {
36 
DeleteFilesInDirectory(const std::filesystem::path & directory)37 absl::Status DeleteFilesInDirectory(const std::filesystem::path& directory) {
38   if (!std::filesystem::exists(directory)) {
39     return absl::InvalidArgumentError(
40         absl::StrCat("Directory does not exist: ", directory.string()));
41   }
42   absl::Status status = absl::OkStatus();
43   // Note this only iterates through the top level directory and will not
44   // traverse subdirectories.
45   for (auto& de : std::filesystem::directory_iterator(directory)) {
46     std::error_code error;
47     // Save the first error, but attempt to delete the other files.
48     if (!std::filesystem::remove(de.path(), error)) {
49       if (status.ok()) {
50         status = absl::InternalError(absl::StrCat(
51             "Failed to delete file with error code: ", error.value()));
52       }
53     }
54   }
55   return status;
56 }
57 
58 }  // namespace
59 
Create(const std::string & cache_dir,LogManager * log_manager)60 absl::StatusOr<std::unique_ptr<TempFiles>> TempFiles::Create(
61     const std::string& cache_dir, LogManager* log_manager) {
62   std::filesystem::path root_path(cache_dir);
63   if (!root_path.is_absolute()) {
64     return absl::InvalidArgumentError(
65         absl::StrCat("The provided path: ", cache_dir,
66                      "is invalid. The path must start with \"/\""));
67   }
68 
69   // Create fcp parent dir in the passed root dir.
70   std::filesystem::path fcp_base_dir = root_path / kParentDir;
71   std::error_code error;
72   std::filesystem::create_directories(fcp_base_dir, error);
73   if (error.value() != 0) {
74     return absl::InternalError(absl::StrCat(
75         "Failed to create TempFiles base directory ",
76         fcp_base_dir.generic_string(), " with error code ", error.value()));
77   }
78 
79   // Create directory in parent dir for temporary files.
80   std::filesystem::path temp_files_dir = fcp_base_dir / kTempFilesDir;
81   std::filesystem::create_directories(temp_files_dir, error);
82   if (error.value() != 0) {
83     return absl::InternalError(
84         absl::StrCat("Failed to create TempFiles temp file directory ",
85                      temp_files_dir.generic_string()));
86   }
87 
88   // We clean up the temp files dir on creation in case we failed to clean it up
89   // during a previous run (i.e. due to the training process getting killed
90   // etc.) and to make sure we don't end up in the pathological case where we
91   // are always crashing partway through training and stranding temp files
92   // because the TempFiles dtor never runs.
93   auto cleanup_status = DeleteFilesInDirectory(temp_files_dir);
94   if (!cleanup_status.ok()) {
95     log_manager->LogDiag(ProdDiagCode::TEMP_FILES_NATIVE_FAILED_TO_DELETE);
96     return cleanup_status;
97   }
98   return absl::WrapUnique(new TempFiles(temp_files_dir, log_manager));
99 }
100 
CreateTempFile(const std::string & prefix,const std::string & suffix)101 absl::StatusOr<std::string> TempFiles::CreateTempFile(
102     const std::string& prefix, const std::string& suffix) {
103   std::filesystem::path candidate_path;
104   int fd;
105   do {
106     candidate_path = temp_files_dir_ /
107                      absl::StrCat(prefix, std::to_string(std::rand()), suffix);
108   } while ((fd = open(candidate_path.c_str(), O_CREAT | O_EXCL | O_RDWR,
109                       S_IRWXU)) == -1 &&
110            errno == EEXIST);
111   close(fd);
112   std::ofstream tmp_file(candidate_path);
113   if (!tmp_file) {
114     return absl::InvalidArgumentError(
115         absl::StrCat("could not create file ", candidate_path.string()));
116   }
117 
118   return candidate_path.string();
119 }
120 
~TempFiles()121 TempFiles::~TempFiles() {
122   if (!DeleteFilesInDirectory(temp_files_dir_).ok()) {
123     log_manager_.LogDiag(ProdDiagCode::TEMP_FILES_NATIVE_FAILED_TO_DELETE);
124   }
125 }
126 
127 }  // namespace cache
128 }  // namespace client
129 }  // namespace fcp
130