1 // Copyright 2014 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/browser/chromeos/file_system_provider/fileapi/file_stream_reader.h"
6
7 #include <utility>
8
9 #include "base/bind.h"
10 #include "base/files/file.h"
11 #include "base/macros.h"
12 #include "base/memory/ptr_util.h"
13 #include "base/memory/ref_counted.h"
14 #include "base/trace_event/trace_event.h"
15 #include "chrome/browser/chromeos/file_system_provider/abort_callback.h"
16 #include "chrome/browser/chromeos/file_system_provider/fileapi/provider_async_file_util.h"
17 #include "chrome/browser/chromeos/file_system_provider/mount_path_util.h"
18 #include "chrome/browser/chromeos/file_system_provider/provided_file_system_interface.h"
19 #include "chrome/browser/chromeos/file_system_provider/scoped_file_opener.h"
20 #include "content/public/browser/browser_task_traits.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "net/base/io_buffer.h"
23 #include "net/base/net_errors.h"
24
25 using content::BrowserThread;
26
27 namespace chromeos {
28 namespace file_system_provider {
29
30 // Converts net::CompletionOnceCallback to net::Int64CompletionOnceCallback.
Int64ToIntCompletionOnceCallback(net::CompletionOnceCallback callback,int64_t result)31 void Int64ToIntCompletionOnceCallback(net::CompletionOnceCallback callback,
32 int64_t result) {
33 std::move(callback).Run(static_cast<int>(result));
34 }
35
36 class FileStreamReader::OperationRunner
37 : public base::RefCountedThreadSafe<
38 FileStreamReader::OperationRunner,
39 content::BrowserThread::DeleteOnUIThread> {
40 public:
OperationRunner()41 OperationRunner() : file_handle_(0) {}
42
43 // Opens a file for reading and calls the completion callback. Must be called
44 // on UI thread.
OpenFileOnUIThread(const storage::FileSystemURL & url,storage::AsyncFileUtil::StatusCallback callback)45 void OpenFileOnUIThread(const storage::FileSystemURL& url,
46 storage::AsyncFileUtil::StatusCallback callback) {
47 DCHECK_CURRENTLY_ON(BrowserThread::UI);
48 DCHECK(abort_callback_.is_null());
49
50 util::FileSystemURLParser parser(url);
51 if (!parser.Parse()) {
52 content::GetIOThreadTaskRunner({})->PostTask(
53 FROM_HERE,
54 base::BindOnce(std::move(callback), base::File::FILE_ERROR_SECURITY));
55 return;
56 }
57
58 file_system_ = parser.file_system()->GetWeakPtr();
59 file_path_ = parser.file_path();
60 file_opener_.reset(new ScopedFileOpener(
61 parser.file_system(), parser.file_path(), OPEN_FILE_MODE_READ,
62 base::Bind(&OperationRunner::OnOpenFileCompletedOnUIThread, this,
63 base::Passed(&callback))));
64 }
65
66 // Requests reading contents of a file. |callback| will always run eventually.
67 // It can be called many times, until |has_more| is set to false. This
68 // function guarantees that it will succeed only if the file has not been
69 // changed while reading. Must be called on UI thread.
ReadFileOnUIThread(scoped_refptr<net::IOBuffer> buffer,int64_t offset,int length,ProvidedFileSystemInterface::ReadChunkReceivedCallback callback)70 void ReadFileOnUIThread(
71 scoped_refptr<net::IOBuffer> buffer,
72 int64_t offset,
73 int length,
74 ProvidedFileSystemInterface::ReadChunkReceivedCallback callback) {
75 DCHECK_CURRENTLY_ON(BrowserThread::UI);
76 DCHECK(abort_callback_.is_null());
77
78 // If the file system got unmounted, then abort the reading operation.
79 if (!file_system_.get()) {
80 content::GetIOThreadTaskRunner({})->PostTask(
81 FROM_HERE, base::BindOnce(callback, 0, false /* has_more */,
82 base::File::FILE_ERROR_ABORT));
83 return;
84 }
85
86 abort_callback_ = file_system_->ReadFile(
87 file_handle_,
88 buffer.get(),
89 offset,
90 length,
91 base::Bind(
92 &OperationRunner::OnReadFileCompletedOnUIThread, this, callback));
93 }
94
95 // Requests metadata of a file. |callback| will always run eventually.
96 // Must be called on UI thread.
GetMetadataOnUIThread(ProvidedFileSystemInterface::GetMetadataCallback callback)97 void GetMetadataOnUIThread(
98 ProvidedFileSystemInterface::GetMetadataCallback callback) {
99 DCHECK_CURRENTLY_ON(BrowserThread::UI);
100 DCHECK(abort_callback_.is_null());
101
102 // If the file system got unmounted, then abort the get length operation.
103 if (!file_system_.get()) {
104 content::GetIOThreadTaskRunner({})->PostTask(
105 FROM_HERE, base::BindOnce(std::move(callback),
106 base::WrapUnique<EntryMetadata>(NULL),
107 base::File::FILE_ERROR_ABORT));
108 return;
109 }
110
111 abort_callback_ = file_system_->GetMetadata(
112 file_path_,
113 ProvidedFileSystemInterface::METADATA_FIELD_SIZE |
114 ProvidedFileSystemInterface::METADATA_FIELD_MODIFICATION_TIME,
115 base::BindOnce(&OperationRunner::OnGetMetadataCompletedOnUIThread, this,
116 std::move(callback)));
117 }
118
119 // Aborts the most recent operation (if exists) and closes a file if opened.
120 // The runner must not be used anymore after calling this method.
CloseRunnerOnUIThread()121 void CloseRunnerOnUIThread() {
122 DCHECK_CURRENTLY_ON(BrowserThread::UI);
123
124 if (!abort_callback_.is_null())
125 std::move(abort_callback_).Run();
126
127 // Close the file (if opened).
128 file_opener_.reset();
129 }
130
131 private:
132 friend struct content::BrowserThread::DeleteOnThread<
133 content::BrowserThread::UI>;
134 friend class base::DeleteHelper<OperationRunner>;
135
~OperationRunner()136 virtual ~OperationRunner() {}
137
138 // Remembers a file handle for further operations and forwards the result to
139 // the IO thread.
OnOpenFileCompletedOnUIThread(storage::AsyncFileUtil::StatusCallback callback,int file_handle,base::File::Error result)140 void OnOpenFileCompletedOnUIThread(
141 storage::AsyncFileUtil::StatusCallback callback,
142 int file_handle,
143 base::File::Error result) {
144 DCHECK_CURRENTLY_ON(BrowserThread::UI);
145 abort_callback_.Reset();
146
147 if (result == base::File::FILE_OK)
148 file_handle_ = file_handle;
149
150 content::GetIOThreadTaskRunner({})->PostTask(
151 FROM_HERE, base::BindOnce(std::move(callback), result));
152 }
153
154 // Forwards a metadata to the IO thread.
OnGetMetadataCompletedOnUIThread(ProvidedFileSystemInterface::GetMetadataCallback callback,std::unique_ptr<EntryMetadata> metadata,base::File::Error result)155 void OnGetMetadataCompletedOnUIThread(
156 ProvidedFileSystemInterface::GetMetadataCallback callback,
157 std::unique_ptr<EntryMetadata> metadata,
158 base::File::Error result) {
159 DCHECK_CURRENTLY_ON(BrowserThread::UI);
160 abort_callback_.Reset();
161
162 content::GetIOThreadTaskRunner({})->PostTask(
163 FROM_HERE,
164 base::BindOnce(std::move(callback), std::move(metadata), result));
165 }
166
167 // Forwards a response of reading from a file to the IO thread.
OnReadFileCompletedOnUIThread(ProvidedFileSystemInterface::ReadChunkReceivedCallback chunk_received_callback,int chunk_length,bool has_more,base::File::Error result)168 void OnReadFileCompletedOnUIThread(
169 ProvidedFileSystemInterface::ReadChunkReceivedCallback
170 chunk_received_callback,
171 int chunk_length,
172 bool has_more,
173 base::File::Error result) {
174 DCHECK_CURRENTLY_ON(BrowserThread::UI);
175 if (!has_more)
176 abort_callback_.Reset();
177
178 content::GetIOThreadTaskRunner({})->PostTask(
179 FROM_HERE, base::BindOnce(chunk_received_callback, chunk_length,
180 has_more, result));
181 }
182
183 AbortCallback abort_callback_;
184 base::WeakPtr<ProvidedFileSystemInterface> file_system_;
185 base::FilePath file_path_;
186 std::unique_ptr<ScopedFileOpener> file_opener_;
187 int file_handle_;
188
189 DISALLOW_COPY_AND_ASSIGN(OperationRunner);
190 };
191
FileStreamReader(storage::FileSystemContext * context,const storage::FileSystemURL & url,int64_t initial_offset,const base::Time & expected_modification_time)192 FileStreamReader::FileStreamReader(storage::FileSystemContext* context,
193 const storage::FileSystemURL& url,
194 int64_t initial_offset,
195 const base::Time& expected_modification_time)
196 : url_(url),
197 current_offset_(initial_offset),
198 current_length_(0),
199 expected_modification_time_(expected_modification_time),
200 runner_(new OperationRunner),
201 state_(NOT_INITIALIZED) {}
202
~FileStreamReader()203 FileStreamReader::~FileStreamReader() {
204 // FileStreamReader doesn't have a Cancel() method like in FileStreamWriter.
205 // Therefore, aborting and/or closing an opened file is done from the
206 // destructor.
207 content::GetUIThreadTaskRunner({})->PostTask(
208 FROM_HERE,
209 base::BindOnce(&OperationRunner::CloseRunnerOnUIThread, runner_));
210
211 // If a read is in progress, mark it as completed.
212 TRACE_EVENT_NESTABLE_ASYNC_END0("file_system_provider",
213 "FileStreamReader::Read", this);
214 }
215
Initialize(base::OnceClosure pending_closure,net::Int64CompletionOnceCallback error_callback)216 void FileStreamReader::Initialize(
217 base::OnceClosure pending_closure,
218 net::Int64CompletionOnceCallback error_callback) {
219 DCHECK_EQ(NOT_INITIALIZED, state_);
220 state_ = INITIALIZING;
221
222 content::GetUIThreadTaskRunner({})->PostTask(
223 FROM_HERE,
224 base::BindOnce(&OperationRunner::OpenFileOnUIThread, runner_, url_,
225 base::BindOnce(&FileStreamReader::OnOpenFileCompleted,
226 weak_ptr_factory_.GetWeakPtr(),
227 std::move(pending_closure),
228 std::move(error_callback))));
229 }
230
OnOpenFileCompleted(base::OnceClosure pending_closure,net::Int64CompletionOnceCallback error_callback,base::File::Error result)231 void FileStreamReader::OnOpenFileCompleted(
232 base::OnceClosure pending_closure,
233 net::Int64CompletionOnceCallback error_callback,
234 base::File::Error result) {
235 DCHECK_CURRENTLY_ON(BrowserThread::IO);
236 DCHECK_EQ(INITIALIZING, state_);
237
238 // In case of an error, return immediately using the |error_callback| of the
239 // Read() or GetLength() pending request.
240 if (result != base::File::FILE_OK) {
241 state_ = FAILED;
242 std::move(error_callback).Run(net::FileErrorToNetError(result));
243 return;
244 }
245
246 DCHECK_EQ(base::File::FILE_OK, result);
247
248 // Verify the last modification time.
249 content::GetUIThreadTaskRunner({})->PostTask(
250 FROM_HERE,
251 base::BindOnce(&OperationRunner::GetMetadataOnUIThread, runner_,
252 base::BindOnce(&FileStreamReader::OnInitializeCompleted,
253 weak_ptr_factory_.GetWeakPtr(),
254 std::move(pending_closure),
255 std::move(error_callback))));
256 }
257
OnInitializeCompleted(base::OnceClosure pending_closure,net::Int64CompletionOnceCallback error_callback,std::unique_ptr<EntryMetadata> metadata,base::File::Error result)258 void FileStreamReader::OnInitializeCompleted(
259 base::OnceClosure pending_closure,
260 net::Int64CompletionOnceCallback error_callback,
261 std::unique_ptr<EntryMetadata> metadata,
262 base::File::Error result) {
263 DCHECK_CURRENTLY_ON(BrowserThread::IO);
264 DCHECK_EQ(INITIALIZING, state_);
265
266 // In case of an error, abort.
267 if (result != base::File::FILE_OK) {
268 state_ = FAILED;
269 std::move(error_callback).Run(net::FileErrorToNetError(result));
270 return;
271 }
272
273 // If the file modification time has changed, then abort. Note, that the file
274 // may be changed without affecting the modification time.
275 DCHECK(metadata.get());
276 if (!expected_modification_time_.is_null() &&
277 *metadata->modification_time != expected_modification_time_) {
278 state_ = FAILED;
279 std::move(error_callback).Run(net::ERR_UPLOAD_FILE_CHANGED);
280 return;
281 }
282
283 DCHECK_EQ(base::File::FILE_OK, result);
284 state_ = INITIALIZED;
285
286 // Run the task waiting for the initialization to be completed.
287 std::move(pending_closure).Run();
288 }
289
Read(net::IOBuffer * buffer,int buffer_length,net::CompletionOnceCallback callback)290 int FileStreamReader::Read(net::IOBuffer* buffer,
291 int buffer_length,
292 net::CompletionOnceCallback callback) {
293 DCHECK_CURRENTLY_ON(BrowserThread::IO);
294 TRACE_EVENT_NESTABLE_ASYNC_BEGIN1("file_system_provider",
295 "FileStreamReader::Read", this,
296 "buffer_length", buffer_length);
297
298 read_callback_ = std::move(callback);
299 switch (state_) {
300 case NOT_INITIALIZED:
301 // Lazily initialize with the first call to Read().
302 Initialize(
303 base::BindOnce(&FileStreamReader::ReadAfterInitialized,
304 weak_ptr_factory_.GetWeakPtr(),
305 base::WrapRefCounted(buffer), buffer_length,
306 base::BindRepeating(&FileStreamReader::OnReadCompleted,
307 weak_ptr_factory_.GetWeakPtr())),
308 base::BindOnce(&Int64ToIntCompletionOnceCallback,
309 base::BindOnce(&FileStreamReader::OnReadCompleted,
310 weak_ptr_factory_.GetWeakPtr())));
311 break;
312
313 case INITIALIZING:
314 NOTREACHED();
315 break;
316
317 case INITIALIZED:
318 ReadAfterInitialized(
319 buffer, buffer_length,
320 base::BindRepeating(&FileStreamReader::OnReadCompleted,
321 weak_ptr_factory_.GetWeakPtr()));
322 break;
323
324 case FAILED:
325 NOTREACHED();
326 break;
327 }
328
329 return net::ERR_IO_PENDING;
330 }
331
OnReadCompleted(int result)332 void FileStreamReader::OnReadCompleted(int result) {
333 DCHECK_CURRENTLY_ON(BrowserThread::IO);
334 std::move(read_callback_).Run(static_cast<int>(result));
335 TRACE_EVENT_NESTABLE_ASYNC_END0("file_system_provider",
336 "FileStreamReader::Read", this);
337 }
338
GetLength(net::Int64CompletionOnceCallback callback)339 int64_t FileStreamReader::GetLength(net::Int64CompletionOnceCallback callback) {
340 DCHECK_CURRENTLY_ON(BrowserThread::IO);
341
342 get_length_callback_ = std::move(callback);
343 switch (state_) {
344 case NOT_INITIALIZED:
345 // Lazily initialize with the first call to GetLength().
346 Initialize(base::BindOnce(&FileStreamReader::GetLengthAfterInitialized,
347 weak_ptr_factory_.GetWeakPtr()),
348 base::BindOnce(&FileStreamReader::OnGetLengthCompleted,
349 weak_ptr_factory_.GetWeakPtr()));
350 break;
351
352 case INITIALIZING:
353 NOTREACHED();
354 break;
355
356 case INITIALIZED:
357 GetLengthAfterInitialized();
358 break;
359
360 case FAILED:
361 NOTREACHED();
362 break;
363 }
364
365 return net::ERR_IO_PENDING;
366 }
367
OnGetLengthCompleted(int64_t result)368 void FileStreamReader::OnGetLengthCompleted(int64_t result) {
369 std::move(get_length_callback_).Run(result);
370 }
371
ReadAfterInitialized(scoped_refptr<net::IOBuffer> buffer,int buffer_length,const net::CompletionRepeatingCallback & callback)372 void FileStreamReader::ReadAfterInitialized(
373 scoped_refptr<net::IOBuffer> buffer,
374 int buffer_length,
375 const net::CompletionRepeatingCallback& callback) {
376 DCHECK_CURRENTLY_ON(BrowserThread::IO);
377 DCHECK_EQ(INITIALIZED, state_);
378
379 current_length_ = 0;
380 content::GetUIThreadTaskRunner({})->PostTask(
381 FROM_HERE, base::BindOnce(&OperationRunner::ReadFileOnUIThread, runner_,
382 buffer, current_offset_, buffer_length,
383 base::BindRepeating(
384 &FileStreamReader::OnReadChunkReceived,
385 weak_ptr_factory_.GetWeakPtr(), callback)));
386 }
387
GetLengthAfterInitialized()388 void FileStreamReader::GetLengthAfterInitialized() {
389 DCHECK_CURRENTLY_ON(BrowserThread::IO);
390 DCHECK_EQ(INITIALIZED, state_);
391
392 content::GetUIThreadTaskRunner({})->PostTask(
393 FROM_HERE,
394 base::BindOnce(
395 &OperationRunner::GetMetadataOnUIThread, runner_,
396 base::BindOnce(&FileStreamReader::OnGetMetadataForGetLengthReceived,
397 weak_ptr_factory_.GetWeakPtr())));
398 }
399
OnReadChunkReceived(const net::CompletionRepeatingCallback & callback,int chunk_length,bool has_more,base::File::Error result)400 void FileStreamReader::OnReadChunkReceived(
401 const net::CompletionRepeatingCallback& callback,
402 int chunk_length,
403 bool has_more,
404 base::File::Error result) {
405 DCHECK_CURRENTLY_ON(BrowserThread::IO);
406 DCHECK_EQ(INITIALIZED, state_);
407
408 current_length_ += chunk_length;
409
410 // If this is the last chunk with a success, then finalize.
411 if (!has_more && result == base::File::FILE_OK) {
412 current_offset_ += current_length_;
413 callback.Run(current_length_);
414 return;
415 }
416
417 // In case of an error, abort.
418 if (result != base::File::FILE_OK) {
419 DCHECK(!has_more);
420 state_ = FAILED;
421 callback.Run(net::FileErrorToNetError(result));
422 return;
423 }
424
425 // More data is about to come, so do not call the callback yet.
426 DCHECK(has_more);
427 }
428
OnGetMetadataForGetLengthReceived(std::unique_ptr<EntryMetadata> metadata,base::File::Error result)429 void FileStreamReader::OnGetMetadataForGetLengthReceived(
430 std::unique_ptr<EntryMetadata> metadata,
431 base::File::Error result) {
432 DCHECK_CURRENTLY_ON(BrowserThread::IO);
433 DCHECK_EQ(INITIALIZED, state_);
434
435 // In case of an error, abort.
436 if (result != base::File::FILE_OK) {
437 state_ = FAILED;
438 std::move(get_length_callback_).Run(net::FileErrorToNetError(result));
439 return;
440 }
441
442 // If the file modification time has changed, then abort. Note, that the file
443 // may be changed without affecting the modification time.
444 DCHECK(metadata.get());
445 if (!expected_modification_time_.is_null() &&
446 *metadata->modification_time != expected_modification_time_) {
447 std::move(get_length_callback_).Run(net::ERR_UPLOAD_FILE_CHANGED);
448 return;
449 }
450
451 DCHECK_EQ(base::File::FILE_OK, result);
452 std::move(get_length_callback_).Run(*metadata->size);
453 }
454
455 } // namespace file_system_provider
456 } // namespace chromeos
457