• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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