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 "chrome/common/important_file_writer.h"
6
7#include <stdio.h>
8
9#include <string>
10
11#include "base/file_path.h"
12#include "base/file_util.h"
13#include "base/logging.h"
14#include "base/message_loop_proxy.h"
15#include "base/string_number_conversions.h"
16#include "base/task.h"
17#include "base/threading/thread.h"
18#include "base/time.h"
19
20using base::TimeDelta;
21
22namespace {
23
24const int kDefaultCommitIntervalMs = 10000;
25
26class WriteToDiskTask : public Task {
27 public:
28  WriteToDiskTask(const FilePath& path, const std::string& data)
29      : path_(path),
30        data_(data) {
31  }
32
33  virtual void Run() {
34    // Write the data to a temp file then rename to avoid data loss if we crash
35    // while writing the file. Ensure that the temp file is on the same volume
36    // as target file, so it can be moved in one step, and that the temp file
37    // is securely created.
38    FilePath tmp_file_path;
39    FILE* tmp_file = file_util::CreateAndOpenTemporaryFileInDir(
40        path_.DirName(), &tmp_file_path);
41    if (!tmp_file) {
42      LogFailure("could not create temporary file");
43      return;
44    }
45
46    size_t bytes_written = fwrite(data_.data(), 1, data_.length(), tmp_file);
47    if (!file_util::CloseFile(tmp_file)) {
48      LogFailure("failed to close temporary file");
49      file_util::Delete(tmp_file_path, false);
50      return;
51    }
52    if (bytes_written < data_.length()) {
53      LogFailure("error writing, bytes_written=" +
54                 base::Uint64ToString(bytes_written));
55      file_util::Delete(tmp_file_path, false);
56      return;
57    }
58
59    if (!file_util::ReplaceFile(tmp_file_path, path_)) {
60      LogFailure("could not rename temporary file");
61      file_util::Delete(tmp_file_path, false);
62      return;
63    }
64  }
65
66 private:
67  void LogFailure(const std::string& message) {
68    PLOG(WARNING) << "failed to write " << path_.value()
69                  << ": " << message;
70  }
71
72  const FilePath path_;
73  const std::string data_;
74
75  DISALLOW_COPY_AND_ASSIGN(WriteToDiskTask);
76};
77
78}  // namespace
79
80ImportantFileWriter::ImportantFileWriter(
81    const FilePath& path, base::MessageLoopProxy* file_message_loop_proxy)
82        : path_(path),
83          file_message_loop_proxy_(file_message_loop_proxy),
84          serializer_(NULL),
85          commit_interval_(TimeDelta::FromMilliseconds(
86              kDefaultCommitIntervalMs)) {
87  DCHECK(CalledOnValidThread());
88  DCHECK(file_message_loop_proxy_.get());
89}
90
91ImportantFileWriter::~ImportantFileWriter() {
92  // We're usually a member variable of some other object, which also tends
93  // to be our serializer. It may not be safe to call back to the parent object
94  // being destructed.
95  DCHECK(!HasPendingWrite());
96}
97
98bool ImportantFileWriter::HasPendingWrite() const {
99  DCHECK(CalledOnValidThread());
100  return timer_.IsRunning();
101}
102
103void ImportantFileWriter::WriteNow(const std::string& data) {
104  DCHECK(CalledOnValidThread());
105
106  if (HasPendingWrite())
107    timer_.Stop();
108
109  if (!file_message_loop_proxy_->PostTask(
110      FROM_HERE, new WriteToDiskTask(path_, data))) {
111    // Posting the task to background message loop is not expected
112    // to fail, but if it does, avoid losing data and just hit the disk
113    // on the current thread.
114    NOTREACHED();
115
116    WriteToDiskTask write_task(path_, data);
117    write_task.Run();
118  }
119}
120
121void ImportantFileWriter::ScheduleWrite(DataSerializer* serializer) {
122  DCHECK(CalledOnValidThread());
123
124  DCHECK(serializer);
125  serializer_ = serializer;
126
127  if (!MessageLoop::current()) {
128    // Happens in unit tests.
129    DoScheduledWrite();
130    return;
131  }
132
133  if (!timer_.IsRunning()) {
134    timer_.Start(commit_interval_, this,
135                 &ImportantFileWriter::DoScheduledWrite);
136  }
137}
138
139void ImportantFileWriter::DoScheduledWrite() {
140  DCHECK(serializer_);
141  std::string data;
142  if (serializer_->SerializeData(&data)) {
143    WriteNow(data);
144  } else {
145    LOG(WARNING) << "failed to serialize data to be saved in "
146                 << path_.value();
147  }
148  serializer_ = NULL;
149}
150