// Copyright (c) 2012 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/disk_cache/file.h" #include "base/file_path.h" #include "base/lazy_instance.h" #include "base/message_loop.h" #include "net/base/net_errors.h" #include "net/disk_cache/disk_cache.h" namespace { // Structure used for asynchronous operations. struct MyOverlapped { MyOverlapped(disk_cache::File* file, size_t offset, disk_cache::FileIOCallback* callback); ~MyOverlapped() {} OVERLAPPED* overlapped() { return &context_.overlapped; } MessageLoopForIO::IOContext context_; scoped_refptr file_; disk_cache::FileIOCallback* callback_; }; COMPILE_ASSERT(!offsetof(MyOverlapped, context_), starts_with_overlapped); // Helper class to handle the IO completion notifications from the message loop. class CompletionHandler : public MessageLoopForIO::IOHandler { virtual void OnIOCompleted(MessageLoopForIO::IOContext* context, DWORD actual_bytes, DWORD error); }; static base::LazyInstance g_completion_handler = LAZY_INSTANCE_INITIALIZER; void CompletionHandler::OnIOCompleted(MessageLoopForIO::IOContext* context, DWORD actual_bytes, DWORD error) { MyOverlapped* data = reinterpret_cast(context); if (error) { DCHECK(!actual_bytes); actual_bytes = static_cast(net::ERR_CACHE_READ_FAILURE); NOTREACHED(); } if (data->callback_) data->callback_->OnFileIOComplete(static_cast(actual_bytes)); delete data; } MyOverlapped::MyOverlapped(disk_cache::File* file, size_t offset, disk_cache::FileIOCallback* callback) { memset(this, 0, sizeof(*this)); context_.handler = g_completion_handler.Pointer(); context_.overlapped.Offset = static_cast(offset); file_ = file; callback_ = callback; } } // namespace namespace disk_cache { File::File(base::PlatformFile file) : init_(true), mixed_(true), platform_file_(INVALID_HANDLE_VALUE), sync_platform_file_(file) { } bool File::Init(const FilePath& name) { DCHECK(!init_); if (init_) return false; DWORD sharing = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; DWORD access = GENERIC_READ | GENERIC_WRITE | DELETE; platform_file_ = CreateFile(name.value().c_str(), access, sharing, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); if (INVALID_HANDLE_VALUE == platform_file_) return false; MessageLoopForIO::current()->RegisterIOHandler( platform_file_, g_completion_handler.Pointer()); init_ = true; sync_platform_file_ = CreateFile(name.value().c_str(), access, sharing, NULL, OPEN_EXISTING, 0, NULL); if (INVALID_HANDLE_VALUE == sync_platform_file_) return false; return true; } File::~File() { if (!init_) return; if (INVALID_HANDLE_VALUE != platform_file_) CloseHandle(platform_file_); if (INVALID_HANDLE_VALUE != sync_platform_file_) CloseHandle(sync_platform_file_); } base::PlatformFile File::platform_file() const { DCHECK(init_); return (INVALID_HANDLE_VALUE == platform_file_) ? sync_platform_file_ : platform_file_; } bool File::IsValid() const { if (!init_) return false; return (INVALID_HANDLE_VALUE != platform_file_ || INVALID_HANDLE_VALUE != sync_platform_file_); } bool File::Read(void* buffer, size_t buffer_len, size_t offset) { DCHECK(init_); if (buffer_len > ULONG_MAX || offset > LONG_MAX) return false; DWORD ret = SetFilePointer(sync_platform_file_, static_cast(offset), NULL, FILE_BEGIN); if (INVALID_SET_FILE_POINTER == ret) return false; DWORD actual; DWORD size = static_cast(buffer_len); if (!ReadFile(sync_platform_file_, buffer, size, &actual, NULL)) return false; return actual == size; } bool File::Write(const void* buffer, size_t buffer_len, size_t offset) { DCHECK(init_); if (buffer_len > ULONG_MAX || offset > ULONG_MAX) return false; DWORD ret = SetFilePointer(sync_platform_file_, static_cast(offset), NULL, FILE_BEGIN); if (INVALID_SET_FILE_POINTER == ret) return false; DWORD actual; DWORD size = static_cast(buffer_len); if (!WriteFile(sync_platform_file_, buffer, size, &actual, NULL)) return false; return actual == size; } // We have to increase the ref counter of the file before performing the IO to // prevent the completion to happen with an invalid handle (if the file is // closed while the IO is in flight). bool File::Read(void* buffer, size_t buffer_len, size_t offset, FileIOCallback* callback, bool* completed) { DCHECK(init_); if (!callback) { if (completed) *completed = true; return Read(buffer, buffer_len, offset); } if (buffer_len > ULONG_MAX || offset > ULONG_MAX) return false; MyOverlapped* data = new MyOverlapped(this, offset, callback); DWORD size = static_cast(buffer_len); DWORD actual; if (!ReadFile(platform_file_, buffer, size, &actual, data->overlapped())) { *completed = false; if (GetLastError() == ERROR_IO_PENDING) return true; delete data; return false; } // The operation completed already. We'll be called back anyway. *completed = (actual == size); DCHECK_EQ(size, actual); data->callback_ = NULL; data->file_ = NULL; // There is no reason to hold on to this anymore. return *completed; } bool File::Write(const void* buffer, size_t buffer_len, size_t offset, FileIOCallback* callback, bool* completed) { DCHECK(init_); if (!callback) { if (completed) *completed = true; return Write(buffer, buffer_len, offset); } return AsyncWrite(buffer, buffer_len, offset, callback, completed); } bool File::AsyncWrite(const void* buffer, size_t buffer_len, size_t offset, FileIOCallback* callback, bool* completed) { DCHECK(init_); DCHECK(callback); DCHECK(completed); if (buffer_len > ULONG_MAX || offset > ULONG_MAX) return false; MyOverlapped* data = new MyOverlapped(this, offset, callback); DWORD size = static_cast(buffer_len); DWORD actual; if (!WriteFile(platform_file_, buffer, size, &actual, data->overlapped())) { *completed = false; if (GetLastError() == ERROR_IO_PENDING) return true; delete data; return false; } // The operation completed already. We'll be called back anyway. *completed = (actual == size); DCHECK_EQ(size, actual); data->callback_ = NULL; data->file_ = NULL; // There is no reason to hold on to this anymore. return *completed; } bool File::SetLength(size_t length) { DCHECK(init_); if (length > ULONG_MAX) return false; DWORD size = static_cast(length); HANDLE file = platform_file(); if (INVALID_SET_FILE_POINTER == SetFilePointer(file, size, NULL, FILE_BEGIN)) return false; return TRUE == SetEndOfFile(file); } size_t File::GetLength() { DCHECK(init_); LARGE_INTEGER size; HANDLE file = platform_file(); if (!GetFileSizeEx(file, &size)) return 0; if (size.HighPart) return ULONG_MAX; return static_cast(size.LowPart); } // Static. void File::WaitForPendingIO(int* num_pending_io) { while (*num_pending_io) { // Asynchronous IO operations may be in flight and the completion may end // up calling us back so let's wait for them. MessageLoopForIO::IOHandler* handler = g_completion_handler.Pointer(); MessageLoopForIO::current()->WaitForIOCompletion(100, handler); } } // Static. void File::DropPendingIO() { // Nothing to do here. } } // namespace disk_cache