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/file_util.h"
14 #include "base/files/file.h"
15 #include "base/files/file_path.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
24 namespace base {
25
26 namespace {
27
28 const int kDefaultCommitIntervalMs = 10000;
29
30 enum TempFileFailure {
31 FAILED_CREATING,
32 FAILED_OPENING,
33 FAILED_CLOSING,
34 FAILED_WRITING,
35 FAILED_RENAMING,
36 TEMP_FILE_FAILURE_MAX
37 };
38
LogFailure(const FilePath & path,TempFileFailure failure_code,const std::string & message)39 void 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
WriteFileAtomically(const FilePath & path,const std::string & data)50 bool 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
ImportantFileWriter(const FilePath & path,base::SequencedTaskRunner * task_runner)91 ImportantFileWriter::ImportantFileWriter(const FilePath& path,
92 base::SequencedTaskRunner* task_runner)
93 : path_(path),
94 task_runner_(task_runner),
95 serializer_(NULL),
96 commit_interval_(TimeDelta::FromMilliseconds(kDefaultCommitIntervalMs)),
97 weak_factory_(this) {
98 DCHECK(CalledOnValidThread());
99 DCHECK(task_runner_.get());
100 }
101
~ImportantFileWriter()102 ImportantFileWriter::~ImportantFileWriter() {
103 // We're usually a member variable of some other object, which also tends
104 // to be our serializer. It may not be safe to call back to the parent object
105 // being destructed.
106 DCHECK(!HasPendingWrite());
107 }
108
HasPendingWrite() const109 bool ImportantFileWriter::HasPendingWrite() const {
110 DCHECK(CalledOnValidThread());
111 return timer_.IsRunning();
112 }
113
WriteNow(const std::string & data)114 void ImportantFileWriter::WriteNow(const std::string& data) {
115 DCHECK(CalledOnValidThread());
116 if (data.length() > static_cast<size_t>(kint32max)) {
117 NOTREACHED();
118 return;
119 }
120
121 if (HasPendingWrite())
122 timer_.Stop();
123
124 if (!PostWriteTask(data)) {
125 // Posting the task to background message loop is not expected
126 // to fail, but if it does, avoid losing data and just hit the disk
127 // on the current thread.
128 NOTREACHED();
129
130 WriteFileAtomically(path_, data);
131 }
132 }
133
ScheduleWrite(DataSerializer * serializer)134 void ImportantFileWriter::ScheduleWrite(DataSerializer* serializer) {
135 DCHECK(CalledOnValidThread());
136
137 DCHECK(serializer);
138 serializer_ = serializer;
139
140 if (!timer_.IsRunning()) {
141 timer_.Start(FROM_HERE, commit_interval_, this,
142 &ImportantFileWriter::DoScheduledWrite);
143 }
144 }
145
DoScheduledWrite()146 void ImportantFileWriter::DoScheduledWrite() {
147 DCHECK(serializer_);
148 std::string data;
149 if (serializer_->SerializeData(&data)) {
150 WriteNow(data);
151 } else {
152 DLOG(WARNING) << "failed to serialize data to be saved in "
153 << path_.value().c_str();
154 }
155 serializer_ = NULL;
156 }
157
RegisterOnNextSuccessfulWriteCallback(const base::Closure & on_next_successful_write)158 void ImportantFileWriter::RegisterOnNextSuccessfulWriteCallback(
159 const base::Closure& on_next_successful_write) {
160 DCHECK(on_next_successful_write_.is_null());
161 on_next_successful_write_ = on_next_successful_write;
162 }
163
PostWriteTask(const std::string & data)164 bool ImportantFileWriter::PostWriteTask(const std::string& data) {
165 // TODO(gab): This code could always use PostTaskAndReplyWithResult and let
166 // ForwardSuccessfulWrite() no-op if |on_next_successful_write_| is null, but
167 // PostTaskAndReply causes memory leaks in tests (crbug.com/371974) and
168 // suppressing all of those is unrealistic hence we avoid most of them by
169 // using PostTask() in the typical scenario below.
170 if (!on_next_successful_write_.is_null()) {
171 return base::PostTaskAndReplyWithResult(
172 task_runner_,
173 FROM_HERE,
174 MakeCriticalClosure(
175 Bind(&ImportantFileWriter::WriteFileAtomically, path_, data)),
176 Bind(&ImportantFileWriter::ForwardSuccessfulWrite,
177 weak_factory_.GetWeakPtr()));
178 }
179 return task_runner_->PostTask(
180 FROM_HERE,
181 MakeCriticalClosure(
182 Bind(IgnoreResult(&ImportantFileWriter::WriteFileAtomically),
183 path_, data)));
184 }
185
ForwardSuccessfulWrite(bool result)186 void ImportantFileWriter::ForwardSuccessfulWrite(bool result) {
187 DCHECK(CalledOnValidThread());
188 if (result && !on_next_successful_write_.is_null()) {
189 on_next_successful_write_.Run();
190 on_next_successful_write_.Reset();
191 }
192 }
193
194 } // namespace base
195