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