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