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
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,const scoped_refptr<base::SequencedTaskRunner> & task_runner)91 ImportantFileWriter::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
~ImportantFileWriter()103 ImportantFileWriter::~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
HasPendingWrite() const110 bool ImportantFileWriter::HasPendingWrite() const {
111 DCHECK(CalledOnValidThread());
112 return timer_.IsRunning();
113 }
114
WriteNow(const std::string & data)115 void 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
ScheduleWrite(DataSerializer * serializer)135 void 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
DoScheduledWrite()147 void 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
RegisterOnNextSuccessfulWriteCallback(const base::Closure & on_next_successful_write)159 void 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
PostWriteTask(const std::string & data)165 bool 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
ForwardSuccessfulWrite(bool result)187 void 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