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/critical_closure.h" 15 #include "base/debug/alias.h" 16 #include "base/files/file.h" 17 #include "base/files/file_path.h" 18 #include "base/files/file_util.h" 19 #include "base/logging.h" 20 #include "base/macros.h" 21 #include "base/metrics/histogram.h" 22 #include "base/numerics/safe_conversions.h" 23 #include "base/strings/string_number_conversions.h" 24 #include "base/strings/string_util.h" 25 #include "base/task_runner.h" 26 #include "base/task_runner_util.h" 27 #include "base/threading/thread.h" 28 #include "base/time/time.h" 29 #include "build/build_config.h" 30 31 namespace base { 32 33 namespace { 34 35 const int kDefaultCommitIntervalMs = 10000; 36 37 // This enum is used to define the buckets for an enumerated UMA histogram. 38 // Hence, 39 // (a) existing enumerated constants should never be deleted or reordered, and 40 // (b) new constants should only be appended at the end of the enumeration. 41 enum TempFileFailure { 42 FAILED_CREATING, 43 FAILED_OPENING, 44 FAILED_CLOSING, // Unused. 45 FAILED_WRITING, 46 FAILED_RENAMING, 47 FAILED_FLUSHING, 48 TEMP_FILE_FAILURE_MAX 49 }; 50 51 void LogFailure(const FilePath& path, TempFileFailure failure_code, 52 StringPiece message) { 53 UMA_HISTOGRAM_ENUMERATION("ImportantFile.TempFileFailures", failure_code, 54 TEMP_FILE_FAILURE_MAX); 55 DPLOG(WARNING) << "temp file failure: " << path.value() << " : " << message; 56 } 57 58 // Helper function to call WriteFileAtomically() with a 59 // std::unique_ptr<std::string>. 60 bool WriteScopedStringToFileAtomically(const FilePath& path, 61 std::unique_ptr<std::string> data) { 62 return ImportantFileWriter::WriteFileAtomically(path, *data); 63 } 64 65 } // namespace 66 67 // static 68 bool ImportantFileWriter::WriteFileAtomically(const FilePath& path, 69 StringPiece data) { 70 #if defined(OS_CHROMEOS) 71 // On Chrome OS, chrome gets killed when it cannot finish shutdown quickly, 72 // and this function seems to be one of the slowest shutdown steps. 73 // Include some info to the report for investigation. crbug.com/418627 74 // TODO(hashimoto): Remove this. 75 struct { 76 size_t data_size; 77 char path[128]; 78 } file_info; 79 file_info.data_size = data.size(); 80 strlcpy(file_info.path, path.value().c_str(), arraysize(file_info.path)); 81 debug::Alias(&file_info); 82 #endif 83 84 // Write the data to a temp file then rename to avoid data loss if we crash 85 // while writing the file. Ensure that the temp file is on the same volume 86 // as target file, so it can be moved in one step, and that the temp file 87 // is securely created. 88 FilePath tmp_file_path; 89 if (!CreateTemporaryFileInDir(path.DirName(), &tmp_file_path)) { 90 LogFailure(path, FAILED_CREATING, "could not create temporary file"); 91 return false; 92 } 93 94 File tmp_file(tmp_file_path, File::FLAG_OPEN | File::FLAG_WRITE); 95 if (!tmp_file.IsValid()) { 96 LogFailure(path, FAILED_OPENING, "could not open temporary file"); 97 return false; 98 } 99 100 // If this fails in the wild, something really bad is going on. 101 const int data_length = checked_cast<int32_t>(data.length()); 102 int bytes_written = tmp_file.Write(0, data.data(), data_length); 103 bool flush_success = tmp_file.Flush(); 104 tmp_file.Close(); 105 106 if (bytes_written < data_length) { 107 LogFailure(path, FAILED_WRITING, "error writing, bytes_written=" + 108 IntToString(bytes_written)); 109 DeleteFile(tmp_file_path, false); 110 return false; 111 } 112 113 if (!flush_success) { 114 LogFailure(path, FAILED_FLUSHING, "error flushing"); 115 DeleteFile(tmp_file_path, false); 116 return false; 117 } 118 119 if (!ReplaceFile(tmp_file_path, path, nullptr)) { 120 LogFailure(path, FAILED_RENAMING, "could not rename temporary file"); 121 DeleteFile(tmp_file_path, false); 122 return false; 123 } 124 125 return true; 126 } 127 128 ImportantFileWriter::ImportantFileWriter( 129 const FilePath& path, 130 scoped_refptr<SequencedTaskRunner> task_runner) 131 : ImportantFileWriter( 132 path, 133 std::move(task_runner), 134 TimeDelta::FromMilliseconds(kDefaultCommitIntervalMs)) {} 135 136 ImportantFileWriter::ImportantFileWriter( 137 const FilePath& path, 138 scoped_refptr<SequencedTaskRunner> task_runner, 139 TimeDelta interval) 140 : path_(path), 141 task_runner_(std::move(task_runner)), 142 serializer_(nullptr), 143 commit_interval_(interval), 144 weak_factory_(this) { 145 DCHECK(CalledOnValidThread()); 146 DCHECK(task_runner_); 147 } 148 149 ImportantFileWriter::~ImportantFileWriter() { 150 // We're usually a member variable of some other object, which also tends 151 // to be our serializer. It may not be safe to call back to the parent object 152 // being destructed. 153 DCHECK(!HasPendingWrite()); 154 } 155 156 bool ImportantFileWriter::HasPendingWrite() const { 157 DCHECK(CalledOnValidThread()); 158 return timer_.IsRunning(); 159 } 160 161 void ImportantFileWriter::WriteNow(std::unique_ptr<std::string> data) { 162 DCHECK(CalledOnValidThread()); 163 if (!IsValueInRangeForNumericType<int32_t>(data->length())) { 164 NOTREACHED(); 165 return; 166 } 167 168 if (HasPendingWrite()) 169 timer_.Stop(); 170 171 auto task = Bind(&WriteScopedStringToFileAtomically, path_, Passed(&data)); 172 if (!PostWriteTask(task)) { 173 // Posting the task to background message loop is not expected 174 // to fail, but if it does, avoid losing data and just hit the disk 175 // on the current thread. 176 NOTREACHED(); 177 178 task.Run(); 179 } 180 } 181 182 void ImportantFileWriter::ScheduleWrite(DataSerializer* serializer) { 183 DCHECK(CalledOnValidThread()); 184 185 DCHECK(serializer); 186 serializer_ = serializer; 187 188 if (!timer_.IsRunning()) { 189 timer_.Start(FROM_HERE, commit_interval_, this, 190 &ImportantFileWriter::DoScheduledWrite); 191 } 192 } 193 194 void ImportantFileWriter::DoScheduledWrite() { 195 DCHECK(serializer_); 196 std::unique_ptr<std::string> data(new std::string); 197 if (serializer_->SerializeData(data.get())) { 198 WriteNow(std::move(data)); 199 } else { 200 DLOG(WARNING) << "failed to serialize data to be saved in " 201 << path_.value(); 202 } 203 serializer_ = nullptr; 204 } 205 206 void ImportantFileWriter::RegisterOnNextSuccessfulWriteCallback( 207 const Closure& on_next_successful_write) { 208 DCHECK(on_next_successful_write_.is_null()); 209 on_next_successful_write_ = on_next_successful_write; 210 } 211 212 bool ImportantFileWriter::PostWriteTask(const Callback<bool()>& task) { 213 // TODO(gab): This code could always use PostTaskAndReplyWithResult and let 214 // ForwardSuccessfulWrite() no-op if |on_next_successful_write_| is null, but 215 // PostTaskAndReply causes memory leaks in tests (crbug.com/371974) and 216 // suppressing all of those is unrealistic hence we avoid most of them by 217 // using PostTask() in the typical scenario below. 218 if (!on_next_successful_write_.is_null()) { 219 return PostTaskAndReplyWithResult( 220 task_runner_.get(), 221 FROM_HERE, 222 MakeCriticalClosure(task), 223 Bind(&ImportantFileWriter::ForwardSuccessfulWrite, 224 weak_factory_.GetWeakPtr())); 225 } 226 return task_runner_->PostTask( 227 FROM_HERE, 228 MakeCriticalClosure(Bind(IgnoreResult(task)))); 229 } 230 231 void ImportantFileWriter::ForwardSuccessfulWrite(bool result) { 232 DCHECK(CalledOnValidThread()); 233 if (result && !on_next_successful_write_.is_null()) { 234 on_next_successful_write_.Run(); 235 on_next_successful_write_.Reset(); 236 } 237 } 238 239 } // namespace base 240