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 const std::string& 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 scoped_ptr<std::string>. 59 bool WriteScopedStringToFileAtomically(const FilePath& path, 60 scoped_ptr<std::string> data) { 61 return ImportantFileWriter::WriteFileAtomically(path, *data); 62 } 63 64 } // namespace 65 66 // static 67 bool ImportantFileWriter::WriteFileAtomically(const FilePath& path, 68 const std::string& data) { 69 #if defined(OS_CHROMEOS) 70 // On Chrome OS, chrome gets killed when it cannot finish shutdown quickly, 71 // and this function seems to be one of the slowest shutdown steps. 72 // Include some info to the report for investigation. crbug.com/418627 73 // TODO(hashimoto): Remove this. 74 struct { 75 size_t data_size; 76 char path[128]; 77 } file_info; 78 file_info.data_size = data.size(); 79 strlcpy(file_info.path, path.value().c_str(), arraysize(file_info.path)); 80 debug::Alias(&file_info); 81 #endif 82 83 // Write the data to a temp file then rename to avoid data loss if we crash 84 // while writing the file. Ensure that the temp file is on the same volume 85 // as target file, so it can be moved in one step, and that the temp file 86 // is securely created. 87 FilePath tmp_file_path; 88 if (!CreateTemporaryFileInDir(path.DirName(), &tmp_file_path)) { 89 LogFailure(path, FAILED_CREATING, "could not create temporary file"); 90 return false; 91 } 92 93 File tmp_file(tmp_file_path, File::FLAG_OPEN | File::FLAG_WRITE); 94 if (!tmp_file.IsValid()) { 95 LogFailure(path, FAILED_OPENING, "could not open temporary file"); 96 return false; 97 } 98 99 // If this fails in the wild, something really bad is going on. 100 const int data_length = checked_cast<int32_t>(data.length()); 101 int bytes_written = tmp_file.Write(0, data.data(), data_length); 102 bool flush_success = tmp_file.Flush(); 103 tmp_file.Close(); 104 105 if (bytes_written < data_length) { 106 LogFailure(path, FAILED_WRITING, "error writing, bytes_written=" + 107 IntToString(bytes_written)); 108 DeleteFile(tmp_file_path, false); 109 return false; 110 } 111 112 if (!flush_success) { 113 LogFailure(path, FAILED_FLUSHING, "error flushing"); 114 DeleteFile(tmp_file_path, false); 115 return false; 116 } 117 118 if (!ReplaceFile(tmp_file_path, path, nullptr)) { 119 LogFailure(path, FAILED_RENAMING, "could not rename temporary file"); 120 DeleteFile(tmp_file_path, false); 121 return false; 122 } 123 124 return true; 125 } 126 127 ImportantFileWriter::ImportantFileWriter( 128 const FilePath& path, 129 const scoped_refptr<SequencedTaskRunner>& task_runner) 130 : ImportantFileWriter( 131 path, 132 task_runner, 133 TimeDelta::FromMilliseconds(kDefaultCommitIntervalMs)) { 134 } 135 136 ImportantFileWriter::ImportantFileWriter( 137 const FilePath& path, 138 const scoped_refptr<SequencedTaskRunner>& task_runner, 139 TimeDelta interval) 140 : path_(path), 141 task_runner_(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(scoped_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 scoped_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