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
20 using base::TimeDelta;
21
22 namespace {
23
24 const int kDefaultCommitIntervalMs = 10000;
25
26 class WriteToDiskTask : public Task {
27 public:
WriteToDiskTask(const FilePath & path,const std::string & data)28 WriteToDiskTask(const FilePath& path, const std::string& data)
29 : path_(path),
30 data_(data) {
31 }
32
Run()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:
LogFailure(const std::string & message)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
ImportantFileWriter(const FilePath & path,base::MessageLoopProxy * file_message_loop_proxy)80 ImportantFileWriter::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
~ImportantFileWriter()91 ImportantFileWriter::~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
HasPendingWrite() const98 bool ImportantFileWriter::HasPendingWrite() const {
99 DCHECK(CalledOnValidThread());
100 return timer_.IsRunning();
101 }
102
WriteNow(const std::string & data)103 void 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
ScheduleWrite(DataSerializer * serializer)121 void 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
DoScheduledWrite()139 void 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