1 // Copyright 2017 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 #ifdef UNSAFE_BUFFERS_BUILD
6 // TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
7 #pragma allow_unsafe_buffers
8 #endif
9
10 #include "net/disk_cache/simple/simple_file_tracker.h"
11
12 #include <algorithm>
13 #include <limits>
14 #include <memory>
15 #include <utility>
16
17 #include "base/files/file.h"
18 #include "base/logging.h"
19 #include "base/metrics/histogram_macros.h"
20 #include "base/not_fatal_until.h"
21 #include "base/synchronization/lock.h"
22 #include "net/disk_cache/disk_cache.h"
23 #include "net/disk_cache/simple/simple_histogram_enums.h"
24 #include "net/disk_cache/simple/simple_synchronous_entry.h"
25
26 namespace disk_cache {
27
28 namespace {
29
RecordFileDescripterLimiterOp(FileDescriptorLimiterOp op)30 void RecordFileDescripterLimiterOp(FileDescriptorLimiterOp op) {
31 UMA_HISTOGRAM_ENUMERATION("SimpleCache.FileDescriptorLimiterAction", op,
32 FD_LIMIT_OP_MAX);
33 }
34
35 } // namespace
36
SimpleFileTracker(int file_limit)37 SimpleFileTracker::SimpleFileTracker(int file_limit)
38 : file_limit_(file_limit) {}
39
~SimpleFileTracker()40 SimpleFileTracker::~SimpleFileTracker() {
41 DCHECK(lru_.empty());
42 DCHECK(tracked_files_.empty());
43 }
44
Register(const SimpleSynchronousEntry * owner,SubFile subfile,std::unique_ptr<base::File> file)45 void SimpleFileTracker::Register(const SimpleSynchronousEntry* owner,
46 SubFile subfile,
47 std::unique_ptr<base::File> file) {
48 DCHECK(file->IsValid());
49 std::vector<std::unique_ptr<base::File>> files_to_close;
50
51 {
52 base::AutoLock hold_lock(lock_);
53
54 // Make sure the list of everything with given hash exists.
55 auto insert_status =
56 tracked_files_.emplace(owner->entry_file_key().entry_hash,
57 std::vector<std::unique_ptr<TrackedFiles>>());
58
59 std::vector<std::unique_ptr<TrackedFiles>>& candidates =
60 insert_status.first->second;
61
62 // See if entry for |owner| already exists, if not append.
63 TrackedFiles* owners_files = nullptr;
64 for (const std::unique_ptr<TrackedFiles>& candidate : candidates) {
65 if (candidate->owner == owner) {
66 owners_files = candidate.get();
67 break;
68 }
69 }
70
71 if (!owners_files) {
72 candidates.emplace_back(std::make_unique<TrackedFiles>());
73 owners_files = candidates.back().get();
74 owners_files->owner = owner;
75 owners_files->key = owner->entry_file_key();
76 }
77
78 EnsureInFrontOfLRU(owners_files);
79
80 int file_index = static_cast<int>(subfile);
81 DCHECK_EQ(TrackedFiles::TF_NO_REGISTRATION,
82 owners_files->state[file_index]);
83 owners_files->files[file_index] = std::move(file);
84 owners_files->state[file_index] = TrackedFiles::TF_REGISTERED;
85 ++open_files_;
86 CloseFilesIfTooManyOpen(&files_to_close);
87 }
88 }
89
Acquire(BackendFileOperations * file_operations,const SimpleSynchronousEntry * owner,SubFile subfile)90 SimpleFileTracker::FileHandle SimpleFileTracker::Acquire(
91 BackendFileOperations* file_operations,
92 const SimpleSynchronousEntry* owner,
93 SubFile subfile) {
94 std::vector<std::unique_ptr<base::File>> files_to_close;
95
96 {
97 base::AutoLock hold_lock(lock_);
98 TrackedFiles* owners_files = Find(owner);
99 int file_index = static_cast<int>(subfile);
100
101 DCHECK_EQ(TrackedFiles::TF_REGISTERED, owners_files->state[file_index]);
102 owners_files->state[file_index] = TrackedFiles::TF_ACQUIRED;
103 EnsureInFrontOfLRU(owners_files);
104
105 // Check to see if we have to reopen the file. That might push us over the
106 // fd limit. CloseFilesIfTooManyOpen will not close anything in
107 // |*owners_files| since it's already in the the TF_ACQUIRED state.
108 if (owners_files->files[file_index] == nullptr) {
109 ReopenFile(file_operations, owners_files, subfile);
110 CloseFilesIfTooManyOpen(&files_to_close);
111 }
112
113 return FileHandle(this, owner, subfile,
114 owners_files->files[file_index].get());
115 }
116 }
117
TrackedFiles()118 SimpleFileTracker::TrackedFiles::TrackedFiles() {
119 std::fill(state, state + kSimpleEntryTotalFileCount, TF_NO_REGISTRATION);
120 }
121
122 SimpleFileTracker::TrackedFiles::~TrackedFiles() = default;
123
Empty() const124 bool SimpleFileTracker::TrackedFiles::Empty() const {
125 for (State s : state)
126 if (s != TF_NO_REGISTRATION)
127 return false;
128 return true;
129 }
130
HasOpenFiles() const131 bool SimpleFileTracker::TrackedFiles::HasOpenFiles() const {
132 for (const std::unique_ptr<base::File>& file : files)
133 if (file != nullptr)
134 return true;
135 return false;
136 }
137
Release(const SimpleSynchronousEntry * owner,SubFile subfile)138 void SimpleFileTracker::Release(const SimpleSynchronousEntry* owner,
139 SubFile subfile) {
140 std::vector<std::unique_ptr<base::File>> files_to_close;
141
142 {
143 base::AutoLock hold_lock(lock_);
144 TrackedFiles* owners_files = Find(owner);
145 int file_index = static_cast<int>(subfile);
146
147 DCHECK(owners_files->state[file_index] == TrackedFiles::TF_ACQUIRED ||
148 owners_files->state[file_index] ==
149 TrackedFiles::TF_ACQUIRED_PENDING_CLOSE);
150
151 // Prepare to executed deferred close, if any.
152 if (owners_files->state[file_index] ==
153 TrackedFiles::TF_ACQUIRED_PENDING_CLOSE) {
154 files_to_close.push_back(PrepareClose(owners_files, file_index));
155 } else {
156 owners_files->state[file_index] = TrackedFiles::TF_REGISTERED;
157 }
158
159 // It's possible that we were over limit and couldn't do much about it
160 // since everything was lent out, so now may be the time to close extra
161 // stuff.
162 CloseFilesIfTooManyOpen(&files_to_close);
163 }
164 }
165
Close(const SimpleSynchronousEntry * owner,SubFile subfile)166 void SimpleFileTracker::Close(const SimpleSynchronousEntry* owner,
167 SubFile subfile) {
168 std::unique_ptr<base::File> file_to_close;
169
170 {
171 base::AutoLock hold_lock(lock_);
172 TrackedFiles* owners_files = Find(owner);
173 int file_index = static_cast<int>(subfile);
174
175 DCHECK(owners_files->state[file_index] == TrackedFiles::TF_ACQUIRED ||
176 owners_files->state[file_index] == TrackedFiles::TF_REGISTERED);
177
178 if (owners_files->state[file_index] == TrackedFiles::TF_ACQUIRED) {
179 // The FD is currently acquired, so we can't clean up the TrackedFiles,
180 // just yet; even if this is the last close, so delay the close until it
181 // gets released.
182 owners_files->state[file_index] = TrackedFiles::TF_ACQUIRED_PENDING_CLOSE;
183 } else {
184 file_to_close = PrepareClose(owners_files, file_index);
185 }
186 }
187 }
188
Doom(const SimpleSynchronousEntry * owner,EntryFileKey * key)189 void SimpleFileTracker::Doom(const SimpleSynchronousEntry* owner,
190 EntryFileKey* key) {
191 base::AutoLock hold_lock(lock_);
192 auto iter = tracked_files_.find(key->entry_hash);
193 CHECK(iter != tracked_files_.end(), base::NotFatalUntil::M130);
194
195 uint64_t max_doom_gen = 0;
196 for (const std::unique_ptr<TrackedFiles>& file_with_same_hash :
197 iter->second) {
198 max_doom_gen =
199 std::max(max_doom_gen, file_with_same_hash->key.doom_generation);
200 }
201
202 // It would take >502 years to doom the same hash enough times (at 10^9 dooms
203 // per second) to wrap the 64 bit counter. Still, if it does wrap around,
204 // there is a security risk since we could confuse different keys.
205 CHECK_NE(max_doom_gen, std::numeric_limits<uint64_t>::max());
206 uint64_t new_doom_gen = max_doom_gen + 1;
207
208 // Update external key.
209 key->doom_generation = new_doom_gen;
210
211 // Update our own.
212 for (const std::unique_ptr<TrackedFiles>& file_with_same_hash :
213 iter->second) {
214 if (file_with_same_hash->owner == owner)
215 file_with_same_hash->key.doom_generation = new_doom_gen;
216 }
217 }
218
IsEmptyForTesting()219 bool SimpleFileTracker::IsEmptyForTesting() {
220 base::AutoLock hold_lock(lock_);
221 return tracked_files_.empty() && lru_.empty();
222 }
223
Find(const SimpleSynchronousEntry * owner)224 SimpleFileTracker::TrackedFiles* SimpleFileTracker::Find(
225 const SimpleSynchronousEntry* owner) {
226 auto candidates = tracked_files_.find(owner->entry_file_key().entry_hash);
227 CHECK(candidates != tracked_files_.end(), base::NotFatalUntil::M130);
228 for (const auto& candidate : candidates->second) {
229 if (candidate->owner == owner) {
230 return candidate.get();
231 }
232 }
233 LOG(DFATAL) << "SimpleFileTracker operation on non-found entry";
234 return nullptr;
235 }
236
PrepareClose(TrackedFiles * owners_files,int file_index)237 std::unique_ptr<base::File> SimpleFileTracker::PrepareClose(
238 TrackedFiles* owners_files,
239 int file_index) {
240 std::unique_ptr<base::File> file_out =
241 std::move(owners_files->files[file_index]);
242 owners_files->state[file_index] = TrackedFiles::TF_NO_REGISTRATION;
243 if (owners_files->Empty()) {
244 auto iter = tracked_files_.find(owners_files->key.entry_hash);
245 for (auto i = iter->second.begin(); i != iter->second.end(); ++i) {
246 if ((*i).get() == owners_files) {
247 if (owners_files->in_lru)
248 lru_.erase(owners_files->position_in_lru);
249 iter->second.erase(i);
250 break;
251 }
252 }
253 if (iter->second.empty())
254 tracked_files_.erase(iter);
255 }
256 if (file_out != nullptr)
257 --open_files_;
258 return file_out;
259 }
260
CloseFilesIfTooManyOpen(std::vector<std::unique_ptr<base::File>> * files_to_close)261 void SimpleFileTracker::CloseFilesIfTooManyOpen(
262 std::vector<std::unique_ptr<base::File>>* files_to_close) {
263 auto i = lru_.end();
264 while (open_files_ > file_limit_ && i != lru_.begin()) {
265 --i; // Point to the actual entry.
266 TrackedFiles* tracked_files = *i;
267 DCHECK(tracked_files->in_lru);
268 for (int j = 0; j < kSimpleEntryTotalFileCount; ++j) {
269 if (tracked_files->state[j] == TrackedFiles::TF_REGISTERED &&
270 tracked_files->files[j] != nullptr) {
271 files_to_close->push_back(std::move(tracked_files->files[j]));
272 --open_files_;
273 RecordFileDescripterLimiterOp(FD_LIMIT_CLOSE_FILE);
274 }
275 }
276
277 if (!tracked_files->HasOpenFiles()) {
278 // If there is nothing here that can possibly be closed, remove this from
279 // LRU for now so we don't have to rescan it next time we are here. If the
280 // files get re-opened (in Acquire), it will get added back in.
281 DCHECK_EQ(*tracked_files->position_in_lru, tracked_files);
282 DCHECK(i == tracked_files->position_in_lru);
283 // Note that we're erasing at i, which would make it invalid, so go back
284 // one element ahead to we can decrement from that on next iteration.
285 ++i;
286 lru_.erase(tracked_files->position_in_lru);
287 tracked_files->in_lru = false;
288 }
289 }
290 }
291
ReopenFile(BackendFileOperations * file_operations,TrackedFiles * owners_files,SubFile subfile)292 void SimpleFileTracker::ReopenFile(BackendFileOperations* file_operations,
293 TrackedFiles* owners_files,
294 SubFile subfile) {
295 int file_index = static_cast<int>(subfile);
296 DCHECK(owners_files->files[file_index] == nullptr);
297 int flags = base::File::FLAG_OPEN | base::File::FLAG_READ |
298 base::File::FLAG_WRITE | base::File::FLAG_WIN_SHARE_DELETE;
299 base::FilePath file_path =
300 owners_files->owner->GetFilenameForSubfile(subfile);
301 owners_files->files[file_index] =
302 std::make_unique<base::File>(file_operations->OpenFile(file_path, flags));
303 if (owners_files->files[file_index]->IsValid()) {
304 RecordFileDescripterLimiterOp(FD_LIMIT_REOPEN_FILE);
305
306 ++open_files_;
307 } else {
308 owners_files->files[file_index] = nullptr;
309 RecordFileDescripterLimiterOp(FD_LIMIT_FAIL_REOPEN_FILE);
310 }
311 }
312
EnsureInFrontOfLRU(TrackedFiles * owners_files)313 void SimpleFileTracker::EnsureInFrontOfLRU(TrackedFiles* owners_files) {
314 if (!owners_files->in_lru) {
315 lru_.push_front(owners_files);
316 owners_files->position_in_lru = lru_.begin();
317 owners_files->in_lru = true;
318 } else if (owners_files->position_in_lru != lru_.begin()) {
319 lru_.splice(lru_.begin(), lru_, owners_files->position_in_lru);
320 }
321 DCHECK_EQ(*owners_files->position_in_lru, owners_files);
322 }
323
324 SimpleFileTracker::FileHandle::FileHandle() = default;
325
FileHandle(SimpleFileTracker * file_tracker,const SimpleSynchronousEntry * entry,SimpleFileTracker::SubFile subfile,base::File * file)326 SimpleFileTracker::FileHandle::FileHandle(SimpleFileTracker* file_tracker,
327 const SimpleSynchronousEntry* entry,
328 SimpleFileTracker::SubFile subfile,
329 base::File* file)
330 : file_tracker_(file_tracker),
331 entry_(entry),
332 subfile_(subfile),
333 file_(file) {}
334
FileHandle(FileHandle && other)335 SimpleFileTracker::FileHandle::FileHandle(FileHandle&& other) {
336 *this = std::move(other);
337 }
338
~FileHandle()339 SimpleFileTracker::FileHandle::~FileHandle() {
340 file_ = nullptr;
341 if (entry_) {
342 file_tracker_->Release(entry_.ExtractAsDangling(), subfile_);
343 }
344 }
345
operator =(FileHandle && other)346 SimpleFileTracker::FileHandle& SimpleFileTracker::FileHandle::operator=(
347 FileHandle&& other) {
348 file_tracker_ = other.file_tracker_;
349 entry_ = other.entry_;
350 subfile_ = other.subfile_;
351 file_ = other.file_;
352 other.file_tracker_ = nullptr;
353 other.entry_ = nullptr;
354 other.file_ = nullptr;
355 return *this;
356 }
357
operator ->() const358 base::File* SimpleFileTracker::FileHandle::operator->() const {
359 return file_;
360 }
361
get() const362 base::File* SimpleFileTracker::FileHandle::get() const {
363 return file_;
364 }
365
IsOK() const366 bool SimpleFileTracker::FileHandle::IsOK() const {
367 return file_ && file_->IsValid();
368 }
369
370 } // namespace disk_cache
371