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