1 // Copyright 2012 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 "net/disk_cache/blockfile/file.h"
6
7 #include <limits.h>
8 #include <utility>
9
10 #include "base/files/file_path.h"
11 #include "base/memory/raw_ptr.h"
12 #include "base/message_loop/message_pump_for_io.h"
13 #include "base/no_destructor.h"
14 #include "base/run_loop.h"
15 #include "base/strings/string_util.h"
16 #include "base/task/current_thread.h"
17 #include "base/task/thread_pool.h"
18 #include "base/task/thread_pool/thread_pool_instance.h"
19 #include "base/threading/platform_thread.h"
20 #include "net/base/net_errors.h"
21 #include "net/disk_cache/disk_cache.h"
22
23 namespace {
24
25 class CompletionHandler;
26 // Structure used for asynchronous operations.
27 struct MyOverlapped {
28 MyOverlapped(disk_cache::File* file, size_t offset,
29 disk_cache::FileIOCallback* callback);
~MyOverlapped__anon5afafd9f0111::MyOverlapped30 ~MyOverlapped() {}
overlapped__anon5afafd9f0111::MyOverlapped31 OVERLAPPED* overlapped() {
32 return &context_.overlapped;
33 }
34
35 base::MessagePumpForIO::IOContext context_;
36 scoped_refptr<disk_cache::File> file_;
37 scoped_refptr<CompletionHandler> completion_handler_;
38 raw_ptr<disk_cache::FileIOCallback> callback_;
39 };
40
41 static_assert(offsetof(MyOverlapped, context_) == 0,
42 "should start with overlapped");
43
44 // Helper class to handle the IO completion notifications from the message loop.
45 class CompletionHandler final : public base::MessagePumpForIO::IOHandler,
46 public base::RefCounted<CompletionHandler> {
47 public:
CompletionHandler()48 CompletionHandler() : base::MessagePumpForIO::IOHandler(FROM_HERE) {}
49 static CompletionHandler* Get();
50
51 CompletionHandler(const CompletionHandler&) = delete;
52 CompletionHandler& operator=(const CompletionHandler&) = delete;
53
54 private:
55 friend class base::RefCounted<CompletionHandler>;
~CompletionHandler()56 ~CompletionHandler() override {}
57
58 // implement base::MessagePumpForIO::IOHandler.
59 void OnIOCompleted(base::MessagePumpForIO::IOContext* context,
60 DWORD actual_bytes,
61 DWORD error) override;
62 };
63
Get()64 CompletionHandler* CompletionHandler::Get() {
65 static base::NoDestructor<scoped_refptr<CompletionHandler>> handler(
66 base::MakeRefCounted<CompletionHandler>());
67 return handler->get();
68 }
69
OnIOCompleted(base::MessagePumpForIO::IOContext * context,DWORD actual_bytes,DWORD error)70 void CompletionHandler::OnIOCompleted(
71 base::MessagePumpForIO::IOContext* context,
72 DWORD actual_bytes,
73 DWORD error) {
74 MyOverlapped* data = reinterpret_cast<MyOverlapped*>(context);
75
76 if (error) {
77 DCHECK(!actual_bytes);
78 actual_bytes = static_cast<DWORD>(net::ERR_CACHE_READ_FAILURE);
79 }
80
81 // `callback_` may self delete while in `OnFileIOComplete`.
82 if (data->callback_)
83 data->callback_.ExtractAsDangling()->OnFileIOComplete(
84 static_cast<int>(actual_bytes));
85
86 delete data;
87 }
88
MyOverlapped(disk_cache::File * file,size_t offset,disk_cache::FileIOCallback * callback)89 MyOverlapped::MyOverlapped(disk_cache::File* file, size_t offset,
90 disk_cache::FileIOCallback* callback) {
91 context_.overlapped.Offset = static_cast<DWORD>(offset);
92 file_ = file;
93 callback_ = callback;
94 completion_handler_ = CompletionHandler::Get();
95 }
96
97 } // namespace
98
99 namespace disk_cache {
100
File(base::File file)101 File::File(base::File file)
102 : init_(true), mixed_(true), sync_base_file_(std::move(file)) {}
103
Init(const base::FilePath & name)104 bool File::Init(const base::FilePath& name) {
105 DCHECK(!init_);
106 if (init_)
107 return false;
108
109 DWORD sharing = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
110 DWORD access = GENERIC_READ | GENERIC_WRITE | DELETE;
111 base_file_ =
112 base::File(CreateFile(name.value().c_str(), access, sharing, nullptr,
113 OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr));
114
115 if (!base_file_.IsValid())
116 return false;
117
118 base::CurrentIOThread::Get()->RegisterIOHandler(base_file_.GetPlatformFile(),
119 CompletionHandler::Get());
120
121 init_ = true;
122 sync_base_file_ = base::File(CreateFile(name.value().c_str(), access, sharing,
123 nullptr, OPEN_EXISTING, 0, nullptr));
124
125 if (!sync_base_file_.IsValid())
126 return false;
127
128 return true;
129 }
130
IsValid() const131 bool File::IsValid() const {
132 if (!init_)
133 return false;
134 return base_file_.IsValid() || sync_base_file_.IsValid();
135 }
136
Read(void * buffer,size_t buffer_len,size_t offset)137 bool File::Read(void* buffer, size_t buffer_len, size_t offset) {
138 DCHECK(init_);
139 if (buffer_len > ULONG_MAX || offset > LONG_MAX)
140 return false;
141
142 int ret = sync_base_file_.Read(offset, static_cast<char*>(buffer),
143 buffer_len);
144 return static_cast<int>(buffer_len) == ret;
145 }
146
Write(const void * buffer,size_t buffer_len,size_t offset)147 bool File::Write(const void* buffer, size_t buffer_len, size_t offset) {
148 DCHECK(init_);
149 if (buffer_len > ULONG_MAX || offset > ULONG_MAX)
150 return false;
151
152 int ret = sync_base_file_.Write(offset, static_cast<const char*>(buffer),
153 buffer_len);
154 return static_cast<int>(buffer_len) == ret;
155 }
156
157 // We have to increase the ref counter of the file before performing the IO to
158 // prevent the completion to happen with an invalid handle (if the file is
159 // closed while the IO is in flight).
Read(void * buffer,size_t buffer_len,size_t offset,FileIOCallback * callback,bool * completed)160 bool File::Read(void* buffer, size_t buffer_len, size_t offset,
161 FileIOCallback* callback, bool* completed) {
162 DCHECK(init_);
163 if (!callback) {
164 if (completed)
165 *completed = true;
166 return Read(buffer, buffer_len, offset);
167 }
168
169 if (buffer_len > ULONG_MAX || offset > ULONG_MAX)
170 return false;
171
172 MyOverlapped* data = new MyOverlapped(this, offset, callback);
173 DWORD size = static_cast<DWORD>(buffer_len);
174
175 DWORD actual;
176 if (!ReadFile(base_file_.GetPlatformFile(), buffer, size, &actual,
177 data->overlapped())) {
178 *completed = false;
179 if (GetLastError() == ERROR_IO_PENDING)
180 return true;
181 delete data;
182 return false;
183 }
184
185 // The operation completed already. We'll be called back anyway.
186 *completed = (actual == size);
187 DCHECK_EQ(size, actual);
188 data->callback_ = nullptr;
189 data->file_ = nullptr; // There is no reason to hold on to this anymore.
190 return *completed;
191 }
192
Write(const void * buffer,size_t buffer_len,size_t offset,FileIOCallback * callback,bool * completed)193 bool File::Write(const void* buffer, size_t buffer_len, size_t offset,
194 FileIOCallback* callback, bool* completed) {
195 DCHECK(init_);
196 if (!callback) {
197 if (completed)
198 *completed = true;
199 return Write(buffer, buffer_len, offset);
200 }
201
202 return AsyncWrite(buffer, buffer_len, offset, callback, completed);
203 }
204
205 File::~File() = default;
206
platform_file() const207 base::PlatformFile File::platform_file() const {
208 DCHECK(init_);
209 return base_file_.IsValid() ? base_file_.GetPlatformFile() :
210 sync_base_file_.GetPlatformFile();
211 }
212
AsyncWrite(const void * buffer,size_t buffer_len,size_t offset,FileIOCallback * callback,bool * completed)213 bool File::AsyncWrite(const void* buffer, size_t buffer_len, size_t offset,
214 FileIOCallback* callback, bool* completed) {
215 DCHECK(init_);
216 DCHECK(callback);
217 DCHECK(completed);
218 if (buffer_len > ULONG_MAX || offset > ULONG_MAX)
219 return false;
220
221 MyOverlapped* data = new MyOverlapped(this, offset, callback);
222 DWORD size = static_cast<DWORD>(buffer_len);
223
224 DWORD actual;
225 if (!WriteFile(base_file_.GetPlatformFile(), buffer, size, &actual,
226 data->overlapped())) {
227 *completed = false;
228 if (GetLastError() == ERROR_IO_PENDING)
229 return true;
230 delete data;
231 return false;
232 }
233
234 // The operation completed already. We'll be called back anyway.
235 *completed = (actual == size);
236 DCHECK_EQ(size, actual);
237 data->callback_ = nullptr;
238 data->file_ = nullptr; // There is no reason to hold on to this anymore.
239 return *completed;
240 }
241
SetLength(size_t length)242 bool File::SetLength(size_t length) {
243 DCHECK(init_);
244 if (length > ULONG_MAX)
245 return false;
246
247 DWORD size = static_cast<DWORD>(length);
248 HANDLE file = platform_file();
249 if (INVALID_SET_FILE_POINTER ==
250 SetFilePointer(file, size, nullptr, FILE_BEGIN))
251 return false;
252
253 return TRUE == SetEndOfFile(file);
254 }
255
GetLength()256 size_t File::GetLength() {
257 DCHECK(init_);
258 LARGE_INTEGER size;
259 HANDLE file = platform_file();
260 if (!GetFileSizeEx(file, &size))
261 return 0;
262 if (size.HighPart)
263 return ULONG_MAX;
264
265 return static_cast<size_t>(size.LowPart);
266 }
267
268 // Static.
WaitForPendingIOForTesting(int * num_pending_io)269 void File::WaitForPendingIOForTesting(int* num_pending_io) {
270 // Spin on the burn-down count until the file IO completes.
271 constexpr base::TimeDelta kMillisecond = base::Milliseconds(1);
272 for (; *num_pending_io; base::PlatformThread::Sleep(kMillisecond)) {
273 // This waits for callbacks running on worker threads.
274 base::ThreadPoolInstance::Get()->FlushForTesting(); // IN-TEST
275 // This waits for the "Reply" tasks running on the current MessageLoop.
276 base::RunLoop().RunUntilIdle();
277 }
278 }
279
280 // Static.
DropPendingIO()281 void File::DropPendingIO() {
282 }
283
284 } // namespace disk_cache
285