1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "base/files/important_file_writer.h" 6 7 #include <stddef.h> 8 #include <stdint.h> 9 #include <stdio.h> 10 #include <string> 11 #include <utility> 12 13 #include "base/bind.h" 14 #include "base/callback_helpers.h" 15 #include "base/critical_closure.h" 16 #include "base/debug/alias.h" 17 #include "base/files/file.h" 18 #include "base/files/file_path.h" 19 #include "base/files/file_util.h" 20 #include "base/logging.h" 21 #include "base/macros.h" 22 #include "base/metrics/histogram_functions.h" 23 #include "base/metrics/histogram_macros.h" 24 #include "base/numerics/safe_conversions.h" 25 #include "base/strings/string_number_conversions.h" 26 #include "base/strings/string_util.h" 27 #include "base/task_runner.h" 28 #include "base/task_runner_util.h" 29 #include "base/threading/thread.h" 30 #include "base/time/time.h" 31 #include "build/build_config.h" 32 33 namespace base { 34 35 namespace { 36 37 constexpr auto kDefaultCommitInterval = TimeDelta::FromSeconds(10); 38 39 // This enum is used to define the buckets for an enumerated UMA histogram. 40 // Hence, 41 // (a) existing enumerated constants should never be deleted or reordered, and 42 // (b) new constants should only be appended at the end of the enumeration. 43 enum TempFileFailure { 44 FAILED_CREATING, 45 FAILED_OPENING, 46 FAILED_CLOSING, // Unused. 47 FAILED_WRITING, 48 FAILED_RENAMING, 49 FAILED_FLUSHING, 50 TEMP_FILE_FAILURE_MAX 51 }; 52 53 // Helper function to write samples to a histogram with a dynamically assigned 54 // histogram name. Works with different error code types convertible to int 55 // which is the actual argument type of UmaHistogramExactLinear. 56 template <typename SampleType> 57 void UmaHistogramExactLinearWithSuffix(const char* histogram_name, 58 StringPiece histogram_suffix, 59 SampleType add_sample, 60 SampleType max_sample) { 61 static_assert(std::is_convertible<SampleType, int>::value, 62 "SampleType should be convertible to int"); 63 DCHECK(histogram_name); 64 std::string histogram_full_name(histogram_name); 65 if (!histogram_suffix.empty()) { 66 histogram_full_name.append("."); 67 histogram_full_name.append(histogram_suffix.data(), 68 histogram_suffix.length()); 69 } 70 UmaHistogramExactLinear(histogram_full_name, static_cast<int>(add_sample), 71 static_cast<int>(max_sample)); 72 } 73 74 // Helper function to write samples to a histogram with a dynamically assigned 75 // histogram name. Works with short timings from 1 ms up to 10 seconds (50 76 // buckets) which is the actual argument type of UmaHistogramTimes. 77 void UmaHistogramTimesWithSuffix(const char* histogram_name, 78 StringPiece histogram_suffix, 79 TimeDelta sample) { 80 DCHECK(histogram_name); 81 std::string histogram_full_name(histogram_name); 82 if (!histogram_suffix.empty()) { 83 histogram_full_name.append("."); 84 histogram_full_name.append(histogram_suffix.data(), 85 histogram_suffix.length()); 86 } 87 UmaHistogramTimes(histogram_full_name, sample); 88 } 89 90 void LogFailure(const FilePath& path, 91 StringPiece histogram_suffix, 92 TempFileFailure failure_code, 93 StringPiece message) { 94 UmaHistogramExactLinearWithSuffix("ImportantFile.TempFileFailures", 95 histogram_suffix, failure_code, 96 TEMP_FILE_FAILURE_MAX); 97 DPLOG(WARNING) << "temp file failure: " << path.value() << " : " << message; 98 } 99 100 // Helper function to call WriteFileAtomically() with a 101 // std::unique_ptr<std::string>. 102 void WriteScopedStringToFileAtomically( 103 const FilePath& path, 104 std::unique_ptr<std::string> data, 105 Closure before_write_callback, 106 Callback<void(bool success)> after_write_callback, 107 const std::string& histogram_suffix) { 108 if (!before_write_callback.is_null()) 109 before_write_callback.Run(); 110 111 TimeTicks start_time = TimeTicks::Now(); 112 bool result = 113 ImportantFileWriter::WriteFileAtomically(path, *data, histogram_suffix); 114 if (result) { 115 UmaHistogramTimesWithSuffix("ImportantFile.TimeToWrite", histogram_suffix, 116 TimeTicks::Now() - start_time); 117 } 118 119 if (!after_write_callback.is_null()) 120 after_write_callback.Run(result); 121 } 122 123 void DeleteTmpFile(const FilePath& tmp_file_path, 124 StringPiece histogram_suffix) { 125 if (!DeleteFile(tmp_file_path, false)) { 126 UmaHistogramExactLinearWithSuffix( 127 "ImportantFile.FileDeleteError", histogram_suffix, 128 -base::File::GetLastFileError(), -base::File::FILE_ERROR_MAX); 129 } 130 } 131 132 } // namespace 133 134 // static 135 bool ImportantFileWriter::WriteFileAtomically(const FilePath& path, 136 StringPiece data, 137 StringPiece histogram_suffix) { 138 #if defined(OS_CHROMEOS) 139 // On Chrome OS, chrome gets killed when it cannot finish shutdown quickly, 140 // and this function seems to be one of the slowest shutdown steps. 141 // Include some info to the report for investigation. crbug.com/418627 142 // TODO(hashimoto): Remove this. 143 struct { 144 size_t data_size; 145 char path[128]; 146 } file_info; 147 file_info.data_size = data.size(); 148 strlcpy(file_info.path, path.value().c_str(), arraysize(file_info.path)); 149 debug::Alias(&file_info); 150 #endif 151 152 // Write the data to a temp file then rename to avoid data loss if we crash 153 // while writing the file. Ensure that the temp file is on the same volume 154 // as target file, so it can be moved in one step, and that the temp file 155 // is securely created. 156 FilePath tmp_file_path; 157 if (!CreateTemporaryFileInDir(path.DirName(), &tmp_file_path)) { 158 UmaHistogramExactLinearWithSuffix( 159 "ImportantFile.FileCreateError", histogram_suffix, 160 -base::File::GetLastFileError(), -base::File::FILE_ERROR_MAX); 161 LogFailure(path, histogram_suffix, FAILED_CREATING, 162 "could not create temporary file"); 163 return false; 164 } 165 166 File tmp_file(tmp_file_path, File::FLAG_OPEN | File::FLAG_WRITE); 167 if (!tmp_file.IsValid()) { 168 UmaHistogramExactLinearWithSuffix( 169 "ImportantFile.FileOpenError", histogram_suffix, 170 -tmp_file.error_details(), -base::File::FILE_ERROR_MAX); 171 LogFailure(path, histogram_suffix, FAILED_OPENING, 172 "could not open temporary file"); 173 DeleteFile(tmp_file_path, false); 174 return false; 175 } 176 177 // If this fails in the wild, something really bad is going on. 178 const int data_length = checked_cast<int32_t>(data.length()); 179 int bytes_written = tmp_file.Write(0, data.data(), data_length); 180 if (bytes_written < data_length) { 181 UmaHistogramExactLinearWithSuffix( 182 "ImportantFile.FileWriteError", histogram_suffix, 183 -base::File::GetLastFileError(), -base::File::FILE_ERROR_MAX); 184 } 185 bool flush_success = tmp_file.Flush(); 186 tmp_file.Close(); 187 188 if (bytes_written < data_length) { 189 LogFailure(path, histogram_suffix, FAILED_WRITING, 190 "error writing, bytes_written=" + IntToString(bytes_written)); 191 DeleteTmpFile(tmp_file_path, histogram_suffix); 192 return false; 193 } 194 195 if (!flush_success) { 196 LogFailure(path, histogram_suffix, FAILED_FLUSHING, "error flushing"); 197 DeleteTmpFile(tmp_file_path, histogram_suffix); 198 return false; 199 } 200 201 base::File::Error replace_file_error = base::File::FILE_OK; 202 if (!ReplaceFile(tmp_file_path, path, &replace_file_error)) { 203 UmaHistogramExactLinearWithSuffix("ImportantFile.FileRenameError", 204 histogram_suffix, -replace_file_error, 205 -base::File::FILE_ERROR_MAX); 206 LogFailure(path, histogram_suffix, FAILED_RENAMING, 207 "could not rename temporary file"); 208 DeleteTmpFile(tmp_file_path, histogram_suffix); 209 return false; 210 } 211 212 return true; 213 } 214 215 ImportantFileWriter::ImportantFileWriter( 216 const FilePath& path, 217 scoped_refptr<SequencedTaskRunner> task_runner, 218 const char* histogram_suffix) 219 : ImportantFileWriter(path, 220 std::move(task_runner), 221 kDefaultCommitInterval, 222 histogram_suffix) {} 223 224 ImportantFileWriter::ImportantFileWriter( 225 const FilePath& path, 226 scoped_refptr<SequencedTaskRunner> task_runner, 227 TimeDelta interval, 228 const char* histogram_suffix) 229 : path_(path), 230 task_runner_(std::move(task_runner)), 231 serializer_(nullptr), 232 commit_interval_(interval), 233 histogram_suffix_(histogram_suffix ? histogram_suffix : ""), 234 weak_factory_(this) { 235 DCHECK(task_runner_); 236 } 237 238 ImportantFileWriter::~ImportantFileWriter() { 239 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); 240 // We're usually a member variable of some other object, which also tends 241 // to be our serializer. It may not be safe to call back to the parent object 242 // being destructed. 243 DCHECK(!HasPendingWrite()); 244 } 245 246 bool ImportantFileWriter::HasPendingWrite() const { 247 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); 248 return timer().IsRunning(); 249 } 250 251 void ImportantFileWriter::WriteNow(std::unique_ptr<std::string> data) { 252 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); 253 if (!IsValueInRangeForNumericType<int32_t>(data->length())) { 254 NOTREACHED(); 255 return; 256 } 257 258 Closure task = AdaptCallbackForRepeating( 259 BindOnce(&WriteScopedStringToFileAtomically, path_, std::move(data), 260 std::move(before_next_write_callback_), 261 std::move(after_next_write_callback_), histogram_suffix_)); 262 263 if (!task_runner_->PostTask(FROM_HERE, MakeCriticalClosure(task))) { 264 // Posting the task to background message loop is not expected 265 // to fail, but if it does, avoid losing data and just hit the disk 266 // on the current thread. 267 NOTREACHED(); 268 269 task.Run(); 270 } 271 ClearPendingWrite(); 272 } 273 274 void ImportantFileWriter::ScheduleWrite(DataSerializer* serializer) { 275 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); 276 277 DCHECK(serializer); 278 serializer_ = serializer; 279 280 if (!timer().IsRunning()) { 281 timer().Start( 282 FROM_HERE, commit_interval_, 283 Bind(&ImportantFileWriter::DoScheduledWrite, Unretained(this))); 284 } 285 } 286 287 void ImportantFileWriter::DoScheduledWrite() { 288 DCHECK(serializer_); 289 std::unique_ptr<std::string> data(new std::string); 290 if (serializer_->SerializeData(data.get())) { 291 WriteNow(std::move(data)); 292 } else { 293 DLOG(WARNING) << "failed to serialize data to be saved in " 294 << path_.value(); 295 } 296 ClearPendingWrite(); 297 } 298 299 void ImportantFileWriter::RegisterOnNextWriteCallbacks( 300 const Closure& before_next_write_callback, 301 const Callback<void(bool success)>& after_next_write_callback) { 302 before_next_write_callback_ = before_next_write_callback; 303 after_next_write_callback_ = after_next_write_callback; 304 } 305 306 void ImportantFileWriter::ClearPendingWrite() { 307 timer().Stop(); 308 serializer_ = nullptr; 309 } 310 311 void ImportantFileWriter::SetTimerForTesting(Timer* timer_override) { 312 timer_override_ = timer_override; 313 } 314 315 } // namespace base 316