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 <stdio.h> 8 9#include <string> 10 11#include "base/bind.h" 12#include "base/critical_closure.h" 13#include "base/files/file.h" 14#include "base/files/file_path.h" 15#include "base/files/file_util.h" 16#include "base/logging.h" 17#include "base/metrics/histogram.h" 18#include "base/strings/string_number_conversions.h" 19#include "base/task_runner.h" 20#include "base/task_runner_util.h" 21#include "base/threading/thread.h" 22#include "base/time/time.h" 23 24namespace base { 25 26namespace { 27 28const int kDefaultCommitIntervalMs = 10000; 29 30enum TempFileFailure { 31 FAILED_CREATING, 32 FAILED_OPENING, 33 FAILED_CLOSING, 34 FAILED_WRITING, 35 FAILED_RENAMING, 36 TEMP_FILE_FAILURE_MAX 37}; 38 39void LogFailure(const FilePath& path, TempFileFailure failure_code, 40 const std::string& message) { 41 UMA_HISTOGRAM_ENUMERATION("ImportantFile.TempFileFailures", failure_code, 42 TEMP_FILE_FAILURE_MAX); 43 DPLOG(WARNING) << "temp file failure: " << path.value().c_str() 44 << " : " << message; 45} 46 47} // namespace 48 49// static 50bool ImportantFileWriter::WriteFileAtomically(const FilePath& path, 51 const std::string& data) { 52 // Write the data to a temp file then rename to avoid data loss if we crash 53 // while writing the file. Ensure that the temp file is on the same volume 54 // as target file, so it can be moved in one step, and that the temp file 55 // is securely created. 56 FilePath tmp_file_path; 57 if (!base::CreateTemporaryFileInDir(path.DirName(), &tmp_file_path)) { 58 LogFailure(path, FAILED_CREATING, "could not create temporary file"); 59 return false; 60 } 61 62 File tmp_file(tmp_file_path, File::FLAG_OPEN | File::FLAG_WRITE); 63 if (!tmp_file.IsValid()) { 64 LogFailure(path, FAILED_OPENING, "could not open temporary file"); 65 return false; 66 } 67 68 // If this happens in the wild something really bad is going on. 69 CHECK_LE(data.length(), static_cast<size_t>(kint32max)); 70 int bytes_written = tmp_file.Write(0, data.data(), 71 static_cast<int>(data.length())); 72 tmp_file.Flush(); // Ignore return value. 73 tmp_file.Close(); 74 75 if (bytes_written < static_cast<int>(data.length())) { 76 LogFailure(path, FAILED_WRITING, "error writing, bytes_written=" + 77 IntToString(bytes_written)); 78 base::DeleteFile(tmp_file_path, false); 79 return false; 80 } 81 82 if (!base::ReplaceFile(tmp_file_path, path, NULL)) { 83 LogFailure(path, FAILED_RENAMING, "could not rename temporary file"); 84 base::DeleteFile(tmp_file_path, false); 85 return false; 86 } 87 88 return true; 89} 90 91ImportantFileWriter::ImportantFileWriter( 92 const FilePath& path, 93 const scoped_refptr<base::SequencedTaskRunner>& task_runner) 94 : path_(path), 95 task_runner_(task_runner), 96 serializer_(NULL), 97 commit_interval_(TimeDelta::FromMilliseconds(kDefaultCommitIntervalMs)), 98 weak_factory_(this) { 99 DCHECK(CalledOnValidThread()); 100 DCHECK(task_runner_.get()); 101} 102 103ImportantFileWriter::~ImportantFileWriter() { 104 // We're usually a member variable of some other object, which also tends 105 // to be our serializer. It may not be safe to call back to the parent object 106 // being destructed. 107 DCHECK(!HasPendingWrite()); 108} 109 110bool ImportantFileWriter::HasPendingWrite() const { 111 DCHECK(CalledOnValidThread()); 112 return timer_.IsRunning(); 113} 114 115void ImportantFileWriter::WriteNow(const std::string& data) { 116 DCHECK(CalledOnValidThread()); 117 if (data.length() > static_cast<size_t>(kint32max)) { 118 NOTREACHED(); 119 return; 120 } 121 122 if (HasPendingWrite()) 123 timer_.Stop(); 124 125 if (!PostWriteTask(data)) { 126 // Posting the task to background message loop is not expected 127 // to fail, but if it does, avoid losing data and just hit the disk 128 // on the current thread. 129 NOTREACHED(); 130 131 WriteFileAtomically(path_, data); 132 } 133} 134 135void ImportantFileWriter::ScheduleWrite(DataSerializer* serializer) { 136 DCHECK(CalledOnValidThread()); 137 138 DCHECK(serializer); 139 serializer_ = serializer; 140 141 if (!timer_.IsRunning()) { 142 timer_.Start(FROM_HERE, commit_interval_, this, 143 &ImportantFileWriter::DoScheduledWrite); 144 } 145} 146 147void ImportantFileWriter::DoScheduledWrite() { 148 DCHECK(serializer_); 149 std::string data; 150 if (serializer_->SerializeData(&data)) { 151 WriteNow(data); 152 } else { 153 DLOG(WARNING) << "failed to serialize data to be saved in " 154 << path_.value().c_str(); 155 } 156 serializer_ = NULL; 157} 158 159void ImportantFileWriter::RegisterOnNextSuccessfulWriteCallback( 160 const base::Closure& on_next_successful_write) { 161 DCHECK(on_next_successful_write_.is_null()); 162 on_next_successful_write_ = on_next_successful_write; 163} 164 165bool ImportantFileWriter::PostWriteTask(const std::string& data) { 166 // TODO(gab): This code could always use PostTaskAndReplyWithResult and let 167 // ForwardSuccessfulWrite() no-op if |on_next_successful_write_| is null, but 168 // PostTaskAndReply causes memory leaks in tests (crbug.com/371974) and 169 // suppressing all of those is unrealistic hence we avoid most of them by 170 // using PostTask() in the typical scenario below. 171 if (!on_next_successful_write_.is_null()) { 172 return base::PostTaskAndReplyWithResult( 173 task_runner_.get(), 174 FROM_HERE, 175 MakeCriticalClosure( 176 Bind(&ImportantFileWriter::WriteFileAtomically, path_, data)), 177 Bind(&ImportantFileWriter::ForwardSuccessfulWrite, 178 weak_factory_.GetWeakPtr())); 179 } 180 return task_runner_->PostTask( 181 FROM_HERE, 182 MakeCriticalClosure( 183 Bind(IgnoreResult(&ImportantFileWriter::WriteFileAtomically), 184 path_, data))); 185} 186 187void ImportantFileWriter::ForwardSuccessfulWrite(bool result) { 188 DCHECK(CalledOnValidThread()); 189 if (result && !on_next_successful_write_.is_null()) { 190 on_next_successful_write_.Run(); 191 on_next_successful_write_.Reset(); 192 } 193} 194 195} // namespace base 196