1// Copyright (c) 2012 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 "net/base/file_stream_context.h"
6
7#include "base/files/file_path.h"
8#include "base/location.h"
9#include "base/message_loop/message_loop_proxy.h"
10#include "base/task_runner.h"
11#include "base/task_runner_util.h"
12#include "base/threading/thread_restrictions.h"
13#include "base/values.h"
14#include "net/base/net_errors.h"
15
16#if defined(OS_ANDROID)
17#include "base/android/content_uri_utils.h"
18#endif
19
20namespace net {
21
22namespace {
23
24void CallInt64ToInt(const CompletionCallback& callback, int64 result) {
25  callback.Run(static_cast<int>(result));
26}
27
28}  // namespace
29
30FileStream::Context::IOResult::IOResult()
31    : result(OK),
32      os_error(0) {
33}
34
35FileStream::Context::IOResult::IOResult(int64 result, int os_error)
36    : result(result),
37      os_error(os_error) {
38}
39
40// static
41FileStream::Context::IOResult FileStream::Context::IOResult::FromOSError(
42    int64 os_error) {
43  return IOResult(MapSystemError(os_error), os_error);
44}
45
46// ---------------------------------------------------------------------
47
48FileStream::Context::OpenResult::OpenResult() {
49}
50
51FileStream::Context::OpenResult::OpenResult(base::File file,
52                                            IOResult error_code)
53    : file(file.Pass()),
54      error_code(error_code) {
55}
56
57FileStream::Context::OpenResult::OpenResult(RValue other)
58    : file(other.object->file.Pass()),
59      error_code(other.object->error_code) {
60}
61
62FileStream::Context::OpenResult& FileStream::Context::OpenResult::operator=(
63    RValue other) {
64  if (this != other.object) {
65    file = other.object->file.Pass();
66    error_code = other.object->error_code;
67  }
68  return *this;
69}
70
71// ---------------------------------------------------------------------
72
73void FileStream::Context::Orphan() {
74  DCHECK(!orphaned_);
75
76  orphaned_ = true;
77
78  if (!async_in_progress_) {
79    CloseAndDelete();
80  } else if (file_.IsValid()) {
81#if defined(OS_WIN)
82    CancelIo(file_.GetPlatformFile());
83#endif
84  }
85}
86
87void FileStream::Context::Open(const base::FilePath& path,
88                               int open_flags,
89                               const CompletionCallback& callback) {
90  DCHECK(!async_in_progress_);
91
92  bool posted = base::PostTaskAndReplyWithResult(
93      task_runner_.get(),
94      FROM_HERE,
95      base::Bind(
96          &Context::OpenFileImpl, base::Unretained(this), path, open_flags),
97      base::Bind(&Context::OnOpenCompleted, base::Unretained(this), callback));
98  DCHECK(posted);
99
100  async_in_progress_ = true;
101}
102
103void FileStream::Context::Close(const CompletionCallback& callback) {
104  DCHECK(!async_in_progress_);
105  bool posted = base::PostTaskAndReplyWithResult(
106      task_runner_.get(),
107      FROM_HERE,
108      base::Bind(&Context::CloseFileImpl, base::Unretained(this)),
109      base::Bind(&Context::OnAsyncCompleted,
110                 base::Unretained(this),
111                 IntToInt64(callback)));
112  DCHECK(posted);
113
114  async_in_progress_ = true;
115}
116
117void FileStream::Context::Seek(base::File::Whence whence,
118                               int64 offset,
119                               const Int64CompletionCallback& callback) {
120  DCHECK(!async_in_progress_);
121
122  bool posted = base::PostTaskAndReplyWithResult(
123      task_runner_.get(),
124      FROM_HERE,
125      base::Bind(
126          &Context::SeekFileImpl, base::Unretained(this), whence, offset),
127      base::Bind(&Context::OnAsyncCompleted,
128                 base::Unretained(this),
129                 callback));
130  DCHECK(posted);
131
132  async_in_progress_ = true;
133}
134
135void FileStream::Context::Flush(const CompletionCallback& callback) {
136  DCHECK(!async_in_progress_);
137
138  bool posted = base::PostTaskAndReplyWithResult(
139      task_runner_.get(),
140      FROM_HERE,
141      base::Bind(&Context::FlushFileImpl, base::Unretained(this)),
142      base::Bind(&Context::OnAsyncCompleted,
143                 base::Unretained(this),
144                 IntToInt64(callback)));
145  DCHECK(posted);
146
147  async_in_progress_ = true;
148}
149
150FileStream::Context::OpenResult FileStream::Context::OpenFileImpl(
151    const base::FilePath& path, int open_flags) {
152#if defined(OS_POSIX)
153  // Always use blocking IO.
154  open_flags &= ~base::File::FLAG_ASYNC;
155#endif
156  base::File file;
157#if defined(OS_ANDROID)
158  if (path.IsContentUri()) {
159    // Check that only Read flags are set.
160    DCHECK_EQ(open_flags & ~base::File::FLAG_ASYNC,
161              base::File::FLAG_OPEN | base::File::FLAG_READ);
162    file = base::OpenContentUriForRead(path);
163  } else {
164#endif  // defined(OS_ANDROID)
165    // FileStream::Context actually closes the file asynchronously,
166    // independently from FileStream's destructor. It can cause problems for
167    // users wanting to delete the file right after FileStream deletion. Thus
168    // we are always adding SHARE_DELETE flag to accommodate such use case.
169    // TODO(rvargas): This sounds like a bug, as deleting the file would
170    // presumably happen on the wrong thread. There should be an async delete.
171    open_flags |= base::File::FLAG_SHARE_DELETE;
172    file.Initialize(path, open_flags);
173#if defined(OS_ANDROID)
174  }
175#endif  // defined(OS_ANDROID)
176  if (!file.IsValid())
177    return OpenResult(base::File(),
178                      IOResult::FromOSError(logging::GetLastSystemErrorCode()));
179
180  return OpenResult(file.Pass(), IOResult(OK, 0));
181}
182
183FileStream::Context::IOResult FileStream::Context::CloseFileImpl() {
184  file_.Close();
185  return IOResult(OK, 0);
186}
187
188FileStream::Context::IOResult FileStream::Context::FlushFileImpl() {
189  if (file_.Flush())
190    return IOResult(OK, 0);
191
192  return IOResult::FromOSError(logging::GetLastSystemErrorCode());
193}
194
195void FileStream::Context::OnOpenCompleted(const CompletionCallback& callback,
196                                          OpenResult open_result) {
197  file_ = open_result.file.Pass();
198  if (file_.IsValid() && !orphaned_)
199    OnFileOpened();
200
201  OnAsyncCompleted(IntToInt64(callback), open_result.error_code);
202}
203
204void FileStream::Context::CloseAndDelete() {
205  DCHECK(!async_in_progress_);
206
207  if (file_.IsValid()) {
208    bool posted = task_runner_.get()->PostTask(
209        FROM_HERE,
210        base::Bind(base::IgnoreResult(&Context::CloseFileImpl),
211                   base::Owned(this)));
212    DCHECK(posted);
213  } else {
214    delete this;
215  }
216}
217
218Int64CompletionCallback FileStream::Context::IntToInt64(
219    const CompletionCallback& callback) {
220  return base::Bind(&CallInt64ToInt, callback);
221}
222
223void FileStream::Context::OnAsyncCompleted(
224    const Int64CompletionCallback& callback,
225    const IOResult& result) {
226  // Reset this before Run() as Run() may issue a new async operation. Also it
227  // should be reset before Close() because it shouldn't run if any async
228  // operation is in progress.
229  async_in_progress_ = false;
230  if (orphaned_)
231    CloseAndDelete();
232  else
233    callback.Run(result.result);
234}
235
236}  // namespace net
237