1// Copyright (c) 2010 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.h"
6
7#include <windows.h>
8
9#include "base/file_path.h"
10#include "base/logging.h"
11#include "base/message_loop.h"
12#include "base/metrics/histogram.h"
13#include "base/threading/thread_restrictions.h"
14#include "net/base/net_errors.h"
15
16namespace net {
17
18// Ensure that we can just use our Whence values directly.
19COMPILE_ASSERT(FROM_BEGIN == FILE_BEGIN, bad_whence_begin);
20COMPILE_ASSERT(FROM_CURRENT == FILE_CURRENT, bad_whence_current);
21COMPILE_ASSERT(FROM_END == FILE_END, bad_whence_end);
22
23static void SetOffset(OVERLAPPED* overlapped, const LARGE_INTEGER& offset) {
24  overlapped->Offset = offset.LowPart;
25  overlapped->OffsetHigh = offset.HighPart;
26}
27
28static void IncrementOffset(OVERLAPPED* overlapped, DWORD count) {
29  LARGE_INTEGER offset;
30  offset.LowPart = overlapped->Offset;
31  offset.HighPart = overlapped->OffsetHigh;
32  offset.QuadPart += static_cast<LONGLONG>(count);
33  SetOffset(overlapped, offset);
34}
35
36static int MapErrorCode(DWORD err) {
37  switch (err) {
38    case ERROR_FILE_NOT_FOUND:
39    case ERROR_PATH_NOT_FOUND:
40      return ERR_FILE_NOT_FOUND;
41    case ERROR_ACCESS_DENIED:
42      return ERR_ACCESS_DENIED;
43    case ERROR_SUCCESS:
44      return OK;
45    default:
46      LOG(WARNING) << "Unknown error " << err << " mapped to net::ERR_FAILED";
47      return ERR_FAILED;
48  }
49}
50
51// FileStream::AsyncContext ----------------------------------------------
52
53class FileStream::AsyncContext : public MessageLoopForIO::IOHandler {
54 public:
55  AsyncContext(FileStream* owner)
56      : owner_(owner), context_(), callback_(NULL), is_closing_(false) {
57    context_.handler = this;
58  }
59  ~AsyncContext();
60
61  void IOCompletionIsPending(CompletionCallback* callback);
62
63  OVERLAPPED* overlapped() { return &context_.overlapped; }
64  CompletionCallback* callback() const { return callback_; }
65
66 private:
67  virtual void OnIOCompleted(MessageLoopForIO::IOContext* context,
68                             DWORD bytes_read, DWORD error);
69
70  FileStream* owner_;
71  MessageLoopForIO::IOContext context_;
72  CompletionCallback* callback_;
73  bool is_closing_;
74};
75
76FileStream::AsyncContext::~AsyncContext() {
77  is_closing_ = true;
78  bool waited = false;
79  base::TimeTicks start = base::TimeTicks::Now();
80  while (callback_) {
81    waited = true;
82    MessageLoopForIO::current()->WaitForIOCompletion(INFINITE, this);
83  }
84  if (waited) {
85    // We want to see if we block the message loop for too long.
86    UMA_HISTOGRAM_TIMES("AsyncIO.FileStreamClose",
87                        base::TimeTicks::Now() - start);
88  }
89}
90
91void FileStream::AsyncContext::IOCompletionIsPending(
92    CompletionCallback* callback) {
93  DCHECK(!callback_);
94  callback_ = callback;
95}
96
97void FileStream::AsyncContext::OnIOCompleted(
98    MessageLoopForIO::IOContext* context, DWORD bytes_read, DWORD error) {
99  DCHECK(&context_ == context);
100  DCHECK(callback_);
101
102  if (is_closing_) {
103    callback_ = NULL;
104    return;
105  }
106
107  int result = static_cast<int>(bytes_read);
108  if (error && error != ERROR_HANDLE_EOF)
109    result = MapErrorCode(error);
110
111  if (bytes_read)
112    IncrementOffset(&context->overlapped, bytes_read);
113
114  CompletionCallback* temp = NULL;
115  std::swap(temp, callback_);
116  temp->Run(result);
117}
118
119// FileStream ------------------------------------------------------------
120
121FileStream::FileStream()
122    : file_(INVALID_HANDLE_VALUE),
123      open_flags_(0),
124      auto_closed_(true) {
125}
126
127FileStream::FileStream(base::PlatformFile file, int flags)
128    : file_(file),
129      open_flags_(flags),
130      auto_closed_(false) {
131  // If the file handle is opened with base::PLATFORM_FILE_ASYNC, we need to
132  // make sure we will perform asynchronous File IO to it.
133  if (flags & base::PLATFORM_FILE_ASYNC) {
134    async_context_.reset(new AsyncContext(this));
135    MessageLoopForIO::current()->RegisterIOHandler(file_,
136                                                   async_context_.get());
137  }
138}
139
140FileStream::~FileStream() {
141  if (auto_closed_)
142    Close();
143}
144
145void FileStream::Close() {
146  if (file_ != INVALID_HANDLE_VALUE)
147    CancelIo(file_);
148
149  async_context_.reset();
150  if (file_ != INVALID_HANDLE_VALUE) {
151    CloseHandle(file_);
152    file_ = INVALID_HANDLE_VALUE;
153  }
154}
155
156int FileStream::Open(const FilePath& path, int open_flags) {
157  if (IsOpen()) {
158    DLOG(FATAL) << "File is already open!";
159    return ERR_UNEXPECTED;
160  }
161
162  open_flags_ = open_flags;
163  file_ = base::CreatePlatformFile(path, open_flags_, NULL, NULL);
164  if (file_ == INVALID_HANDLE_VALUE) {
165    DWORD error = GetLastError();
166    LOG(WARNING) << "Failed to open file: " << error;
167    return MapErrorCode(error);
168  }
169
170  if (open_flags_ & base::PLATFORM_FILE_ASYNC) {
171    async_context_.reset(new AsyncContext(this));
172    MessageLoopForIO::current()->RegisterIOHandler(file_,
173                                                   async_context_.get());
174  }
175
176  return OK;
177}
178
179bool FileStream::IsOpen() const {
180  return file_ != INVALID_HANDLE_VALUE;
181}
182
183int64 FileStream::Seek(Whence whence, int64 offset) {
184  if (!IsOpen())
185    return ERR_UNEXPECTED;
186  DCHECK(!async_context_.get() || !async_context_->callback());
187
188  LARGE_INTEGER distance, result;
189  distance.QuadPart = offset;
190  DWORD move_method = static_cast<DWORD>(whence);
191  if (!SetFilePointerEx(file_, distance, &result, move_method)) {
192    DWORD error = GetLastError();
193    LOG(WARNING) << "SetFilePointerEx failed: " << error;
194    return MapErrorCode(error);
195  }
196  if (async_context_.get())
197    SetOffset(async_context_->overlapped(), result);
198  return result.QuadPart;
199}
200
201int64 FileStream::Available() {
202  base::ThreadRestrictions::AssertIOAllowed();
203
204  if (!IsOpen())
205    return ERR_UNEXPECTED;
206
207  int64 cur_pos = Seek(FROM_CURRENT, 0);
208  if (cur_pos < 0)
209    return cur_pos;
210
211  LARGE_INTEGER file_size;
212  if (!GetFileSizeEx(file_, &file_size)) {
213    DWORD error = GetLastError();
214    LOG(WARNING) << "GetFileSizeEx failed: " << error;
215    return MapErrorCode(error);
216  }
217
218  return file_size.QuadPart - cur_pos;
219}
220
221int FileStream::Read(
222    char* buf, int buf_len, CompletionCallback* callback) {
223  if (!IsOpen())
224    return ERR_UNEXPECTED;
225  DCHECK(open_flags_ & base::PLATFORM_FILE_READ);
226
227  OVERLAPPED* overlapped = NULL;
228  if (async_context_.get()) {
229    DCHECK(callback);
230    DCHECK(!async_context_->callback());
231    overlapped = async_context_->overlapped();
232  } else {
233    DCHECK(!callback);
234    base::ThreadRestrictions::AssertIOAllowed();
235  }
236
237  int rv;
238
239  DWORD bytes_read;
240  if (!ReadFile(file_, buf, buf_len, &bytes_read, overlapped)) {
241    DWORD error = GetLastError();
242    if (async_context_.get() && error == ERROR_IO_PENDING) {
243      async_context_->IOCompletionIsPending(callback);
244      rv = ERR_IO_PENDING;
245    } else if (error == ERROR_HANDLE_EOF) {
246      rv = 0;  // Report EOF by returning 0 bytes read.
247    } else {
248      LOG(WARNING) << "ReadFile failed: " << error;
249      rv = MapErrorCode(error);
250    }
251  } else if (overlapped) {
252    async_context_->IOCompletionIsPending(callback);
253    rv = ERR_IO_PENDING;
254  } else {
255    rv = static_cast<int>(bytes_read);
256  }
257  return rv;
258}
259
260int FileStream::ReadUntilComplete(char *buf, int buf_len) {
261  int to_read = buf_len;
262  int bytes_total = 0;
263
264  do {
265    int bytes_read = Read(buf, to_read, NULL);
266    if (bytes_read <= 0) {
267      if (bytes_total == 0)
268        return bytes_read;
269
270      return bytes_total;
271    }
272
273    bytes_total += bytes_read;
274    buf += bytes_read;
275    to_read -= bytes_read;
276  } while (bytes_total < buf_len);
277
278  return bytes_total;
279}
280
281int FileStream::Write(
282    const char* buf, int buf_len, CompletionCallback* callback) {
283  if (!IsOpen())
284    return ERR_UNEXPECTED;
285  DCHECK(open_flags_ & base::PLATFORM_FILE_WRITE);
286
287  OVERLAPPED* overlapped = NULL;
288  if (async_context_.get()) {
289    DCHECK(callback);
290    DCHECK(!async_context_->callback());
291    overlapped = async_context_->overlapped();
292  } else {
293    DCHECK(!callback);
294    base::ThreadRestrictions::AssertIOAllowed();
295  }
296
297  int rv;
298  DWORD bytes_written;
299  if (!WriteFile(file_, buf, buf_len, &bytes_written, overlapped)) {
300    DWORD error = GetLastError();
301    if (async_context_.get() && error == ERROR_IO_PENDING) {
302      async_context_->IOCompletionIsPending(callback);
303      rv = ERR_IO_PENDING;
304    } else {
305      LOG(WARNING) << "WriteFile failed: " << error;
306      rv = MapErrorCode(error);
307    }
308  } else if (overlapped) {
309    async_context_->IOCompletionIsPending(callback);
310    rv = ERR_IO_PENDING;
311  } else {
312    rv = static_cast<int>(bytes_written);
313  }
314  return rv;
315}
316
317int FileStream::Flush() {
318  base::ThreadRestrictions::AssertIOAllowed();
319
320  if (!IsOpen())
321    return ERR_UNEXPECTED;
322
323  DCHECK(open_flags_ & base::PLATFORM_FILE_WRITE);
324  if (FlushFileBuffers(file_)) {
325    return OK;
326  }
327
328  int rv;
329  DWORD error = GetLastError();
330  rv = MapErrorCode(error);
331  return rv;
332}
333
334int64 FileStream::Truncate(int64 bytes) {
335  base::ThreadRestrictions::AssertIOAllowed();
336
337  if (!IsOpen())
338    return ERR_UNEXPECTED;
339
340  // We better be open for reading.
341  DCHECK(open_flags_ & base::PLATFORM_FILE_WRITE);
342
343  // Seek to the position to truncate from.
344  int64 seek_position = Seek(FROM_BEGIN, bytes);
345  if (seek_position != bytes)
346    return ERR_UNEXPECTED;
347
348  // And truncate the file.
349  BOOL result = SetEndOfFile(file_);
350  if (!result) {
351    DWORD error = GetLastError();
352    LOG(WARNING) << "SetEndOfFile failed: " << error;
353    return MapErrorCode(error);
354  }
355
356  // Success.
357  return seek_position;
358}
359
360}  // namespace net
361