important_file_writer.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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_path.h"
15#include "base/logging.h"
16#include "base/metrics/histogram.h"
17#include "base/strings/string_number_conversions.h"
18#include "base/task_runner.h"
19#include "base/threading/thread.h"
20#include "base/time.h"
21
22namespace base {
23
24namespace {
25
26const int kDefaultCommitIntervalMs = 10000;
27
28enum TempFileFailure {
29  FAILED_CREATING,
30  FAILED_OPENING,
31  FAILED_CLOSING,
32  FAILED_WRITING,
33  FAILED_RENAMING,
34  TEMP_FILE_FAILURE_MAX
35};
36
37void LogFailure(const FilePath& path, TempFileFailure failure_code,
38                const std::string& message) {
39  UMA_HISTOGRAM_ENUMERATION("ImportantFile.TempFileFailures", failure_code,
40                            TEMP_FILE_FAILURE_MAX);
41  DPLOG(WARNING) << "temp file failure: " << path.value().c_str()
42                 << " : " << message;
43}
44
45}  // namespace
46
47// static
48bool ImportantFileWriter::WriteFileAtomically(const FilePath& path,
49                                              const std::string& data) {
50  // Write the data to a temp file then rename to avoid data loss if we crash
51  // while writing the file. Ensure that the temp file is on the same volume
52  // as target file, so it can be moved in one step, and that the temp file
53  // is securely created.
54  FilePath tmp_file_path;
55  if (!file_util::CreateTemporaryFileInDir(path.DirName(), &tmp_file_path)) {
56    LogFailure(path, FAILED_CREATING, "could not create temporary file");
57    return false;
58  }
59
60  int flags = PLATFORM_FILE_OPEN | PLATFORM_FILE_WRITE;
61  PlatformFile tmp_file =
62      CreatePlatformFile(tmp_file_path, flags, NULL, NULL);
63  if (tmp_file == kInvalidPlatformFileValue) {
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 = WritePlatformFile(
71      tmp_file, 0, data.data(), static_cast<int>(data.length()));
72  FlushPlatformFile(tmp_file);  // Ignore return value.
73
74  if (!ClosePlatformFile(tmp_file)) {
75    LogFailure(path, FAILED_CLOSING, "failed to close temporary file");
76    file_util::Delete(tmp_file_path, false);
77    return false;
78  }
79
80  if (bytes_written < static_cast<int>(data.length())) {
81    LogFailure(path, FAILED_WRITING, "error writing, bytes_written=" +
82               IntToString(bytes_written));
83    file_util::Delete(tmp_file_path, false);
84    return false;
85  }
86
87  if (!file_util::ReplaceFile(tmp_file_path, path)) {
88    LogFailure(path, FAILED_RENAMING, "could not rename temporary file");
89    file_util::Delete(tmp_file_path, false);
90    return false;
91  }
92
93  return true;
94}
95
96ImportantFileWriter::ImportantFileWriter(
97    const FilePath& path, base::SequencedTaskRunner* task_runner)
98        : path_(path),
99          task_runner_(task_runner),
100          serializer_(NULL),
101          commit_interval_(TimeDelta::FromMilliseconds(
102              kDefaultCommitIntervalMs)) {
103  DCHECK(CalledOnValidThread());
104  DCHECK(task_runner_.get());
105}
106
107ImportantFileWriter::~ImportantFileWriter() {
108  // We're usually a member variable of some other object, which also tends
109  // to be our serializer. It may not be safe to call back to the parent object
110  // being destructed.
111  DCHECK(!HasPendingWrite());
112}
113
114bool ImportantFileWriter::HasPendingWrite() const {
115  DCHECK(CalledOnValidThread());
116  return timer_.IsRunning();
117}
118
119void ImportantFileWriter::WriteNow(const std::string& data) {
120  DCHECK(CalledOnValidThread());
121  if (data.length() > static_cast<size_t>(kint32max)) {
122    NOTREACHED();
123    return;
124  }
125
126  if (HasPendingWrite())
127    timer_.Stop();
128
129  if (!task_runner_->PostTask(
130          FROM_HERE,
131          MakeCriticalClosure(
132              Bind(IgnoreResult(&ImportantFileWriter::WriteFileAtomically),
133                   path_, data)))) {
134    // Posting the task to background message loop is not expected
135    // to fail, but if it does, avoid losing data and just hit the disk
136    // on the current thread.
137    NOTREACHED();
138
139    WriteFileAtomically(path_, data);
140  }
141}
142
143void ImportantFileWriter::ScheduleWrite(DataSerializer* serializer) {
144  DCHECK(CalledOnValidThread());
145
146  DCHECK(serializer);
147  serializer_ = serializer;
148
149  if (!timer_.IsRunning()) {
150    timer_.Start(FROM_HERE, commit_interval_, this,
151                 &ImportantFileWriter::DoScheduledWrite);
152  }
153}
154
155void ImportantFileWriter::DoScheduledWrite() {
156  DCHECK(serializer_);
157  std::string data;
158  if (serializer_->SerializeData(&data)) {
159    WriteNow(data);
160  } else {
161    DLOG(WARNING) << "failed to serialize data to be saved in "
162                  << path_.value().c_str();
163  }
164  serializer_ = NULL;
165}
166
167}  // namespace base
168