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