1 // Copyright 2020 The Chromium Authors
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_cleaner.h"
6
7 #include <algorithm>
8 #include <functional>
9 #include <iterator>
10 #include <utility>
11
12 #include "base/files/file_enumerator.h"
13 #include "base/files/file_util.h"
14 #include "base/functional/bind.h"
15 #include "base/no_destructor.h"
16 #include "base/process/process.h"
17 #include "base/task/sequenced_task_runner.h"
18 #include "base/task/task_traits.h"
19 #include "base/task/thread_pool.h"
20 #include "base/time/time.h"
21 #include "build/build_config.h"
22
23 namespace base {
24
25 namespace {
26
GetUpperBoundTime()27 base::Time GetUpperBoundTime() {
28 #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS) || BUILDFLAG(IS_FUCHSIA)
29 // If process creation time is not available then use instance creation
30 // time as the upper-bound for old files. Modification times may be
31 // rounded-down to coarse-grained increments, e.g. FAT has 2s granularity,
32 // so it is necessary to set the upper-bound earlier than Now() by at least
33 // that margin to account for modification times being rounded-down.
34 return Time::Now() - Seconds(2);
35 #else
36 return Process::Current().CreationTime() - Seconds(2);
37 #endif
38 }
39
40 } // namespace
41
42 // static
GetInstance()43 ImportantFileWriterCleaner& ImportantFileWriterCleaner::GetInstance() {
44 static NoDestructor<ImportantFileWriterCleaner> instance;
45 return *instance;
46 }
47
48 // static
AddDirectory(const FilePath & directory)49 void ImportantFileWriterCleaner::AddDirectory(const FilePath& directory) {
50 auto& instance = GetInstance();
51 scoped_refptr<SequencedTaskRunner> task_runner;
52 {
53 AutoLock scoped_lock(instance.task_runner_lock_);
54 task_runner = instance.task_runner_;
55 }
56 if (!task_runner)
57 return;
58 if (task_runner->RunsTasksInCurrentSequence()) {
59 instance.AddDirectoryImpl(directory);
60 } else {
61 // Unretained is safe here since the cleaner instance is never destroyed.
62 task_runner->PostTask(
63 FROM_HERE, BindOnce(&ImportantFileWriterCleaner::AddDirectoryImpl,
64 Unretained(&instance), directory));
65 }
66 }
67
Initialize()68 void ImportantFileWriterCleaner::Initialize() {
69 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
70 AutoLock scoped_lock(task_runner_lock_);
71 DCHECK(!task_runner_ ||
72 task_runner_ == SequencedTaskRunner::GetCurrentDefault());
73 task_runner_ = SequencedTaskRunner::GetCurrentDefault();
74 }
75
Start()76 void ImportantFileWriterCleaner::Start() {
77 #if DCHECK_IS_ON()
78 {
79 AutoLock scoped_lock(task_runner_lock_);
80 DCHECK(task_runner_);
81 }
82 #endif
83 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
84
85 if (is_started())
86 return;
87
88 started_ = true;
89
90 if (!pending_directories_.empty())
91 ScheduleTask();
92 }
93
Stop()94 void ImportantFileWriterCleaner::Stop() {
95 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
96
97 if (!is_started())
98 return;
99
100 if (is_running())
101 stop_flag_.store(true, std::memory_order_relaxed);
102 else
103 DoStop();
104 }
105
UninitializeForTesting()106 void ImportantFileWriterCleaner::UninitializeForTesting() {
107 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
108 DCHECK(!is_started());
109 {
110 AutoLock scoped_lock(task_runner_lock_);
111 task_runner_ = nullptr;
112 }
113 // AddDirectory may have been called after Stop. Clear the containers just in
114 // case.
115 important_directories_.clear();
116 pending_directories_.clear();
117 DETACH_FROM_SEQUENCE(sequence_checker_);
118 }
119
GetUpperBoundTimeForTest() const120 base::Time ImportantFileWriterCleaner::GetUpperBoundTimeForTest() const {
121 return upper_bound_time_;
122 }
123
ImportantFileWriterCleaner()124 ImportantFileWriterCleaner::ImportantFileWriterCleaner()
125 : upper_bound_time_(GetUpperBoundTime()) {
126 DETACH_FROM_SEQUENCE(sequence_checker_);
127 }
128
AddDirectoryImpl(const FilePath & directory)129 void ImportantFileWriterCleaner::AddDirectoryImpl(const FilePath& directory) {
130 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
131
132 if (!important_directories_.insert(directory).second)
133 return; // This directory has already been seen.
134
135 pending_directories_.push_back(directory);
136
137 if (!is_started())
138 return; // Nothing more to do if Start() has not been called.
139
140 // Start the background task if it's not already running. If it is running, a
141 // new task will be posted on completion of the current one by
142 // OnBackgroundTaskFinished to handle all directories added while it was
143 // running.
144 if (!is_running())
145 ScheduleTask();
146 }
147
ScheduleTask()148 void ImportantFileWriterCleaner::ScheduleTask() {
149 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
150 DCHECK(is_started());
151 DCHECK(!is_running());
152 DCHECK(!pending_directories_.empty());
153 DCHECK(!stop_flag_.load(std::memory_order_relaxed));
154
155 // Pass the set of directories to be processed.
156 running_ = ThreadPool::PostTaskAndReplyWithResult(
157 FROM_HERE,
158 {TaskPriority::BEST_EFFORT, TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN,
159 MayBlock()},
160 BindOnce(&ImportantFileWriterCleaner::CleanInBackground,
161 upper_bound_time_, std::move(pending_directories_),
162 std::ref(stop_flag_)),
163 // Unretained is safe here since the cleaner instance is never destroyed.
164 BindOnce(&ImportantFileWriterCleaner::OnBackgroundTaskFinished,
165 Unretained(this)));
166 }
167
168 // static
CleanInBackground(Time upper_bound_time,std::vector<FilePath> directories,std::atomic_bool & stop_flag)169 bool ImportantFileWriterCleaner::CleanInBackground(
170 Time upper_bound_time,
171 std::vector<FilePath> directories,
172 std::atomic_bool& stop_flag) {
173 DCHECK(!directories.empty());
174 for (auto scan = directories.begin(), end = directories.end(); scan != end;
175 ++scan) {
176 const auto& directory = *scan;
177 FileEnumerator file_enum(
178 directory, /*recursive=*/false, FileEnumerator::FILES,
179 FormatTemporaryFileName(FILE_PATH_LITERAL("*")).value());
180 for (FilePath path = file_enum.Next(); !path.empty();
181 path = file_enum.Next()) {
182 const FileEnumerator::FileInfo info = file_enum.GetInfo();
183 if (info.GetLastModifiedTime() >= upper_bound_time)
184 continue;
185 // Cleanup is a best-effort process, so ignore any failures here and
186 // continue to clean as much as possible. Metrics tell us that ~98.4% of
187 // directories are cleaned with no failures.
188 DeleteFile(path);
189 // Break out without checking for the next file if a stop is requested.
190 if (stop_flag.load(std::memory_order_relaxed))
191 return false;
192 }
193 }
194 return true;
195 }
196
OnBackgroundTaskFinished(bool processing_completed)197 void ImportantFileWriterCleaner::OnBackgroundTaskFinished(
198 bool processing_completed) {
199 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
200
201 running_ = false;
202
203 // There are no other accessors of |stop_flag_| at this point, so atomic
204 // operations aren't needed. There is no way to read it without such, so use
205 // the same (relaxed) ordering as elsewhere.
206 const bool stop = stop_flag_.exchange(false, std::memory_order_relaxed);
207 DCHECK(stop || processing_completed);
208
209 if (stop) {
210 DoStop();
211 } else if (!pending_directories_.empty()) {
212 // Run the task again with the new directories.
213 ScheduleTask();
214 } // else do nothing until a new directory is added.
215 }
216
DoStop()217 void ImportantFileWriterCleaner::DoStop() {
218 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
219 DCHECK(is_started());
220 DCHECK(!is_running());
221
222 important_directories_.clear();
223 pending_directories_.clear();
224 started_ = false;
225 }
226
227 } // namespace base
228