// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/base/file_stream.h" #include <windows.h> #include "base/file_path.h" #include "base/logging.h" #include "base/message_loop.h" #include "base/metrics/histogram.h" #include "base/threading/thread_restrictions.h" #include "net/base/net_errors.h" namespace net { // Ensure that we can just use our Whence values directly. COMPILE_ASSERT(FROM_BEGIN == FILE_BEGIN, bad_whence_begin); COMPILE_ASSERT(FROM_CURRENT == FILE_CURRENT, bad_whence_current); COMPILE_ASSERT(FROM_END == FILE_END, bad_whence_end); static void SetOffset(OVERLAPPED* overlapped, const LARGE_INTEGER& offset) { overlapped->Offset = offset.LowPart; overlapped->OffsetHigh = offset.HighPart; } static void IncrementOffset(OVERLAPPED* overlapped, DWORD count) { LARGE_INTEGER offset; offset.LowPart = overlapped->Offset; offset.HighPart = overlapped->OffsetHigh; offset.QuadPart += static_cast<LONGLONG>(count); SetOffset(overlapped, offset); } static int MapErrorCode(DWORD err) { switch (err) { case ERROR_FILE_NOT_FOUND: case ERROR_PATH_NOT_FOUND: return ERR_FILE_NOT_FOUND; case ERROR_ACCESS_DENIED: return ERR_ACCESS_DENIED; case ERROR_SUCCESS: return OK; default: LOG(WARNING) << "Unknown error " << err << " mapped to net::ERR_FAILED"; return ERR_FAILED; } } // FileStream::AsyncContext ---------------------------------------------- class FileStream::AsyncContext : public MessageLoopForIO::IOHandler { public: AsyncContext(FileStream* owner) : owner_(owner), context_(), callback_(NULL), is_closing_(false) { context_.handler = this; } ~AsyncContext(); void IOCompletionIsPending(CompletionCallback* callback); OVERLAPPED* overlapped() { return &context_.overlapped; } CompletionCallback* callback() const { return callback_; } private: virtual void OnIOCompleted(MessageLoopForIO::IOContext* context, DWORD bytes_read, DWORD error); FileStream* owner_; MessageLoopForIO::IOContext context_; CompletionCallback* callback_; bool is_closing_; }; FileStream::AsyncContext::~AsyncContext() { is_closing_ = true; bool waited = false; base::TimeTicks start = base::TimeTicks::Now(); while (callback_) { waited = true; MessageLoopForIO::current()->WaitForIOCompletion(INFINITE, this); } if (waited) { // We want to see if we block the message loop for too long. UMA_HISTOGRAM_TIMES("AsyncIO.FileStreamClose", base::TimeTicks::Now() - start); } } void FileStream::AsyncContext::IOCompletionIsPending( CompletionCallback* callback) { DCHECK(!callback_); callback_ = callback; } void FileStream::AsyncContext::OnIOCompleted( MessageLoopForIO::IOContext* context, DWORD bytes_read, DWORD error) { DCHECK(&context_ == context); DCHECK(callback_); if (is_closing_) { callback_ = NULL; return; } int result = static_cast<int>(bytes_read); if (error && error != ERROR_HANDLE_EOF) result = MapErrorCode(error); if (bytes_read) IncrementOffset(&context->overlapped, bytes_read); CompletionCallback* temp = NULL; std::swap(temp, callback_); temp->Run(result); } // FileStream ------------------------------------------------------------ FileStream::FileStream() : file_(INVALID_HANDLE_VALUE), open_flags_(0), auto_closed_(true) { } FileStream::FileStream(base::PlatformFile file, int flags) : file_(file), open_flags_(flags), auto_closed_(false) { // If the file handle is opened with base::PLATFORM_FILE_ASYNC, we need to // make sure we will perform asynchronous File IO to it. if (flags & base::PLATFORM_FILE_ASYNC) { async_context_.reset(new AsyncContext(this)); MessageLoopForIO::current()->RegisterIOHandler(file_, async_context_.get()); } } FileStream::~FileStream() { if (auto_closed_) Close(); } void FileStream::Close() { if (file_ != INVALID_HANDLE_VALUE) CancelIo(file_); async_context_.reset(); if (file_ != INVALID_HANDLE_VALUE) { CloseHandle(file_); file_ = INVALID_HANDLE_VALUE; } } int FileStream::Open(const FilePath& path, int open_flags) { if (IsOpen()) { DLOG(FATAL) << "File is already open!"; return ERR_UNEXPECTED; } open_flags_ = open_flags; file_ = base::CreatePlatformFile(path, open_flags_, NULL, NULL); if (file_ == INVALID_HANDLE_VALUE) { DWORD error = GetLastError(); LOG(WARNING) << "Failed to open file: " << error; return MapErrorCode(error); } if (open_flags_ & base::PLATFORM_FILE_ASYNC) { async_context_.reset(new AsyncContext(this)); MessageLoopForIO::current()->RegisterIOHandler(file_, async_context_.get()); } return OK; } bool FileStream::IsOpen() const { return file_ != INVALID_HANDLE_VALUE; } int64 FileStream::Seek(Whence whence, int64 offset) { if (!IsOpen()) return ERR_UNEXPECTED; DCHECK(!async_context_.get() || !async_context_->callback()); LARGE_INTEGER distance, result; distance.QuadPart = offset; DWORD move_method = static_cast<DWORD>(whence); if (!SetFilePointerEx(file_, distance, &result, move_method)) { DWORD error = GetLastError(); LOG(WARNING) << "SetFilePointerEx failed: " << error; return MapErrorCode(error); } if (async_context_.get()) SetOffset(async_context_->overlapped(), result); return result.QuadPart; } int64 FileStream::Available() { base::ThreadRestrictions::AssertIOAllowed(); if (!IsOpen()) return ERR_UNEXPECTED; int64 cur_pos = Seek(FROM_CURRENT, 0); if (cur_pos < 0) return cur_pos; LARGE_INTEGER file_size; if (!GetFileSizeEx(file_, &file_size)) { DWORD error = GetLastError(); LOG(WARNING) << "GetFileSizeEx failed: " << error; return MapErrorCode(error); } return file_size.QuadPart - cur_pos; } int FileStream::Read( char* buf, int buf_len, CompletionCallback* callback) { if (!IsOpen()) return ERR_UNEXPECTED; DCHECK(open_flags_ & base::PLATFORM_FILE_READ); OVERLAPPED* overlapped = NULL; if (async_context_.get()) { DCHECK(callback); DCHECK(!async_context_->callback()); overlapped = async_context_->overlapped(); } else { DCHECK(!callback); base::ThreadRestrictions::AssertIOAllowed(); } int rv; DWORD bytes_read; if (!ReadFile(file_, buf, buf_len, &bytes_read, overlapped)) { DWORD error = GetLastError(); if (async_context_.get() && error == ERROR_IO_PENDING) { async_context_->IOCompletionIsPending(callback); rv = ERR_IO_PENDING; } else if (error == ERROR_HANDLE_EOF) { rv = 0; // Report EOF by returning 0 bytes read. } else { LOG(WARNING) << "ReadFile failed: " << error; rv = MapErrorCode(error); } } else if (overlapped) { async_context_->IOCompletionIsPending(callback); rv = ERR_IO_PENDING; } else { rv = static_cast<int>(bytes_read); } return rv; } int FileStream::ReadUntilComplete(char *buf, int buf_len) { int to_read = buf_len; int bytes_total = 0; do { int bytes_read = Read(buf, to_read, NULL); if (bytes_read <= 0) { if (bytes_total == 0) return bytes_read; return bytes_total; } bytes_total += bytes_read; buf += bytes_read; to_read -= bytes_read; } while (bytes_total < buf_len); return bytes_total; } int FileStream::Write( const char* buf, int buf_len, CompletionCallback* callback) { if (!IsOpen()) return ERR_UNEXPECTED; DCHECK(open_flags_ & base::PLATFORM_FILE_WRITE); OVERLAPPED* overlapped = NULL; if (async_context_.get()) { DCHECK(callback); DCHECK(!async_context_->callback()); overlapped = async_context_->overlapped(); } else { DCHECK(!callback); base::ThreadRestrictions::AssertIOAllowed(); } int rv; DWORD bytes_written; if (!WriteFile(file_, buf, buf_len, &bytes_written, overlapped)) { DWORD error = GetLastError(); if (async_context_.get() && error == ERROR_IO_PENDING) { async_context_->IOCompletionIsPending(callback); rv = ERR_IO_PENDING; } else { LOG(WARNING) << "WriteFile failed: " << error; rv = MapErrorCode(error); } } else if (overlapped) { async_context_->IOCompletionIsPending(callback); rv = ERR_IO_PENDING; } else { rv = static_cast<int>(bytes_written); } return rv; } int FileStream::Flush() { base::ThreadRestrictions::AssertIOAllowed(); if (!IsOpen()) return ERR_UNEXPECTED; DCHECK(open_flags_ & base::PLATFORM_FILE_WRITE); if (FlushFileBuffers(file_)) { return OK; } int rv; DWORD error = GetLastError(); rv = MapErrorCode(error); return rv; } int64 FileStream::Truncate(int64 bytes) { base::ThreadRestrictions::AssertIOAllowed(); if (!IsOpen()) return ERR_UNEXPECTED; // We better be open for reading. DCHECK(open_flags_ & base::PLATFORM_FILE_WRITE); // Seek to the position to truncate from. int64 seek_position = Seek(FROM_BEGIN, bytes); if (seek_position != bytes) return ERR_UNEXPECTED; // And truncate the file. BOOL result = SetEndOfFile(file_); if (!result) { DWORD error = GetLastError(); LOG(WARNING) << "SetEndOfFile failed: " << error; return MapErrorCode(error); } // Success. return seek_position; } } // namespace net