diff options
author | pilgrim <pilgrim@chromium.org> | 2014-09-05 10:30:15 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-09-05 17:32:24 +0000 |
commit | 4af8c2120e23d17c1cac3f929c3a1d901c5701ab (patch) | |
tree | 40e4ca0d45ec498ea74b9b4b232764abc1ec6ab1 /storage | |
parent | a602902631a762be0b49ac0af09de0c9c840e183 (diff) | |
download | chromium_src-4af8c2120e23d17c1cac3f929c3a1d901c5701ab.zip chromium_src-4af8c2120e23d17c1cac3f929c3a1d901c5701ab.tar.gz chromium_src-4af8c2120e23d17c1cac3f929c3a1d901c5701ab.tar.bz2 |
Migrate webkit/browser/ to storage/browser/
using TBR because Darin told me he defers to James' judgement. Using NOTRY because there is one presubmit error (dump_file_system.cc was moved and it spews printf).
BUG=338338
TBR=darin@chromium.org
NOTRY=true
Review URL: https://codereview.chromium.org/539143002
Cr-Commit-Position: refs/heads/master@{#293547}
Diffstat (limited to 'storage')
154 files changed, 29926 insertions, 0 deletions
diff --git a/storage/DEPS b/storage/DEPS index 8e3d4fd..c311233 100644 --- a/storage/DEPS +++ b/storage/DEPS @@ -1,5 +1,7 @@ include_rules = [ "+net", "+sql", + "+third_party/leveldatabase", + "+third_party/sqlite", "+third_party/WebKit/public/platform", ] diff --git a/storage/browser/BUILD.gn b/storage/browser/BUILD.gn new file mode 100644 index 0000000..7b0b461 --- /dev/null +++ b/storage/browser/BUILD.gn @@ -0,0 +1,192 @@ +# Copyright 2014 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. + +# GYP version: storage/storage_browser.gyp:storage +component("browser") { + output_name = "storage_browser" + sources = [ + "storage_browser_export.h", + "blob/blob_data_handle.cc", + "blob/blob_data_handle.h", + "blob/blob_storage_context.cc", + "blob/blob_storage_context.h", + "blob/blob_url_request_job.cc", + "blob/blob_url_request_job.h", + "blob/blob_url_request_job_factory.cc", + "blob/blob_url_request_job_factory.h", + "blob/file_stream_reader.cc", + "blob/file_stream_reader.h", + "blob/local_file_stream_reader.cc", + "blob/local_file_stream_reader.h", + "blob/view_blob_internals_job.cc", + "blob/view_blob_internals_job.h", + "database/database_quota_client.cc", + "database/database_quota_client.h", + "database/database_tracker.cc", + "database/database_tracker.h", + "database/database_util.cc", + "database/database_util.h", + "database/databases_table.cc", + "database/databases_table.h", + "database/vfs_backend.cc", + "database/vfs_backend.h", + "fileapi/async_file_util.h", + "fileapi/async_file_util_adapter.cc", + "fileapi/async_file_util_adapter.h", + "fileapi/copy_or_move_file_validator.h", + "fileapi/copy_or_move_operation_delegate.cc", + "fileapi/copy_or_move_operation_delegate.h", + "fileapi/dragged_file_util.cc", + "fileapi/dragged_file_util.h", + "fileapi/external_mount_points.cc", + "fileapi/external_mount_points.h", + "fileapi/file_observers.h", + "fileapi/file_permission_policy.h", + "fileapi/file_stream_writer.h", + "fileapi/file_system_backend.h", + "fileapi/file_system_context.cc", + "fileapi/file_system_context.h", + "fileapi/file_system_dir_url_request_job.cc", + "fileapi/file_system_dir_url_request_job.h", + "fileapi/file_system_file_stream_reader.cc", + "fileapi/file_system_file_stream_reader.h", + "fileapi/file_system_file_util.cc", + "fileapi/file_system_file_util.h", + "fileapi/file_system_operation.h", + "fileapi/file_system_operation_context.cc", + "fileapi/file_system_operation_context.h", + "fileapi/file_system_operation_impl.cc", + "fileapi/file_system_operation_impl.h", + "fileapi/file_system_operation_runner.cc", + "fileapi/file_system_operation_runner.h", + "fileapi/file_system_options.cc", + "fileapi/file_system_options.h", + "fileapi/file_system_quota_client.cc", + "fileapi/file_system_quota_client.h", + "fileapi/file_system_quota_util.h", + "fileapi/file_system_url.cc", + "fileapi/file_system_url.h", + "fileapi/file_system_url_request_job.cc", + "fileapi/file_system_url_request_job.h", + "fileapi/file_system_url_request_job_factory.cc", + "fileapi/file_system_url_request_job_factory.h", + "fileapi/file_system_usage_cache.cc", + "fileapi/file_system_usage_cache.h", + "fileapi/file_writer_delegate.cc", + "fileapi/file_writer_delegate.h", + "fileapi/isolated_context.cc", + "fileapi/isolated_context.h", + "fileapi/isolated_file_system_backend.cc", + "fileapi/isolated_file_system_backend.h", + "fileapi/local_file_stream_writer.cc", + "fileapi/local_file_stream_writer.h", + "fileapi/local_file_util.cc", + "fileapi/local_file_util.h", + "fileapi/mount_points.cc", + "fileapi/mount_points.h", + "fileapi/native_file_util.cc", + "fileapi/native_file_util.h", + "fileapi/obfuscated_file_util.cc", + "fileapi/obfuscated_file_util.h", + "fileapi/open_file_system_mode.h", + "fileapi/plugin_private_file_system_backend.cc", + "fileapi/plugin_private_file_system_backend.h", + "fileapi/quota/open_file_handle.cc", + "fileapi/quota/open_file_handle.h", + "fileapi/quota/open_file_handle_context.cc", + "fileapi/quota/open_file_handle_context.h", + "fileapi/quota/quota_backend_impl.cc", + "fileapi/quota/quota_backend_impl.h", + "fileapi/quota/quota_reservation.cc", + "fileapi/quota/quota_reservation.h", + "fileapi/quota/quota_reservation_buffer.cc", + "fileapi/quota/quota_reservation_buffer.h", + "fileapi/quota/quota_reservation_manager.cc", + "fileapi/quota/quota_reservation_manager.h", + "fileapi/recursive_operation_delegate.cc", + "fileapi/recursive_operation_delegate.h", + "fileapi/remove_operation_delegate.cc", + "fileapi/remove_operation_delegate.h", + "fileapi/sandbox_directory_database.cc", + "fileapi/sandbox_directory_database.h", + "fileapi/sandbox_file_stream_writer.cc", + "fileapi/sandbox_file_stream_writer.h", + "fileapi/sandbox_file_system_backend.cc", + "fileapi/sandbox_file_system_backend.h", + "fileapi/sandbox_file_system_backend_delegate.cc", + "fileapi/sandbox_file_system_backend_delegate.h", + "fileapi/sandbox_isolated_origin_database.cc", + "fileapi/sandbox_isolated_origin_database.h", + "fileapi/sandbox_origin_database.cc", + "fileapi/sandbox_origin_database.h", + "fileapi/sandbox_origin_database_interface.cc", + "fileapi/sandbox_origin_database_interface.h", + "fileapi/sandbox_prioritized_origin_database.cc", + "fileapi/sandbox_prioritized_origin_database.h", + "fileapi/sandbox_quota_observer.cc", + "fileapi/sandbox_quota_observer.h", + "fileapi/task_runner_bound_observer_list.h", + "fileapi/timed_task_helper.cc", + "fileapi/timed_task_helper.h", + "fileapi/transient_file_util.cc", + "fileapi/transient_file_util.h", + "quota/quota_callbacks.h", + "quota/quota_client.h", + "quota/quota_database.cc", + "quota/quota_database.h", + "quota/quota_manager.cc", + "quota/quota_manager.h", + "quota/quota_manager_proxy.cc", + "quota/quota_manager_proxy.h", + "quota/quota_task.cc", + "quota/quota_task.h", + "quota/quota_temporary_storage_evictor.cc", + "quota/quota_temporary_storage_evictor.h", + "quota/special_storage_policy.cc", + "quota/special_storage_policy.h", + "quota/storage_monitor.cc", + "quota/storage_monitor.h", + "quota/storage_observer.cc", + "quota/storage_observer.h", + "quota/usage_tracker.cc", + "quota/usage_tracker.h", + ] + + defines = [ "STORAGE_BROWSER_IMPLEMENTATION" ] + configs += [ "//build/config/compiler:wexit_time_destructors" ] + if (is_win) { + cflags = [ "/wd4267" ] # TODO(jschuh): fix size_t to int truncations. + } + + deps = [ + "//base", + "//base:i18n", + "//base/third_party/dynamic_annotations", + "//net", + "//sql", + "//storage/common", + "//third_party/leveldatabase", + "//third_party/sqlite", + "//url", + ] + + # TODO(GYP) support chrome_multiple_dll + #['chrome_multiple_dll!=1', { + # 'dependencies': [ + # '<(DEPTH)/third_party/WebKit/public/blink.gyp:blink', + # ], + #}], +} + +executable("dump_file_system") { + sources = [ + "fileapi/dump_file_system.cc", + ] + + deps = [ + ":browser", + "//base", + "//storage/common", + ] +} diff --git a/storage/browser/DEPS b/storage/browser/DEPS new file mode 100644 index 0000000..62bbdd0 --- /dev/null +++ b/storage/browser/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "-webkit/renderer", +] diff --git a/storage/browser/blob/OWNERS b/storage/browser/blob/OWNERS new file mode 100644 index 0000000..c7e8dcb --- /dev/null +++ b/storage/browser/blob/OWNERS @@ -0,0 +1 @@ +jianli@chromium.org diff --git a/storage/browser/blob/blob_data_handle.cc b/storage/browser/blob/blob_data_handle.cc new file mode 100644 index 0000000..cf647b4 --- /dev/null +++ b/storage/browser/blob/blob_data_handle.cc @@ -0,0 +1,68 @@ +// Copyright (c) 2013 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 "storage/browser/blob/blob_data_handle.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/sequenced_task_runner.h" +#include "storage/browser/blob/blob_storage_context.h" +#include "storage/common/blob/blob_data.h" + +namespace storage { + +BlobDataHandle::BlobDataHandleShared::BlobDataHandleShared( + BlobData* blob_data, + BlobStorageContext* context, + base::SequencedTaskRunner* task_runner) + : blob_data_(blob_data), + context_(context->AsWeakPtr()) { + context_->IncrementBlobRefCount(blob_data->uuid()); +} + +BlobData* BlobDataHandle::BlobDataHandleShared::data() const { + return blob_data_.get(); +} + +const std::string& BlobDataHandle::BlobDataHandleShared::uuid() const { + return blob_data_->uuid(); +} + +BlobDataHandle::BlobDataHandleShared::~BlobDataHandleShared() { + if (context_.get()) + context_->DecrementBlobRefCount(blob_data_->uuid()); +} + +BlobDataHandle::BlobDataHandle(BlobData* blob_data, + BlobStorageContext* context, + base::SequencedTaskRunner* task_runner) + : io_task_runner_(task_runner), + shared_(new BlobDataHandleShared(blob_data, context, task_runner)) { + DCHECK(io_task_runner_.get()); + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); +} + +BlobDataHandle::BlobDataHandle(const BlobDataHandle& other) { + io_task_runner_ = other.io_task_runner_; + shared_ = other.shared_; +} + +BlobDataHandle::~BlobDataHandle() { + BlobDataHandleShared* raw = shared_.get(); + raw->AddRef(); + shared_ = 0; + io_task_runner_->ReleaseSoon(FROM_HERE, raw); +} + +BlobData* BlobDataHandle::data() const { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + return shared_->data(); +} + +std::string BlobDataHandle::uuid() const { + return shared_->uuid(); +} + +} // namespace storage diff --git a/storage/browser/blob/blob_data_handle.h b/storage/browser/blob/blob_data_handle.h new file mode 100644 index 0000000..1120bdf --- /dev/null +++ b/storage/browser/blob/blob_data_handle.h @@ -0,0 +1,71 @@ +// Copyright (c) 2013 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. + +#ifndef STORAGE_BROWSER_BLOB_BLOB_DATA_HANDLE_H_ +#define STORAGE_BROWSER_BLOB_BLOB_DATA_HANDLE_H_ + +#include <string> + +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/supports_user_data.h" +#include "storage/browser/storage_browser_export.h" + +namespace base { +class SequencedTaskRunner; +} + +namespace storage { + +class BlobData; +class BlobStorageContext; + +// A scoper object for use in chrome's main browser process, ensures +// the underlying BlobData and its uuid remain in BlobStorageContext's +// collection for the duration. This object has delete semantics and +// maybe deleted on any thread. +class STORAGE_EXPORT BlobDataHandle + : public base::SupportsUserData::Data { + public: + BlobDataHandle(const BlobDataHandle& other); // May be copied on any thread. + virtual ~BlobDataHandle(); // Maybe be deleted on any thread. + BlobData* data() const; // May only be accessed on the IO thread. + + std::string uuid() const; // May be accessed on any thread. + + private: + class BlobDataHandleShared + : public base::RefCountedThreadSafe<BlobDataHandleShared> { + public: + BlobDataHandleShared(BlobData* blob_data, + BlobStorageContext* context, + base::SequencedTaskRunner* task_runner); + + BlobData* data() const; + const std::string& uuid() const; + + private: + friend class base::DeleteHelper<BlobDataHandleShared>; + friend class base::RefCountedThreadSafe<BlobDataHandleShared>; + friend class BlobDataHandle; + + virtual ~BlobDataHandleShared(); + + scoped_refptr<BlobData> blob_data_; + base::WeakPtr<BlobStorageContext> context_; + + DISALLOW_COPY_AND_ASSIGN(BlobDataHandleShared); + }; + + friend class BlobStorageContext; + BlobDataHandle(BlobData* blob_data, BlobStorageContext* context, + base::SequencedTaskRunner* task_runner); + + scoped_refptr<base::SequencedTaskRunner> io_task_runner_; + scoped_refptr<BlobDataHandleShared> shared_; +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_BLOB_BLOB_DATA_HANDLE_H_ diff --git a/storage/browser/blob/blob_storage_context.cc b/storage/browser/blob/blob_storage_context.cc new file mode 100644 index 0000000..554c84e --- /dev/null +++ b/storage/browser/blob/blob_storage_context.cc @@ -0,0 +1,325 @@ +// Copyright (c) 2013 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 "storage/browser/blob/blob_storage_context.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/message_loop/message_loop_proxy.h" +#include "storage/browser/blob/blob_data_handle.h" +#include "storage/common/blob/blob_data.h" +#include "url/gurl.h" + +namespace storage { + +namespace { + +// We can't use GURL directly for these hash fragment manipulations +// since it doesn't have specific knowlege of the BlobURL format. GURL +// treats BlobURLs as if they were PathURLs which don't support hash +// fragments. + +bool BlobUrlHasRef(const GURL& url) { + return url.spec().find('#') != std::string::npos; +} + +GURL ClearBlobUrlRef(const GURL& url) { + size_t hash_pos = url.spec().find('#'); + if (hash_pos == std::string::npos) + return url; + return GURL(url.spec().substr(0, hash_pos)); +} + +// TODO(michaeln): use base::SysInfo::AmountOfPhysicalMemoryMB() in some +// way to come up with a better limit. +static const int64 kMaxMemoryUsage = 500 * 1024 * 1024; // Half a gig. + +} // namespace + +BlobStorageContext::BlobMapEntry::BlobMapEntry() + : refcount(0), flags(0) { +} + +BlobStorageContext::BlobMapEntry::BlobMapEntry( + int refcount, int flags, BlobData* data) + : refcount(refcount), flags(flags), data(data) { +} + +BlobStorageContext::BlobMapEntry::~BlobMapEntry() { +} + +BlobStorageContext::BlobStorageContext() + : memory_usage_(0) { +} + +BlobStorageContext::~BlobStorageContext() { +} + +scoped_ptr<BlobDataHandle> BlobStorageContext::GetBlobDataFromUUID( + const std::string& uuid) { + scoped_ptr<BlobDataHandle> result; + BlobMap::iterator found = blob_map_.find(uuid); + if (found == blob_map_.end()) + return result.Pass(); + if (found->second.flags & EXCEEDED_MEMORY) + return result.Pass(); + DCHECK(!(found->second.flags & BEING_BUILT)); + result.reset(new BlobDataHandle( + found->second.data.get(), this, base::MessageLoopProxy::current().get())); + return result.Pass(); +} + +scoped_ptr<BlobDataHandle> BlobStorageContext::GetBlobDataFromPublicURL( + const GURL& url) { + BlobURLMap::iterator found = public_blob_urls_.find( + BlobUrlHasRef(url) ? ClearBlobUrlRef(url) : url); + if (found == public_blob_urls_.end()) + return scoped_ptr<BlobDataHandle>(); + return GetBlobDataFromUUID(found->second); +} + +scoped_ptr<BlobDataHandle> BlobStorageContext::AddFinishedBlob( + const BlobData* data) { + StartBuildingBlob(data->uuid()); + for (std::vector<BlobData::Item>::const_iterator iter = + data->items().begin(); + iter != data->items().end(); ++iter) { + AppendBlobDataItem(data->uuid(), *iter); + } + FinishBuildingBlob(data->uuid(), data->content_type()); + scoped_ptr<BlobDataHandle> handle = GetBlobDataFromUUID(data->uuid()); + DecrementBlobRefCount(data->uuid()); + return handle.Pass(); +} + +bool BlobStorageContext::RegisterPublicBlobURL( + const GURL& blob_url, const std::string& uuid) { + DCHECK(!BlobUrlHasRef(blob_url)); + DCHECK(IsInUse(uuid)); + DCHECK(!IsUrlRegistered(blob_url)); + if (!IsInUse(uuid) || IsUrlRegistered(blob_url)) + return false; + IncrementBlobRefCount(uuid); + public_blob_urls_[blob_url] = uuid; + return true; +} + +void BlobStorageContext::RevokePublicBlobURL(const GURL& blob_url) { + DCHECK(!BlobUrlHasRef(blob_url)); + if (!IsUrlRegistered(blob_url)) + return; + DecrementBlobRefCount(public_blob_urls_[blob_url]); + public_blob_urls_.erase(blob_url); +} + +void BlobStorageContext::StartBuildingBlob(const std::string& uuid) { + DCHECK(!IsInUse(uuid) && !uuid.empty()); + blob_map_[uuid] = BlobMapEntry(1, BEING_BUILT, new BlobData(uuid)); +} + +void BlobStorageContext::AppendBlobDataItem( + const std::string& uuid, const BlobData::Item& item) { + DCHECK(IsBeingBuilt(uuid)); + BlobMap::iterator found = blob_map_.find(uuid); + if (found == blob_map_.end()) + return; + if (found->second.flags & EXCEEDED_MEMORY) + return; + BlobData* target_blob_data = found->second.data.get(); + DCHECK(target_blob_data); + + bool exceeded_memory = false; + + // The blob data is stored in the canonical way which only contains a + // list of Data, File, and FileSystem items. Aggregated TYPE_BLOB items + // are expanded into the primitive constituent types. + // 1) The Data item is denoted by the raw data and length. + // 2) The File item is denoted by the file path, the range and the expected + // modification time. + // 3) The FileSystem File item is denoted by the FileSystem URL, the range + // and the expected modification time. + // 4) The Blob items are expanded. + // TODO(michaeln): Would be nice to avoid copying Data items when expanding. + + DCHECK(item.length() > 0); + switch (item.type()) { + case BlobData::Item::TYPE_BYTES: + DCHECK(!item.offset()); + exceeded_memory = !AppendBytesItem(target_blob_data, + item.bytes(), + static_cast<int64>(item.length())); + break; + case BlobData::Item::TYPE_FILE: + AppendFileItem(target_blob_data, + item.path(), + item.offset(), + item.length(), + item.expected_modification_time()); + break; + case BlobData::Item::TYPE_FILE_FILESYSTEM: + AppendFileSystemFileItem(target_blob_data, + item.filesystem_url(), + item.offset(), + item.length(), + item.expected_modification_time()); + break; + case BlobData::Item::TYPE_BLOB: { + scoped_ptr<BlobDataHandle> src = GetBlobDataFromUUID(item.blob_uuid()); + if (src) + exceeded_memory = !ExpandStorageItems(target_blob_data, + src->data(), + item.offset(), + item.length()); + break; + } + default: + NOTREACHED(); + break; + } + + // If we're using too much memory, drop this blob's data. + // TODO(michaeln): Blob memory storage does not yet spill over to disk, + // as a stop gap, we'll prevent memory usage over a max amount. + if (exceeded_memory) { + memory_usage_ -= target_blob_data->GetMemoryUsage(); + found->second.flags |= EXCEEDED_MEMORY; + found->second.data = new BlobData(uuid); + return; + } +} + +void BlobStorageContext::FinishBuildingBlob( + const std::string& uuid, const std::string& content_type) { + DCHECK(IsBeingBuilt(uuid)); + BlobMap::iterator found = blob_map_.find(uuid); + if (found == blob_map_.end()) + return; + found->second.data->set_content_type(content_type); + found->second.flags &= ~BEING_BUILT; +} + +void BlobStorageContext::CancelBuildingBlob(const std::string& uuid) { + DCHECK(IsBeingBuilt(uuid)); + DecrementBlobRefCount(uuid); +} + +void BlobStorageContext::IncrementBlobRefCount(const std::string& uuid) { + BlobMap::iterator found = blob_map_.find(uuid); + if (found == blob_map_.end()) { + DCHECK(false); + return; + } + ++(found->second.refcount); +} + +void BlobStorageContext::DecrementBlobRefCount(const std::string& uuid) { + BlobMap::iterator found = blob_map_.find(uuid); + if (found == blob_map_.end()) + return; + DCHECK_EQ(found->second.data->uuid(), uuid); + if (--(found->second.refcount) == 0) { + memory_usage_ -= found->second.data->GetMemoryUsage(); + blob_map_.erase(found); + } +} + +bool BlobStorageContext::ExpandStorageItems( + BlobData* target_blob_data, BlobData* src_blob_data, + uint64 offset, uint64 length) { + DCHECK(target_blob_data && src_blob_data && + length != static_cast<uint64>(-1)); + + std::vector<BlobData::Item>::const_iterator iter = + src_blob_data->items().begin(); + if (offset) { + for (; iter != src_blob_data->items().end(); ++iter) { + if (offset >= iter->length()) + offset -= iter->length(); + else + break; + } + } + + for (; iter != src_blob_data->items().end() && length > 0; ++iter) { + uint64 current_length = iter->length() - offset; + uint64 new_length = current_length > length ? length : current_length; + if (iter->type() == BlobData::Item::TYPE_BYTES) { + if (!AppendBytesItem( + target_blob_data, + iter->bytes() + static_cast<size_t>(iter->offset() + offset), + static_cast<int64>(new_length))) { + return false; // exceeded memory + } + } else if (iter->type() == BlobData::Item::TYPE_FILE) { + AppendFileItem(target_blob_data, + iter->path(), + iter->offset() + offset, + new_length, + iter->expected_modification_time()); + } else { + DCHECK(iter->type() == BlobData::Item::TYPE_FILE_FILESYSTEM); + AppendFileSystemFileItem(target_blob_data, + iter->filesystem_url(), + iter->offset() + offset, + new_length, + iter->expected_modification_time()); + } + length -= new_length; + offset = 0; + } + return true; +} + +bool BlobStorageContext::AppendBytesItem( + BlobData* target_blob_data, const char* bytes, int64 length) { + if (length < 0) { + DCHECK(false); + return false; + } + if (memory_usage_ + length > kMaxMemoryUsage) + return false; + target_blob_data->AppendData(bytes, static_cast<size_t>(length)); + memory_usage_ += length; + return true; +} + +void BlobStorageContext::AppendFileItem( + BlobData* target_blob_data, + const base::FilePath& file_path, uint64 offset, uint64 length, + const base::Time& expected_modification_time) { + target_blob_data->AppendFile(file_path, offset, length, + expected_modification_time); + + // It may be a temporary file that should be deleted when no longer needed. + scoped_refptr<ShareableFileReference> shareable_file = + ShareableFileReference::Get(file_path); + if (shareable_file.get()) + target_blob_data->AttachShareableFileReference(shareable_file.get()); +} + +void BlobStorageContext::AppendFileSystemFileItem( + BlobData* target_blob_data, + const GURL& filesystem_url, uint64 offset, uint64 length, + const base::Time& expected_modification_time) { + target_blob_data->AppendFileSystemFile(filesystem_url, offset, length, + expected_modification_time); +} + +bool BlobStorageContext::IsInUse(const std::string& uuid) { + return blob_map_.find(uuid) != blob_map_.end(); +} + +bool BlobStorageContext::IsBeingBuilt(const std::string& uuid) { + BlobMap::iterator found = blob_map_.find(uuid); + if (found == blob_map_.end()) + return false; + return found->second.flags & BEING_BUILT; +} + +bool BlobStorageContext::IsUrlRegistered(const GURL& blob_url) { + return public_blob_urls_.find(blob_url) != public_blob_urls_.end(); +} + +} // namespace storage diff --git a/storage/browser/blob/blob_storage_context.h b/storage/browser/blob/blob_storage_context.h new file mode 100644 index 0000000..7ccc133 --- /dev/null +++ b/storage/browser/blob/blob_storage_context.h @@ -0,0 +1,118 @@ +// Copyright (c) 2013 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. + +#ifndef STORAGE_BROWSER_BLOB_BLOB_STORAGE_CONTEXT_H_ +#define STORAGE_BROWSER_BLOB_BLOB_STORAGE_CONTEXT_H_ + +#include <map> +#include <string> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "storage/browser/blob/blob_data_handle.h" +#include "storage/browser/storage_browser_export.h" +#include "storage/common/blob/blob_data.h" + +class GURL; + +namespace base { +class FilePath; +class Time; +} + +namespace content { +class BlobStorageHost; +} + +namespace storage { + +class BlobDataHandle; + +// This class handles the logistics of blob Storage within the browser process, +// and maintains a mapping from blob uuid to the data. The class is single +// threaded and should only be used on the IO thread. +// In chromium, there is one instance per profile. +class STORAGE_EXPORT BlobStorageContext + : public base::SupportsWeakPtr<BlobStorageContext> { + public: + BlobStorageContext(); + ~BlobStorageContext(); + + scoped_ptr<BlobDataHandle> GetBlobDataFromUUID(const std::string& uuid); + scoped_ptr<BlobDataHandle> GetBlobDataFromPublicURL(const GURL& url); + + // Useful for coining blobs from within the browser process. If the + // blob cannot be added due to memory consumption, returns NULL. + scoped_ptr<BlobDataHandle> AddFinishedBlob(const BlobData* blob_data); + + // Useful for coining blob urls from within the browser process. + bool RegisterPublicBlobURL(const GURL& url, const std::string& uuid); + void RevokePublicBlobURL(const GURL& url); + + private: + friend class content::BlobStorageHost; + friend class BlobDataHandle::BlobDataHandleShared; + friend class ViewBlobInternalsJob; + + enum EntryFlags { + BEING_BUILT = 1 << 0, + EXCEEDED_MEMORY = 1 << 1, + }; + + struct BlobMapEntry { + int refcount; + int flags; + scoped_refptr<BlobData> data; + + BlobMapEntry(); + BlobMapEntry(int refcount, int flags, BlobData* data); + ~BlobMapEntry(); + }; + + typedef std::map<std::string, BlobMapEntry> + BlobMap; + typedef std::map<GURL, std::string> BlobURLMap; + + void StartBuildingBlob(const std::string& uuid); + void AppendBlobDataItem(const std::string& uuid, + const BlobData::Item& data_item); + void FinishBuildingBlob(const std::string& uuid, const std::string& type); + void CancelBuildingBlob(const std::string& uuid); + void IncrementBlobRefCount(const std::string& uuid); + void DecrementBlobRefCount(const std::string& uuid); + + bool ExpandStorageItems(BlobData* target_blob_data, + BlobData* src_blob_data, + uint64 offset, + uint64 length); + bool AppendBytesItem(BlobData* target_blob_data, + const char* data, int64 length); + void AppendFileItem(BlobData* target_blob_data, + const base::FilePath& file_path, + uint64 offset, uint64 length, + const base::Time& expected_modification_time); + void AppendFileSystemFileItem( + BlobData* target_blob_data, + const GURL& url, uint64 offset, uint64 length, + const base::Time& expected_modification_time); + + bool IsInUse(const std::string& uuid); + bool IsBeingBuilt(const std::string& uuid); + bool IsUrlRegistered(const GURL& blob_url); + + BlobMap blob_map_; + BlobURLMap public_blob_urls_; + + // Used to keep track of how much memory is being utilized for blob data, + // we count only the items of TYPE_DATA which are held in memory and not + // items of TYPE_FILE. + int64 memory_usage_; + + DISALLOW_COPY_AND_ASSIGN(BlobStorageContext); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_BLOB_BLOB_STORAGE_CONTEXT_H_ diff --git a/storage/browser/blob/blob_url_request_job.cc b/storage/browser/blob/blob_url_request_job.cc new file mode 100644 index 0000000..0084277 --- /dev/null +++ b/storage/browser/blob/blob_url_request_job.cc @@ -0,0 +1,590 @@ +// 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 "storage/browser/blob/blob_url_request_job.h" + +#include <limits> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/files/file_util_proxy.h" +#include "base/format_macros.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/http/http_request_headers.h" +#include "net/http/http_response_headers.h" +#include "net/http/http_response_info.h" +#include "net/http/http_util.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_error_job.h" +#include "net/url_request/url_request_status.h" +#include "storage/browser/blob/file_stream_reader.h" +#include "storage/browser/fileapi/file_system_context.h" +#include "storage/browser/fileapi/file_system_url.h" + +namespace storage { + +namespace { + +bool IsFileType(BlobData::Item::Type type) { + switch (type) { + case BlobData::Item::TYPE_FILE: + case BlobData::Item::TYPE_FILE_FILESYSTEM: + return true; + default: + return false; + } +} + +} // namespace + +BlobURLRequestJob::BlobURLRequestJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate, + const scoped_refptr<BlobData>& blob_data, + storage::FileSystemContext* file_system_context, + base::MessageLoopProxy* file_thread_proxy) + : net::URLRequestJob(request, network_delegate), + blob_data_(blob_data), + file_system_context_(file_system_context), + file_thread_proxy_(file_thread_proxy), + total_size_(0), + remaining_bytes_(0), + pending_get_file_info_count_(0), + current_item_index_(0), + current_item_offset_(0), + error_(false), + byte_range_set_(false), + weak_factory_(this) { + DCHECK(file_thread_proxy_.get()); +} + +void BlobURLRequestJob::Start() { + // Continue asynchronously. + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&BlobURLRequestJob::DidStart, weak_factory_.GetWeakPtr())); +} + +void BlobURLRequestJob::Kill() { + DeleteCurrentFileReader(); + + net::URLRequestJob::Kill(); + weak_factory_.InvalidateWeakPtrs(); +} + +bool BlobURLRequestJob::ReadRawData(net::IOBuffer* dest, + int dest_size, + int* bytes_read) { + DCHECK_NE(dest_size, 0); + DCHECK(bytes_read); + DCHECK_GE(remaining_bytes_, 0); + + // Bail out immediately if we encounter an error. + if (error_) { + *bytes_read = 0; + return true; + } + + if (remaining_bytes_ < dest_size) + dest_size = static_cast<int>(remaining_bytes_); + + // If we should copy zero bytes because |remaining_bytes_| is zero, short + // circuit here. + if (!dest_size) { + *bytes_read = 0; + return true; + } + + // Keep track of the buffer. + DCHECK(!read_buf_.get()); + read_buf_ = new net::DrainableIOBuffer(dest, dest_size); + + return ReadLoop(bytes_read); +} + +bool BlobURLRequestJob::GetMimeType(std::string* mime_type) const { + if (!response_info_) + return false; + + return response_info_->headers->GetMimeType(mime_type); +} + +void BlobURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) { + if (response_info_) + *info = *response_info_; +} + +int BlobURLRequestJob::GetResponseCode() const { + if (!response_info_) + return -1; + + return response_info_->headers->response_code(); +} + +void BlobURLRequestJob::SetExtraRequestHeaders( + const net::HttpRequestHeaders& headers) { + std::string range_header; + if (headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header)) { + // We only care about "Range" header here. + std::vector<net::HttpByteRange> ranges; + if (net::HttpUtil::ParseRangeHeader(range_header, &ranges)) { + if (ranges.size() == 1) { + byte_range_set_ = true; + byte_range_ = ranges[0]; + } else { + // We don't support multiple range requests in one single URL request, + // because we need to do multipart encoding here. + // TODO(jianli): Support multipart byte range requests. + NotifyFailure(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE); + } + } + } +} + +BlobURLRequestJob::~BlobURLRequestJob() { + STLDeleteValues(&index_to_reader_); +} + +void BlobURLRequestJob::DidStart() { + error_ = false; + + // We only support GET request per the spec. + if (request()->method() != "GET") { + NotifyFailure(net::ERR_METHOD_NOT_SUPPORTED); + return; + } + + // If the blob data is not present, bail out. + if (!blob_data_.get()) { + NotifyFailure(net::ERR_FILE_NOT_FOUND); + return; + } + + CountSize(); +} + +bool BlobURLRequestJob::AddItemLength(size_t index, int64 item_length) { + if (item_length > kint64max - total_size_) { + NotifyFailure(net::ERR_FAILED); + return false; + } + + // Cache the size and add it to the total size. + DCHECK_LT(index, item_length_list_.size()); + item_length_list_[index] = item_length; + total_size_ += item_length; + return true; +} + +void BlobURLRequestJob::CountSize() { + pending_get_file_info_count_ = 0; + total_size_ = 0; + item_length_list_.resize(blob_data_->items().size()); + + for (size_t i = 0; i < blob_data_->items().size(); ++i) { + const BlobData::Item& item = blob_data_->items().at(i); + if (IsFileType(item.type())) { + ++pending_get_file_info_count_; + GetFileStreamReader(i)->GetLength( + base::Bind(&BlobURLRequestJob::DidGetFileItemLength, + weak_factory_.GetWeakPtr(), i)); + continue; + } + + if (!AddItemLength(i, item.length())) + return; + } + + if (pending_get_file_info_count_ == 0) + DidCountSize(net::OK); +} + +void BlobURLRequestJob::DidCountSize(int error) { + DCHECK(!error_); + + // If an error occured, bail out. + if (error != net::OK) { + NotifyFailure(error); + return; + } + + // Apply the range requirement. + if (!byte_range_.ComputeBounds(total_size_)) { + NotifyFailure(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE); + return; + } + + remaining_bytes_ = byte_range_.last_byte_position() - + byte_range_.first_byte_position() + 1; + DCHECK_GE(remaining_bytes_, 0); + + // Do the seek at the beginning of the request. + if (byte_range_.first_byte_position()) + Seek(byte_range_.first_byte_position()); + + NotifySuccess(); +} + +void BlobURLRequestJob::DidGetFileItemLength(size_t index, int64 result) { + // Do nothing if we have encountered an error. + if (error_) + return; + + if (result == net::ERR_UPLOAD_FILE_CHANGED) { + NotifyFailure(net::ERR_FILE_NOT_FOUND); + return; + } else if (result < 0) { + NotifyFailure(result); + return; + } + + DCHECK_LT(index, blob_data_->items().size()); + const BlobData::Item& item = blob_data_->items().at(index); + DCHECK(IsFileType(item.type())); + + uint64 file_length = result; + uint64 item_offset = item.offset(); + uint64 item_length = item.length(); + + if (item_offset > file_length) { + NotifyFailure(net::ERR_FILE_NOT_FOUND); + return; + } + + uint64 max_length = file_length - item_offset; + + // If item length is -1, we need to use the file size being resolved + // in the real time. + if (item_length == static_cast<uint64>(-1)) { + item_length = max_length; + } else if (item_length > max_length) { + NotifyFailure(net::ERR_FILE_NOT_FOUND); + return; + } + + if (!AddItemLength(index, item_length)) + return; + + if (--pending_get_file_info_count_ == 0) + DidCountSize(net::OK); +} + +void BlobURLRequestJob::Seek(int64 offset) { + // Skip the initial items that are not in the range. + for (current_item_index_ = 0; + current_item_index_ < blob_data_->items().size() && + offset >= item_length_list_[current_item_index_]; + ++current_item_index_) { + offset -= item_length_list_[current_item_index_]; + } + + // Set the offset that need to jump to for the first item in the range. + current_item_offset_ = offset; + + if (offset == 0) + return; + + // Adjust the offset of the first stream if it is of file type. + const BlobData::Item& item = blob_data_->items().at(current_item_index_); + if (IsFileType(item.type())) { + DeleteCurrentFileReader(); + CreateFileStreamReader(current_item_index_, offset); + } +} + +bool BlobURLRequestJob::ReadItem() { + // Are we done with reading all the blob data? + if (remaining_bytes_ == 0) + return true; + + // If we get to the last item but still expect something to read, bail out + // since something is wrong. + if (current_item_index_ >= blob_data_->items().size()) { + NotifyFailure(net::ERR_FAILED); + return false; + } + + // Compute the bytes to read for current item. + int bytes_to_read = ComputeBytesToRead(); + + // If nothing to read for current item, advance to next item. + if (bytes_to_read == 0) { + AdvanceItem(); + return ReadItem(); + } + + // Do the reading. + const BlobData::Item& item = blob_data_->items().at(current_item_index_); + if (item.type() == BlobData::Item::TYPE_BYTES) + return ReadBytesItem(item, bytes_to_read); + if (IsFileType(item.type())) { + return ReadFileItem(GetFileStreamReader(current_item_index_), + bytes_to_read); + } + NOTREACHED(); + return false; +} + +void BlobURLRequestJob::AdvanceItem() { + // Close the file if the current item is a file. + DeleteCurrentFileReader(); + + // Advance to the next item. + current_item_index_++; + current_item_offset_ = 0; +} + +void BlobURLRequestJob::AdvanceBytesRead(int result) { + DCHECK_GT(result, 0); + + // Do we finish reading the current item? + current_item_offset_ += result; + if (current_item_offset_ == item_length_list_[current_item_index_]) + AdvanceItem(); + + // Subtract the remaining bytes. + remaining_bytes_ -= result; + DCHECK_GE(remaining_bytes_, 0); + + // Adjust the read buffer. + read_buf_->DidConsume(result); + DCHECK_GE(read_buf_->BytesRemaining(), 0); +} + +bool BlobURLRequestJob::ReadBytesItem(const BlobData::Item& item, + int bytes_to_read) { + DCHECK_GE(read_buf_->BytesRemaining(), bytes_to_read); + + memcpy(read_buf_->data(), + item.bytes() + item.offset() + current_item_offset_, + bytes_to_read); + + AdvanceBytesRead(bytes_to_read); + return true; +} + +bool BlobURLRequestJob::ReadFileItem(FileStreamReader* reader, + int bytes_to_read) { + DCHECK_GE(read_buf_->BytesRemaining(), bytes_to_read); + DCHECK(reader); + const int result = reader->Read( + read_buf_.get(), + bytes_to_read, + base::Bind(&BlobURLRequestJob::DidReadFile, base::Unretained(this))); + if (result >= 0) { + // Data is immediately available. + if (GetStatus().is_io_pending()) + DidReadFile(result); + else + AdvanceBytesRead(result); + return true; + } + if (result == net::ERR_IO_PENDING) + SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0)); + else + NotifyFailure(result); + return false; +} + +void BlobURLRequestJob::DidReadFile(int result) { + if (result <= 0) { + NotifyFailure(net::ERR_FAILED); + return; + } + SetStatus(net::URLRequestStatus()); // Clear the IO_PENDING status + + AdvanceBytesRead(result); + + // If the read buffer is completely filled, we're done. + if (!read_buf_->BytesRemaining()) { + int bytes_read = BytesReadCompleted(); + NotifyReadComplete(bytes_read); + return; + } + + // Otherwise, continue the reading. + int bytes_read = 0; + if (ReadLoop(&bytes_read)) + NotifyReadComplete(bytes_read); +} + +void BlobURLRequestJob::DeleteCurrentFileReader() { + IndexToReaderMap::iterator found = index_to_reader_.find(current_item_index_); + if (found != index_to_reader_.end() && found->second) { + delete found->second; + index_to_reader_.erase(found); + } +} + +int BlobURLRequestJob::BytesReadCompleted() { + int bytes_read = read_buf_->BytesConsumed(); + read_buf_ = NULL; + return bytes_read; +} + +int BlobURLRequestJob::ComputeBytesToRead() const { + int64 current_item_length = item_length_list_[current_item_index_]; + + int64 item_remaining = current_item_length - current_item_offset_; + int64 buf_remaining = read_buf_->BytesRemaining(); + int64 max_remaining = std::numeric_limits<int>::max(); + + int64 min = std::min(std::min(std::min(item_remaining, + buf_remaining), + remaining_bytes_), + max_remaining); + + return static_cast<int>(min); +} + +bool BlobURLRequestJob::ReadLoop(int* bytes_read) { + // Read until we encounter an error or could not get the data immediately. + while (remaining_bytes_ > 0 && read_buf_->BytesRemaining() > 0) { + if (!ReadItem()) + return false; + } + + *bytes_read = BytesReadCompleted(); + return true; +} + +void BlobURLRequestJob::NotifySuccess() { + net::HttpStatusCode status_code = net::HTTP_OK; + if (byte_range_set_ && byte_range_.IsValid()) + status_code = net::HTTP_PARTIAL_CONTENT; + HeadersCompleted(status_code); +} + +void BlobURLRequestJob::NotifyFailure(int error_code) { + error_ = true; + + // If we already return the headers on success, we can't change the headers + // now. Instead, we just error out. + if (response_info_) { + NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, + error_code)); + return; + } + + net::HttpStatusCode status_code = net::HTTP_INTERNAL_SERVER_ERROR; + switch (error_code) { + case net::ERR_ACCESS_DENIED: + status_code = net::HTTP_FORBIDDEN; + break; + case net::ERR_FILE_NOT_FOUND: + status_code = net::HTTP_NOT_FOUND; + break; + case net::ERR_METHOD_NOT_SUPPORTED: + status_code = net::HTTP_METHOD_NOT_ALLOWED; + break; + case net::ERR_REQUEST_RANGE_NOT_SATISFIABLE: + status_code = net::HTTP_REQUESTED_RANGE_NOT_SATISFIABLE; + break; + case net::ERR_FAILED: + break; + default: + DCHECK(false); + break; + } + HeadersCompleted(status_code); +} + +void BlobURLRequestJob::HeadersCompleted(net::HttpStatusCode status_code) { + std::string status("HTTP/1.1 "); + status.append(base::IntToString(status_code)); + status.append(" "); + status.append(net::GetHttpReasonPhrase(status_code)); + status.append("\0\0", 2); + net::HttpResponseHeaders* headers = new net::HttpResponseHeaders(status); + + if (status_code == net::HTTP_OK || status_code == net::HTTP_PARTIAL_CONTENT) { + std::string content_length_header(net::HttpRequestHeaders::kContentLength); + content_length_header.append(": "); + content_length_header.append(base::Int64ToString(remaining_bytes_)); + headers->AddHeader(content_length_header); + if (status_code == net::HTTP_PARTIAL_CONTENT) { + DCHECK(byte_range_set_); + DCHECK(byte_range_.IsValid()); + std::string content_range_header(net::HttpResponseHeaders::kContentRange); + content_range_header.append(": bytes "); + content_range_header.append(base::StringPrintf( + "%" PRId64 "-%" PRId64, + byte_range_.first_byte_position(), byte_range_.last_byte_position())); + content_range_header.append("/"); + content_range_header.append(base::StringPrintf("%" PRId64, total_size_)); + headers->AddHeader(content_range_header); + } + if (!blob_data_->content_type().empty()) { + std::string content_type_header(net::HttpRequestHeaders::kContentType); + content_type_header.append(": "); + content_type_header.append(blob_data_->content_type()); + headers->AddHeader(content_type_header); + } + if (!blob_data_->content_disposition().empty()) { + std::string content_disposition_header("Content-Disposition: "); + content_disposition_header.append(blob_data_->content_disposition()); + headers->AddHeader(content_disposition_header); + } + } + + response_info_.reset(new net::HttpResponseInfo()); + response_info_->headers = headers; + + set_expected_content_size(remaining_bytes_); + + NotifyHeadersComplete(); +} + +FileStreamReader* BlobURLRequestJob::GetFileStreamReader(size_t index) { + DCHECK_LT(index, blob_data_->items().size()); + const BlobData::Item& item = blob_data_->items().at(index); + if (!IsFileType(item.type())) + return NULL; + if (index_to_reader_.find(index) == index_to_reader_.end()) + CreateFileStreamReader(index, 0); + DCHECK(index_to_reader_[index]); + return index_to_reader_[index]; +} + +void BlobURLRequestJob::CreateFileStreamReader(size_t index, + int64 additional_offset) { + DCHECK_LT(index, blob_data_->items().size()); + const BlobData::Item& item = blob_data_->items().at(index); + DCHECK(IsFileType(item.type())); + DCHECK_EQ(0U, index_to_reader_.count(index)); + + FileStreamReader* reader = NULL; + switch (item.type()) { + case BlobData::Item::TYPE_FILE: + reader = FileStreamReader::CreateForLocalFile( + file_thread_proxy_.get(), + item.path(), + item.offset() + additional_offset, + item.expected_modification_time()); + break; + case BlobData::Item::TYPE_FILE_FILESYSTEM: + reader = file_system_context_->CreateFileStreamReader( + storage::FileSystemURL( + file_system_context_->CrackURL( + item.filesystem_url())), + item.offset() + additional_offset, + item.expected_modification_time()) + .release(); + break; + default: + NOTREACHED(); + } + DCHECK(reader); + index_to_reader_[index] = reader; +} + +} // namespace storage diff --git a/storage/browser/blob/blob_url_request_job.h b/storage/browser/blob/blob_url_request_job.h new file mode 100644 index 0000000..58b6620 --- /dev/null +++ b/storage/browser/blob/blob_url_request_job.h @@ -0,0 +1,129 @@ +// 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. + +#ifndef STORAGE_BROWSER_BLOB_BLOB_URL_REQUEST_JOB_H_ +#define STORAGE_BROWSER_BLOB_BLOB_URL_REQUEST_JOB_H_ + +#include <map> + +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "net/http/http_byte_range.h" +#include "net/http/http_status_code.h" +#include "net/url_request/url_request_job.h" +#include "storage/browser/storage_browser_export.h" +#include "storage/common/blob/blob_data.h" + +namespace base { +class MessageLoopProxy; +} + +namespace storage { +class FileSystemContext; +} + +namespace net { +class DrainableIOBuffer; +class IOBuffer; +} + +namespace storage { + +class FileStreamReader; + +// A request job that handles reading blob URLs. +class STORAGE_EXPORT BlobURLRequestJob + : public net::URLRequestJob { + public: + BlobURLRequestJob(net::URLRequest* request, + net::NetworkDelegate* network_delegate, + const scoped_refptr<BlobData>& blob_data, + storage::FileSystemContext* file_system_context, + base::MessageLoopProxy* resolving_message_loop_proxy); + + // net::URLRequestJob methods. + virtual void Start() OVERRIDE; + virtual void Kill() OVERRIDE; + virtual bool ReadRawData(net::IOBuffer* buf, + int buf_size, + int* bytes_read) OVERRIDE; + virtual bool GetMimeType(std::string* mime_type) const OVERRIDE; + virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE; + virtual int GetResponseCode() const OVERRIDE; + virtual void SetExtraRequestHeaders( + const net::HttpRequestHeaders& headers) OVERRIDE; + + protected: + virtual ~BlobURLRequestJob(); + + private: + typedef std::map<size_t, FileStreamReader*> IndexToReaderMap; + + // For preparing for read: get the size, apply the range and perform seek. + void DidStart(); + bool AddItemLength(size_t index, int64 item_length); + void CountSize(); + void DidCountSize(int error); + void DidGetFileItemLength(size_t index, int64 result); + void Seek(int64 offset); + + // For reading the blob. + bool ReadLoop(int* bytes_read); + bool ReadItem(); + void AdvanceItem(); + void AdvanceBytesRead(int result); + bool ReadBytesItem(const BlobData::Item& item, int bytes_to_read); + bool ReadFileItem(FileStreamReader* reader, int bytes_to_read); + + void DidReadFile(int result); + void DeleteCurrentFileReader(); + + int ComputeBytesToRead() const; + int BytesReadCompleted(); + + // These methods convert the result of blob data reading into response headers + // and pass it to URLRequestJob's NotifyDone() or NotifyHeadersComplete(). + void NotifySuccess(); + void NotifyFailure(int); + void HeadersCompleted(net::HttpStatusCode status_code); + + // Returns a FileStreamReader for a blob item at |index|. + // If the item at |index| is not of file this returns NULL. + FileStreamReader* GetFileStreamReader(size_t index); + + // Creates a FileStreamReader for the item at |index| with additional_offset. + void CreateFileStreamReader(size_t index, int64 additional_offset); + + scoped_refptr<BlobData> blob_data_; + + // Variables for controlling read from |blob_data_|. + scoped_refptr<storage::FileSystemContext> file_system_context_; + scoped_refptr<base::MessageLoopProxy> file_thread_proxy_; + std::vector<int64> item_length_list_; + int64 total_size_; + int64 remaining_bytes_; + int pending_get_file_info_count_; + IndexToReaderMap index_to_reader_; + size_t current_item_index_; + int64 current_item_offset_; + + // Holds the buffer for read data with the IOBuffer interface. + scoped_refptr<net::DrainableIOBuffer> read_buf_; + + // Is set when NotifyFailure() is called and reset when DidStart is called. + bool error_; + + bool byte_range_set_; + net::HttpByteRange byte_range_; + + scoped_ptr<net::HttpResponseInfo> response_info_; + + base::WeakPtrFactory<BlobURLRequestJob> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(BlobURLRequestJob); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_BLOB_BLOB_URL_REQUEST_JOB_H_ diff --git a/storage/browser/blob/blob_url_request_job_factory.cc b/storage/browser/blob/blob_url_request_job_factory.cc new file mode 100644 index 0000000..60f9c73 --- /dev/null +++ b/storage/browser/blob/blob_url_request_job_factory.cc @@ -0,0 +1,90 @@ +// Copyright (c) 2011 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 "storage/browser/blob/blob_url_request_job_factory.h" + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/strings/string_util.h" +#include "net/base/request_priority.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_job_factory.h" +#include "storage/browser/blob/blob_data_handle.h" +#include "storage/browser/blob/blob_storage_context.h" +#include "storage/browser/blob/blob_url_request_job.h" +#include "storage/browser/fileapi/file_system_context.h" + +namespace storage { + +namespace { + +int kUserDataKey; // The value is not important, the addr is a key. + +BlobDataHandle* GetRequestedBlobDataHandle(net::URLRequest* request) { + return static_cast<BlobDataHandle*>(request->GetUserData(&kUserDataKey)); +} + +} // namespace + +// static +scoped_ptr<net::URLRequest> BlobProtocolHandler::CreateBlobRequest( + scoped_ptr<BlobDataHandle> blob_data_handle, + const net::URLRequestContext* request_context, + net::URLRequest::Delegate* request_delegate) { + const GURL kBlobUrl("blob://see_user_data/"); + scoped_ptr<net::URLRequest> request = request_context->CreateRequest( + kBlobUrl, net::DEFAULT_PRIORITY, request_delegate, NULL); + SetRequestedBlobDataHandle(request.get(), blob_data_handle.Pass()); + return request.Pass(); +} + +// static +void BlobProtocolHandler::SetRequestedBlobDataHandle( + net::URLRequest* request, + scoped_ptr<BlobDataHandle> blob_data_handle) { + request->SetUserData(&kUserDataKey, blob_data_handle.release()); +} + +BlobProtocolHandler::BlobProtocolHandler( + BlobStorageContext* context, + storage::FileSystemContext* file_system_context, + const scoped_refptr<base::MessageLoopProxy>& loop_proxy) + : file_system_context_(file_system_context), file_loop_proxy_(loop_proxy) { + if (context) + context_ = context->AsWeakPtr(); +} + +BlobProtocolHandler::~BlobProtocolHandler() { +} + +net::URLRequestJob* BlobProtocolHandler::MaybeCreateJob( + net::URLRequest* request, net::NetworkDelegate* network_delegate) const { + return new storage::BlobURLRequestJob(request, + network_delegate, + LookupBlobData(request), + file_system_context_.get(), + file_loop_proxy_.get()); +} + +scoped_refptr<storage::BlobData> BlobProtocolHandler::LookupBlobData( + net::URLRequest* request) const { + BlobDataHandle* blob_data_handle = GetRequestedBlobDataHandle(request); + if (blob_data_handle) + return blob_data_handle->data(); + if (!context_.get()) + return NULL; + + // Support looking up based on uuid, the FeedbackExtensionAPI relies on this. + // TODO(michaeln): Replace this use case and others like it with a BlobReader + // impl that does not depend on urlfetching to perform this function. + const std::string kPrefix("blob:uuid/"); + if (!StartsWithASCII(request->url().spec(), kPrefix, true)) + return NULL; + std::string uuid = request->url().spec().substr(kPrefix.length()); + scoped_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(uuid); + return handle.get() ? handle->data() : NULL; +} + +} // namespace storage diff --git a/storage/browser/blob/blob_url_request_job_factory.h b/storage/browser/blob/blob_url_request_job_factory.h new file mode 100644 index 0000000..e1769eb --- /dev/null +++ b/storage/browser/blob/blob_url_request_job_factory.h @@ -0,0 +1,71 @@ +// Copyright (c) 2011 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. + +#ifndef STORAGE_BROWSER_BLOB_BLOB_URL_REQUEST_JOB_FACTORY_H_ +#define STORAGE_BROWSER_BLOB_BLOB_URL_REQUEST_JOB_FACTORY_H_ + +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_job_factory.h" +#include "storage/browser/storage_browser_export.h" + +namespace base { +class MessageLoopProxy; +} // namespace base + +namespace storage { +class FileSystemContext; +} // namespace storage + +namespace net { +class URLRequestContext; +} // namespace net + +namespace storage { + +class BlobData; +class BlobDataHandle; +class BlobStorageContext; + +class STORAGE_EXPORT BlobProtocolHandler + : public net::URLRequestJobFactory::ProtocolHandler { + public: + // A helper to manufacture an URLRequest to retrieve the given blob. + static scoped_ptr<net::URLRequest> CreateBlobRequest( + scoped_ptr<BlobDataHandle> blob_data_handle, + const net::URLRequestContext* request_context, + net::URLRequest::Delegate* request_delegate); + + // This class ignores the request's URL and uses the value given + // to SetRequestedBlobDataHandle instead. + static void SetRequestedBlobDataHandle( + net::URLRequest* request, + scoped_ptr<BlobDataHandle> blob_data_handle); + + BlobProtocolHandler( + BlobStorageContext* context, + storage::FileSystemContext* file_system_context, + const scoped_refptr<base::MessageLoopProxy>& file_loop_proxy); + virtual ~BlobProtocolHandler(); + + virtual net::URLRequestJob* MaybeCreateJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate) const OVERRIDE; + + private: + scoped_refptr<BlobData> LookupBlobData( + net::URLRequest* request) const; + + base::WeakPtr<BlobStorageContext> context_; + const scoped_refptr<storage::FileSystemContext> file_system_context_; + const scoped_refptr<base::MessageLoopProxy> file_loop_proxy_; + + DISALLOW_COPY_AND_ASSIGN(BlobProtocolHandler); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_BLOB_BLOB_URL_REQUEST_JOB_FACTORY_H_ diff --git a/storage/browser/blob/file_stream_reader.cc b/storage/browser/blob/file_stream_reader.cc new file mode 100644 index 0000000..5c4aaa2 --- /dev/null +++ b/storage/browser/blob/file_stream_reader.cc @@ -0,0 +1,20 @@ +// Copyright 2014 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 "storage/browser/blob/file_stream_reader.h" + +#include "base/time/time.h" + +namespace storage { + +// Verify if the underlying file has not been modified. +bool FileStreamReader::VerifySnapshotTime( + const base::Time& expected_modification_time, + const base::File::Info& file_info) { + return expected_modification_time.is_null() || + expected_modification_time.ToTimeT() == + file_info.last_modified.ToTimeT(); +} + +} // namespace storage diff --git a/storage/browser/blob/file_stream_reader.h b/storage/browser/blob/file_stream_reader.h new file mode 100644 index 0000000..9b2ce02 --- /dev/null +++ b/storage/browser/blob/file_stream_reader.h @@ -0,0 +1,99 @@ +// 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. + +#ifndef STORAGE_BLOB_FILE_STREAM_READER_H_ +#define STORAGE_BLOB_FILE_STREAM_READER_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/files/file.h" +#include "net/base/completion_callback.h" +#include "storage/browser/storage_browser_export.h" + +namespace base { +class FilePath; +class TaskRunner; +class Time; +} + +namespace net { +class IOBuffer; +} + +namespace storage { +class FileSystemContext; +class FileSystemURL; +} + +namespace storage { + +// A generic interface for reading a file-like object. +class FileStreamReader { + public: + // Creates a new FileReader for a local file |file_path|. + // |initial_offset| specifies the offset in the file where the first read + // should start. If the given offset is out of the file range any + // read operation may error out with net::ERR_REQUEST_RANGE_NOT_SATISFIABLE. + // |expected_modification_time| specifies the expected last modification + // If the value is non-null, the reader will check the underlying file's + // actual modification time to see if the file has been modified, and if + // it does any succeeding read operations should fail with + // ERR_UPLOAD_FILE_CHANGED error. + STORAGE_EXPORT static FileStreamReader* + CreateForLocalFile(base::TaskRunner* task_runner, + const base::FilePath& file_path, + int64 initial_offset, + const base::Time& expected_modification_time); + + // Creates a new reader for a filesystem URL |url| form |initial_offset|. + // |expected_modification_time| specifies the expected last modification if + // the value is non-null, the reader will check the underlying file's actual + // modification time to see if the file has been modified, and if it does any + // succeeding read operations should fail with ERR_UPLOAD_FILE_CHANGED error. + STORAGE_EXPORT static FileStreamReader* + CreateForFileSystemFile(storage::FileSystemContext* context, + const storage::FileSystemURL& url, + int64 initial_offset, + const base::Time& expected_modification_time); + + // Verify if the underlying file has not been modified. + STORAGE_EXPORT static bool VerifySnapshotTime( + const base::Time& expected_modification_time, + const base::File::Info& file_info); + + // It is valid to delete the reader at any time. If the stream is deleted + // while it has a pending read, its callback will not be called. + virtual ~FileStreamReader() {} + + // Reads from the current cursor position asynchronously. + // + // Up to buf_len bytes will be copied into buf. (In other words, partial + // reads are allowed.) Returns the number of bytes copied, 0 if at + // end-of-file, or an error code if the operation could not be performed. + // If the read could not complete synchronously, then ERR_IO_PENDING is + // returned, and the callback will be run on the thread where Read() + // was called, when the read has completed. + // + // It is invalid to call Read while there is an in-flight Read operation. + // + // If the stream is deleted while it has an in-flight Read operation + // |callback| will not be called. + virtual int Read(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) = 0; + + // Returns the length of the file if it could successfully retrieve the + // file info *and* its last modification time equals to + // expected modification time (rv >= 0 cases). + // Otherwise, a negative error code is returned (rv < 0 cases). + // If the stream is deleted while it has an in-flight GetLength operation + // |callback| will not be called. + // Note that the return type is int64 to return a larger file's size (a file + // larger than 2G) but an error code should fit in the int range (may be + // smaller than int64 range). + virtual int64 GetLength(const net::Int64CompletionCallback& callback) = 0; +}; + +} // namespace storage + +#endif // STORAGE_BLOB_FILE_STREAM_READER_H_ diff --git a/storage/browser/blob/local_file_stream_reader.cc b/storage/browser/blob/local_file_stream_reader.cc new file mode 100644 index 0000000..e6403b7 --- /dev/null +++ b/storage/browser/blob/local_file_stream_reader.cc @@ -0,0 +1,169 @@ +// 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 "storage/browser/blob/local_file_stream_reader.h" + +#include "base/files/file_util.h" +#include "base/files/file_util_proxy.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/task_runner.h" +#include "net/base/file_stream.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" + +namespace storage { + +namespace { + +const int kOpenFlagsForRead = base::File::FLAG_OPEN | + base::File::FLAG_READ | + base::File::FLAG_ASYNC; + +} // namespace + +FileStreamReader* FileStreamReader::CreateForLocalFile( + base::TaskRunner* task_runner, + const base::FilePath& file_path, + int64 initial_offset, + const base::Time& expected_modification_time) { + return new LocalFileStreamReader(task_runner, file_path, initial_offset, + expected_modification_time); +} + +LocalFileStreamReader::~LocalFileStreamReader() { +} + +int LocalFileStreamReader::Read(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) { + DCHECK(!has_pending_open_); + if (stream_impl_) + return stream_impl_->Read(buf, buf_len, callback); + return Open(base::Bind(&LocalFileStreamReader::DidOpenForRead, + weak_factory_.GetWeakPtr(), + make_scoped_refptr(buf), buf_len, callback)); +} + +int64 LocalFileStreamReader::GetLength( + const net::Int64CompletionCallback& callback) { + const bool posted = base::FileUtilProxy::GetFileInfo( + task_runner_.get(), + file_path_, + base::Bind(&LocalFileStreamReader::DidGetFileInfoForGetLength, + weak_factory_.GetWeakPtr(), + callback)); + DCHECK(posted); + return net::ERR_IO_PENDING; +} + +LocalFileStreamReader::LocalFileStreamReader( + base::TaskRunner* task_runner, + const base::FilePath& file_path, + int64 initial_offset, + const base::Time& expected_modification_time) + : task_runner_(task_runner), + file_path_(file_path), + initial_offset_(initial_offset), + expected_modification_time_(expected_modification_time), + has_pending_open_(false), + weak_factory_(this) {} + +int LocalFileStreamReader::Open(const net::CompletionCallback& callback) { + DCHECK(!has_pending_open_); + DCHECK(!stream_impl_.get()); + has_pending_open_ = true; + + // Call GetLength first to make it perform last-modified-time verification, + // and then call DidVerifyForOpen for do the rest. + return GetLength(base::Bind(&LocalFileStreamReader::DidVerifyForOpen, + weak_factory_.GetWeakPtr(), callback)); +} + +void LocalFileStreamReader::DidVerifyForOpen( + const net::CompletionCallback& callback, + int64 get_length_result) { + if (get_length_result < 0) { + callback.Run(static_cast<int>(get_length_result)); + return; + } + + stream_impl_.reset(new net::FileStream(task_runner_)); + const int result = stream_impl_->Open( + file_path_, kOpenFlagsForRead, + base::Bind(&LocalFileStreamReader::DidOpenFileStream, + weak_factory_.GetWeakPtr(), + callback)); + if (result != net::ERR_IO_PENDING) + callback.Run(result); +} + +void LocalFileStreamReader::DidOpenFileStream( + const net::CompletionCallback& callback, + int result) { + if (result != net::OK) { + callback.Run(result); + return; + } + result = stream_impl_->Seek( + base::File::FROM_BEGIN, initial_offset_, + base::Bind(&LocalFileStreamReader::DidSeekFileStream, + weak_factory_.GetWeakPtr(), + callback)); + if (result != net::ERR_IO_PENDING) { + callback.Run(result); + } +} + +void LocalFileStreamReader::DidSeekFileStream( + const net::CompletionCallback& callback, + int64 seek_result) { + if (seek_result < 0) { + callback.Run(static_cast<int>(seek_result)); + return; + } + if (seek_result != initial_offset_) { + callback.Run(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE); + return; + } + callback.Run(net::OK); +} + +void LocalFileStreamReader::DidOpenForRead( + net::IOBuffer* buf, + int buf_len, + const net::CompletionCallback& callback, + int open_result) { + DCHECK(has_pending_open_); + has_pending_open_ = false; + if (open_result != net::OK) { + stream_impl_.reset(); + callback.Run(open_result); + return; + } + DCHECK(stream_impl_.get()); + const int read_result = stream_impl_->Read(buf, buf_len, callback); + if (read_result != net::ERR_IO_PENDING) + callback.Run(read_result); +} + +void LocalFileStreamReader::DidGetFileInfoForGetLength( + const net::Int64CompletionCallback& callback, + base::File::Error error, + const base::File::Info& file_info) { + if (file_info.is_directory) { + callback.Run(net::ERR_FILE_NOT_FOUND); + return; + } + if (error != base::File::FILE_OK) { + callback.Run(net::FileErrorToNetError(error)); + return; + } + if (!VerifySnapshotTime(expected_modification_time_, file_info)) { + callback.Run(net::ERR_UPLOAD_FILE_CHANGED); + return; + } + callback.Run(file_info.size); +} + +} // namespace storage diff --git a/storage/browser/blob/local_file_stream_reader.h b/storage/browser/blob/local_file_stream_reader.h new file mode 100644 index 0000000..b9e3328 --- /dev/null +++ b/storage/browser/blob/local_file_stream_reader.h @@ -0,0 +1,81 @@ +// 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. + +#ifndef STORAGE_BROWSER_BLOB_LOCAL_FILE_STREAM_READER_H_ +#define STORAGE_BROWSER_BLOB_LOCAL_FILE_STREAM_READER_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/memory/weak_ptr.h" +#include "base/time/time.h" +#include "storage/browser/blob/file_stream_reader.h" +#include "storage/browser/storage_browser_export.h" + +namespace base { +class TaskRunner; +} + +namespace content { +class LocalFileStreamReaderTest; +} + +namespace net { +class FileStream; +} + +namespace storage { + +// A thin wrapper of net::FileStream with range support for sliced file +// handling. +class STORAGE_EXPORT LocalFileStreamReader + : public NON_EXPORTED_BASE(FileStreamReader) { + public: + virtual ~LocalFileStreamReader(); + + // FileStreamReader overrides. + virtual int Read(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) OVERRIDE; + virtual int64 GetLength( + const net::Int64CompletionCallback& callback) OVERRIDE; + + private: + friend class FileStreamReader; + friend class content::LocalFileStreamReaderTest; + + LocalFileStreamReader(base::TaskRunner* task_runner, + const base::FilePath& file_path, + int64 initial_offset, + const base::Time& expected_modification_time); + int Open(const net::CompletionCallback& callback); + + // Callbacks that are chained from Open for Read. + void DidVerifyForOpen(const net::CompletionCallback& callback, + int64 get_length_result); + void DidOpenFileStream(const net::CompletionCallback& callback, + int result); + void DidSeekFileStream(const net::CompletionCallback& callback, + int64 seek_result); + void DidOpenForRead(net::IOBuffer* buf, + int buf_len, + const net::CompletionCallback& callback, + int open_result); + + void DidGetFileInfoForGetLength(const net::Int64CompletionCallback& callback, + base::File::Error error, + const base::File::Info& file_info); + + scoped_refptr<base::TaskRunner> task_runner_; + scoped_ptr<net::FileStream> stream_impl_; + const base::FilePath file_path_; + const int64 initial_offset_; + const base::Time expected_modification_time_; + bool has_pending_open_; + base::WeakPtrFactory<LocalFileStreamReader> weak_factory_; +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_BLOB_LOCAL_FILE_STREAM_READER_H_ diff --git a/storage/browser/blob/view_blob_internals_job.cc b/storage/browser/blob/view_blob_internals_job.cc new file mode 100644 index 0000000..2065a31 --- /dev/null +++ b/storage/browser/blob/view_blob_internals_job.cc @@ -0,0 +1,242 @@ +// 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 "storage/browser/blob/view_blob_internals_job.h" + +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/format_macros.h" +#include "base/i18n/number_formatting.h" +#include "base/i18n/time_formatting.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "net/base/escape.h" +#include "net/base/net_errors.h" +#include "net/url_request/url_request.h" +#include "storage/browser/blob/blob_storage_context.h" +#include "storage/common/blob/blob_data.h" + +namespace { + +const char kEmptyBlobStorageMessage[] = "No available blob data."; +const char kContentType[] = "Content Type: "; +const char kContentDisposition[] = "Content Disposition: "; +const char kCount[] = "Count: "; +const char kIndex[] = "Index: "; +const char kType[] = "Type: "; +const char kPath[] = "Path: "; +const char kURL[] = "URL: "; +const char kModificationTime[] = "Modification Time: "; +const char kOffset[] = "Offset: "; +const char kLength[] = "Length: "; +const char kUUID[] = "Uuid: "; +const char kRefcount[] = "Refcount: "; + +void StartHTML(std::string* out) { + out->append( + "<!DOCTYPE HTML>" + "<html><title>Blob Storage Internals</title>" + "<meta http-equiv=\"Content-Security-Policy\"" + " content=\"object-src 'none'; script-src 'none'\">\n" + "<style>\n" + "body { font-family: sans-serif; font-size: 0.8em; }\n" + "tt, code, pre { font-family: WebKitHack, monospace; }\n" + "form { display: inline }\n" + ".subsection_body { margin: 10px 0 10px 2em; }\n" + ".subsection_title { font-weight: bold; }\n" + "</style>\n" + "</head><body>\n\n"); +} + +void EndHTML(std::string* out) { + out->append("\n</body></html>"); +} + +void AddHTMLBoldText(const std::string& text, std::string* out) { + out->append("<b>"); + out->append(net::EscapeForHTML(text)); + out->append("</b>"); +} + +void StartHTMLList(std::string* out) { + out->append("\n<ul>"); +} + +void EndHTMLList(std::string* out) { + out->append("</ul>\n"); +} + +void AddHTMLListItem(const std::string& element_title, + const std::string& element_data, + std::string* out) { + out->append("<li>"); + // No need to escape element_title since constant string is passed. + out->append(element_title); + out->append(net::EscapeForHTML(element_data)); + out->append("</li>\n"); +} + +void AddHorizontalRule(std::string* out) { + out->append("\n<hr>\n"); +} + +} // namespace + +namespace storage { + +ViewBlobInternalsJob::ViewBlobInternalsJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate, + BlobStorageContext* blob_storage_context) + : net::URLRequestSimpleJob(request, network_delegate), + blob_storage_context_(blob_storage_context), + weak_factory_(this) { +} + +ViewBlobInternalsJob::~ViewBlobInternalsJob() { +} + +void ViewBlobInternalsJob::Start() { + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&ViewBlobInternalsJob::StartAsync, + weak_factory_.GetWeakPtr())); +} + +bool ViewBlobInternalsJob::IsRedirectResponse(GURL* location, + int* http_status_code) { + if (request_->url().has_query()) { + // Strip the query parameters. + GURL::Replacements replacements; + replacements.ClearQuery(); + *location = request_->url().ReplaceComponents(replacements); + *http_status_code = 307; + return true; + } + return false; +} + +void ViewBlobInternalsJob::Kill() { + net::URLRequestSimpleJob::Kill(); + weak_factory_.InvalidateWeakPtrs(); +} + +int ViewBlobInternalsJob::GetData( + std::string* mime_type, + std::string* charset, + std::string* data, + const net::CompletionCallback& callback) const { + mime_type->assign("text/html"); + charset->assign("UTF-8"); + + data->clear(); + StartHTML(data); + if (blob_storage_context_->blob_map_.empty()) + data->append(kEmptyBlobStorageMessage); + else + GenerateHTML(data); + EndHTML(data); + return net::OK; +} + +void ViewBlobInternalsJob::GenerateHTML(std::string* out) const { + for (BlobStorageContext::BlobMap::const_iterator iter = + blob_storage_context_->blob_map_.begin(); + iter != blob_storage_context_->blob_map_.end(); + ++iter) { + AddHTMLBoldText(iter->first, out); + GenerateHTMLForBlobData(*(iter->second.data.get()), + iter->second.refcount, + out); + } + if (!blob_storage_context_->public_blob_urls_.empty()) { + AddHorizontalRule(out); + for (BlobStorageContext::BlobURLMap::const_iterator iter = + blob_storage_context_->public_blob_urls_.begin(); + iter != blob_storage_context_->public_blob_urls_.end(); + ++iter) { + AddHTMLBoldText(iter->first.spec(), out); + StartHTMLList(out); + AddHTMLListItem(kUUID, iter->second, out); + EndHTMLList(out); + } + } +} + +void ViewBlobInternalsJob::GenerateHTMLForBlobData(const BlobData& blob_data, + int refcount, + std::string* out) { + StartHTMLList(out); + + AddHTMLListItem(kRefcount, base::IntToString(refcount), out); + if (!blob_data.content_type().empty()) + AddHTMLListItem(kContentType, blob_data.content_type(), out); + if (!blob_data.content_disposition().empty()) + AddHTMLListItem(kContentDisposition, blob_data.content_disposition(), out); + + bool has_multi_items = blob_data.items().size() > 1; + if (has_multi_items) { + AddHTMLListItem(kCount, + base::UTF16ToUTF8(base::FormatNumber(blob_data.items().size())), out); + } + + for (size_t i = 0; i < blob_data.items().size(); ++i) { + if (has_multi_items) { + AddHTMLListItem(kIndex, base::UTF16ToUTF8(base::FormatNumber(i)), out); + StartHTMLList(out); + } + const BlobData::Item& item = blob_data.items().at(i); + + switch (item.type()) { + case BlobData::Item::TYPE_BYTES: + AddHTMLListItem(kType, "data", out); + break; + case BlobData::Item::TYPE_FILE: + AddHTMLListItem(kType, "file", out); + AddHTMLListItem(kPath, + net::EscapeForHTML(item.path().AsUTF8Unsafe()), + out); + if (!item.expected_modification_time().is_null()) { + AddHTMLListItem(kModificationTime, base::UTF16ToUTF8( + TimeFormatFriendlyDateAndTime(item.expected_modification_time())), + out); + } + break; + case BlobData::Item::TYPE_BLOB: + NOTREACHED(); // Should be flattened in the storage context. + break; + case BlobData::Item::TYPE_FILE_FILESYSTEM: + AddHTMLListItem(kType, "filesystem", out); + AddHTMLListItem(kURL, item.filesystem_url().spec(), out); + if (!item.expected_modification_time().is_null()) { + AddHTMLListItem(kModificationTime, base::UTF16ToUTF8( + TimeFormatFriendlyDateAndTime(item.expected_modification_time())), + out); + } + break; + case BlobData::Item::TYPE_UNKNOWN: + NOTREACHED(); + break; + } + if (item.offset()) { + AddHTMLListItem(kOffset, base::UTF16ToUTF8(base::FormatNumber( + static_cast<int64>(item.offset()))), out); + } + if (static_cast<int64>(item.length()) != -1) { + AddHTMLListItem(kLength, base::UTF16ToUTF8(base::FormatNumber( + static_cast<int64>(item.length()))), out); + } + + if (has_multi_items) + EndHTMLList(out); + } + + EndHTMLList(out); +} + +} // namespace storage diff --git a/storage/browser/blob/view_blob_internals_job.h b/storage/browser/blob/view_blob_internals_job.h new file mode 100644 index 0000000..3fc2f08 --- /dev/null +++ b/storage/browser/blob/view_blob_internals_job.h @@ -0,0 +1,57 @@ +// 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. + +#ifndef STORAGE_BROWSER_BLOB_VIEW_BLOB_INTERNALS_JOB_H_ +#define STORAGE_BROWSER_BLOB_VIEW_BLOB_INTERNALS_JOB_H_ + +#include <string> + +#include "base/memory/weak_ptr.h" +#include "net/url_request/url_request_simple_job.h" +#include "storage/browser/storage_browser_export.h" + +namespace net { +class URLRequest; +} // namespace net + +namespace storage { + +class BlobData; +class BlobStorageContext; + +// A job subclass that implements a protocol to inspect the internal +// state of blob registry. +class STORAGE_EXPORT ViewBlobInternalsJob + : public net::URLRequestSimpleJob { + public: + ViewBlobInternalsJob(net::URLRequest* request, + net::NetworkDelegate* network_delegate, + BlobStorageContext* blob_storage_context); + + virtual void Start() OVERRIDE; + virtual int GetData(std::string* mime_type, + std::string* charset, + std::string* data, + const net::CompletionCallback& callback) const OVERRIDE; + virtual bool IsRedirectResponse(GURL* location, + int* http_status_code) OVERRIDE; + virtual void Kill() OVERRIDE; + + private: + virtual ~ViewBlobInternalsJob(); + + void GenerateHTML(std::string* out) const; + static void GenerateHTMLForBlobData(const BlobData& blob_data, + int refcount, + std::string* out); + + BlobStorageContext* blob_storage_context_; + base::WeakPtrFactory<ViewBlobInternalsJob> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(ViewBlobInternalsJob); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_BLOB_VIEW_BLOB_INTERNALS_JOB_H_ diff --git a/storage/browser/database/database_quota_client.cc b/storage/browser/database/database_quota_client.cc new file mode 100644 index 0000000..5119a1c --- /dev/null +++ b/storage/browser/database/database_quota_client.cc @@ -0,0 +1,220 @@ +// 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 "storage/browser/database/database_quota_client.h" + +#include <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/location.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/task_runner_util.h" +#include "net/base/net_errors.h" +#include "net/base/net_util.h" +#include "storage/browser/database/database_tracker.h" +#include "storage/browser/database/database_util.h" +#include "storage/common/database/database_identifier.h" + +using storage::QuotaClient; + +namespace storage { + +namespace { + +int64 GetOriginUsageOnDBThread( + DatabaseTracker* db_tracker, + const GURL& origin_url) { + OriginInfo info; + if (db_tracker->GetOriginInfo(storage::GetIdentifierFromOrigin(origin_url), + &info)) + return info.TotalSize(); + return 0; +} + +void GetOriginsOnDBThread( + DatabaseTracker* db_tracker, + std::set<GURL>* origins_ptr) { + std::vector<std::string> origin_identifiers; + if (db_tracker->GetAllOriginIdentifiers(&origin_identifiers)) { + for (std::vector<std::string>::const_iterator iter = + origin_identifiers.begin(); + iter != origin_identifiers.end(); ++iter) { + GURL origin = storage::GetOriginFromIdentifier(*iter); + origins_ptr->insert(origin); + } + } +} + +void GetOriginsForHostOnDBThread( + DatabaseTracker* db_tracker, + std::set<GURL>* origins_ptr, + const std::string& host) { + std::vector<std::string> origin_identifiers; + if (db_tracker->GetAllOriginIdentifiers(&origin_identifiers)) { + for (std::vector<std::string>::const_iterator iter = + origin_identifiers.begin(); + iter != origin_identifiers.end(); ++iter) { + GURL origin = storage::GetOriginFromIdentifier(*iter); + if (host == net::GetHostOrSpecFromURL(origin)) + origins_ptr->insert(origin); + } + } +} + +void DidGetOrigins( + const QuotaClient::GetOriginsCallback& callback, + std::set<GURL>* origins_ptr) { + callback.Run(*origins_ptr); +} + +void DidDeleteOriginData( + base::SingleThreadTaskRunner* original_task_runner, + const QuotaClient::DeletionCallback& callback, + int result) { + if (result == net::ERR_IO_PENDING) { + // The callback will be invoked via + // DatabaseTracker::ScheduleDatabasesForDeletion. + return; + } + + storage::QuotaStatusCode status; + if (result == net::OK) + status = storage::kQuotaStatusOk; + else + status = storage::kQuotaStatusUnknown; + + if (original_task_runner->BelongsToCurrentThread()) + callback.Run(status); + else + original_task_runner->PostTask(FROM_HERE, base::Bind(callback, status)); +} + +} // namespace + +DatabaseQuotaClient::DatabaseQuotaClient( + base::MessageLoopProxy* db_tracker_thread, + DatabaseTracker* db_tracker) + : db_tracker_thread_(db_tracker_thread), db_tracker_(db_tracker) { +} + +DatabaseQuotaClient::~DatabaseQuotaClient() { + if (db_tracker_thread_.get() && + !db_tracker_thread_->RunsTasksOnCurrentThread() && db_tracker_.get()) { + DatabaseTracker* tracker = db_tracker_.get(); + tracker->AddRef(); + db_tracker_ = NULL; + if (!db_tracker_thread_->ReleaseSoon(FROM_HERE, tracker)) + tracker->Release(); + } +} + +QuotaClient::ID DatabaseQuotaClient::id() const { + return kDatabase; +} + +void DatabaseQuotaClient::OnQuotaManagerDestroyed() { + delete this; +} + +void DatabaseQuotaClient::GetOriginUsage(const GURL& origin_url, + storage::StorageType type, + const GetUsageCallback& callback) { + DCHECK(!callback.is_null()); + DCHECK(db_tracker_.get()); + + // All databases are in the temp namespace for now. + if (type != storage::kStorageTypeTemporary) { + callback.Run(0); + return; + } + + base::PostTaskAndReplyWithResult( + db_tracker_thread_.get(), + FROM_HERE, + base::Bind(&GetOriginUsageOnDBThread, db_tracker_, origin_url), + callback); +} + +void DatabaseQuotaClient::GetOriginsForType( + storage::StorageType type, + const GetOriginsCallback& callback) { + DCHECK(!callback.is_null()); + DCHECK(db_tracker_.get()); + + // All databases are in the temp namespace for now. + if (type != storage::kStorageTypeTemporary) { + callback.Run(std::set<GURL>()); + return; + } + + std::set<GURL>* origins_ptr = new std::set<GURL>(); + db_tracker_thread_->PostTaskAndReply( + FROM_HERE, + base::Bind(&GetOriginsOnDBThread, + db_tracker_, + base::Unretained(origins_ptr)), + base::Bind(&DidGetOrigins, + callback, + base::Owned(origins_ptr))); +} + +void DatabaseQuotaClient::GetOriginsForHost( + storage::StorageType type, + const std::string& host, + const GetOriginsCallback& callback) { + DCHECK(!callback.is_null()); + DCHECK(db_tracker_.get()); + + // All databases are in the temp namespace for now. + if (type != storage::kStorageTypeTemporary) { + callback.Run(std::set<GURL>()); + return; + } + + std::set<GURL>* origins_ptr = new std::set<GURL>(); + db_tracker_thread_->PostTaskAndReply( + FROM_HERE, + base::Bind(&GetOriginsForHostOnDBThread, + db_tracker_, + base::Unretained(origins_ptr), + host), + base::Bind(&DidGetOrigins, + callback, + base::Owned(origins_ptr))); +} + +void DatabaseQuotaClient::DeleteOriginData(const GURL& origin, + storage::StorageType type, + const DeletionCallback& callback) { + DCHECK(!callback.is_null()); + DCHECK(db_tracker_.get()); + + // All databases are in the temp namespace for now, so nothing to delete. + if (type != storage::kStorageTypeTemporary) { + callback.Run(storage::kQuotaStatusOk); + return; + } + + base::Callback<void(int)> delete_callback = + base::Bind(&DidDeleteOriginData, + base::MessageLoopProxy::current(), + callback); + + PostTaskAndReplyWithResult( + db_tracker_thread_.get(), + FROM_HERE, + base::Bind(&DatabaseTracker::DeleteDataForOrigin, + db_tracker_, + storage::GetIdentifierFromOrigin(origin), + delete_callback), + delete_callback); +} + +bool DatabaseQuotaClient::DoesSupport(storage::StorageType type) const { + return type == storage::kStorageTypeTemporary; +} + +} // namespace storage diff --git a/storage/browser/database/database_quota_client.h b/storage/browser/database/database_quota_client.h new file mode 100644 index 0000000..ae276c7 --- /dev/null +++ b/storage/browser/database/database_quota_client.h @@ -0,0 +1,57 @@ +// Copyright (c) 2011 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. + +#ifndef STORAGE_BROWSER_DATABASE_DATABASE_QUOTA_CLIENT_H_ +#define STORAGE_BROWSER_DATABASE_DATABASE_QUOTA_CLIENT_H_ + +#include <set> +#include <string> + +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop_proxy.h" +#include "storage/browser/quota/quota_client.h" +#include "storage/browser/storage_browser_export.h" +#include "storage/common/quota/quota_types.h" + +namespace storage { + +class DatabaseTracker; + +// A QuotaClient implementation to integrate WebSQLDatabases +// with the quota management system. This interface is used +// on the IO thread by the quota manager. +class STORAGE_EXPORT_PRIVATE DatabaseQuotaClient + : public storage::QuotaClient { + public: + DatabaseQuotaClient( + base::MessageLoopProxy* tracker_thread, + DatabaseTracker* tracker); + virtual ~DatabaseQuotaClient(); + + // QuotaClient method overrides + virtual ID id() const OVERRIDE; + virtual void OnQuotaManagerDestroyed() OVERRIDE; + virtual void GetOriginUsage(const GURL& origin_url, + storage::StorageType type, + const GetUsageCallback& callback) OVERRIDE; + virtual void GetOriginsForType(storage::StorageType type, + const GetOriginsCallback& callback) OVERRIDE; + virtual void GetOriginsForHost(storage::StorageType type, + const std::string& host, + const GetOriginsCallback& callback) OVERRIDE; + virtual void DeleteOriginData(const GURL& origin, + storage::StorageType type, + const DeletionCallback& callback) OVERRIDE; + virtual bool DoesSupport(storage::StorageType type) const OVERRIDE; + + private: + scoped_refptr<base::MessageLoopProxy> db_tracker_thread_; + scoped_refptr<DatabaseTracker> db_tracker_; // only used on its thread + + DISALLOW_COPY_AND_ASSIGN(DatabaseQuotaClient); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_DATABASE_DATABASE_QUOTA_CLIENT_H_ diff --git a/storage/browser/database/database_tracker.cc b/storage/browser/database/database_tracker.cc new file mode 100644 index 0000000..b7e4623 --- /dev/null +++ b/storage/browser/database/database_tracker.cc @@ -0,0 +1,869 @@ +// 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 "storage/browser/database/database_tracker.h" + +#include <algorithm> +#include <vector> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/files/file.h" +#include "base/files/file_enumerator.h" +#include "base/files/file_util.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "net/base/net_errors.h" +#include "sql/connection.h" +#include "sql/meta_table.h" +#include "sql/transaction.h" +#include "storage/browser/database/database_quota_client.h" +#include "storage/browser/database/database_util.h" +#include "storage/browser/database/databases_table.h" +#include "storage/browser/quota/quota_manager_proxy.h" +#include "storage/browser/quota/special_storage_policy.h" +#include "storage/common/database/database_identifier.h" +#include "third_party/sqlite/sqlite3.h" + +namespace storage { + +const base::FilePath::CharType kDatabaseDirectoryName[] = + FILE_PATH_LITERAL("databases"); +const base::FilePath::CharType kIncognitoDatabaseDirectoryName[] = + FILE_PATH_LITERAL("databases-incognito"); +const base::FilePath::CharType kTrackerDatabaseFileName[] = + FILE_PATH_LITERAL("Databases.db"); +static const int kCurrentVersion = 2; +static const int kCompatibleVersion = 1; + +const base::FilePath::CharType kTemporaryDirectoryPrefix[] = + FILE_PATH_LITERAL("DeleteMe"); +const base::FilePath::CharType kTemporaryDirectoryPattern[] = + FILE_PATH_LITERAL("DeleteMe*"); + +OriginInfo::OriginInfo() + : total_size_(0) {} + +OriginInfo::OriginInfo(const OriginInfo& origin_info) + : origin_identifier_(origin_info.origin_identifier_), + total_size_(origin_info.total_size_), + database_info_(origin_info.database_info_) {} + +OriginInfo::~OriginInfo() {} + +void OriginInfo::GetAllDatabaseNames( + std::vector<base::string16>* databases) const { + for (DatabaseInfoMap::const_iterator it = database_info_.begin(); + it != database_info_.end(); it++) { + databases->push_back(it->first); + } +} + +int64 OriginInfo::GetDatabaseSize(const base::string16& database_name) const { + DatabaseInfoMap::const_iterator it = database_info_.find(database_name); + if (it != database_info_.end()) + return it->second.first; + return 0; +} + +base::string16 OriginInfo::GetDatabaseDescription( + const base::string16& database_name) const { + DatabaseInfoMap::const_iterator it = database_info_.find(database_name); + if (it != database_info_.end()) + return it->second.second; + return base::string16(); +} + +OriginInfo::OriginInfo(const std::string& origin_identifier, int64 total_size) + : origin_identifier_(origin_identifier), total_size_(total_size) {} + +DatabaseTracker::DatabaseTracker( + const base::FilePath& profile_path, + bool is_incognito, + storage::SpecialStoragePolicy* special_storage_policy, + storage::QuotaManagerProxy* quota_manager_proxy, + base::MessageLoopProxy* db_tracker_thread) + : is_initialized_(false), + is_incognito_(is_incognito), + force_keep_session_state_(false), + shutting_down_(false), + profile_path_(profile_path), + db_dir_(is_incognito_ + ? profile_path_.Append(kIncognitoDatabaseDirectoryName) + : profile_path_.Append(kDatabaseDirectoryName)), + db_(new sql::Connection()), + special_storage_policy_(special_storage_policy), + quota_manager_proxy_(quota_manager_proxy), + db_tracker_thread_(db_tracker_thread), + incognito_origin_directories_generator_(0) { + if (quota_manager_proxy) { + quota_manager_proxy->RegisterClient( + new DatabaseQuotaClient(db_tracker_thread, this)); + } +} + +DatabaseTracker::~DatabaseTracker() { + DCHECK(dbs_to_be_deleted_.empty()); + DCHECK(deletion_callbacks_.empty()); +} + +void DatabaseTracker::DatabaseOpened(const std::string& origin_identifier, + const base::string16& database_name, + const base::string16& database_description, + int64 estimated_size, + int64* database_size) { + if (shutting_down_ || !LazyInit()) { + *database_size = 0; + return; + } + + if (quota_manager_proxy_.get()) + quota_manager_proxy_->NotifyStorageAccessed( + storage::QuotaClient::kDatabase, + storage::GetOriginFromIdentifier(origin_identifier), + storage::kStorageTypeTemporary); + + InsertOrUpdateDatabaseDetails(origin_identifier, database_name, + database_description, estimated_size); + if (database_connections_.AddConnection(origin_identifier, database_name)) { + *database_size = SeedOpenDatabaseInfo(origin_identifier, + database_name, + database_description); + return; + } + *database_size = UpdateOpenDatabaseInfoAndNotify(origin_identifier, + database_name, + &database_description); +} + +void DatabaseTracker::DatabaseModified(const std::string& origin_identifier, + const base::string16& database_name) { + if (!LazyInit()) + return; + UpdateOpenDatabaseSizeAndNotify(origin_identifier, database_name); +} + +void DatabaseTracker::DatabaseClosed(const std::string& origin_identifier, + const base::string16& database_name) { + if (database_connections_.IsEmpty()) { + DCHECK(!is_initialized_); + return; + } + + // We call NotifiyStorageAccessed when a db is opened and also when + // closed because we don't call it for read while open. + if (quota_manager_proxy_.get()) + quota_manager_proxy_->NotifyStorageAccessed( + storage::QuotaClient::kDatabase, + storage::GetOriginFromIdentifier(origin_identifier), + storage::kStorageTypeTemporary); + + UpdateOpenDatabaseSizeAndNotify(origin_identifier, database_name); + if (database_connections_.RemoveConnection(origin_identifier, database_name)) + DeleteDatabaseIfNeeded(origin_identifier, database_name); +} + +void DatabaseTracker::HandleSqliteError( + const std::string& origin_identifier, + const base::string16& database_name, + int error) { + // We only handle errors that indicate corruption and we + // do so with a heavy hand, we delete it. Any renderers/workers + // with this database open will receive a message to close it + // immediately, once all have closed, the files will be deleted. + // In the interim, all attempts to open a new connection to that + // database will fail. + // Note: the client-side filters out all but these two errors as + // a small optimization, see WebDatabaseObserverImpl::HandleSqliteError. + if (error == SQLITE_CORRUPT || error == SQLITE_NOTADB) { + DeleteDatabase(origin_identifier, database_name, + net::CompletionCallback()); + } +} + +void DatabaseTracker::CloseDatabases(const DatabaseConnections& connections) { + if (database_connections_.IsEmpty()) { + DCHECK(!is_initialized_ || connections.IsEmpty()); + return; + } + + // When being closed by this route, there's a chance that + // the tracker missed some DatabseModified calls. This method is used + // when a renderer crashes to cleanup its open resources. + // We need to examine what we have in connections for the + // size of each open databases and notify any differences between the + // actual file sizes now. + std::vector<std::pair<std::string, base::string16> > open_dbs; + connections.ListConnections(&open_dbs); + for (std::vector<std::pair<std::string, base::string16> >::iterator it = + open_dbs.begin(); it != open_dbs.end(); ++it) + UpdateOpenDatabaseSizeAndNotify(it->first, it->second); + + std::vector<std::pair<std::string, base::string16> > closed_dbs; + database_connections_.RemoveConnections(connections, &closed_dbs); + for (std::vector<std::pair<std::string, base::string16> >::iterator it = + closed_dbs.begin(); it != closed_dbs.end(); ++it) { + DeleteDatabaseIfNeeded(it->first, it->second); + } +} + +void DatabaseTracker::DeleteDatabaseIfNeeded( + const std::string& origin_identifier, + const base::string16& database_name) { + DCHECK(!database_connections_.IsDatabaseOpened(origin_identifier, + database_name)); + if (IsDatabaseScheduledForDeletion(origin_identifier, database_name)) { + DeleteClosedDatabase(origin_identifier, database_name); + dbs_to_be_deleted_[origin_identifier].erase(database_name); + if (dbs_to_be_deleted_[origin_identifier].empty()) + dbs_to_be_deleted_.erase(origin_identifier); + + PendingDeletionCallbacks::iterator callback = deletion_callbacks_.begin(); + while (callback != deletion_callbacks_.end()) { + DatabaseSet::iterator found_origin = + callback->second.find(origin_identifier); + if (found_origin != callback->second.end()) { + std::set<base::string16>& databases = found_origin->second; + databases.erase(database_name); + if (databases.empty()) { + callback->second.erase(found_origin); + if (callback->second.empty()) { + net::CompletionCallback cb = callback->first; + cb.Run(net::OK); + callback = deletion_callbacks_.erase(callback); + continue; + } + } + } + + ++callback; + } + } +} + +void DatabaseTracker::AddObserver(Observer* observer) { + observers_.AddObserver(observer); +} + +void DatabaseTracker::RemoveObserver(Observer* observer) { + // When we remove a listener, we do not know which cached information + // is still needed and which information can be discarded. So we just + // clear all caches and re-populate them as needed. + observers_.RemoveObserver(observer); + ClearAllCachedOriginInfo(); +} + +void DatabaseTracker::CloseTrackerDatabaseAndClearCaches() { + ClearAllCachedOriginInfo(); + + if (!is_incognito_) { + meta_table_.reset(NULL); + databases_table_.reset(NULL); + db_->Close(); + is_initialized_ = false; + } +} + +base::string16 DatabaseTracker::GetOriginDirectory( + const std::string& origin_identifier) { + if (!is_incognito_) + return base::UTF8ToUTF16(origin_identifier); + + OriginDirectoriesMap::const_iterator it = + incognito_origin_directories_.find(origin_identifier); + if (it != incognito_origin_directories_.end()) + return it->second; + + base::string16 origin_directory = + base::IntToString16(incognito_origin_directories_generator_++); + incognito_origin_directories_[origin_identifier] = origin_directory; + return origin_directory; +} + +base::FilePath DatabaseTracker::GetFullDBFilePath( + const std::string& origin_identifier, + const base::string16& database_name) { + DCHECK(!origin_identifier.empty()); + if (!LazyInit()) + return base::FilePath(); + + int64 id = databases_table_->GetDatabaseID(origin_identifier, database_name); + if (id < 0) + return base::FilePath(); + + return db_dir_.Append(base::FilePath::FromUTF16Unsafe( + GetOriginDirectory(origin_identifier))).AppendASCII( + base::Int64ToString(id)); +} + +bool DatabaseTracker::GetOriginInfo(const std::string& origin_identifier, + OriginInfo* info) { + DCHECK(info); + CachedOriginInfo* cached_info = GetCachedOriginInfo(origin_identifier); + if (!cached_info) + return false; + *info = OriginInfo(*cached_info); + return true; +} + +bool DatabaseTracker::GetAllOriginIdentifiers( + std::vector<std::string>* origin_identifiers) { + DCHECK(origin_identifiers); + DCHECK(origin_identifiers->empty()); + if (!LazyInit()) + return false; + return databases_table_->GetAllOriginIdentifiers(origin_identifiers); +} + +bool DatabaseTracker::GetAllOriginsInfo( + std::vector<OriginInfo>* origins_info) { + DCHECK(origins_info); + DCHECK(origins_info->empty()); + + std::vector<std::string> origins; + if (!GetAllOriginIdentifiers(&origins)) + return false; + + for (std::vector<std::string>::const_iterator it = origins.begin(); + it != origins.end(); it++) { + CachedOriginInfo* origin_info = GetCachedOriginInfo(*it); + if (!origin_info) { + // Restore 'origins_info' to its initial state. + origins_info->clear(); + return false; + } + origins_info->push_back(OriginInfo(*origin_info)); + } + + return true; +} + +bool DatabaseTracker::DeleteClosedDatabase( + const std::string& origin_identifier, + const base::string16& database_name) { + if (!LazyInit()) + return false; + + // Check if the database is opened by any renderer. + if (database_connections_.IsDatabaseOpened(origin_identifier, database_name)) + return false; + + int64 db_file_size = quota_manager_proxy_.get() + ? GetDBFileSize(origin_identifier, database_name) + : 0; + + // Try to delete the file on the hard drive. + base::FilePath db_file = GetFullDBFilePath(origin_identifier, database_name); + if (!sql::Connection::Delete(db_file)) + return false; + + if (quota_manager_proxy_.get() && db_file_size) + quota_manager_proxy_->NotifyStorageModified( + storage::QuotaClient::kDatabase, + storage::GetOriginFromIdentifier(origin_identifier), + storage::kStorageTypeTemporary, + -db_file_size); + + // Clean up the main database and invalidate the cached record. + databases_table_->DeleteDatabaseDetails(origin_identifier, database_name); + origins_info_map_.erase(origin_identifier); + + std::vector<DatabaseDetails> details; + if (databases_table_->GetAllDatabaseDetailsForOriginIdentifier( + origin_identifier, &details) && details.empty()) { + // Try to delete the origin in case this was the last database. + DeleteOrigin(origin_identifier, false); + } + return true; +} + +bool DatabaseTracker::DeleteOrigin(const std::string& origin_identifier, + bool force) { + if (!LazyInit()) + return false; + + // Check if any database in this origin is opened by any renderer. + if (database_connections_.IsOriginUsed(origin_identifier) && !force) + return false; + + int64 deleted_size = 0; + if (quota_manager_proxy_.get()) { + CachedOriginInfo* origin_info = GetCachedOriginInfo(origin_identifier); + if (origin_info) + deleted_size = origin_info->TotalSize(); + } + + origins_info_map_.erase(origin_identifier); + base::FilePath origin_dir = db_dir_.AppendASCII(origin_identifier); + + // Create a temporary directory to move possibly still existing databases to, + // as we can't delete the origin directory on windows if it contains opened + // files. + base::FilePath new_origin_dir; + base::CreateTemporaryDirInDir(db_dir_, + kTemporaryDirectoryPrefix, + &new_origin_dir); + base::FileEnumerator databases( + origin_dir, + false, + base::FileEnumerator::FILES); + for (base::FilePath database = databases.Next(); !database.empty(); + database = databases.Next()) { + base::FilePath new_file = new_origin_dir.Append(database.BaseName()); + base::Move(database, new_file); + } + base::DeleteFile(origin_dir, true); + base::DeleteFile(new_origin_dir, true); // might fail on windows. + + databases_table_->DeleteOriginIdentifier(origin_identifier); + + if (quota_manager_proxy_.get() && deleted_size) { + quota_manager_proxy_->NotifyStorageModified( + storage::QuotaClient::kDatabase, + storage::GetOriginFromIdentifier(origin_identifier), + storage::kStorageTypeTemporary, + -deleted_size); + } + + return true; +} + +bool DatabaseTracker::IsDatabaseScheduledForDeletion( + const std::string& origin_identifier, + const base::string16& database_name) { + DatabaseSet::iterator it = dbs_to_be_deleted_.find(origin_identifier); + if (it == dbs_to_be_deleted_.end()) + return false; + + std::set<base::string16>& databases = it->second; + return (databases.find(database_name) != databases.end()); +} + +bool DatabaseTracker::LazyInit() { + if (!is_initialized_ && !shutting_down_) { + DCHECK(!db_->is_open()); + DCHECK(!databases_table_.get()); + DCHECK(!meta_table_.get()); + + // If there are left-over directories from failed deletion attempts, clean + // them up. + if (base::DirectoryExists(db_dir_)) { + base::FileEnumerator directories( + db_dir_, + false, + base::FileEnumerator::DIRECTORIES, + kTemporaryDirectoryPattern); + for (base::FilePath directory = directories.Next(); !directory.empty(); + directory = directories.Next()) { + base::DeleteFile(directory, true); + } + } + + // If the tracker database exists, but it's corrupt or doesn't + // have a meta table, delete the database directory. + const base::FilePath kTrackerDatabaseFullPath = + db_dir_.Append(base::FilePath(kTrackerDatabaseFileName)); + if (base::DirectoryExists(db_dir_) && + base::PathExists(kTrackerDatabaseFullPath) && + (!db_->Open(kTrackerDatabaseFullPath) || + !sql::MetaTable::DoesTableExist(db_.get()))) { + db_->Close(); + if (!base::DeleteFile(db_dir_, true)) + return false; + } + + db_->set_histogram_tag("DatabaseTracker"); + + databases_table_.reset(new DatabasesTable(db_.get())); + meta_table_.reset(new sql::MetaTable()); + + is_initialized_ = + base::CreateDirectory(db_dir_) && + (db_->is_open() || + (is_incognito_ ? db_->OpenInMemory() : + db_->Open(kTrackerDatabaseFullPath))) && + UpgradeToCurrentVersion(); + if (!is_initialized_) { + databases_table_.reset(NULL); + meta_table_.reset(NULL); + db_->Close(); + } + } + return is_initialized_; +} + +bool DatabaseTracker::UpgradeToCurrentVersion() { + sql::Transaction transaction(db_.get()); + if (!transaction.Begin() || + !meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion) || + (meta_table_->GetCompatibleVersionNumber() > kCurrentVersion) || + !databases_table_->Init()) + return false; + + if (meta_table_->GetVersionNumber() < kCurrentVersion) + meta_table_->SetVersionNumber(kCurrentVersion); + + return transaction.Commit(); +} + +void DatabaseTracker::InsertOrUpdateDatabaseDetails( + const std::string& origin_identifier, + const base::string16& database_name, + const base::string16& database_description, + int64 estimated_size) { + DatabaseDetails details; + if (!databases_table_->GetDatabaseDetails( + origin_identifier, database_name, &details)) { + details.origin_identifier = origin_identifier; + details.database_name = database_name; + details.description = database_description; + details.estimated_size = estimated_size; + databases_table_->InsertDatabaseDetails(details); + } else if ((details.description != database_description) || + (details.estimated_size != estimated_size)) { + details.description = database_description; + details.estimated_size = estimated_size; + databases_table_->UpdateDatabaseDetails(details); + } +} + +void DatabaseTracker::ClearAllCachedOriginInfo() { + origins_info_map_.clear(); +} + +DatabaseTracker::CachedOriginInfo* DatabaseTracker::MaybeGetCachedOriginInfo( + const std::string& origin_identifier, bool create_if_needed) { + if (!LazyInit()) + return NULL; + + // Populate the cache with data for this origin if needed. + if (origins_info_map_.find(origin_identifier) == origins_info_map_.end()) { + if (!create_if_needed) + return NULL; + + std::vector<DatabaseDetails> details; + if (!databases_table_->GetAllDatabaseDetailsForOriginIdentifier( + origin_identifier, &details)) { + return NULL; + } + + CachedOriginInfo& origin_info = origins_info_map_[origin_identifier]; + origin_info.SetOriginIdentifier(origin_identifier); + for (std::vector<DatabaseDetails>::const_iterator it = details.begin(); + it != details.end(); it++) { + int64 db_file_size; + if (database_connections_.IsDatabaseOpened( + origin_identifier, it->database_name)) { + db_file_size = database_connections_.GetOpenDatabaseSize( + origin_identifier, it->database_name); + } else { + db_file_size = GetDBFileSize(origin_identifier, it->database_name); + } + origin_info.SetDatabaseSize(it->database_name, db_file_size); + origin_info.SetDatabaseDescription(it->database_name, it->description); + } + } + + return &origins_info_map_[origin_identifier]; +} + +int64 DatabaseTracker::GetDBFileSize(const std::string& origin_identifier, + const base::string16& database_name) { + base::FilePath db_file_name = GetFullDBFilePath(origin_identifier, + database_name); + int64 db_file_size = 0; + if (!base::GetFileSize(db_file_name, &db_file_size)) + db_file_size = 0; + return db_file_size; +} + +int64 DatabaseTracker::SeedOpenDatabaseInfo( + const std::string& origin_id, const base::string16& name, + const base::string16& description) { + DCHECK(database_connections_.IsDatabaseOpened(origin_id, name)); + int64 size = GetDBFileSize(origin_id, name); + database_connections_.SetOpenDatabaseSize(origin_id, name, size); + CachedOriginInfo* info = MaybeGetCachedOriginInfo(origin_id, false); + if (info) { + info->SetDatabaseSize(name, size); + info->SetDatabaseDescription(name, description); + } + return size; +} + +int64 DatabaseTracker::UpdateOpenDatabaseInfoAndNotify( + const std::string& origin_id, const base::string16& name, + const base::string16* opt_description) { + DCHECK(database_connections_.IsDatabaseOpened(origin_id, name)); + int64 new_size = GetDBFileSize(origin_id, name); + int64 old_size = database_connections_.GetOpenDatabaseSize(origin_id, name); + CachedOriginInfo* info = MaybeGetCachedOriginInfo(origin_id, false); + if (info && opt_description) + info->SetDatabaseDescription(name, *opt_description); + if (old_size != new_size) { + database_connections_.SetOpenDatabaseSize(origin_id, name, new_size); + if (info) + info->SetDatabaseSize(name, new_size); + if (quota_manager_proxy_.get()) + quota_manager_proxy_->NotifyStorageModified( + storage::QuotaClient::kDatabase, + storage::GetOriginFromIdentifier(origin_id), + storage::kStorageTypeTemporary, + new_size - old_size); + FOR_EACH_OBSERVER(Observer, observers_, OnDatabaseSizeChanged( + origin_id, name, new_size)); + } + return new_size; +} + +void DatabaseTracker::ScheduleDatabaseForDeletion( + const std::string& origin_identifier, + const base::string16& database_name) { + DCHECK(database_connections_.IsDatabaseOpened(origin_identifier, + database_name)); + dbs_to_be_deleted_[origin_identifier].insert(database_name); + FOR_EACH_OBSERVER(Observer, observers_, OnDatabaseScheduledForDeletion( + origin_identifier, database_name)); +} + +void DatabaseTracker::ScheduleDatabasesForDeletion( + const DatabaseSet& databases, + const net::CompletionCallback& callback) { + DCHECK(!databases.empty()); + + if (!callback.is_null()) + deletion_callbacks_.push_back(std::make_pair(callback, databases)); + for (DatabaseSet::const_iterator ori = databases.begin(); + ori != databases.end(); ++ori) { + for (std::set<base::string16>::const_iterator db = ori->second.begin(); + db != ori->second.end(); ++db) + ScheduleDatabaseForDeletion(ori->first, *db); + } +} + +int DatabaseTracker::DeleteDatabase(const std::string& origin_identifier, + const base::string16& database_name, + const net::CompletionCallback& callback) { + if (!LazyInit()) + return net::ERR_FAILED; + + if (database_connections_.IsDatabaseOpened(origin_identifier, + database_name)) { + if (!callback.is_null()) { + DatabaseSet set; + set[origin_identifier].insert(database_name); + deletion_callbacks_.push_back(std::make_pair(callback, set)); + } + ScheduleDatabaseForDeletion(origin_identifier, database_name); + return net::ERR_IO_PENDING; + } + DeleteClosedDatabase(origin_identifier, database_name); + return net::OK; +} + +int DatabaseTracker::DeleteDataModifiedSince( + const base::Time& cutoff, + const net::CompletionCallback& callback) { + if (!LazyInit()) + return net::ERR_FAILED; + + DatabaseSet to_be_deleted; + + std::vector<std::string> origins_identifiers; + if (!databases_table_->GetAllOriginIdentifiers(&origins_identifiers)) + return net::ERR_FAILED; + int rv = net::OK; + for (std::vector<std::string>::const_iterator ori = + origins_identifiers.begin(); + ori != origins_identifiers.end(); ++ori) { + if (special_storage_policy_.get() && + special_storage_policy_->IsStorageProtected( + storage::GetOriginFromIdentifier(*ori))) { + continue; + } + + std::vector<DatabaseDetails> details; + if (!databases_table_-> + GetAllDatabaseDetailsForOriginIdentifier(*ori, &details)) + rv = net::ERR_FAILED; + for (std::vector<DatabaseDetails>::const_iterator db = details.begin(); + db != details.end(); ++db) { + base::FilePath db_file = GetFullDBFilePath(*ori, db->database_name); + base::File::Info file_info; + base::GetFileInfo(db_file, &file_info); + if (file_info.last_modified < cutoff) + continue; + + // Check if the database is opened by any renderer. + if (database_connections_.IsDatabaseOpened(*ori, db->database_name)) + to_be_deleted[*ori].insert(db->database_name); + else + DeleteClosedDatabase(*ori, db->database_name); + } + } + + if (rv != net::OK) + return rv; + + if (!to_be_deleted.empty()) { + ScheduleDatabasesForDeletion(to_be_deleted, callback); + return net::ERR_IO_PENDING; + } + return net::OK; +} + +int DatabaseTracker::DeleteDataForOrigin( + const std::string& origin, const net::CompletionCallback& callback) { + if (!LazyInit()) + return net::ERR_FAILED; + + DatabaseSet to_be_deleted; + + std::vector<DatabaseDetails> details; + if (!databases_table_-> + GetAllDatabaseDetailsForOriginIdentifier(origin, &details)) + return net::ERR_FAILED; + for (std::vector<DatabaseDetails>::const_iterator db = details.begin(); + db != details.end(); ++db) { + // Check if the database is opened by any renderer. + if (database_connections_.IsDatabaseOpened(origin, db->database_name)) + to_be_deleted[origin].insert(db->database_name); + else + DeleteClosedDatabase(origin, db->database_name); + } + + if (!to_be_deleted.empty()) { + ScheduleDatabasesForDeletion(to_be_deleted, callback); + return net::ERR_IO_PENDING; + } + return net::OK; +} + +const base::File* DatabaseTracker::GetIncognitoFile( + const base::string16& vfs_file_name) const { + DCHECK(is_incognito_); + FileHandlesMap::const_iterator it = + incognito_file_handles_.find(vfs_file_name); + if (it != incognito_file_handles_.end()) + return it->second; + + return NULL; +} + +const base::File* DatabaseTracker::SaveIncognitoFile( + const base::string16& vfs_file_name, + base::File file) { + DCHECK(is_incognito_); + if (!file.IsValid()) + return NULL; + + base::File* to_insert = new base::File(file.Pass()); + std::pair<FileHandlesMap::iterator, bool> rv = + incognito_file_handles_.insert(std::make_pair(vfs_file_name, to_insert)); + DCHECK(rv.second); + return rv.first->second; +} + +void DatabaseTracker::CloseIncognitoFileHandle( + const base::string16& vfs_file_name) { + DCHECK(is_incognito_); + DCHECK(incognito_file_handles_.find(vfs_file_name) != + incognito_file_handles_.end()); + + FileHandlesMap::iterator it = incognito_file_handles_.find(vfs_file_name); + if (it != incognito_file_handles_.end()) { + delete it->second; + incognito_file_handles_.erase(it); + } +} + +bool DatabaseTracker::HasSavedIncognitoFileHandle( + const base::string16& vfs_file_name) const { + return (incognito_file_handles_.find(vfs_file_name) != + incognito_file_handles_.end()); +} + +void DatabaseTracker::DeleteIncognitoDBDirectory() { + is_initialized_ = false; + + for (FileHandlesMap::iterator it = incognito_file_handles_.begin(); + it != incognito_file_handles_.end(); it++) { + delete it->second; + } + + base::FilePath incognito_db_dir = + profile_path_.Append(kIncognitoDatabaseDirectoryName); + if (base::DirectoryExists(incognito_db_dir)) + base::DeleteFile(incognito_db_dir, true); +} + +void DatabaseTracker::ClearSessionOnlyOrigins() { + bool has_session_only_databases = + special_storage_policy_.get() && + special_storage_policy_->HasSessionOnlyOrigins(); + + // Clearing only session-only databases, and there are none. + if (!has_session_only_databases) + return; + + if (!LazyInit()) + return; + + std::vector<std::string> origin_identifiers; + GetAllOriginIdentifiers(&origin_identifiers); + + for (std::vector<std::string>::iterator origin = + origin_identifiers.begin(); + origin != origin_identifiers.end(); ++origin) { + GURL origin_url = storage::GetOriginFromIdentifier(*origin); + if (!special_storage_policy_->IsStorageSessionOnly(origin_url)) + continue; + if (special_storage_policy_->IsStorageProtected(origin_url)) + continue; + storage::OriginInfo origin_info; + std::vector<base::string16> databases; + GetOriginInfo(*origin, &origin_info); + origin_info.GetAllDatabaseNames(&databases); + + for (std::vector<base::string16>::iterator database = databases.begin(); + database != databases.end(); ++database) { + base::File file(GetFullDBFilePath(*origin, *database), + base::File::FLAG_OPEN_ALWAYS | + base::File::FLAG_SHARE_DELETE | + base::File::FLAG_DELETE_ON_CLOSE | + base::File::FLAG_READ); + } + DeleteOrigin(*origin, true); + } +} + + +void DatabaseTracker::Shutdown() { + DCHECK(db_tracker_thread_.get()); + DCHECK(db_tracker_thread_->BelongsToCurrentThread()); + if (shutting_down_) { + NOTREACHED(); + return; + } + shutting_down_ = true; + if (is_incognito_) + DeleteIncognitoDBDirectory(); + else if (!force_keep_session_state_) + ClearSessionOnlyOrigins(); + CloseTrackerDatabaseAndClearCaches(); +} + +void DatabaseTracker::SetForceKeepSessionState() { + DCHECK(db_tracker_thread_.get()); + if (!db_tracker_thread_->BelongsToCurrentThread()) { + db_tracker_thread_->PostTask( + FROM_HERE, + base::Bind(&DatabaseTracker::SetForceKeepSessionState, this)); + return; + } + force_keep_session_state_ = true; +} + +} // namespace storage diff --git a/storage/browser/database/database_tracker.h b/storage/browser/database/database_tracker.h new file mode 100644 index 0000000..f998b4b --- /dev/null +++ b/storage/browser/database/database_tracker.h @@ -0,0 +1,319 @@ +// 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. + +#ifndef STORAGE_BROWSER_DATABASE_DATABASE_TRACKER_H_ +#define STORAGE_BROWSER_DATABASE_DATABASE_TRACKER_H_ + +#include <map> +#include <set> +#include <utility> + +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/gtest_prod_util.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/observer_list.h" +#include "base/strings/string16.h" +#include "base/strings/string_util.h" +#include "base/time/time.h" +#include "net/base/completion_callback.h" +#include "storage/browser/storage_browser_export.h" +#include "storage/common/database/database_connections.h" + +namespace base { +class MessageLoopProxy; +} + +namespace content { +class DatabaseTracker_TestHelper_Test; +class MockDatabaseTracker; +} + +namespace sql { +class Connection; +class MetaTable; +} + +namespace storage { +class QuotaManagerProxy; +class SpecialStoragePolicy; +} + +namespace storage { + +STORAGE_EXPORT extern const base::FilePath::CharType + kDatabaseDirectoryName[]; +STORAGE_EXPORT extern const base::FilePath::CharType + kTrackerDatabaseFileName[]; + +class DatabasesTable; + +// This class is used to store information about all databases in an origin. +class STORAGE_EXPORT OriginInfo { + public: + OriginInfo(); + OriginInfo(const OriginInfo& origin_info); + ~OriginInfo(); + + const std::string& GetOriginIdentifier() const { return origin_identifier_; } + int64 TotalSize() const { return total_size_; } + void GetAllDatabaseNames(std::vector<base::string16>* databases) const; + int64 GetDatabaseSize(const base::string16& database_name) const; + base::string16 GetDatabaseDescription( + const base::string16& database_name) const; + + protected: + typedef std::map<base::string16, std::pair<int64, base::string16> > + DatabaseInfoMap; + + OriginInfo(const std::string& origin_identifier, int64 total_size); + + std::string origin_identifier_; + int64 total_size_; + DatabaseInfoMap database_info_; +}; + +// This class manages the main database and keeps track of open databases. +// +// The data in this class is not thread-safe, so all methods of this class +// should be called on the same thread. The only exceptions are the ctor(), +// the dtor() and the database_directory() and quota_manager_proxy() getters. +// +// Furthermore, some methods of this class have to read/write data from/to +// the disk. Therefore, in a multi-threaded application, all methods of this +// class should be called on the thread dedicated to file operations (file +// thread in the browser process, for example), if such a thread exists. +class STORAGE_EXPORT DatabaseTracker + : public base::RefCountedThreadSafe<DatabaseTracker> { + public: + class Observer { + public: + virtual void OnDatabaseSizeChanged(const std::string& origin_identifier, + const base::string16& database_name, + int64 database_size) = 0; + virtual void OnDatabaseScheduledForDeletion( + const std::string& origin_identifier, + const base::string16& database_name) = 0; + + protected: + virtual ~Observer() {} + }; + + DatabaseTracker(const base::FilePath& profile_path, + bool is_incognito, + storage::SpecialStoragePolicy* special_storage_policy, + storage::QuotaManagerProxy* quota_manager_proxy, + base::MessageLoopProxy* db_tracker_thread); + + void DatabaseOpened(const std::string& origin_identifier, + const base::string16& database_name, + const base::string16& database_details, + int64 estimated_size, + int64* database_size); + void DatabaseModified(const std::string& origin_identifier, + const base::string16& database_name); + void DatabaseClosed(const std::string& origin_identifier, + const base::string16& database_name); + void HandleSqliteError(const std::string& origin_identifier, + const base::string16& database_name, + int error); + + void CloseDatabases(const DatabaseConnections& connections); + + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + + void CloseTrackerDatabaseAndClearCaches(); + + const base::FilePath& DatabaseDirectory() const { return db_dir_; } + base::FilePath GetFullDBFilePath(const std::string& origin_identifier, + const base::string16& database_name); + + // virtual for unit-testing only + virtual bool GetOriginInfo(const std::string& origin_id, OriginInfo* info); + virtual bool GetAllOriginIdentifiers(std::vector<std::string>* origin_ids); + virtual bool GetAllOriginsInfo(std::vector<OriginInfo>* origins_info); + + // Safe to call on any thread. + storage::QuotaManagerProxy* quota_manager_proxy() const { + return quota_manager_proxy_.get(); + } + + bool IsDatabaseScheduledForDeletion(const std::string& origin_identifier, + const base::string16& database_name); + + // Deletes a single database. Returns net::OK on success, net::FAILED on + // failure, or net::ERR_IO_PENDING and |callback| is invoked upon completion, + // if non-NULL. + int DeleteDatabase(const std::string& origin_identifier, + const base::string16& database_name, + const net::CompletionCallback& callback); + + // Delete any databases that have been touched since the cutoff date that's + // supplied, omitting any that match IDs within |protected_origins|. + // Returns net::OK on success, net::FAILED if not all databases could be + // deleted, and net::ERR_IO_PENDING and |callback| is invoked upon completion, + // if non-NULL. Protected origins, according the the SpecialStoragePolicy, + // are not deleted by this method. + int DeleteDataModifiedSince(const base::Time& cutoff, + const net::CompletionCallback& callback); + + // Delete all databases that belong to the given origin. Returns net::OK on + // success, net::FAILED if not all databases could be deleted, and + // net::ERR_IO_PENDING and |callback| is invoked upon completion, if non-NULL. + // virtual for unit testing only + virtual int DeleteDataForOrigin(const std::string& origin_identifier, + const net::CompletionCallback& callback); + + bool IsIncognitoProfile() const { return is_incognito_; } + + const base::File* GetIncognitoFile(const base::string16& vfs_file_path) const; + const base::File* SaveIncognitoFile(const base::string16& vfs_file_path, + base::File file); + void CloseIncognitoFileHandle(const base::string16& vfs_file_path); + bool HasSavedIncognitoFileHandle(const base::string16& vfs_file_path) const; + + // Shutdown the database tracker, deleting database files if the tracker is + // used for an incognito profile. + void Shutdown(); + // Disables the exit-time deletion of session-only data. + void SetForceKeepSessionState(); + + private: + friend class base::RefCountedThreadSafe<DatabaseTracker>; + friend class content::DatabaseTracker_TestHelper_Test; + friend class content::MockDatabaseTracker; // for testing + + typedef std::map<std::string, std::set<base::string16> > DatabaseSet; + typedef std::vector<std::pair<net::CompletionCallback, DatabaseSet> > + PendingDeletionCallbacks; + typedef std::map<base::string16, base::File*> FileHandlesMap; + typedef std::map<std::string, base::string16> OriginDirectoriesMap; + + class CachedOriginInfo : public OriginInfo { + public: + CachedOriginInfo() : OriginInfo(std::string(), 0) {} + void SetOriginIdentifier(const std::string& origin_identifier) { + origin_identifier_ = origin_identifier; + } + void SetDatabaseSize(const base::string16& database_name, int64 new_size) { + int64 old_size = 0; + if (database_info_.find(database_name) != database_info_.end()) + old_size = database_info_[database_name].first; + database_info_[database_name].first = new_size; + if (new_size != old_size) + total_size_ += new_size - old_size; + } + void SetDatabaseDescription(const base::string16& database_name, + const base::string16& description) { + database_info_[database_name].second = description; + } + }; + + // virtual for unit-testing only. + virtual ~DatabaseTracker(); + + // Deletes the directory that stores all DBs in incognito mode, if it exists. + void DeleteIncognitoDBDirectory(); + + // Deletes session-only databases. Blocks databases from being created/opened. + void ClearSessionOnlyOrigins(); + + bool DeleteClosedDatabase(const std::string& origin_identifier, + const base::string16& database_name); + + // Delete all files belonging to the given origin given that no database + // connections within this origin are open, or if |force| is true, delete + // the meta data and rename the associated directory. + bool DeleteOrigin(const std::string& origin_identifier, bool force); + void DeleteDatabaseIfNeeded(const std::string& origin_identifier, + const base::string16& database_name); + + bool LazyInit(); + bool UpgradeToCurrentVersion(); + void InsertOrUpdateDatabaseDetails(const std::string& origin_identifier, + const base::string16& database_name, + const base::string16& database_details, + int64 estimated_size); + + void ClearAllCachedOriginInfo(); + CachedOriginInfo* MaybeGetCachedOriginInfo( + const std::string& origin_identifier, + bool create_if_needed); + CachedOriginInfo* GetCachedOriginInfo( + const std::string& origin_identifier) { + return MaybeGetCachedOriginInfo(origin_identifier, true); + } + + int64 GetDBFileSize(const std::string& origin_identifier, + const base::string16& database_name); + int64 SeedOpenDatabaseInfo(const std::string& origin_identifier, + const base::string16& database_name, + const base::string16& description); + int64 UpdateOpenDatabaseInfoAndNotify(const std::string& origin_identifier, + const base::string16& database_name, + const base::string16* opt_description); + int64 UpdateOpenDatabaseSizeAndNotify(const std::string& origin_identifier, + const base::string16& database_name) { + return UpdateOpenDatabaseInfoAndNotify( + origin_identifier, database_name, NULL); + } + + + void ScheduleDatabaseForDeletion(const std::string& origin_identifier, + const base::string16& database_name); + // Schedule a set of open databases for deletion. If non-null, callback is + // invoked upon completion. + void ScheduleDatabasesForDeletion(const DatabaseSet& databases, + const net::CompletionCallback& callback); + + // Returns the directory where all DB files for the given origin are stored. + base::string16 GetOriginDirectory(const std::string& origin_identifier); + + bool is_initialized_; + const bool is_incognito_; + bool force_keep_session_state_; + bool shutting_down_; + const base::FilePath profile_path_; + const base::FilePath db_dir_; + scoped_ptr<sql::Connection> db_; + scoped_ptr<DatabasesTable> databases_table_; + scoped_ptr<sql::MetaTable> meta_table_; + ObserverList<Observer, true> observers_; + std::map<std::string, CachedOriginInfo> origins_info_map_; + DatabaseConnections database_connections_; + + // The set of databases that should be deleted but are still opened + DatabaseSet dbs_to_be_deleted_; + PendingDeletionCallbacks deletion_callbacks_; + + // Apps and Extensions can have special rights. + scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy_; + + scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy_; + + // The database tracker thread we're supposed to run file IO on. + scoped_refptr<base::MessageLoopProxy> db_tracker_thread_; + + // When in incognito mode, store a DELETE_ON_CLOSE handle to each + // main DB and journal file that was accessed. When the incognito profile + // goes away (or when the browser crashes), all these handles will be + // closed, and the files will be deleted. + FileHandlesMap incognito_file_handles_; + + // In a non-incognito profile, all DBs in an origin are stored in a directory + // named after the origin. In an incognito profile though, we do not want the + // directory structure to reveal the origins visited by the user (in case the + // browser process crashes and those directories are not deleted). So we use + // this map to assign directory names that do not reveal this information. + OriginDirectoriesMap incognito_origin_directories_; + int incognito_origin_directories_generator_; + + FRIEND_TEST_ALL_PREFIXES(DatabaseTracker, TestHelper); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_DATABASE_DATABASE_TRACKER_H_ diff --git a/storage/browser/database/database_util.cc b/storage/browser/database/database_util.cc new file mode 100644 index 0000000..113f533 --- /dev/null +++ b/storage/browser/database/database_util.cc @@ -0,0 +1,108 @@ +// 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 "storage/browser/database/database_util.h" + +#include "base/basictypes.h" +#include "base/strings/utf_string_conversions.h" +#include "storage/browser/database/database_tracker.h" +#include "storage/browser/database/vfs_backend.h" +#include "storage/common/database/database_identifier.h" + +namespace storage { + +namespace { + +bool IsSafeSuffix(const base::string16& suffix) { + base::char16 prev_c = 0; + for (base::string16::const_iterator it = suffix.begin(); + it < suffix.end(); ++it) { + base::char16 c = *it; + if (!(IsAsciiAlpha(c) || IsAsciiDigit(c) || + c == '-' || c == '.' || c == '_')) { + return false; + } + if (c == '.' && prev_c == '.') + return false; + prev_c = c; + } + return true; +} + +} + +const char DatabaseUtil::kJournalFileSuffix[] = "-journal"; + +bool DatabaseUtil::CrackVfsFileName(const base::string16& vfs_file_name, + std::string* origin_identifier, + base::string16* database_name, + base::string16* sqlite_suffix) { + // 'vfs_file_name' is of the form <origin_identifier>/<db_name>#<suffix>. + // <suffix> is optional. + DCHECK(!vfs_file_name.empty()); + size_t first_slash_index = vfs_file_name.find('/'); + size_t last_pound_index = vfs_file_name.rfind('#'); + // '/' and '#' must be present in the string. Also, the string cannot start + // with a '/' (origin_identifier cannot be empty) and '/' must come before '#' + if ((first_slash_index == base::string16::npos) || + (last_pound_index == base::string16::npos) || + (first_slash_index == 0) || + (first_slash_index > last_pound_index)) { + return false; + } + + std::string origin_id = base::UTF16ToASCII( + vfs_file_name.substr(0, first_slash_index)); + if (!IsValidOriginIdentifier(origin_id)) + return false; + + base::string16 suffix = vfs_file_name.substr( + last_pound_index + 1, vfs_file_name.length() - last_pound_index - 1); + if (!IsSafeSuffix(suffix)) + return false; + + if (origin_identifier) + *origin_identifier = origin_id; + + if (database_name) { + *database_name = vfs_file_name.substr( + first_slash_index + 1, last_pound_index - first_slash_index - 1); + } + + if (sqlite_suffix) + *sqlite_suffix = suffix; + + return true; +} + +base::FilePath DatabaseUtil::GetFullFilePathForVfsFile( + DatabaseTracker* db_tracker, const base::string16& vfs_file_name) { + std::string origin_identifier; + base::string16 database_name; + base::string16 sqlite_suffix; + if (!CrackVfsFileName(vfs_file_name, &origin_identifier, + &database_name, &sqlite_suffix)) { + return base::FilePath(); // invalid vfs_file_name + } + + base::FilePath full_path = db_tracker->GetFullDBFilePath( + origin_identifier, database_name); + if (!full_path.empty() && !sqlite_suffix.empty()) { + DCHECK(full_path.Extension().empty()); + full_path = full_path.InsertBeforeExtensionASCII( + base::UTF16ToASCII(sqlite_suffix)); + } + // Watch out for directory traversal attempts from a compromised renderer. + if (full_path.value().find(FILE_PATH_LITERAL("..")) != + base::FilePath::StringType::npos) + return base::FilePath(); + return full_path; +} + +bool DatabaseUtil::IsValidOriginIdentifier( + const std::string& origin_identifier) { + return GetOriginFromIdentifier(origin_identifier).is_valid(); +} + +} // namespace storage diff --git a/storage/browser/database/database_util.h b/storage/browser/database/database_util.h new file mode 100644 index 0000000..6c67fac --- /dev/null +++ b/storage/browser/database/database_util.h @@ -0,0 +1,39 @@ +// 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. + +#ifndef STORAGE_BROWSER_DATABASE_DATABASE_UTIL_H_ +#define STORAGE_BROWSER_DATABASE_DATABASE_UTIL_H_ + +#include <string> +#include "base/strings/string16.h" +#include "storage/browser/storage_browser_export.h" +#include "url/gurl.h" + +namespace base { +class FilePath; +} + +namespace storage { + +class DatabaseTracker; + +class STORAGE_EXPORT DatabaseUtil { + public: + static const char kJournalFileSuffix[]; + + // Extract various information from a database vfs_file_name. All return + // parameters are optional. + static bool CrackVfsFileName(const base::string16& vfs_file_name, + std::string* origin_identifier, + base::string16* database_name, + base::string16* sqlite_suffix); + static base::FilePath GetFullFilePathForVfsFile( + DatabaseTracker* db_tracker, + const base::string16& vfs_file_name); + static bool IsValidOriginIdentifier(const std::string& origin_identifier); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_DATABASE_DATABASE_UTIL_H_ diff --git a/storage/browser/database/databases_table.cc b/storage/browser/database/databases_table.cc new file mode 100644 index 0000000..6d84ae8 --- /dev/null +++ b/storage/browser/database/databases_table.cc @@ -0,0 +1,150 @@ +// 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 "storage/browser/database/databases_table.h" + +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "sql/statement.h" + +namespace storage { + +DatabaseDetails::DatabaseDetails() : estimated_size(0) { } + +DatabaseDetails::~DatabaseDetails() {} + +bool DatabasesTable::Init() { + // 'Databases' schema: + // id A unique ID assigned to each database + // origin The originto which the database belongs. This is a + // string that can be used as part of a file name + // (http_webkit.org_0, for example). + // name The database name. + // description A short description of the database. + // estimated_size The estimated size of the database. + return db_->DoesTableExist("Databases") || + (db_->Execute( + "CREATE TABLE Databases (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "origin TEXT NOT NULL, " + "name TEXT NOT NULL, " + "description TEXT NOT NULL, " + "estimated_size INTEGER NOT NULL)") && + db_->Execute( + "CREATE INDEX origin_index ON Databases (origin)") && + db_->Execute( + "CREATE UNIQUE INDEX unique_index ON Databases (origin, name)")); +} + +int64 DatabasesTable::GetDatabaseID(const std::string& origin_identifier, + const base::string16& database_name) { + sql::Statement select_statement(db_->GetCachedStatement( + SQL_FROM_HERE, "SELECT id FROM Databases WHERE origin = ? AND name = ?")); + select_statement.BindString(0, origin_identifier); + select_statement.BindString16(1, database_name); + + if (select_statement.Step()) { + return select_statement.ColumnInt64(0); + } + + return -1; +} + +bool DatabasesTable::GetDatabaseDetails(const std::string& origin_identifier, + const base::string16& database_name, + DatabaseDetails* details) { + DCHECK(details); + sql::Statement select_statement(db_->GetCachedStatement( + SQL_FROM_HERE, "SELECT description, estimated_size FROM Databases " + "WHERE origin = ? AND name = ?")); + select_statement.BindString(0, origin_identifier); + select_statement.BindString16(1, database_name); + + if (select_statement.Step()) { + details->origin_identifier = origin_identifier; + details->database_name = database_name; + details->description = select_statement.ColumnString16(0); + details->estimated_size = select_statement.ColumnInt64(1); + return true; + } + + return false; +} + +bool DatabasesTable::InsertDatabaseDetails(const DatabaseDetails& details) { + sql::Statement insert_statement(db_->GetCachedStatement( + SQL_FROM_HERE, "INSERT INTO Databases (origin, name, description, " + "estimated_size) VALUES (?, ?, ?, ?)")); + insert_statement.BindString(0, details.origin_identifier); + insert_statement.BindString16(1, details.database_name); + insert_statement.BindString16(2, details.description); + insert_statement.BindInt64(3, details.estimated_size); + + return insert_statement.Run(); +} + +bool DatabasesTable::UpdateDatabaseDetails(const DatabaseDetails& details) { + sql::Statement update_statement(db_->GetCachedStatement( + SQL_FROM_HERE, "UPDATE Databases SET description = ?, " + "estimated_size = ? WHERE origin = ? AND name = ?")); + update_statement.BindString16(0, details.description); + update_statement.BindInt64(1, details.estimated_size); + update_statement.BindString(2, details.origin_identifier); + update_statement.BindString16(3, details.database_name); + + return (update_statement.Run() && db_->GetLastChangeCount()); +} + +bool DatabasesTable::DeleteDatabaseDetails( + const std::string& origin_identifier, + const base::string16& database_name) { + sql::Statement delete_statement(db_->GetCachedStatement( + SQL_FROM_HERE, "DELETE FROM Databases WHERE origin = ? AND name = ?")); + delete_statement.BindString(0, origin_identifier); + delete_statement.BindString16(1, database_name); + + return (delete_statement.Run() && db_->GetLastChangeCount()); +} + +bool DatabasesTable::GetAllOriginIdentifiers( + std::vector<std::string>* origin_identifiers) { + sql::Statement statement(db_->GetCachedStatement( + SQL_FROM_HERE, "SELECT DISTINCT origin FROM Databases ORDER BY origin")); + + while (statement.Step()) + origin_identifiers->push_back(statement.ColumnString(0)); + + return statement.Succeeded(); +} + +bool DatabasesTable::GetAllDatabaseDetailsForOriginIdentifier( + const std::string& origin_identifier, + std::vector<DatabaseDetails>* details_vector) { + sql::Statement statement(db_->GetCachedStatement( + SQL_FROM_HERE, "SELECT name, description, estimated_size " + "FROM Databases WHERE origin = ? ORDER BY name")); + statement.BindString(0, origin_identifier); + + while (statement.Step()) { + DatabaseDetails details; + details.origin_identifier = origin_identifier; + details.database_name = statement.ColumnString16(0); + details.description = statement.ColumnString16(1); + details.estimated_size = statement.ColumnInt64(2); + details_vector->push_back(details); + } + + return statement.Succeeded(); +} + +bool DatabasesTable::DeleteOriginIdentifier( + const std::string& origin_identifier) { + sql::Statement delete_statement(db_->GetCachedStatement( + SQL_FROM_HERE, "DELETE FROM Databases WHERE origin = ?")); + delete_statement.BindString(0, origin_identifier); + + return (delete_statement.Run() && db_->GetLastChangeCount()); +} + +} // namespace storage diff --git a/storage/browser/database/databases_table.h b/storage/browser/database/databases_table.h new file mode 100644 index 0000000..2ade812 --- /dev/null +++ b/storage/browser/database/databases_table.h @@ -0,0 +1,54 @@ +// Copyright (c) 2009 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. + +#ifndef STORAGE_BROWSER_DATABASE_DATABASES_TABLE_H_ +#define STORAGE_BROWSER_DATABASE_DATABASES_TABLE_H_ + +#include <vector> + +#include "base/strings/string16.h" +#include "storage/browser/storage_browser_export.h" + +namespace sql { +class Connection; +} + +namespace storage { + +struct STORAGE_EXPORT_PRIVATE DatabaseDetails { + DatabaseDetails(); + ~DatabaseDetails(); + + std::string origin_identifier; + base::string16 database_name; + base::string16 description; + int64 estimated_size; +}; + +class STORAGE_EXPORT_PRIVATE DatabasesTable { + public: + explicit DatabasesTable(sql::Connection* db) : db_(db) { } + + bool Init(); + int64 GetDatabaseID(const std::string& origin_identifier, + const base::string16& database_name); + bool GetDatabaseDetails(const std::string& origin_identifier, + const base::string16& database_name, + DatabaseDetails* details); + bool InsertDatabaseDetails(const DatabaseDetails& details); + bool UpdateDatabaseDetails(const DatabaseDetails& details); + bool DeleteDatabaseDetails(const std::string& origin_identifier, + const base::string16& database_name); + bool GetAllOriginIdentifiers(std::vector<std::string>* origin_identifiers); + bool GetAllDatabaseDetailsForOriginIdentifier( + const std::string& origin_identifier, + std::vector<DatabaseDetails>* details); + bool DeleteOriginIdentifier(const std::string& origin_identifier); + private: + sql::Connection* db_; +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_DATABASE_DATABASES_TABLE_H_ diff --git a/storage/browser/database/vfs_backend.cc b/storage/browser/database/vfs_backend.cc new file mode 100644 index 0000000..1f8c97b --- /dev/null +++ b/storage/browser/database/vfs_backend.cc @@ -0,0 +1,158 @@ +// Copyright (c) 2011 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 "storage/browser/database/vfs_backend.h" + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "third_party/sqlite/sqlite3.h" + +namespace storage { + +static const int kFileTypeMask = 0x00007F00; + +// static +bool VfsBackend::OpenTypeIsReadWrite(int desired_flags) { + return (desired_flags & SQLITE_OPEN_READWRITE) != 0; +} + +// static +bool VfsBackend::OpenFileFlagsAreConsistent(int desired_flags) { + const int file_type = desired_flags & kFileTypeMask; + const bool is_exclusive = (desired_flags & SQLITE_OPEN_EXCLUSIVE) != 0; + const bool is_delete = (desired_flags & SQLITE_OPEN_DELETEONCLOSE) != 0; + const bool is_create = (desired_flags & SQLITE_OPEN_CREATE) != 0; + const bool is_read_only = (desired_flags & SQLITE_OPEN_READONLY) != 0; + const bool is_read_write = (desired_flags & SQLITE_OPEN_READWRITE) != 0; + + // All files should be opened either read-write or read-only, but not both. + if (is_read_only == is_read_write) + return false; + + // If a new file is created, it must also be writable. + if (is_create && !is_read_write) + return false; + + // If we're accessing an existing file, we cannot give exclusive access, and + // we can't delete it. + // Normally, we'd also check that 'is_delete' is false for a main DB, main + // journal or master journal file; however, when in incognito mode, we use + // the SQLITE_OPEN_DELETEONCLOSE flag when opening those files too and keep + // an open handle to them for as long as the incognito profile is around. + if ((is_exclusive || is_delete) && !is_create) + return false; + + // Make sure we're opening the DB directory or that a file type is set. + return (file_type == SQLITE_OPEN_MAIN_DB) || + (file_type == SQLITE_OPEN_TEMP_DB) || + (file_type == SQLITE_OPEN_MAIN_JOURNAL) || + (file_type == SQLITE_OPEN_TEMP_JOURNAL) || + (file_type == SQLITE_OPEN_SUBJOURNAL) || + (file_type == SQLITE_OPEN_MASTER_JOURNAL) || + (file_type == SQLITE_OPEN_TRANSIENT_DB); +} + +// static +base::File VfsBackend::OpenFile(const base::FilePath& file_path, + int desired_flags) { + DCHECK(!file_path.empty()); + + // Verify the flags for consistency and create the database + // directory if it doesn't exist. + if (!OpenFileFlagsAreConsistent(desired_flags) || + !base::CreateDirectory(file_path.DirName())) { + return base::File(); + } + + int flags = 0; + flags |= base::File::FLAG_READ; + if (desired_flags & SQLITE_OPEN_READWRITE) + flags |= base::File::FLAG_WRITE; + + if (!(desired_flags & SQLITE_OPEN_MAIN_DB)) + flags |= base::File::FLAG_EXCLUSIVE_READ | base::File::FLAG_EXCLUSIVE_WRITE; + + flags |= ((desired_flags & SQLITE_OPEN_CREATE) ? + base::File::FLAG_OPEN_ALWAYS : base::File::FLAG_OPEN); + + if (desired_flags & SQLITE_OPEN_EXCLUSIVE) + flags |= base::File::FLAG_EXCLUSIVE_READ | base::File::FLAG_EXCLUSIVE_WRITE; + + if (desired_flags & SQLITE_OPEN_DELETEONCLOSE) { + flags |= base::File::FLAG_TEMPORARY | base::File::FLAG_HIDDEN | + base::File::FLAG_DELETE_ON_CLOSE; + } + + // This flag will allow us to delete the file later on from the browser + // process. + flags |= base::File::FLAG_SHARE_DELETE; + + // Try to open/create the DB file. + return base::File(file_path, flags); +} + +// static +base::File VfsBackend::OpenTempFileInDirectory(const base::FilePath& dir_path, + int desired_flags) { + // We should be able to delete temp files when they're closed + // and create them as needed + if (!(desired_flags & SQLITE_OPEN_DELETEONCLOSE) || + !(desired_flags & SQLITE_OPEN_CREATE)) { + return base::File(); + } + + // Get a unique temp file name in the database directory. + base::FilePath temp_file_path; + if (!base::CreateTemporaryFileInDir(dir_path, &temp_file_path)) + return base::File(); + + return OpenFile(temp_file_path, desired_flags); +} + +// static +int VfsBackend::DeleteFile(const base::FilePath& file_path, bool sync_dir) { + if (!base::PathExists(file_path)) + return SQLITE_OK; + if (!base::DeleteFile(file_path, false)) + return SQLITE_IOERR_DELETE; + + int error_code = SQLITE_OK; +#if defined(OS_POSIX) + if (sync_dir) { + base::File dir(file_path.DirName(), base::File::FLAG_READ); + if (dir.IsValid()) { + if (!dir.Flush()) + error_code = SQLITE_IOERR_DIR_FSYNC; + } else { + error_code = SQLITE_CANTOPEN; + } + } +#endif + return error_code; +} + +// static +uint32 VfsBackend::GetFileAttributes(const base::FilePath& file_path) { +#if defined(OS_WIN) + uint32 attributes = ::GetFileAttributes(file_path.value().c_str()); +#elif defined(OS_POSIX) + uint32 attributes = 0; + if (!access(file_path.value().c_str(), R_OK)) + attributes |= static_cast<uint32>(R_OK); + if (!access(file_path.value().c_str(), W_OK)) + attributes |= static_cast<uint32>(W_OK); + if (!attributes) + attributes = -1; +#endif + return attributes; +} + +// static +int64 VfsBackend::GetFileSize(const base::FilePath& file_path) { + int64 size = 0; + return (base::GetFileSize(file_path, &size) ? size : 0); +} + +} // namespace storage diff --git a/storage/browser/database/vfs_backend.h b/storage/browser/database/vfs_backend.h new file mode 100644 index 0000000..0ee1dd3 --- /dev/null +++ b/storage/browser/database/vfs_backend.h @@ -0,0 +1,42 @@ +// Copyright (c) 2011 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. + +#ifndef STORAGE_BROWSER_DATABASE_VFS_BACKEND_H_ +#define STORAGE_BROWSER_DATABASE_VFS_BACKEND_H_ + +#include "base/files/file.h" +#include "base/process/process.h" +#include "base/strings/string16.h" +#include "storage/browser/storage_browser_export.h" + +namespace base { +class FilePath; +} + +namespace storage { + +class STORAGE_EXPORT VfsBackend { + public: + static base::File OpenFile(const base::FilePath& file_path, + int desired_flags); + + static base::File OpenTempFileInDirectory(const base::FilePath& dir_path, + int desired_flags); + + static int DeleteFile(const base::FilePath& file_path, bool sync_dir); + + static uint32 GetFileAttributes(const base::FilePath& file_path); + + static int64 GetFileSize(const base::FilePath& file_path); + + // Used to make decisions in the DatabaseDispatcherHost. + static bool OpenTypeIsReadWrite(int desired_flags); + + private: + static bool OpenFileFlagsAreConsistent(int desired_flags); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_DATABASE_VFS_BACKEND_H_ diff --git a/storage/browser/fileapi/OWNERS b/storage/browser/fileapi/OWNERS new file mode 100644 index 0000000..232db84 --- /dev/null +++ b/storage/browser/fileapi/OWNERS @@ -0,0 +1,2 @@ +tzik@chromium.org +nhiroki@chromium.org diff --git a/storage/browser/fileapi/async_file_util.h b/storage/browser/fileapi/async_file_util.h new file mode 100644 index 0000000..eab6af7 --- /dev/null +++ b/storage/browser/fileapi/async_file_util.h @@ -0,0 +1,366 @@ +// Copyright (c) 2013 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. + +#ifndef STORAGE_BROWSER_FILEAPI_ASYNC_FILE_UTIL_H_ +#define STORAGE_BROWSER_FILEAPI_ASYNC_FILE_UTIL_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/callback_forward.h" +#include "base/files/file.h" +#include "base/files/file_util_proxy.h" +#include "base/memory/scoped_ptr.h" +#include "storage/browser/fileapi/file_system_operation.h" +#include "storage/browser/storage_browser_export.h" +#include "storage/common/fileapi/directory_entry.h" + +namespace base { +class Time; +} + +namespace storage { +class ShareableFileReference; +} + +namespace storage { + +class FileSystemOperationContext; +class FileSystemURL; + +// An interface which provides filesystem-specific file operations for +// FileSystemOperationImpl. +// +// Each filesystem which needs to be dispatched from FileSystemOperationImpl +// must implement this interface or a synchronous version of interface: +// FileSystemFileUtil. +// +// As far as an instance of this class is owned by a FileSystemBackend +// (which is owned by FileSystemContext), it's guaranteed that this instance's +// alive while FileSystemOperationContext given to each operation is kept +// alive. (Note that this instance might be freed on different thread +// from the thread it is created.) +// +// It is NOT valid to give null callback to this class, and implementors +// can assume that they don't get any null callbacks. +// +class AsyncFileUtil { + public: + typedef base::Callback<void(base::File::Error result)> StatusCallback; + + // |on_close_callback| will be called after the |file| is closed in the + // child process. |on_close_callback|.is_null() can be true, if no operation + // is needed on closing the file. + typedef base::Callback< + void(base::File file, + const base::Closure& on_close_callback)> CreateOrOpenCallback; + + typedef base::Callback< + void(base::File::Error result, + bool created)> EnsureFileExistsCallback; + + typedef base::Callback< + void(base::File::Error result, + const base::File::Info& file_info)> GetFileInfoCallback; + + typedef std::vector<DirectoryEntry> EntryList; + typedef base::Callback< + void(base::File::Error result, + const EntryList& file_list, + bool has_more)> ReadDirectoryCallback; + + typedef base::Callback< + void(base::File::Error result, + const base::File::Info& file_info, + const base::FilePath& platform_path, + const scoped_refptr<storage::ShareableFileReference>& file_ref)> + CreateSnapshotFileCallback; + + typedef base::Callback<void(int64 size)> CopyFileProgressCallback; + + typedef FileSystemOperation::CopyOrMoveOption CopyOrMoveOption; + + // Creates an AsyncFileUtil instance which performs file operations on + // local native file system. The created instance assumes + // FileSystemURL::path() has the target platform path. + STORAGE_EXPORT static AsyncFileUtil* + CreateForLocalFileSystem(); + + AsyncFileUtil() {} + virtual ~AsyncFileUtil() {} + + // Creates or opens a file with the given flags. + // If File::FLAG_CREATE is set in |file_flags| it always tries to create + // a new file at the given |url| and calls back with + // File::FILE_ERROR_FILE_EXISTS if the |url| already exists. + // + // FileSystemOperationImpl::OpenFile calls this. + // This is used only by Pepper/NaCl File API. + // + virtual void CreateOrOpen( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + int file_flags, + const CreateOrOpenCallback& callback) = 0; + + // Ensures that the given |url| exist. This creates a empty new file + // at |url| if the |url| does not exist. + // + // FileSystemOperationImpl::CreateFile calls this. + // + // This reports following error code via |callback|: + // - File::FILE_OK and created==true if a file has not existed and + // is created at |url|. + // - File::FILE_OK and created==false if the file already exists. + // - Other error code (with created=false) if a file hasn't existed yet + // and there was an error while creating a new file. + // + virtual void EnsureFileExists( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const EnsureFileExistsCallback& callback) = 0; + + // Creates directory at given url. + // + // FileSystemOperationImpl::CreateDirectory calls this. + // + // This reports following error code via |callback|: + // - File::FILE_ERROR_NOT_FOUND if the |url|'s parent directory + // does not exist and |recursive| is false. + // - File::FILE_ERROR_EXISTS if a directory already exists at |url| + // and |exclusive| is true. + // - File::FILE_ERROR_EXISTS if a file already exists at |url| + // (regardless of |exclusive| value). + // - Other error code if it failed to create a directory. + // + virtual void CreateDirectory( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + bool exclusive, + bool recursive, + const StatusCallback& callback) = 0; + + // Retrieves the information about a file. + // + // FileSystemOperationImpl::GetMetadata calls this. + // + // This reports following error code via |callback|: + // - File::FILE_ERROR_NOT_FOUND if the file doesn't exist. + // - Other error code if there was an error while retrieving the file info. + // + virtual void GetFileInfo( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const GetFileInfoCallback& callback) = 0; + + // Reads contents of a directory at |path|. + // + // FileSystemOperationImpl::ReadDirectory calls this. + // + // Note that the |name| field of each entry in |file_list| + // returned by |callback| should have a base file name + // of the entry relative to the directory, but not an absolute path. + // + // (E.g. if ReadDirectory is called for a directory + // 'path/to/dir' and the directory has entries 'a' and 'b', + // the returned |file_list| should include entries whose names + // are 'a' and 'b', but not '/path/to/dir/a' and '/path/to/dir/b'.) + // + // This reports following error code via |callback|: + // - File::FILE_ERROR_NOT_FOUND if the target directory doesn't exist. + // - File::FILE_ERROR_NOT_A_DIRECTORY if an entry exists at |url| but + // is a file (not a directory). + // + virtual void ReadDirectory( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const ReadDirectoryCallback& callback) = 0; + + // Modifies timestamps of a file or directory at |url| with + // |last_access_time| and |last_modified_time|. The function DOES NOT + // create a file unlike 'touch' command on Linux. + // + // FileSystemOperationImpl::TouchFile calls this. + // This is used only by Pepper/NaCl File API. + // + virtual void Touch( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const base::Time& last_access_time, + const base::Time& last_modified_time, + const StatusCallback& callback) = 0; + + // Truncates a file at |path| to |length|. If |length| is larger than + // the original file size, the file will be extended, and the extended + // part is filled with null bytes. + // + // FileSystemOperationImpl::Truncate calls this. + // + // This reports following error code via |callback|: + // - File::FILE_ERROR_NOT_FOUND if the file doesn't exist. + // + virtual void Truncate( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + int64 length, + const StatusCallback& callback) = 0; + + // Copies a file from |src_url| to |dest_url|. + // This must be called for files that belong to the same filesystem + // (i.e. type() and origin() of the |src_url| and |dest_url| must match). + // |progress_callback| is a callback to report the progress update. + // See file_system_operations.h for details. This should be called on the + // same thread as where the method's called (IO thread). Calling this + // is optional. It is recommended to use this callback for heavier operations + // (such as file network downloading), so that, e.g., clients (UIs) can + // update its state to show progress to users. This may be a null callback. + // + // FileSystemOperationImpl::Copy calls this for same-filesystem copy case. + // + // This reports following error code via |callback|: + // - File::FILE_ERROR_NOT_FOUND if |src_url| + // or the parent directory of |dest_url| does not exist. + // - File::FILE_ERROR_NOT_A_FILE if |src_url| exists but is not a file. + // - File::FILE_ERROR_INVALID_OPERATION if |dest_url| exists and + // is not a file. + // - File::FILE_ERROR_FAILED if |dest_url| does not exist and + // its parent path is a file. + // + virtual void CopyFileLocal( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOption option, + const CopyFileProgressCallback& progress_callback, + const StatusCallback& callback) = 0; + + // Moves a local file from |src_url| to |dest_url|. + // This must be called for files that belong to the same filesystem + // (i.e. type() and origin() of the |src_url| and |dest_url| must match). + // + // FileSystemOperationImpl::Move calls this for same-filesystem move case. + // + // This reports following error code via |callback|: + // - File::FILE_ERROR_NOT_FOUND if |src_url| + // or the parent directory of |dest_url| does not exist. + // - File::FILE_ERROR_NOT_A_FILE if |src_url| exists but is not a file. + // - File::FILE_ERROR_INVALID_OPERATION if |dest_url| exists and + // is not a file. + // - File::FILE_ERROR_FAILED if |dest_url| does not exist and + // its parent path is a file. + // + virtual void MoveFileLocal( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOption option, + const StatusCallback& callback) = 0; + + // Copies in a single file from a different filesystem. + // + // FileSystemOperationImpl::Copy or Move calls this for cross-filesystem + // cases. + // + // This reports following error code via |callback|: + // - File::FILE_ERROR_NOT_FOUND if |src_file_path| + // or the parent directory of |dest_url| does not exist. + // - File::FILE_ERROR_INVALID_OPERATION if |dest_url| exists and + // is not a file. + // - File::FILE_ERROR_FAILED if |dest_url| does not exist and + // its parent path is a file. + // + virtual void CopyInForeignFile( + scoped_ptr<FileSystemOperationContext> context, + const base::FilePath& src_file_path, + const FileSystemURL& dest_url, + const StatusCallback& callback) = 0; + + // Deletes a single file. + // + // FileSystemOperationImpl::RemoveFile calls this. + // + // This reports following error code via |callback|: + // - File::FILE_ERROR_NOT_FOUND if |url| does not exist. + // - File::FILE_ERROR_NOT_A_FILE if |url| is not a file. + // + virtual void DeleteFile( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const StatusCallback& callback) = 0; + + // Removes a single empty directory. + // + // FileSystemOperationImpl::RemoveDirectory calls this. + // + // This reports following error code via |callback|: + // - File::FILE_ERROR_NOT_FOUND if |url| does not exist. + // - File::FILE_ERROR_NOT_A_DIRECTORY if |url| is not a directory. + // - File::FILE_ERROR_NOT_EMPTY if |url| is not empty. + // + virtual void DeleteDirectory( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const StatusCallback& callback) = 0; + + // Removes a single file or a single directory with its contents + // (i.e. files/subdirectories under the directory). + // + // FileSystemOperationImpl::Remove calls this. + // On some platforms, such as Chrome OS Drive File System, recursive file + // deletion can be implemented more efficiently than calling DeleteFile() and + // DeleteDirectory() for each files/directories. + // This method is optional, so if not supported, + // File::FILE_ERROR_INVALID_OPERATION should be returned via |callback|. + // + // This reports following error code via |callback|: + // - File::FILE_ERROR_NOT_FOUND if |url| does not exist. + // - File::FILE_ERROR_INVALID_OPERATION if this operation is not supported. + virtual void DeleteRecursively( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const StatusCallback& callback) = 0; + + // Creates a local snapshot file for a given |url| and returns the + // metadata and platform path of the snapshot file via |callback|. + // In regular filesystem cases the implementation may simply return + // the metadata of the file itself (as well as GetMetadata does), + // while in non-regular filesystem case the backend may create a + // temporary snapshot file which holds the file data and return + // the metadata of the temporary file. + // + // In the callback, it returns: + // |file_info| is the metadata of the snapshot file created. + // |platform_path| is the full absolute platform path to the snapshot + // file created. If a file is not backed by a real local file in + // the implementor's FileSystem, the implementor must create a + // local snapshot file and return the path of the created file. + // + // If implementors creates a temporary file for snapshotting and wants + // FileAPI backend to take care of the lifetime of the file (so that + // it won't get deleted while JS layer has any references to the created + // File/Blob object), it should return non-empty |file_ref|. + // Via the |file_ref| implementors can schedule a file deletion + // or arbitrary callbacks when the last reference of File/Blob is dropped. + // + // FileSystemOperationImpl::CreateSnapshotFile calls this. + // + // This reports following error code via |callback|: + // - File::FILE_ERROR_NOT_FOUND if |url| does not exist. + // - File::FILE_ERROR_NOT_A_FILE if |url| exists but is a directory. + // + // The field values of |file_info| are undefined (implementation + // dependent) in error cases, and the caller should always + // check the return code. + virtual void CreateSnapshotFile( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const CreateSnapshotFileCallback& callback) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(AsyncFileUtil); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_ASYNC_FILE_UTIL_H_ diff --git a/storage/browser/fileapi/async_file_util_adapter.cc b/storage/browser/fileapi/async_file_util_adapter.cc new file mode 100644 index 0000000..3bb20f9 --- /dev/null +++ b/storage/browser/fileapi/async_file_util_adapter.cc @@ -0,0 +1,352 @@ +// Copyright (c) 2013 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 "storage/browser/fileapi/async_file_util_adapter.h" + +#include <vector> + +#include "base/bind.h" +#include "base/sequenced_task_runner.h" +#include "base/task_runner_util.h" +#include "base/thread_task_runner_handle.h" +#include "storage/browser/fileapi/file_system_context.h" +#include "storage/browser/fileapi/file_system_file_util.h" +#include "storage/browser/fileapi/file_system_operation_context.h" +#include "storage/browser/fileapi/file_system_url.h" +#include "storage/common/blob/shareable_file_reference.h" +#include "storage/common/fileapi/file_system_util.h" + +using base::Bind; +using base::Callback; +using base::Owned; +using base::Unretained; +using storage::ShareableFileReference; + +namespace storage { + +namespace { + +class EnsureFileExistsHelper { + public: + EnsureFileExistsHelper() : error_(base::File::FILE_OK), created_(false) {} + + void RunWork(FileSystemFileUtil* file_util, + FileSystemOperationContext* context, + const FileSystemURL& url) { + error_ = file_util->EnsureFileExists(context, url, &created_); + } + + void Reply(const AsyncFileUtil::EnsureFileExistsCallback& callback) { + callback.Run(error_, created_); + } + + private: + base::File::Error error_; + bool created_; + DISALLOW_COPY_AND_ASSIGN(EnsureFileExistsHelper); +}; + +class GetFileInfoHelper { + public: + GetFileInfoHelper() + : error_(base::File::FILE_OK) {} + + void GetFileInfo(FileSystemFileUtil* file_util, + FileSystemOperationContext* context, + const FileSystemURL& url) { + error_ = file_util->GetFileInfo(context, url, &file_info_, &platform_path_); + } + + void CreateSnapshotFile(FileSystemFileUtil* file_util, + FileSystemOperationContext* context, + const FileSystemURL& url) { + scoped_file_ = file_util->CreateSnapshotFile( + context, url, &error_, &file_info_, &platform_path_); + } + + void ReplyFileInfo(const AsyncFileUtil::GetFileInfoCallback& callback) { + callback.Run(error_, file_info_); + } + + void ReplySnapshotFile( + const AsyncFileUtil::CreateSnapshotFileCallback& callback) { + callback.Run(error_, file_info_, platform_path_, + ShareableFileReference::GetOrCreate(scoped_file_.Pass())); + } + + private: + base::File::Error error_; + base::File::Info file_info_; + base::FilePath platform_path_; + storage::ScopedFile scoped_file_; + DISALLOW_COPY_AND_ASSIGN(GetFileInfoHelper); +}; + +void ReadDirectoryHelper(FileSystemFileUtil* file_util, + FileSystemOperationContext* context, + const FileSystemURL& url, + base::SingleThreadTaskRunner* origin_loop, + const AsyncFileUtil::ReadDirectoryCallback& callback) { + base::File::Info file_info; + base::FilePath platform_path; + base::File::Error error = file_util->GetFileInfo( + context, url, &file_info, &platform_path); + + if (error == base::File::FILE_OK && !file_info.is_directory) + error = base::File::FILE_ERROR_NOT_A_DIRECTORY; + + std::vector<DirectoryEntry> entries; + if (error != base::File::FILE_OK) { + origin_loop->PostTask( + FROM_HERE, base::Bind(callback, error, entries, false /* has_more */)); + return; + } + + // Note: Increasing this value may make some tests in LayoutTests meaningless. + // (Namely, read-directory-many.html and read-directory-sync-many.html are + // assuming that they are reading much more entries than this constant.) + const size_t kResultChunkSize = 100; + + scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> file_enum( + file_util->CreateFileEnumerator(context, url)); + + base::FilePath current; + while (!(current = file_enum->Next()).empty()) { + DirectoryEntry entry; + entry.is_directory = file_enum->IsDirectory(); + entry.name = VirtualPath::BaseName(current).value(); + entry.size = file_enum->Size(); + entry.last_modified_time = file_enum->LastModifiedTime(); + entries.push_back(entry); + + if (entries.size() == kResultChunkSize) { + origin_loop->PostTask( + FROM_HERE, base::Bind(callback, base::File::FILE_OK, entries, + true /* has_more */)); + entries.clear(); + } + } + origin_loop->PostTask( + FROM_HERE, base::Bind(callback, base::File::FILE_OK, entries, + false /* has_more */)); +} + +void RunCreateOrOpenCallback( + FileSystemOperationContext* context, + const AsyncFileUtil::CreateOrOpenCallback& callback, + base::File file) { + callback.Run(file.Pass(), base::Closure()); +} + +} // namespace + +AsyncFileUtilAdapter::AsyncFileUtilAdapter( + FileSystemFileUtil* sync_file_util) + : sync_file_util_(sync_file_util) { + DCHECK(sync_file_util_.get()); +} + +AsyncFileUtilAdapter::~AsyncFileUtilAdapter() { +} + +void AsyncFileUtilAdapter::CreateOrOpen( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + int file_flags, + const CreateOrOpenCallback& callback) { + FileSystemOperationContext* context_ptr = context.release(); + base::PostTaskAndReplyWithResult( + context_ptr->task_runner(), + FROM_HERE, + Bind(&FileSystemFileUtil::CreateOrOpen, Unretained(sync_file_util_.get()), + context_ptr, url, file_flags), + Bind(&RunCreateOrOpenCallback, base::Owned(context_ptr), callback)); +} + +void AsyncFileUtilAdapter::EnsureFileExists( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const EnsureFileExistsCallback& callback) { + EnsureFileExistsHelper* helper = new EnsureFileExistsHelper; + FileSystemOperationContext* context_ptr = context.release(); + const bool success = context_ptr->task_runner()->PostTaskAndReply( + FROM_HERE, + Bind(&EnsureFileExistsHelper::RunWork, Unretained(helper), + sync_file_util_.get(), base::Owned(context_ptr), url), + Bind(&EnsureFileExistsHelper::Reply, Owned(helper), callback)); + DCHECK(success); +} + +void AsyncFileUtilAdapter::CreateDirectory( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + bool exclusive, + bool recursive, + const StatusCallback& callback) { + FileSystemOperationContext* context_ptr = context.release(); + const bool success = base::PostTaskAndReplyWithResult( + context_ptr->task_runner(), FROM_HERE, + Bind(&FileSystemFileUtil::CreateDirectory, + Unretained(sync_file_util_.get()), + base::Owned(context_ptr), url, exclusive, recursive), + callback); + DCHECK(success); +} + +void AsyncFileUtilAdapter::GetFileInfo( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const GetFileInfoCallback& callback) { + FileSystemOperationContext* context_ptr = context.release(); + GetFileInfoHelper* helper = new GetFileInfoHelper; + const bool success = context_ptr->task_runner()->PostTaskAndReply( + FROM_HERE, + Bind(&GetFileInfoHelper::GetFileInfo, Unretained(helper), + sync_file_util_.get(), base::Owned(context_ptr), url), + Bind(&GetFileInfoHelper::ReplyFileInfo, Owned(helper), callback)); + DCHECK(success); +} + +void AsyncFileUtilAdapter::ReadDirectory( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const ReadDirectoryCallback& callback) { + FileSystemOperationContext* context_ptr = context.release(); + const bool success = context_ptr->task_runner()->PostTask( + FROM_HERE, + Bind(&ReadDirectoryHelper, + sync_file_util_.get(), base::Owned(context_ptr), url, + base::ThreadTaskRunnerHandle::Get(), callback)); + DCHECK(success); +} + +void AsyncFileUtilAdapter::Touch( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const base::Time& last_access_time, + const base::Time& last_modified_time, + const StatusCallback& callback) { + FileSystemOperationContext* context_ptr = context.release(); + const bool success = base::PostTaskAndReplyWithResult( + context_ptr->task_runner(), FROM_HERE, + Bind(&FileSystemFileUtil::Touch, Unretained(sync_file_util_.get()), + base::Owned(context_ptr), url, + last_access_time, last_modified_time), + callback); + DCHECK(success); +} + +void AsyncFileUtilAdapter::Truncate( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + int64 length, + const StatusCallback& callback) { + FileSystemOperationContext* context_ptr = context.release(); + const bool success = base::PostTaskAndReplyWithResult( + context_ptr->task_runner(), FROM_HERE, + Bind(&FileSystemFileUtil::Truncate, Unretained(sync_file_util_.get()), + base::Owned(context_ptr), url, length), + callback); + DCHECK(success); +} + +void AsyncFileUtilAdapter::CopyFileLocal( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOption option, + const CopyFileProgressCallback& progress_callback, + const StatusCallback& callback) { + // TODO(hidehiko): Support progress_callback. + FileSystemOperationContext* context_ptr = context.release(); + const bool success = base::PostTaskAndReplyWithResult( + context_ptr->task_runner(), FROM_HERE, + Bind(&FileSystemFileUtil::CopyOrMoveFile, + Unretained(sync_file_util_.get()), base::Owned(context_ptr), + src_url, dest_url, option, true /* copy */), + callback); + DCHECK(success); +} + +void AsyncFileUtilAdapter::MoveFileLocal( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOption option, + const StatusCallback& callback) { + FileSystemOperationContext* context_ptr = context.release(); + const bool success = base::PostTaskAndReplyWithResult( + context_ptr->task_runner(), FROM_HERE, + Bind(&FileSystemFileUtil::CopyOrMoveFile, + Unretained(sync_file_util_.get()), base::Owned(context_ptr), + src_url, dest_url, option, false /* copy */), + callback); + DCHECK(success); +} + +void AsyncFileUtilAdapter::CopyInForeignFile( + scoped_ptr<FileSystemOperationContext> context, + const base::FilePath& src_file_path, + const FileSystemURL& dest_url, + const StatusCallback& callback) { + FileSystemOperationContext* context_ptr = context.release(); + const bool success = base::PostTaskAndReplyWithResult( + context_ptr->task_runner(), FROM_HERE, + Bind(&FileSystemFileUtil::CopyInForeignFile, + Unretained(sync_file_util_.get()), + base::Owned(context_ptr), src_file_path, dest_url), + callback); + DCHECK(success); +} + +void AsyncFileUtilAdapter::DeleteFile( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const StatusCallback& callback) { + FileSystemOperationContext* context_ptr = context.release(); + const bool success = base::PostTaskAndReplyWithResult( + context_ptr->task_runner(), FROM_HERE, + Bind(&FileSystemFileUtil::DeleteFile, + Unretained(sync_file_util_.get()), + base::Owned(context_ptr), url), + callback); + DCHECK(success); +} + +void AsyncFileUtilAdapter::DeleteDirectory( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const StatusCallback& callback) { + FileSystemOperationContext* context_ptr = context.release(); + const bool success = base::PostTaskAndReplyWithResult( + context_ptr->task_runner(), FROM_HERE, + Bind(&FileSystemFileUtil::DeleteDirectory, + Unretained(sync_file_util_.get()), + base::Owned(context_ptr), url), + callback); + DCHECK(success); +} + +void AsyncFileUtilAdapter::DeleteRecursively( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const StatusCallback& callback) { + callback.Run(base::File::FILE_ERROR_INVALID_OPERATION); +} + +void AsyncFileUtilAdapter::CreateSnapshotFile( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const CreateSnapshotFileCallback& callback) { + FileSystemOperationContext* context_ptr = context.release(); + GetFileInfoHelper* helper = new GetFileInfoHelper; + const bool success = context_ptr->task_runner()->PostTaskAndReply( + FROM_HERE, + Bind(&GetFileInfoHelper::CreateSnapshotFile, Unretained(helper), + sync_file_util_.get(), base::Owned(context_ptr), url), + Bind(&GetFileInfoHelper::ReplySnapshotFile, Owned(helper), callback)); + DCHECK(success); +} + +} // namespace storage diff --git a/storage/browser/fileapi/async_file_util_adapter.h b/storage/browser/fileapi/async_file_util_adapter.h new file mode 100644 index 0000000..4ba4542 --- /dev/null +++ b/storage/browser/fileapi/async_file_util_adapter.h @@ -0,0 +1,118 @@ +// Copyright (c) 2013 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. + +#ifndef STORAGE_BROWSER_FILEAPI_ASYNC_FILE_UTIL_ADAPTER_H_ +#define STORAGE_BROWSER_FILEAPI_ASYNC_FILE_UTIL_ADAPTER_H_ + +#include "base/memory/scoped_ptr.h" +#include "storage/browser/fileapi/async_file_util.h" + +namespace storage { + +class FileSystemFileUtil; + +// An adapter class for FileSystemFileUtil classes to provide asynchronous +// interface. +// +// A filesystem can do either: +// - implement a synchronous version of FileUtil by extending +// FileSystemFileUtil and atach it to this adapter, or +// - directly implement AsyncFileUtil. +// +// This instance (as thus this->sync_file_util_) is guaranteed to be alive +// as far as FileSystemOperationContext given to each operation is kept alive. +class STORAGE_EXPORT AsyncFileUtilAdapter + : public NON_EXPORTED_BASE(AsyncFileUtil) { + public: + // Creates a new AsyncFileUtil for |sync_file_util|. This takes the + // ownership of |sync_file_util|. (This doesn't take scoped_ptr<> just + // to save extra make_scoped_ptr; in all use cases a new fresh FileUtil is + // created only for this adapter.) + explicit AsyncFileUtilAdapter(FileSystemFileUtil* sync_file_util); + + virtual ~AsyncFileUtilAdapter(); + + FileSystemFileUtil* sync_file_util() { + return sync_file_util_.get(); + } + + // AsyncFileUtil overrides. + virtual void CreateOrOpen( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + int file_flags, + const CreateOrOpenCallback& callback) OVERRIDE; + virtual void EnsureFileExists( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const EnsureFileExistsCallback& callback) OVERRIDE; + virtual void CreateDirectory( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + bool exclusive, + bool recursive, + const StatusCallback& callback) OVERRIDE; + virtual void GetFileInfo( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const GetFileInfoCallback& callback) OVERRIDE; + virtual void ReadDirectory( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const ReadDirectoryCallback& callback) OVERRIDE; + virtual void Touch( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const base::Time& last_access_time, + const base::Time& last_modified_time, + const StatusCallback& callback) OVERRIDE; + virtual void Truncate( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + int64 length, + const StatusCallback& callback) OVERRIDE; + virtual void CopyFileLocal( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOption option, + const CopyFileProgressCallback& progress_callback, + const StatusCallback& callback) OVERRIDE; + virtual void MoveFileLocal( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOption option, + const StatusCallback& callback) OVERRIDE; + virtual void CopyInForeignFile( + scoped_ptr<FileSystemOperationContext> context, + const base::FilePath& src_file_path, + const FileSystemURL& dest_url, + const StatusCallback& callback) OVERRIDE; + virtual void DeleteFile( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const StatusCallback& callback) OVERRIDE; + virtual void DeleteDirectory( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const StatusCallback& callback) OVERRIDE; + virtual void DeleteRecursively( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const StatusCallback& callback) OVERRIDE; + virtual void CreateSnapshotFile( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const CreateSnapshotFileCallback& callback) OVERRIDE; + + private: + scoped_ptr<FileSystemFileUtil> sync_file_util_; + + DISALLOW_COPY_AND_ASSIGN(AsyncFileUtilAdapter); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_ASYNC_FILE_UTIL_ADAPTER_H_ diff --git a/storage/browser/fileapi/copy_or_move_file_validator.h b/storage/browser/fileapi/copy_or_move_file_validator.h new file mode 100644 index 0000000..f79c473 --- /dev/null +++ b/storage/browser/fileapi/copy_or_move_file_validator.h @@ -0,0 +1,54 @@ +// Copyright 2013 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. + +#ifndef STORAGE_BROWSER_FILEAPI_COPY_OR_MOVE_FILE_VALIDATOR_H_ +#define STORAGE_BROWSER_FILEAPI_COPY_OR_MOVE_FILE_VALIDATOR_H_ + +#include "base/callback.h" +#include "base/files/file.h" +#include "storage/browser/storage_browser_export.h" + +namespace base { +class FilePath; +} + +namespace storage { + +class FileSystemURL; + +class STORAGE_EXPORT CopyOrMoveFileValidator { + public: + // Callback that is invoked when validation completes. A result of + // base::File::FILE_OK means the file validated. + typedef base::Callback<void(base::File::Error result)> ResultCallback; + + virtual ~CopyOrMoveFileValidator() {} + + // Called on a source file before copying or moving to the final + // destination. + virtual void StartPreWriteValidation( + const ResultCallback& result_callback) = 0; + + // Called on a destination file after copying or moving to the final + // destination. Suitable for running Anti-Virus checks. + virtual void StartPostWriteValidation( + const base::FilePath& dest_platform_path, + const ResultCallback& result_callback) = 0; +}; + +class CopyOrMoveFileValidatorFactory { + public: + virtual ~CopyOrMoveFileValidatorFactory() {} + + // This method must always return a non-NULL validator. |src_url| is needed + // in addition to |platform_path| because in the obfuscated file system + // case, |platform_path| will be an obfuscated filename and extension. + virtual CopyOrMoveFileValidator* CreateCopyOrMoveFileValidator( + const FileSystemURL& src_url, + const base::FilePath& platform_path) = 0; +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_COPY_OR_MOVE_FILE_VALIDATOR_H_ diff --git a/storage/browser/fileapi/copy_or_move_operation_delegate.cc b/storage/browser/fileapi/copy_or_move_operation_delegate.cc new file mode 100644 index 0000000..84244df --- /dev/null +++ b/storage/browser/fileapi/copy_or_move_operation_delegate.cc @@ -0,0 +1,1034 @@ +// Copyright (c) 2013 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 "storage/browser/fileapi/copy_or_move_operation_delegate.h" + +#include "base/bind.h" +#include "base/files/file_path.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "storage/browser/blob/file_stream_reader.h" +#include "storage/browser/fileapi/copy_or_move_file_validator.h" +#include "storage/browser/fileapi/file_observers.h" +#include "storage/browser/fileapi/file_stream_writer.h" +#include "storage/browser/fileapi/file_system_context.h" +#include "storage/browser/fileapi/file_system_operation_runner.h" +#include "storage/browser/fileapi/file_system_url.h" +#include "storage/browser/fileapi/recursive_operation_delegate.h" +#include "storage/common/blob/shareable_file_reference.h" +#include "storage/common/fileapi/file_system_util.h" + +namespace storage { + +const int64 kFlushIntervalInBytes = 10 << 20; // 10MB. + +class CopyOrMoveOperationDelegate::CopyOrMoveImpl { + public: + virtual ~CopyOrMoveImpl() {} + virtual void Run( + const CopyOrMoveOperationDelegate::StatusCallback& callback) = 0; + virtual void Cancel() = 0; + + protected: + CopyOrMoveImpl() {} + + private: + DISALLOW_COPY_AND_ASSIGN(CopyOrMoveImpl); +}; + +namespace { + +// Copies a file on a (same) file system. Just delegate the operation to +// |operation_runner|. +class CopyOrMoveOnSameFileSystemImpl + : public CopyOrMoveOperationDelegate::CopyOrMoveImpl { + public: + CopyOrMoveOnSameFileSystemImpl( + FileSystemOperationRunner* operation_runner, + CopyOrMoveOperationDelegate::OperationType operation_type, + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOperationDelegate::CopyOrMoveOption option, + const FileSystemOperation::CopyFileProgressCallback& + file_progress_callback) + : operation_runner_(operation_runner), + operation_type_(operation_type), + src_url_(src_url), + dest_url_(dest_url), + option_(option), + file_progress_callback_(file_progress_callback) { + } + + virtual void Run( + const CopyOrMoveOperationDelegate::StatusCallback& callback) OVERRIDE { + if (operation_type_ == CopyOrMoveOperationDelegate::OPERATION_MOVE) { + operation_runner_->MoveFileLocal(src_url_, dest_url_, option_, callback); + } else { + operation_runner_->CopyFileLocal( + src_url_, dest_url_, option_, file_progress_callback_, callback); + } + } + + virtual void Cancel() OVERRIDE { + // We can do nothing for the copy/move operation on a local file system. + // Assuming the operation is quickly done, it should be ok to just wait + // for the completion. + } + + private: + FileSystemOperationRunner* operation_runner_; + CopyOrMoveOperationDelegate::OperationType operation_type_; + FileSystemURL src_url_; + FileSystemURL dest_url_; + CopyOrMoveOperationDelegate::CopyOrMoveOption option_; + FileSystemOperation::CopyFileProgressCallback file_progress_callback_; + DISALLOW_COPY_AND_ASSIGN(CopyOrMoveOnSameFileSystemImpl); +}; + +// Specifically for cross file system copy/move operation, this class creates +// a snapshot file, validates it if necessary, runs copying process, +// validates the created file, and removes source file for move (noop for +// copy). +class SnapshotCopyOrMoveImpl + : public CopyOrMoveOperationDelegate::CopyOrMoveImpl { + public: + SnapshotCopyOrMoveImpl( + FileSystemOperationRunner* operation_runner, + CopyOrMoveOperationDelegate::OperationType operation_type, + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOperationDelegate::CopyOrMoveOption option, + CopyOrMoveFileValidatorFactory* validator_factory, + const FileSystemOperation::CopyFileProgressCallback& + file_progress_callback) + : operation_runner_(operation_runner), + operation_type_(operation_type), + src_url_(src_url), + dest_url_(dest_url), + option_(option), + validator_factory_(validator_factory), + file_progress_callback_(file_progress_callback), + cancel_requested_(false), + weak_factory_(this) { + } + + virtual void Run( + const CopyOrMoveOperationDelegate::StatusCallback& callback) OVERRIDE { + file_progress_callback_.Run(0); + operation_runner_->CreateSnapshotFile( + src_url_, + base::Bind(&SnapshotCopyOrMoveImpl::RunAfterCreateSnapshot, + weak_factory_.GetWeakPtr(), callback)); + } + + virtual void Cancel() OVERRIDE { + cancel_requested_ = true; + } + + private: + void RunAfterCreateSnapshot( + const CopyOrMoveOperationDelegate::StatusCallback& callback, + base::File::Error error, + const base::File::Info& file_info, + const base::FilePath& platform_path, + const scoped_refptr<storage::ShareableFileReference>& file_ref) { + if (cancel_requested_) + error = base::File::FILE_ERROR_ABORT; + + if (error != base::File::FILE_OK) { + callback.Run(error); + return; + } + + // For now we assume CreateSnapshotFile always return a valid local file + // path. + DCHECK(!platform_path.empty()); + + if (!validator_factory_) { + // No validation is needed. + RunAfterPreWriteValidation(platform_path, file_info, file_ref, callback, + base::File::FILE_OK); + return; + } + + // Run pre write validation. + PreWriteValidation( + platform_path, + base::Bind(&SnapshotCopyOrMoveImpl::RunAfterPreWriteValidation, + weak_factory_.GetWeakPtr(), + platform_path, file_info, file_ref, callback)); + } + + void RunAfterPreWriteValidation( + const base::FilePath& platform_path, + const base::File::Info& file_info, + const scoped_refptr<storage::ShareableFileReference>& file_ref, + const CopyOrMoveOperationDelegate::StatusCallback& callback, + base::File::Error error) { + if (cancel_requested_) + error = base::File::FILE_ERROR_ABORT; + + if (error != base::File::FILE_OK) { + callback.Run(error); + return; + } + + // |file_ref| is unused but necessary to keep the file alive until + // CopyInForeignFile() is completed. + operation_runner_->CopyInForeignFile( + platform_path, dest_url_, + base::Bind(&SnapshotCopyOrMoveImpl::RunAfterCopyInForeignFile, + weak_factory_.GetWeakPtr(), file_info, file_ref, callback)); + } + + void RunAfterCopyInForeignFile( + const base::File::Info& file_info, + const scoped_refptr<storage::ShareableFileReference>& file_ref, + const CopyOrMoveOperationDelegate::StatusCallback& callback, + base::File::Error error) { + if (cancel_requested_) + error = base::File::FILE_ERROR_ABORT; + + if (error != base::File::FILE_OK) { + callback.Run(error); + return; + } + + file_progress_callback_.Run(file_info.size); + + if (option_ == FileSystemOperation::OPTION_NONE) { + RunAfterTouchFile(callback, base::File::FILE_OK); + return; + } + + operation_runner_->TouchFile( + dest_url_, base::Time::Now() /* last_access */, + file_info.last_modified, + base::Bind(&SnapshotCopyOrMoveImpl::RunAfterTouchFile, + weak_factory_.GetWeakPtr(), callback)); + } + + void RunAfterTouchFile( + const CopyOrMoveOperationDelegate::StatusCallback& callback, + base::File::Error error) { + // Even if TouchFile is failed, just ignore it. + + if (cancel_requested_) { + callback.Run(base::File::FILE_ERROR_ABORT); + return; + } + + // |validator_| is NULL when the destination filesystem does not do + // validation. + if (!validator_) { + // No validation is needed. + RunAfterPostWriteValidation(callback, base::File::FILE_OK); + return; + } + + PostWriteValidation( + base::Bind(&SnapshotCopyOrMoveImpl::RunAfterPostWriteValidation, + weak_factory_.GetWeakPtr(), callback)); + } + + void RunAfterPostWriteValidation( + const CopyOrMoveOperationDelegate::StatusCallback& callback, + base::File::Error error) { + if (cancel_requested_) { + callback.Run(base::File::FILE_ERROR_ABORT); + return; + } + + if (error != base::File::FILE_OK) { + // Failed to validate. Remove the destination file. + operation_runner_->Remove( + dest_url_, true /* recursive */, + base::Bind(&SnapshotCopyOrMoveImpl::DidRemoveDestForError, + weak_factory_.GetWeakPtr(), error, callback)); + return; + } + + if (operation_type_ == CopyOrMoveOperationDelegate::OPERATION_COPY) { + callback.Run(base::File::FILE_OK); + return; + } + + DCHECK_EQ(CopyOrMoveOperationDelegate::OPERATION_MOVE, operation_type_); + + // Remove the source for finalizing move operation. + operation_runner_->Remove( + src_url_, true /* recursive */, + base::Bind(&SnapshotCopyOrMoveImpl::RunAfterRemoveSourceForMove, + weak_factory_.GetWeakPtr(), callback)); + } + + void RunAfterRemoveSourceForMove( + const CopyOrMoveOperationDelegate::StatusCallback& callback, + base::File::Error error) { + if (cancel_requested_) + error = base::File::FILE_ERROR_ABORT; + + if (error == base::File::FILE_ERROR_NOT_FOUND) + error = base::File::FILE_OK; + callback.Run(error); + } + + void DidRemoveDestForError( + base::File::Error prior_error, + const CopyOrMoveOperationDelegate::StatusCallback& callback, + base::File::Error error) { + if (error != base::File::FILE_OK) { + VLOG(1) << "Error removing destination file after validation error: " + << error; + } + callback.Run(prior_error); + } + + // Runs pre-write validation. + void PreWriteValidation( + const base::FilePath& platform_path, + const CopyOrMoveOperationDelegate::StatusCallback& callback) { + DCHECK(validator_factory_); + validator_.reset( + validator_factory_->CreateCopyOrMoveFileValidator( + src_url_, platform_path)); + validator_->StartPreWriteValidation(callback); + } + + // Runs post-write validation. + void PostWriteValidation( + const CopyOrMoveOperationDelegate::StatusCallback& callback) { + operation_runner_->CreateSnapshotFile( + dest_url_, + base::Bind( + &SnapshotCopyOrMoveImpl::PostWriteValidationAfterCreateSnapshotFile, + weak_factory_.GetWeakPtr(), callback)); + } + + void PostWriteValidationAfterCreateSnapshotFile( + const CopyOrMoveOperationDelegate::StatusCallback& callback, + base::File::Error error, + const base::File::Info& file_info, + const base::FilePath& platform_path, + const scoped_refptr<storage::ShareableFileReference>& file_ref) { + if (cancel_requested_) + error = base::File::FILE_ERROR_ABORT; + + if (error != base::File::FILE_OK) { + callback.Run(error); + return; + } + + DCHECK(validator_); + // Note: file_ref passed here to keep the file alive until after + // the StartPostWriteValidation operation finishes. + validator_->StartPostWriteValidation( + platform_path, + base::Bind(&SnapshotCopyOrMoveImpl::DidPostWriteValidation, + weak_factory_.GetWeakPtr(), file_ref, callback)); + } + + // |file_ref| is unused; it is passed here to make sure the reference is + // alive until after post-write validation is complete. + void DidPostWriteValidation( + const scoped_refptr<storage::ShareableFileReference>& file_ref, + const CopyOrMoveOperationDelegate::StatusCallback& callback, + base::File::Error error) { + callback.Run(error); + } + + FileSystemOperationRunner* operation_runner_; + CopyOrMoveOperationDelegate::OperationType operation_type_; + FileSystemURL src_url_; + FileSystemURL dest_url_; + + CopyOrMoveOperationDelegate::CopyOrMoveOption option_; + CopyOrMoveFileValidatorFactory* validator_factory_; + scoped_ptr<CopyOrMoveFileValidator> validator_; + FileSystemOperation::CopyFileProgressCallback file_progress_callback_; + bool cancel_requested_; + base::WeakPtrFactory<SnapshotCopyOrMoveImpl> weak_factory_; + DISALLOW_COPY_AND_ASSIGN(SnapshotCopyOrMoveImpl); +}; + +// The size of buffer for StreamCopyHelper. +const int kReadBufferSize = 32768; + +// To avoid too many progress callbacks, it should be called less +// frequently than 50ms. +const int kMinProgressCallbackInvocationSpanInMilliseconds = 50; + +// Specifically for cross file system copy/move operation, this class uses +// stream reader and writer for copying. Validator is not supported, so if +// necessary SnapshotCopyOrMoveImpl should be used. +class StreamCopyOrMoveImpl + : public CopyOrMoveOperationDelegate::CopyOrMoveImpl { + public: + StreamCopyOrMoveImpl( + FileSystemOperationRunner* operation_runner, + FileSystemContext* file_system_context, + CopyOrMoveOperationDelegate::OperationType operation_type, + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOperationDelegate::CopyOrMoveOption option, + scoped_ptr<storage::FileStreamReader> reader, + scoped_ptr<FileStreamWriter> writer, + const FileSystemOperation::CopyFileProgressCallback& + file_progress_callback) + : operation_runner_(operation_runner), + file_system_context_(file_system_context), + operation_type_(operation_type), + src_url_(src_url), + dest_url_(dest_url), + option_(option), + reader_(reader.Pass()), + writer_(writer.Pass()), + file_progress_callback_(file_progress_callback), + cancel_requested_(false), + weak_factory_(this) {} + + virtual void Run( + const CopyOrMoveOperationDelegate::StatusCallback& callback) OVERRIDE { + // Reader can be created even if the entry does not exist or the entry is + // a directory. To check errors before destination file creation, + // check metadata first. + operation_runner_->GetMetadata( + src_url_, + base::Bind(&StreamCopyOrMoveImpl::RunAfterGetMetadataForSource, + weak_factory_.GetWeakPtr(), callback)); + } + + virtual void Cancel() OVERRIDE { + cancel_requested_ = true; + if (copy_helper_) + copy_helper_->Cancel(); + } + + private: + void NotifyOnStartUpdate(const FileSystemURL& url) { + if (file_system_context_->GetUpdateObservers(url.type())) { + file_system_context_->GetUpdateObservers(url.type()) + ->Notify(&FileUpdateObserver::OnStartUpdate, MakeTuple(url)); + } + } + + void NotifyOnModifyFile(const FileSystemURL& url) { + if (file_system_context_->GetChangeObservers(url.type())) { + file_system_context_->GetChangeObservers(url.type()) + ->Notify(&FileChangeObserver::OnModifyFile, MakeTuple(url)); + } + } + + void NotifyOnEndUpdate(const FileSystemURL& url) { + if (file_system_context_->GetUpdateObservers(url.type())) { + file_system_context_->GetUpdateObservers(url.type()) + ->Notify(&FileUpdateObserver::OnEndUpdate, MakeTuple(url)); + } + } + + void RunAfterGetMetadataForSource( + const CopyOrMoveOperationDelegate::StatusCallback& callback, + base::File::Error error, + const base::File::Info& file_info) { + if (cancel_requested_) + error = base::File::FILE_ERROR_ABORT; + + if (error != base::File::FILE_OK) { + callback.Run(error); + return; + } + + if (file_info.is_directory) { + // If not a directory, failed with appropriate error code. + callback.Run(base::File::FILE_ERROR_NOT_A_FILE); + return; + } + + // To use FileStreamWriter, we need to ensure the destination file exists. + operation_runner_->CreateFile( + dest_url_, + true /* exclusive */, + base::Bind(&StreamCopyOrMoveImpl::RunAfterCreateFileForDestination, + weak_factory_.GetWeakPtr(), + callback, + file_info.last_modified)); + } + + void RunAfterCreateFileForDestination( + const CopyOrMoveOperationDelegate::StatusCallback& callback, + const base::Time& last_modified, + base::File::Error error) { + if (cancel_requested_) + error = base::File::FILE_ERROR_ABORT; + // This conversion is to return the consistent status code with + // FileSystemFileUtil::Copy. + if (error == base::File::FILE_ERROR_NOT_A_FILE) + error = base::File::FILE_ERROR_INVALID_OPERATION; + + if (error != base::File::FILE_OK && + error != base::File::FILE_ERROR_EXISTS) { + callback.Run(error); + return; + } + + if (error == base::File::FILE_ERROR_EXISTS) { + operation_runner_->Truncate( + dest_url_, + 0 /* length */, + base::Bind(&StreamCopyOrMoveImpl::RunAfterTruncateForDestination, + weak_factory_.GetWeakPtr(), + callback, + last_modified)); + return; + } + RunAfterTruncateForDestination( + callback, last_modified, base::File::FILE_OK); + } + + void RunAfterTruncateForDestination( + const CopyOrMoveOperationDelegate::StatusCallback& callback, + const base::Time& last_modified, + base::File::Error error) { + if (cancel_requested_) + error = base::File::FILE_ERROR_ABORT; + + if (error != base::File::FILE_OK) { + callback.Run(error); + return; + } + + const bool need_flush = dest_url_.mount_option().copy_sync_option() == + storage::COPY_SYNC_OPTION_SYNC; + + NotifyOnStartUpdate(dest_url_); + DCHECK(!copy_helper_); + copy_helper_.reset( + new CopyOrMoveOperationDelegate::StreamCopyHelper( + reader_.Pass(), writer_.Pass(), + need_flush, + kReadBufferSize, + file_progress_callback_, + base::TimeDelta::FromMilliseconds( + kMinProgressCallbackInvocationSpanInMilliseconds))); + copy_helper_->Run( + base::Bind(&StreamCopyOrMoveImpl::RunAfterStreamCopy, + weak_factory_.GetWeakPtr(), callback, last_modified)); + } + + void RunAfterStreamCopy( + const CopyOrMoveOperationDelegate::StatusCallback& callback, + const base::Time& last_modified, + base::File::Error error) { + NotifyOnModifyFile(dest_url_); + NotifyOnEndUpdate(dest_url_); + if (cancel_requested_) + error = base::File::FILE_ERROR_ABORT; + + if (error != base::File::FILE_OK) { + callback.Run(error); + return; + } + + if (option_ == FileSystemOperation::OPTION_NONE) { + RunAfterTouchFile(callback, base::File::FILE_OK); + return; + } + + operation_runner_->TouchFile( + dest_url_, base::Time::Now() /* last_access */, last_modified, + base::Bind(&StreamCopyOrMoveImpl::RunAfterTouchFile, + weak_factory_.GetWeakPtr(), callback)); + } + + void RunAfterTouchFile( + const CopyOrMoveOperationDelegate::StatusCallback& callback, + base::File::Error error) { + // Even if TouchFile is failed, just ignore it. + if (cancel_requested_) { + callback.Run(base::File::FILE_ERROR_ABORT); + return; + } + + if (operation_type_ == CopyOrMoveOperationDelegate::OPERATION_COPY) { + callback.Run(base::File::FILE_OK); + return; + } + + DCHECK_EQ(CopyOrMoveOperationDelegate::OPERATION_MOVE, operation_type_); + + // Remove the source for finalizing move operation. + operation_runner_->Remove( + src_url_, false /* recursive */, + base::Bind(&StreamCopyOrMoveImpl::RunAfterRemoveForMove, + weak_factory_.GetWeakPtr(), callback)); + } + + void RunAfterRemoveForMove( + const CopyOrMoveOperationDelegate::StatusCallback& callback, + base::File::Error error) { + if (cancel_requested_) + error = base::File::FILE_ERROR_ABORT; + if (error == base::File::FILE_ERROR_NOT_FOUND) + error = base::File::FILE_OK; + callback.Run(error); + } + + FileSystemOperationRunner* operation_runner_; + scoped_refptr<FileSystemContext> file_system_context_; + CopyOrMoveOperationDelegate::OperationType operation_type_; + FileSystemURL src_url_; + FileSystemURL dest_url_; + CopyOrMoveOperationDelegate::CopyOrMoveOption option_; + scoped_ptr<storage::FileStreamReader> reader_; + scoped_ptr<FileStreamWriter> writer_; + FileSystemOperation::CopyFileProgressCallback file_progress_callback_; + scoped_ptr<CopyOrMoveOperationDelegate::StreamCopyHelper> copy_helper_; + bool cancel_requested_; + base::WeakPtrFactory<StreamCopyOrMoveImpl> weak_factory_; + DISALLOW_COPY_AND_ASSIGN(StreamCopyOrMoveImpl); +}; + +} // namespace + +CopyOrMoveOperationDelegate::StreamCopyHelper::StreamCopyHelper( + scoped_ptr<storage::FileStreamReader> reader, + scoped_ptr<FileStreamWriter> writer, + bool need_flush, + int buffer_size, + const FileSystemOperation::CopyFileProgressCallback& file_progress_callback, + const base::TimeDelta& min_progress_callback_invocation_span) + : reader_(reader.Pass()), + writer_(writer.Pass()), + need_flush_(need_flush), + file_progress_callback_(file_progress_callback), + io_buffer_(new net::IOBufferWithSize(buffer_size)), + num_copied_bytes_(0), + previous_flush_offset_(0), + min_progress_callback_invocation_span_( + min_progress_callback_invocation_span), + cancel_requested_(false), + weak_factory_(this) { +} + +CopyOrMoveOperationDelegate::StreamCopyHelper::~StreamCopyHelper() { +} + +void CopyOrMoveOperationDelegate::StreamCopyHelper::Run( + const StatusCallback& callback) { + file_progress_callback_.Run(0); + last_progress_callback_invocation_time_ = base::Time::Now(); + Read(callback); +} + +void CopyOrMoveOperationDelegate::StreamCopyHelper::Cancel() { + cancel_requested_ = true; +} + +void CopyOrMoveOperationDelegate::StreamCopyHelper::Read( + const StatusCallback& callback) { + int result = reader_->Read( + io_buffer_.get(), io_buffer_->size(), + base::Bind(&StreamCopyHelper::DidRead, + weak_factory_.GetWeakPtr(), callback)); + if (result != net::ERR_IO_PENDING) + DidRead(callback, result); +} + +void CopyOrMoveOperationDelegate::StreamCopyHelper::DidRead( + const StatusCallback& callback, int result) { + if (cancel_requested_) { + callback.Run(base::File::FILE_ERROR_ABORT); + return; + } + + if (result < 0) { + callback.Run(NetErrorToFileError(result)); + return; + } + + if (result == 0) { + // Here is the EOF. + if (need_flush_) + Flush(callback, true /* is_eof */); + else + callback.Run(base::File::FILE_OK); + return; + } + + Write(callback, new net::DrainableIOBuffer(io_buffer_.get(), result)); +} + +void CopyOrMoveOperationDelegate::StreamCopyHelper::Write( + const StatusCallback& callback, + scoped_refptr<net::DrainableIOBuffer> buffer) { + DCHECK_GT(buffer->BytesRemaining(), 0); + + int result = writer_->Write( + buffer.get(), buffer->BytesRemaining(), + base::Bind(&StreamCopyHelper::DidWrite, + weak_factory_.GetWeakPtr(), callback, buffer)); + if (result != net::ERR_IO_PENDING) + DidWrite(callback, buffer, result); +} + +void CopyOrMoveOperationDelegate::StreamCopyHelper::DidWrite( + const StatusCallback& callback, + scoped_refptr<net::DrainableIOBuffer> buffer, + int result) { + if (cancel_requested_) { + callback.Run(base::File::FILE_ERROR_ABORT); + return; + } + + if (result < 0) { + callback.Run(NetErrorToFileError(result)); + return; + } + + buffer->DidConsume(result); + num_copied_bytes_ += result; + + // Check the elapsed time since last |file_progress_callback_| invocation. + base::Time now = base::Time::Now(); + if (now - last_progress_callback_invocation_time_ >= + min_progress_callback_invocation_span_) { + file_progress_callback_.Run(num_copied_bytes_); + last_progress_callback_invocation_time_ = now; + } + + if (buffer->BytesRemaining() > 0) { + Write(callback, buffer); + return; + } + + if (need_flush_ && + (num_copied_bytes_ - previous_flush_offset_) > kFlushIntervalInBytes) { + Flush(callback, false /* not is_eof */); + } else { + Read(callback); + } +} + +void CopyOrMoveOperationDelegate::StreamCopyHelper::Flush( + const StatusCallback& callback, bool is_eof) { + int result = writer_->Flush( + base::Bind(&StreamCopyHelper::DidFlush, + weak_factory_.GetWeakPtr(), callback, is_eof)); + if (result != net::ERR_IO_PENDING) + DidFlush(callback, is_eof, result); +} + +void CopyOrMoveOperationDelegate::StreamCopyHelper::DidFlush( + const StatusCallback& callback, bool is_eof, int result) { + if (cancel_requested_) { + callback.Run(base::File::FILE_ERROR_ABORT); + return; + } + + previous_flush_offset_ = num_copied_bytes_; + if (is_eof) + callback.Run(NetErrorToFileError(result)); + else + Read(callback); +} + +CopyOrMoveOperationDelegate::CopyOrMoveOperationDelegate( + FileSystemContext* file_system_context, + const FileSystemURL& src_root, + const FileSystemURL& dest_root, + OperationType operation_type, + CopyOrMoveOption option, + const CopyProgressCallback& progress_callback, + const StatusCallback& callback) + : RecursiveOperationDelegate(file_system_context), + src_root_(src_root), + dest_root_(dest_root), + operation_type_(operation_type), + option_(option), + progress_callback_(progress_callback), + callback_(callback), + weak_factory_(this) { + same_file_system_ = src_root_.IsInSameFileSystem(dest_root_); +} + +CopyOrMoveOperationDelegate::~CopyOrMoveOperationDelegate() { + STLDeleteElements(&running_copy_set_); +} + +void CopyOrMoveOperationDelegate::Run() { + // Not supported; this should never be called. + NOTREACHED(); +} + +void CopyOrMoveOperationDelegate::RunRecursively() { + // Perform light-weight checks first. + + // It is an error to try to copy/move an entry into its child. + if (same_file_system_ && src_root_.path().IsParent(dest_root_.path())) { + callback_.Run(base::File::FILE_ERROR_INVALID_OPERATION); + return; + } + + if (same_file_system_ && src_root_.path() == dest_root_.path()) { + // In JS API this should return error, but we return success because Pepper + // wants to return success and we have a code path that returns error in + // Blink for JS (http://crbug.com/329517). + callback_.Run(base::File::FILE_OK); + return; + } + + // Start to process the source directory recursively. + // TODO(kinuko): This could be too expensive for same_file_system_==true + // and operation==MOVE case, probably we can just rename the root directory. + // http://crbug.com/172187 + StartRecursiveOperation(src_root_, callback_); +} + +void CopyOrMoveOperationDelegate::ProcessFile( + const FileSystemURL& src_url, + const StatusCallback& callback) { + if (!progress_callback_.is_null()) { + progress_callback_.Run( + FileSystemOperation::BEGIN_COPY_ENTRY, src_url, FileSystemURL(), 0); + } + + FileSystemURL dest_url = CreateDestURL(src_url); + CopyOrMoveImpl* impl = NULL; + if (same_file_system_ && + (file_system_context() + ->GetFileSystemBackend(src_url.type()) + ->HasInplaceCopyImplementation(src_url.type()) || + operation_type_ == OperationType::OPERATION_MOVE)) { + impl = new CopyOrMoveOnSameFileSystemImpl( + operation_runner(), operation_type_, src_url, dest_url, option_, + base::Bind(&CopyOrMoveOperationDelegate::OnCopyFileProgress, + weak_factory_.GetWeakPtr(), src_url)); + } else { + // Cross filesystem case. + base::File::Error error = base::File::FILE_ERROR_FAILED; + CopyOrMoveFileValidatorFactory* validator_factory = + file_system_context()->GetCopyOrMoveFileValidatorFactory( + dest_root_.type(), &error); + if (error != base::File::FILE_OK) { + callback.Run(error); + return; + } + + if (!validator_factory) { + scoped_ptr<storage::FileStreamReader> reader = + file_system_context()->CreateFileStreamReader( + src_url, 0, base::Time()); + scoped_ptr<FileStreamWriter> writer = + file_system_context()->CreateFileStreamWriter(dest_url, 0); + if (reader && writer) { + impl = new StreamCopyOrMoveImpl( + operation_runner(), + file_system_context(), + operation_type_, + src_url, + dest_url, + option_, + reader.Pass(), + writer.Pass(), + base::Bind(&CopyOrMoveOperationDelegate::OnCopyFileProgress, + weak_factory_.GetWeakPtr(), + src_url)); + } + } + + if (!impl) { + impl = new SnapshotCopyOrMoveImpl( + operation_runner(), operation_type_, src_url, dest_url, option_, + validator_factory, + base::Bind(&CopyOrMoveOperationDelegate::OnCopyFileProgress, + weak_factory_.GetWeakPtr(), src_url)); + } + } + + // Register the running task. + running_copy_set_.insert(impl); + impl->Run(base::Bind( + &CopyOrMoveOperationDelegate::DidCopyOrMoveFile, + weak_factory_.GetWeakPtr(), src_url, dest_url, callback, impl)); +} + +void CopyOrMoveOperationDelegate::ProcessDirectory( + const FileSystemURL& src_url, + const StatusCallback& callback) { + if (src_url == src_root_) { + // The src_root_ looks to be a directory. + // Try removing the dest_root_ to see if it exists and/or it is an + // empty directory. + // We do not invoke |progress_callback_| for source root, because it is + // already called in ProcessFile(). + operation_runner()->RemoveDirectory( + dest_root_, + base::Bind(&CopyOrMoveOperationDelegate::DidTryRemoveDestRoot, + weak_factory_.GetWeakPtr(), callback)); + return; + } + + if (!progress_callback_.is_null()) { + progress_callback_.Run( + FileSystemOperation::BEGIN_COPY_ENTRY, src_url, FileSystemURL(), 0); + } + + ProcessDirectoryInternal(src_url, CreateDestURL(src_url), callback); +} + +void CopyOrMoveOperationDelegate::PostProcessDirectory( + const FileSystemURL& src_url, + const StatusCallback& callback) { + if (option_ == FileSystemOperation::OPTION_NONE) { + PostProcessDirectoryAfterTouchFile( + src_url, callback, base::File::FILE_OK); + return; + } + + operation_runner()->GetMetadata( + src_url, + base::Bind( + &CopyOrMoveOperationDelegate::PostProcessDirectoryAfterGetMetadata, + weak_factory_.GetWeakPtr(), src_url, callback)); +} + +void CopyOrMoveOperationDelegate::OnCancel() { + // Request to cancel all running Copy/Move file. + for (std::set<CopyOrMoveImpl*>::iterator iter = running_copy_set_.begin(); + iter != running_copy_set_.end(); ++iter) + (*iter)->Cancel(); +} + +void CopyOrMoveOperationDelegate::DidCopyOrMoveFile( + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + const StatusCallback& callback, + CopyOrMoveImpl* impl, + base::File::Error error) { + running_copy_set_.erase(impl); + delete impl; + + if (!progress_callback_.is_null() && error == base::File::FILE_OK) { + progress_callback_.Run( + FileSystemOperation::END_COPY_ENTRY, src_url, dest_url, 0); + } + + callback.Run(error); +} + +void CopyOrMoveOperationDelegate::DidTryRemoveDestRoot( + const StatusCallback& callback, + base::File::Error error) { + if (error == base::File::FILE_ERROR_NOT_A_DIRECTORY) { + callback_.Run(base::File::FILE_ERROR_INVALID_OPERATION); + return; + } + if (error != base::File::FILE_OK && + error != base::File::FILE_ERROR_NOT_FOUND) { + callback_.Run(error); + return; + } + + ProcessDirectoryInternal(src_root_, dest_root_, callback); +} + +void CopyOrMoveOperationDelegate::ProcessDirectoryInternal( + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + const StatusCallback& callback) { + // If operation_type == Move we may need to record directories and + // restore directory timestamps in the end, though it may have + // negative performance impact. + // See http://crbug.com/171284 for more details. + operation_runner()->CreateDirectory( + dest_url, false /* exclusive */, false /* recursive */, + base::Bind(&CopyOrMoveOperationDelegate::DidCreateDirectory, + weak_factory_.GetWeakPtr(), src_url, dest_url, callback)); +} + +void CopyOrMoveOperationDelegate::DidCreateDirectory( + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + const StatusCallback& callback, + base::File::Error error) { + if (!progress_callback_.is_null() && error == base::File::FILE_OK) { + progress_callback_.Run( + FileSystemOperation::END_COPY_ENTRY, src_url, dest_url, 0); + } + + callback.Run(error); +} + +void CopyOrMoveOperationDelegate::PostProcessDirectoryAfterGetMetadata( + const FileSystemURL& src_url, + const StatusCallback& callback, + base::File::Error error, + const base::File::Info& file_info) { + if (error != base::File::FILE_OK) { + // Ignore the error, and run post process which should run after TouchFile. + PostProcessDirectoryAfterTouchFile( + src_url, callback, base::File::FILE_OK); + return; + } + + operation_runner()->TouchFile( + CreateDestURL(src_url), base::Time::Now() /* last access */, + file_info.last_modified, + base::Bind( + &CopyOrMoveOperationDelegate::PostProcessDirectoryAfterTouchFile, + weak_factory_.GetWeakPtr(), src_url, callback)); +} + +void CopyOrMoveOperationDelegate::PostProcessDirectoryAfterTouchFile( + const FileSystemURL& src_url, + const StatusCallback& callback, + base::File::Error error) { + // Even if the TouchFile is failed, just ignore it. + + if (operation_type_ == OPERATION_COPY) { + callback.Run(base::File::FILE_OK); + return; + } + + DCHECK_EQ(OPERATION_MOVE, operation_type_); + + // All files and subdirectories in the directory should be moved here, + // so remove the source directory for finalizing move operation. + operation_runner()->Remove( + src_url, false /* recursive */, + base::Bind(&CopyOrMoveOperationDelegate::DidRemoveSourceForMove, + weak_factory_.GetWeakPtr(), callback)); +} + +void CopyOrMoveOperationDelegate::DidRemoveSourceForMove( + const StatusCallback& callback, + base::File::Error error) { + if (error == base::File::FILE_ERROR_NOT_FOUND) + error = base::File::FILE_OK; + callback.Run(error); +} + +void CopyOrMoveOperationDelegate::OnCopyFileProgress( + const FileSystemURL& src_url, int64 size) { + if (!progress_callback_.is_null()) { + progress_callback_.Run( + FileSystemOperation::PROGRESS, src_url, FileSystemURL(), size); + } +} + +FileSystemURL CopyOrMoveOperationDelegate::CreateDestURL( + const FileSystemURL& src_url) const { + DCHECK_EQ(src_root_.type(), src_url.type()); + DCHECK_EQ(src_root_.origin(), src_url.origin()); + + base::FilePath relative = dest_root_.virtual_path(); + src_root_.virtual_path().AppendRelativePath(src_url.virtual_path(), + &relative); + return file_system_context()->CreateCrackedFileSystemURL( + dest_root_.origin(), + dest_root_.mount_type(), + relative); +} + +} // namespace storage diff --git a/storage/browser/fileapi/copy_or_move_operation_delegate.h b/storage/browser/fileapi/copy_or_move_operation_delegate.h new file mode 100644 index 0000000..0b97d1c --- /dev/null +++ b/storage/browser/fileapi/copy_or_move_operation_delegate.h @@ -0,0 +1,162 @@ +// Copyright (c) 2013 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. + +#ifndef STORAGE_BROWSER_FILEAPI_COPY_OR_MOVE_OPERATION_DELEGATE_H_ +#define STORAGE_BROWSER_FILEAPI_COPY_OR_MOVE_OPERATION_DELEGATE_H_ + +#include <set> +#include <stack> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/time/time.h" +#include "storage/browser/fileapi/recursive_operation_delegate.h" + +namespace net { +class DrainableIOBuffer; +class IOBufferWithSize; +} + +namespace storage { +class FileStreamReader; +class ShareableFileReference; +} + +namespace storage { + +class CopyOrMoveFileValidator; +class FileStreamWriter; + +// A delegate class for recursive copy or move operations. +class CopyOrMoveOperationDelegate + : public RecursiveOperationDelegate { + public: + class CopyOrMoveImpl; + typedef FileSystemOperation::CopyProgressCallback CopyProgressCallback; + typedef FileSystemOperation::CopyOrMoveOption CopyOrMoveOption; + + enum OperationType { + OPERATION_COPY, + OPERATION_MOVE + }; + + // Helper to copy a file by reader and writer streams. + // Export for testing. + class STORAGE_EXPORT StreamCopyHelper { + public: + StreamCopyHelper( + scoped_ptr<storage::FileStreamReader> reader, + scoped_ptr<FileStreamWriter> writer, + bool need_flush, + int buffer_size, + const FileSystemOperation::CopyFileProgressCallback& + file_progress_callback, + const base::TimeDelta& min_progress_callback_invocation_span); + ~StreamCopyHelper(); + + void Run(const StatusCallback& callback); + + // Requests cancelling. After the cancelling is done, |callback| passed to + // Run will be called. + void Cancel(); + + private: + // Reads the content from the |reader_|. + void Read(const StatusCallback& callback); + void DidRead(const StatusCallback& callback, int result); + + // Writes the content in |buffer| to |writer_|. + void Write(const StatusCallback& callback, + scoped_refptr<net::DrainableIOBuffer> buffer); + void DidWrite(const StatusCallback& callback, + scoped_refptr<net::DrainableIOBuffer> buffer, int result); + + // Flushes the written content in |writer_|. + void Flush(const StatusCallback& callback, bool is_eof); + void DidFlush(const StatusCallback& callback, bool is_eof, int result); + + scoped_ptr<storage::FileStreamReader> reader_; + scoped_ptr<FileStreamWriter> writer_; + const bool need_flush_; + FileSystemOperation::CopyFileProgressCallback file_progress_callback_; + scoped_refptr<net::IOBufferWithSize> io_buffer_; + int64 num_copied_bytes_; + int64 previous_flush_offset_; + base::Time last_progress_callback_invocation_time_; + base::TimeDelta min_progress_callback_invocation_span_; + bool cancel_requested_; + base::WeakPtrFactory<StreamCopyHelper> weak_factory_; + DISALLOW_COPY_AND_ASSIGN(StreamCopyHelper); + }; + + CopyOrMoveOperationDelegate( + FileSystemContext* file_system_context, + const FileSystemURL& src_root, + const FileSystemURL& dest_root, + OperationType operation_type, + CopyOrMoveOption option, + const CopyProgressCallback& progress_callback, + const StatusCallback& callback); + virtual ~CopyOrMoveOperationDelegate(); + + // RecursiveOperationDelegate overrides: + virtual void Run() OVERRIDE; + virtual void RunRecursively() OVERRIDE; + virtual void ProcessFile(const FileSystemURL& url, + const StatusCallback& callback) OVERRIDE; + virtual void ProcessDirectory(const FileSystemURL& url, + const StatusCallback& callback) OVERRIDE; + virtual void PostProcessDirectory(const FileSystemURL& url, + const StatusCallback& callback) OVERRIDE; + + + protected: + virtual void OnCancel() OVERRIDE; + + private: + void DidCopyOrMoveFile(const FileSystemURL& src_url, + const FileSystemURL& dest_url, + const StatusCallback& callback, + CopyOrMoveImpl* impl, + base::File::Error error); + void DidTryRemoveDestRoot(const StatusCallback& callback, + base::File::Error error); + void ProcessDirectoryInternal(const FileSystemURL& src_url, + const FileSystemURL& dest_url, + const StatusCallback& callback); + void DidCreateDirectory(const FileSystemURL& src_url, + const FileSystemURL& dest_url, + const StatusCallback& callback, + base::File::Error error); + void PostProcessDirectoryAfterGetMetadata( + const FileSystemURL& src_url, + const StatusCallback& callback, + base::File::Error error, + const base::File::Info& file_info); + void PostProcessDirectoryAfterTouchFile(const FileSystemURL& src_url, + const StatusCallback& callback, + base::File::Error error); + void DidRemoveSourceForMove(const StatusCallback& callback, + base::File::Error error); + + void OnCopyFileProgress(const FileSystemURL& src_url, int64 size); + FileSystemURL CreateDestURL(const FileSystemURL& src_url) const; + + FileSystemURL src_root_; + FileSystemURL dest_root_; + bool same_file_system_; + OperationType operation_type_; + CopyOrMoveOption option_; + CopyProgressCallback progress_callback_; + StatusCallback callback_; + + std::set<CopyOrMoveImpl*> running_copy_set_; + base::WeakPtrFactory<CopyOrMoveOperationDelegate> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(CopyOrMoveOperationDelegate); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_COPY_OR_MOVE_OPERATION_DELEGATE_H_ diff --git a/storage/browser/fileapi/dragged_file_util.cc b/storage/browser/fileapi/dragged_file_util.cc new file mode 100644 index 0000000..1f15188 --- /dev/null +++ b/storage/browser/fileapi/dragged_file_util.cc @@ -0,0 +1,105 @@ +// Copyright 2013 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 "storage/browser/fileapi/dragged_file_util.h" + +#include <string> +#include <vector> + +#include "base/files/file_util.h" +#include "storage/browser/fileapi/file_system_context.h" +#include "storage/browser/fileapi/file_system_operation_context.h" +#include "storage/browser/fileapi/file_system_url.h" +#include "storage/browser/fileapi/isolated_context.h" +#include "storage/browser/fileapi/native_file_util.h" +#include "storage/common/blob/shareable_file_reference.h" + +namespace storage { + +typedef IsolatedContext::MountPointInfo FileInfo; + +namespace { + +// Simply enumerate each path from a given fileinfo set. +// Used to enumerate top-level paths of an isolated filesystem. +class SetFileEnumerator : public FileSystemFileUtil::AbstractFileEnumerator { + public: + explicit SetFileEnumerator(const std::vector<FileInfo>& files) + : files_(files) { + file_iter_ = files_.begin(); + } + virtual ~SetFileEnumerator() {} + + // AbstractFileEnumerator overrides. + virtual base::FilePath Next() OVERRIDE { + if (file_iter_ == files_.end()) + return base::FilePath(); + base::FilePath platform_file = (file_iter_++)->path; + NativeFileUtil::GetFileInfo(platform_file, &file_info_); + return platform_file; + } + virtual int64 Size() OVERRIDE { return file_info_.size; } + virtual bool IsDirectory() OVERRIDE { return file_info_.is_directory; } + virtual base::Time LastModifiedTime() OVERRIDE { + return file_info_.last_modified; + } + + private: + std::vector<FileInfo> files_; + std::vector<FileInfo>::const_iterator file_iter_; + base::File::Info file_info_; +}; + +} // namespace + +//------------------------------------------------------------------------- + +DraggedFileUtil::DraggedFileUtil() {} + +base::File::Error DraggedFileUtil::GetFileInfo( + FileSystemOperationContext* context, + const FileSystemURL& url, + base::File::Info* file_info, + base::FilePath* platform_path) { + DCHECK(file_info); + std::string filesystem_id; + DCHECK(url.is_valid()); + if (url.path().empty()) { + // The root directory case. + // For now we leave three time fields (modified/accessed/creation time) + // NULL as it is not really clear what to be set for this virtual directory. + // TODO(kinuko): Maybe we want to set the time when this filesystem is + // created (i.e. when the files/directories are dropped). + file_info->is_directory = true; + file_info->is_symbolic_link = false; + file_info->size = 0; + return base::File::FILE_OK; + } + base::File::Error error = + NativeFileUtil::GetFileInfo(url.path(), file_info); + if (base::IsLink(url.path()) && !base::FilePath().IsParent(url.path())) { + // Don't follow symlinks unless it's the one that are selected by the user. + return base::File::FILE_ERROR_NOT_FOUND; + } + if (error == base::File::FILE_OK) + *platform_path = url.path(); + return error; +} + +scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> + DraggedFileUtil::CreateFileEnumerator( + FileSystemOperationContext* context, + const FileSystemURL& root) { + DCHECK(root.is_valid()); + if (!root.path().empty()) + return LocalFileUtil::CreateFileEnumerator(context, root); + + // Root path case. + std::vector<FileInfo> toplevels; + IsolatedContext::GetInstance()->GetDraggedFileInfo( + root.filesystem_id(), &toplevels); + return scoped_ptr<AbstractFileEnumerator>(new SetFileEnumerator(toplevels)); +} + +} // namespace storage diff --git a/storage/browser/fileapi/dragged_file_util.h b/storage/browser/fileapi/dragged_file_util.h new file mode 100644 index 0000000..205e8ef --- /dev/null +++ b/storage/browser/fileapi/dragged_file_util.h @@ -0,0 +1,41 @@ +// Copyright 2013 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. + +#ifndef STORAGE_BROWSER_FILEAPI_DRAGGED_FILE_UTIL_H_ +#define STORAGE_BROWSER_FILEAPI_DRAGGED_FILE_UTIL_H_ + +#include "base/memory/scoped_ptr.h" +#include "storage/browser/fileapi/local_file_util.h" +#include "storage/browser/storage_browser_export.h" + +namespace storage { + +class FileSystemOperationContext; + +// Dragged file system is a specialized LocalFileUtil where read access to +// the virtual root directory (i.e. empty cracked path case) is allowed +// and single isolated context may be associated with multiple file paths. +class STORAGE_EXPORT_PRIVATE DraggedFileUtil + : public LocalFileUtil { + public: + DraggedFileUtil(); + virtual ~DraggedFileUtil() {} + + // FileSystemFileUtil overrides. + virtual base::File::Error GetFileInfo( + FileSystemOperationContext* context, + const FileSystemURL& url, + base::File::Info* file_info, + base::FilePath* platform_path) OVERRIDE; + virtual scoped_ptr<AbstractFileEnumerator> CreateFileEnumerator( + FileSystemOperationContext* context, + const FileSystemURL& root_url) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(DraggedFileUtil); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_DRAGGED_FILE_UTIL_H_ diff --git a/storage/browser/fileapi/dump_file_system.cc b/storage/browser/fileapi/dump_file_system.cc new file mode 100644 index 0000000..49a2fa3 --- /dev/null +++ b/storage/browser/fileapi/dump_file_system.cc @@ -0,0 +1,204 @@ +// Copyright (c) 2013 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. +// +// A tool to dump HTML5 filesystem from CUI. +// +// Usage: +// +// ./out/Release/dump_file_system [options] <filesystem dir> [origin]... +// +// If no origin is specified, this dumps all origins in the profile dir. +// +// Available options: +// +// -t : dumps temporary files instead of persistent. +// -s : dumps syncable files instead of persistent. +// -l : more information will be displayed. +// +// The format of -l option is: +// +// === ORIGIN origin_name origin_dir === +// file_name file_id file_size file_content_path +// ... +// +// where file_name has a trailing slash, file_size is the number of +// children, and file_content_path is empty if the file is a directory. +// + +#include <stdio.h> +#include <stdlib.h> + +#include <stack> +#include <string> +#include <utility> +#include <vector> + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/format_macros.h" +#include "base/strings/stringprintf.h" +#include "storage/browser/fileapi/obfuscated_file_util.h" +#include "storage/browser/fileapi/sandbox_directory_database.h" +#include "storage/browser/fileapi/sandbox_file_system_backend.h" +#include "storage/browser/fileapi/sandbox_origin_database.h" +#include "storage/common/fileapi/file_system_types.h" +#include "storage/common/fileapi/file_system_util.h" + +namespace { + +bool g_opt_long; +const char* g_opt_fs_type = "p"; + +void ShowMessageAndExit(const std::string& msg) { + fprintf(stderr, "%s\n", msg.c_str()); + exit(EXIT_FAILURE); +} + +void ShowUsageAndExit(const std::string& arg0) { + ShowMessageAndExit( + "Usage: " + arg0 + + " [-l] [-t] [-s] <filesystem dir> [origin]..."); +} + +} // namespace + +namespace storage { + +static void DumpDirectoryTree(const std::string& origin_name, + base::FilePath origin_dir) { + origin_dir = origin_dir.Append(g_opt_fs_type); + + printf("=== ORIGIN %s %s ===\n", + origin_name.c_str(), FilePathToString(origin_dir).c_str()); + + if (!base::DirectoryExists(origin_dir)) + return; + + SandboxDirectoryDatabase directory_db(origin_dir, NULL); + SandboxDirectoryDatabase::FileId root_id; + if (!directory_db.GetFileWithPath(StringToFilePath("/"), &root_id)) + return; + + std::stack<std::pair<SandboxDirectoryDatabase::FileId, + std::string> > paths; + paths.push(std::make_pair(root_id, "")); + while (!paths.empty()) { + SandboxDirectoryDatabase::FileId id = paths.top().first; + const std::string dirname = paths.top().second; + paths.pop(); + + SandboxDirectoryDatabase::FileInfo info; + if (!directory_db.GetFileInfo(id, &info)) { + ShowMessageAndExit(base::StringPrintf("GetFileInfo failed for %"PRId64, + id)); + } + + const std::string name = + dirname + "/" + FilePathToString(base::FilePath(info.name)); + std::vector<SandboxDirectoryDatabase::FileId> children; + if (info.is_directory()) { + if (!directory_db.ListChildren(id, &children)) { + ShowMessageAndExit(base::StringPrintf( + "ListChildren failed for %s (%"PRId64")", + info.name.c_str(), id)); + } + + for (size_t j = children.size(); j; j--) + paths.push(make_pair(children[j-1], name)); + } + + // +1 for the leading extra slash. + const char* display_name = name.c_str() + 1; + const char* directory_suffix = info.is_directory() ? "/" : ""; + if (g_opt_long) { + int64 size; + if (info.is_directory()) { + size = static_cast<int64>(children.size()); + } else { + base::GetFileSize(origin_dir.Append(info.data_path), &size); + } + // TODO(hamaji): Modification time? + printf("%s%s %"PRId64" %"PRId64" %s\n", + display_name, + directory_suffix, + id, + size, + FilePathToString(info.data_path).c_str()); + } else { + printf("%s%s\n", display_name, directory_suffix); + } + } +} + +static void DumpOrigin(const base::FilePath& file_system_dir, + const std::string& origin_name) { + SandboxOriginDatabase origin_db(file_system_dir, NULL); + base::FilePath origin_dir; + if (!origin_db.HasOriginPath(origin_name)) { + ShowMessageAndExit("Origin " + origin_name + " is not in " + + FilePathToString(file_system_dir)); + } + + if (!origin_db.GetPathForOrigin(origin_name, &origin_dir)) { + ShowMessageAndExit("Failed to get path of origin " + origin_name + + " in " + FilePathToString(file_system_dir)); + } + DumpDirectoryTree(origin_name, file_system_dir.Append(origin_dir)); +} + +static void DumpFileSystem(const base::FilePath& file_system_dir) { + SandboxOriginDatabase origin_db(file_system_dir, NULL); + std::vector<SandboxOriginDatabase::OriginRecord> origins; + origin_db.ListAllOrigins(&origins); + for (size_t i = 0; i < origins.size(); i++) { + const SandboxOriginDatabase::OriginRecord& origin = origins[i]; + DumpDirectoryTree(origin.origin, file_system_dir.Append(origin.path)); + puts(""); + } +} + +} // namespace storage + +int main(int argc, char* argv[]) { + const char* arg0 = argv[0]; + std::string username = "Default"; + while (true) { + if (argc < 2) + ShowUsageAndExit(arg0); + + if (std::string(argv[1]) == "-l") { + g_opt_long = true; + argc--; + argv++; + } else if (std::string(argv[1]) == "-t") { + g_opt_fs_type = "t"; + argc--; + argv++; + } else if (std::string(argv[1]) == "-s") { + g_opt_fs_type = "s"; + argc--; + argv++; + } else { + break; + } + } + + if (argc < 2) + ShowUsageAndExit(arg0); + + const base::FilePath file_system_dir = storage::StringToFilePath(argv[1]); + if (!base::DirectoryExists(file_system_dir)) { + ShowMessageAndExit(storage::FilePathToString(file_system_dir) + + " is not a filesystem directory"); + } + + if (argc == 2) { + storage::DumpFileSystem(file_system_dir); + } else { + for (int i = 2; i < argc; i++) { + storage::DumpOrigin(file_system_dir, argv[i]); + } + } + return 0; +} diff --git a/storage/browser/fileapi/external_mount_points.cc b/storage/browser/fileapi/external_mount_points.cc new file mode 100644 index 0000000..baa0c49 --- /dev/null +++ b/storage/browser/fileapi/external_mount_points.cc @@ -0,0 +1,357 @@ +// Copyright (c) 2013 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 "storage/browser/fileapi/external_mount_points.h" + +#include "base/files/file_path.h" +#include "base/lazy_instance.h" +#include "base/path_service.h" +#include "base/stl_util.h" +#include "storage/browser/fileapi/file_system_url.h" + +namespace { + +// Normalizes file path so it has normalized separators and ends with exactly +// one separator. Paths have to be normalized this way for use in +// GetVirtualPath method. Separators cannot be completely stripped, or +// GetVirtualPath could not working in some edge cases. +// For example, /a/b/c(1)/d would be erroneously resolved as c/d if the +// following mount points were registered: "/a/b/c", "/a/b/c(1)". (Note: +// "/a/b/c" < "/a/b/c(1)" < "/a/b/c/"). +base::FilePath NormalizeFilePath(const base::FilePath& path) { + if (path.empty()) + return path; + + base::FilePath::StringType path_str = path.StripTrailingSeparators().value(); + if (!base::FilePath::IsSeparator(path_str[path_str.length() - 1])) + path_str.append(FILE_PATH_LITERAL("/")); + + return base::FilePath(path_str).NormalizePathSeparators(); +} + +bool IsOverlappingMountPathForbidden(storage::FileSystemType type) { + return type != storage::kFileSystemTypeNativeMedia && + type != storage::kFileSystemTypeDeviceMedia; +} + +// Wrapper around ref-counted ExternalMountPoints that will be used to lazily +// create and initialize LazyInstance system ExternalMountPoints. +class SystemMountPointsLazyWrapper { + public: + SystemMountPointsLazyWrapper() + : system_mount_points_(storage::ExternalMountPoints::CreateRefCounted()) { + } + + ~SystemMountPointsLazyWrapper() {} + + storage::ExternalMountPoints* get() { return system_mount_points_.get(); } + + private: + scoped_refptr<storage::ExternalMountPoints> system_mount_points_; +}; + +base::LazyInstance<SystemMountPointsLazyWrapper>::Leaky + g_external_mount_points = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +namespace storage { + +class ExternalMountPoints::Instance { + public: + Instance(FileSystemType type, + const base::FilePath& path, + const FileSystemMountOption& mount_option) + : type_(type), + path_(path.StripTrailingSeparators()), + mount_option_(mount_option) {} + ~Instance() {} + + FileSystemType type() const { return type_; } + const base::FilePath& path() const { return path_; } + const FileSystemMountOption& mount_option() const { return mount_option_; } + + private: + const FileSystemType type_; + const base::FilePath path_; + const FileSystemMountOption mount_option_; + + DISALLOW_COPY_AND_ASSIGN(Instance); +}; + +//-------------------------------------------------------------------------- + +// static +ExternalMountPoints* ExternalMountPoints::GetSystemInstance() { + return g_external_mount_points.Pointer()->get(); +} + +// static +scoped_refptr<ExternalMountPoints> ExternalMountPoints::CreateRefCounted() { + return new ExternalMountPoints(); +} + +bool ExternalMountPoints::RegisterFileSystem( + const std::string& mount_name, + FileSystemType type, + const FileSystemMountOption& mount_option, + const base::FilePath& path_in) { + // COPY_SYNC_OPTION_SYNC is only applicable to native local file system. + DCHECK(type == kFileSystemTypeNativeLocal || + mount_option.copy_sync_option() != COPY_SYNC_OPTION_SYNC); + + base::AutoLock locker(lock_); + + base::FilePath path = NormalizeFilePath(path_in); + if (!ValidateNewMountPoint(mount_name, type, path)) + return false; + + instance_map_[mount_name] = new Instance(type, path, mount_option); + if (!path.empty() && IsOverlappingMountPathForbidden(type)) + path_to_name_map_.insert(std::make_pair(path, mount_name)); + return true; +} + +bool ExternalMountPoints::HandlesFileSystemMountType( + FileSystemType type) const { + return type == kFileSystemTypeExternal || + type == kFileSystemTypeNativeForPlatformApp; +} + +bool ExternalMountPoints::RevokeFileSystem(const std::string& mount_name) { + base::AutoLock locker(lock_); + NameToInstance::iterator found = instance_map_.find(mount_name); + if (found == instance_map_.end()) + return false; + Instance* instance = found->second; + if (IsOverlappingMountPathForbidden(instance->type())) + path_to_name_map_.erase(NormalizeFilePath(instance->path())); + delete found->second; + instance_map_.erase(found); + return true; +} + +bool ExternalMountPoints::GetRegisteredPath( + const std::string& filesystem_id, base::FilePath* path) const { + DCHECK(path); + base::AutoLock locker(lock_); + NameToInstance::const_iterator found = instance_map_.find(filesystem_id); + if (found == instance_map_.end()) + return false; + *path = found->second->path(); + return true; +} + +bool ExternalMountPoints::CrackVirtualPath( + const base::FilePath& virtual_path, + std::string* mount_name, + FileSystemType* type, + std::string* cracked_id, + base::FilePath* path, + FileSystemMountOption* mount_option) const { + DCHECK(mount_name); + DCHECK(path); + + // The path should not contain any '..' references. + if (virtual_path.ReferencesParent()) + return false; + + // The virtual_path should comprise of <mount_name> and <relative_path> parts. + std::vector<base::FilePath::StringType> components; + virtual_path.GetComponents(&components); + if (components.size() < 1) + return false; + + std::vector<base::FilePath::StringType>::iterator component_iter = + components.begin(); + std::string maybe_mount_name = + base::FilePath(*component_iter++).MaybeAsASCII(); + if (maybe_mount_name.empty()) + return false; + + base::FilePath cracked_path; + { + base::AutoLock locker(lock_); + NameToInstance::const_iterator found_instance = + instance_map_.find(maybe_mount_name); + if (found_instance == instance_map_.end()) + return false; + + *mount_name = maybe_mount_name; + const Instance* instance = found_instance->second; + if (type) + *type = instance->type(); + cracked_path = instance->path(); + *mount_option = instance->mount_option(); + } + + for (; component_iter != components.end(); ++component_iter) + cracked_path = cracked_path.Append(*component_iter); + *path = cracked_path; + return true; +} + +FileSystemURL ExternalMountPoints::CrackURL(const GURL& url) const { + FileSystemURL filesystem_url = FileSystemURL(url); + if (!filesystem_url.is_valid()) + return FileSystemURL(); + return CrackFileSystemURL(filesystem_url); +} + +FileSystemURL ExternalMountPoints::CreateCrackedFileSystemURL( + const GURL& origin, + FileSystemType type, + const base::FilePath& path) const { + return CrackFileSystemURL(FileSystemURL(origin, type, path)); +} + +void ExternalMountPoints::AddMountPointInfosTo( + std::vector<MountPointInfo>* mount_points) const { + base::AutoLock locker(lock_); + DCHECK(mount_points); + for (NameToInstance::const_iterator iter = instance_map_.begin(); + iter != instance_map_.end(); ++iter) { + mount_points->push_back(MountPointInfo(iter->first, iter->second->path())); + } +} + +bool ExternalMountPoints::GetVirtualPath(const base::FilePath& path_in, + base::FilePath* virtual_path) const { + DCHECK(virtual_path); + + base::AutoLock locker(lock_); + + base::FilePath path = NormalizeFilePath(path_in); + std::map<base::FilePath, std::string>::const_reverse_iterator iter( + path_to_name_map_.upper_bound(path)); + if (iter == path_to_name_map_.rend()) + return false; + + *virtual_path = CreateVirtualRootPath(iter->second); + if (iter->first == path) + return true; + return iter->first.AppendRelativePath(path, virtual_path); +} + +base::FilePath ExternalMountPoints::CreateVirtualRootPath( + const std::string& mount_name) const { + return base::FilePath().AppendASCII(mount_name); +} + +FileSystemURL ExternalMountPoints::CreateExternalFileSystemURL( + const GURL& origin, + const std::string& mount_name, + const base::FilePath& path) const { + return CreateCrackedFileSystemURL( + origin, + storage::kFileSystemTypeExternal, + // Avoid using FilePath::Append as path may be an absolute path. + base::FilePath(CreateVirtualRootPath(mount_name).value() + + base::FilePath::kSeparators[0] + path.value())); +} + +void ExternalMountPoints::RevokeAllFileSystems() { + NameToInstance instance_map_copy; + { + base::AutoLock locker(lock_); + instance_map_copy = instance_map_; + instance_map_.clear(); + path_to_name_map_.clear(); + } + STLDeleteContainerPairSecondPointers(instance_map_copy.begin(), + instance_map_copy.end()); +} + +ExternalMountPoints::ExternalMountPoints() {} + +ExternalMountPoints::~ExternalMountPoints() { + STLDeleteContainerPairSecondPointers(instance_map_.begin(), + instance_map_.end()); +} + +FileSystemURL ExternalMountPoints::CrackFileSystemURL( + const FileSystemURL& url) const { + if (!HandlesFileSystemMountType(url.type())) + return FileSystemURL(); + + base::FilePath virtual_path = url.path(); + if (url.type() == kFileSystemTypeNativeForPlatformApp) { +#if defined(OS_CHROMEOS) + // On Chrome OS, find a mount point and virtual path for the external fs. + if (!GetVirtualPath(url.path(), &virtual_path)) + return FileSystemURL(); +#else + // On other OS, it is simply a native local path. + return FileSystemURL( + url.origin(), url.mount_type(), url.virtual_path(), + url.mount_filesystem_id(), kFileSystemTypeNativeLocal, + url.path(), url.filesystem_id(), url.mount_option()); +#endif + } + + std::string mount_name; + FileSystemType cracked_type; + std::string cracked_id; + base::FilePath cracked_path; + FileSystemMountOption cracked_mount_option; + + if (!CrackVirtualPath(virtual_path, &mount_name, &cracked_type, + &cracked_id, &cracked_path, &cracked_mount_option)) { + return FileSystemURL(); + } + + return FileSystemURL( + url.origin(), url.mount_type(), url.virtual_path(), + !url.filesystem_id().empty() ? url.filesystem_id() : mount_name, + cracked_type, cracked_path, + cracked_id.empty() ? mount_name : cracked_id, cracked_mount_option); +} + +bool ExternalMountPoints::ValidateNewMountPoint(const std::string& mount_name, + FileSystemType type, + const base::FilePath& path) { + lock_.AssertAcquired(); + + // Mount name must not be empty. + if (mount_name.empty()) + return false; + + // Verify there is no registered mount point with the same name. + NameToInstance::iterator found = instance_map_.find(mount_name); + if (found != instance_map_.end()) + return false; + + // Allow empty paths. + if (path.empty()) + return true; + + // Verify path is legal. + if (path.ReferencesParent() || !path.IsAbsolute()) + return false; + + if (IsOverlappingMountPathForbidden(type)) { + // Check there the new path does not overlap with one of the existing ones. + std::map<base::FilePath, std::string>::reverse_iterator potential_parent( + path_to_name_map_.upper_bound(path)); + if (potential_parent != path_to_name_map_.rend()) { + if (potential_parent->first == path || + potential_parent->first.IsParent(path)) { + return false; + } + } + + std::map<base::FilePath, std::string>::iterator potential_child = + path_to_name_map_.upper_bound(path); + if (potential_child != path_to_name_map_.end()) { + if (potential_child->first == path || + path.IsParent(potential_child->first)) { + return false; + } + } + } + + return true; +} + +} // namespace storage diff --git a/storage/browser/fileapi/external_mount_points.h b/storage/browser/fileapi/external_mount_points.h new file mode 100644 index 0000000..26468c5 --- /dev/null +++ b/storage/browser/fileapi/external_mount_points.h @@ -0,0 +1,159 @@ +// Copyright (c) 2013 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. + +#ifndef STORAGE_BROWSER_FILEAPI_EXTERNAL_MOUNT_POINTS_H_ +#define STORAGE_BROWSER_FILEAPI_EXTERNAL_MOUNT_POINTS_H_ + +#include <map> +#include <string> +#include <vector> + +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "storage/browser/fileapi/mount_points.h" +#include "storage/browser/storage_browser_export.h" +#include "storage/common/fileapi/file_system_mount_option.h" +#include "storage/common/fileapi/file_system_types.h" + +namespace base { +class FilePath; +} + +namespace storage { + +class FileSystemURL; + +// Manages external filesystem namespaces that are identified by 'mount name' +// and are persisted until RevokeFileSystem is called. +// Files in an external filesystem are identified by a filesystem URL like: +// +// filesystem:<origin>/external/<mount_name>/relative/path +// +class STORAGE_EXPORT ExternalMountPoints + : public base::RefCountedThreadSafe<ExternalMountPoints>, + public MountPoints { + public: + static ExternalMountPoints* GetSystemInstance(); + static scoped_refptr<ExternalMountPoints> CreateRefCounted(); + + // Registers a new named external filesystem. + // The |path| is registered as the root path of the mount point which + // is identified by a URL "filesystem:.../external/mount_name". + // + // For example, if the path "/media/removable" is registered with + // the mount_name "removable", a filesystem URL like + // "filesystem:.../external/removable/a/b" will be resolved as + // "/media/removable/a/b". + // + // The |mount_name| should NOT contain a path separator '/'. + // Returns false if the given name is already registered. + // + // Overlapping mount points in a single MountPoints instance are not allowed. + // Adding mount point whose path overlaps with an existing mount point will + // fail except for media galleries, which do not count toward registered + // paths for overlap calculation. + // + // If not empty, |path| must be absolute. It is allowed for the path to be + // empty, but |GetVirtualPath| will not work for those mount points. + // + // An external file system registered by this method can be revoked + // by calling RevokeFileSystem with |mount_name|. + bool RegisterFileSystem(const std::string& mount_name, + FileSystemType type, + const FileSystemMountOption& mount_option, + const base::FilePath& path); + + // MountPoints overrides. + virtual bool HandlesFileSystemMountType(FileSystemType type) const OVERRIDE; + virtual bool RevokeFileSystem(const std::string& mount_name) OVERRIDE; + virtual bool GetRegisteredPath(const std::string& mount_name, + base::FilePath* path) const OVERRIDE; + virtual bool CrackVirtualPath( + const base::FilePath& virtual_path, + std::string* mount_name, + FileSystemType* type, + std::string* cracked_id, + base::FilePath* path, + FileSystemMountOption* mount_option) const OVERRIDE; + virtual FileSystemURL CrackURL(const GURL& url) const OVERRIDE; + virtual FileSystemURL CreateCrackedFileSystemURL( + const GURL& origin, + FileSystemType type, + const base::FilePath& path) const OVERRIDE; + + // Returns a list of registered MountPointInfos (of <mount_name, path>). + void AddMountPointInfosTo(std::vector<MountPointInfo>* mount_points) const; + + // Converts a path on a registered file system to virtual path relative to the + // file system root. E.g. if 'Downloads' file system is mapped to + // '/usr/local/home/Downloads', and |absolute| path is set to + // '/usr/local/home/Downloads/foo', the method will set |virtual_path| to + // 'Downloads/foo'. + // Returns false if the path cannot be resolved (e.g. if the path is not + // part of any registered filesystem). + // + // Media gallery type file systems do not count for this calculation. i.e. + // if only a media gallery is registered for the path, false will be returned. + // If a media gallery and another file system are registered for related + // paths, only the other registration is taken into account. + // + // Returned virtual_path will have normalized path separators. + bool GetVirtualPath(const base::FilePath& absolute_path, + base::FilePath* virtual_path) const; + + // Returns the virtual root path that looks like /<mount_name>. + base::FilePath CreateVirtualRootPath(const std::string& mount_name) const; + + FileSystemURL CreateExternalFileSystemURL( + const GURL& origin, + const std::string& mount_name, + const base::FilePath& path) const; + + // Revoke all registered filesystems. Used only by testing (for clean-ups). + void RevokeAllFileSystems(); + + private: + friend class base::RefCountedThreadSafe<ExternalMountPoints>; + + // Represents each file system instance (defined in the .cc). + class Instance; + + typedef std::map<std::string, Instance*> NameToInstance; + + // Reverse map from registered path to its corresponding mount name. + typedef std::map<base::FilePath, std::string> PathToName; + + // Use |GetSystemInstance| of |CreateRefCounted| to get an instance. + ExternalMountPoints(); + virtual ~ExternalMountPoints(); + + // MountPoint overrides. + virtual FileSystemURL CrackFileSystemURL( + const FileSystemURL& url) const OVERRIDE; + + // Performs sanity checks on the new mount point. + // Checks the following: + // - there is no registered mount point with mount_name + // - path does not contain a reference to a parent + // - path is absolute + // - path does not overlap with an existing mount point path unless it is a + // media gallery type. + // + // |lock_| should be taken before calling this method. + bool ValidateNewMountPoint(const std::string& mount_name, + FileSystemType type, + const base::FilePath& path); + + // This lock needs to be obtained when accessing the instance_map_. + mutable base::Lock lock_; + + NameToInstance instance_map_; + PathToName path_to_name_map_; + + DISALLOW_COPY_AND_ASSIGN(ExternalMountPoints); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_EXTERNAL_MOUNT_POINTS_H_ diff --git a/storage/browser/fileapi/file_observers.h b/storage/browser/fileapi/file_observers.h new file mode 100644 index 0000000..7a07193 --- /dev/null +++ b/storage/browser/fileapi/file_observers.h @@ -0,0 +1,83 @@ +// 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. + +#ifndef STORAGE_BROWSER_FILEAPI_FILE_OBSERVERS_H_ +#define STORAGE_BROWSER_FILEAPI_FILE_OBSERVERS_H_ + +#include "base/basictypes.h" +#include "storage/browser/storage_browser_export.h" + +// TODO(kinuko): Split this file into per-observer multiple files. + +namespace storage { + +class FileSystemURL; + +// An abstract interface to observe update operations. +// +// OnStartUpdate and OnEndUpdate are called once for each target url +// before and after following operations regardless of whether the operation +// is made recursively or not (i.e. StartUpdate() will be called only once +// for destination url regardless of whether it is recursive copy or not): +// CreateFile(), CreateDirectory(), +// Copy() (destination only), +// Move() (both for source and destination), +// Remove(), Write(), Truncate(), TouchFile() +// +// OnUpdate() is called each time the |url| is updated but works only for +// sandboxed files (where usage is tracked). +class STORAGE_EXPORT FileUpdateObserver { + public: + FileUpdateObserver() {} + virtual ~FileUpdateObserver() {} + + virtual void OnStartUpdate(const FileSystemURL& url) = 0; + virtual void OnUpdate(const FileSystemURL& url, int64 delta) = 0; + virtual void OnEndUpdate(const FileSystemURL& url) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(FileUpdateObserver); +}; + +// An abstract interface to observe file access. +// OnAccess is called whenever an operation reads file contents or metadata. +// (It is called only once per operation regardless of whether the operation +// is recursive or not) +class STORAGE_EXPORT FileAccessObserver { + public: + FileAccessObserver() {} + virtual ~FileAccessObserver() {} + + virtual void OnAccess(const FileSystemURL& url) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(FileAccessObserver); +}; + +// An abstract interface to observe file changes. +// Each method of this class is called once per file/directory is created, +// removed or modified. For recursive operations each method is called for +// each subdirectory/subfile. Currently ChangeObserver is only supported +// by the local sandbox file system. +class STORAGE_EXPORT FileChangeObserver { + public: + FileChangeObserver() {} + virtual ~FileChangeObserver() {} + + virtual void OnCreateFile(const FileSystemURL& url) = 0; + virtual void OnCreateFileFrom(const FileSystemURL& url, + const FileSystemURL& src) = 0; + virtual void OnRemoveFile(const FileSystemURL& url) = 0; + virtual void OnModifyFile(const FileSystemURL& url) = 0; + + virtual void OnCreateDirectory(const FileSystemURL& url) = 0; + virtual void OnRemoveDirectory(const FileSystemURL& url) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(FileChangeObserver); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_FILE_OBSERVERS_H_ diff --git a/storage/browser/fileapi/file_permission_policy.h b/storage/browser/fileapi/file_permission_policy.h new file mode 100644 index 0000000..8580954 --- /dev/null +++ b/storage/browser/fileapi/file_permission_policy.h @@ -0,0 +1,28 @@ +// Copyright (c) 2013 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. + +#ifndef STORAGE_BROWSER_FILEAPI_FILE_PERMISSION_POLICY_H_ +#define STORAGE_BROWSER_FILEAPI_FILE_PERMISSION_POLICY_H_ + +#include "storage/browser/storage_browser_export.h" + +namespace storage { + +enum FilePermissionPolicy { + // Any access should be always denied. + FILE_PERMISSION_ALWAYS_DENY = 0x0, + + // Access is sandboxed, no extra permission check is necessary. + FILE_PERMISSION_SANDBOX = 1 << 0, + + // Access should be restricted to read-only. + FILE_PERMISSION_READ_ONLY = 1 << 1, + + // Access should be examined by per-file permission policy. + FILE_PERMISSION_USE_FILE_PERMISSION = 1 << 2, +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_FILE_PERMISSION_POLICY_H_ diff --git a/storage/browser/fileapi/file_stream_writer.h b/storage/browser/fileapi/file_stream_writer.h new file mode 100644 index 0000000..ec59ec0 --- /dev/null +++ b/storage/browser/fileapi/file_stream_writer.h @@ -0,0 +1,87 @@ +// 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. + +#ifndef STORAGE_BROWSER_FILEAPI_FILE_STREAM_WRITER_H_ +#define STORAGE_BROWSER_FILEAPI_FILE_STREAM_WRITER_H_ + +#include "base/basictypes.h" +#include "net/base/completion_callback.h" +#include "storage/browser/storage_browser_export.h" + +namespace base { +class FilePath; +class TaskRunner; +} + +namespace net { +class IOBuffer; +} + +namespace storage { + +// A generic interface for writing to a file-like object. +class FileStreamWriter { + public: + enum OpenOrCreate { OPEN_EXISTING_FILE, CREATE_NEW_FILE }; + + // Creates a writer for the existing file in the path |file_path| starting + // from |initial_offset|. Uses |task_runner| for async file operations. + STORAGE_EXPORT static FileStreamWriter* CreateForLocalFile( + base::TaskRunner* task_runner, + const base::FilePath& file_path, + int64 initial_offset, + OpenOrCreate open_or_create); + + // Closes the file. If there's an in-flight operation, it is canceled (i.e., + // the callback function associated with the operation is not called). + virtual ~FileStreamWriter() {} + + // Writes to the current cursor position asynchronously. + // + // Up to buf_len bytes will be written. (In other words, partial + // writes are allowed.) If the write completed synchronously, it returns + // the number of bytes written. If the operation could not be performed, it + // returns an error code. Otherwise, net::ERR_IO_PENDING is returned, and the + // callback will be run on the thread where Write() was called when the write + // has completed. + // + // This errors out (either synchronously or via callback) with: + // net::ERR_FILE_NOT_FOUND: When the target file is not found. + // net::ERR_ACCESS_DENIED: When the target file is a directory or + // the writer has no permission on the file. + // net::ERR_FILE_NO_SPACE: When the write will result in out of quota + // or there is not enough room left on the disk. + // + // It is invalid to call Write while there is an in-flight async operation. + virtual int Write(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) = 0; + + // Cancels an in-flight async operation. + // + // If the cancel is finished synchronously, it returns net::OK. If the + // cancel could not be performed, it returns an error code. Especially when + // there is no in-flight operation, net::ERR_UNEXPECTED is returned. + // Otherwise, net::ERR_IO_PENDING is returned, and the callback will be run on + // the thread where Cancel() was called when the cancel has completed. It is + // invalid to call Cancel() more than once on the same async operation. + // + // In either case, the callback function passed to the in-flight async + // operation is dismissed immediately when Cancel() is called, and thus + // will never be called. + virtual int Cancel(const net::CompletionCallback& callback) = 0; + + // Flushes the data written so far. + // + // If the flush finished synchronously, it return net::OK. If the flush could + // not be performed, it returns an error code. Otherwise, net::ERR_IO_PENDING + // is returned, and the callback will be run on the thread where Flush() was + // called when the flush has completed. + // + // It is invalid to call Flush while there is an in-flight async operation. + virtual int Flush(const net::CompletionCallback& callback) = 0; +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_FILE_STREAM_WRITER_H_ diff --git a/storage/browser/fileapi/file_system_backend.h b/storage/browser/fileapi/file_system_backend.h new file mode 100644 index 0000000..8f455d9 --- /dev/null +++ b/storage/browser/fileapi/file_system_backend.h @@ -0,0 +1,169 @@ +// 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. + +#ifndef STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_BACKEND_H_ +#define STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_BACKEND_H_ + +#include <string> +#include <vector> + +#include "base/callback_forward.h" +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "storage/browser/fileapi/file_permission_policy.h" +#include "storage/browser/fileapi/open_file_system_mode.h" +#include "storage/browser/storage_browser_export.h" +#include "storage/common/fileapi/file_system_types.h" + +class GURL; + +namespace storage { + +class AsyncFileUtil; +class CopyOrMoveFileValidatorFactory; +class FileSystemURL; +class FileStreamReader; +class FileStreamWriter; +class FileSystemContext; +class FileSystemFileUtil; +class FileSystemOperation; +class FileSystemQuotaUtil; +class WatcherManager; + +// Callback to take GURL. +typedef base::Callback<void(const GURL& url)> URLCallback; + +// An interface for defining a file system backend. +// +// NOTE: when you implement a new FileSystemBackend for your own +// FileSystem module, please contact to kinuko@chromium.org. +// +class STORAGE_EXPORT FileSystemBackend { + public: + // Callback for InitializeFileSystem. + typedef base::Callback<void(const GURL& root_url, + const std::string& name, + base::File::Error error)> + OpenFileSystemCallback; + virtual ~FileSystemBackend() {} + + // Returns true if this filesystem backend can handle |type|. + // One filesystem backend may be able to handle multiple filesystem types. + virtual bool CanHandleType(FileSystemType type) const = 0; + + // This method is called right after the backend is registered in the + // FileSystemContext and before any other methods are called. Each backend can + // do additional initialization which depends on FileSystemContext here. + virtual void Initialize(FileSystemContext* context) = 0; + + // Resolves the filesystem root URL and the name for the given |url|. + // This verifies if it is allowed to request (or create) the filesystem and if + // it can access (or create) the root directory. + // If |mode| is CREATE_IF_NONEXISTENT calling this may also create the root + // directory (and/or related database entries etc) for the filesystem if it + // doesn't exist. + virtual void ResolveURL(const FileSystemURL& url, + OpenFileSystemMode mode, + const OpenFileSystemCallback& callback) = 0; + + // Returns the specialized AsyncFileUtil for this backend. + virtual AsyncFileUtil* GetAsyncFileUtil(FileSystemType type) = 0; + + // Returns the specialized WatcherManager for this backend. + virtual WatcherManager* GetWatcherManager(FileSystemType type) = 0; + + // Returns the specialized CopyOrMoveFileValidatorFactory for this backend + // and |type|. If |error_code| is File::FILE_OK and the result is NULL, + // then no validator is required. + virtual CopyOrMoveFileValidatorFactory* GetCopyOrMoveFileValidatorFactory( + FileSystemType type, base::File::Error* error_code) = 0; + + // Returns a new instance of the specialized FileSystemOperation for this + // backend based on the given triplet of |origin_url|, |file_system_type| + // and |virtual_path|. On failure to create a file system operation, set + // |error_code| correspondingly. + // This method is usually dispatched by + // FileSystemContext::CreateFileSystemOperation. + virtual FileSystemOperation* CreateFileSystemOperation( + const FileSystemURL& url, + FileSystemContext* context, + base::File::Error* error_code) const = 0; + + // Returns true if Blobs accessing |url| should use FileStreamReader. + // If false, Blobs are accessed using a snapshot file by calling + // AsyncFileUtil::CreateSnapshotFile. + virtual bool SupportsStreaming(const FileSystemURL& url) const = 0; + + // Returns true if specified |type| of filesystem can handle Copy() + // of the files in the same file system instead of streaming + // read/write implementation. + virtual bool HasInplaceCopyImplementation(FileSystemType type) const = 0; + + // Creates a new file stream reader for a given filesystem URL |url| with an + // offset |offset|. |expected_modification_time| specifies the expected last + // modification if the value is non-null, the reader will check the underlying + // file's actual modification time to see if the file has been modified, and + // if it does any succeeding read operations should fail with + // ERR_UPLOAD_FILE_CHANGED error. + // This method itself does *not* check if the given path exists and is a + // regular file. + // The |length| argument says how many bytes are going to be read using the + // instance of the file stream reader. If unknown, then equal to -1. + virtual scoped_ptr<storage::FileStreamReader> CreateFileStreamReader( + const FileSystemURL& url, + int64 offset, + const base::Time& expected_modification_time, + FileSystemContext* context) const = 0; + + // Creates a new file stream writer for a given filesystem URL |url| with an + // offset |offset|. + // This method itself does *not* check if the given path exists and is a + // regular file. + virtual scoped_ptr<FileStreamWriter> CreateFileStreamWriter( + const FileSystemURL& url, + int64 offset, + FileSystemContext* context) const = 0; + + // Returns the specialized FileSystemQuotaUtil for this backend. + // This could return NULL if this backend does not support quota. + virtual FileSystemQuotaUtil* GetQuotaUtil() = 0; +}; + +// An interface to control external file system access permissions. +// TODO(satorux): Move this out of 'storage/browser/fileapi'. crbug.com/257279 +class ExternalFileSystemBackend : public FileSystemBackend { + public: + // Returns true if |url| is allowed to be accessed. + // This is supposed to perform ExternalFileSystem-specific security + // checks. + virtual bool IsAccessAllowed(const storage::FileSystemURL& url) const = 0; + // Returns the list of top level directories that are exposed by this + // provider. This list is used to set appropriate child process file access + // permissions. + virtual std::vector<base::FilePath> GetRootDirectories() const = 0; + // Grants access to all external file system from extension identified with + // |extension_id|. + virtual void GrantFullAccessToExtension(const std::string& extension_id) = 0; + // Grants access to |virtual_path| from |origin_url|. + virtual void GrantFileAccessToExtension( + const std::string& extension_id, + const base::FilePath& virtual_path) = 0; + // Revokes file access from extension identified with |extension_id|. + virtual void RevokeAccessForExtension( + const std::string& extension_id) = 0; + // Gets virtual path by known filesystem path. Returns false when filesystem + // path is not exposed by this provider. + virtual bool GetVirtualPath(const base::FilePath& file_system_path, + base::FilePath* virtual_path) = 0; + // Gets a redirect URL for contents. e.g. Google Drive URL for hosted + // documents. Returns empty URL if the entry does not have the redirect URL. + virtual void GetRedirectURLForContents( + const storage::FileSystemURL& url, + const storage::URLCallback& callback) = 0; +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_BACKEND_H_ diff --git a/storage/browser/fileapi/file_system_context.cc b/storage/browser/fileapi/file_system_context.cc new file mode 100644 index 0000000..f42e11a --- /dev/null +++ b/storage/browser/fileapi/file_system_context.cc @@ -0,0 +1,644 @@ +// 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 "storage/browser/fileapi/file_system_context.h" + +#include "base/bind.h" +#include "base/single_thread_task_runner.h" +#include "base/stl_util.h" +#include "base/task_runner_util.h" +#include "net/url_request/url_request.h" +#include "storage/browser/blob/file_stream_reader.h" +#include "storage/browser/fileapi/copy_or_move_file_validator.h" +#include "storage/browser/fileapi/external_mount_points.h" +#include "storage/browser/fileapi/file_permission_policy.h" +#include "storage/browser/fileapi/file_stream_writer.h" +#include "storage/browser/fileapi/file_system_file_util.h" +#include "storage/browser/fileapi/file_system_operation.h" +#include "storage/browser/fileapi/file_system_operation_runner.h" +#include "storage/browser/fileapi/file_system_options.h" +#include "storage/browser/fileapi/file_system_quota_client.h" +#include "storage/browser/fileapi/file_system_url.h" +#include "storage/browser/fileapi/isolated_context.h" +#include "storage/browser/fileapi/isolated_file_system_backend.h" +#include "storage/browser/fileapi/mount_points.h" +#include "storage/browser/fileapi/quota/quota_reservation.h" +#include "storage/browser/fileapi/sandbox_file_system_backend.h" +#include "storage/browser/quota/quota_manager_proxy.h" +#include "storage/browser/quota/special_storage_policy.h" +#include "storage/common/fileapi/file_system_info.h" +#include "storage/common/fileapi/file_system_util.h" +#include "url/gurl.h" + +using storage::QuotaClient; + +namespace storage { + +namespace { + +QuotaClient* CreateQuotaClient( + FileSystemContext* context, + bool is_incognito) { + return new FileSystemQuotaClient(context, is_incognito); +} + + +void DidGetMetadataForResolveURL( + const base::FilePath& path, + const FileSystemContext::ResolveURLCallback& callback, + const FileSystemInfo& info, + base::File::Error error, + const base::File::Info& file_info) { + if (error != base::File::FILE_OK) { + if (error == base::File::FILE_ERROR_NOT_FOUND) { + callback.Run(base::File::FILE_OK, info, path, + FileSystemContext::RESOLVED_ENTRY_NOT_FOUND); + } else { + callback.Run(error, FileSystemInfo(), base::FilePath(), + FileSystemContext::RESOLVED_ENTRY_NOT_FOUND); + } + return; + } + callback.Run(error, info, path, file_info.is_directory ? + FileSystemContext::RESOLVED_ENTRY_DIRECTORY : + FileSystemContext::RESOLVED_ENTRY_FILE); +} + +void RelayResolveURLCallback( + scoped_refptr<base::MessageLoopProxy> message_loop, + const FileSystemContext::ResolveURLCallback& callback, + base::File::Error result, + const FileSystemInfo& info, + const base::FilePath& file_path, + FileSystemContext::ResolvedEntryType type) { + message_loop->PostTask( + FROM_HERE, base::Bind(callback, result, info, file_path, type)); +} + +} // namespace + +// static +int FileSystemContext::GetPermissionPolicy(FileSystemType type) { + switch (type) { + case kFileSystemTypeTemporary: + case kFileSystemTypePersistent: + case kFileSystemTypeSyncable: + return FILE_PERMISSION_SANDBOX; + + case kFileSystemTypeDrive: + case kFileSystemTypeNativeForPlatformApp: + case kFileSystemTypeNativeLocal: + case kFileSystemTypeCloudDevice: + case kFileSystemTypeProvided: + case kFileSystemTypeDeviceMediaAsFileStorage: + return FILE_PERMISSION_USE_FILE_PERMISSION; + + case kFileSystemTypeRestrictedNativeLocal: + return FILE_PERMISSION_READ_ONLY | + FILE_PERMISSION_USE_FILE_PERMISSION; + + case kFileSystemTypeDeviceMedia: + case kFileSystemTypeIphoto: + case kFileSystemTypeItunes: + case kFileSystemTypeNativeMedia: + case kFileSystemTypePicasa: + return FILE_PERMISSION_USE_FILE_PERMISSION; + + // Following types are only accessed via IsolatedFileSystem, and + // don't have their own permission policies. + case kFileSystemTypeDragged: + case kFileSystemTypeForTransientFile: + case kFileSystemTypePluginPrivate: + return FILE_PERMISSION_ALWAYS_DENY; + + // Following types only appear as mount_type, and will not be + // queried for their permission policies. + case kFileSystemTypeIsolated: + case kFileSystemTypeExternal: + return FILE_PERMISSION_ALWAYS_DENY; + + // Following types should not be used to access files by FileAPI clients. + case kFileSystemTypeTest: + case kFileSystemTypeSyncableForInternalSync: + case kFileSystemInternalTypeEnumEnd: + case kFileSystemInternalTypeEnumStart: + case kFileSystemTypeUnknown: + return FILE_PERMISSION_ALWAYS_DENY; + } + NOTREACHED(); + return FILE_PERMISSION_ALWAYS_DENY; +} + +FileSystemContext::FileSystemContext( + base::SingleThreadTaskRunner* io_task_runner, + base::SequencedTaskRunner* file_task_runner, + ExternalMountPoints* external_mount_points, + storage::SpecialStoragePolicy* special_storage_policy, + storage::QuotaManagerProxy* quota_manager_proxy, + ScopedVector<FileSystemBackend> additional_backends, + const std::vector<URLRequestAutoMountHandler>& auto_mount_handlers, + const base::FilePath& partition_path, + const FileSystemOptions& options) + : io_task_runner_(io_task_runner), + default_file_task_runner_(file_task_runner), + quota_manager_proxy_(quota_manager_proxy), + sandbox_delegate_( + new SandboxFileSystemBackendDelegate(quota_manager_proxy, + file_task_runner, + partition_path, + special_storage_policy, + options)), + sandbox_backend_(new SandboxFileSystemBackend(sandbox_delegate_.get())), + isolated_backend_(new IsolatedFileSystemBackend()), + plugin_private_backend_( + new PluginPrivateFileSystemBackend(file_task_runner, + partition_path, + special_storage_policy, + options)), + additional_backends_(additional_backends.Pass()), + auto_mount_handlers_(auto_mount_handlers), + external_mount_points_(external_mount_points), + partition_path_(partition_path), + is_incognito_(options.is_incognito()), + operation_runner_(new FileSystemOperationRunner(this)) { + RegisterBackend(sandbox_backend_.get()); + RegisterBackend(isolated_backend_.get()); + RegisterBackend(plugin_private_backend_.get()); + + for (ScopedVector<FileSystemBackend>::const_iterator iter = + additional_backends_.begin(); + iter != additional_backends_.end(); ++iter) { + RegisterBackend(*iter); + } + + if (quota_manager_proxy) { + // Quota client assumes all backends have registered. + quota_manager_proxy->RegisterClient(CreateQuotaClient( + this, options.is_incognito())); + } + + sandbox_backend_->Initialize(this); + isolated_backend_->Initialize(this); + plugin_private_backend_->Initialize(this); + for (ScopedVector<FileSystemBackend>::const_iterator iter = + additional_backends_.begin(); + iter != additional_backends_.end(); ++iter) { + (*iter)->Initialize(this); + } + + // Additional mount points must be added before regular system-wide + // mount points. + if (external_mount_points) + url_crackers_.push_back(external_mount_points); + url_crackers_.push_back(ExternalMountPoints::GetSystemInstance()); + url_crackers_.push_back(IsolatedContext::GetInstance()); +} + +bool FileSystemContext::DeleteDataForOriginOnFileTaskRunner( + const GURL& origin_url) { + DCHECK(default_file_task_runner()->RunsTasksOnCurrentThread()); + DCHECK(origin_url == origin_url.GetOrigin()); + + bool success = true; + for (FileSystemBackendMap::iterator iter = backend_map_.begin(); + iter != backend_map_.end(); + ++iter) { + FileSystemBackend* backend = iter->second; + if (!backend->GetQuotaUtil()) + continue; + if (backend->GetQuotaUtil()->DeleteOriginDataOnFileTaskRunner( + this, quota_manager_proxy(), origin_url, iter->first) + != base::File::FILE_OK) { + // Continue the loop, but record the failure. + success = false; + } + } + + return success; +} + +scoped_refptr<QuotaReservation> +FileSystemContext::CreateQuotaReservationOnFileTaskRunner( + const GURL& origin_url, + FileSystemType type) { + DCHECK(default_file_task_runner()->RunsTasksOnCurrentThread()); + FileSystemBackend* backend = GetFileSystemBackend(type); + if (!backend || !backend->GetQuotaUtil()) + return scoped_refptr<QuotaReservation>(); + return backend->GetQuotaUtil()->CreateQuotaReservationOnFileTaskRunner( + origin_url, type); +} + +void FileSystemContext::Shutdown() { + if (!io_task_runner_->RunsTasksOnCurrentThread()) { + io_task_runner_->PostTask( + FROM_HERE, base::Bind(&FileSystemContext::Shutdown, + make_scoped_refptr(this))); + return; + } + operation_runner_->Shutdown(); +} + +FileSystemQuotaUtil* +FileSystemContext::GetQuotaUtil(FileSystemType type) const { + FileSystemBackend* backend = GetFileSystemBackend(type); + if (!backend) + return NULL; + return backend->GetQuotaUtil(); +} + +AsyncFileUtil* FileSystemContext::GetAsyncFileUtil( + FileSystemType type) const { + FileSystemBackend* backend = GetFileSystemBackend(type); + if (!backend) + return NULL; + return backend->GetAsyncFileUtil(type); +} + +CopyOrMoveFileValidatorFactory* +FileSystemContext::GetCopyOrMoveFileValidatorFactory( + FileSystemType type, base::File::Error* error_code) const { + DCHECK(error_code); + *error_code = base::File::FILE_OK; + FileSystemBackend* backend = GetFileSystemBackend(type); + if (!backend) + return NULL; + return backend->GetCopyOrMoveFileValidatorFactory( + type, error_code); +} + +FileSystemBackend* FileSystemContext::GetFileSystemBackend( + FileSystemType type) const { + FileSystemBackendMap::const_iterator found = backend_map_.find(type); + if (found != backend_map_.end()) + return found->second; + NOTREACHED() << "Unknown filesystem type: " << type; + return NULL; +} + +WatcherManager* FileSystemContext::GetWatcherManager( + FileSystemType type) const { + FileSystemBackend* backend = GetFileSystemBackend(type); + if (!backend) + return NULL; + return backend->GetWatcherManager(type); +} + +bool FileSystemContext::IsSandboxFileSystem(FileSystemType type) const { + FileSystemBackendMap::const_iterator found = backend_map_.find(type); + return found != backend_map_.end() && found->second->GetQuotaUtil(); +} + +const UpdateObserverList* FileSystemContext::GetUpdateObservers( + FileSystemType type) const { + FileSystemBackend* backend = GetFileSystemBackend(type); + if (backend->GetQuotaUtil()) + return backend->GetQuotaUtil()->GetUpdateObservers(type); + return NULL; +} + +const ChangeObserverList* FileSystemContext::GetChangeObservers( + FileSystemType type) const { + FileSystemBackend* backend = GetFileSystemBackend(type); + if (backend->GetQuotaUtil()) + return backend->GetQuotaUtil()->GetChangeObservers(type); + return NULL; +} + +const AccessObserverList* FileSystemContext::GetAccessObservers( + FileSystemType type) const { + FileSystemBackend* backend = GetFileSystemBackend(type); + if (backend->GetQuotaUtil()) + return backend->GetQuotaUtil()->GetAccessObservers(type); + return NULL; +} + +void FileSystemContext::GetFileSystemTypes( + std::vector<FileSystemType>* types) const { + types->clear(); + for (FileSystemBackendMap::const_iterator iter = backend_map_.begin(); + iter != backend_map_.end(); ++iter) + types->push_back(iter->first); +} + +ExternalFileSystemBackend* +FileSystemContext::external_backend() const { + return static_cast<ExternalFileSystemBackend*>( + GetFileSystemBackend(kFileSystemTypeExternal)); +} + +void FileSystemContext::OpenFileSystem( + const GURL& origin_url, + FileSystemType type, + OpenFileSystemMode mode, + const OpenFileSystemCallback& callback) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + DCHECK(!callback.is_null()); + + if (!FileSystemContext::IsSandboxFileSystem(type)) { + // Disallow opening a non-sandboxed filesystem. + callback.Run(GURL(), std::string(), base::File::FILE_ERROR_SECURITY); + return; + } + + FileSystemBackend* backend = GetFileSystemBackend(type); + if (!backend) { + callback.Run(GURL(), std::string(), base::File::FILE_ERROR_SECURITY); + return; + } + + backend->ResolveURL( + CreateCrackedFileSystemURL(origin_url, type, base::FilePath()), + mode, + callback); +} + +void FileSystemContext::ResolveURL( + const FileSystemURL& url, + const ResolveURLCallback& callback) { + DCHECK(!callback.is_null()); + + // If not on IO thread, forward before passing the task to the backend. + if (!io_task_runner_->RunsTasksOnCurrentThread()) { + ResolveURLCallback relay_callback = + base::Bind(&RelayResolveURLCallback, + base::MessageLoopProxy::current(), callback); + io_task_runner_->PostTask( + FROM_HERE, + base::Bind(&FileSystemContext::ResolveURL, this, url, relay_callback)); + return; + } + + FileSystemBackend* backend = GetFileSystemBackend(url.type()); + if (!backend) { + callback.Run(base::File::FILE_ERROR_SECURITY, + FileSystemInfo(), base::FilePath(), + FileSystemContext::RESOLVED_ENTRY_NOT_FOUND); + return; + } + + backend->ResolveURL( + url, + OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT, + base::Bind(&FileSystemContext::DidOpenFileSystemForResolveURL, + this, + url, + callback)); +} + +void FileSystemContext::AttemptAutoMountForURLRequest( + const net::URLRequest* url_request, + const std::string& storage_domain, + const StatusCallback& callback) { + FileSystemURL filesystem_url(url_request->url()); + if (filesystem_url.type() == kFileSystemTypeExternal) { + for (size_t i = 0; i < auto_mount_handlers_.size(); i++) { + if (auto_mount_handlers_[i].Run(url_request, filesystem_url, + storage_domain, callback)) { + return; + } + } + } + callback.Run(base::File::FILE_ERROR_NOT_FOUND); +} + +void FileSystemContext::DeleteFileSystem( + const GURL& origin_url, + FileSystemType type, + const StatusCallback& callback) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + DCHECK(origin_url == origin_url.GetOrigin()); + DCHECK(!callback.is_null()); + + FileSystemBackend* backend = GetFileSystemBackend(type); + if (!backend) { + callback.Run(base::File::FILE_ERROR_SECURITY); + return; + } + if (!backend->GetQuotaUtil()) { + callback.Run(base::File::FILE_ERROR_INVALID_OPERATION); + return; + } + + base::PostTaskAndReplyWithResult( + default_file_task_runner(), + FROM_HERE, + // It is safe to pass Unretained(quota_util) since context owns it. + base::Bind(&FileSystemQuotaUtil::DeleteOriginDataOnFileTaskRunner, + base::Unretained(backend->GetQuotaUtil()), + make_scoped_refptr(this), + base::Unretained(quota_manager_proxy()), + origin_url, + type), + callback); +} + +scoped_ptr<storage::FileStreamReader> FileSystemContext::CreateFileStreamReader( + const FileSystemURL& url, + int64 offset, + const base::Time& expected_modification_time) { + if (!url.is_valid()) + return scoped_ptr<storage::FileStreamReader>(); + FileSystemBackend* backend = GetFileSystemBackend(url.type()); + if (!backend) + return scoped_ptr<storage::FileStreamReader>(); + return backend->CreateFileStreamReader( + url, offset, expected_modification_time, this); +} + +scoped_ptr<FileStreamWriter> FileSystemContext::CreateFileStreamWriter( + const FileSystemURL& url, + int64 offset) { + if (!url.is_valid()) + return scoped_ptr<FileStreamWriter>(); + FileSystemBackend* backend = GetFileSystemBackend(url.type()); + if (!backend) + return scoped_ptr<FileStreamWriter>(); + return backend->CreateFileStreamWriter(url, offset, this); +} + +scoped_ptr<FileSystemOperationRunner> +FileSystemContext::CreateFileSystemOperationRunner() { + return make_scoped_ptr(new FileSystemOperationRunner(this)); +} + +FileSystemURL FileSystemContext::CrackURL(const GURL& url) const { + return CrackFileSystemURL(FileSystemURL(url)); +} + +FileSystemURL FileSystemContext::CreateCrackedFileSystemURL( + const GURL& origin, + FileSystemType type, + const base::FilePath& path) const { + return CrackFileSystemURL(FileSystemURL(origin, type, path)); +} + +#if defined(OS_CHROMEOS) +void FileSystemContext::EnableTemporaryFileSystemInIncognito() { + sandbox_backend_->set_enable_temporary_file_system_in_incognito(true); +} +#endif + +bool FileSystemContext::CanServeURLRequest(const FileSystemURL& url) const { + // We never support accessing files in isolated filesystems via an URL. + if (url.mount_type() == kFileSystemTypeIsolated) + return false; +#if defined(OS_CHROMEOS) + if (url.type() == kFileSystemTypeTemporary && + sandbox_backend_->enable_temporary_file_system_in_incognito()) { + return true; + } +#endif + return !is_incognito_ || !FileSystemContext::IsSandboxFileSystem(url.type()); +} + +bool FileSystemContext::ShouldFlushOnWriteCompletion( + FileSystemType type) const { + if (IsSandboxFileSystem(type)) { + // Disable Flush() for each write operation on SandboxFileSystems since it + // hurts the performance, assuming the FileSystems are stored in a local + // disk, we don't need to keep calling fsync() for it. + // On the other hand, other FileSystems that may stored on a removable media + // should be Flush()ed as soon as a write operation is completed, so that + // written data is saved over sudden media removal. + return false; + } + return true; +} + +void FileSystemContext::OpenPluginPrivateFileSystem( + const GURL& origin_url, + FileSystemType type, + const std::string& filesystem_id, + const std::string& plugin_id, + OpenFileSystemMode mode, + const StatusCallback& callback) { + DCHECK(plugin_private_backend_); + plugin_private_backend_->OpenPrivateFileSystem( + origin_url, type, filesystem_id, plugin_id, mode, callback); +} + +FileSystemContext::~FileSystemContext() { +} + +void FileSystemContext::DeleteOnCorrectThread() const { + if (!io_task_runner_->RunsTasksOnCurrentThread() && + io_task_runner_->DeleteSoon(FROM_HERE, this)) { + return; + } + delete this; +} + +FileSystemOperation* FileSystemContext::CreateFileSystemOperation( + const FileSystemURL& url, base::File::Error* error_code) { + if (!url.is_valid()) { + if (error_code) + *error_code = base::File::FILE_ERROR_INVALID_URL; + return NULL; + } + + FileSystemBackend* backend = GetFileSystemBackend(url.type()); + if (!backend) { + if (error_code) + *error_code = base::File::FILE_ERROR_FAILED; + return NULL; + } + + base::File::Error fs_error = base::File::FILE_OK; + FileSystemOperation* operation = + backend->CreateFileSystemOperation(url, this, &fs_error); + + if (error_code) + *error_code = fs_error; + return operation; +} + +FileSystemURL FileSystemContext::CrackFileSystemURL( + const FileSystemURL& url) const { + if (!url.is_valid()) + return FileSystemURL(); + + // The returned value in case there is no crackers which can crack the url. + // This is valid situation for non isolated/external file systems. + FileSystemURL current = url; + + // File system may be mounted multiple times (e.g., an isolated filesystem on + // top of an external filesystem). Hence cracking needs to be iterated. + for (;;) { + FileSystemURL cracked = current; + for (size_t i = 0; i < url_crackers_.size(); ++i) { + if (!url_crackers_[i]->HandlesFileSystemMountType(current.type())) + continue; + cracked = url_crackers_[i]->CrackFileSystemURL(current); + if (cracked.is_valid()) + break; + } + if (cracked == current) + break; + current = cracked; + } + return current; +} + +void FileSystemContext::RegisterBackend(FileSystemBackend* backend) { + const FileSystemType mount_types[] = { + kFileSystemTypeTemporary, + kFileSystemTypePersistent, + kFileSystemTypeIsolated, + kFileSystemTypeExternal, + }; + // Register file system backends for public mount types. + for (size_t j = 0; j < ARRAYSIZE_UNSAFE(mount_types); ++j) { + if (backend->CanHandleType(mount_types[j])) { + const bool inserted = backend_map_.insert( + std::make_pair(mount_types[j], backend)).second; + DCHECK(inserted); + } + } + // Register file system backends for internal types. + for (int t = kFileSystemInternalTypeEnumStart + 1; + t < kFileSystemInternalTypeEnumEnd; ++t) { + FileSystemType type = static_cast<FileSystemType>(t); + if (backend->CanHandleType(type)) { + const bool inserted = backend_map_.insert( + std::make_pair(type, backend)).second; + DCHECK(inserted); + } + } +} + +void FileSystemContext::DidOpenFileSystemForResolveURL( + const FileSystemURL& url, + const FileSystemContext::ResolveURLCallback& callback, + const GURL& filesystem_root, + const std::string& filesystem_name, + base::File::Error error) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + + if (error != base::File::FILE_OK) { + callback.Run(error, FileSystemInfo(), base::FilePath(), + FileSystemContext::RESOLVED_ENTRY_NOT_FOUND); + return; + } + + storage::FileSystemInfo info( + filesystem_name, filesystem_root, url.mount_type()); + + // Extract the virtual path not containing a filesystem type part from |url|. + base::FilePath parent = CrackURL(filesystem_root).virtual_path(); + base::FilePath child = url.virtual_path(); + base::FilePath path; + + if (parent.empty()) { + path = child; + } else if (parent != child) { + bool result = parent.AppendRelativePath(child, &path); + DCHECK(result); + } + + operation_runner()->GetMetadata( + url, base::Bind(&DidGetMetadataForResolveURL, path, callback, info)); +} + +} // namespace storage diff --git a/storage/browser/fileapi/file_system_context.h b/storage/browser/fileapi/file_system_context.h new file mode 100644 index 0000000..09b2409 --- /dev/null +++ b/storage/browser/fileapi/file_system_context.h @@ -0,0 +1,428 @@ +// 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. + +#ifndef STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_CONTEXT_H_ +#define STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_CONTEXT_H_ + +#include <map> +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/files/file.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/sequenced_task_runner_helpers.h" +#include "storage/browser/fileapi/file_system_url.h" +#include "storage/browser/fileapi/open_file_system_mode.h" +#include "storage/browser/fileapi/plugin_private_file_system_backend.h" +#include "storage/browser/fileapi/sandbox_file_system_backend_delegate.h" +#include "storage/browser/fileapi/task_runner_bound_observer_list.h" +#include "storage/browser/storage_browser_export.h" +#include "storage/common/fileapi/file_system_types.h" + +namespace base { +class FilePath; +class SequencedTaskRunner; +class SingleThreadTaskRunner; +} + +namespace chrome { +class NativeMediaFileUtilTest; +} + +namespace storage { +class QuotaManagerProxy; +class SpecialStoragePolicy; +} + +namespace net { +class URLRequest; +} + +namespace storage { +class BlobURLRequestJobTest; +class FileStreamReader; +} + +namespace storage { + +class AsyncFileUtil; +class CopyOrMoveFileValidatorFactory; +class ExternalFileSystemBackend; +class ExternalMountPoints; +class FileStreamWriter; +class FileSystemBackend; +class FileSystemFileUtil; +class FileSystemOperation; +class FileSystemOperationRunner; +class FileSystemOptions; +class FileSystemQuotaUtil; +class FileSystemURL; +class IsolatedFileSystemBackend; +class MountPoints; +class QuotaReservation; +class SandboxFileSystemBackend; +class WatchManager; + +struct DefaultContextDeleter; +struct FileSystemInfo; + +// An auto mount handler will attempt to mount the file system requested in +// |url_request|. If the URL is for this auto mount handler, it returns true +// and calls |callback| when the attempt is complete. If the auto mounter +// does not recognize the URL, it returns false and does not call |callback|. +// Called on the IO thread. +typedef base::Callback<bool( + const net::URLRequest* url_request, + const FileSystemURL& filesystem_url, + const std::string& storage_domain, + const base::Callback<void(base::File::Error result)>& callback)> + URLRequestAutoMountHandler; + +// This class keeps and provides a file system context for FileSystem API. +// An instance of this class is created and owned by profile. +class STORAGE_EXPORT FileSystemContext + : public base::RefCountedThreadSafe<FileSystemContext, + DefaultContextDeleter> { + public: + // Returns file permission policy we should apply for the given |type|. + // The return value must be bitwise-or'd of FilePermissionPolicy. + // + // Note: if a part of a filesystem is returned via 'Isolated' mount point, + // its per-filesystem permission overrides the underlying filesystem's + // permission policy. + static int GetPermissionPolicy(FileSystemType type); + + // file_task_runner is used as default TaskRunner. + // Unless a FileSystemBackend is overridden in CreateFileSystemOperation, + // it is used for all file operations and file related meta operations. + // The code assumes that file_task_runner->RunsTasksOnCurrentThread() + // returns false if the current task is not running on the thread that allows + // blocking file operations (like SequencedWorkerPool implementation does). + // + // |external_mount_points| contains non-system external mount points available + // in the context. If not NULL, it will be used during URL cracking. + // |external_mount_points| may be NULL only on platforms different from + // ChromeOS (i.e. platforms that don't use external_mount_point_provider). + // + // |additional_backends| are added to the internal backend map + // to serve filesystem requests for non-regular types. + // If none is given, this context only handles HTML5 Sandbox FileSystem + // and Drag-and-drop Isolated FileSystem requests. + // + // |auto_mount_handlers| are used to resolve calls to + // AttemptAutoMountForURLRequest. Only external filesystems are auto mounted + // when a filesystem: URL request is made. + FileSystemContext( + base::SingleThreadTaskRunner* io_task_runner, + base::SequencedTaskRunner* file_task_runner, + ExternalMountPoints* external_mount_points, + storage::SpecialStoragePolicy* special_storage_policy, + storage::QuotaManagerProxy* quota_manager_proxy, + ScopedVector<FileSystemBackend> additional_backends, + const std::vector<URLRequestAutoMountHandler>& auto_mount_handlers, + const base::FilePath& partition_path, + const FileSystemOptions& options); + + bool DeleteDataForOriginOnFileTaskRunner(const GURL& origin_url); + + // Creates a new QuotaReservation for the given |origin_url| and |type|. + // Returns NULL if |type| does not support quota or reservation fails. + // This should be run on |default_file_task_runner_| and the returned value + // should be destroyed on the runner. + scoped_refptr<QuotaReservation> CreateQuotaReservationOnFileTaskRunner( + const GURL& origin_url, + FileSystemType type); + + storage::QuotaManagerProxy* quota_manager_proxy() const { + return quota_manager_proxy_.get(); + } + + // Discards inflight operations in the operation runner. + void Shutdown(); + + // Returns a quota util for a given filesystem type. This may + // return NULL if the type does not support the usage tracking or + // it is not a quota-managed storage. + FileSystemQuotaUtil* GetQuotaUtil(FileSystemType type) const; + + // Returns the appropriate AsyncFileUtil instance for the given |type|. + AsyncFileUtil* GetAsyncFileUtil(FileSystemType type) const; + + // Returns the appropriate CopyOrMoveFileValidatorFactory for the given + // |type|. If |error_code| is File::FILE_OK and the result is NULL, + // then no validator is required. + CopyOrMoveFileValidatorFactory* GetCopyOrMoveFileValidatorFactory( + FileSystemType type, base::File::Error* error_code) const; + + // Returns the file system backend instance for the given |type|. + // This may return NULL if it is given an invalid or unsupported filesystem + // type. + FileSystemBackend* GetFileSystemBackend( + FileSystemType type) const; + + // Returns the watcher manager for the given |type|. + // This may return NULL if the type does not support watching. + WatcherManager* GetWatcherManager(FileSystemType type) const; + + // Returns true for sandboxed filesystems. Currently this does + // the same as GetQuotaUtil(type) != NULL. (In an assumption that + // all sandboxed filesystems must cooperate with QuotaManager so that + // they can get deleted) + bool IsSandboxFileSystem(FileSystemType type) const; + + // Returns observers for the given filesystem type. + const UpdateObserverList* GetUpdateObservers(FileSystemType type) const; + const ChangeObserverList* GetChangeObservers(FileSystemType type) const; + const AccessObserverList* GetAccessObservers(FileSystemType type) const; + + // Returns all registered filesystem types. + void GetFileSystemTypes(std::vector<FileSystemType>* types) const; + + // Returns a FileSystemBackend instance for external filesystem + // type, which is used only by chromeos for now. This is equivalent to + // calling GetFileSystemBackend(kFileSystemTypeExternal). + ExternalFileSystemBackend* external_backend() const; + + // Used for OpenFileSystem. + typedef base::Callback<void(const GURL& root, + const std::string& name, + base::File::Error result)> + OpenFileSystemCallback; + + // Used for ResolveURL. + enum ResolvedEntryType { + RESOLVED_ENTRY_FILE, + RESOLVED_ENTRY_DIRECTORY, + RESOLVED_ENTRY_NOT_FOUND, + }; + typedef base::Callback<void(base::File::Error result, + const FileSystemInfo& info, + const base::FilePath& file_path, + ResolvedEntryType type)> ResolveURLCallback; + + // Used for DeleteFileSystem and OpenPluginPrivateFileSystem. + typedef base::Callback<void(base::File::Error result)> StatusCallback; + + // Opens the filesystem for the given |origin_url| and |type|, and dispatches + // |callback| on completion. + // If |create| is true this may actually set up a filesystem instance + // (e.g. by creating the root directory or initializing the database + // entry etc). + void OpenFileSystem( + const GURL& origin_url, + FileSystemType type, + OpenFileSystemMode mode, + const OpenFileSystemCallback& callback); + + // Opens the filesystem for the given |url| as read-only, if the filesystem + // backend referred by the URL allows opening by resolveURL. Otherwise it + // fails with FILE_ERROR_SECURITY. The entry pointed by the URL can be + // absent; in that case RESOLVED_ENTRY_NOT_FOUND type is returned to the + // callback for indicating the absence. Can be called from any thread with + // a message loop. |callback| is invoked on the caller thread. + void ResolveURL( + const FileSystemURL& url, + const ResolveURLCallback& callback); + + // Attempts to mount the filesystem needed to satisfy |url_request| made + // from |storage_domain|. If an appropriate file system is not found, + // callback will return an error. + void AttemptAutoMountForURLRequest(const net::URLRequest* url_request, + const std::string& storage_domain, + const StatusCallback& callback); + + // Deletes the filesystem for the given |origin_url| and |type|. This should + // be called on the IO thread. + void DeleteFileSystem( + const GURL& origin_url, + FileSystemType type, + const StatusCallback& callback); + + // Creates new FileStreamReader instance to read a file pointed by the given + // filesystem URL |url| starting from |offset|. |expected_modification_time| + // specifies the expected last modification if the value is non-null, the + // reader will check the underlying file's actual modification time to see if + // the file has been modified, and if it does any succeeding read operations + // should fail with ERR_UPLOAD_FILE_CHANGED error. + // This method internally cracks the |url|, get an appropriate + // FileSystemBackend for the URL and call the backend's CreateFileReader. + // The resolved FileSystemBackend could perform further specialization + // depending on the filesystem type pointed by the |url|. + scoped_ptr<storage::FileStreamReader> CreateFileStreamReader( + const FileSystemURL& url, + int64 offset, + const base::Time& expected_modification_time); + + // Creates new FileStreamWriter instance to write into a file pointed by + // |url| from |offset|. + scoped_ptr<FileStreamWriter> CreateFileStreamWriter( + const FileSystemURL& url, + int64 offset); + + // Creates a new FileSystemOperationRunner. + scoped_ptr<FileSystemOperationRunner> CreateFileSystemOperationRunner(); + + base::SequencedTaskRunner* default_file_task_runner() { + return default_file_task_runner_.get(); + } + + FileSystemOperationRunner* operation_runner() { + return operation_runner_.get(); + } + + const base::FilePath& partition_path() const { return partition_path_; } + + // Same as |CrackFileSystemURL|, but cracks FileSystemURL created from |url|. + FileSystemURL CrackURL(const GURL& url) const; + // Same as |CrackFileSystemURL|, but cracks FileSystemURL created from method + // arguments. + FileSystemURL CreateCrackedFileSystemURL(const GURL& origin, + FileSystemType type, + const base::FilePath& path) const; + +#if defined(OS_CHROMEOS) + // Used only on ChromeOS for now. + void EnableTemporaryFileSystemInIncognito(); +#endif + + SandboxFileSystemBackendDelegate* sandbox_delegate() { + return sandbox_delegate_.get(); + } + + // Returns true if the requested url is ok to be served. + // (E.g. this returns false if the context is created for incognito mode) + bool CanServeURLRequest(const FileSystemURL& url) const; + + // Returns true if a file in the file system should be flushed for each write + // completion. + bool ShouldFlushOnWriteCompletion(FileSystemType type) const; + + // This must be used to open 'plugin private' filesystem. + // See "plugin_private_file_system_backend.h" for more details. + void OpenPluginPrivateFileSystem( + const GURL& origin_url, + FileSystemType type, + const std::string& filesystem_id, + const std::string& plugin_id, + OpenFileSystemMode mode, + const StatusCallback& callback); + + private: + typedef std::map<FileSystemType, FileSystemBackend*> + FileSystemBackendMap; + + // For CreateFileSystemOperation. + friend class FileSystemOperationRunner; + + // For sandbox_backend(). + friend class content::SandboxFileSystemTestHelper; + + // For plugin_private_backend(). + friend class content::PluginPrivateFileSystemBackendTest; + + // Deleters. + friend struct DefaultContextDeleter; + friend class base::DeleteHelper<FileSystemContext>; + friend class base::RefCountedThreadSafe<FileSystemContext, + DefaultContextDeleter>; + ~FileSystemContext(); + + void DeleteOnCorrectThread() const; + + // Creates a new FileSystemOperation instance by getting an appropriate + // FileSystemBackend for |url| and calling the backend's corresponding + // CreateFileSystemOperation method. + // The resolved FileSystemBackend could perform further specialization + // depending on the filesystem type pointed by the |url|. + // + // Called by FileSystemOperationRunner. + FileSystemOperation* CreateFileSystemOperation( + const FileSystemURL& url, + base::File::Error* error_code); + + // For non-cracked isolated and external mount points, returns a FileSystemURL + // created by cracking |url|. The url is cracked using MountPoints registered + // as |url_crackers_|. If the url cannot be cracked, returns invalid + // FileSystemURL. + // + // If the original url does not point to an isolated or external filesystem, + // returns the original url, without attempting to crack it. + FileSystemURL CrackFileSystemURL(const FileSystemURL& url) const; + + // For initial backend_map construction. This must be called only from + // the constructor. + void RegisterBackend(FileSystemBackend* backend); + + void DidOpenFileSystemForResolveURL( + const FileSystemURL& url, + const ResolveURLCallback& callback, + const GURL& filesystem_root, + const std::string& filesystem_name, + base::File::Error error); + + // Returns a FileSystemBackend, used only by test code. + SandboxFileSystemBackend* sandbox_backend() const { + return sandbox_backend_.get(); + } + + // Used only by test code. + PluginPrivateFileSystemBackend* plugin_private_backend() const { + return plugin_private_backend_.get(); + } + + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_; + scoped_refptr<base::SequencedTaskRunner> default_file_task_runner_; + + scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy_; + + scoped_ptr<SandboxFileSystemBackendDelegate> sandbox_delegate_; + + // Regular file system backends. + scoped_ptr<SandboxFileSystemBackend> sandbox_backend_; + scoped_ptr<IsolatedFileSystemBackend> isolated_backend_; + + // Additional file system backends. + scoped_ptr<PluginPrivateFileSystemBackend> plugin_private_backend_; + ScopedVector<FileSystemBackend> additional_backends_; + + std::vector<URLRequestAutoMountHandler> auto_mount_handlers_; + + // Registered file system backends. + // The map must be constructed in the constructor since it can be accessed + // on multiple threads. + // This map itself doesn't retain each backend's ownership; ownerships + // of the backends are held by additional_backends_ or other scoped_ptr + // backend fields. + FileSystemBackendMap backend_map_; + + // External mount points visible in the file system context (excluding system + // external mount points). + scoped_refptr<ExternalMountPoints> external_mount_points_; + + // MountPoints used to crack FileSystemURLs. The MountPoints are ordered + // in order they should try to crack a FileSystemURL. + std::vector<MountPoints*> url_crackers_; + + // The base path of the storage partition for this context. + const base::FilePath partition_path_; + + bool is_incognito_; + + scoped_ptr<FileSystemOperationRunner> operation_runner_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(FileSystemContext); +}; + +struct DefaultContextDeleter { + static void Destruct(const FileSystemContext* context) { + context->DeleteOnCorrectThread(); + } +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_CONTEXT_H_ diff --git a/storage/browser/fileapi/file_system_dir_url_request_job.cc b/storage/browser/fileapi/file_system_dir_url_request_job.cc new file mode 100644 index 0000000..cce9c56 --- /dev/null +++ b/storage/browser/fileapi/file_system_dir_url_request_job.cc @@ -0,0 +1,160 @@ +// 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 "storage/browser/fileapi/file_system_dir_url_request_job.h" + +#include <algorithm> + +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/sys_string_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "build/build_config.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/base/net_util.h" +#include "net/url_request/url_request.h" +#include "storage/browser/fileapi/file_system_context.h" +#include "storage/browser/fileapi/file_system_operation_runner.h" +#include "storage/browser/fileapi/file_system_url.h" +#include "storage/common/fileapi/directory_entry.h" +#include "storage/common/fileapi/file_system_util.h" +#include "url/gurl.h" + +using net::NetworkDelegate; +using net::URLRequest; +using net::URLRequestJob; +using net::URLRequestStatus; + +namespace storage { + +FileSystemDirURLRequestJob::FileSystemDirURLRequestJob( + URLRequest* request, + NetworkDelegate* network_delegate, + const std::string& storage_domain, + FileSystemContext* file_system_context) + : URLRequestJob(request, network_delegate), + storage_domain_(storage_domain), + file_system_context_(file_system_context), + weak_factory_(this) { +} + +FileSystemDirURLRequestJob::~FileSystemDirURLRequestJob() { +} + +bool FileSystemDirURLRequestJob::ReadRawData(net::IOBuffer* dest, int dest_size, + int *bytes_read) { + int count = std::min(dest_size, static_cast<int>(data_.size())); + if (count > 0) { + memcpy(dest->data(), data_.data(), count); + data_.erase(0, count); + } + *bytes_read = count; + return true; +} + +void FileSystemDirURLRequestJob::Start() { + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&FileSystemDirURLRequestJob::StartAsync, + weak_factory_.GetWeakPtr())); +} + +void FileSystemDirURLRequestJob::Kill() { + URLRequestJob::Kill(); + weak_factory_.InvalidateWeakPtrs(); +} + +bool FileSystemDirURLRequestJob::GetMimeType(std::string* mime_type) const { + *mime_type = "text/html"; + return true; +} + +bool FileSystemDirURLRequestJob::GetCharset(std::string* charset) { + *charset = "utf-8"; + return true; +} + +void FileSystemDirURLRequestJob::StartAsync() { + if (!request_) + return; + url_ = file_system_context_->CrackURL(request_->url()); + if (!url_.is_valid()) { + file_system_context_->AttemptAutoMountForURLRequest( + request_, + storage_domain_, + base::Bind(&FileSystemDirURLRequestJob::DidAttemptAutoMount, + weak_factory_.GetWeakPtr())); + return; + } + if (!file_system_context_->CanServeURLRequest(url_)) { + // In incognito mode the API is not usable and there should be no data. + if (url_.is_valid() && VirtualPath::IsRootPath(url_.virtual_path())) { + // Return an empty directory if the filesystem root is queried. + DidReadDirectory(base::File::FILE_OK, + std::vector<DirectoryEntry>(), + false); + return; + } + NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, + net::ERR_FILE_NOT_FOUND)); + return; + } + file_system_context_->operation_runner()->ReadDirectory( + url_, + base::Bind(&FileSystemDirURLRequestJob::DidReadDirectory, this)); +} + +void FileSystemDirURLRequestJob::DidAttemptAutoMount(base::File::Error result) { + if (result >= 0 && + file_system_context_->CrackURL(request_->url()).is_valid()) { + StartAsync(); + } else { + NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, + net::ERR_FILE_NOT_FOUND)); + } +} + +void FileSystemDirURLRequestJob::DidReadDirectory( + base::File::Error result, + const std::vector<DirectoryEntry>& entries, + bool has_more) { + if (result != base::File::FILE_OK) { + int rv = net::ERR_FILE_NOT_FOUND; + if (result == base::File::FILE_ERROR_INVALID_URL) + rv = net::ERR_INVALID_URL; + NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv)); + return; + } + + if (!request_) + return; + + if (data_.empty()) { + base::FilePath relative_path = url_.path(); +#if defined(OS_POSIX) + relative_path = + base::FilePath(FILE_PATH_LITERAL("/") + relative_path.value()); +#endif + const base::string16& title = relative_path.LossyDisplayName(); + data_.append(net::GetDirectoryListingHeader(title)); + } + + typedef std::vector<DirectoryEntry>::const_iterator EntryIterator; + for (EntryIterator it = entries.begin(); it != entries.end(); ++it) { + const base::string16& name = base::FilePath(it->name).LossyDisplayName(); + data_.append(net::GetDirectoryListingEntry( + name, std::string(), it->is_directory, it->size, + it->last_modified_time)); + } + + if (!has_more) { + set_expected_content_size(data_.size()); + NotifyHeadersComplete(); + } +} + +} // namespace storage diff --git a/storage/browser/fileapi/file_system_dir_url_request_job.h b/storage/browser/fileapi/file_system_dir_url_request_job.h new file mode 100644 index 0000000..8c71cdf --- /dev/null +++ b/storage/browser/fileapi/file_system_dir_url_request_job.h @@ -0,0 +1,69 @@ +// 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. + +#ifndef STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_DIR_URL_REQUEST_JOB_H_ +#define STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_DIR_URL_REQUEST_JOB_H_ + +#include <string> +#include <vector> + +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop_proxy.h" +#include "net/url_request/url_request_job.h" +#include "storage/browser/fileapi/file_system_url.h" +#include "storage/browser/storage_browser_export.h" + +namespace storage { + +class FileSystemContext; +struct DirectoryEntry; + +// A request job that handles reading filesystem: URLs for directories. +class STORAGE_EXPORT_PRIVATE FileSystemDirURLRequestJob + : public net::URLRequestJob { + public: + FileSystemDirURLRequestJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate, + const std::string& storage_domain, + FileSystemContext* file_system_context); + + // URLRequestJob methods: + virtual void Start() OVERRIDE; + virtual void Kill() OVERRIDE; + virtual bool ReadRawData(net::IOBuffer* buf, + int buf_size, + int* bytes_read) OVERRIDE; + virtual bool GetCharset(std::string* charset) OVERRIDE; + + // FilterContext methods (via URLRequestJob): + virtual bool GetMimeType(std::string* mime_type) const OVERRIDE; + // TODO(adamk): Implement GetResponseInfo and GetResponseCode to simulate + // an HTTP response. + + private: + class CallbackDispatcher; + + virtual ~FileSystemDirURLRequestJob(); + + void StartAsync(); + void DidAttemptAutoMount(base::File::Error result); + void DidReadDirectory(base::File::Error result, + const std::vector<DirectoryEntry>& entries, + bool has_more); + + std::string data_; + FileSystemURL url_; + const std::string storage_domain_; + FileSystemContext* file_system_context_; + base::WeakPtrFactory<FileSystemDirURLRequestJob> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(FileSystemDirURLRequestJob); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_DIR_URL_REQUEST_JOB_H_ diff --git a/storage/browser/fileapi/file_system_file_stream_reader.cc b/storage/browser/fileapi/file_system_file_stream_reader.cc new file mode 100644 index 0000000..60a806a --- /dev/null +++ b/storage/browser/fileapi/file_system_file_stream_reader.cc @@ -0,0 +1,140 @@ +// 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 "storage/browser/fileapi/file_system_file_stream_reader.h" + +#include "base/files/file_util_proxy.h" +#include "base/single_thread_task_runner.h" +#include "net/base/file_stream.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "storage/browser/blob/file_stream_reader.h" +#include "storage/browser/fileapi/file_system_context.h" +#include "storage/browser/fileapi/file_system_operation_runner.h" + +using storage::FileStreamReader; + +// TODO(kinuko): Remove this temporary namespace hack after we move both +// blob and fileapi into content namespace. +namespace storage { + +FileStreamReader* FileStreamReader::CreateForFileSystemFile( + storage::FileSystemContext* file_system_context, + const storage::FileSystemURL& url, + int64 initial_offset, + const base::Time& expected_modification_time) { + return new storage::FileSystemFileStreamReader( + file_system_context, url, initial_offset, expected_modification_time); +} + +} // namespace storage + +namespace storage { + +namespace { + +void ReadAdapter(base::WeakPtr<FileSystemFileStreamReader> reader, + net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) { + if (!reader.get()) + return; + int rv = reader->Read(buf, buf_len, callback); + if (rv != net::ERR_IO_PENDING) + callback.Run(rv); +} + +void GetLengthAdapter(base::WeakPtr<FileSystemFileStreamReader> reader, + const net::Int64CompletionCallback& callback) { + if (!reader.get()) + return; + int rv = reader->GetLength(callback); + if (rv != net::ERR_IO_PENDING) + callback.Run(rv); +} + +void Int64CallbackAdapter(const net::Int64CompletionCallback& callback, + int value) { + callback.Run(value); +} + +} // namespace + +FileSystemFileStreamReader::~FileSystemFileStreamReader() { +} + +int FileSystemFileStreamReader::Read( + net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) { + if (local_file_reader_) + return local_file_reader_->Read(buf, buf_len, callback); + return CreateSnapshot( + base::Bind(&ReadAdapter, weak_factory_.GetWeakPtr(), + make_scoped_refptr(buf), buf_len, callback), + callback); +} + +int64 FileSystemFileStreamReader::GetLength( + const net::Int64CompletionCallback& callback) { + if (local_file_reader_) + return local_file_reader_->GetLength(callback); + return CreateSnapshot( + base::Bind(&GetLengthAdapter, weak_factory_.GetWeakPtr(), callback), + base::Bind(&Int64CallbackAdapter, callback)); +} + +FileSystemFileStreamReader::FileSystemFileStreamReader( + FileSystemContext* file_system_context, + const FileSystemURL& url, + int64 initial_offset, + const base::Time& expected_modification_time) + : file_system_context_(file_system_context), + url_(url), + initial_offset_(initial_offset), + expected_modification_time_(expected_modification_time), + has_pending_create_snapshot_(false), + weak_factory_(this) { +} + +int FileSystemFileStreamReader::CreateSnapshot( + const base::Closure& callback, + const net::CompletionCallback& error_callback) { + DCHECK(!has_pending_create_snapshot_); + has_pending_create_snapshot_ = true; + file_system_context_->operation_runner()->CreateSnapshotFile( + url_, + base::Bind(&FileSystemFileStreamReader::DidCreateSnapshot, + weak_factory_.GetWeakPtr(), + callback, + error_callback)); + return net::ERR_IO_PENDING; +} + +void FileSystemFileStreamReader::DidCreateSnapshot( + const base::Closure& callback, + const net::CompletionCallback& error_callback, + base::File::Error file_error, + const base::File::Info& file_info, + const base::FilePath& platform_path, + const scoped_refptr<storage::ShareableFileReference>& file_ref) { + DCHECK(has_pending_create_snapshot_); + DCHECK(!local_file_reader_.get()); + has_pending_create_snapshot_ = false; + + if (file_error != base::File::FILE_OK) { + error_callback.Run(net::FileErrorToNetError(file_error)); + return; + } + + // Keep the reference (if it's non-null) so that the file won't go away. + snapshot_ref_ = file_ref; + + local_file_reader_.reset( + FileStreamReader::CreateForLocalFile( + file_system_context_->default_file_task_runner(), + platform_path, initial_offset_, expected_modification_time_)); + + callback.Run(); +} + +} // namespace storage diff --git a/storage/browser/fileapi/file_system_file_stream_reader.h b/storage/browser/fileapi/file_system_file_stream_reader.h new file mode 100644 index 0000000..3fe075b6 --- /dev/null +++ b/storage/browser/fileapi/file_system_file_stream_reader.h @@ -0,0 +1,79 @@ +// 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. + +#ifndef STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_FILE_STREAM_READER_H_ +#define STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_FILE_STREAM_READER_H_ + +#include "base/bind.h" +#include "base/files/file.h" +#include "base/memory/ref_counted.h" +#include "base/time/time.h" +#include "storage/browser/blob/file_stream_reader.h" +#include "storage/browser/fileapi/file_system_url.h" +#include "storage/browser/storage_browser_export.h" +#include "storage/common/blob/shareable_file_reference.h" + +namespace base { +class FilePath; +class SequencedTaskRunner; +} + +namespace content { +class FileSystemFileStreamReaderTest; +} + +namespace storage { + +class FileSystemContext; + +// Generic FileStreamReader implementation for FileSystem files. +// Note: This generic implementation would work for any filesystems but +// remote filesystem should implement its own reader rather than relying +// on FileSystemOperation::GetSnapshotFile() which may force downloading +// the entire contents for remote files. +class STORAGE_EXPORT_PRIVATE FileSystemFileStreamReader + : public NON_EXPORTED_BASE(storage::FileStreamReader) { + public: + virtual ~FileSystemFileStreamReader(); + + // FileStreamReader overrides. + virtual int Read(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) OVERRIDE; + virtual int64 GetLength( + const net::Int64CompletionCallback& callback) OVERRIDE; + + private: + friend class storage::FileStreamReader; + friend class content::FileSystemFileStreamReaderTest; + + FileSystemFileStreamReader(FileSystemContext* file_system_context, + const FileSystemURL& url, + int64 initial_offset, + const base::Time& expected_modification_time); + + int CreateSnapshot(const base::Closure& callback, + const net::CompletionCallback& error_callback); + void DidCreateSnapshot( + const base::Closure& callback, + const net::CompletionCallback& error_callback, + base::File::Error file_error, + const base::File::Info& file_info, + const base::FilePath& platform_path, + const scoped_refptr<storage::ShareableFileReference>& file_ref); + + scoped_refptr<FileSystemContext> file_system_context_; + FileSystemURL url_; + const int64 initial_offset_; + const base::Time expected_modification_time_; + scoped_ptr<storage::FileStreamReader> local_file_reader_; + scoped_refptr<storage::ShareableFileReference> snapshot_ref_; + bool has_pending_create_snapshot_; + base::WeakPtrFactory<FileSystemFileStreamReader> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(FileSystemFileStreamReader); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_FILE_STREAM_READER_H_ diff --git a/storage/browser/fileapi/file_system_file_util.cc b/storage/browser/fileapi/file_system_file_util.cc new file mode 100644 index 0000000..f356109 --- /dev/null +++ b/storage/browser/fileapi/file_system_file_util.cc @@ -0,0 +1,25 @@ +// 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 "storage/browser/fileapi/file_system_file_util.h" + +namespace storage { + +base::FilePath FileSystemFileUtil::EmptyFileEnumerator::Next() { + return base::FilePath(); +} + +int64 FileSystemFileUtil::EmptyFileEnumerator::Size() { + return 0; +} + +base::Time FileSystemFileUtil::EmptyFileEnumerator::LastModifiedTime() { + return base::Time(); +} + +bool FileSystemFileUtil::EmptyFileEnumerator::IsDirectory() { + return false; +} + +} // namespace storage diff --git a/storage/browser/fileapi/file_system_file_util.h b/storage/browser/fileapi/file_system_file_util.h new file mode 100644 index 0000000..868d091 --- /dev/null +++ b/storage/browser/fileapi/file_system_file_util.h @@ -0,0 +1,186 @@ +// 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. + +#ifndef STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_FILE_UTIL_H_ +#define STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_FILE_UTIL_H_ + +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "storage/browser/fileapi/file_system_operation.h" +#include "storage/browser/storage_browser_export.h" +#include "storage/common/blob/scoped_file.h" + +namespace base { +class Time; +} + +namespace storage { + +class FileSystemOperationContext; +class FileSystemURL; + +// A file utility interface that provides basic file utility methods for +// FileSystem API. +// +// Layering structure of the FileSystemFileUtil was split out. +// See http://crbug.com/128136 if you need it. +class STORAGE_EXPORT FileSystemFileUtil { + public: + typedef FileSystemOperation::CopyOrMoveOption CopyOrMoveOption; + + // It will be implemented by each subclass such as FileSystemFileEnumerator. + class STORAGE_EXPORT AbstractFileEnumerator { + public: + virtual ~AbstractFileEnumerator() {} + + // Returns an empty string if there are no more results. + virtual base::FilePath Next() = 0; + + // These methods return metadata for the file most recently returned by + // Next(). If Next() has never been called, or if Next() most recently + // returned an empty string, then return the default values of 0, + // "null time", and false, respectively. + virtual int64 Size() = 0; + virtual base::Time LastModifiedTime() = 0; + virtual bool IsDirectory() = 0; + }; + + class STORAGE_EXPORT EmptyFileEnumerator + : public AbstractFileEnumerator { + virtual base::FilePath Next() OVERRIDE; + virtual int64 Size() OVERRIDE; + virtual base::Time LastModifiedTime() OVERRIDE; + virtual bool IsDirectory() OVERRIDE; + }; + + virtual ~FileSystemFileUtil() {} + + // Creates or opens a file with the given flags. + // See header comments for AsyncFileUtil::CreateOrOpen() for more details. + // This is used only by Pepper/NaCl File API. + virtual base::File CreateOrOpen( + FileSystemOperationContext* context, + const FileSystemURL& url, + int file_flags) = 0; + + // Ensures that the given |url| exist. This creates a empty new file + // at |url| if the |url| does not exist. + // See header comments for AsyncFileUtil::EnsureFileExists() for more details. + virtual base::File::Error EnsureFileExists( + FileSystemOperationContext* context, + const FileSystemURL& url, bool* created) = 0; + + // Creates directory at given url. + // See header comments for AsyncFileUtil::CreateDirectory() for more details. + virtual base::File::Error CreateDirectory( + FileSystemOperationContext* context, + const FileSystemURL& url, + bool exclusive, + bool recursive) = 0; + + // Retrieves the information about a file. + // See header comments for AsyncFileUtil::GetFileInfo() for more details. + virtual base::File::Error GetFileInfo( + FileSystemOperationContext* context, + const FileSystemURL& url, + base::File::Info* file_info, + base::FilePath* platform_path) = 0; + + // Returns a pointer to a new instance of AbstractFileEnumerator which is + // implemented for each FileSystemFileUtil subclass. The instance needs to be + // freed by the caller, and its lifetime should not extend past when the + // current call returns to the main FILE message loop. + // + // The supplied context must remain valid at least lifetime of the enumerator + // instance. + virtual scoped_ptr<AbstractFileEnumerator> CreateFileEnumerator( + FileSystemOperationContext* context, + const FileSystemURL& root_url) = 0; + + // Maps |file_system_url| given |context| into |local_file_path| + // which represents physical file location on the host OS. + // This may not always make sense for all subclasses. + virtual base::File::Error GetLocalFilePath( + FileSystemOperationContext* context, + const FileSystemURL& file_system_url, + base::FilePath* local_file_path) = 0; + + // Updates the file metadata information. + // See header comments for AsyncFileUtil::Touch() for more details. + virtual base::File::Error Touch( + FileSystemOperationContext* context, + const FileSystemURL& url, + const base::Time& last_access_time, + const base::Time& last_modified_time) = 0; + + // Truncates a file to the given length. + // See header comments for AsyncFileUtil::Truncate() for more details. + virtual base::File::Error Truncate( + FileSystemOperationContext* context, + const FileSystemURL& url, + int64 length) = 0; + + // Copies or moves a single file from |src_url| to |dest_url|. + // The filesystem type of |src_url| and |dest_url| MUST be same. + // For |option|, please see file_system_operation.h + // + // This returns: + // - File::FILE_ERROR_NOT_FOUND if |src_url| + // or the parent directory of |dest_url| does not exist. + // - File::FILE_ERROR_NOT_A_FILE if |src_url| exists but is not a file. + // - File::FILE_ERROR_INVALID_OPERATION if |dest_url| exists and + // is not a file. + // - File::FILE_ERROR_FAILED if |dest_url| does not exist and + // its parent path is a file. + // + virtual base::File::Error CopyOrMoveFile( + FileSystemOperationContext* context, + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOption option, + bool copy) = 0; + + // Copies in a single file from a different filesystem. + // See header comments for AsyncFileUtil::CopyInForeignFile() for + // more details. + virtual base::File::Error CopyInForeignFile( + FileSystemOperationContext* context, + const base::FilePath& src_file_path, + const FileSystemURL& dest_url) = 0; + + // Deletes a single file. + // See header comments for AsyncFileUtil::DeleteFile() for more details. + virtual base::File::Error DeleteFile( + FileSystemOperationContext* context, + const FileSystemURL& url) = 0; + + // Deletes a single empty directory. + // See header comments for AsyncFileUtil::DeleteDirectory() for more details. + virtual base::File::Error DeleteDirectory( + FileSystemOperationContext* context, + const FileSystemURL& url) = 0; + + // Creates a local snapshot file for a given |url| and returns the + // metadata and platform path of the snapshot file via |callback|. + // + // See header comments for AsyncFileUtil::CreateSnapshotFile() for + // more details. + virtual storage::ScopedFile CreateSnapshotFile( + FileSystemOperationContext* context, + const FileSystemURL& url, + base::File::Error* error, + base::File::Info* file_info, + base::FilePath* platform_path) = 0; + + protected: + FileSystemFileUtil() {} + + private: + DISALLOW_COPY_AND_ASSIGN(FileSystemFileUtil); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_FILE_UTIL_H_ diff --git a/storage/browser/fileapi/file_system_operation.h b/storage/browser/fileapi/file_system_operation.h new file mode 100644 index 0000000..4b38da0 --- /dev/null +++ b/storage/browser/fileapi/file_system_operation.h @@ -0,0 +1,485 @@ +// 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. + +#ifndef STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_H_ +#define STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_H_ + +#include <vector> + +#include "base/callback.h" +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/process/process.h" +#include "storage/browser/fileapi/file_system_operation_context.h" +#include "storage/browser/storage_browser_export.h" +#include "storage/common/fileapi/directory_entry.h" + +namespace base { +class Time; +} + +namespace net { +class URLRequest; +} + +namespace storage { +class ShareableFileReference; +} + +class GURL; + +namespace storage { + +class FileSystemContext; +class FileSystemURL; +class FileWriterDelegate; + +// The interface class for FileSystemOperation implementations. +// +// This interface defines file system operations required to implement +// "File API: Directories and System" +// http://www.w3.org/TR/file-system-api/ +// +// DESIGN NOTES +// +// This class is designed to +// +// 1) Serve one-time file system operation per instance. Only one +// method(CreateFile, CreateDirectory, Copy, Move, DirectoryExists, +// GetMetadata, ReadDirectory and Remove) may be called during the +// lifetime of this object and it should be called no more than once. +// +// 2) Deliver the results of operations to the client via the callback function +// passed as the last parameter of the method. +// +// Note that it is valid to delete an operation while it is running. +// The callback will NOT be fired if the operation is deleted before +// it gets called. +class FileSystemOperation { + public: + STORAGE_EXPORT static FileSystemOperation* Create( + const FileSystemURL& url, + FileSystemContext* file_system_context, + scoped_ptr<FileSystemOperationContext> operation_context); + + virtual ~FileSystemOperation() {} + + // Used for CreateFile(), etc. |result| is the return code of the operation. + typedef base::Callback<void(base::File::Error result)> StatusCallback; + + // Used for GetMetadata(). |result| is the return code of the operation, + // |file_info| is the obtained file info. + typedef base::Callback< + void(base::File::Error result, + const base::File::Info& file_info)> GetMetadataCallback; + + // Used for OpenFile(). |on_close_callback| will be called after the file is + // closed in the child process. It can be null, if no operation is needed on + // closing a file. + typedef base::Callback< + void(base::File file, + const base::Closure& on_close_callback)> OpenFileCallback; + + // Used for ReadDirectoryCallback. + typedef std::vector<DirectoryEntry> FileEntryList; + + // Used for ReadDirectory(). |result| is the return code of the operation, + // |file_list| is the list of files read, and |has_more| is true if some files + // are yet to be read. + typedef base::Callback< + void(base::File::Error result, + const FileEntryList& file_list, + bool has_more)> ReadDirectoryCallback; + + // Used for CreateSnapshotFile(). (Please see the comment at + // CreateSnapshotFile() below for how the method is called) + // |result| is the return code of the operation. + // |file_info| is the metadata of the snapshot file created. + // |platform_path| is the path to the snapshot file created. + // + // The snapshot file could simply be of the local file pointed by the given + // filesystem URL in local filesystem cases; remote filesystems + // may want to download the file into a temporary snapshot file and then + // return the metadata of the temporary file. + // + // |file_ref| is used to manage the lifetime of the returned + // snapshot file. It can be set to let the chromium backend take + // care of the life time of the snapshot file. Otherwise (if the returned + // file does not require any handling) the implementation can just + // return NULL. In a more complex case, the implementaiton can manage + // the lifetime of the snapshot file on its own (e.g. by its cache system) + // but also can be notified via the reference when the file becomes no + // longer necessary in the javascript world. + // Please see the comment for ShareableFileReference for details. + // + typedef base::Callback< + void(base::File::Error result, + const base::File::Info& file_info, + const base::FilePath& platform_path, + const scoped_refptr<storage::ShareableFileReference>& file_ref)> + SnapshotFileCallback; + + // Used for progress update callback for Copy(). + // + // BEGIN_COPY_ENTRY is fired for each copy creation beginning (for both + // file and directory). + // The |source_url| is the URL of the source entry. |size| should not be + // used. + // + // END_COPY_ENTRY is fired for each copy creation finishing (for both + // file and directory). + // The |source_url| is the URL of the source entry. The |destination_url| is + // the URL of the destination entry. |size| should not be used. + // + // PROGRESS is fired periodically during file copying (not fired for + // directory copy). + // The |source_url| is the URL of the source file. |size| is the number + // of cumulative copied bytes for the currently copied file. + // Both at beginning and ending of file copying, PROGRESS event should be + // called. At beginning, |size| should be 0. At ending, |size| should be + // the size of the file. + // + // Here is an example callback sequence of recursive copy. Suppose + // there are a/b/c.txt (100 bytes) and a/b/d.txt (200 bytes), and trying to + // copy a to x recursively, then the progress update sequence will be: + // + // BEGIN_COPY_ENTRY a (starting create "a" directory in x/). + // END_COPY_ENTRY a x/a (creating "a" directory in x/ is finished). + // + // BEGIN_COPY_ENTRY a/b (starting create "b" directory in x/a). + // END_COPY_ENTRY a/b x/a/b (creating "b" directory in x/a/ is finished). + // + // BEGIN_COPY_ENTRY a/b/c.txt (starting to copy "c.txt" in x/a/b/). + // PROGRESS a/b/c.txt 0 (The first PROGRESS's |size| should be 0). + // PROGRESS a/b/c.txt 10 + // : + // PROGRESS a/b/c.txt 90 + // PROGRESS a/b/c.txt 100 (The last PROGRESS's |size| should be the size of + // the file). + // END_COPY_ENTRY a/b/c.txt x/a/b/c.txt (copying "c.txt" is finished). + // + // BEGIN_COPY_ENTRY a/b/d.txt (starting to copy "d.txt" in x/a/b). + // PROGRESS a/b/d.txt 0 (The first PROGRESS's |size| should be 0). + // PROGRESS a/b/d.txt 10 + // : + // PROGRESS a/b/d.txt 190 + // PROGRESS a/b/d.txt 200 (The last PROGRESS's |size| should be the size of + // the file). + // END_COPY_ENTRY a/b/d.txt x/a/b/d.txt (copy "d.txt" is finished). + // + // Note that event sequence of a/b/c.txt and a/b/d.txt can be interlaced, + // because they can be done in parallel. Also PROGRESS events are optional, + // so they may not be appeared. + // All the progress callback invocation should be done before StatusCallback + // given to the Copy is called. Especially if an error is found before first + // progres callback invocation, the progress callback may NOT invoked for the + // copy. + // + // Note for future extension. Currently this callback is only supported on + // Copy(). We can extend this to Move(), because Move() is sometimes + // implemented as "copy then delete." + // In more precise, Move() usually can be implemented either 1) by updating + // the metadata of resource (e.g. root of moving directory tree), or 2) by + // copying directory tree and them removing the source tree. + // For 1)'s case, we can simply add BEGIN_MOVE_ENTRY and END_MOVE_ENTRY + // for root directory. + // For 2)'s case, we can add BEGIN_DELETE_ENTRY and END_DELETE_ENTRY for each + // entry. + // For both cases, we probably won't need to use PROGRESS event because + // these operations should be done quickly (at least much faster than copying + // usually). + enum CopyProgressType { + BEGIN_COPY_ENTRY, + END_COPY_ENTRY, + PROGRESS, + }; + typedef base::Callback<void(CopyProgressType type, + const FileSystemURL& source_url, + const FileSystemURL& destination_url, + int64 size)> + CopyProgressCallback; + + // Used for CopyFileLocal() to report progress update. + // |size| is the cumulative copied bytes for the copy. + // At the beginning the progress callback should be called with |size| = 0, + // and also at the ending the progress callback should be called with |size| + // set to the copied file size. + typedef base::Callback<void(int64 size)> CopyFileProgressCallback; + + // The option for copy or move operation. + enum CopyOrMoveOption { + // No additional operation. + OPTION_NONE, + + // Preserves last modified time if possible. If the operation to update + // last modified time is not supported on the file system for the + // destination file, this option would be simply ignored (i.e. Copy would + // be successfully done without preserving last modified time). + OPTION_PRESERVE_LAST_MODIFIED, + }; + + // Used for Write(). + typedef base::Callback<void(base::File::Error result, + int64 bytes, + bool complete)> WriteCallback; + + // Creates a file at |path|. If |exclusive| is true, an error is raised + // in case a file is already present at the URL. + virtual void CreateFile(const FileSystemURL& path, + bool exclusive, + const StatusCallback& callback) = 0; + + // Creates a directory at |path|. If |exclusive| is true, an error is + // raised in case a directory is already present at the URL. If + // |recursive| is true, create parent directories as needed just like + // mkdir -p does. + virtual void CreateDirectory(const FileSystemURL& path, + bool exclusive, + bool recursive, + const StatusCallback& callback) = 0; + + // Copies a file or directory from |src_path| to |dest_path|. If + // |src_path| is a directory, the contents of |src_path| are copied to + // |dest_path| recursively. A new file or directory is created at + // |dest_path| as needed. + // |option| specifies the minor behavior of Copy(). See CopyOrMoveOption's + // comment for details. + // |progress_callback| is periodically called to report the progress + // update. See also the comment of CopyProgressCallback. This callback is + // optional. + // + // For recursive case this internally creates new FileSystemOperations and + // calls: + // - ReadDirectory, CopyFileLocal and CreateDirectory + // for same-filesystem case, or + // - ReadDirectory and CreateSnapshotFile on source filesystem and + // CopyInForeignFile and CreateDirectory on dest filesystem + // for cross-filesystem case. + // + virtual void Copy(const FileSystemURL& src_path, + const FileSystemURL& dest_path, + CopyOrMoveOption option, + const CopyProgressCallback& progress_callback, + const StatusCallback& callback) = 0; + + // Moves a file or directory from |src_path| to |dest_path|. A new file + // or directory is created at |dest_path| as needed. + // |option| specifies the minor behavior of Copy(). See CopyOrMoveOption's + // comment for details. + // + // For recursive case this internally creates new FileSystemOperations and + // calls: + // - ReadDirectory, MoveFileLocal, CreateDirectory and Remove + // for same-filesystem case, or + // - ReadDirectory, CreateSnapshotFile and Remove on source filesystem and + // CopyInForeignFile and CreateDirectory on dest filesystem + // for cross-filesystem case. + // + virtual void Move(const FileSystemURL& src_path, + const FileSystemURL& dest_path, + CopyOrMoveOption option, + const StatusCallback& callback) = 0; + + // Checks if a directory is present at |path|. + virtual void DirectoryExists(const FileSystemURL& path, + const StatusCallback& callback) = 0; + + // Checks if a file is present at |path|. + virtual void FileExists(const FileSystemURL& path, + const StatusCallback& callback) = 0; + + // Gets the metadata of a file or directory at |path|. + virtual void GetMetadata(const FileSystemURL& path, + const GetMetadataCallback& callback) = 0; + + // Reads contents of a directory at |path|. + virtual void ReadDirectory(const FileSystemURL& path, + const ReadDirectoryCallback& callback) = 0; + + // Removes a file or directory at |path|. If |recursive| is true, remove + // all files and directories under the directory at |path| recursively. + virtual void Remove(const FileSystemURL& path, bool recursive, + const StatusCallback& callback) = 0; + + // Writes the data read from |blob_request| using |writer_delegate|. + virtual void Write( + const FileSystemURL& url, + scoped_ptr<FileWriterDelegate> writer_delegate, + scoped_ptr<net::URLRequest> blob_request, + const WriteCallback& callback) = 0; + + // Truncates a file at |path| to |length|. If |length| is larger than + // the original file size, the file will be extended, and the extended + // part is filled with null bytes. + virtual void Truncate(const FileSystemURL& path, int64 length, + const StatusCallback& callback) = 0; + + // Tries to cancel the current operation [we support cancelling write or + // truncate only]. Reports failure for the current operation, then reports + // success for the cancel operation itself via the |cancel_dispatcher|. + // + // E.g. a typical cancel implementation would look like: + // + // virtual void SomeOperationImpl::Cancel( + // const StatusCallback& cancel_callback) { + // // Abort the current inflight operation first. + // ... + // + // // Dispatch ABORT error for the current operation by invoking + // // the callback function for the ongoing operation, + // operation_callback.Run(base::File::FILE_ERROR_ABORT, ...); + // + // // Dispatch 'success' for the cancel (or dispatch appropriate + // // error code with DidFail() if the cancel has somehow failed). + // cancel_callback.Run(base::File::FILE_OK); + // } + // + // Note that, for reporting failure, the callback function passed to a + // cancellable operations are kept around with the operation instance + // (as |operation_callback_| in the code example). + virtual void Cancel(const StatusCallback& cancel_callback) = 0; + + // Modifies timestamps of a file or directory at |path| with + // |last_access_time| and |last_modified_time|. The function DOES NOT + // create a file unlike 'touch' command on Linux. + // + // This function is used only by Pepper as of writing. + virtual void TouchFile(const FileSystemURL& path, + const base::Time& last_access_time, + const base::Time& last_modified_time, + const StatusCallback& callback) = 0; + + // Opens a file at |path| with |file_flags|, where flags are OR'ed + // values of base::File::Flags. + // + // This function is used only by Pepper as of writing. + virtual void OpenFile(const FileSystemURL& path, + int file_flags, + const OpenFileCallback& callback) = 0; + + // Creates a local snapshot file for a given |path| and returns the + // metadata and platform path of the snapshot file via |callback|. + // In local filesystem cases the implementation may simply return + // the metadata of the file itself (as well as GetMetadata does), + // while in remote filesystem case the backend may want to download the file + // into a temporary snapshot file and return the metadata of the + // temporary file. Or if the implementaiton already has the local cache + // data for |path| it can simply return the path to the cache. + virtual void CreateSnapshotFile(const FileSystemURL& path, + const SnapshotFileCallback& callback) = 0; + + // Copies in a single file from a different filesystem. + // + // This returns: + // - File::FILE_ERROR_NOT_FOUND if |src_file_path| + // or the parent directory of |dest_url| does not exist. + // - File::FILE_ERROR_INVALID_OPERATION if |dest_url| exists and + // is not a file. + // - File::FILE_ERROR_FAILED if |dest_url| does not exist and + // its parent path is a file. + // + virtual void CopyInForeignFile(const base::FilePath& src_local_disk_path, + const FileSystemURL& dest_url, + const StatusCallback& callback) = 0; + + // Removes a single file. + // + // This returns: + // - File::FILE_ERROR_NOT_FOUND if |url| does not exist. + // - File::FILE_ERROR_NOT_A_FILE if |url| is not a file. + // + virtual void RemoveFile(const FileSystemURL& url, + const StatusCallback& callback) = 0; + + // Removes a single empty directory. + // + // This returns: + // - File::FILE_ERROR_NOT_FOUND if |url| does not exist. + // - File::FILE_ERROR_NOT_A_DIRECTORY if |url| is not a directory. + // - File::FILE_ERROR_NOT_EMPTY if |url| is not empty. + // + virtual void RemoveDirectory(const FileSystemURL& url, + const StatusCallback& callback) = 0; + + // Copies a file from |src_url| to |dest_url|. + // This must be called for files that belong to the same filesystem + // (i.e. type() and origin() of the |src_url| and |dest_url| must match). + // |option| specifies the minor behavior of Copy(). See CopyOrMoveOption's + // comment for details. + // |progress_callback| is periodically called to report the progress + // update. See also the comment of CopyFileProgressCallback. This callback is + // optional. + // + // This returns: + // - File::FILE_ERROR_NOT_FOUND if |src_url| + // or the parent directory of |dest_url| does not exist. + // - File::FILE_ERROR_NOT_A_FILE if |src_url| exists but is not a file. + // - File::FILE_ERROR_INVALID_OPERATION if |dest_url| exists and + // is not a file. + // - File::FILE_ERROR_FAILED if |dest_url| does not exist and + // its parent path is a file. + // + virtual void CopyFileLocal(const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOption option, + const CopyFileProgressCallback& progress_callback, + const StatusCallback& callback) = 0; + + // Moves a local file from |src_url| to |dest_url|. + // This must be called for files that belong to the same filesystem + // (i.e. type() and origin() of the |src_url| and |dest_url| must match). + // |option| specifies the minor behavior of Copy(). See CopyOrMoveOption's + // comment for details. + // + // This returns: + // - File::FILE_ERROR_NOT_FOUND if |src_url| + // or the parent directory of |dest_url| does not exist. + // - File::FILE_ERROR_NOT_A_FILE if |src_url| exists but is not a file. + // - File::FILE_ERROR_INVALID_OPERATION if |dest_url| exists and + // is not a file. + // - File::FILE_ERROR_FAILED if |dest_url| does not exist and + // its parent path is a file. + // + virtual void MoveFileLocal(const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOption option, + const StatusCallback& callback) = 0; + + // Synchronously gets the platform path for the given |url|. + // This may fail if the given |url|'s filesystem type is neither + // temporary nor persistent. + // In such a case, base::File::FILE_ERROR_INVALID_OPERATION will be + // returned. + virtual base::File::Error SyncGetPlatformPath( + const FileSystemURL& url, + base::FilePath* platform_path) = 0; + + protected: + // Used only for internal assertions. + enum OperationType { + kOperationNone, + kOperationCreateFile, + kOperationCreateDirectory, + kOperationCreateSnapshotFile, + kOperationCopy, + kOperationCopyInForeignFile, + kOperationMove, + kOperationDirectoryExists, + kOperationFileExists, + kOperationGetMetadata, + kOperationReadDirectory, + kOperationRemove, + kOperationWrite, + kOperationTruncate, + kOperationTouchFile, + kOperationOpenFile, + kOperationCloseFile, + kOperationGetLocalPath, + kOperationCancel, + }; +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_H_ diff --git a/storage/browser/fileapi/file_system_operation_context.cc b/storage/browser/fileapi/file_system_operation_context.cc new file mode 100644 index 0000000..b392d7b --- /dev/null +++ b/storage/browser/fileapi/file_system_operation_context.cc @@ -0,0 +1,34 @@ +// 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 "storage/browser/fileapi/file_system_operation_context.h" + +#include "base/sequenced_task_runner.h" +#include "storage/browser/fileapi/file_system_context.h" + +namespace storage { + +FileSystemOperationContext::FileSystemOperationContext( + FileSystemContext* context) + : file_system_context_(context), + task_runner_(file_system_context_->default_file_task_runner()), + allowed_bytes_growth_(0), + quota_limit_type_(storage::kQuotaLimitTypeUnknown) { +} + +FileSystemOperationContext::FileSystemOperationContext( + FileSystemContext* context, + base::SequencedTaskRunner* task_runner) + : file_system_context_(context), + task_runner_(task_runner), + allowed_bytes_growth_(0), + quota_limit_type_(storage::kQuotaLimitTypeUnknown) { +} + +FileSystemOperationContext::~FileSystemOperationContext() { + DetachUserDataThread(); + setter_thread_checker_.DetachFromThread(); +} + +} // namespace storage diff --git a/storage/browser/fileapi/file_system_operation_context.h b/storage/browser/fileapi/file_system_operation_context.h new file mode 100644 index 0000000..ceecec1 --- /dev/null +++ b/storage/browser/fileapi/file_system_operation_context.h @@ -0,0 +1,97 @@ +// 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. + +#ifndef STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_CONTEXT_H_ +#define STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_CONTEXT_H_ + +#include "base/files/file_path.h" +#include "base/supports_user_data.h" +#include "base/threading/thread_checker.h" +#include "storage/browser/fileapi/task_runner_bound_observer_list.h" +#include "storage/browser/storage_browser_export.h" +#include "storage/common/quota/quota_types.h" + +namespace base { +class SequencedTaskRunner; +} + +namespace storage { + +class FileSystemContext; + +// A context class which is carried around by FileSystemOperation and +// its delegated tasks. It is valid to reuse one context instance across +// multiple operations as far as those operations are supposed to share +// the same context (e.g. use the same task runner, share the quota etc). +// Note that the remaining quota bytes (allowed_bytes_growth) may be +// updated during the execution of write operations. +class STORAGE_EXPORT_PRIVATE FileSystemOperationContext + : public base::SupportsUserData { + public: + explicit FileSystemOperationContext(FileSystemContext* context); + + // Specifies |task_runner| which the operation is performed on. + // The backend of |task_runner| must outlive the IO thread. + FileSystemOperationContext(FileSystemContext* context, + base::SequencedTaskRunner* task_runner); + + virtual ~FileSystemOperationContext(); + + FileSystemContext* file_system_context() const { + return file_system_context_.get(); + } + + // Updates the current remaining quota. + // This can be called to update the remaining quota during the operation. + void set_allowed_bytes_growth(const int64& allowed_bytes_growth) { + allowed_bytes_growth_ = allowed_bytes_growth; + } + + // Returns the current remaining quota. + int64 allowed_bytes_growth() const { return allowed_bytes_growth_; } + storage::QuotaLimitType quota_limit_type() const { return quota_limit_type_; } + base::SequencedTaskRunner* task_runner() const { return task_runner_.get(); } + + ChangeObserverList* change_observers() { return &change_observers_; } + UpdateObserverList* update_observers() { return &update_observers_; } + + // Following setters should be called only on the same thread as the + // FileSystemOperationContext is created (i.e. are not supposed be updated + // after the context's passed onto other task runners). + void set_change_observers(const ChangeObserverList& list) { + DCHECK(setter_thread_checker_.CalledOnValidThread()); + change_observers_ = list; + } + void set_update_observers(const UpdateObserverList& list) { + DCHECK(setter_thread_checker_.CalledOnValidThread()); + update_observers_ = list; + } + void set_quota_limit_type(storage::QuotaLimitType limit_type) { + DCHECK(setter_thread_checker_.CalledOnValidThread()); + quota_limit_type_ = limit_type; + } + + private: + scoped_refptr<FileSystemContext> file_system_context_; + scoped_refptr<base::SequencedTaskRunner> task_runner_; + + // The current remaining quota, used by ObfuscatedFileUtil. + int64 allowed_bytes_growth_; + + // The current quota limit type, used by ObfuscatedFileUtil. + storage::QuotaLimitType quota_limit_type_; + + // Observers attached to this context. + ChangeObserverList change_observers_; + UpdateObserverList update_observers_; + + // Used to check its setters are not called on arbitrary thread. + base::ThreadChecker setter_thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(FileSystemOperationContext); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_CONTEXT_H_ diff --git a/storage/browser/fileapi/file_system_operation_impl.cc b/storage/browser/fileapi/file_system_operation_impl.cc new file mode 100644 index 0000000..75985f4 --- /dev/null +++ b/storage/browser/fileapi/file_system_operation_impl.cc @@ -0,0 +1,559 @@ +// Copyright 2013 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 "storage/browser/fileapi/file_system_operation_impl.h" + +#include "base/bind.h" +#include "base/single_thread_task_runner.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "net/base/escape.h" +#include "net/url_request/url_request.h" +#include "storage/browser/fileapi/async_file_util.h" +#include "storage/browser/fileapi/copy_or_move_operation_delegate.h" +#include "storage/browser/fileapi/file_observers.h" +#include "storage/browser/fileapi/file_system_backend.h" +#include "storage/browser/fileapi/file_system_context.h" +#include "storage/browser/fileapi/file_system_file_util.h" +#include "storage/browser/fileapi/file_system_operation_context.h" +#include "storage/browser/fileapi/file_system_url.h" +#include "storage/browser/fileapi/file_writer_delegate.h" +#include "storage/browser/fileapi/remove_operation_delegate.h" +#include "storage/browser/fileapi/sandbox_file_system_backend.h" +#include "storage/browser/quota/quota_manager_proxy.h" +#include "storage/common/blob/shareable_file_reference.h" +#include "storage/common/fileapi/file_system_types.h" +#include "storage/common/fileapi/file_system_util.h" +#include "storage/common/quota/quota_types.h" + +using storage::ScopedFile; + +namespace storage { + +FileSystemOperation* FileSystemOperation::Create( + const FileSystemURL& url, + FileSystemContext* file_system_context, + scoped_ptr<FileSystemOperationContext> operation_context) { + return new FileSystemOperationImpl(url, file_system_context, + operation_context.Pass()); +} + +FileSystemOperationImpl::~FileSystemOperationImpl() { +} + +void FileSystemOperationImpl::CreateFile(const FileSystemURL& url, + bool exclusive, + const StatusCallback& callback) { + DCHECK(SetPendingOperationType(kOperationCreateFile)); + GetUsageAndQuotaThenRunTask( + url, + base::Bind(&FileSystemOperationImpl::DoCreateFile, + weak_factory_.GetWeakPtr(), url, callback, exclusive), + base::Bind(callback, base::File::FILE_ERROR_FAILED)); +} + +void FileSystemOperationImpl::CreateDirectory(const FileSystemURL& url, + bool exclusive, + bool recursive, + const StatusCallback& callback) { + DCHECK(SetPendingOperationType(kOperationCreateDirectory)); + GetUsageAndQuotaThenRunTask( + url, + base::Bind(&FileSystemOperationImpl::DoCreateDirectory, + weak_factory_.GetWeakPtr(), url, callback, + exclusive, recursive), + base::Bind(callback, base::File::FILE_ERROR_FAILED)); +} + +void FileSystemOperationImpl::Copy( + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOption option, + const CopyProgressCallback& progress_callback, + const StatusCallback& callback) { + DCHECK(SetPendingOperationType(kOperationCopy)); + DCHECK(!recursive_operation_delegate_); + + // TODO(hidehiko): Support |progress_callback|. (crbug.com/278038). + recursive_operation_delegate_.reset( + new CopyOrMoveOperationDelegate( + file_system_context(), + src_url, dest_url, + CopyOrMoveOperationDelegate::OPERATION_COPY, + option, + progress_callback, + base::Bind(&FileSystemOperationImpl::DidFinishOperation, + weak_factory_.GetWeakPtr(), callback))); + recursive_operation_delegate_->RunRecursively(); +} + +void FileSystemOperationImpl::Move(const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOption option, + const StatusCallback& callback) { + DCHECK(SetPendingOperationType(kOperationMove)); + DCHECK(!recursive_operation_delegate_); + recursive_operation_delegate_.reset( + new CopyOrMoveOperationDelegate( + file_system_context(), + src_url, dest_url, + CopyOrMoveOperationDelegate::OPERATION_MOVE, + option, + FileSystemOperation::CopyProgressCallback(), + base::Bind(&FileSystemOperationImpl::DidFinishOperation, + weak_factory_.GetWeakPtr(), callback))); + recursive_operation_delegate_->RunRecursively(); +} + +void FileSystemOperationImpl::DirectoryExists(const FileSystemURL& url, + const StatusCallback& callback) { + DCHECK(SetPendingOperationType(kOperationDirectoryExists)); + async_file_util_->GetFileInfo( + operation_context_.Pass(), url, + base::Bind(&FileSystemOperationImpl::DidDirectoryExists, + weak_factory_.GetWeakPtr(), callback)); +} + +void FileSystemOperationImpl::FileExists(const FileSystemURL& url, + const StatusCallback& callback) { + DCHECK(SetPendingOperationType(kOperationFileExists)); + async_file_util_->GetFileInfo( + operation_context_.Pass(), url, + base::Bind(&FileSystemOperationImpl::DidFileExists, + weak_factory_.GetWeakPtr(), callback)); +} + +void FileSystemOperationImpl::GetMetadata( + const FileSystemURL& url, const GetMetadataCallback& callback) { + DCHECK(SetPendingOperationType(kOperationGetMetadata)); + async_file_util_->GetFileInfo(operation_context_.Pass(), url, callback); +} + +void FileSystemOperationImpl::ReadDirectory( + const FileSystemURL& url, const ReadDirectoryCallback& callback) { + DCHECK(SetPendingOperationType(kOperationReadDirectory)); + async_file_util_->ReadDirectory( + operation_context_.Pass(), url, callback); +} + +void FileSystemOperationImpl::Remove(const FileSystemURL& url, + bool recursive, + const StatusCallback& callback) { + DCHECK(SetPendingOperationType(kOperationRemove)); + DCHECK(!recursive_operation_delegate_); + + if (recursive) { + // For recursive removal, try to delegate the operation to AsyncFileUtil + // first. If not supported, it is delegated to RemoveOperationDelegate + // in DidDeleteRecursively. + async_file_util_->DeleteRecursively( + operation_context_.Pass(), url, + base::Bind(&FileSystemOperationImpl::DidDeleteRecursively, + weak_factory_.GetWeakPtr(), url, callback)); + return; + } + + recursive_operation_delegate_.reset( + new RemoveOperationDelegate( + file_system_context(), url, + base::Bind(&FileSystemOperationImpl::DidFinishOperation, + weak_factory_.GetWeakPtr(), callback))); + recursive_operation_delegate_->Run(); +} + +void FileSystemOperationImpl::Write( + const FileSystemURL& url, + scoped_ptr<FileWriterDelegate> writer_delegate, + scoped_ptr<net::URLRequest> blob_request, + const WriteCallback& callback) { + DCHECK(SetPendingOperationType(kOperationWrite)); + file_writer_delegate_ = writer_delegate.Pass(); + file_writer_delegate_->Start( + blob_request.Pass(), + base::Bind(&FileSystemOperationImpl::DidWrite, + weak_factory_.GetWeakPtr(), url, callback)); +} + +void FileSystemOperationImpl::Truncate(const FileSystemURL& url, int64 length, + const StatusCallback& callback) { + DCHECK(SetPendingOperationType(kOperationTruncate)); + GetUsageAndQuotaThenRunTask( + url, + base::Bind(&FileSystemOperationImpl::DoTruncate, + weak_factory_.GetWeakPtr(), url, callback, length), + base::Bind(callback, base::File::FILE_ERROR_FAILED)); +} + +void FileSystemOperationImpl::TouchFile(const FileSystemURL& url, + const base::Time& last_access_time, + const base::Time& last_modified_time, + const StatusCallback& callback) { + DCHECK(SetPendingOperationType(kOperationTouchFile)); + async_file_util_->Touch( + operation_context_.Pass(), url, + last_access_time, last_modified_time, + base::Bind(&FileSystemOperationImpl::DidFinishOperation, + weak_factory_.GetWeakPtr(), callback)); +} + +void FileSystemOperationImpl::OpenFile(const FileSystemURL& url, + int file_flags, + const OpenFileCallback& callback) { + DCHECK(SetPendingOperationType(kOperationOpenFile)); + + if (file_flags & + (base::File::FLAG_TEMPORARY | base::File::FLAG_HIDDEN)) { + callback.Run(base::File(base::File::FILE_ERROR_FAILED), + base::Closure()); + return; + } + GetUsageAndQuotaThenRunTask( + url, + base::Bind(&FileSystemOperationImpl::DoOpenFile, + weak_factory_.GetWeakPtr(), + url, callback, file_flags), + base::Bind(callback, Passed(base::File(base::File::FILE_ERROR_FAILED)), + base::Closure())); +} + +// We can only get here on a write or truncate that's not yet completed. +// We don't support cancelling any other operation at this time. +void FileSystemOperationImpl::Cancel(const StatusCallback& cancel_callback) { + DCHECK(cancel_callback_.is_null()); + cancel_callback_ = cancel_callback; + + if (file_writer_delegate_.get()) { + DCHECK_EQ(kOperationWrite, pending_operation_); + // This will call DidWrite() with ABORT status code. + file_writer_delegate_->Cancel(); + } else if (recursive_operation_delegate_) { + // This will call DidFinishOperation() with ABORT status code. + recursive_operation_delegate_->Cancel(); + } else { + // For truncate we have no way to cancel the inflight operation (for now). + // Let it just run and dispatch cancel callback later. + DCHECK_EQ(kOperationTruncate, pending_operation_); + } +} + +void FileSystemOperationImpl::CreateSnapshotFile( + const FileSystemURL& url, + const SnapshotFileCallback& callback) { + DCHECK(SetPendingOperationType(kOperationCreateSnapshotFile)); + async_file_util_->CreateSnapshotFile( + operation_context_.Pass(), url, callback); +} + +void FileSystemOperationImpl::CopyInForeignFile( + const base::FilePath& src_local_disk_file_path, + const FileSystemURL& dest_url, + const StatusCallback& callback) { + DCHECK(SetPendingOperationType(kOperationCopyInForeignFile)); + GetUsageAndQuotaThenRunTask( + dest_url, + base::Bind(&FileSystemOperationImpl::DoCopyInForeignFile, + weak_factory_.GetWeakPtr(), src_local_disk_file_path, dest_url, + callback), + base::Bind(callback, base::File::FILE_ERROR_FAILED)); +} + +void FileSystemOperationImpl::RemoveFile( + const FileSystemURL& url, + const StatusCallback& callback) { + DCHECK(SetPendingOperationType(kOperationRemove)); + async_file_util_->DeleteFile( + operation_context_.Pass(), url, + base::Bind(&FileSystemOperationImpl::DidFinishOperation, + weak_factory_.GetWeakPtr(), callback)); +} + +void FileSystemOperationImpl::RemoveDirectory( + const FileSystemURL& url, + const StatusCallback& callback) { + DCHECK(SetPendingOperationType(kOperationRemove)); + async_file_util_->DeleteDirectory( + operation_context_.Pass(), url, + base::Bind(&FileSystemOperationImpl::DidFinishOperation, + weak_factory_.GetWeakPtr(), callback)); +} + +void FileSystemOperationImpl::CopyFileLocal( + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOption option, + const CopyFileProgressCallback& progress_callback, + const StatusCallback& callback) { + DCHECK(SetPendingOperationType(kOperationCopy)); + DCHECK(src_url.IsInSameFileSystem(dest_url)); + + GetUsageAndQuotaThenRunTask( + dest_url, + base::Bind(&FileSystemOperationImpl::DoCopyFileLocal, + weak_factory_.GetWeakPtr(), src_url, dest_url, option, + progress_callback, callback), + base::Bind(callback, base::File::FILE_ERROR_FAILED)); +} + +void FileSystemOperationImpl::MoveFileLocal( + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOption option, + const StatusCallback& callback) { + DCHECK(SetPendingOperationType(kOperationMove)); + DCHECK(src_url.IsInSameFileSystem(dest_url)); + GetUsageAndQuotaThenRunTask( + dest_url, + base::Bind(&FileSystemOperationImpl::DoMoveFileLocal, + weak_factory_.GetWeakPtr(), + src_url, dest_url, option, callback), + base::Bind(callback, base::File::FILE_ERROR_FAILED)); +} + +base::File::Error FileSystemOperationImpl::SyncGetPlatformPath( + const FileSystemURL& url, + base::FilePath* platform_path) { + DCHECK(SetPendingOperationType(kOperationGetLocalPath)); + if (!file_system_context()->IsSandboxFileSystem(url.type())) + return base::File::FILE_ERROR_INVALID_OPERATION; + FileSystemFileUtil* file_util = + file_system_context()->sandbox_delegate()->sync_file_util(); + file_util->GetLocalFilePath(operation_context_.get(), url, platform_path); + return base::File::FILE_OK; +} + +FileSystemOperationImpl::FileSystemOperationImpl( + const FileSystemURL& url, + FileSystemContext* file_system_context, + scoped_ptr<FileSystemOperationContext> operation_context) + : file_system_context_(file_system_context), + operation_context_(operation_context.Pass()), + async_file_util_(NULL), + pending_operation_(kOperationNone), + weak_factory_(this) { + DCHECK(operation_context_.get()); + operation_context_->DetachUserDataThread(); + async_file_util_ = file_system_context_->GetAsyncFileUtil(url.type()); + DCHECK(async_file_util_); +} + +void FileSystemOperationImpl::GetUsageAndQuotaThenRunTask( + const FileSystemURL& url, + const base::Closure& task, + const base::Closure& error_callback) { + storage::QuotaManagerProxy* quota_manager_proxy = + file_system_context()->quota_manager_proxy(); + if (!quota_manager_proxy || + !file_system_context()->GetQuotaUtil(url.type())) { + // If we don't have the quota manager or the requested filesystem type + // does not support quota, we should be able to let it go. + operation_context_->set_allowed_bytes_growth(kint64max); + task.Run(); + return; + } + + DCHECK(quota_manager_proxy); + DCHECK(quota_manager_proxy->quota_manager()); + quota_manager_proxy->quota_manager()->GetUsageAndQuota( + url.origin(), + FileSystemTypeToQuotaStorageType(url.type()), + base::Bind(&FileSystemOperationImpl::DidGetUsageAndQuotaAndRunTask, + weak_factory_.GetWeakPtr(), task, error_callback)); +} + +void FileSystemOperationImpl::DidGetUsageAndQuotaAndRunTask( + const base::Closure& task, + const base::Closure& error_callback, + storage::QuotaStatusCode status, + int64 usage, + int64 quota) { + if (status != storage::kQuotaStatusOk) { + LOG(WARNING) << "Got unexpected quota error : " << status; + error_callback.Run(); + return; + } + + operation_context_->set_allowed_bytes_growth(quota - usage); + task.Run(); +} + +void FileSystemOperationImpl::DoCreateFile( + const FileSystemURL& url, + const StatusCallback& callback, + bool exclusive) { + async_file_util_->EnsureFileExists( + operation_context_.Pass(), url, + base::Bind( + exclusive ? + &FileSystemOperationImpl::DidEnsureFileExistsExclusive : + &FileSystemOperationImpl::DidEnsureFileExistsNonExclusive, + weak_factory_.GetWeakPtr(), callback)); +} + +void FileSystemOperationImpl::DoCreateDirectory( + const FileSystemURL& url, + const StatusCallback& callback, + bool exclusive, bool recursive) { + async_file_util_->CreateDirectory( + operation_context_.Pass(), + url, exclusive, recursive, + base::Bind(&FileSystemOperationImpl::DidFinishOperation, + weak_factory_.GetWeakPtr(), callback)); +} + +void FileSystemOperationImpl::DoCopyFileLocal( + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOption option, + const CopyFileProgressCallback& progress_callback, + const StatusCallback& callback) { + async_file_util_->CopyFileLocal( + operation_context_.Pass(), src_url, dest_url, option, progress_callback, + base::Bind(&FileSystemOperationImpl::DidFinishOperation, + weak_factory_.GetWeakPtr(), callback)); +} + +void FileSystemOperationImpl::DoMoveFileLocal( + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOption option, + const StatusCallback& callback) { + async_file_util_->MoveFileLocal( + operation_context_.Pass(), src_url, dest_url, option, + base::Bind(&FileSystemOperationImpl::DidFinishOperation, + weak_factory_.GetWeakPtr(), callback)); +} + +void FileSystemOperationImpl::DoCopyInForeignFile( + const base::FilePath& src_local_disk_file_path, + const FileSystemURL& dest_url, + const StatusCallback& callback) { + async_file_util_->CopyInForeignFile( + operation_context_.Pass(), + src_local_disk_file_path, dest_url, + base::Bind(&FileSystemOperationImpl::DidFinishOperation, + weak_factory_.GetWeakPtr(), callback)); +} + +void FileSystemOperationImpl::DoTruncate(const FileSystemURL& url, + const StatusCallback& callback, + int64 length) { + async_file_util_->Truncate( + operation_context_.Pass(), url, length, + base::Bind(&FileSystemOperationImpl::DidFinishOperation, + weak_factory_.GetWeakPtr(), callback)); +} + +void FileSystemOperationImpl::DoOpenFile(const FileSystemURL& url, + const OpenFileCallback& callback, + int file_flags) { + async_file_util_->CreateOrOpen( + operation_context_.Pass(), url, file_flags, + base::Bind(&FileSystemOperationImpl::DidOpenFile, + weak_factory_.GetWeakPtr(), callback)); +} + +void FileSystemOperationImpl::DidEnsureFileExistsExclusive( + const StatusCallback& callback, + base::File::Error rv, bool created) { + if (rv == base::File::FILE_OK && !created) { + callback.Run(base::File::FILE_ERROR_EXISTS); + } else { + DidFinishOperation(callback, rv); + } +} + +void FileSystemOperationImpl::DidEnsureFileExistsNonExclusive( + const StatusCallback& callback, + base::File::Error rv, bool /* created */) { + DidFinishOperation(callback, rv); +} + +void FileSystemOperationImpl::DidFinishOperation( + const StatusCallback& callback, + base::File::Error rv) { + if (!cancel_callback_.is_null()) { + StatusCallback cancel_callback = cancel_callback_; + callback.Run(rv); + + // Return OK only if we succeeded to stop the operation. + cancel_callback.Run(rv == base::File::FILE_ERROR_ABORT ? + base::File::FILE_OK : + base::File::FILE_ERROR_INVALID_OPERATION); + } else { + callback.Run(rv); + } +} + +void FileSystemOperationImpl::DidDirectoryExists( + const StatusCallback& callback, + base::File::Error rv, + const base::File::Info& file_info) { + if (rv == base::File::FILE_OK && !file_info.is_directory) + rv = base::File::FILE_ERROR_NOT_A_DIRECTORY; + callback.Run(rv); +} + +void FileSystemOperationImpl::DidFileExists( + const StatusCallback& callback, + base::File::Error rv, + const base::File::Info& file_info) { + if (rv == base::File::FILE_OK && file_info.is_directory) + rv = base::File::FILE_ERROR_NOT_A_FILE; + callback.Run(rv); +} + +void FileSystemOperationImpl::DidDeleteRecursively( + const FileSystemURL& url, + const StatusCallback& callback, + base::File::Error rv) { + if (rv == base::File::FILE_ERROR_INVALID_OPERATION) { + // Recursive removal is not supported on this platform. + DCHECK(!recursive_operation_delegate_); + recursive_operation_delegate_.reset( + new RemoveOperationDelegate( + file_system_context(), url, + base::Bind(&FileSystemOperationImpl::DidFinishOperation, + weak_factory_.GetWeakPtr(), callback))); + recursive_operation_delegate_->RunRecursively(); + return; + } + + callback.Run(rv); +} + +void FileSystemOperationImpl::DidWrite( + const FileSystemURL& url, + const WriteCallback& write_callback, + base::File::Error rv, + int64 bytes, + FileWriterDelegate::WriteProgressStatus write_status) { + const bool complete = ( + write_status != FileWriterDelegate::SUCCESS_IO_PENDING); + if (complete && write_status != FileWriterDelegate::ERROR_WRITE_NOT_STARTED) { + DCHECK(operation_context_); + operation_context_->change_observers()->Notify( + &FileChangeObserver::OnModifyFile, MakeTuple(url)); + } + + StatusCallback cancel_callback = cancel_callback_; + write_callback.Run(rv, bytes, complete); + if (!cancel_callback.is_null()) + cancel_callback.Run(base::File::FILE_OK); +} + +void FileSystemOperationImpl::DidOpenFile( + const OpenFileCallback& callback, + base::File file, + const base::Closure& on_close_callback) { + callback.Run(file.Pass(), on_close_callback); +} + +bool FileSystemOperationImpl::SetPendingOperationType(OperationType type) { + if (pending_operation_ != kOperationNone) + return false; + pending_operation_ = type; + return true; +} + +} // namespace storage diff --git a/storage/browser/fileapi/file_system_operation_impl.h b/storage/browser/fileapi/file_system_operation_impl.h new file mode 100644 index 0000000..76cf436 --- /dev/null +++ b/storage/browser/fileapi/file_system_operation_impl.h @@ -0,0 +1,206 @@ +// Copyright 2013 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. + +#ifndef STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_IMPL_H_ +#define STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_IMPL_H_ + +#include <vector> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "storage/browser/fileapi/file_system_operation.h" +#include "storage/browser/fileapi/file_system_operation_context.h" +#include "storage/browser/fileapi/file_system_url.h" +#include "storage/browser/fileapi/file_writer_delegate.h" +#include "storage/browser/storage_browser_export.h" +#include "storage/common/blob/scoped_file.h" +#include "storage/common/quota/quota_types.h" + +namespace storage { + +class AsyncFileUtil; +class FileSystemContext; +class RecursiveOperationDelegate; + +// The default implementation of FileSystemOperation for file systems. +class STORAGE_EXPORT FileSystemOperationImpl + : public NON_EXPORTED_BASE(FileSystemOperation) { + public: + virtual ~FileSystemOperationImpl(); + + // FileSystemOperation overrides. + virtual void CreateFile(const FileSystemURL& url, + bool exclusive, + const StatusCallback& callback) OVERRIDE; + virtual void CreateDirectory(const FileSystemURL& url, + bool exclusive, + bool recursive, + const StatusCallback& callback) OVERRIDE; + virtual void Copy(const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOption option, + const CopyProgressCallback& progress_callback, + const StatusCallback& callback) OVERRIDE; + virtual void Move(const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOption option, + const StatusCallback& callback) OVERRIDE; + virtual void DirectoryExists(const FileSystemURL& url, + const StatusCallback& callback) OVERRIDE; + virtual void FileExists(const FileSystemURL& url, + const StatusCallback& callback) OVERRIDE; + virtual void GetMetadata(const FileSystemURL& url, + const GetMetadataCallback& callback) OVERRIDE; + virtual void ReadDirectory(const FileSystemURL& url, + const ReadDirectoryCallback& callback) OVERRIDE; + virtual void Remove(const FileSystemURL& url, bool recursive, + const StatusCallback& callback) OVERRIDE; + virtual void Write(const FileSystemURL& url, + scoped_ptr<FileWriterDelegate> writer_delegate, + scoped_ptr<net::URLRequest> blob_request, + const WriteCallback& callback) OVERRIDE; + virtual void Truncate(const FileSystemURL& url, int64 length, + const StatusCallback& callback) OVERRIDE; + virtual void TouchFile(const FileSystemURL& url, + const base::Time& last_access_time, + const base::Time& last_modified_time, + const StatusCallback& callback) OVERRIDE; + virtual void OpenFile(const FileSystemURL& url, + int file_flags, + const OpenFileCallback& callback) OVERRIDE; + virtual void Cancel(const StatusCallback& cancel_callback) OVERRIDE; + virtual void CreateSnapshotFile( + const FileSystemURL& path, + const SnapshotFileCallback& callback) OVERRIDE; + virtual void CopyInForeignFile(const base::FilePath& src_local_disk_path, + const FileSystemURL& dest_url, + const StatusCallback& callback) OVERRIDE; + virtual void RemoveFile(const FileSystemURL& url, + const StatusCallback& callback) OVERRIDE; + virtual void RemoveDirectory(const FileSystemURL& url, + const StatusCallback& callback) OVERRIDE; + virtual void CopyFileLocal(const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOption option, + const CopyFileProgressCallback& progress_callback, + const StatusCallback& callback) OVERRIDE; + virtual void MoveFileLocal(const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOption option, + const StatusCallback& callback) OVERRIDE; + virtual base::File::Error SyncGetPlatformPath( + const FileSystemURL& url, + base::FilePath* platform_path) OVERRIDE; + + FileSystemContext* file_system_context() const { + return file_system_context_.get(); + } + + private: + friend class FileSystemOperation; + + FileSystemOperationImpl( + const FileSystemURL& url, + FileSystemContext* file_system_context, + scoped_ptr<FileSystemOperationContext> operation_context); + + // Queries the quota and usage and then runs the given |task|. + // If an error occurs during the quota query it runs |error_callback| instead. + void GetUsageAndQuotaThenRunTask( + const FileSystemURL& url, + const base::Closure& task, + const base::Closure& error_callback); + + // Called after the quota info is obtained from the quota manager + // (which is triggered by GetUsageAndQuotaThenRunTask). + // Sets the quota info in the operation_context_ and then runs the given + // |task| if the returned quota status is successful, otherwise runs + // |error_callback|. + void DidGetUsageAndQuotaAndRunTask(const base::Closure& task, + const base::Closure& error_callback, + storage::QuotaStatusCode status, + int64 usage, + int64 quota); + + // The 'body' methods that perform the actual work (i.e. posting the + // file task on proxy_) after the quota check. + void DoCreateFile(const FileSystemURL& url, + const StatusCallback& callback, bool exclusive); + void DoCreateDirectory(const FileSystemURL& url, + const StatusCallback& callback, + bool exclusive, + bool recursive); + void DoCopyFileLocal(const FileSystemURL& src, + const FileSystemURL& dest, + CopyOrMoveOption option, + const CopyFileProgressCallback& progress_callback, + const StatusCallback& callback); + void DoMoveFileLocal(const FileSystemURL& src, + const FileSystemURL& dest, + CopyOrMoveOption option, + const StatusCallback& callback); + void DoCopyInForeignFile(const base::FilePath& src_local_disk_file_path, + const FileSystemURL& dest, + const StatusCallback& callback); + void DoTruncate(const FileSystemURL& url, + const StatusCallback& callback, int64 length); + void DoOpenFile(const FileSystemURL& url, + const OpenFileCallback& callback, int file_flags); + + // Callback for CreateFile for |exclusive|=true cases. + void DidEnsureFileExistsExclusive(const StatusCallback& callback, + base::File::Error rv, + bool created); + + // Callback for CreateFile for |exclusive|=false cases. + void DidEnsureFileExistsNonExclusive(const StatusCallback& callback, + base::File::Error rv, + bool created); + + void DidFinishOperation(const StatusCallback& callback, + base::File::Error rv); + void DidDirectoryExists(const StatusCallback& callback, + base::File::Error rv, + const base::File::Info& file_info); + void DidFileExists(const StatusCallback& callback, + base::File::Error rv, + const base::File::Info& file_info); + void DidDeleteRecursively(const FileSystemURL& url, + const StatusCallback& callback, + base::File::Error rv); + void DidWrite(const FileSystemURL& url, + const WriteCallback& callback, + base::File::Error rv, + int64 bytes, + FileWriterDelegate::WriteProgressStatus write_status); + void DidOpenFile(const OpenFileCallback& callback, + base::File file, + const base::Closure& on_close_callback); + + // Used only for internal assertions. + // Returns false if there's another inflight pending operation. + bool SetPendingOperationType(OperationType type); + + scoped_refptr<FileSystemContext> file_system_context_; + + scoped_ptr<FileSystemOperationContext> operation_context_; + AsyncFileUtil* async_file_util_; // Not owned. + + scoped_ptr<FileWriterDelegate> file_writer_delegate_; + scoped_ptr<RecursiveOperationDelegate> recursive_operation_delegate_; + + StatusCallback cancel_callback_; + + // A flag to make sure we call operation only once per instance. + OperationType pending_operation_; + + base::WeakPtrFactory<FileSystemOperationImpl> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(FileSystemOperationImpl); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_IMPL_H_ diff --git a/storage/browser/fileapi/file_system_operation_runner.cc b/storage/browser/fileapi/file_system_operation_runner.cc new file mode 100644 index 0000000..58ccb94 --- /dev/null +++ b/storage/browser/fileapi/file_system_operation_runner.cc @@ -0,0 +1,687 @@ +// Copyright 2013 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 "storage/browser/fileapi/file_system_operation_runner.h" + +#include "base/bind.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/stl_util.h" +#include "net/url_request/url_request_context.h" +#include "storage/browser/blob/blob_url_request_job_factory.h" +#include "storage/browser/fileapi/file_observers.h" +#include "storage/browser/fileapi/file_stream_writer.h" +#include "storage/browser/fileapi/file_system_context.h" +#include "storage/browser/fileapi/file_system_operation.h" +#include "storage/browser/fileapi/file_writer_delegate.h" +#include "storage/common/blob/shareable_file_reference.h" + +namespace storage { + +typedef FileSystemOperationRunner::OperationID OperationID; + +class FileSystemOperationRunner::BeginOperationScoper + : public base::SupportsWeakPtr< + FileSystemOperationRunner::BeginOperationScoper> { + public: + BeginOperationScoper() {} + private: + DISALLOW_COPY_AND_ASSIGN(BeginOperationScoper); +}; + +FileSystemOperationRunner::OperationHandle::OperationHandle() {} +FileSystemOperationRunner::OperationHandle::~OperationHandle() {} + +FileSystemOperationRunner::~FileSystemOperationRunner() { +} + +void FileSystemOperationRunner::Shutdown() { + operations_.Clear(); +} + +OperationID FileSystemOperationRunner::CreateFile( + const FileSystemURL& url, + bool exclusive, + const StatusCallback& callback) { + base::File::Error error = base::File::FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(url, &error); + + BeginOperationScoper scope; + OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr()); + if (!operation) { + DidFinish(handle, callback, error); + return handle.id; + } + PrepareForWrite(handle.id, url); + operation->CreateFile( + url, exclusive, + base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(), + handle, callback)); + return handle.id; +} + +OperationID FileSystemOperationRunner::CreateDirectory( + const FileSystemURL& url, + bool exclusive, + bool recursive, + const StatusCallback& callback) { + base::File::Error error = base::File::FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(url, &error); + BeginOperationScoper scope; + OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr()); + if (!operation) { + DidFinish(handle, callback, error); + return handle.id; + } + PrepareForWrite(handle.id, url); + operation->CreateDirectory( + url, exclusive, recursive, + base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(), + handle, callback)); + return handle.id; +} + +OperationID FileSystemOperationRunner::Copy( + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOption option, + const CopyProgressCallback& progress_callback, + const StatusCallback& callback) { + base::File::Error error = base::File::FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(dest_url, &error); + BeginOperationScoper scope; + OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr()); + if (!operation) { + DidFinish(handle, callback, error); + return handle.id; + } + PrepareForWrite(handle.id, dest_url); + PrepareForRead(handle.id, src_url); + operation->Copy( + src_url, dest_url, option, + progress_callback.is_null() ? + CopyProgressCallback() : + base::Bind(&FileSystemOperationRunner::OnCopyProgress, AsWeakPtr(), + handle, progress_callback), + base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(), + handle, callback)); + return handle.id; +} + +OperationID FileSystemOperationRunner::Move( + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOption option, + const StatusCallback& callback) { + base::File::Error error = base::File::FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(dest_url, &error); + BeginOperationScoper scope; + OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr()); + if (!operation) { + DidFinish(handle, callback, error); + return handle.id; + } + PrepareForWrite(handle.id, dest_url); + PrepareForWrite(handle.id, src_url); + operation->Move( + src_url, dest_url, option, + base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(), + handle, callback)); + return handle.id; +} + +OperationID FileSystemOperationRunner::DirectoryExists( + const FileSystemURL& url, + const StatusCallback& callback) { + base::File::Error error = base::File::FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(url, &error); + BeginOperationScoper scope; + OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr()); + if (!operation) { + DidFinish(handle, callback, error); + return handle.id; + } + PrepareForRead(handle.id, url); + operation->DirectoryExists( + url, + base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(), + handle, callback)); + return handle.id; +} + +OperationID FileSystemOperationRunner::FileExists( + const FileSystemURL& url, + const StatusCallback& callback) { + base::File::Error error = base::File::FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(url, &error); + BeginOperationScoper scope; + OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr()); + if (!operation) { + DidFinish(handle, callback, error); + return handle.id; + } + PrepareForRead(handle.id, url); + operation->FileExists( + url, + base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(), + handle, callback)); + return handle.id; +} + +OperationID FileSystemOperationRunner::GetMetadata( + const FileSystemURL& url, + const GetMetadataCallback& callback) { + base::File::Error error = base::File::FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(url, &error); + BeginOperationScoper scope; + OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr()); + if (!operation) { + DidGetMetadata(handle, callback, error, base::File::Info()); + return handle.id; + } + PrepareForRead(handle.id, url); + operation->GetMetadata( + url, + base::Bind(&FileSystemOperationRunner::DidGetMetadata, AsWeakPtr(), + handle, callback)); + return handle.id; +} + +OperationID FileSystemOperationRunner::ReadDirectory( + const FileSystemURL& url, + const ReadDirectoryCallback& callback) { + base::File::Error error = base::File::FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(url, &error); + BeginOperationScoper scope; + OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr()); + if (!operation) { + DidReadDirectory(handle, callback, error, std::vector<DirectoryEntry>(), + false); + return handle.id; + } + PrepareForRead(handle.id, url); + operation->ReadDirectory( + url, + base::Bind(&FileSystemOperationRunner::DidReadDirectory, AsWeakPtr(), + handle, callback)); + return handle.id; +} + +OperationID FileSystemOperationRunner::Remove( + const FileSystemURL& url, bool recursive, + const StatusCallback& callback) { + base::File::Error error = base::File::FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(url, &error); + BeginOperationScoper scope; + OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr()); + if (!operation) { + DidFinish(handle, callback, error); + return handle.id; + } + PrepareForWrite(handle.id, url); + operation->Remove( + url, recursive, + base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(), + handle, callback)); + return handle.id; +} + +OperationID FileSystemOperationRunner::Write( + const net::URLRequestContext* url_request_context, + const FileSystemURL& url, + scoped_ptr<storage::BlobDataHandle> blob, + int64 offset, + const WriteCallback& callback) { + base::File::Error error = base::File::FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(url, &error); + + BeginOperationScoper scope; + OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr()); + if (!operation) { + DidWrite(handle, callback, error, 0, true); + return handle.id; + } + + scoped_ptr<FileStreamWriter> writer( + file_system_context_->CreateFileStreamWriter(url, offset)); + if (!writer) { + // Write is not supported. + DidWrite(handle, callback, base::File::FILE_ERROR_SECURITY, 0, true); + return handle.id; + } + + FileWriterDelegate::FlushPolicy flush_policy = + file_system_context_->ShouldFlushOnWriteCompletion(url.type()) + ? FileWriterDelegate::FLUSH_ON_COMPLETION + : FileWriterDelegate::NO_FLUSH_ON_COMPLETION; + scoped_ptr<FileWriterDelegate> writer_delegate( + new FileWriterDelegate(writer.Pass(), flush_policy)); + + scoped_ptr<net::URLRequest> blob_request( + storage::BlobProtocolHandler::CreateBlobRequest( + blob.Pass(), url_request_context, writer_delegate.get())); + + PrepareForWrite(handle.id, url); + operation->Write( + url, writer_delegate.Pass(), blob_request.Pass(), + base::Bind(&FileSystemOperationRunner::DidWrite, AsWeakPtr(), + handle, callback)); + return handle.id; +} + +OperationID FileSystemOperationRunner::Truncate( + const FileSystemURL& url, int64 length, + const StatusCallback& callback) { + base::File::Error error = base::File::FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(url, &error); + BeginOperationScoper scope; + OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr()); + if (!operation) { + DidFinish(handle, callback, error); + return handle.id; + } + PrepareForWrite(handle.id, url); + operation->Truncate( + url, length, + base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(), + handle, callback)); + return handle.id; +} + +void FileSystemOperationRunner::Cancel( + OperationID id, + const StatusCallback& callback) { + if (ContainsKey(finished_operations_, id)) { + DCHECK(!ContainsKey(stray_cancel_callbacks_, id)); + stray_cancel_callbacks_[id] = callback; + return; + } + FileSystemOperation* operation = operations_.Lookup(id); + if (!operation) { + // There is no operation with |id|. + callback.Run(base::File::FILE_ERROR_INVALID_OPERATION); + return; + } + operation->Cancel(callback); +} + +OperationID FileSystemOperationRunner::TouchFile( + const FileSystemURL& url, + const base::Time& last_access_time, + const base::Time& last_modified_time, + const StatusCallback& callback) { + base::File::Error error = base::File::FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(url, &error); + BeginOperationScoper scope; + OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr()); + if (!operation) { + DidFinish(handle, callback, error); + return handle.id; + } + PrepareForWrite(handle.id, url); + operation->TouchFile( + url, last_access_time, last_modified_time, + base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(), + handle, callback)); + return handle.id; +} + +OperationID FileSystemOperationRunner::OpenFile( + const FileSystemURL& url, + int file_flags, + const OpenFileCallback& callback) { + base::File::Error error = base::File::FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(url, &error); + BeginOperationScoper scope; + OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr()); + if (!operation) { + DidOpenFile(handle, callback, base::File(error), base::Closure()); + return handle.id; + } + if (file_flags & + (base::File::FLAG_CREATE | base::File::FLAG_OPEN_ALWAYS | + base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_OPEN_TRUNCATED | + base::File::FLAG_WRITE | base::File::FLAG_EXCLUSIVE_WRITE | + base::File::FLAG_DELETE_ON_CLOSE | + base::File::FLAG_WRITE_ATTRIBUTES)) { + PrepareForWrite(handle.id, url); + } else { + PrepareForRead(handle.id, url); + } + operation->OpenFile( + url, file_flags, + base::Bind(&FileSystemOperationRunner::DidOpenFile, AsWeakPtr(), + handle, callback)); + return handle.id; +} + +OperationID FileSystemOperationRunner::CreateSnapshotFile( + const FileSystemURL& url, + const SnapshotFileCallback& callback) { + base::File::Error error = base::File::FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(url, &error); + BeginOperationScoper scope; + OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr()); + if (!operation) { + DidCreateSnapshot(handle, callback, error, base::File::Info(), + base::FilePath(), NULL); + return handle.id; + } + PrepareForRead(handle.id, url); + operation->CreateSnapshotFile( + url, + base::Bind(&FileSystemOperationRunner::DidCreateSnapshot, AsWeakPtr(), + handle, callback)); + return handle.id; +} + +OperationID FileSystemOperationRunner::CopyInForeignFile( + const base::FilePath& src_local_disk_path, + const FileSystemURL& dest_url, + const StatusCallback& callback) { + base::File::Error error = base::File::FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(dest_url, &error); + BeginOperationScoper scope; + OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr()); + if (!operation) { + DidFinish(handle, callback, error); + return handle.id; + } + operation->CopyInForeignFile( + src_local_disk_path, dest_url, + base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(), + handle, callback)); + return handle.id; +} + +OperationID FileSystemOperationRunner::RemoveFile( + const FileSystemURL& url, + const StatusCallback& callback) { + base::File::Error error = base::File::FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(url, &error); + BeginOperationScoper scope; + OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr()); + if (!operation) { + DidFinish(handle, callback, error); + return handle.id; + } + operation->RemoveFile( + url, + base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(), + handle, callback)); + return handle.id; +} + +OperationID FileSystemOperationRunner::RemoveDirectory( + const FileSystemURL& url, + const StatusCallback& callback) { + base::File::Error error = base::File::FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(url, &error); + BeginOperationScoper scope; + OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr()); + if (!operation) { + DidFinish(handle, callback, error); + return handle.id; + } + operation->RemoveDirectory( + url, + base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(), + handle, callback)); + return handle.id; +} + +OperationID FileSystemOperationRunner::CopyFileLocal( + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOption option, + const CopyFileProgressCallback& progress_callback, + const StatusCallback& callback) { + base::File::Error error = base::File::FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(src_url, &error); + BeginOperationScoper scope; + OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr()); + if (!operation) { + DidFinish(handle, callback, error); + return handle.id; + } + operation->CopyFileLocal( + src_url, dest_url, option, progress_callback, + base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(), + handle, callback)); + return handle.id; +} + +OperationID FileSystemOperationRunner::MoveFileLocal( + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOption option, + const StatusCallback& callback) { + base::File::Error error = base::File::FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(src_url, &error); + BeginOperationScoper scope; + OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr()); + if (!operation) { + DidFinish(handle, callback, error); + return handle.id; + } + operation->MoveFileLocal( + src_url, dest_url, option, + base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(), + handle, callback)); + return handle.id; +} + +base::File::Error FileSystemOperationRunner::SyncGetPlatformPath( + const FileSystemURL& url, + base::FilePath* platform_path) { + base::File::Error error = base::File::FILE_OK; + scoped_ptr<FileSystemOperation> operation( + file_system_context_->CreateFileSystemOperation(url, &error)); + if (!operation.get()) + return error; + return operation->SyncGetPlatformPath(url, platform_path); +} + +FileSystemOperationRunner::FileSystemOperationRunner( + FileSystemContext* file_system_context) + : file_system_context_(file_system_context) {} + +void FileSystemOperationRunner::DidFinish( + const OperationHandle& handle, + const StatusCallback& callback, + base::File::Error rv) { + if (handle.scope) { + finished_operations_.insert(handle.id); + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, base::Bind(&FileSystemOperationRunner::DidFinish, + AsWeakPtr(), handle, callback, rv)); + return; + } + callback.Run(rv); + FinishOperation(handle.id); +} + +void FileSystemOperationRunner::DidGetMetadata( + const OperationHandle& handle, + const GetMetadataCallback& callback, + base::File::Error rv, + const base::File::Info& file_info) { + if (handle.scope) { + finished_operations_.insert(handle.id); + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, base::Bind(&FileSystemOperationRunner::DidGetMetadata, + AsWeakPtr(), handle, callback, rv, file_info)); + return; + } + callback.Run(rv, file_info); + FinishOperation(handle.id); +} + +void FileSystemOperationRunner::DidReadDirectory( + const OperationHandle& handle, + const ReadDirectoryCallback& callback, + base::File::Error rv, + const std::vector<DirectoryEntry>& entries, + bool has_more) { + if (handle.scope) { + finished_operations_.insert(handle.id); + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, base::Bind(&FileSystemOperationRunner::DidReadDirectory, + AsWeakPtr(), handle, callback, rv, + entries, has_more)); + return; + } + callback.Run(rv, entries, has_more); + if (rv != base::File::FILE_OK || !has_more) + FinishOperation(handle.id); +} + +void FileSystemOperationRunner::DidWrite( + const OperationHandle& handle, + const WriteCallback& callback, + base::File::Error rv, + int64 bytes, + bool complete) { + if (handle.scope) { + finished_operations_.insert(handle.id); + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, base::Bind(&FileSystemOperationRunner::DidWrite, AsWeakPtr(), + handle, callback, rv, bytes, complete)); + return; + } + callback.Run(rv, bytes, complete); + if (rv != base::File::FILE_OK || complete) + FinishOperation(handle.id); +} + +void FileSystemOperationRunner::DidOpenFile( + const OperationHandle& handle, + const OpenFileCallback& callback, + base::File file, + const base::Closure& on_close_callback) { + if (handle.scope) { + finished_operations_.insert(handle.id); + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, base::Bind(&FileSystemOperationRunner::DidOpenFile, + AsWeakPtr(), handle, callback, Passed(&file), + on_close_callback)); + return; + } + callback.Run(file.Pass(), on_close_callback); + FinishOperation(handle.id); +} + +void FileSystemOperationRunner::DidCreateSnapshot( + const OperationHandle& handle, + const SnapshotFileCallback& callback, + base::File::Error rv, + const base::File::Info& file_info, + const base::FilePath& platform_path, + const scoped_refptr<storage::ShareableFileReference>& file_ref) { + if (handle.scope) { + finished_operations_.insert(handle.id); + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, base::Bind(&FileSystemOperationRunner::DidCreateSnapshot, + AsWeakPtr(), handle, callback, rv, file_info, + platform_path, file_ref)); + return; + } + callback.Run(rv, file_info, platform_path, file_ref); + FinishOperation(handle.id); +} + +void FileSystemOperationRunner::OnCopyProgress( + const OperationHandle& handle, + const CopyProgressCallback& callback, + FileSystemOperation::CopyProgressType type, + const FileSystemURL& source_url, + const FileSystemURL& dest_url, + int64 size) { + if (handle.scope) { + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, base::Bind( + &FileSystemOperationRunner::OnCopyProgress, + AsWeakPtr(), handle, callback, type, source_url, dest_url, size)); + return; + } + callback.Run(type, source_url, dest_url, size); +} + +void FileSystemOperationRunner::PrepareForWrite(OperationID id, + const FileSystemURL& url) { + if (file_system_context_->GetUpdateObservers(url.type())) { + file_system_context_->GetUpdateObservers(url.type())->Notify( + &FileUpdateObserver::OnStartUpdate, MakeTuple(url)); + } + write_target_urls_[id].insert(url); +} + +void FileSystemOperationRunner::PrepareForRead(OperationID id, + const FileSystemURL& url) { + if (file_system_context_->GetAccessObservers(url.type())) { + file_system_context_->GetAccessObservers(url.type())->Notify( + &FileAccessObserver::OnAccess, MakeTuple(url)); + } +} + +FileSystemOperationRunner::OperationHandle +FileSystemOperationRunner::BeginOperation( + FileSystemOperation* operation, + base::WeakPtr<BeginOperationScoper> scope) { + OperationHandle handle; + handle.id = operations_.Add(operation); + handle.scope = scope; + return handle; +} + +void FileSystemOperationRunner::FinishOperation(OperationID id) { + OperationToURLSet::iterator found = write_target_urls_.find(id); + if (found != write_target_urls_.end()) { + const FileSystemURLSet& urls = found->second; + for (FileSystemURLSet::const_iterator iter = urls.begin(); + iter != urls.end(); ++iter) { + if (file_system_context_->GetUpdateObservers(iter->type())) { + file_system_context_->GetUpdateObservers(iter->type())->Notify( + &FileUpdateObserver::OnEndUpdate, MakeTuple(*iter)); + } + } + write_target_urls_.erase(found); + } + + // IDMap::Lookup fails if the operation is NULL, so we don't check + // operations_.Lookup(id) here. + + operations_.Remove(id); + finished_operations_.erase(id); + + // Dispatch stray cancel callback if exists. + std::map<OperationID, StatusCallback>::iterator found_cancel = + stray_cancel_callbacks_.find(id); + if (found_cancel != stray_cancel_callbacks_.end()) { + // This cancel has been requested after the operation has finished, + // so report that we failed to stop it. + found_cancel->second.Run(base::File::FILE_ERROR_INVALID_OPERATION); + stray_cancel_callbacks_.erase(found_cancel); + } +} + +} // namespace storage diff --git a/storage/browser/fileapi/file_system_operation_runner.h b/storage/browser/fileapi/file_system_operation_runner.h new file mode 100644 index 0000000..deca413 --- /dev/null +++ b/storage/browser/fileapi/file_system_operation_runner.h @@ -0,0 +1,322 @@ +// Copyright 2013 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. + +#ifndef STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_RUNNER_H_ +#define STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_RUNNER_H_ + +#include <map> +#include <set> +#include <vector> + +#include "base/basictypes.h" +#include "base/id_map.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "storage/browser/blob/blob_data_handle.h" +#include "storage/browser/fileapi/file_system_operation.h" +#include "storage/browser/fileapi/file_system_url.h" +#include "storage/browser/storage_browser_export.h" + +namespace net { +class URLRequestContext; +} + +namespace storage { + +class FileSystemURL; +class FileSystemContext; + +// A central interface for running FileSystem API operations. +// All operation methods take callback and returns OperationID, which is +// an integer value which can be used for cancelling an operation. +// All operation methods return kErrorOperationID if running (posting) an +// operation fails, in addition to dispatching the callback with an error +// code (therefore in most cases the caller does not need to check the +// returned operation ID). +class STORAGE_EXPORT FileSystemOperationRunner + : public base::SupportsWeakPtr<FileSystemOperationRunner> { + public: + typedef FileSystemOperation::GetMetadataCallback GetMetadataCallback; + typedef FileSystemOperation::ReadDirectoryCallback ReadDirectoryCallback; + typedef FileSystemOperation::SnapshotFileCallback SnapshotFileCallback; + typedef FileSystemOperation::StatusCallback StatusCallback; + typedef FileSystemOperation::WriteCallback WriteCallback; + typedef FileSystemOperation::OpenFileCallback OpenFileCallback; + typedef FileSystemOperation::CopyProgressCallback CopyProgressCallback; + typedef FileSystemOperation::CopyFileProgressCallback + CopyFileProgressCallback; + typedef FileSystemOperation::CopyOrMoveOption CopyOrMoveOption; + + typedef int OperationID; + + virtual ~FileSystemOperationRunner(); + + // Cancels all inflight operations. + void Shutdown(); + + // Creates a file at |url|. If |exclusive| is true, an error is raised + // in case a file is already present at the URL. + OperationID CreateFile(const FileSystemURL& url, + bool exclusive, + const StatusCallback& callback); + + OperationID CreateDirectory(const FileSystemURL& url, + bool exclusive, + bool recursive, + const StatusCallback& callback); + + // Copies a file or directory from |src_url| to |dest_url|. If + // |src_url| is a directory, the contents of |src_url| are copied to + // |dest_url| recursively. A new file or directory is created at + // |dest_url| as needed. + // For |option| and |progress_callback|, see file_system_operation.h for + // details. + OperationID Copy(const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOption option, + const CopyProgressCallback& progress_callback, + const StatusCallback& callback); + + // Moves a file or directory from |src_url| to |dest_url|. A new file + // or directory is created at |dest_url| as needed. + // For |option|, see file_system_operation.h for details. + OperationID Move(const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOption option, + const StatusCallback& callback); + + // Checks if a directory is present at |url|. + OperationID DirectoryExists(const FileSystemURL& url, + const StatusCallback& callback); + + // Checks if a file is present at |url|. + OperationID FileExists(const FileSystemURL& url, + const StatusCallback& callback); + + // Gets the metadata of a file or directory at |url|. + OperationID GetMetadata(const FileSystemURL& url, + const GetMetadataCallback& callback); + + // Reads contents of a directory at |url|. + OperationID ReadDirectory(const FileSystemURL& url, + const ReadDirectoryCallback& callback); + + // Removes a file or directory at |url|. If |recursive| is true, remove + // all files and directories under the directory at |url| recursively. + OperationID Remove(const FileSystemURL& url, bool recursive, + const StatusCallback& callback); + + // Writes contents of |blob_url| to |url| at |offset|. + // |url_request_context| is used to read contents in |blob|. + OperationID Write(const net::URLRequestContext* url_request_context, + const FileSystemURL& url, + scoped_ptr<storage::BlobDataHandle> blob, + int64 offset, + const WriteCallback& callback); + + // Truncates a file at |url| to |length|. If |length| is larger than + // the original file size, the file will be extended, and the extended + // part is filled with null bytes. + OperationID Truncate(const FileSystemURL& url, int64 length, + const StatusCallback& callback); + + // Tries to cancel the operation |id| [we support cancelling write or + // truncate only]. Reports failure for the current operation, then reports + // success for the cancel operation itself via the |callback|. + void Cancel(OperationID id, const StatusCallback& callback); + + // Modifies timestamps of a file or directory at |url| with + // |last_access_time| and |last_modified_time|. The function DOES NOT + // create a file unlike 'touch' command on Linux. + // + // This function is used only by Pepper as of writing. + OperationID TouchFile(const FileSystemURL& url, + const base::Time& last_access_time, + const base::Time& last_modified_time, + const StatusCallback& callback); + + // Opens a file at |url| with |file_flags|, where flags are OR'ed + // values of base::PlatformFileFlags. + // + // |peer_handle| is the process handle of a pepper plugin process, which + // is necessary for underlying IPC calls with Pepper plugins. + // + // This function is used only by Pepper as of writing. + OperationID OpenFile(const FileSystemURL& url, + int file_flags, + const OpenFileCallback& callback); + + // Creates a local snapshot file for a given |url| and returns the + // metadata and platform url of the snapshot file via |callback|. + // In local filesystem cases the implementation may simply return + // the metadata of the file itself (as well as GetMetadata does), + // while in remote filesystem case the backend may want to download the file + // into a temporary snapshot file and return the metadata of the + // temporary file. Or if the implementaiton already has the local cache + // data for |url| it can simply return the url to the cache. + OperationID CreateSnapshotFile(const FileSystemURL& url, + const SnapshotFileCallback& callback); + + // Copies in a single file from a different filesystem. + // + // This returns: + // - File::FILE_ERROR_NOT_FOUND if |src_file_path| + // or the parent directory of |dest_url| does not exist. + // - File::FILE_ERROR_INVALID_OPERATION if |dest_url| exists and + // is not a file. + // - File::FILE_ERROR_FAILED if |dest_url| does not exist and + // its parent path is a file. + // + OperationID CopyInForeignFile(const base::FilePath& src_local_disk_path, + const FileSystemURL& dest_url, + const StatusCallback& callback); + + // Removes a single file. + // + // This returns: + // - File::FILE_ERROR_NOT_FOUND if |url| does not exist. + // - File::FILE_ERROR_NOT_A_FILE if |url| is not a file. + // + OperationID RemoveFile(const FileSystemURL& url, + const StatusCallback& callback); + + // Removes a single empty directory. + // + // This returns: + // - File::FILE_ERROR_NOT_FOUND if |url| does not exist. + // - File::FILE_ERROR_NOT_A_DIRECTORY if |url| is not a directory. + // - File::FILE_ERROR_NOT_EMPTY if |url| is not empty. + // + OperationID RemoveDirectory(const FileSystemURL& url, + const StatusCallback& callback); + + // Copies a file from |src_url| to |dest_url|. + // This must be called for files that belong to the same filesystem + // (i.e. type() and origin() of the |src_url| and |dest_url| must match). + // For |option| and |progress_callback|, see file_system_operation.h for + // details. + // + // This returns: + // - File::FILE_ERROR_NOT_FOUND if |src_url| + // or the parent directory of |dest_url| does not exist. + // - File::FILE_ERROR_NOT_A_FILE if |src_url| exists but is not a file. + // - File::FILE_ERROR_INVALID_OPERATION if |dest_url| exists and + // is not a file. + // - File::FILE_ERROR_FAILED if |dest_url| does not exist and + // its parent path is a file. + // + OperationID CopyFileLocal(const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOption option, + const CopyFileProgressCallback& progress_callback, + const StatusCallback& callback); + + // Moves a local file from |src_url| to |dest_url|. + // This must be called for files that belong to the same filesystem + // (i.e. type() and origin() of the |src_url| and |dest_url| must match). + // For |option|, see file_system_operation.h for details. + // + // This returns: + // - File::FILE_ERROR_NOT_FOUND if |src_url| + // or the parent directory of |dest_url| does not exist. + // - File::FILE_ERROR_NOT_A_FILE if |src_url| exists but is not a file. + // - File::FILE_ERROR_INVALID_OPERATION if |dest_url| exists and + // is not a file. + // - File::FILE_ERROR_FAILED if |dest_url| does not exist and + // its parent path is a file. + // + OperationID MoveFileLocal(const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOption option, + const StatusCallback& callback); + + // This is called only by pepper plugin as of writing to synchronously get + // the underlying platform path to upload a file in the sandboxed filesystem + // (e.g. TEMPORARY or PERSISTENT). + base::File::Error SyncGetPlatformPath(const FileSystemURL& url, + base::FilePath* platform_path); + + private: + class BeginOperationScoper; + + struct OperationHandle { + OperationID id; + base::WeakPtr<BeginOperationScoper> scope; + + OperationHandle(); + ~OperationHandle(); + }; + + friend class FileSystemContext; + explicit FileSystemOperationRunner(FileSystemContext* file_system_context); + + void DidFinish(const OperationHandle& handle, + const StatusCallback& callback, + base::File::Error rv); + void DidGetMetadata(const OperationHandle& handle, + const GetMetadataCallback& callback, + base::File::Error rv, + const base::File::Info& file_info); + void DidReadDirectory(const OperationHandle& handle, + const ReadDirectoryCallback& callback, + base::File::Error rv, + const std::vector<DirectoryEntry>& entries, + bool has_more); + void DidWrite(const OperationHandle& handle, + const WriteCallback& callback, + base::File::Error rv, + int64 bytes, + bool complete); + void DidOpenFile( + const OperationHandle& handle, + const OpenFileCallback& callback, + base::File file, + const base::Closure& on_close_callback); + void DidCreateSnapshot( + const OperationHandle& handle, + const SnapshotFileCallback& callback, + base::File::Error rv, + const base::File::Info& file_info, + const base::FilePath& platform_path, + const scoped_refptr<storage::ShareableFileReference>& file_ref); + + void OnCopyProgress( + const OperationHandle& handle, + const CopyProgressCallback& callback, + FileSystemOperation::CopyProgressType type, + const FileSystemURL& source_url, + const FileSystemURL& dest_url, + int64 size); + + void PrepareForWrite(OperationID id, const FileSystemURL& url); + void PrepareForRead(OperationID id, const FileSystemURL& url); + + // These must be called at the beginning and end of any async operations. + OperationHandle BeginOperation(FileSystemOperation* operation, + base::WeakPtr<BeginOperationScoper> scope); + void FinishOperation(OperationID id); + + // Not owned; file_system_context owns this. + FileSystemContext* file_system_context_; + + // IDMap<FileSystemOperation, IDMapOwnPointer> operations_; + IDMap<FileSystemOperation, IDMapOwnPointer> operations_; + + // We keep track of the file to be modified by each operation so that + // we can notify observers when we're done. + typedef std::map<OperationID, FileSystemURLSet> OperationToURLSet; + OperationToURLSet write_target_urls_; + + // Operations that are finished but not yet fire their callbacks. + std::set<OperationID> finished_operations_; + + // Callbacks for stray cancels whose target operation is already finished. + std::map<OperationID, StatusCallback> stray_cancel_callbacks_; + + DISALLOW_COPY_AND_ASSIGN(FileSystemOperationRunner); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_RUNNER_H_ diff --git a/storage/browser/fileapi/file_system_options.cc b/storage/browser/fileapi/file_system_options.cc new file mode 100644 index 0000000..47fc3d0 --- /dev/null +++ b/storage/browser/fileapi/file_system_options.cc @@ -0,0 +1,21 @@ +// 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 "storage/browser/fileapi/file_system_options.h" + +namespace storage { + +FileSystemOptions::FileSystemOptions( + ProfileMode profile_mode, + const std::vector<std::string>& additional_allowed_schemes, + leveldb::Env* env_override) + : profile_mode_(profile_mode), + additional_allowed_schemes_(additional_allowed_schemes), + env_override_(env_override) { +} + +FileSystemOptions::~FileSystemOptions() { +} + +} // namespace storage diff --git a/storage/browser/fileapi/file_system_options.h b/storage/browser/fileapi/file_system_options.h new file mode 100644 index 0000000..6e15baf --- /dev/null +++ b/storage/browser/fileapi/file_system_options.h @@ -0,0 +1,62 @@ +// 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. + +#ifndef STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPTIONS_H_ +#define STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPTIONS_H_ + +#include <string> +#include <vector> + +#include "storage/browser/storage_browser_export.h" + +namespace leveldb { +class Env; +} + +namespace storage { + +// Provides runtime options that may change FileSystem API behavior. +// This object is copyable. +class STORAGE_EXPORT FileSystemOptions { + public: + enum ProfileMode { + PROFILE_MODE_NORMAL = 0, + PROFILE_MODE_INCOGNITO + }; + + // |profile_mode| specifies if the profile (for this filesystem) + // is running in incognito mode (PROFILE_MODE_INCOGNITO) or no + // (PROFILE_MODE_NORMAL). + // |additional_allowed_schemes| specifies schemes that are allowed + // to access FileSystem API in addition to "http" and "https". + // Non-NULL |env_override| overrides internal LevelDB environment. + FileSystemOptions( + ProfileMode profile_mode, + const std::vector<std::string>& additional_allowed_schemes, + leveldb::Env* env_override); + + ~FileSystemOptions(); + + // Returns true if it is running in the incognito mode. + bool is_incognito() const { return profile_mode_ == PROFILE_MODE_INCOGNITO; } + + // Returns the schemes that must be allowed to access FileSystem API + // in addition to standard "http" and "https". + // (e.g. If the --allow-file-access-from-files option is given in chrome + // "file" scheme will also need to be allowed). + const std::vector<std::string>& additional_allowed_schemes() const { + return additional_allowed_schemes_; + } + + leveldb::Env* env_override() const { return env_override_; } + + private: + const ProfileMode profile_mode_; + const std::vector<std::string> additional_allowed_schemes_; + leveldb::Env* env_override_; +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPTIONS_H_ diff --git a/storage/browser/fileapi/file_system_quota_client.cc b/storage/browser/fileapi/file_system_quota_client.cc new file mode 100644 index 0000000..c8c79b3 --- /dev/null +++ b/storage/browser/fileapi/file_system_quota_client.cc @@ -0,0 +1,210 @@ +// 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 "storage/browser/fileapi/file_system_quota_client.h" + +#include <algorithm> +#include <set> + +#include "base/bind.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/sequenced_task_runner.h" +#include "base/single_thread_task_runner.h" +#include "base/task_runner_util.h" +#include "net/base/net_util.h" +#include "storage/browser/fileapi/file_system_context.h" +#include "storage/browser/fileapi/file_system_quota_util.h" +#include "storage/browser/fileapi/file_system_usage_cache.h" +#include "storage/browser/fileapi/sandbox_file_system_backend.h" +#include "storage/common/fileapi/file_system_util.h" +#include "url/gurl.h" + +using storage::StorageType; + +namespace storage { + +namespace { + +void GetOriginsForTypeOnFileTaskRunner( + FileSystemContext* context, + StorageType storage_type, + std::set<GURL>* origins_ptr) { + FileSystemType type = QuotaStorageTypeToFileSystemType(storage_type); + DCHECK(type != kFileSystemTypeUnknown); + + FileSystemQuotaUtil* quota_util = context->GetQuotaUtil(type); + if (!quota_util) + return; + quota_util->GetOriginsForTypeOnFileTaskRunner(type, origins_ptr); +} + +void GetOriginsForHostOnFileTaskRunner( + FileSystemContext* context, + StorageType storage_type, + const std::string& host, + std::set<GURL>* origins_ptr) { + FileSystemType type = QuotaStorageTypeToFileSystemType(storage_type); + DCHECK(type != kFileSystemTypeUnknown); + + FileSystemQuotaUtil* quota_util = context->GetQuotaUtil(type); + if (!quota_util) + return; + quota_util->GetOriginsForHostOnFileTaskRunner(type, host, origins_ptr); +} + +void DidGetOrigins(const storage::QuotaClient::GetOriginsCallback& callback, + std::set<GURL>* origins_ptr) { + callback.Run(*origins_ptr); +} + +storage::QuotaStatusCode DeleteOriginOnFileTaskRunner( + FileSystemContext* context, + const GURL& origin, + FileSystemType type) { + FileSystemBackend* provider = context->GetFileSystemBackend(type); + if (!provider || !provider->GetQuotaUtil()) + return storage::kQuotaErrorNotSupported; + base::File::Error result = + provider->GetQuotaUtil()->DeleteOriginDataOnFileTaskRunner( + context, context->quota_manager_proxy(), origin, type); + if (result == base::File::FILE_OK) + return storage::kQuotaStatusOk; + return storage::kQuotaErrorInvalidModification; +} + +} // namespace + +FileSystemQuotaClient::FileSystemQuotaClient( + FileSystemContext* file_system_context, + bool is_incognito) + : file_system_context_(file_system_context), + is_incognito_(is_incognito) { +} + +FileSystemQuotaClient::~FileSystemQuotaClient() {} + +storage::QuotaClient::ID FileSystemQuotaClient::id() const { + return storage::QuotaClient::kFileSystem; +} + +void FileSystemQuotaClient::OnQuotaManagerDestroyed() { + delete this; +} + +void FileSystemQuotaClient::GetOriginUsage( + const GURL& origin_url, + StorageType storage_type, + const GetUsageCallback& callback) { + DCHECK(!callback.is_null()); + + if (is_incognito_) { + // We don't support FileSystem in incognito mode yet. + callback.Run(0); + return; + } + + FileSystemType type = QuotaStorageTypeToFileSystemType(storage_type); + DCHECK(type != kFileSystemTypeUnknown); + + FileSystemQuotaUtil* quota_util = file_system_context_->GetQuotaUtil(type); + if (!quota_util) { + callback.Run(0); + return; + } + + base::PostTaskAndReplyWithResult( + file_task_runner(), + FROM_HERE, + // It is safe to pass Unretained(quota_util) since context owns it. + base::Bind(&FileSystemQuotaUtil::GetOriginUsageOnFileTaskRunner, + base::Unretained(quota_util), + file_system_context_, + origin_url, + type), + callback); +} + +void FileSystemQuotaClient::GetOriginsForType( + StorageType storage_type, + const GetOriginsCallback& callback) { + DCHECK(!callback.is_null()); + + if (is_incognito_) { + // We don't support FileSystem in incognito mode yet. + std::set<GURL> origins; + callback.Run(origins); + return; + } + + std::set<GURL>* origins_ptr = new std::set<GURL>(); + file_task_runner()->PostTaskAndReply( + FROM_HERE, + base::Bind(&GetOriginsForTypeOnFileTaskRunner, + file_system_context_, + storage_type, + base::Unretained(origins_ptr)), + base::Bind(&DidGetOrigins, + callback, + base::Owned(origins_ptr))); +} + +void FileSystemQuotaClient::GetOriginsForHost( + StorageType storage_type, + const std::string& host, + const GetOriginsCallback& callback) { + DCHECK(!callback.is_null()); + + if (is_incognito_) { + // We don't support FileSystem in incognito mode yet. + std::set<GURL> origins; + callback.Run(origins); + return; + } + + std::set<GURL>* origins_ptr = new std::set<GURL>(); + file_task_runner()->PostTaskAndReply( + FROM_HERE, + base::Bind(&GetOriginsForHostOnFileTaskRunner, + file_system_context_, + storage_type, + host, + base::Unretained(origins_ptr)), + base::Bind(&DidGetOrigins, + callback, + base::Owned(origins_ptr))); +} + +void FileSystemQuotaClient::DeleteOriginData( + const GURL& origin, + StorageType type, + const DeletionCallback& callback) { + FileSystemType fs_type = QuotaStorageTypeToFileSystemType(type); + DCHECK(fs_type != kFileSystemTypeUnknown); + + base::PostTaskAndReplyWithResult( + file_task_runner(), + FROM_HERE, + base::Bind(&DeleteOriginOnFileTaskRunner, + file_system_context_, + origin, + fs_type), + callback); +} + +bool FileSystemQuotaClient::DoesSupport( + storage::StorageType storage_type) const { + FileSystemType type = QuotaStorageTypeToFileSystemType(storage_type); + DCHECK(type != kFileSystemTypeUnknown); + return file_system_context_->IsSandboxFileSystem(type); +} + +base::SequencedTaskRunner* FileSystemQuotaClient::file_task_runner() const { + return file_system_context_->default_file_task_runner(); +} + +} // namespace storage diff --git a/storage/browser/fileapi/file_system_quota_client.h b/storage/browser/fileapi/file_system_quota_client.h new file mode 100644 index 0000000..e82a0ed --- /dev/null +++ b/storage/browser/fileapi/file_system_quota_client.h @@ -0,0 +1,70 @@ +// 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. + +#ifndef STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_QUOTA_CLIENT_H_ +#define STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_QUOTA_CLIENT_H_ + +#include <set> +#include <string> +#include <utility> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "storage/browser/fileapi/file_system_quota_util.h" +#include "storage/browser/quota/quota_client.h" +#include "storage/browser/storage_browser_export.h" +#include "storage/common/fileapi/file_system_types.h" + +namespace base { +class SequencedTaskRunner; +} + +namespace storage { + +class FileSystemContext; + +// An instance of this class is created per-profile. This class +// is self-destructed and will delete itself when OnQuotaManagerDestroyed +// is called. +// All of the public methods of this class are called by the quota manager +// (except for the constructor/destructor). +class STORAGE_EXPORT_PRIVATE FileSystemQuotaClient + : public NON_EXPORTED_BASE(storage::QuotaClient) { + public: + FileSystemQuotaClient( + FileSystemContext* file_system_context, + bool is_incognito); + virtual ~FileSystemQuotaClient(); + + // QuotaClient methods. + virtual storage::QuotaClient::ID id() const OVERRIDE; + virtual void OnQuotaManagerDestroyed() OVERRIDE; + virtual void GetOriginUsage(const GURL& origin_url, + storage::StorageType type, + const GetUsageCallback& callback) OVERRIDE; + virtual void GetOriginsForType(storage::StorageType type, + const GetOriginsCallback& callback) OVERRIDE; + virtual void GetOriginsForHost(storage::StorageType type, + const std::string& host, + const GetOriginsCallback& callback) OVERRIDE; + virtual void DeleteOriginData(const GURL& origin, + storage::StorageType type, + const DeletionCallback& callback) OVERRIDE; + virtual bool DoesSupport(storage::StorageType type) const OVERRIDE; + + private: + base::SequencedTaskRunner* file_task_runner() const; + + scoped_refptr<FileSystemContext> file_system_context_; + + bool is_incognito_; + + DISALLOW_COPY_AND_ASSIGN(FileSystemQuotaClient); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_QUOTA_CLIENT_H_ diff --git a/storage/browser/fileapi/file_system_quota_util.h b/storage/browser/fileapi/file_system_quota_util.h new file mode 100644 index 0000000..b5288ff --- /dev/null +++ b/storage/browser/fileapi/file_system_quota_util.h @@ -0,0 +1,91 @@ +// 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. + +#ifndef STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_QUOTA_UTIL_H_ +#define STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_QUOTA_UTIL_H_ + +#include <set> +#include <string> + +#include "base/basictypes.h" +#include "base/files/file.h" +#include "storage/browser/fileapi/task_runner_bound_observer_list.h" +#include "storage/browser/storage_browser_export.h" +#include "storage/common/fileapi/file_system_types.h" +#include "url/gurl.h" + +namespace base { +class SequencedTaskRunner; +} + +namespace storage { +class QuotaManagerProxy; +} + +namespace storage { + +class FileSystemContext; +class QuotaReservation; + +// An abstract interface that provides common quota-related utility functions +// for file_system_quota_client. +// All the methods of this class are synchronous and need to be called on +// the thread that the method name implies. +class STORAGE_EXPORT FileSystemQuotaUtil { + public: + virtual ~FileSystemQuotaUtil() {} + + // Deletes the data on the origin and reports the amount of deleted data + // to the quota manager via |proxy|. + virtual base::File::Error DeleteOriginDataOnFileTaskRunner( + FileSystemContext* context, + storage::QuotaManagerProxy* proxy, + const GURL& origin_url, + FileSystemType type) = 0; + + virtual void GetOriginsForTypeOnFileTaskRunner(storage::FileSystemType type, + std::set<GURL>* origins) = 0; + + virtual void GetOriginsForHostOnFileTaskRunner(storage::FileSystemType type, + const std::string& host, + std::set<GURL>* origins) = 0; + + // Returns the amount of data used for the origin for usage tracking. + virtual int64 GetOriginUsageOnFileTaskRunner( + storage::FileSystemContext* file_system_context, + const GURL& origin_url, + storage::FileSystemType type) = 0; + + // Creates new reservation object for the origin and the type. + virtual scoped_refptr<QuotaReservation> + CreateQuotaReservationOnFileTaskRunner( + const GURL& origin_url, + FileSystemType type) = 0; + + virtual void AddFileUpdateObserver( + FileSystemType type, + FileUpdateObserver* observer, + base::SequencedTaskRunner* task_runner) = 0; + virtual void AddFileChangeObserver( + FileSystemType type, + FileChangeObserver* observer, + base::SequencedTaskRunner* task_runner) = 0; + virtual void AddFileAccessObserver( + FileSystemType type, + FileAccessObserver* observer, + base::SequencedTaskRunner* task_runner) = 0; + + // Returns the observer list for |type|, or returns NULL if any observers + // have not been registered on |type|. + virtual const UpdateObserverList* GetUpdateObservers( + FileSystemType type) const = 0; + virtual const ChangeObserverList* GetChangeObservers( + FileSystemType type) const = 0; + virtual const AccessObserverList* GetAccessObservers( + FileSystemType type) const = 0; +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_QUOTA_UTIL_H_ diff --git a/storage/browser/fileapi/file_system_url.cc b/storage/browser/fileapi/file_system_url.cc new file mode 100644 index 0000000..26d0a97c --- /dev/null +++ b/storage/browser/fileapi/file_system_url.cc @@ -0,0 +1,153 @@ +// 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 "storage/browser/fileapi/file_system_url.h" + +#include <sstream> + +#include "base/logging.h" +#include "base/strings/string_util.h" +#include "net/base/escape.h" +#include "storage/common/fileapi/file_system_types.h" +#include "storage/common/fileapi/file_system_util.h" + +namespace storage { + +namespace { + +} // namespace + +FileSystemURL::FileSystemURL() + : is_valid_(false), + mount_type_(kFileSystemTypeUnknown), + type_(kFileSystemTypeUnknown), + mount_option_(COPY_SYNC_OPTION_NO_SYNC) { +} + +// static +FileSystemURL FileSystemURL::CreateForTest(const GURL& url) { + return FileSystemURL(url); +} + +FileSystemURL FileSystemURL::CreateForTest(const GURL& origin, + FileSystemType mount_type, + const base::FilePath& virtual_path) { + return FileSystemURL(origin, mount_type, virtual_path); +} + +FileSystemURL::FileSystemURL(const GURL& url) + : mount_type_(kFileSystemTypeUnknown), + type_(kFileSystemTypeUnknown), + mount_option_(COPY_SYNC_OPTION_NO_SYNC) { + is_valid_ = ParseFileSystemSchemeURL(url, &origin_, &mount_type_, + &virtual_path_); + path_ = virtual_path_; + type_ = mount_type_; +} + +FileSystemURL::FileSystemURL(const GURL& origin, + FileSystemType mount_type, + const base::FilePath& virtual_path) + : is_valid_(true), + origin_(origin), + mount_type_(mount_type), + virtual_path_(virtual_path.NormalizePathSeparators()), + type_(mount_type), + path_(virtual_path.NormalizePathSeparators()), + mount_option_(COPY_SYNC_OPTION_NO_SYNC) { +} + +FileSystemURL::FileSystemURL(const GURL& origin, + FileSystemType mount_type, + const base::FilePath& virtual_path, + const std::string& mount_filesystem_id, + FileSystemType cracked_type, + const base::FilePath& cracked_path, + const std::string& filesystem_id, + const FileSystemMountOption& mount_option) + : is_valid_(true), + origin_(origin), + mount_type_(mount_type), + virtual_path_(virtual_path.NormalizePathSeparators()), + mount_filesystem_id_(mount_filesystem_id), + type_(cracked_type), + path_(cracked_path.NormalizePathSeparators()), + filesystem_id_(filesystem_id), + mount_option_(mount_option) { +} + +FileSystemURL::~FileSystemURL() {} + +GURL FileSystemURL::ToGURL() const { + if (!is_valid_) + return GURL(); + + std::string url = GetFileSystemRootURI(origin_, mount_type_).spec(); + if (url.empty()) + return GURL(); + + // Exactly match with DOMFileSystemBase::createFileSystemURL()'s encoding + // behavior, where the path is escaped by KURL::encodeWithURLEscapeSequences + // which is essentially encodeURIComponent except '/'. + std::string escaped = net::EscapeQueryParamValue( + virtual_path_.NormalizePathSeparatorsTo('/').AsUTF8Unsafe(), + false /* use_plus */); + ReplaceSubstringsAfterOffset(&escaped, 0, "%2F", "/"); + url.append(escaped); + + // Build nested GURL. + return GURL(url); +} + +std::string FileSystemURL::DebugString() const { + if (!is_valid_) + return "invalid filesystem: URL"; + std::ostringstream ss; + ss << GetFileSystemRootURI(origin_, mount_type_); + + // filesystem_id_ will be non empty for (and only for) cracked URLs. + if (!filesystem_id_.empty()) { + ss << virtual_path_.value(); + ss << " ("; + ss << GetFileSystemTypeString(type_) << "@" << filesystem_id_ << ":"; + ss << path_.value(); + ss << ")"; + } else { + ss << path_.value(); + } + return ss.str(); +} + +bool FileSystemURL::IsParent(const FileSystemURL& child) const { + return IsInSameFileSystem(child) && + path().IsParent(child.path()); +} + +bool FileSystemURL::IsInSameFileSystem(const FileSystemURL& other) const { + return origin() == other.origin() && + type() == other.type() && + filesystem_id() == other.filesystem_id(); +} + +bool FileSystemURL::operator==(const FileSystemURL& that) const { + return origin_ == that.origin_ && + type_ == that.type_ && + path_ == that.path_ && + filesystem_id_ == that.filesystem_id_ && + is_valid_ == that.is_valid_; +} + +bool FileSystemURL::Comparator::operator()(const FileSystemURL& lhs, + const FileSystemURL& rhs) const { + DCHECK(lhs.is_valid_ && rhs.is_valid_); + if (lhs.origin_ != rhs.origin_) + return lhs.origin_ < rhs.origin_; + if (lhs.type_ != rhs.type_) + return lhs.type_ < rhs.type_; + if (lhs.filesystem_id_ != rhs.filesystem_id_) + return lhs.filesystem_id_ < rhs.filesystem_id_; + return lhs.path_ < rhs.path_; +} + +} // namespace storage diff --git a/storage/browser/fileapi/file_system_url.h b/storage/browser/fileapi/file_system_url.h new file mode 100644 index 0000000..7b0e97d --- /dev/null +++ b/storage/browser/fileapi/file_system_url.h @@ -0,0 +1,180 @@ +// 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. + +#ifndef STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_URL_H_ +#define STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_URL_H_ + +#include <set> +#include <string> + +#include "base/files/file_path.h" +#include "storage/browser/storage_browser_export.h" +#include "storage/common/fileapi/file_system_mount_option.h" +#include "storage/common/fileapi/file_system_types.h" +#include "url/gurl.h" + +namespace storage { + +// A class representing a filesystem URL which consists of origin URL, +// type and an internal path used inside the filesystem. +// +// When a FileSystemURL instance is created for a GURL (for filesystem: scheme), +// each accessor method would return following values: +// +// Example: For a URL 'filesystem:http://foo.com/temporary/foo/bar': +// origin() returns 'http://foo.com', +// mount_type() returns kFileSystemTypeTemporary, +// virtual_path() returns 'foo/bar', +// type() returns the same value as mount_type(), +// path() returns the same value as virtual_path(), +// +// All other accessors return empty or invalid value. +// +// FileSystemURL can also be created to represent a 'cracked' filesystem URL if +// the original URL's type/path is pointing to a mount point which can be +// further resolved to a lower filesystem type/path. +// +// Example: Assume a path '/media/removable' is mounted at mount name +// 'mount_name' with type kFileSystemTypeFoo as an external file system. +// +// The original URL would look like: +// 'filesystem:http://bar.com/external/mount_name/foo/bar': +// +// FileSystemURL('http://bar.com', +// kFileSystemTypeExternal, +// 'mount_name/foo/bar' +// 'mount_name', +// kFileSystemTypeFoo, +// '/media/removable/foo/bar'); +// would create a FileSystemURL whose accessors return: +// +// origin() returns 'http://bar.com', +// mount_type() returns kFileSystemTypeExternal, +// virtual_path() returns 'mount_name/foo/bar', +// type() returns the kFileSystemTypeFoo, +// path() returns '/media/removable/foo/bar', +// +// Note that in either case virtual_path() always returns the path part after +// 'type' part in the original URL, and mount_type() always returns the 'type' +// part in the original URL. +// +// Additionally, following accessors would return valid values: +// filesystem_id() returns 'mount_name'. +// +// It is impossible to directly create a valid FileSystemURL instance (except by +// using CreatedForTest methods, which should not be used in production code). +// To get a valid FileSystemURL, one of the following methods can be used: +// <Friend>::CrackURL, <Friend>::CreateCrackedFileSystemURL, where <Friend> is +// one of the friended classes. +// +// TODO(ericu): Look into making virtual_path() [and all FileSystem API virtual +// paths] just an std::string, to prevent platform-specific base::FilePath +// behavior from getting invoked by accident. Currently the base::FilePath +// returned here needs special treatment, as it may contain paths that are +// illegal on the current platform. +// To avoid problems, use VirtualPath::BaseName and +// VirtualPath::GetComponents instead of the base::FilePath methods. +class STORAGE_EXPORT FileSystemURL { + public: + FileSystemURL(); + ~FileSystemURL(); + + // Methods for creating FileSystemURL without attempting to crack them. + // Should be used only in tests. + static FileSystemURL CreateForTest(const GURL& url); + static FileSystemURL CreateForTest(const GURL& origin, + FileSystemType mount_type, + const base::FilePath& virtual_path); + + // Returns true if this instance represents a valid FileSystem URL. + bool is_valid() const { return is_valid_; } + + // Returns the origin part of this URL. See the class comment for details. + const GURL& origin() const { return origin_; } + + // Returns the type part of this URL. See the class comment for details. + FileSystemType type() const { return type_; } + + // Returns the cracked path of this URL. See the class comment for details. + const base::FilePath& path() const { return path_; } + + // Returns the original path part of this URL. + // See the class comment for details. + // TODO(kinuko): this must return std::string. + const base::FilePath& virtual_path() const { return virtual_path_; } + + // Returns the filesystem ID/mount name for isolated/external filesystem URLs. + // See the class comment for details. + const std::string& filesystem_id() const { return filesystem_id_; } + const std::string& mount_filesystem_id() const { + return mount_filesystem_id_; + } + + FileSystemType mount_type() const { return mount_type_; } + + const FileSystemMountOption& mount_option() const { return mount_option_; } + + // Returns the formatted URL of this instance. + GURL ToGURL() const; + + std::string DebugString() const; + + // Returns true if this URL is a strict parent of the |child|. + bool IsParent(const FileSystemURL& child) const; + + bool IsInSameFileSystem(const FileSystemURL& other) const; + + bool operator==(const FileSystemURL& that) const; + + bool operator!=(const FileSystemURL& that) const { + return !(*this == that); + } + + struct STORAGE_EXPORT Comparator { + bool operator() (const FileSystemURL& lhs, const FileSystemURL& rhs) const; + }; + + private: + friend class FileSystemContext; + friend class ExternalMountPoints; + friend class IsolatedContext; + + explicit FileSystemURL(const GURL& filesystem_url); + FileSystemURL(const GURL& origin, + FileSystemType mount_type, + const base::FilePath& virtual_path); + // Creates a cracked FileSystemURL. + FileSystemURL(const GURL& origin, + FileSystemType mount_type, + const base::FilePath& virtual_path, + const std::string& mount_filesystem_id, + FileSystemType cracked_type, + const base::FilePath& cracked_path, + const std::string& filesystem_id, + const FileSystemMountOption& mount_option); + + bool is_valid_; + + // Values parsed from the original URL. + GURL origin_; + FileSystemType mount_type_; + base::FilePath virtual_path_; + + // Values obtained by cracking URLs. + // |mount_filesystem_id_| is retrieved from the first round of cracking, + // and the rest of the fields are from recursive cracking. Permission + // checking on the top-level mount information should be done with the former, + // and the low-level file operation should be implemented with the latter. + std::string mount_filesystem_id_; + FileSystemType type_; + base::FilePath path_; + std::string filesystem_id_; + FileSystemMountOption mount_option_; +}; + +typedef std::set<FileSystemURL, FileSystemURL::Comparator> FileSystemURLSet; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_URL_H_ diff --git a/storage/browser/fileapi/file_system_url_request_job.cc b/storage/browser/fileapi/file_system_url_request_job.cc new file mode 100644 index 0000000..8c3e832 --- /dev/null +++ b/storage/browser/fileapi/file_system_url_request_job.cc @@ -0,0 +1,263 @@ +// 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 "storage/browser/fileapi/file_system_url_request_job.h" + +#include <vector> + +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/files/file_util_proxy.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/threading/thread_restrictions.h" +#include "base/time/time.h" +#include "build/build_config.h" +#include "net/base/file_stream.h" +#include "net/base/io_buffer.h" +#include "net/base/mime_util.h" +#include "net/base/net_errors.h" +#include "net/base/net_util.h" +#include "net/http/http_response_headers.h" +#include "net/http/http_response_info.h" +#include "net/http/http_util.h" +#include "net/url_request/url_request.h" +#include "storage/browser/blob/file_stream_reader.h" +#include "storage/browser/fileapi/file_system_context.h" +#include "storage/browser/fileapi/file_system_operation_runner.h" +#include "storage/common/fileapi/file_system_util.h" +#include "url/gurl.h" + +using net::NetworkDelegate; +using net::URLRequest; +using net::URLRequestJob; +using net::URLRequestStatus; + +namespace storage { + +static net::HttpResponseHeaders* CreateHttpResponseHeaders() { + // HttpResponseHeaders expects its input string to be terminated by two NULs. + static const char kStatus[] = "HTTP/1.1 200 OK\0"; + static const size_t kStatusLen = arraysize(kStatus); + + net::HttpResponseHeaders* headers = + new net::HttpResponseHeaders(std::string(kStatus, kStatusLen)); + + // Tell WebKit never to cache this content. + std::string cache_control(net::HttpRequestHeaders::kCacheControl); + cache_control.append(": no-cache"); + headers->AddHeader(cache_control); + + return headers; +} + +FileSystemURLRequestJob::FileSystemURLRequestJob( + URLRequest* request, + NetworkDelegate* network_delegate, + const std::string& storage_domain, + FileSystemContext* file_system_context) + : URLRequestJob(request, network_delegate), + storage_domain_(storage_domain), + file_system_context_(file_system_context), + is_directory_(false), + remaining_bytes_(0), + weak_factory_(this) { +} + +FileSystemURLRequestJob::~FileSystemURLRequestJob() {} + +void FileSystemURLRequestJob::Start() { + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&FileSystemURLRequestJob::StartAsync, + weak_factory_.GetWeakPtr())); +} + +void FileSystemURLRequestJob::Kill() { + reader_.reset(); + URLRequestJob::Kill(); + weak_factory_.InvalidateWeakPtrs(); +} + +bool FileSystemURLRequestJob::ReadRawData(net::IOBuffer* dest, int dest_size, + int *bytes_read) { + DCHECK_NE(dest_size, 0); + DCHECK(bytes_read); + DCHECK_GE(remaining_bytes_, 0); + + if (reader_.get() == NULL) + return false; + + if (remaining_bytes_ < dest_size) + dest_size = static_cast<int>(remaining_bytes_); + + if (!dest_size) { + *bytes_read = 0; + return true; + } + + const int rv = reader_->Read(dest, dest_size, + base::Bind(&FileSystemURLRequestJob::DidRead, + weak_factory_.GetWeakPtr())); + if (rv >= 0) { + // Data is immediately available. + *bytes_read = rv; + remaining_bytes_ -= rv; + DCHECK_GE(remaining_bytes_, 0); + return true; + } + if (rv == net::ERR_IO_PENDING) + SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); + else + NotifyFailed(rv); + return false; +} + +bool FileSystemURLRequestJob::GetMimeType(std::string* mime_type) const { + DCHECK(request_); + DCHECK(url_.is_valid()); + base::FilePath::StringType extension = url_.path().Extension(); + if (!extension.empty()) + extension = extension.substr(1); + return net::GetWellKnownMimeTypeFromExtension(extension, mime_type); +} + +void FileSystemURLRequestJob::SetExtraRequestHeaders( + const net::HttpRequestHeaders& headers) { + std::string range_header; + if (headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header)) { + std::vector<net::HttpByteRange> ranges; + if (net::HttpUtil::ParseRangeHeader(range_header, &ranges)) { + if (ranges.size() == 1) { + byte_range_ = ranges[0]; + } else { + // We don't support multiple range requests in one single URL request. + // TODO(adamk): decide whether we want to support multiple range + // requests. + NotifyFailed(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE); + } + } + } +} + +void FileSystemURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) { + if (response_info_) + *info = *response_info_; +} + +int FileSystemURLRequestJob::GetResponseCode() const { + if (response_info_) + return 200; + return URLRequestJob::GetResponseCode(); +} + +void FileSystemURLRequestJob::StartAsync() { + if (!request_) + return; + DCHECK(!reader_.get()); + url_ = file_system_context_->CrackURL(request_->url()); + if (!url_.is_valid()) { + file_system_context_->AttemptAutoMountForURLRequest( + request_, + storage_domain_, + base::Bind(&FileSystemURLRequestJob::DidAttemptAutoMount, + weak_factory_.GetWeakPtr())); + return; + } + if (!file_system_context_->CanServeURLRequest(url_)) { + // In incognito mode the API is not usable and there should be no data. + NotifyFailed(net::ERR_FILE_NOT_FOUND); + return; + } + file_system_context_->operation_runner()->GetMetadata( + url_, + base::Bind(&FileSystemURLRequestJob::DidGetMetadata, + weak_factory_.GetWeakPtr())); +} + +void FileSystemURLRequestJob::DidAttemptAutoMount(base::File::Error result) { + if (result >= 0 && + file_system_context_->CrackURL(request_->url()).is_valid()) { + StartAsync(); + } else { + NotifyFailed(net::ERR_FILE_NOT_FOUND); + } +} + +void FileSystemURLRequestJob::DidGetMetadata( + base::File::Error error_code, + const base::File::Info& file_info) { + if (error_code != base::File::FILE_OK) { + NotifyFailed(error_code == base::File::FILE_ERROR_INVALID_URL ? + net::ERR_INVALID_URL : net::ERR_FILE_NOT_FOUND); + return; + } + + // We may have been orphaned... + if (!request_) + return; + + is_directory_ = file_info.is_directory; + + if (!byte_range_.ComputeBounds(file_info.size)) { + NotifyFailed(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE); + return; + } + + if (is_directory_) { + NotifyHeadersComplete(); + return; + } + + remaining_bytes_ = byte_range_.last_byte_position() - + byte_range_.first_byte_position() + 1; + DCHECK_GE(remaining_bytes_, 0); + + DCHECK(!reader_.get()); + reader_ = file_system_context_->CreateFileStreamReader( + url_, byte_range_.first_byte_position(), base::Time()); + + set_expected_content_size(remaining_bytes_); + response_info_.reset(new net::HttpResponseInfo()); + response_info_->headers = CreateHttpResponseHeaders(); + NotifyHeadersComplete(); +} + +void FileSystemURLRequestJob::DidRead(int result) { + if (result > 0) + SetStatus(URLRequestStatus()); // Clear the IO_PENDING status + else if (result == 0) + NotifyDone(URLRequestStatus()); + else + NotifyFailed(result); + + remaining_bytes_ -= result; + DCHECK_GE(remaining_bytes_, 0); + + NotifyReadComplete(result); +} + +bool FileSystemURLRequestJob::IsRedirectResponse(GURL* location, + int* http_status_code) { + if (is_directory_) { + // This happens when we discovered the file is a directory, so needs a + // slash at the end of the path. + std::string new_path = request_->url().path(); + new_path.push_back('/'); + GURL::Replacements replacements; + replacements.SetPathStr(new_path); + *location = request_->url().ReplaceComponents(replacements); + *http_status_code = 301; // simulate a permanent redirect + return true; + } + + return false; +} + +void FileSystemURLRequestJob::NotifyFailed(int rv) { + NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv)); +} + +} // namespace storage diff --git a/storage/browser/fileapi/file_system_url_request_job.h b/storage/browser/fileapi/file_system_url_request_job.h new file mode 100644 index 0000000..9910e5d --- /dev/null +++ b/storage/browser/fileapi/file_system_url_request_job.h @@ -0,0 +1,85 @@ +// 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. + +#ifndef STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_URL_REQUEST_JOB_H_ +#define STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_URL_REQUEST_JOB_H_ + +#include <string> + +#include "base/files/file.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "net/http/http_byte_range.h" +#include "net/url_request/url_request_job.h" +#include "storage/browser/fileapi/file_system_url.h" +#include "storage/browser/storage_browser_export.h" + +class GURL; + +namespace base { +class FilePath; +} + +namespace storage { +class FileStreamReader; +} + +namespace storage { +class FileSystemContext; + +// A request job that handles reading filesystem: URLs +class STORAGE_EXPORT_PRIVATE FileSystemURLRequestJob + : public net::URLRequestJob { + public: + FileSystemURLRequestJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate, + const std::string& storage_domain, + FileSystemContext* file_system_context); + + // URLRequestJob methods: + virtual void Start() OVERRIDE; + virtual void Kill() OVERRIDE; + virtual bool ReadRawData(net::IOBuffer* buf, + int buf_size, + int* bytes_read) OVERRIDE; + virtual bool IsRedirectResponse(GURL* location, + int* http_status_code) OVERRIDE; + virtual void SetExtraRequestHeaders( + const net::HttpRequestHeaders& headers) OVERRIDE; + virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE; + virtual int GetResponseCode() const OVERRIDE; + + // FilterContext methods (via URLRequestJob): + virtual bool GetMimeType(std::string* mime_type) const OVERRIDE; + + private: + class CallbackDispatcher; + + virtual ~FileSystemURLRequestJob(); + + void StartAsync(); + void DidAttemptAutoMount(base::File::Error result); + void DidGetMetadata(base::File::Error error_code, + const base::File::Info& file_info); + void DidRead(int result); + void NotifyFailed(int rv); + + const std::string storage_domain_; + FileSystemContext* file_system_context_; + scoped_ptr<storage::FileStreamReader> reader_; + FileSystemURL url_; + bool is_directory_; + scoped_ptr<net::HttpResponseInfo> response_info_; + int64 remaining_bytes_; + net::HttpByteRange byte_range_; + base::WeakPtrFactory<FileSystemURLRequestJob> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(FileSystemURLRequestJob); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_URL_REQUEST_JOB_H_ diff --git a/storage/browser/fileapi/file_system_url_request_job_factory.cc b/storage/browser/fileapi/file_system_url_request_job_factory.cc new file mode 100644 index 0000000..68150e4 --- /dev/null +++ b/storage/browser/fileapi/file_system_url_request_job_factory.cc @@ -0,0 +1,73 @@ +// 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 "storage/browser/fileapi/file_system_url_request_job_factory.h" + +#include <string> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "net/url_request/url_request.h" +#include "storage/browser/fileapi/file_system_dir_url_request_job.h" +#include "storage/browser/fileapi/file_system_url_request_job.h" + +namespace storage { + +namespace { + +class FileSystemProtocolHandler + : public net::URLRequestJobFactory::ProtocolHandler { + public: + FileSystemProtocolHandler(const std::string& storage_domain, + FileSystemContext* context); + virtual ~FileSystemProtocolHandler(); + + virtual net::URLRequestJob* MaybeCreateJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate) const OVERRIDE; + + private: + const std::string storage_domain_; + + // No scoped_refptr because |file_system_context_| is owned by the + // ProfileIOData, which also owns this ProtocolHandler. + FileSystemContext* const file_system_context_; + + DISALLOW_COPY_AND_ASSIGN(FileSystemProtocolHandler); +}; + +FileSystemProtocolHandler::FileSystemProtocolHandler( + const std::string& storage_domain, + FileSystemContext* context) + : storage_domain_(storage_domain), + file_system_context_(context) { + DCHECK(file_system_context_); +} + +FileSystemProtocolHandler::~FileSystemProtocolHandler() {} + +net::URLRequestJob* FileSystemProtocolHandler::MaybeCreateJob( + net::URLRequest* request, net::NetworkDelegate* network_delegate) const { + const std::string path = request->url().path(); + + // If the path ends with a /, we know it's a directory. If the path refers + // to a directory and gets dispatched to FileSystemURLRequestJob, that class + // redirects back here, by adding a / to the URL. + if (!path.empty() && path[path.size() - 1] == '/') { + return new FileSystemDirURLRequestJob( + request, network_delegate, storage_domain_, file_system_context_); + } + return new FileSystemURLRequestJob( + request, network_delegate, storage_domain_, file_system_context_); +} + +} // anonymous namespace + +net::URLRequestJobFactory::ProtocolHandler* CreateFileSystemProtocolHandler( + const std::string& storage_domain, FileSystemContext* context) { + DCHECK(context); + return new FileSystemProtocolHandler(storage_domain, context); +} + +} // namespace storage diff --git a/storage/browser/fileapi/file_system_url_request_job_factory.h b/storage/browser/fileapi/file_system_url_request_job_factory.h new file mode 100644 index 0000000..921b5e3 --- /dev/null +++ b/storage/browser/fileapi/file_system_url_request_job_factory.h @@ -0,0 +1,31 @@ +// 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. + +#ifndef STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_URL_REQUEST_JOB_FACTORY_H_ +#define STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_URL_REQUEST_JOB_FACTORY_H_ + +#include <string> + +#include "net/url_request/url_request_job_factory.h" + +#include "storage/browser/storage_browser_export.h" + +namespace base { +class MessageLoopProxy; +} // namespace base + +namespace storage { + +class FileSystemContext; + +// |context|'s lifetime should exceed the lifetime of the ProtocolHandler. +// Currently, this is only used by ProfileIOData which owns |context| and the +// ProtocolHandler. +STORAGE_EXPORT net::URLRequestJobFactory::ProtocolHandler* + CreateFileSystemProtocolHandler(const std::string& storage_domain, + FileSystemContext* context); + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_URL_REQUEST_JOB_FACTORY_H_ diff --git a/storage/browser/fileapi/file_system_usage_cache.cc b/storage/browser/fileapi/file_system_usage_cache.cc new file mode 100644 index 0000000..0cfbfd8 --- /dev/null +++ b/storage/browser/fileapi/file_system_usage_cache.cc @@ -0,0 +1,307 @@ +// 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 "storage/browser/fileapi/file_system_usage_cache.h" + +#include <utility> + +#include "base/bind.h" +#include "base/debug/trace_event.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/pickle.h" +#include "base/stl_util.h" +#include "storage/browser/fileapi/timed_task_helper.h" + +namespace storage { + +namespace { +const int64 kCloseDelaySeconds = 5; +const size_t kMaxHandleCacheSize = 2; +} // namespace + +FileSystemUsageCache::FileSystemUsageCache( + base::SequencedTaskRunner* task_runner) + : task_runner_(task_runner), + weak_factory_(this) { +} + +FileSystemUsageCache::~FileSystemUsageCache() { + task_runner_ = NULL; + CloseCacheFiles(); +} + +const base::FilePath::CharType FileSystemUsageCache::kUsageFileName[] = + FILE_PATH_LITERAL(".usage"); +const char FileSystemUsageCache::kUsageFileHeader[] = "FSU5"; +const int FileSystemUsageCache::kUsageFileHeaderSize = 4; + +// Pickle::{Read,Write}Bool treat bool as int +const int FileSystemUsageCache::kUsageFileSize = + sizeof(Pickle::Header) + + FileSystemUsageCache::kUsageFileHeaderSize + + sizeof(int) + sizeof(int32) + sizeof(int64); // NOLINT + +bool FileSystemUsageCache::GetUsage(const base::FilePath& usage_file_path, + int64* usage_out) { + TRACE_EVENT0("FileSystem", "UsageCache::GetUsage"); + DCHECK(CalledOnValidThread()); + DCHECK(usage_out); + bool is_valid = true; + uint32 dirty = 0; + int64 usage = 0; + if (!Read(usage_file_path, &is_valid, &dirty, &usage)) + return false; + *usage_out = usage; + return true; +} + +bool FileSystemUsageCache::GetDirty(const base::FilePath& usage_file_path, + uint32* dirty_out) { + TRACE_EVENT0("FileSystem", "UsageCache::GetDirty"); + DCHECK(CalledOnValidThread()); + DCHECK(dirty_out); + bool is_valid = true; + uint32 dirty = 0; + int64 usage = 0; + if (!Read(usage_file_path, &is_valid, &dirty, &usage)) + return false; + *dirty_out = dirty; + return true; +} + +bool FileSystemUsageCache::IncrementDirty( + const base::FilePath& usage_file_path) { + TRACE_EVENT0("FileSystem", "UsageCache::IncrementDirty"); + DCHECK(CalledOnValidThread()); + bool is_valid = true; + uint32 dirty = 0; + int64 usage = 0; + bool new_handle = !HasCacheFileHandle(usage_file_path); + if (!Read(usage_file_path, &is_valid, &dirty, &usage)) + return false; + + bool success = Write(usage_file_path, is_valid, dirty + 1, usage); + if (success && dirty == 0 && new_handle) + FlushFile(usage_file_path); + return success; +} + +bool FileSystemUsageCache::DecrementDirty( + const base::FilePath& usage_file_path) { + TRACE_EVENT0("FileSystem", "UsageCache::DecrementDirty"); + DCHECK(CalledOnValidThread()); + bool is_valid = true; + uint32 dirty = 0; + int64 usage = 0; + if (!Read(usage_file_path, &is_valid, &dirty, &usage) || dirty <= 0) + return false; + + if (dirty <= 0) + return false; + + return Write(usage_file_path, is_valid, dirty - 1, usage); +} + +bool FileSystemUsageCache::Invalidate(const base::FilePath& usage_file_path) { + TRACE_EVENT0("FileSystem", "UsageCache::Invalidate"); + DCHECK(CalledOnValidThread()); + bool is_valid = true; + uint32 dirty = 0; + int64 usage = 0; + if (!Read(usage_file_path, &is_valid, &dirty, &usage)) + return false; + + return Write(usage_file_path, false, dirty, usage); +} + +bool FileSystemUsageCache::IsValid(const base::FilePath& usage_file_path) { + TRACE_EVENT0("FileSystem", "UsageCache::IsValid"); + DCHECK(CalledOnValidThread()); + bool is_valid = true; + uint32 dirty = 0; + int64 usage = 0; + if (!Read(usage_file_path, &is_valid, &dirty, &usage)) + return false; + return is_valid; +} + +bool FileSystemUsageCache::AtomicUpdateUsageByDelta( + const base::FilePath& usage_file_path, int64 delta) { + TRACE_EVENT0("FileSystem", "UsageCache::AtomicUpdateUsageByDelta"); + DCHECK(CalledOnValidThread()); + bool is_valid = true; + uint32 dirty = 0; + int64 usage = 0;; + if (!Read(usage_file_path, &is_valid, &dirty, &usage)) + return false; + return Write(usage_file_path, is_valid, dirty, usage + delta); +} + +bool FileSystemUsageCache::UpdateUsage(const base::FilePath& usage_file_path, + int64 fs_usage) { + TRACE_EVENT0("FileSystem", "UsageCache::UpdateUsage"); + DCHECK(CalledOnValidThread()); + return Write(usage_file_path, true, 0, fs_usage); +} + +bool FileSystemUsageCache::Exists(const base::FilePath& usage_file_path) { + TRACE_EVENT0("FileSystem", "UsageCache::Exists"); + DCHECK(CalledOnValidThread()); + return base::PathExists(usage_file_path); +} + +bool FileSystemUsageCache::Delete(const base::FilePath& usage_file_path) { + TRACE_EVENT0("FileSystem", "UsageCache::Delete"); + DCHECK(CalledOnValidThread()); + CloseCacheFiles(); + return base::DeleteFile(usage_file_path, true); +} + +void FileSystemUsageCache::CloseCacheFiles() { + TRACE_EVENT0("FileSystem", "UsageCache::CloseCacheFiles"); + DCHECK(CalledOnValidThread()); + STLDeleteValues(&cache_files_); + timer_.reset(); +} + +bool FileSystemUsageCache::Read(const base::FilePath& usage_file_path, + bool* is_valid, + uint32* dirty_out, + int64* usage_out) { + TRACE_EVENT0("FileSystem", "UsageCache::Read"); + DCHECK(CalledOnValidThread()); + DCHECK(is_valid); + DCHECK(dirty_out); + DCHECK(usage_out); + char buffer[kUsageFileSize]; + const char *header; + if (usage_file_path.empty() || + !ReadBytes(usage_file_path, buffer, kUsageFileSize)) + return false; + Pickle read_pickle(buffer, kUsageFileSize); + PickleIterator iter(read_pickle); + uint32 dirty = 0; + int64 usage = 0; + + if (!read_pickle.ReadBytes(&iter, &header, kUsageFileHeaderSize) || + !read_pickle.ReadBool(&iter, is_valid) || + !read_pickle.ReadUInt32(&iter, &dirty) || + !read_pickle.ReadInt64(&iter, &usage)) + return false; + + if (header[0] != kUsageFileHeader[0] || + header[1] != kUsageFileHeader[1] || + header[2] != kUsageFileHeader[2] || + header[3] != kUsageFileHeader[3]) + return false; + + *dirty_out = dirty; + *usage_out = usage; + return true; +} + +bool FileSystemUsageCache::Write(const base::FilePath& usage_file_path, + bool is_valid, + int32 dirty, + int64 usage) { + TRACE_EVENT0("FileSystem", "UsageCache::Write"); + DCHECK(CalledOnValidThread()); + Pickle write_pickle; + write_pickle.WriteBytes(kUsageFileHeader, kUsageFileHeaderSize); + write_pickle.WriteBool(is_valid); + write_pickle.WriteUInt32(dirty); + write_pickle.WriteInt64(usage); + + if (!WriteBytes(usage_file_path, + static_cast<const char*>(write_pickle.data()), + write_pickle.size())) { + Delete(usage_file_path); + return false; + } + return true; +} + +base::File* FileSystemUsageCache::GetFile(const base::FilePath& file_path) { + DCHECK(CalledOnValidThread()); + if (cache_files_.size() >= kMaxHandleCacheSize) + CloseCacheFiles(); + ScheduleCloseTimer(); + + base::File* new_file = NULL; + std::pair<CacheFiles::iterator, bool> inserted = + cache_files_.insert(std::make_pair(file_path, new_file)); + if (!inserted.second) + return inserted.first->second; + + new_file = new base::File(file_path, + base::File::FLAG_OPEN_ALWAYS | + base::File::FLAG_READ | + base::File::FLAG_WRITE); + if (!new_file->IsValid()) { + cache_files_.erase(inserted.first); + delete new_file; + return NULL; + } + + inserted.first->second = new_file; + return new_file; +} + +bool FileSystemUsageCache::ReadBytes(const base::FilePath& file_path, + char* buffer, + int64 buffer_size) { + DCHECK(CalledOnValidThread()); + base::File* file = GetFile(file_path); + if (!file) + return false; + return file->Read(0, buffer, buffer_size) == buffer_size; +} + +bool FileSystemUsageCache::WriteBytes(const base::FilePath& file_path, + const char* buffer, + int64 buffer_size) { + DCHECK(CalledOnValidThread()); + base::File* file = GetFile(file_path); + if (!file) + return false; + return file->Write(0, buffer, buffer_size) == buffer_size; +} + +bool FileSystemUsageCache::FlushFile(const base::FilePath& file_path) { + TRACE_EVENT0("FileSystem", "UsageCache::FlushFile"); + DCHECK(CalledOnValidThread()); + base::File* file = GetFile(file_path); + if (!file) + return false; + return file->Flush(); +} + +void FileSystemUsageCache::ScheduleCloseTimer() { + DCHECK(CalledOnValidThread()); + if (!timer_) + timer_.reset(new TimedTaskHelper(task_runner_.get())); + + if (timer_->IsRunning()) { + timer_->Reset(); + return; + } + + timer_->Start(FROM_HERE, + base::TimeDelta::FromSeconds(kCloseDelaySeconds), + base::Bind(&FileSystemUsageCache::CloseCacheFiles, + weak_factory_.GetWeakPtr())); +} + +bool FileSystemUsageCache::CalledOnValidThread() { + return !task_runner_.get() || task_runner_->RunsTasksOnCurrentThread(); +} + +bool FileSystemUsageCache::HasCacheFileHandle(const base::FilePath& file_path) { + DCHECK(CalledOnValidThread()); + DCHECK_LE(cache_files_.size(), kMaxHandleCacheSize); + return ContainsKey(cache_files_, file_path); +} + +} // namespace storage diff --git a/storage/browser/fileapi/file_system_usage_cache.h b/storage/browser/fileapi/file_system_usage_cache.h new file mode 100644 index 0000000..c2b2aef --- /dev/null +++ b/storage/browser/fileapi/file_system_usage_cache.h @@ -0,0 +1,105 @@ +// 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. + +#ifndef STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_USAGE_CACHE_H_ +#define STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_USAGE_CACHE_H_ + +#include <map> + +#include "base/basictypes.h" +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/sequenced_task_runner.h" +#include "storage/browser/storage_browser_export.h" + +namespace storage { + +class TimedTaskHelper; + +class STORAGE_EXPORT_PRIVATE FileSystemUsageCache { + public: + explicit FileSystemUsageCache(base::SequencedTaskRunner* task_runner); + ~FileSystemUsageCache(); + + // Gets the size described in the .usage file even if dirty > 0 or + // is_valid == false. Returns true if the .usage file is available. + bool GetUsage(const base::FilePath& usage_file_path, int64* usage); + + // Gets the dirty count in the .usage file. + // Returns true if the .usage file is available. + bool GetDirty(const base::FilePath& usage_file_path, uint32* dirty); + + // Increments or decrements the "dirty" entry in the .usage file. + // Returns false if no .usage is available. + bool IncrementDirty(const base::FilePath& usage_file_path); + bool DecrementDirty(const base::FilePath& usage_file_path); + + // Notifies quota system that it needs to recalculate the usage cache of the + // origin. Returns false if no .usage is available. + bool Invalidate(const base::FilePath& usage_file_path); + bool IsValid(const base::FilePath& usage_file_path); + + // Updates the size described in the .usage file. + bool UpdateUsage(const base::FilePath& usage_file_path, int64 fs_usage); + + // Updates the size described in the .usage file by delta with keeping dirty + // even if dirty > 0. + bool AtomicUpdateUsageByDelta(const base::FilePath& usage_file_path, + int64 delta); + + bool Exists(const base::FilePath& usage_file_path); + bool Delete(const base::FilePath& usage_file_path); + + void CloseCacheFiles(); + + static const base::FilePath::CharType kUsageFileName[]; + static const char kUsageFileHeader[]; + static const int kUsageFileSize; + static const int kUsageFileHeaderSize; + + private: + typedef std::map<base::FilePath, base::File*> CacheFiles; + + // Read the size, validity and the "dirty" entry described in the .usage file. + // Returns less than zero if no .usage file is available. + bool Read(const base::FilePath& usage_file_path, + bool* is_valid, + uint32* dirty, + int64* usage); + + bool Write(const base::FilePath& usage_file_path, + bool is_valid, + int32 dirty, + int64 fs_usage); + + base::File* GetFile(const base::FilePath& file_path); + + bool ReadBytes(const base::FilePath& file_path, + char* buffer, + int64 buffer_size); + bool WriteBytes(const base::FilePath& file_path, + const char* buffer, + int64 buffer_size); + bool FlushFile(const base::FilePath& file_path); + void ScheduleCloseTimer(); + + bool HasCacheFileHandle(const base::FilePath& file_path); + + bool CalledOnValidThread(); + + scoped_ptr<TimedTaskHelper> timer_; + CacheFiles cache_files_; + + scoped_refptr<base::SequencedTaskRunner> task_runner_; + + base::WeakPtrFactory<FileSystemUsageCache> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(FileSystemUsageCache); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_USAGE_CACHE_H_ diff --git a/storage/browser/fileapi/file_writer_delegate.cc b/storage/browser/fileapi/file_writer_delegate.cc new file mode 100644 index 0000000..fc533eb --- /dev/null +++ b/storage/browser/fileapi/file_writer_delegate.cc @@ -0,0 +1,244 @@ +// 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 "storage/browser/fileapi/file_writer_delegate.h" + +#include "base/bind.h" +#include "base/callback.h" +#include "base/files/file_util_proxy.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/sequenced_task_runner.h" +#include "base/threading/thread_restrictions.h" +#include "net/base/net_errors.h" +#include "storage/browser/fileapi/file_stream_writer.h" +#include "storage/browser/fileapi/file_system_context.h" +#include "storage/common/fileapi/file_system_util.h" + +namespace storage { + +static const int kReadBufSize = 32768; + +FileWriterDelegate::FileWriterDelegate( + scoped_ptr<FileStreamWriter> file_stream_writer, + FlushPolicy flush_policy) + : file_stream_writer_(file_stream_writer.Pass()), + writing_started_(false), + flush_policy_(flush_policy), + bytes_written_backlog_(0), + bytes_written_(0), + bytes_read_(0), + io_buffer_(new net::IOBufferWithSize(kReadBufSize)), + weak_factory_(this) { +} + +FileWriterDelegate::~FileWriterDelegate() { +} + +void FileWriterDelegate::Start(scoped_ptr<net::URLRequest> request, + const DelegateWriteCallback& write_callback) { + write_callback_ = write_callback; + request_ = request.Pass(); + request_->Start(); +} + +void FileWriterDelegate::Cancel() { + if (request_) { + // This halts any callbacks on this delegate. + request_->set_delegate(NULL); + request_->Cancel(); + } + + const int status = file_stream_writer_->Cancel( + base::Bind(&FileWriterDelegate::OnWriteCancelled, + weak_factory_.GetWeakPtr())); + // Return true to finish immediately if we have no pending writes. + // Otherwise we'll do the final cleanup in the Cancel callback. + if (status != net::ERR_IO_PENDING) { + write_callback_.Run(base::File::FILE_ERROR_ABORT, 0, + GetCompletionStatusOnError()); + } +} + +void FileWriterDelegate::OnReceivedRedirect( + net::URLRequest* request, + const net::RedirectInfo& redirect_info, + bool* defer_redirect) { + NOTREACHED(); + OnError(base::File::FILE_ERROR_SECURITY); +} + +void FileWriterDelegate::OnAuthRequired(net::URLRequest* request, + net::AuthChallengeInfo* auth_info) { + NOTREACHED(); + OnError(base::File::FILE_ERROR_SECURITY); +} + +void FileWriterDelegate::OnCertificateRequested( + net::URLRequest* request, + net::SSLCertRequestInfo* cert_request_info) { + NOTREACHED(); + OnError(base::File::FILE_ERROR_SECURITY); +} + +void FileWriterDelegate::OnSSLCertificateError(net::URLRequest* request, + const net::SSLInfo& ssl_info, + bool fatal) { + NOTREACHED(); + OnError(base::File::FILE_ERROR_SECURITY); +} + +void FileWriterDelegate::OnResponseStarted(net::URLRequest* request) { + DCHECK_EQ(request_.get(), request); + if (!request->status().is_success() || request->GetResponseCode() != 200) { + OnError(base::File::FILE_ERROR_FAILED); + return; + } + Read(); +} + +void FileWriterDelegate::OnReadCompleted(net::URLRequest* request, + int bytes_read) { + DCHECK_EQ(request_.get(), request); + if (!request->status().is_success()) { + OnError(base::File::FILE_ERROR_FAILED); + return; + } + OnDataReceived(bytes_read); +} + +void FileWriterDelegate::Read() { + bytes_written_ = 0; + bytes_read_ = 0; + if (request_->Read(io_buffer_.get(), io_buffer_->size(), &bytes_read_)) { + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&FileWriterDelegate::OnDataReceived, + weak_factory_.GetWeakPtr(), bytes_read_)); + } else if (!request_->status().is_io_pending()) { + OnError(base::File::FILE_ERROR_FAILED); + } +} + +void FileWriterDelegate::OnDataReceived(int bytes_read) { + bytes_read_ = bytes_read; + if (!bytes_read_) { // We're done. + OnProgress(0, true); + } else { + // This could easily be optimized to rotate between a pool of buffers, so + // that we could read and write at the same time. It's not yet clear that + // it's necessary. + cursor_ = new net::DrainableIOBuffer(io_buffer_.get(), bytes_read_); + Write(); + } +} + +void FileWriterDelegate::Write() { + writing_started_ = true; + int64 bytes_to_write = bytes_read_ - bytes_written_; + int write_response = + file_stream_writer_->Write(cursor_.get(), + static_cast<int>(bytes_to_write), + base::Bind(&FileWriterDelegate::OnDataWritten, + weak_factory_.GetWeakPtr())); + if (write_response > 0) { + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&FileWriterDelegate::OnDataWritten, + weak_factory_.GetWeakPtr(), write_response)); + } else if (net::ERR_IO_PENDING != write_response) { + OnError(NetErrorToFileError(write_response)); + } +} + +void FileWriterDelegate::OnDataWritten(int write_response) { + if (write_response > 0) { + OnProgress(write_response, false); + cursor_->DidConsume(write_response); + bytes_written_ += write_response; + if (bytes_written_ == bytes_read_) + Read(); + else + Write(); + } else { + OnError(NetErrorToFileError(write_response)); + } +} + +FileWriterDelegate::WriteProgressStatus +FileWriterDelegate::GetCompletionStatusOnError() const { + return writing_started_ ? ERROR_WRITE_STARTED : ERROR_WRITE_NOT_STARTED; +} + +void FileWriterDelegate::OnError(base::File::Error error) { + if (request_) { + request_->set_delegate(NULL); + request_->Cancel(); + } + + if (writing_started_) + MaybeFlushForCompletion(error, 0, ERROR_WRITE_STARTED); + else + write_callback_.Run(error, 0, ERROR_WRITE_NOT_STARTED); +} + +void FileWriterDelegate::OnProgress(int bytes_written, bool done) { + DCHECK(bytes_written + bytes_written_backlog_ >= bytes_written_backlog_); + static const int kMinProgressDelayMS = 200; + base::Time currentTime = base::Time::Now(); + if (done || last_progress_event_time_.is_null() || + (currentTime - last_progress_event_time_).InMilliseconds() > + kMinProgressDelayMS) { + bytes_written += bytes_written_backlog_; + last_progress_event_time_ = currentTime; + bytes_written_backlog_ = 0; + + if (done) { + MaybeFlushForCompletion(base::File::FILE_OK, bytes_written, + SUCCESS_COMPLETED); + } else { + write_callback_.Run(base::File::FILE_OK, bytes_written, + SUCCESS_IO_PENDING); + } + return; + } + bytes_written_backlog_ += bytes_written; +} + +void FileWriterDelegate::OnWriteCancelled(int status) { + write_callback_.Run(base::File::FILE_ERROR_ABORT, 0, + GetCompletionStatusOnError()); +} + +void FileWriterDelegate::MaybeFlushForCompletion( + base::File::Error error, + int bytes_written, + WriteProgressStatus progress_status) { + if (flush_policy_ == NO_FLUSH_ON_COMPLETION) { + write_callback_.Run(error, bytes_written, progress_status); + return; + } + DCHECK_EQ(FLUSH_ON_COMPLETION, flush_policy_); + + int flush_error = file_stream_writer_->Flush( + base::Bind(&FileWriterDelegate::OnFlushed, weak_factory_.GetWeakPtr(), + error, bytes_written, progress_status)); + if (flush_error != net::ERR_IO_PENDING) + OnFlushed(error, bytes_written, progress_status, flush_error); +} + +void FileWriterDelegate::OnFlushed(base::File::Error error, + int bytes_written, + WriteProgressStatus progress_status, + int flush_error) { + if (error == base::File::FILE_OK && flush_error != net::OK) { + // If the Flush introduced an error, overwrite the status. + // Otherwise, keep the original error status. + error = NetErrorToFileError(flush_error); + progress_status = GetCompletionStatusOnError(); + } + write_callback_.Run(error, bytes_written, progress_status); +} + +} // namespace storage diff --git a/storage/browser/fileapi/file_writer_delegate.h b/storage/browser/fileapi/file_writer_delegate.h new file mode 100644 index 0000000..eefad4c --- /dev/null +++ b/storage/browser/fileapi/file_writer_delegate.h @@ -0,0 +1,110 @@ +// 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. + +#ifndef STORAGE_BROWSER_FILEAPI_FILE_WRITER_DELEGATE_H_ +#define STORAGE_BROWSER_FILEAPI_FILE_WRITER_DELEGATE_H_ + +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/time/time.h" +#include "net/base/file_stream.h" +#include "net/base/io_buffer.h" +#include "net/url_request/url_request.h" +#include "storage/browser/storage_browser_export.h" + +namespace storage { + +class FileStreamWriter; + +class STORAGE_EXPORT_PRIVATE FileWriterDelegate + : public net::URLRequest::Delegate { + public: + enum FlushPolicy { + FLUSH_ON_COMPLETION, + NO_FLUSH_ON_COMPLETION, + }; + + enum WriteProgressStatus { + SUCCESS_IO_PENDING, + SUCCESS_COMPLETED, + ERROR_WRITE_STARTED, + ERROR_WRITE_NOT_STARTED, + }; + + typedef base::Callback<void(base::File::Error result, + int64 bytes, + WriteProgressStatus write_status)> + DelegateWriteCallback; + + FileWriterDelegate(scoped_ptr<FileStreamWriter> file_writer, + FlushPolicy flush_policy); + virtual ~FileWriterDelegate(); + + void Start(scoped_ptr<net::URLRequest> request, + const DelegateWriteCallback& write_callback); + + // Cancels the current write operation. This will synchronously or + // asynchronously call the given write callback (which may result in + // deleting this). + void Cancel(); + + virtual void OnReceivedRedirect(net::URLRequest* request, + const net::RedirectInfo& redirect_info, + bool* defer_redirect) OVERRIDE; + virtual void OnAuthRequired(net::URLRequest* request, + net::AuthChallengeInfo* auth_info) OVERRIDE; + virtual void OnCertificateRequested( + net::URLRequest* request, + net::SSLCertRequestInfo* cert_request_info) OVERRIDE; + virtual void OnSSLCertificateError(net::URLRequest* request, + const net::SSLInfo& ssl_info, + bool fatal) OVERRIDE; + virtual void OnResponseStarted(net::URLRequest* request) OVERRIDE; + virtual void OnReadCompleted(net::URLRequest* request, + int bytes_read) OVERRIDE; + + private: + void OnGetFileInfoAndStartRequest( + scoped_ptr<net::URLRequest> request, + base::File::Error error, + const base::File::Info& file_info); + void Read(); + void OnDataReceived(int bytes_read); + void Write(); + void OnDataWritten(int write_response); + void OnError(base::File::Error error); + void OnProgress(int bytes_read, bool done); + void OnWriteCancelled(int status); + void MaybeFlushForCompletion(base::File::Error error, + int bytes_written, + WriteProgressStatus progress_status); + void OnFlushed(base::File::Error error, + int bytes_written, + WriteProgressStatus progress_status, + int flush_error); + + WriteProgressStatus GetCompletionStatusOnError() const; + + DelegateWriteCallback write_callback_; + scoped_ptr<FileStreamWriter> file_stream_writer_; + base::Time last_progress_event_time_; + bool writing_started_; + FlushPolicy flush_policy_; + int bytes_written_backlog_; + int bytes_written_; + int bytes_read_; + scoped_refptr<net::IOBufferWithSize> io_buffer_; + scoped_refptr<net::DrainableIOBuffer> cursor_; + scoped_ptr<net::URLRequest> request_; + + base::WeakPtrFactory<FileWriterDelegate> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(FileWriterDelegate); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_FILE_WRITER_DELEGATE_H_ diff --git a/storage/browser/fileapi/isolated_context.cc b/storage/browser/fileapi/isolated_context.cc new file mode 100644 index 0000000..20e28c1 --- /dev/null +++ b/storage/browser/fileapi/isolated_context.cc @@ -0,0 +1,484 @@ +// 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 "storage/browser/fileapi/isolated_context.h" + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/rand_util.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "storage/browser/fileapi/file_system_url.h" + +namespace storage { + +namespace { + +base::FilePath::StringType GetRegisterNameForPath(const base::FilePath& path) { + // If it's not a root path simply return a base name. + if (path.DirName() != path) + return path.BaseName().value(); + +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + base::FilePath::StringType name; + for (size_t i = 0; + i < path.value().size() && !base::FilePath::IsSeparator(path.value()[i]); + ++i) { + if (path.value()[i] == L':') { + name.append(L"_drive"); + break; + } + name.append(1, path.value()[i]); + } + return name; +#else + return FILE_PATH_LITERAL("<root>"); +#endif +} + +bool IsSinglePathIsolatedFileSystem(FileSystemType type) { + DCHECK_NE(kFileSystemTypeUnknown, type); + // As of writing dragged file system is the only filesystem which could have + // multiple top-level paths. + return type != kFileSystemTypeDragged; +} + +static base::LazyInstance<IsolatedContext>::Leaky g_isolated_context = + LAZY_INSTANCE_INITIALIZER; + +} // namespace + +IsolatedContext::FileInfoSet::FileInfoSet() {} +IsolatedContext::FileInfoSet::~FileInfoSet() {} + +bool IsolatedContext::FileInfoSet::AddPath( + const base::FilePath& path, std::string* registered_name) { + // The given path should not contain any '..' and should be absolute. + if (path.ReferencesParent() || !path.IsAbsolute()) + return false; + base::FilePath::StringType name = GetRegisterNameForPath(path); + std::string utf8name = base::FilePath(name).AsUTF8Unsafe(); + base::FilePath normalized_path = path.NormalizePathSeparators(); + bool inserted = + fileset_.insert(MountPointInfo(utf8name, normalized_path)).second; + if (!inserted) { + int suffix = 1; + std::string basepart = + base::FilePath(name).RemoveExtension().AsUTF8Unsafe(); + std::string ext = + base::FilePath(base::FilePath(name).Extension()).AsUTF8Unsafe(); + while (!inserted) { + utf8name = base::StringPrintf("%s (%d)", basepart.c_str(), suffix++); + if (!ext.empty()) + utf8name.append(ext); + inserted = + fileset_.insert(MountPointInfo(utf8name, normalized_path)).second; + } + } + if (registered_name) + *registered_name = utf8name; + return true; +} + +bool IsolatedContext::FileInfoSet::AddPathWithName( + const base::FilePath& path, const std::string& name) { + // The given path should not contain any '..' and should be absolute. + if (path.ReferencesParent() || !path.IsAbsolute()) + return false; + return fileset_.insert( + MountPointInfo(name, path.NormalizePathSeparators())).second; +} + +//-------------------------------------------------------------------------- + +class IsolatedContext::Instance { + public: + enum PathType { + PLATFORM_PATH, + VIRTUAL_PATH + }; + + // For a single-path isolated file system, which could be registered by + // IsolatedContext::RegisterFileSystemForPath() or + // IsolatedContext::RegisterFileSystemForVirtualPath(). + // Most of isolated file system contexts should be of this type. + Instance(FileSystemType type, + const std::string& filesystem_id, + const MountPointInfo& file_info, + PathType path_type); + + // For a multi-paths isolated file system. As of writing only file system + // type which could have multi-paths is Dragged file system, and + // could be registered by IsolatedContext::RegisterDraggedFileSystem(). + Instance(FileSystemType type, const std::set<MountPointInfo>& files); + + ~Instance(); + + FileSystemType type() const { return type_; } + const std::string& filesystem_id() const { return filesystem_id_; } + const MountPointInfo& file_info() const { return file_info_; } + const std::set<MountPointInfo>& files() const { return files_; } + int ref_counts() const { return ref_counts_; } + + void AddRef() { ++ref_counts_; } + void RemoveRef() { --ref_counts_; } + + bool ResolvePathForName(const std::string& name, base::FilePath* path) const; + + // Returns true if the instance is a single-path instance. + bool IsSinglePathInstance() const; + + private: + const FileSystemType type_; + const std::string filesystem_id_; + + // For single-path instance. + const MountPointInfo file_info_; + const PathType path_type_; + + // For multiple-path instance (e.g. dragged file system). + const std::set<MountPointInfo> files_; + + // Reference counts. Note that an isolated filesystem is created with ref==0 + // and will get deleted when the ref count reaches <=0. + int ref_counts_; + + DISALLOW_COPY_AND_ASSIGN(Instance); +}; + +IsolatedContext::Instance::Instance(FileSystemType type, + const std::string& filesystem_id, + const MountPointInfo& file_info, + Instance::PathType path_type) + : type_(type), + filesystem_id_(filesystem_id), + file_info_(file_info), + path_type_(path_type), + ref_counts_(0) { + DCHECK(IsSinglePathIsolatedFileSystem(type_)); +} + +IsolatedContext::Instance::Instance(FileSystemType type, + const std::set<MountPointInfo>& files) + : type_(type), + path_type_(PLATFORM_PATH), + files_(files), + ref_counts_(0) { + DCHECK(!IsSinglePathIsolatedFileSystem(type_)); +} + +IsolatedContext::Instance::~Instance() {} + +bool IsolatedContext::Instance::ResolvePathForName(const std::string& name, + base::FilePath* path) const { + if (IsSinglePathIsolatedFileSystem(type_)) { + switch (path_type_) { + case PLATFORM_PATH: + *path = file_info_.path; + break; + case VIRTUAL_PATH: + *path = base::FilePath(); + break; + default: + NOTREACHED(); + } + + return file_info_.name == name; + } + std::set<MountPointInfo>::const_iterator found = files_.find( + MountPointInfo(name, base::FilePath())); + if (found == files_.end()) + return false; + *path = found->path; + return true; +} + +bool IsolatedContext::Instance::IsSinglePathInstance() const { + return IsSinglePathIsolatedFileSystem(type_); +} + +//-------------------------------------------------------------------------- + +// static +IsolatedContext* IsolatedContext::GetInstance() { + return g_isolated_context.Pointer(); +} + +// static +bool IsolatedContext::IsIsolatedType(FileSystemType type) { + return type == kFileSystemTypeIsolated || type == kFileSystemTypeExternal; +} + +std::string IsolatedContext::RegisterDraggedFileSystem( + const FileInfoSet& files) { + base::AutoLock locker(lock_); + std::string filesystem_id = GetNewFileSystemId(); + instance_map_[filesystem_id] = new Instance( + kFileSystemTypeDragged, files.fileset()); + return filesystem_id; +} + +std::string IsolatedContext::RegisterFileSystemForPath( + FileSystemType type, + const std::string& filesystem_id, + const base::FilePath& path_in, + std::string* register_name) { + base::FilePath path(path_in.NormalizePathSeparators()); + if (path.ReferencesParent() || !path.IsAbsolute()) + return std::string(); + std::string name; + if (register_name && !register_name->empty()) { + name = *register_name; + } else { + name = base::FilePath(GetRegisterNameForPath(path)).AsUTF8Unsafe(); + if (register_name) + register_name->assign(name); + } + + base::AutoLock locker(lock_); + std::string new_id = GetNewFileSystemId(); + instance_map_[new_id] = new Instance(type, filesystem_id, + MountPointInfo(name, path), + Instance::PLATFORM_PATH); + path_to_id_map_[path].insert(new_id); + return new_id; +} + +std::string IsolatedContext::RegisterFileSystemForVirtualPath( + FileSystemType type, + const std::string& register_name, + const base::FilePath& cracked_path_prefix) { + base::AutoLock locker(lock_); + base::FilePath path(cracked_path_prefix.NormalizePathSeparators()); + if (path.ReferencesParent()) + return std::string(); + std::string filesystem_id = GetNewFileSystemId(); + instance_map_[filesystem_id] = new Instance( + type, + std::string(), // filesystem_id + MountPointInfo(register_name, cracked_path_prefix), + Instance::VIRTUAL_PATH); + path_to_id_map_[path].insert(filesystem_id); + return filesystem_id; +} + +bool IsolatedContext::HandlesFileSystemMountType(FileSystemType type) const { + return type == kFileSystemTypeIsolated; +} + +bool IsolatedContext::RevokeFileSystem(const std::string& filesystem_id) { + base::AutoLock locker(lock_); + return UnregisterFileSystem(filesystem_id); +} + +bool IsolatedContext::GetRegisteredPath( + const std::string& filesystem_id, base::FilePath* path) const { + DCHECK(path); + base::AutoLock locker(lock_); + IDToInstance::const_iterator found = instance_map_.find(filesystem_id); + if (found == instance_map_.end() || !found->second->IsSinglePathInstance()) + return false; + *path = found->second->file_info().path; + return true; +} + +bool IsolatedContext::CrackVirtualPath( + const base::FilePath& virtual_path, + std::string* id_or_name, + FileSystemType* type, + std::string* cracked_id, + base::FilePath* path, + FileSystemMountOption* mount_option) const { + DCHECK(id_or_name); + DCHECK(path); + + // This should not contain any '..' references. + if (virtual_path.ReferencesParent()) + return false; + + // Set the default mount option. + *mount_option = FileSystemMountOption(); + + // The virtual_path should comprise <id_or_name> and <relative_path> parts. + std::vector<base::FilePath::StringType> components; + virtual_path.GetComponents(&components); + if (components.size() < 1) + return false; + std::vector<base::FilePath::StringType>::iterator component_iter = + components.begin(); + std::string fsid = base::FilePath(*component_iter++).MaybeAsASCII(); + if (fsid.empty()) + return false; + + base::FilePath cracked_path; + { + base::AutoLock locker(lock_); + IDToInstance::const_iterator found_instance = instance_map_.find(fsid); + if (found_instance == instance_map_.end()) + return false; + *id_or_name = fsid; + const Instance* instance = found_instance->second; + if (type) + *type = instance->type(); + if (cracked_id) + *cracked_id = instance->filesystem_id(); + + if (component_iter == components.end()) { + // The virtual root case. + path->clear(); + return true; + } + + // *component_iter should be a name of the registered path. + std::string name = base::FilePath(*component_iter++).AsUTF8Unsafe(); + if (!instance->ResolvePathForName(name, &cracked_path)) + return false; + } + + for (; component_iter != components.end(); ++component_iter) + cracked_path = cracked_path.Append(*component_iter); + *path = cracked_path; + return true; +} + +FileSystemURL IsolatedContext::CrackURL(const GURL& url) const { + FileSystemURL filesystem_url = FileSystemURL(url); + if (!filesystem_url.is_valid()) + return FileSystemURL(); + return CrackFileSystemURL(filesystem_url); +} + +FileSystemURL IsolatedContext::CreateCrackedFileSystemURL( + const GURL& origin, + FileSystemType type, + const base::FilePath& path) const { + return CrackFileSystemURL(FileSystemURL(origin, type, path)); +} + +void IsolatedContext::RevokeFileSystemByPath(const base::FilePath& path_in) { + base::AutoLock locker(lock_); + base::FilePath path(path_in.NormalizePathSeparators()); + PathToID::iterator ids_iter = path_to_id_map_.find(path); + if (ids_iter == path_to_id_map_.end()) + return; + std::set<std::string>& ids = ids_iter->second; + for (std::set<std::string>::iterator iter = ids.begin(); + iter != ids.end(); ++iter) { + IDToInstance::iterator found = instance_map_.find(*iter); + if (found != instance_map_.end()) { + delete found->second; + instance_map_.erase(found); + } + } + path_to_id_map_.erase(ids_iter); +} + +void IsolatedContext::AddReference(const std::string& filesystem_id) { + base::AutoLock locker(lock_); + DCHECK(instance_map_.find(filesystem_id) != instance_map_.end()); + instance_map_[filesystem_id]->AddRef(); +} + +void IsolatedContext::RemoveReference(const std::string& filesystem_id) { + base::AutoLock locker(lock_); + // This could get called for non-existent filesystem if it has been + // already deleted by RevokeFileSystemByPath. + IDToInstance::iterator found = instance_map_.find(filesystem_id); + if (found == instance_map_.end()) + return; + Instance* instance = found->second; + DCHECK_GT(instance->ref_counts(), 0); + instance->RemoveRef(); + if (instance->ref_counts() == 0) { + bool deleted = UnregisterFileSystem(filesystem_id); + DCHECK(deleted); + } +} + +bool IsolatedContext::GetDraggedFileInfo( + const std::string& filesystem_id, + std::vector<MountPointInfo>* files) const { + DCHECK(files); + base::AutoLock locker(lock_); + IDToInstance::const_iterator found = instance_map_.find(filesystem_id); + if (found == instance_map_.end() || + found->second->type() != kFileSystemTypeDragged) + return false; + files->assign(found->second->files().begin(), + found->second->files().end()); + return true; +} + +base::FilePath IsolatedContext::CreateVirtualRootPath( + const std::string& filesystem_id) const { + return base::FilePath().AppendASCII(filesystem_id); +} + +IsolatedContext::IsolatedContext() { +} + +IsolatedContext::~IsolatedContext() { + STLDeleteContainerPairSecondPointers(instance_map_.begin(), + instance_map_.end()); +} + +FileSystemURL IsolatedContext::CrackFileSystemURL( + const FileSystemURL& url) const { + if (!HandlesFileSystemMountType(url.type())) + return FileSystemURL(); + + std::string mount_name; + std::string cracked_mount_name; + FileSystemType cracked_type; + base::FilePath cracked_path; + FileSystemMountOption cracked_mount_option; + if (!CrackVirtualPath(url.path(), &mount_name, &cracked_type, + &cracked_mount_name, &cracked_path, + &cracked_mount_option)) { + return FileSystemURL(); + } + + return FileSystemURL( + url.origin(), url.mount_type(), url.virtual_path(), + !url.filesystem_id().empty() ? url.filesystem_id() : mount_name, + cracked_type, cracked_path, + cracked_mount_name.empty() ? mount_name : cracked_mount_name, + cracked_mount_option); +} + +bool IsolatedContext::UnregisterFileSystem(const std::string& filesystem_id) { + lock_.AssertAcquired(); + IDToInstance::iterator found = instance_map_.find(filesystem_id); + if (found == instance_map_.end()) + return false; + Instance* instance = found->second; + if (instance->IsSinglePathInstance()) { + PathToID::iterator ids_iter = path_to_id_map_.find( + instance->file_info().path); + DCHECK(ids_iter != path_to_id_map_.end()); + ids_iter->second.erase(filesystem_id); + if (ids_iter->second.empty()) + path_to_id_map_.erase(ids_iter); + } + delete found->second; + instance_map_.erase(found); + return true; +} + +std::string IsolatedContext::GetNewFileSystemId() const { + // Returns an arbitrary random string which must be unique in the map. + lock_.AssertAcquired(); + uint32 random_data[4]; + std::string id; + do { + base::RandBytes(random_data, sizeof(random_data)); + id = base::HexEncode(random_data, sizeof(random_data)); + } while (instance_map_.find(id) != instance_map_.end()); + return id; +} + +} // namespace storage diff --git a/storage/browser/fileapi/isolated_context.h b/storage/browser/fileapi/isolated_context.h new file mode 100644 index 0000000..aa18e9b --- /dev/null +++ b/storage/browser/fileapi/isolated_context.h @@ -0,0 +1,201 @@ +// 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. + +#ifndef STORAGE_BROWSER_FILEAPI_ISOLATED_CONTEXT_H_ +#define STORAGE_BROWSER_FILEAPI_ISOLATED_CONTEXT_H_ + +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/lazy_instance.h" +#include "base/memory/singleton.h" +#include "base/synchronization/lock.h" +#include "storage/browser/fileapi/mount_points.h" +#include "storage/browser/storage_browser_export.h" +#include "storage/common/fileapi/file_system_types.h" + +namespace storage { +class FileSystemURL; +} + +namespace storage { + +// Manages isolated filesystem mount points which have no well-known names +// and are identified by a string 'filesystem ID', which usually just looks +// like random value. +// This type of filesystem can be created on the fly and may go away when it has +// no references from renderers. +// Files in an isolated filesystem are registered with corresponding names and +// identified by a filesystem URL like: +// +// filesystem:<origin>/isolated/<filesystem_id>/<name>/relative/path +// +// Some methods of this class are virtual just for mocking. +// +class STORAGE_EXPORT IsolatedContext : public MountPoints { + public: + class STORAGE_EXPORT FileInfoSet { + public: + FileInfoSet(); + ~FileInfoSet(); + + // Add the given |path| to the set and populates |registered_name| with + // the registered name assigned for the path. |path| needs to be + // absolute and should not contain parent references. + // Return false if the |path| is not valid and could not be added. + bool AddPath(const base::FilePath& path, std::string* registered_name); + + // Add the given |path| with the |name|. + // Return false if the |name| is already registered in the set or + // is not valid and could not be added. + bool AddPathWithName(const base::FilePath& path, const std::string& name); + + const std::set<MountPointInfo>& fileset() const { return fileset_; } + + private: + std::set<MountPointInfo> fileset_; + }; + + // The instance is lazily created per browser process. + static IsolatedContext* GetInstance(); + + // Returns true if the given filesystem type is managed by IsolatedContext + // (i.e. if the given |type| is Isolated or External). + // TODO(kinuko): needs a better function name. + static bool IsIsolatedType(FileSystemType type); + + // Registers a new isolated filesystem with the given FileInfoSet |files| + // and returns the new filesystem_id. The files are registered with their + // register_name as their keys so that later we can resolve the full paths + // for the given name. We only expose the name and the ID for the + // newly created filesystem to the renderer for the sake of security. + // + // The renderer will be sending filesystem requests with a virtual path like + // '/<filesystem_id>/<registered_name>/<relative_path_from_the_dropped_path>' + // for which we could crack in the browser process by calling + // CrackIsolatedPath to get the full path. + // + // For example: if a dropped file has a path like '/a/b/foo' and we register + // the path with the name 'foo' in the newly created filesystem. + // Later if the context is asked to crack a virtual path like '/<fsid>/foo' + // it can properly return the original path '/a/b/foo' by looking up the + // internal mapping. Similarly if a dropped entry is a directory and its + // path is like '/a/b/dir' a virtual path like '/<fsid>/dir/foo' can be + // cracked into '/a/b/dir/foo'. + // + // Note that the path in |fileset| that contains '..' or is not an + // absolute path is skipped and is not registered. + std::string RegisterDraggedFileSystem(const FileInfoSet& files); + + // Registers a new isolated filesystem for a given |path| of filesystem + // |type| filesystem with |filesystem_id| and returns a new filesystem ID. + // |path| must be an absolute path which has no parent references ('..'). + // If |register_name| is non-null and has non-empty string the path is + // registered as the given |register_name|, otherwise it is populated + // with the name internally assigned to the path. + std::string RegisterFileSystemForPath(FileSystemType type, + const std::string& filesystem_id, + const base::FilePath& path, + std::string* register_name); + + // Registers a virtual filesystem. This is different from + // RegisterFileSystemForPath because register_name is required, and + // cracked_path_prefix is allowed to be non-absolute. + // |register_name| is required, since we cannot infer one from the path. + // |cracked_path_prefix| has no parent references, but can be relative. + std::string RegisterFileSystemForVirtualPath( + FileSystemType type, + const std::string& register_name, + const base::FilePath& cracked_path_prefix); + + // Revokes all filesystem(s) registered for the given path. + // This is assumed to be called when the registered path becomes + // globally invalid, e.g. when a device for the path is detached. + // + // Note that this revokes the filesystem no matter how many references it has. + // It is ok to call this for the path that has no associated filesystems. + // Note that this only works for the filesystems registered by + // |RegisterFileSystemForPath|. + void RevokeFileSystemByPath(const base::FilePath& path); + + // Adds a reference to a filesystem specified by the given filesystem_id. + void AddReference(const std::string& filesystem_id); + + // Removes a reference to a filesystem specified by the given filesystem_id. + // If the reference count reaches 0 the isolated context gets destroyed. + // It is OK to call this on the filesystem that has been already deleted + // (e.g. by RevokeFileSystemByPath). + void RemoveReference(const std::string& filesystem_id); + + // Returns a set of dragged MountPointInfos registered for the + // |filesystem_id|. + // The filesystem_id must be pointing to a dragged file system + // (i.e. must be the one registered by RegisterDraggedFileSystem). + // Returns false if the |filesystem_id| is not valid. + bool GetDraggedFileInfo(const std::string& filesystem_id, + std::vector<MountPointInfo>* files) const; + + // MountPoints overrides. + virtual bool HandlesFileSystemMountType(FileSystemType type) const OVERRIDE; + virtual bool RevokeFileSystem(const std::string& filesystem_id) OVERRIDE; + virtual bool GetRegisteredPath(const std::string& filesystem_id, + base::FilePath* path) const OVERRIDE; + virtual bool CrackVirtualPath( + const base::FilePath& virtual_path, + std::string* filesystem_id, + FileSystemType* type, + std::string* cracked_id, + base::FilePath* path, + FileSystemMountOption* mount_option) const OVERRIDE; + virtual FileSystemURL CrackURL(const GURL& url) const OVERRIDE; + virtual FileSystemURL CreateCrackedFileSystemURL( + const GURL& origin, + FileSystemType type, + const base::FilePath& path) const OVERRIDE; + + // Returns the virtual root path that looks like /<filesystem_id>. + base::FilePath CreateVirtualRootPath(const std::string& filesystem_id) const; + + private: + friend struct base::DefaultLazyInstanceTraits<IsolatedContext>; + + // Represents each file system instance (defined in the .cc). + class Instance; + + typedef std::map<std::string, Instance*> IDToInstance; + + // Reverse map from registered path to IDs. + typedef std::map<base::FilePath, std::set<std::string> > PathToID; + + // Obtain an instance of this class via GetInstance(). + IsolatedContext(); + virtual ~IsolatedContext(); + + // MountPoints overrides. + virtual FileSystemURL CrackFileSystemURL( + const FileSystemURL& url) const OVERRIDE; + + // Unregisters a file system of given |filesystem_id|. Must be called with + // lock_ held. Returns true if the file system is unregistered. + bool UnregisterFileSystem(const std::string& filesystem_id); + + // Returns a new filesystem_id. Called with lock. + std::string GetNewFileSystemId() const; + + // This lock needs to be obtained when accessing the instance_map_. + mutable base::Lock lock_; + + IDToInstance instance_map_; + PathToID path_to_id_map_; + + DISALLOW_COPY_AND_ASSIGN(IsolatedContext); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_ISOLATED_CONTEXT_H_ diff --git a/storage/browser/fileapi/isolated_file_system_backend.cc b/storage/browser/fileapi/isolated_file_system_backend.cc new file mode 100644 index 0000000..90a0ce8 --- /dev/null +++ b/storage/browser/fileapi/isolated_file_system_backend.cc @@ -0,0 +1,152 @@ +// 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 "storage/browser/fileapi/isolated_file_system_backend.h" + +#include <string> + +#include "base/bind.h" +#include "base/files/file_path.h" +#include "base/files/file_util_proxy.h" +#include "base/logging.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/sequenced_task_runner.h" +#include "storage/browser/blob/file_stream_reader.h" +#include "storage/browser/fileapi/async_file_util_adapter.h" +#include "storage/browser/fileapi/copy_or_move_file_validator.h" +#include "storage/browser/fileapi/dragged_file_util.h" +#include "storage/browser/fileapi/file_stream_writer.h" +#include "storage/browser/fileapi/file_system_context.h" +#include "storage/browser/fileapi/file_system_operation.h" +#include "storage/browser/fileapi/file_system_operation_context.h" +#include "storage/browser/fileapi/isolated_context.h" +#include "storage/browser/fileapi/native_file_util.h" +#include "storage/browser/fileapi/transient_file_util.h" +#include "storage/browser/fileapi/watcher_manager.h" +#include "storage/common/fileapi/file_system_types.h" +#include "storage/common/fileapi/file_system_util.h" + +namespace storage { + +IsolatedFileSystemBackend::IsolatedFileSystemBackend() + : isolated_file_util_(new AsyncFileUtilAdapter(new LocalFileUtil())), + dragged_file_util_(new AsyncFileUtilAdapter(new DraggedFileUtil())), + transient_file_util_(new AsyncFileUtilAdapter(new TransientFileUtil())) { +} + +IsolatedFileSystemBackend::~IsolatedFileSystemBackend() { +} + +bool IsolatedFileSystemBackend::CanHandleType(FileSystemType type) const { + switch (type) { + case kFileSystemTypeIsolated: + case kFileSystemTypeDragged: + case kFileSystemTypeForTransientFile: + return true; +#if !defined(OS_CHROMEOS) + case kFileSystemTypeNativeLocal: + case kFileSystemTypeNativeForPlatformApp: + return true; +#endif + default: + return false; + } +} + +void IsolatedFileSystemBackend::Initialize(FileSystemContext* context) { +} + +void IsolatedFileSystemBackend::ResolveURL( + const FileSystemURL& url, + OpenFileSystemMode mode, + const OpenFileSystemCallback& callback) { + // We never allow opening a new isolated FileSystem via usual ResolveURL. + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(callback, + GURL(), + std::string(), + base::File::FILE_ERROR_SECURITY)); +} + +AsyncFileUtil* IsolatedFileSystemBackend::GetAsyncFileUtil( + FileSystemType type) { + switch (type) { + case kFileSystemTypeNativeLocal: + return isolated_file_util_.get(); + case kFileSystemTypeDragged: + return dragged_file_util_.get(); + case kFileSystemTypeForTransientFile: + return transient_file_util_.get(); + default: + NOTREACHED(); + } + return NULL; +} + +WatcherManager* IsolatedFileSystemBackend::GetWatcherManager( + FileSystemType type) { + return NULL; +} + +CopyOrMoveFileValidatorFactory* +IsolatedFileSystemBackend::GetCopyOrMoveFileValidatorFactory( + FileSystemType type, base::File::Error* error_code) { + DCHECK(error_code); + *error_code = base::File::FILE_OK; + return NULL; +} + +FileSystemOperation* IsolatedFileSystemBackend::CreateFileSystemOperation( + const FileSystemURL& url, + FileSystemContext* context, + base::File::Error* error_code) const { + return FileSystemOperation::Create( + url, context, make_scoped_ptr(new FileSystemOperationContext(context))); +} + +bool IsolatedFileSystemBackend::SupportsStreaming( + const storage::FileSystemURL& url) const { + return false; +} + +bool IsolatedFileSystemBackend::HasInplaceCopyImplementation( + storage::FileSystemType type) const { + DCHECK(type == kFileSystemTypeNativeLocal || type == kFileSystemTypeDragged || + type == kFileSystemTypeForTransientFile); + return false; +} + +scoped_ptr<storage::FileStreamReader> +IsolatedFileSystemBackend::CreateFileStreamReader( + const FileSystemURL& url, + int64 offset, + const base::Time& expected_modification_time, + FileSystemContext* context) const { + return scoped_ptr<storage::FileStreamReader>( + storage::FileStreamReader::CreateForLocalFile( + context->default_file_task_runner(), + url.path(), + offset, + expected_modification_time)); +} + +scoped_ptr<FileStreamWriter> IsolatedFileSystemBackend::CreateFileStreamWriter( + const FileSystemURL& url, + int64 offset, + FileSystemContext* context) const { + return scoped_ptr<FileStreamWriter>( + FileStreamWriter::CreateForLocalFile( + context->default_file_task_runner(), + url.path(), + offset, + FileStreamWriter::OPEN_EXISTING_FILE)); +} + +FileSystemQuotaUtil* IsolatedFileSystemBackend::GetQuotaUtil() { + // No quota support. + return NULL; +} + +} // namespace storage diff --git a/storage/browser/fileapi/isolated_file_system_backend.h b/storage/browser/fileapi/isolated_file_system_backend.h new file mode 100644 index 0000000..3894ff4 --- /dev/null +++ b/storage/browser/fileapi/isolated_file_system_backend.h @@ -0,0 +1,57 @@ +// 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. + +#ifndef STORAGE_BROWSER_FILEAPI_ISOLATED_FILE_SYSTEM_BACKEND_H_ +#define STORAGE_BROWSER_FILEAPI_ISOLATED_FILE_SYSTEM_BACKEND_H_ + +#include "base/memory/scoped_ptr.h" +#include "storage/browser/fileapi/file_system_backend.h" + +namespace storage { + +class AsyncFileUtilAdapter; + +class IsolatedFileSystemBackend : public FileSystemBackend { + public: + IsolatedFileSystemBackend(); + virtual ~IsolatedFileSystemBackend(); + + // FileSystemBackend implementation. + virtual bool CanHandleType(FileSystemType type) const OVERRIDE; + virtual void Initialize(FileSystemContext* context) OVERRIDE; + virtual void ResolveURL(const FileSystemURL& url, + OpenFileSystemMode mode, + const OpenFileSystemCallback& callback) OVERRIDE; + virtual AsyncFileUtil* GetAsyncFileUtil(FileSystemType type) OVERRIDE; + virtual WatcherManager* GetWatcherManager(FileSystemType type) OVERRIDE; + virtual CopyOrMoveFileValidatorFactory* GetCopyOrMoveFileValidatorFactory( + FileSystemType type, + base::File::Error* error_code) OVERRIDE; + virtual FileSystemOperation* CreateFileSystemOperation( + const FileSystemURL& url, + FileSystemContext* context, + base::File::Error* error_code) const OVERRIDE; + virtual bool SupportsStreaming(const FileSystemURL& url) const OVERRIDE; + virtual bool HasInplaceCopyImplementation( + storage::FileSystemType type) const OVERRIDE; + virtual scoped_ptr<storage::FileStreamReader> CreateFileStreamReader( + const FileSystemURL& url, + int64 offset, + const base::Time& expected_modification_time, + FileSystemContext* context) const OVERRIDE; + virtual scoped_ptr<FileStreamWriter> CreateFileStreamWriter( + const FileSystemURL& url, + int64 offset, + FileSystemContext* context) const OVERRIDE; + virtual FileSystemQuotaUtil* GetQuotaUtil() OVERRIDE; + + private: + scoped_ptr<AsyncFileUtilAdapter> isolated_file_util_; + scoped_ptr<AsyncFileUtilAdapter> dragged_file_util_; + scoped_ptr<AsyncFileUtilAdapter> transient_file_util_; +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_ISOLATED_FILE_SYSTEM_BACKEND_H_ diff --git a/storage/browser/fileapi/local_file_stream_writer.cc b/storage/browser/fileapi/local_file_stream_writer.cc new file mode 100644 index 0000000..0eb555f --- /dev/null +++ b/storage/browser/fileapi/local_file_stream_writer.cc @@ -0,0 +1,257 @@ +// 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 "storage/browser/fileapi/local_file_stream_writer.h" + +#include "base/callback.h" +#include "base/message_loop/message_loop.h" +#include "net/base/file_stream.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" + +namespace storage { + +namespace { + +const int kOpenFlagsForWrite = base::File::FLAG_OPEN | + base::File::FLAG_WRITE | + base::File::FLAG_ASYNC; +const int kCreateFlagsForWrite = base::File::FLAG_CREATE | + base::File::FLAG_WRITE | + base::File::FLAG_ASYNC; + +} // namespace + +FileStreamWriter* FileStreamWriter::CreateForLocalFile( + base::TaskRunner* task_runner, + const base::FilePath& file_path, + int64 initial_offset, + OpenOrCreate open_or_create) { + return new LocalFileStreamWriter( + task_runner, file_path, initial_offset, open_or_create); +} + +LocalFileStreamWriter::~LocalFileStreamWriter() { + // Invalidate weak pointers so that we won't receive any callbacks from + // in-flight stream operations, which might be triggered during the file close + // in the FileStream destructor. + weak_factory_.InvalidateWeakPtrs(); + + // FileStream's destructor closes the file safely, since we opened the file + // by its Open() method. +} + +int LocalFileStreamWriter::Write(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) { + DCHECK(!has_pending_operation_); + DCHECK(cancel_callback_.is_null()); + + has_pending_operation_ = true; + if (stream_impl_) { + int result = InitiateWrite(buf, buf_len, callback); + if (result != net::ERR_IO_PENDING) + has_pending_operation_ = false; + return result; + } + return InitiateOpen(callback, + base::Bind(&LocalFileStreamWriter::ReadyToWrite, + weak_factory_.GetWeakPtr(), + make_scoped_refptr(buf), buf_len, callback)); +} + +int LocalFileStreamWriter::Cancel(const net::CompletionCallback& callback) { + if (!has_pending_operation_) + return net::ERR_UNEXPECTED; + + DCHECK(!callback.is_null()); + cancel_callback_ = callback; + return net::ERR_IO_PENDING; +} + +int LocalFileStreamWriter::Flush(const net::CompletionCallback& callback) { + DCHECK(!has_pending_operation_); + DCHECK(cancel_callback_.is_null()); + + // Write() is not called yet, so there's nothing to flush. + if (!stream_impl_) + return net::OK; + + has_pending_operation_ = true; + int result = InitiateFlush(callback); + if (result != net::ERR_IO_PENDING) + has_pending_operation_ = false; + return result; +} + +LocalFileStreamWriter::LocalFileStreamWriter(base::TaskRunner* task_runner, + const base::FilePath& file_path, + int64 initial_offset, + OpenOrCreate open_or_create) + : file_path_(file_path), + open_or_create_(open_or_create), + initial_offset_(initial_offset), + task_runner_(task_runner), + has_pending_operation_(false), + weak_factory_(this) {} + +int LocalFileStreamWriter::InitiateOpen( + const net::CompletionCallback& error_callback, + const base::Closure& main_operation) { + DCHECK(has_pending_operation_); + DCHECK(!stream_impl_.get()); + + stream_impl_.reset(new net::FileStream(task_runner_)); + + int open_flags = 0; + switch (open_or_create_) { + case OPEN_EXISTING_FILE: + open_flags = kOpenFlagsForWrite; + break; + case CREATE_NEW_FILE: + open_flags = kCreateFlagsForWrite; + break; + } + + return stream_impl_->Open(file_path_, + open_flags, + base::Bind(&LocalFileStreamWriter::DidOpen, + weak_factory_.GetWeakPtr(), + error_callback, + main_operation)); +} + +void LocalFileStreamWriter::DidOpen( + const net::CompletionCallback& error_callback, + const base::Closure& main_operation, + int result) { + DCHECK(has_pending_operation_); + DCHECK(stream_impl_.get()); + + if (CancelIfRequested()) + return; + + if (result != net::OK) { + has_pending_operation_ = false; + stream_impl_.reset(NULL); + error_callback.Run(result); + return; + } + + InitiateSeek(error_callback, main_operation); +} + +void LocalFileStreamWriter::InitiateSeek( + const net::CompletionCallback& error_callback, + const base::Closure& main_operation) { + DCHECK(has_pending_operation_); + DCHECK(stream_impl_.get()); + + if (initial_offset_ == 0) { + // No need to seek. + main_operation.Run(); + return; + } + + int result = stream_impl_->Seek(base::File::FROM_BEGIN, initial_offset_, + base::Bind(&LocalFileStreamWriter::DidSeek, + weak_factory_.GetWeakPtr(), + error_callback, + main_operation)); + if (result != net::ERR_IO_PENDING) { + has_pending_operation_ = false; + error_callback.Run(result); + } +} + +void LocalFileStreamWriter::DidSeek( + const net::CompletionCallback& error_callback, + const base::Closure& main_operation, + int64 result) { + DCHECK(has_pending_operation_); + + if (CancelIfRequested()) + return; + + if (result != initial_offset_) { + // TODO(kinaba) add a more specific error code. + result = net::ERR_FAILED; + } + + if (result < 0) { + has_pending_operation_ = false; + error_callback.Run(static_cast<int>(result)); + return; + } + + main_operation.Run(); +} + +void LocalFileStreamWriter::ReadyToWrite( + net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) { + DCHECK(has_pending_operation_); + + int result = InitiateWrite(buf, buf_len, callback); + if (result != net::ERR_IO_PENDING) { + has_pending_operation_ = false; + callback.Run(result); + } +} + +int LocalFileStreamWriter::InitiateWrite( + net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) { + DCHECK(has_pending_operation_); + DCHECK(stream_impl_.get()); + + return stream_impl_->Write(buf, buf_len, + base::Bind(&LocalFileStreamWriter::DidWrite, + weak_factory_.GetWeakPtr(), + callback)); +} + +void LocalFileStreamWriter::DidWrite(const net::CompletionCallback& callback, + int result) { + DCHECK(has_pending_operation_); + + if (CancelIfRequested()) + return; + has_pending_operation_ = false; + callback.Run(result); +} + +int LocalFileStreamWriter::InitiateFlush( + const net::CompletionCallback& callback) { + DCHECK(has_pending_operation_); + DCHECK(stream_impl_.get()); + + return stream_impl_->Flush(base::Bind(&LocalFileStreamWriter::DidFlush, + weak_factory_.GetWeakPtr(), + callback)); +} + +void LocalFileStreamWriter::DidFlush(const net::CompletionCallback& callback, + int result) { + DCHECK(has_pending_operation_); + + if (CancelIfRequested()) + return; + has_pending_operation_ = false; + callback.Run(result); +} + +bool LocalFileStreamWriter::CancelIfRequested() { + DCHECK(has_pending_operation_); + + if (cancel_callback_.is_null()) + return false; + + net::CompletionCallback pending_cancel = cancel_callback_; + has_pending_operation_ = false; + cancel_callback_.Reset(); + pending_cancel.Run(net::OK); + return true; +} + +} // namespace storage diff --git a/storage/browser/fileapi/local_file_stream_writer.h b/storage/browser/fileapi/local_file_stream_writer.h new file mode 100644 index 0000000..17417d0 --- /dev/null +++ b/storage/browser/fileapi/local_file_stream_writer.h @@ -0,0 +1,100 @@ +// 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. + +#ifndef STORAGE_BROWSER_FILEAPI_LOCAL_FILE_STREAM_WRITER_H_ +#define STORAGE_BROWSER_FILEAPI_LOCAL_FILE_STREAM_WRITER_H_ + +#include <utility> + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/task_runner.h" +#include "storage/browser/fileapi/file_stream_writer.h" +#include "storage/browser/storage_browser_export.h" + +namespace content { +class LocalFileStreamWriterTest; +} + +namespace net { +class FileStream; +} + +namespace storage { + +// This class is a thin wrapper around net::FileStream for writing local files. +class STORAGE_EXPORT LocalFileStreamWriter + : public NON_EXPORTED_BASE(FileStreamWriter) { + public: + virtual ~LocalFileStreamWriter(); + + // FileStreamWriter overrides. + virtual int Write(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) OVERRIDE; + virtual int Cancel(const net::CompletionCallback& callback) OVERRIDE; + virtual int Flush(const net::CompletionCallback& callback) OVERRIDE; + + private: + friend class content::LocalFileStreamWriterTest; + friend class FileStreamWriter; + LocalFileStreamWriter(base::TaskRunner* task_runner, + const base::FilePath& file_path, + int64 initial_offset, + OpenOrCreate open_or_create); + + // Opens |file_path_| and if it succeeds, proceeds to InitiateSeek(). + // If failed, the error code is returned by calling |error_callback|. + int InitiateOpen(const net::CompletionCallback& error_callback, + const base::Closure& main_operation); + void DidOpen(const net::CompletionCallback& error_callback, + const base::Closure& main_operation, + int result); + + // Seeks to |initial_offset_| and proceeds to |main_operation| if it succeeds. + // If failed, the error code is returned by calling |error_callback|. + void InitiateSeek(const net::CompletionCallback& error_callback, + const base::Closure& main_operation); + void DidSeek(const net::CompletionCallback& error_callback, + const base::Closure& main_operation, + int64 result); + + // Passed as the |main_operation| of InitiateOpen() function. + void ReadyToWrite(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback); + + // Writes asynchronously to the file. + int InitiateWrite(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback); + void DidWrite(const net::CompletionCallback& callback, int result); + + // Flushes asynchronously to the file. + int InitiateFlush(const net::CompletionCallback& callback); + void DidFlush(const net::CompletionCallback& callback, int result); + + // Stops the in-flight operation and calls |cancel_callback_| if it has been + // set by Cancel() for the current operation. + bool CancelIfRequested(); + + // Initialization parameters. + const base::FilePath file_path_; + OpenOrCreate open_or_create_; + const int64 initial_offset_; + scoped_refptr<base::TaskRunner> task_runner_; + + // Current states of the operation. + bool has_pending_operation_; + scoped_ptr<net::FileStream> stream_impl_; + net::CompletionCallback cancel_callback_; + + base::WeakPtrFactory<LocalFileStreamWriter> weak_factory_; + DISALLOW_COPY_AND_ASSIGN(LocalFileStreamWriter); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_LOCAL_FILE_STREAM_WRITER_H_ diff --git a/storage/browser/fileapi/local_file_util.cc b/storage/browser/fileapi/local_file_util.cc new file mode 100644 index 0000000..19ce59e --- /dev/null +++ b/storage/browser/fileapi/local_file_util.cc @@ -0,0 +1,265 @@ +// 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 "storage/browser/fileapi/local_file_util.h" + +#include "base/files/file_enumerator.h" +#include "base/files/file_util.h" +#include "base/files/file_util_proxy.h" +#include "storage/browser/fileapi/async_file_util_adapter.h" +#include "storage/browser/fileapi/file_system_context.h" +#include "storage/browser/fileapi/file_system_operation_context.h" +#include "storage/browser/fileapi/file_system_url.h" +#include "storage/browser/fileapi/native_file_util.h" +#include "storage/common/fileapi/file_system_types.h" +#include "storage/common/fileapi/file_system_util.h" +#include "url/gurl.h" + +namespace storage { + +AsyncFileUtil* AsyncFileUtil::CreateForLocalFileSystem() { + return new AsyncFileUtilAdapter(new LocalFileUtil()); +} + +class LocalFileEnumerator : public FileSystemFileUtil::AbstractFileEnumerator { + public: + LocalFileEnumerator(const base::FilePath& platform_root_path, + const base::FilePath& virtual_root_path, + int file_type) + : file_enum_(platform_root_path, false /* recursive */, file_type), + platform_root_path_(platform_root_path), + virtual_root_path_(virtual_root_path) { + } + + virtual ~LocalFileEnumerator() {} + + virtual base::FilePath Next() OVERRIDE; + virtual int64 Size() OVERRIDE; + virtual base::Time LastModifiedTime() OVERRIDE; + virtual bool IsDirectory() OVERRIDE; + + private: + base::FileEnumerator file_enum_; + base::FileEnumerator::FileInfo file_util_info_; + base::FilePath platform_root_path_; + base::FilePath virtual_root_path_; +}; + +base::FilePath LocalFileEnumerator::Next() { + base::FilePath next = file_enum_.Next(); + // Don't return symlinks. + while (!next.empty() && base::IsLink(next)) + next = file_enum_.Next(); + if (next.empty()) + return next; + file_util_info_ = file_enum_.GetInfo(); + + base::FilePath path; + platform_root_path_.AppendRelativePath(next, &path); + return virtual_root_path_.Append(path); +} + +int64 LocalFileEnumerator::Size() { + return file_util_info_.GetSize(); +} + +base::Time LocalFileEnumerator::LastModifiedTime() { + return file_util_info_.GetLastModifiedTime(); +} + +bool LocalFileEnumerator::IsDirectory() { + return file_util_info_.IsDirectory(); +} + +LocalFileUtil::LocalFileUtil() {} + +LocalFileUtil::~LocalFileUtil() {} + +base::File LocalFileUtil::CreateOrOpen( + FileSystemOperationContext* context, + const FileSystemURL& url, int file_flags) { + base::FilePath file_path; + base::File::Error error = GetLocalFilePath(context, url, &file_path); + if (error != base::File::FILE_OK) + return base::File(error); + // Disallow opening files in symlinked paths. + if (base::IsLink(file_path)) + return base::File(base::File::FILE_ERROR_NOT_FOUND); + + return NativeFileUtil::CreateOrOpen(file_path, file_flags); +} + +base::File::Error LocalFileUtil::EnsureFileExists( + FileSystemOperationContext* context, + const FileSystemURL& url, + bool* created) { + base::FilePath file_path; + base::File::Error error = GetLocalFilePath(context, url, &file_path); + if (error != base::File::FILE_OK) + return error; + return NativeFileUtil::EnsureFileExists(file_path, created); +} + +base::File::Error LocalFileUtil::CreateDirectory( + FileSystemOperationContext* context, + const FileSystemURL& url, + bool exclusive, + bool recursive) { + base::FilePath file_path; + base::File::Error error = GetLocalFilePath(context, url, &file_path); + if (error != base::File::FILE_OK) + return error; + return NativeFileUtil::CreateDirectory(file_path, exclusive, recursive); +} + +base::File::Error LocalFileUtil::GetFileInfo( + FileSystemOperationContext* context, + const FileSystemURL& url, + base::File::Info* file_info, + base::FilePath* platform_file_path) { + base::FilePath file_path; + base::File::Error error = GetLocalFilePath(context, url, &file_path); + if (error != base::File::FILE_OK) + return error; + // We should not follow symbolic links in sandboxed file system. + if (base::IsLink(file_path)) + return base::File::FILE_ERROR_NOT_FOUND; + + error = NativeFileUtil::GetFileInfo(file_path, file_info); + if (error == base::File::FILE_OK) + *platform_file_path = file_path; + return error; +} + +scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> LocalFileUtil:: + CreateFileEnumerator( + FileSystemOperationContext* context, + const FileSystemURL& root_url) { + base::FilePath file_path; + if (GetLocalFilePath(context, root_url, &file_path) != + base::File::FILE_OK) { + return make_scoped_ptr(new EmptyFileEnumerator) + .PassAs<FileSystemFileUtil::AbstractFileEnumerator>(); + } + return make_scoped_ptr(new LocalFileEnumerator( + file_path, root_url.path(), + base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES)) + .PassAs<FileSystemFileUtil::AbstractFileEnumerator>(); +} + +base::File::Error LocalFileUtil::GetLocalFilePath( + FileSystemOperationContext* context, + const FileSystemURL& url, + base::FilePath* local_file_path) { + DCHECK(local_file_path); + DCHECK(url.is_valid()); + if (url.path().empty()) { + // Root direcory case, which should not be accessed. + return base::File::FILE_ERROR_ACCESS_DENIED; + } + *local_file_path = url.path(); + return base::File::FILE_OK; +} + +base::File::Error LocalFileUtil::Touch( + FileSystemOperationContext* context, + const FileSystemURL& url, + const base::Time& last_access_time, + const base::Time& last_modified_time) { + base::FilePath file_path; + base::File::Error error = GetLocalFilePath(context, url, &file_path); + if (error != base::File::FILE_OK) + return error; + return NativeFileUtil::Touch(file_path, last_access_time, last_modified_time); +} + +base::File::Error LocalFileUtil::Truncate( + FileSystemOperationContext* context, + const FileSystemURL& url, + int64 length) { + base::FilePath file_path; + base::File::Error error = GetLocalFilePath(context, url, &file_path); + if (error != base::File::FILE_OK) + return error; + return NativeFileUtil::Truncate(file_path, length); +} + +base::File::Error LocalFileUtil::CopyOrMoveFile( + FileSystemOperationContext* context, + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOption option, + bool copy) { + base::FilePath src_file_path; + base::File::Error error = GetLocalFilePath(context, src_url, &src_file_path); + if (error != base::File::FILE_OK) + return error; + + base::FilePath dest_file_path; + error = GetLocalFilePath(context, dest_url, &dest_file_path); + if (error != base::File::FILE_OK) + return error; + + return NativeFileUtil::CopyOrMoveFile( + src_file_path, + dest_file_path, + option, + storage::NativeFileUtil::CopyOrMoveModeForDestination(dest_url, copy)); +} + +base::File::Error LocalFileUtil::CopyInForeignFile( + FileSystemOperationContext* context, + const base::FilePath& src_file_path, + const FileSystemURL& dest_url) { + if (src_file_path.empty()) + return base::File::FILE_ERROR_INVALID_OPERATION; + + base::FilePath dest_file_path; + base::File::Error error = + GetLocalFilePath(context, dest_url, &dest_file_path); + if (error != base::File::FILE_OK) + return error; + return NativeFileUtil::CopyOrMoveFile( + src_file_path, + dest_file_path, + FileSystemOperation::OPTION_NONE, + storage::NativeFileUtil::CopyOrMoveModeForDestination(dest_url, + true /* copy */)); +} + +base::File::Error LocalFileUtil::DeleteFile( + FileSystemOperationContext* context, + const FileSystemURL& url) { + base::FilePath file_path; + base::File::Error error = GetLocalFilePath(context, url, &file_path); + if (error != base::File::FILE_OK) + return error; + return NativeFileUtil::DeleteFile(file_path); +} + +base::File::Error LocalFileUtil::DeleteDirectory( + FileSystemOperationContext* context, + const FileSystemURL& url) { + base::FilePath file_path; + base::File::Error error = GetLocalFilePath(context, url, &file_path); + if (error != base::File::FILE_OK) + return error; + return NativeFileUtil::DeleteDirectory(file_path); +} + +storage::ScopedFile LocalFileUtil::CreateSnapshotFile( + FileSystemOperationContext* context, + const FileSystemURL& url, + base::File::Error* error, + base::File::Info* file_info, + base::FilePath* platform_path) { + DCHECK(file_info); + // We're just returning the local file information. + *error = GetFileInfo(context, url, file_info, platform_path); + if (*error == base::File::FILE_OK && file_info->is_directory) + *error = base::File::FILE_ERROR_NOT_A_FILE; + return storage::ScopedFile(); +} + +} // namespace storage diff --git a/storage/browser/fileapi/local_file_util.h b/storage/browser/fileapi/local_file_util.h new file mode 100644 index 0000000..dbbdef0 --- /dev/null +++ b/storage/browser/fileapi/local_file_util.h @@ -0,0 +1,94 @@ +// 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. + +#ifndef STORAGE_BROWSER_FILEAPI_LOCAL_FILE_UTIL_H_ +#define STORAGE_BROWSER_FILEAPI_LOCAL_FILE_UTIL_H_ + +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "storage/browser/fileapi/file_system_file_util.h" +#include "storage/browser/storage_browser_export.h" + +namespace base { +class Time; +} + +class GURL; + +namespace storage { + +class FileSystemOperationContext; +class FileSystemURL; + +// An instance of this class is created and owned by *FileSystemBackend. +class STORAGE_EXPORT LocalFileUtil + : public FileSystemFileUtil { + public: + LocalFileUtil(); + virtual ~LocalFileUtil(); + + virtual base::File CreateOrOpen( + FileSystemOperationContext* context, + const FileSystemURL& url, + int file_flags) OVERRIDE; + virtual base::File::Error EnsureFileExists( + FileSystemOperationContext* context, + const FileSystemURL& url, bool* created) OVERRIDE; + virtual base::File::Error CreateDirectory( + FileSystemOperationContext* context, + const FileSystemURL& url, + bool exclusive, + bool recursive) OVERRIDE; + virtual base::File::Error GetFileInfo( + FileSystemOperationContext* context, + const FileSystemURL& url, + base::File::Info* file_info, + base::FilePath* platform_file) OVERRIDE; + virtual scoped_ptr<AbstractFileEnumerator> CreateFileEnumerator( + FileSystemOperationContext* context, + const FileSystemURL& root_url) OVERRIDE; + virtual base::File::Error GetLocalFilePath( + FileSystemOperationContext* context, + const FileSystemURL& file_system_url, + base::FilePath* local_file_path) OVERRIDE; + virtual base::File::Error Touch( + FileSystemOperationContext* context, + const FileSystemURL& url, + const base::Time& last_access_time, + const base::Time& last_modified_time) OVERRIDE; + virtual base::File::Error Truncate( + FileSystemOperationContext* context, + const FileSystemURL& url, + int64 length) OVERRIDE; + virtual base::File::Error CopyOrMoveFile( + FileSystemOperationContext* context, + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOption option, + bool copy) OVERRIDE; + virtual base::File::Error CopyInForeignFile( + FileSystemOperationContext* context, + const base::FilePath& src_file_path, + const FileSystemURL& dest_url) OVERRIDE; + virtual base::File::Error DeleteFile( + FileSystemOperationContext* context, + const FileSystemURL& url) OVERRIDE; + virtual base::File::Error DeleteDirectory( + FileSystemOperationContext* context, + const FileSystemURL& url) OVERRIDE; + virtual storage::ScopedFile CreateSnapshotFile( + FileSystemOperationContext* context, + const FileSystemURL& url, + base::File::Error* error, + base::File::Info* file_info, + base::FilePath* platform_path) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(LocalFileUtil); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_LOCAL_FILE_UTIL_H_ diff --git a/storage/browser/fileapi/mount_points.cc b/storage/browser/fileapi/mount_points.cc new file mode 100644 index 0000000..bc0bf84 --- /dev/null +++ b/storage/browser/fileapi/mount_points.cc @@ -0,0 +1,14 @@ +// Copyright (c) 2013 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 "storage/browser/fileapi/mount_points.h" + +namespace storage { + +MountPoints::MountPointInfo::MountPointInfo() {} +MountPoints::MountPointInfo::MountPointInfo( + const std::string& name, const base::FilePath& path) + : name(name), path(path) {} + +} // namespace storage diff --git a/storage/browser/fileapi/mount_points.h b/storage/browser/fileapi/mount_points.h new file mode 100644 index 0000000..61da5fa --- /dev/null +++ b/storage/browser/fileapi/mount_points.h @@ -0,0 +1,108 @@ +// Copyright (c) 2013 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. + +#ifndef STORAGE_BROWSER_FILEAPI_MOUNT_POINTS_H_ +#define STORAGE_BROWSER_FILEAPI_MOUNT_POINTS_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "storage/browser/storage_browser_export.h" +#include "storage/common/fileapi/file_system_util.h" + +class GURL; + +namespace storage { +class FileSystemMountOption; +class FileSystemURL; +} + +namespace storage { + +// Represents a set of mount points for File API. +class STORAGE_EXPORT MountPoints { + public: + struct STORAGE_EXPORT MountPointInfo { + MountPointInfo(); + MountPointInfo(const std::string& name, const base::FilePath& path); + + // The name to be used to register the path. The registered file can + // be referred by a virtual path /<filesystem_id>/<name>. + // The name should NOT contain a path separator '/'. + std::string name; + + // The path of the file. + base::FilePath path; + + // For STL operation. + bool operator<(const MountPointInfo& that) const { + return name < that.name; + } + }; + + MountPoints() {} + virtual ~MountPoints() {} + + // Revokes a mount point identified by |mount_name|. + // Returns false if the |mount_name| is not (no longer) registered. + // TODO(kinuko): Probably this should be rather named RevokeMountPoint. + virtual bool RevokeFileSystem(const std::string& mount_name) = 0; + + // Returns true if the MountPoints implementation handles filesystems with + // the given mount type. + virtual bool HandlesFileSystemMountType(FileSystemType type) const = 0; + + // Same as CreateCrackedFileSystemURL, but cracks FileSystemURL created + // from |url|. + virtual FileSystemURL CrackURL(const GURL& url) const = 0; + + // Creates a FileSystemURL with the given origin, type and path and tries to + // crack it as a part of one of the registered mount points. + // If the the URL is not valid or does not belong to any of the mount points + // registered in this context, returns empty, invalid FileSystemURL. + virtual FileSystemURL CreateCrackedFileSystemURL( + const GURL& origin, + storage::FileSystemType type, + const base::FilePath& path) const = 0; + + // Returns the mount point root path registered for a given |mount_name|. + // Returns false if the given |mount_name| is not valid. + virtual bool GetRegisteredPath(const std::string& mount_name, + base::FilePath* path) const = 0; + + // Cracks the given |virtual_path| (which is the path part of a filesystem URL + // without '/external' or '/isolated' prefix part) and populates the + // |mount_name|, |type|, and |path| if the <mount_name> part embedded in + // the |virtual_path| (i.e. the first component of the |virtual_path|) is a + // valid registered filesystem ID or mount name for an existing mount point. + // + // Returns false if the given virtual_path cannot be cracked. + // + // Note that |path| is set to empty paths if the filesystem type is isolated + // and |virtual_path| has no <relative_path> part (i.e. pointing to the + // virtual root). + virtual bool CrackVirtualPath(const base::FilePath& virtual_path, + std::string* mount_name, + FileSystemType* type, + std::string* cracked_id, + base::FilePath* path, + FileSystemMountOption* mount_option) const = 0; + + protected: + friend class FileSystemContext; + + // Same as CrackURL and CreateCrackedFileSystemURL, but cracks the url already + // instantiated as the FileSystemURL class. This is internally used for nested + // URL cracking in FileSystemContext. + virtual FileSystemURL CrackFileSystemURL(const FileSystemURL& url) const = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(MountPoints); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_MOUNT_POINTS_H_ diff --git a/storage/browser/fileapi/native_file_util.cc b/storage/browser/fileapi/native_file_util.cc new file mode 100644 index 0000000..b44179c --- /dev/null +++ b/storage/browser/fileapi/native_file_util.cc @@ -0,0 +1,315 @@ +// 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 "storage/browser/fileapi/native_file_util.h" + +#include "base/files/file.h" +#include "base/files/file_enumerator.h" +#include "base/files/file_util.h" +#include "base/memory/scoped_ptr.h" +#include "storage/browser/fileapi/file_system_operation_context.h" +#include "storage/browser/fileapi/file_system_url.h" + +namespace storage { + +namespace { + +// Sets permissions on directory at |dir_path| based on the target platform. +// Returns true on success, or false otherwise. +// +// TODO(benchan): Find a better place outside webkit to host this function. +bool SetPlatformSpecificDirectoryPermissions(const base::FilePath& dir_path) { +#if defined(OS_CHROMEOS) + // System daemons on Chrome OS may run as a user different than the Chrome + // process but need to access files under the directories created here. + // Because of that, grant the execute permission on the created directory + // to group and other users. + if (HANDLE_EINTR(chmod(dir_path.value().c_str(), + S_IRWXU | S_IXGRP | S_IXOTH)) != 0) { + return false; + } +#endif + // Keep the directory permissions unchanged on non-Chrome OS platforms. + return true; +} + +// Copies a file |from| to |to|, and ensure the written content is synced to +// the disk. This is essentially base::CopyFile followed by fsync(). +bool CopyFileAndSync(const base::FilePath& from, const base::FilePath& to) { + base::File infile(from, base::File::FLAG_OPEN | base::File::FLAG_READ); + if (!infile.IsValid()) { + return false; + } + + base::File outfile(to, + base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); + if (!outfile.IsValid()) { + return false; + } + + const int kBufferSize = 32768; + std::vector<char> buffer(kBufferSize); + + for (;;) { + int bytes_read = infile.ReadAtCurrentPos(&buffer[0], kBufferSize); + if (bytes_read < 0) + return false; + if (bytes_read == 0) + break; + for (int bytes_written = 0; bytes_written < bytes_read; ) { + int bytes_written_partial = outfile.WriteAtCurrentPos( + &buffer[bytes_written], bytes_read - bytes_written); + if (bytes_written_partial < 0) + return false; + bytes_written += bytes_written_partial; + } + } + + return outfile.Flush(); +} + +} // namespace + +using base::PlatformFile; + +class NativeFileEnumerator : public FileSystemFileUtil::AbstractFileEnumerator { + public: + NativeFileEnumerator(const base::FilePath& root_path, + bool recursive, + int file_type) + : file_enum_(root_path, recursive, file_type) { + } + + virtual ~NativeFileEnumerator() {} + + virtual base::FilePath Next() OVERRIDE; + virtual int64 Size() OVERRIDE; + virtual base::Time LastModifiedTime() OVERRIDE; + virtual bool IsDirectory() OVERRIDE; + + private: + base::FileEnumerator file_enum_; + base::FileEnumerator::FileInfo file_util_info_; +}; + +base::FilePath NativeFileEnumerator::Next() { + base::FilePath rv = file_enum_.Next(); + if (!rv.empty()) + file_util_info_ = file_enum_.GetInfo(); + return rv; +} + +int64 NativeFileEnumerator::Size() { + return file_util_info_.GetSize(); +} + +base::Time NativeFileEnumerator::LastModifiedTime() { + return file_util_info_.GetLastModifiedTime(); +} + +bool NativeFileEnumerator::IsDirectory() { + return file_util_info_.IsDirectory(); +} + +NativeFileUtil::CopyOrMoveMode NativeFileUtil::CopyOrMoveModeForDestination( + const FileSystemURL& dest_url, bool copy) { + if (copy) { + return dest_url.mount_option().copy_sync_option() == COPY_SYNC_OPTION_SYNC ? + COPY_SYNC : COPY_NOSYNC; + } + return MOVE; +} + +base::File NativeFileUtil::CreateOrOpen(const base::FilePath& path, + int file_flags) { + if (!base::DirectoryExists(path.DirName())) { + // If its parent does not exist, should return NOT_FOUND error. + return base::File(base::File::FILE_ERROR_NOT_FOUND); + } + + // TODO(rvargas): Check |file_flags| instead. See bug 356358. + if (base::DirectoryExists(path)) + return base::File(base::File::FILE_ERROR_NOT_A_FILE); + + return base::File(path, file_flags); +} + +base::File::Error NativeFileUtil::EnsureFileExists( + const base::FilePath& path, + bool* created) { + if (!base::DirectoryExists(path.DirName())) + // If its parent does not exist, should return NOT_FOUND error. + return base::File::FILE_ERROR_NOT_FOUND; + + // Tries to create the |path| exclusively. This should fail + // with base::File::FILE_ERROR_EXISTS if the path already exists. + base::File file(path, base::File::FLAG_CREATE | base::File::FLAG_READ); + + if (file.IsValid()) { + if (created) + *created = file.created(); + return base::File::FILE_OK; + } + + base::File::Error error_code = file.error_details(); + if (error_code == base::File::FILE_ERROR_EXISTS) { + // Make sure created_ is false. + if (created) + *created = false; + error_code = base::File::FILE_OK; + } + return error_code; +} + +base::File::Error NativeFileUtil::CreateDirectory( + const base::FilePath& path, + bool exclusive, + bool recursive) { + // If parent dir of file doesn't exist. + if (!recursive && !base::PathExists(path.DirName())) + return base::File::FILE_ERROR_NOT_FOUND; + + bool path_exists = base::PathExists(path); + if (exclusive && path_exists) + return base::File::FILE_ERROR_EXISTS; + + // If file exists at the path. + if (path_exists && !base::DirectoryExists(path)) + return base::File::FILE_ERROR_EXISTS; + + if (!base::CreateDirectory(path)) + return base::File::FILE_ERROR_FAILED; + + if (!SetPlatformSpecificDirectoryPermissions(path)) { + // Since some file systems don't support permission setting, we do not treat + // an error from the function as the failure of copying. Just log it. + LOG(WARNING) << "Setting directory permission failed: " + << path.AsUTF8Unsafe(); + } + + return base::File::FILE_OK; +} + +base::File::Error NativeFileUtil::GetFileInfo( + const base::FilePath& path, + base::File::Info* file_info) { + if (!base::PathExists(path)) + return base::File::FILE_ERROR_NOT_FOUND; + + if (!base::GetFileInfo(path, file_info)) + return base::File::FILE_ERROR_FAILED; + return base::File::FILE_OK; +} + +scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> + NativeFileUtil::CreateFileEnumerator(const base::FilePath& root_path, + bool recursive) { + return make_scoped_ptr(new NativeFileEnumerator( + root_path, recursive, + base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES)) + .PassAs<FileSystemFileUtil::AbstractFileEnumerator>(); +} + +base::File::Error NativeFileUtil::Touch( + const base::FilePath& path, + const base::Time& last_access_time, + const base::Time& last_modified_time) { + if (!base::TouchFile(path, last_access_time, last_modified_time)) + return base::File::FILE_ERROR_FAILED; + return base::File::FILE_OK; +} + +base::File::Error NativeFileUtil::Truncate(const base::FilePath& path, + int64 length) { + base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_WRITE); + if (!file.IsValid()) + return file.error_details(); + + if (!file.SetLength(length)) + return base::File::FILE_ERROR_FAILED; + + return base::File::FILE_OK; +} + +bool NativeFileUtil::PathExists(const base::FilePath& path) { + return base::PathExists(path); +} + +bool NativeFileUtil::DirectoryExists(const base::FilePath& path) { + return base::DirectoryExists(path); +} + +base::File::Error NativeFileUtil::CopyOrMoveFile( + const base::FilePath& src_path, + const base::FilePath& dest_path, + FileSystemOperation::CopyOrMoveOption option, + CopyOrMoveMode mode) { + base::File::Info info; + base::File::Error error = NativeFileUtil::GetFileInfo(src_path, &info); + if (error != base::File::FILE_OK) + return error; + if (info.is_directory) + return base::File::FILE_ERROR_NOT_A_FILE; + base::Time last_modified = info.last_modified; + + error = NativeFileUtil::GetFileInfo(dest_path, &info); + if (error != base::File::FILE_OK && + error != base::File::FILE_ERROR_NOT_FOUND) + return error; + if (info.is_directory) + return base::File::FILE_ERROR_INVALID_OPERATION; + if (error == base::File::FILE_ERROR_NOT_FOUND) { + error = NativeFileUtil::GetFileInfo(dest_path.DirName(), &info); + if (error != base::File::FILE_OK) + return error; + if (!info.is_directory) + return base::File::FILE_ERROR_NOT_FOUND; + } + + switch (mode) { + case COPY_NOSYNC: + if (!base::CopyFile(src_path, dest_path)) + return base::File::FILE_ERROR_FAILED; + break; + case COPY_SYNC: + if (!CopyFileAndSync(src_path, dest_path)) + return base::File::FILE_ERROR_FAILED; + break; + case MOVE: + if (!base::Move(src_path, dest_path)) + return base::File::FILE_ERROR_FAILED; + break; + } + + // Preserve the last modified time. Do not return error here even if + // the setting is failed, because the copy itself is successfully done. + if (option == FileSystemOperation::OPTION_PRESERVE_LAST_MODIFIED) + base::TouchFile(dest_path, last_modified, last_modified); + + return base::File::FILE_OK; +} + +base::File::Error NativeFileUtil::DeleteFile(const base::FilePath& path) { + if (!base::PathExists(path)) + return base::File::FILE_ERROR_NOT_FOUND; + if (base::DirectoryExists(path)) + return base::File::FILE_ERROR_NOT_A_FILE; + if (!base::DeleteFile(path, false)) + return base::File::FILE_ERROR_FAILED; + return base::File::FILE_OK; +} + +base::File::Error NativeFileUtil::DeleteDirectory(const base::FilePath& path) { + if (!base::PathExists(path)) + return base::File::FILE_ERROR_NOT_FOUND; + if (!base::DirectoryExists(path)) + return base::File::FILE_ERROR_NOT_A_DIRECTORY; + if (!base::IsDirectoryEmpty(path)) + return base::File::FILE_ERROR_NOT_EMPTY; + if (!base::DeleteFile(path, false)) + return base::File::FILE_ERROR_FAILED; + return base::File::FILE_OK; +} + +} // namespace storage diff --git a/storage/browser/fileapi/native_file_util.h b/storage/browser/fileapi/native_file_util.h new file mode 100644 index 0000000..b8c180e --- /dev/null +++ b/storage/browser/fileapi/native_file_util.h @@ -0,0 +1,73 @@ +// 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. + +#ifndef STORAGE_BROWSER_FILEAPI_NATIVE_FILE_UTIL_H_ +#define STORAGE_BROWSER_FILEAPI_NATIVE_FILE_UTIL_H_ + +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/files/file_util_proxy.h" +#include "base/memory/scoped_ptr.h" +#include "storage/browser/fileapi/file_system_file_util.h" +#include "storage/browser/storage_browser_export.h" + +namespace base { +class Time; +} + +namespace storage { + +// A thin wrapper class for accessing the OS native filesystem. +// This performs common error checks necessary to implement FileUtil family +// in addition to perform native filesystem operations. +// +// For the error checks it performs please see the comment for +// FileSystemFileUtil interface +// (webkit/browser/fileapi/file_system_file_util.h). +// +// Note that all the methods of this class are static and this does NOT +// inherit from FileSystemFileUtil. +class STORAGE_EXPORT_PRIVATE NativeFileUtil { + public: + enum CopyOrMoveMode { + COPY_NOSYNC, + COPY_SYNC, + MOVE + }; + static CopyOrMoveMode CopyOrMoveModeForDestination( + const FileSystemURL& dest_url, bool copy); + + static base::File CreateOrOpen(const base::FilePath& path, int file_flags); + static base::File::Error EnsureFileExists(const base::FilePath& path, + bool* created); + static base::File::Error CreateDirectory(const base::FilePath& path, + bool exclusive, + bool recursive); + static base::File::Error GetFileInfo(const base::FilePath& path, + base::File::Info* file_info); + static scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> + CreateFileEnumerator(const base::FilePath& root_path, + bool recursive); + static base::File::Error Touch(const base::FilePath& path, + const base::Time& last_access_time, + const base::Time& last_modified_time); + static base::File::Error Truncate(const base::FilePath& path, + int64 length); + static bool PathExists(const base::FilePath& path); + static bool DirectoryExists(const base::FilePath& path); + static base::File::Error CopyOrMoveFile( + const base::FilePath& src_path, + const base::FilePath& dest_path, + FileSystemOperation::CopyOrMoveOption option, + CopyOrMoveMode mode); + static base::File::Error DeleteFile(const base::FilePath& path); + static base::File::Error DeleteDirectory(const base::FilePath& path); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(NativeFileUtil); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_NATIVE_FILE_UTIL_H_ diff --git a/storage/browser/fileapi/obfuscated_file_util.cc b/storage/browser/fileapi/obfuscated_file_util.cc new file mode 100644 index 0000000..8c02dd1 --- /dev/null +++ b/storage/browser/fileapi/obfuscated_file_util.cc @@ -0,0 +1,1424 @@ +// 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 "storage/browser/fileapi/obfuscated_file_util.h" + +#include <queue> +#include <string> +#include <vector> + +#include "base/files/file_util.h" +#include "base/format_macros.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/metrics/histogram.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/strings/sys_string_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "storage/browser/fileapi/file_observers.h" +#include "storage/browser/fileapi/file_system_context.h" +#include "storage/browser/fileapi/file_system_operation_context.h" +#include "storage/browser/fileapi/file_system_url.h" +#include "storage/browser/fileapi/native_file_util.h" +#include "storage/browser/fileapi/sandbox_file_system_backend.h" +#include "storage/browser/fileapi/sandbox_isolated_origin_database.h" +#include "storage/browser/fileapi/sandbox_origin_database.h" +#include "storage/browser/fileapi/sandbox_prioritized_origin_database.h" +#include "storage/browser/fileapi/timed_task_helper.h" +#include "storage/browser/quota/quota_manager.h" +#include "storage/common/database/database_identifier.h" +#include "storage/common/fileapi/file_system_util.h" +#include "url/gurl.h" + +// Example of various paths: +// void ObfuscatedFileUtil::DoSomething(const FileSystemURL& url) { +// base::FilePath virtual_path = url.path(); +// base::FilePath local_path = GetLocalFilePath(url); +// +// NativeFileUtil::DoSomething(local_path); +// file_util::DoAnother(local_path); +// } + +namespace storage { + +namespace { + +typedef SandboxDirectoryDatabase::FileId FileId; +typedef SandboxDirectoryDatabase::FileInfo FileInfo; + +void InitFileInfo( + SandboxDirectoryDatabase::FileInfo* file_info, + SandboxDirectoryDatabase::FileId parent_id, + const base::FilePath::StringType& file_name) { + DCHECK(file_info); + file_info->parent_id = parent_id; + file_info->name = file_name; +} + +// Costs computed as per crbug.com/86114, based on the LevelDB implementation of +// path storage under Linux. It's not clear if that will differ on Windows, on +// which base::FilePath uses wide chars [since they're converted to UTF-8 for +// storage anyway], but as long as the cost is high enough that one can't cheat +// on quota by storing data in paths, it doesn't need to be all that accurate. +const int64 kPathCreationQuotaCost = 146; // Bytes per inode, basically. +const int64 kPathByteQuotaCost = 2; // Bytes per byte of path length in UTF-8. + +int64 UsageForPath(size_t length) { + return kPathCreationQuotaCost + + static_cast<int64>(length) * kPathByteQuotaCost; +} + +bool AllocateQuota(FileSystemOperationContext* context, int64 growth) { + if (context->allowed_bytes_growth() == storage::QuotaManager::kNoLimit) + return true; + + int64 new_quota = context->allowed_bytes_growth() - growth; + if (growth > 0 && new_quota < 0) + return false; + context->set_allowed_bytes_growth(new_quota); + return true; +} + +void UpdateUsage( + FileSystemOperationContext* context, + const FileSystemURL& url, + int64 growth) { + context->update_observers()->Notify( + &FileUpdateObserver::OnUpdate, MakeTuple(url, growth)); +} + +void TouchDirectory(SandboxDirectoryDatabase* db, FileId dir_id) { + DCHECK(db); + if (!db->UpdateModificationTime(dir_id, base::Time::Now())) + NOTREACHED(); +} + +enum IsolatedOriginStatus { + kIsolatedOriginMatch, + kIsolatedOriginDontMatch, + kIsolatedOriginStatusMax, +}; + +} // namespace + +class ObfuscatedFileEnumerator + : public FileSystemFileUtil::AbstractFileEnumerator { + public: + ObfuscatedFileEnumerator( + SandboxDirectoryDatabase* db, + FileSystemOperationContext* context, + ObfuscatedFileUtil* obfuscated_file_util, + const FileSystemURL& root_url, + bool recursive) + : db_(db), + context_(context), + obfuscated_file_util_(obfuscated_file_util), + root_url_(root_url), + recursive_(recursive), + current_file_id_(0) { + base::FilePath root_virtual_path = root_url.path(); + FileId file_id; + + if (!db_->GetFileWithPath(root_virtual_path, &file_id)) + return; + + FileRecord record = { file_id, root_virtual_path }; + recurse_queue_.push(record); + } + + virtual ~ObfuscatedFileEnumerator() {} + + virtual base::FilePath Next() OVERRIDE { + ProcessRecurseQueue(); + if (display_stack_.empty()) + return base::FilePath(); + + current_file_id_ = display_stack_.back(); + display_stack_.pop_back(); + + FileInfo file_info; + base::FilePath platform_file_path; + base::File::Error error = + obfuscated_file_util_->GetFileInfoInternal( + db_, context_, root_url_, current_file_id_, + &file_info, ¤t_platform_file_info_, &platform_file_path); + if (error != base::File::FILE_OK) + return Next(); + + base::FilePath virtual_path = + current_parent_virtual_path_.Append(file_info.name); + if (recursive_ && file_info.is_directory()) { + FileRecord record = { current_file_id_, virtual_path }; + recurse_queue_.push(record); + } + return virtual_path; + } + + virtual int64 Size() OVERRIDE { + return current_platform_file_info_.size; + } + + virtual base::Time LastModifiedTime() OVERRIDE { + return current_platform_file_info_.last_modified; + } + + virtual bool IsDirectory() OVERRIDE { + return current_platform_file_info_.is_directory; + } + + private: + typedef SandboxDirectoryDatabase::FileId FileId; + typedef SandboxDirectoryDatabase::FileInfo FileInfo; + + struct FileRecord { + FileId file_id; + base::FilePath virtual_path; + }; + + void ProcessRecurseQueue() { + while (display_stack_.empty() && !recurse_queue_.empty()) { + FileRecord entry = recurse_queue_.front(); + recurse_queue_.pop(); + if (!db_->ListChildren(entry.file_id, &display_stack_)) { + display_stack_.clear(); + return; + } + current_parent_virtual_path_ = entry.virtual_path; + } + } + + SandboxDirectoryDatabase* db_; + FileSystemOperationContext* context_; + ObfuscatedFileUtil* obfuscated_file_util_; + FileSystemURL root_url_; + bool recursive_; + + std::queue<FileRecord> recurse_queue_; + std::vector<FileId> display_stack_; + base::FilePath current_parent_virtual_path_; + + FileId current_file_id_; + base::File::Info current_platform_file_info_; +}; + +class ObfuscatedOriginEnumerator + : public ObfuscatedFileUtil::AbstractOriginEnumerator { + public: + typedef SandboxOriginDatabase::OriginRecord OriginRecord; + ObfuscatedOriginEnumerator( + SandboxOriginDatabaseInterface* origin_database, + const base::FilePath& base_file_path) + : base_file_path_(base_file_path) { + if (origin_database) + origin_database->ListAllOrigins(&origins_); + } + + virtual ~ObfuscatedOriginEnumerator() {} + + // Returns the next origin. Returns empty if there are no more origins. + virtual GURL Next() OVERRIDE { + OriginRecord record; + if (!origins_.empty()) { + record = origins_.back(); + origins_.pop_back(); + } + current_ = record; + return storage::GetOriginFromIdentifier(record.origin); + } + + // Returns the current origin's information. + virtual bool HasTypeDirectory(const std::string& type_string) const OVERRIDE { + if (current_.path.empty()) + return false; + if (type_string.empty()) { + NOTREACHED(); + return false; + } + base::FilePath path = + base_file_path_.Append(current_.path).AppendASCII(type_string); + return base::DirectoryExists(path); + } + + private: + std::vector<OriginRecord> origins_; + OriginRecord current_; + base::FilePath base_file_path_; +}; + +ObfuscatedFileUtil::ObfuscatedFileUtil( + storage::SpecialStoragePolicy* special_storage_policy, + const base::FilePath& file_system_directory, + leveldb::Env* env_override, + base::SequencedTaskRunner* file_task_runner, + const GetTypeStringForURLCallback& get_type_string_for_url, + const std::set<std::string>& known_type_strings, + SandboxFileSystemBackendDelegate* sandbox_delegate) + : special_storage_policy_(special_storage_policy), + file_system_directory_(file_system_directory), + env_override_(env_override), + db_flush_delay_seconds_(10 * 60), // 10 mins. + file_task_runner_(file_task_runner), + get_type_string_for_url_(get_type_string_for_url), + known_type_strings_(known_type_strings), + sandbox_delegate_(sandbox_delegate) { +} + +ObfuscatedFileUtil::~ObfuscatedFileUtil() { + DropDatabases(); +} + +base::File ObfuscatedFileUtil::CreateOrOpen( + FileSystemOperationContext* context, + const FileSystemURL& url, int file_flags) { + base::File file = CreateOrOpenInternal(context, url, file_flags); + if (file.IsValid() && file_flags & base::File::FLAG_WRITE && + context->quota_limit_type() == storage::kQuotaLimitTypeUnlimited && + sandbox_delegate_) { + sandbox_delegate_->StickyInvalidateUsageCache(url.origin(), url.type()); + } + return file.Pass(); +} + +base::File::Error ObfuscatedFileUtil::EnsureFileExists( + FileSystemOperationContext* context, + const FileSystemURL& url, + bool* created) { + SandboxDirectoryDatabase* db = GetDirectoryDatabase(url, true); + if (!db) + return base::File::FILE_ERROR_FAILED; + + FileId file_id; + if (db->GetFileWithPath(url.path(), &file_id)) { + FileInfo file_info; + if (!db->GetFileInfo(file_id, &file_info)) { + NOTREACHED(); + return base::File::FILE_ERROR_FAILED; + } + if (file_info.is_directory()) + return base::File::FILE_ERROR_NOT_A_FILE; + if (created) + *created = false; + return base::File::FILE_OK; + } + FileId parent_id; + if (!db->GetFileWithPath(VirtualPath::DirName(url.path()), &parent_id)) + return base::File::FILE_ERROR_NOT_FOUND; + + FileInfo file_info; + InitFileInfo(&file_info, parent_id, + VirtualPath::BaseName(url.path()).value()); + + int64 growth = UsageForPath(file_info.name.size()); + if (!AllocateQuota(context, growth)) + return base::File::FILE_ERROR_NO_SPACE; + base::File::Error error = CreateFile(context, base::FilePath(), url, + &file_info); + if (created && base::File::FILE_OK == error) { + *created = true; + UpdateUsage(context, url, growth); + context->change_observers()->Notify( + &FileChangeObserver::OnCreateFile, MakeTuple(url)); + } + return error; +} + +base::File::Error ObfuscatedFileUtil::CreateDirectory( + FileSystemOperationContext* context, + const FileSystemURL& url, + bool exclusive, + bool recursive) { + SandboxDirectoryDatabase* db = GetDirectoryDatabase(url, true); + if (!db) + return base::File::FILE_ERROR_FAILED; + + FileId file_id; + if (db->GetFileWithPath(url.path(), &file_id)) { + FileInfo file_info; + if (exclusive) + return base::File::FILE_ERROR_EXISTS; + if (!db->GetFileInfo(file_id, &file_info)) { + NOTREACHED(); + return base::File::FILE_ERROR_FAILED; + } + if (!file_info.is_directory()) + return base::File::FILE_ERROR_NOT_A_DIRECTORY; + return base::File::FILE_OK; + } + + std::vector<base::FilePath::StringType> components; + VirtualPath::GetComponents(url.path(), &components); + FileId parent_id = 0; + size_t index; + for (index = 0; index < components.size(); ++index) { + base::FilePath::StringType name = components[index]; + if (name == FILE_PATH_LITERAL("/")) + continue; + if (!db->GetChildWithName(parent_id, name, &parent_id)) + break; + } + if (!db->IsDirectory(parent_id)) + return base::File::FILE_ERROR_NOT_A_DIRECTORY; + if (!recursive && components.size() - index > 1) + return base::File::FILE_ERROR_NOT_FOUND; + bool first = true; + for (; index < components.size(); ++index) { + FileInfo file_info; + file_info.name = components[index]; + if (file_info.name == FILE_PATH_LITERAL("/")) + continue; + file_info.modification_time = base::Time::Now(); + file_info.parent_id = parent_id; + int64 growth = UsageForPath(file_info.name.size()); + if (!AllocateQuota(context, growth)) + return base::File::FILE_ERROR_NO_SPACE; + base::File::Error error = db->AddFileInfo(file_info, &parent_id); + if (error != base::File::FILE_OK) + return error; + UpdateUsage(context, url, growth); + context->change_observers()->Notify( + &FileChangeObserver::OnCreateDirectory, MakeTuple(url)); + if (first) { + first = false; + TouchDirectory(db, file_info.parent_id); + } + } + return base::File::FILE_OK; +} + +base::File::Error ObfuscatedFileUtil::GetFileInfo( + FileSystemOperationContext* context, + const FileSystemURL& url, + base::File::Info* file_info, + base::FilePath* platform_file_path) { + SandboxDirectoryDatabase* db = GetDirectoryDatabase(url, false); + if (!db) + return base::File::FILE_ERROR_NOT_FOUND; + FileId file_id; + if (!db->GetFileWithPath(url.path(), &file_id)) + return base::File::FILE_ERROR_NOT_FOUND; + FileInfo local_info; + return GetFileInfoInternal(db, context, url, + file_id, &local_info, + file_info, platform_file_path); +} + +scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> + ObfuscatedFileUtil::CreateFileEnumerator( + FileSystemOperationContext* context, + const FileSystemURL& root_url) { + return CreateFileEnumerator(context, root_url, false /* recursive */); +} + +base::File::Error ObfuscatedFileUtil::GetLocalFilePath( + FileSystemOperationContext* context, + const FileSystemURL& url, + base::FilePath* local_path) { + SandboxDirectoryDatabase* db = GetDirectoryDatabase(url, false); + if (!db) + return base::File::FILE_ERROR_NOT_FOUND; + FileId file_id; + if (!db->GetFileWithPath(url.path(), &file_id)) + return base::File::FILE_ERROR_NOT_FOUND; + FileInfo file_info; + if (!db->GetFileInfo(file_id, &file_info) || file_info.is_directory()) { + NOTREACHED(); + // Directories have no local file path. + return base::File::FILE_ERROR_NOT_FOUND; + } + *local_path = DataPathToLocalPath(url, file_info.data_path); + + if (local_path->empty()) + return base::File::FILE_ERROR_NOT_FOUND; + return base::File::FILE_OK; +} + +base::File::Error ObfuscatedFileUtil::Touch( + FileSystemOperationContext* context, + const FileSystemURL& url, + const base::Time& last_access_time, + const base::Time& last_modified_time) { + SandboxDirectoryDatabase* db = GetDirectoryDatabase(url, false); + if (!db) + return base::File::FILE_ERROR_NOT_FOUND; + FileId file_id; + if (!db->GetFileWithPath(url.path(), &file_id)) + return base::File::FILE_ERROR_NOT_FOUND; + + FileInfo file_info; + if (!db->GetFileInfo(file_id, &file_info)) { + NOTREACHED(); + return base::File::FILE_ERROR_FAILED; + } + if (file_info.is_directory()) { + if (!db->UpdateModificationTime(file_id, last_modified_time)) + return base::File::FILE_ERROR_FAILED; + return base::File::FILE_OK; + } + return NativeFileUtil::Touch( + DataPathToLocalPath(url, file_info.data_path), + last_access_time, last_modified_time); +} + +base::File::Error ObfuscatedFileUtil::Truncate( + FileSystemOperationContext* context, + const FileSystemURL& url, + int64 length) { + base::File::Info file_info; + base::FilePath local_path; + base::File::Error error = + GetFileInfo(context, url, &file_info, &local_path); + if (error != base::File::FILE_OK) + return error; + + int64 growth = length - file_info.size; + if (!AllocateQuota(context, growth)) + return base::File::FILE_ERROR_NO_SPACE; + error = NativeFileUtil::Truncate(local_path, length); + if (error == base::File::FILE_OK) { + UpdateUsage(context, url, growth); + context->change_observers()->Notify( + &FileChangeObserver::OnModifyFile, MakeTuple(url)); + } + return error; +} + +base::File::Error ObfuscatedFileUtil::CopyOrMoveFile( + FileSystemOperationContext* context, + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOption option, + bool copy) { + // Cross-filesystem copies and moves should be handled via CopyInForeignFile. + DCHECK(src_url.origin() == dest_url.origin()); + DCHECK(src_url.type() == dest_url.type()); + + SandboxDirectoryDatabase* db = GetDirectoryDatabase(src_url, true); + if (!db) + return base::File::FILE_ERROR_FAILED; + + FileId src_file_id; + if (!db->GetFileWithPath(src_url.path(), &src_file_id)) + return base::File::FILE_ERROR_NOT_FOUND; + + FileId dest_file_id; + bool overwrite = db->GetFileWithPath(dest_url.path(), + &dest_file_id); + + FileInfo src_file_info; + base::File::Info src_platform_file_info; + base::FilePath src_local_path; + base::File::Error error = GetFileInfoInternal( + db, context, src_url, src_file_id, + &src_file_info, &src_platform_file_info, &src_local_path); + if (error != base::File::FILE_OK) + return error; + if (src_file_info.is_directory()) + return base::File::FILE_ERROR_NOT_A_FILE; + + FileInfo dest_file_info; + base::File::Info dest_platform_file_info; // overwrite case only + base::FilePath dest_local_path; // overwrite case only + if (overwrite) { + base::File::Error error = GetFileInfoInternal( + db, context, dest_url, dest_file_id, + &dest_file_info, &dest_platform_file_info, &dest_local_path); + if (error == base::File::FILE_ERROR_NOT_FOUND) + overwrite = false; // fallback to non-overwrite case + else if (error != base::File::FILE_OK) + return error; + else if (dest_file_info.is_directory()) + return base::File::FILE_ERROR_INVALID_OPERATION; + } + if (!overwrite) { + FileId dest_parent_id; + if (!db->GetFileWithPath(VirtualPath::DirName(dest_url.path()), + &dest_parent_id)) { + return base::File::FILE_ERROR_NOT_FOUND; + } + + dest_file_info = src_file_info; + dest_file_info.parent_id = dest_parent_id; + dest_file_info.name = + VirtualPath::BaseName(dest_url.path()).value(); + } + + int64 growth = 0; + if (copy) + growth += src_platform_file_info.size; + else + growth -= UsageForPath(src_file_info.name.size()); + if (overwrite) + growth -= dest_platform_file_info.size; + else + growth += UsageForPath(dest_file_info.name.size()); + if (!AllocateQuota(context, growth)) + return base::File::FILE_ERROR_NO_SPACE; + + /* + * Copy-with-overwrite + * Just overwrite data file + * Copy-without-overwrite + * Copy backing file + * Create new metadata pointing to new backing file. + * Move-with-overwrite + * transaction: + * Remove source entry. + * Point target entry to source entry's backing file. + * Delete target entry's old backing file + * Move-without-overwrite + * Just update metadata + */ + error = base::File::FILE_ERROR_FAILED; + if (copy) { + if (overwrite) { + error = NativeFileUtil::CopyOrMoveFile( + src_local_path, + dest_local_path, + option, + storage::NativeFileUtil::CopyOrMoveModeForDestination( + dest_url, true /* copy */)); + } else { // non-overwrite + error = CreateFile(context, src_local_path, dest_url, &dest_file_info); + } + } else { + if (overwrite) { + if (db->OverwritingMoveFile(src_file_id, dest_file_id)) { + if (base::File::FILE_OK != + NativeFileUtil::DeleteFile(dest_local_path)) + LOG(WARNING) << "Leaked a backing file."; + error = base::File::FILE_OK; + } else { + error = base::File::FILE_ERROR_FAILED; + } + } else { // non-overwrite + if (db->UpdateFileInfo(src_file_id, dest_file_info)) + error = base::File::FILE_OK; + else + error = base::File::FILE_ERROR_FAILED; + } + } + + if (error != base::File::FILE_OK) + return error; + + if (overwrite) { + context->change_observers()->Notify( + &FileChangeObserver::OnModifyFile, + MakeTuple(dest_url)); + } else { + context->change_observers()->Notify( + &FileChangeObserver::OnCreateFileFrom, + MakeTuple(dest_url, src_url)); + } + + if (!copy) { + context->change_observers()->Notify( + &FileChangeObserver::OnRemoveFile, MakeTuple(src_url)); + TouchDirectory(db, src_file_info.parent_id); + } + + TouchDirectory(db, dest_file_info.parent_id); + + UpdateUsage(context, dest_url, growth); + return error; +} + +base::File::Error ObfuscatedFileUtil::CopyInForeignFile( + FileSystemOperationContext* context, + const base::FilePath& src_file_path, + const FileSystemURL& dest_url) { + SandboxDirectoryDatabase* db = GetDirectoryDatabase(dest_url, true); + if (!db) + return base::File::FILE_ERROR_FAILED; + + base::File::Info src_platform_file_info; + if (!base::GetFileInfo(src_file_path, &src_platform_file_info)) + return base::File::FILE_ERROR_NOT_FOUND; + + FileId dest_file_id; + bool overwrite = db->GetFileWithPath(dest_url.path(), + &dest_file_id); + + FileInfo dest_file_info; + base::File::Info dest_platform_file_info; // overwrite case only + if (overwrite) { + base::FilePath dest_local_path; + base::File::Error error = GetFileInfoInternal( + db, context, dest_url, dest_file_id, + &dest_file_info, &dest_platform_file_info, &dest_local_path); + if (error == base::File::FILE_ERROR_NOT_FOUND) + overwrite = false; // fallback to non-overwrite case + else if (error != base::File::FILE_OK) + return error; + else if (dest_file_info.is_directory()) + return base::File::FILE_ERROR_INVALID_OPERATION; + } + if (!overwrite) { + FileId dest_parent_id; + if (!db->GetFileWithPath(VirtualPath::DirName(dest_url.path()), + &dest_parent_id)) { + return base::File::FILE_ERROR_NOT_FOUND; + } + if (!dest_file_info.is_directory()) + return base::File::FILE_ERROR_FAILED; + InitFileInfo(&dest_file_info, dest_parent_id, + VirtualPath::BaseName(dest_url.path()).value()); + } + + int64 growth = src_platform_file_info.size; + if (overwrite) + growth -= dest_platform_file_info.size; + else + growth += UsageForPath(dest_file_info.name.size()); + if (!AllocateQuota(context, growth)) + return base::File::FILE_ERROR_NO_SPACE; + + base::File::Error error; + if (overwrite) { + base::FilePath dest_local_path = + DataPathToLocalPath(dest_url, dest_file_info.data_path); + error = NativeFileUtil::CopyOrMoveFile( + src_file_path, + dest_local_path, + FileSystemOperation::OPTION_NONE, + storage::NativeFileUtil::CopyOrMoveModeForDestination(dest_url, + true /* copy */)); + } else { + error = CreateFile(context, src_file_path, dest_url, &dest_file_info); + } + + if (error != base::File::FILE_OK) + return error; + + if (overwrite) { + context->change_observers()->Notify( + &FileChangeObserver::OnModifyFile, MakeTuple(dest_url)); + } else { + context->change_observers()->Notify( + &FileChangeObserver::OnCreateFile, MakeTuple(dest_url)); + } + + UpdateUsage(context, dest_url, growth); + TouchDirectory(db, dest_file_info.parent_id); + return base::File::FILE_OK; +} + +base::File::Error ObfuscatedFileUtil::DeleteFile( + FileSystemOperationContext* context, + const FileSystemURL& url) { + SandboxDirectoryDatabase* db = GetDirectoryDatabase(url, true); + if (!db) + return base::File::FILE_ERROR_FAILED; + FileId file_id; + if (!db->GetFileWithPath(url.path(), &file_id)) + return base::File::FILE_ERROR_NOT_FOUND; + + FileInfo file_info; + base::File::Info platform_file_info; + base::FilePath local_path; + base::File::Error error = GetFileInfoInternal( + db, context, url, file_id, &file_info, &platform_file_info, &local_path); + if (error != base::File::FILE_ERROR_NOT_FOUND && + error != base::File::FILE_OK) + return error; + + if (file_info.is_directory()) + return base::File::FILE_ERROR_NOT_A_FILE; + + int64 growth = -UsageForPath(file_info.name.size()) - platform_file_info.size; + AllocateQuota(context, growth); + if (!db->RemoveFileInfo(file_id)) { + NOTREACHED(); + return base::File::FILE_ERROR_FAILED; + } + UpdateUsage(context, url, growth); + TouchDirectory(db, file_info.parent_id); + + context->change_observers()->Notify( + &FileChangeObserver::OnRemoveFile, MakeTuple(url)); + + if (error == base::File::FILE_ERROR_NOT_FOUND) + return base::File::FILE_OK; + + error = NativeFileUtil::DeleteFile(local_path); + if (base::File::FILE_OK != error) + LOG(WARNING) << "Leaked a backing file."; + return base::File::FILE_OK; +} + +base::File::Error ObfuscatedFileUtil::DeleteDirectory( + FileSystemOperationContext* context, + const FileSystemURL& url) { + SandboxDirectoryDatabase* db = GetDirectoryDatabase(url, true); + if (!db) + return base::File::FILE_ERROR_FAILED; + + FileId file_id; + if (!db->GetFileWithPath(url.path(), &file_id)) + return base::File::FILE_ERROR_NOT_FOUND; + FileInfo file_info; + if (!db->GetFileInfo(file_id, &file_info)) { + NOTREACHED(); + return base::File::FILE_ERROR_FAILED; + } + if (!file_info.is_directory()) + return base::File::FILE_ERROR_NOT_A_DIRECTORY; + if (!db->RemoveFileInfo(file_id)) + return base::File::FILE_ERROR_NOT_EMPTY; + int64 growth = -UsageForPath(file_info.name.size()); + AllocateQuota(context, growth); + UpdateUsage(context, url, growth); + TouchDirectory(db, file_info.parent_id); + context->change_observers()->Notify( + &FileChangeObserver::OnRemoveDirectory, MakeTuple(url)); + return base::File::FILE_OK; +} + +storage::ScopedFile ObfuscatedFileUtil::CreateSnapshotFile( + FileSystemOperationContext* context, + const FileSystemURL& url, + base::File::Error* error, + base::File::Info* file_info, + base::FilePath* platform_path) { + // We're just returning the local file information. + *error = GetFileInfo(context, url, file_info, platform_path); + if (*error == base::File::FILE_OK && file_info->is_directory) { + *file_info = base::File::Info(); + *error = base::File::FILE_ERROR_NOT_A_FILE; + } + return storage::ScopedFile(); +} + +scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> + ObfuscatedFileUtil::CreateFileEnumerator( + FileSystemOperationContext* context, + const FileSystemURL& root_url, + bool recursive) { + SandboxDirectoryDatabase* db = GetDirectoryDatabase(root_url, false); + if (!db) { + return scoped_ptr<AbstractFileEnumerator>(new EmptyFileEnumerator()); + } + return scoped_ptr<AbstractFileEnumerator>( + new ObfuscatedFileEnumerator(db, context, this, root_url, recursive)); +} + +bool ObfuscatedFileUtil::IsDirectoryEmpty( + FileSystemOperationContext* context, + const FileSystemURL& url) { + SandboxDirectoryDatabase* db = GetDirectoryDatabase(url, false); + if (!db) + return true; // Not a great answer, but it's what others do. + FileId file_id; + if (!db->GetFileWithPath(url.path(), &file_id)) + return true; // Ditto. + FileInfo file_info; + if (!db->GetFileInfo(file_id, &file_info)) { + DCHECK(!file_id); + // It's the root directory and the database hasn't been initialized yet. + return true; + } + if (!file_info.is_directory()) + return true; + std::vector<FileId> children; + // TODO(ericu): This could easily be made faster with help from the database. + if (!db->ListChildren(file_id, &children)) + return true; + return children.empty(); +} + +base::FilePath ObfuscatedFileUtil::GetDirectoryForOriginAndType( + const GURL& origin, + const std::string& type_string, + bool create, + base::File::Error* error_code) { + base::FilePath origin_dir = GetDirectoryForOrigin(origin, create, error_code); + if (origin_dir.empty()) + return base::FilePath(); + if (type_string.empty()) + return origin_dir; + base::FilePath path = origin_dir.AppendASCII(type_string); + base::File::Error error = base::File::FILE_OK; + if (!base::DirectoryExists(path) && + (!create || !base::CreateDirectory(path))) { + error = create ? + base::File::FILE_ERROR_FAILED : + base::File::FILE_ERROR_NOT_FOUND; + } + + if (error_code) + *error_code = error; + return path; +} + +bool ObfuscatedFileUtil::DeleteDirectoryForOriginAndType( + const GURL& origin, + const std::string& type_string) { + base::File::Error error = base::File::FILE_OK; + base::FilePath origin_type_path = GetDirectoryForOriginAndType( + origin, type_string, false, &error); + if (origin_type_path.empty()) + return true; + if (error != base::File::FILE_ERROR_NOT_FOUND) { + // TODO(dmikurube): Consider the return value of DestroyDirectoryDatabase. + // We ignore its error now since 1) it doesn't matter the final result, and + // 2) it always returns false in Windows because of LevelDB's + // implementation. + // Information about failure would be useful for debugging. + if (!type_string.empty()) + DestroyDirectoryDatabase(origin, type_string); + if (!base::DeleteFile(origin_type_path, true /* recursive */)) + return false; + } + + base::FilePath origin_path = VirtualPath::DirName(origin_type_path); + DCHECK_EQ(origin_path.value(), + GetDirectoryForOrigin(origin, false, NULL).value()); + + if (!type_string.empty()) { + // At this point we are sure we had successfully deleted the origin/type + // directory (i.e. we're ready to just return true). + // See if we have other directories in this origin directory. + for (std::set<std::string>::iterator iter = known_type_strings_.begin(); + iter != known_type_strings_.end(); + ++iter) { + if (*iter == type_string) + continue; + if (base::DirectoryExists(origin_path.AppendASCII(*iter))) { + // Other type's directory exists; just return true here. + return true; + } + } + } + + // No other directories seem exist. Try deleting the entire origin directory. + InitOriginDatabase(origin, false); + if (origin_database_) { + origin_database_->RemovePathForOrigin( + storage::GetIdentifierFromOrigin(origin)); + } + if (!base::DeleteFile(origin_path, true /* recursive */)) + return false; + + return true; +} + +ObfuscatedFileUtil::AbstractOriginEnumerator* +ObfuscatedFileUtil::CreateOriginEnumerator() { + std::vector<SandboxOriginDatabase::OriginRecord> origins; + + InitOriginDatabase(GURL(), false); + return new ObfuscatedOriginEnumerator( + origin_database_.get(), file_system_directory_); +} + +bool ObfuscatedFileUtil::DestroyDirectoryDatabase( + const GURL& origin, + const std::string& type_string) { + std::string key = GetDirectoryDatabaseKey(origin, type_string); + if (key.empty()) + return true; + DirectoryMap::iterator iter = directories_.find(key); + if (iter != directories_.end()) { + SandboxDirectoryDatabase* database = iter->second; + directories_.erase(iter); + delete database; + } + + base::File::Error error = base::File::FILE_OK; + base::FilePath path = GetDirectoryForOriginAndType( + origin, type_string, false, &error); + if (path.empty() || error == base::File::FILE_ERROR_NOT_FOUND) + return true; + return SandboxDirectoryDatabase::DestroyDatabase(path, env_override_); +} + +// static +int64 ObfuscatedFileUtil::ComputeFilePathCost(const base::FilePath& path) { + return UsageForPath(VirtualPath::BaseName(path).value().size()); +} + +void ObfuscatedFileUtil::MaybePrepopulateDatabase( + const std::vector<std::string>& type_strings_to_prepopulate) { + SandboxPrioritizedOriginDatabase database(file_system_directory_, + env_override_); + std::string origin_string = database.GetPrimaryOrigin(); + if (origin_string.empty() || !database.HasOriginPath(origin_string)) + return; + const GURL origin = storage::GetOriginFromIdentifier(origin_string); + + // Prepopulate the directory database(s) if and only if this instance + // has primary origin and the directory database is already there. + for (size_t i = 0; i < type_strings_to_prepopulate.size(); ++i) { + const std::string type_string = type_strings_to_prepopulate[i]; + // Only handles known types. + if (!ContainsKey(known_type_strings_, type_string)) + continue; + base::File::Error error = base::File::FILE_ERROR_FAILED; + base::FilePath path = GetDirectoryForOriginAndType( + origin, type_string, false, &error); + if (error != base::File::FILE_OK) + continue; + scoped_ptr<SandboxDirectoryDatabase> db( + new SandboxDirectoryDatabase(path, env_override_)); + if (db->Init(SandboxDirectoryDatabase::FAIL_ON_CORRUPTION)) { + directories_[GetDirectoryDatabaseKey(origin, type_string)] = db.release(); + MarkUsed(); + // Don't populate more than one database, as it may rather hurt + // performance. + break; + } + } +} + +base::FilePath ObfuscatedFileUtil::GetDirectoryForURL( + const FileSystemURL& url, + bool create, + base::File::Error* error_code) { + return GetDirectoryForOriginAndType( + url.origin(), CallGetTypeStringForURL(url), create, error_code); +} + +std::string ObfuscatedFileUtil::CallGetTypeStringForURL( + const FileSystemURL& url) { + DCHECK(!get_type_string_for_url_.is_null()); + return get_type_string_for_url_.Run(url); +} + +base::File::Error ObfuscatedFileUtil::GetFileInfoInternal( + SandboxDirectoryDatabase* db, + FileSystemOperationContext* context, + const FileSystemURL& url, + FileId file_id, + FileInfo* local_info, + base::File::Info* file_info, + base::FilePath* platform_file_path) { + DCHECK(db); + DCHECK(context); + DCHECK(file_info); + DCHECK(platform_file_path); + + if (!db->GetFileInfo(file_id, local_info)) { + NOTREACHED(); + return base::File::FILE_ERROR_FAILED; + } + + if (local_info->is_directory()) { + file_info->size = 0; + file_info->is_directory = true; + file_info->is_symbolic_link = false; + file_info->last_modified = local_info->modification_time; + *platform_file_path = base::FilePath(); + // We don't fill in ctime or atime. + return base::File::FILE_OK; + } + if (local_info->data_path.empty()) + return base::File::FILE_ERROR_INVALID_OPERATION; + base::FilePath local_path = DataPathToLocalPath(url, local_info->data_path); + base::File::Error error = NativeFileUtil::GetFileInfo( + local_path, file_info); + // We should not follow symbolic links in sandboxed file system. + if (base::IsLink(local_path)) { + LOG(WARNING) << "Found a symbolic file."; + error = base::File::FILE_ERROR_NOT_FOUND; + } + if (error == base::File::FILE_OK) { + *platform_file_path = local_path; + } else if (error == base::File::FILE_ERROR_NOT_FOUND) { + LOG(WARNING) << "Lost a backing file."; + InvalidateUsageCache(context, url.origin(), url.type()); + if (!db->RemoveFileInfo(file_id)) + return base::File::FILE_ERROR_FAILED; + } + return error; +} + +base::File ObfuscatedFileUtil::CreateAndOpenFile( + FileSystemOperationContext* context, + const FileSystemURL& dest_url, + FileInfo* dest_file_info, int file_flags) { + SandboxDirectoryDatabase* db = GetDirectoryDatabase(dest_url, true); + + base::FilePath root, dest_local_path; + base::File::Error error = GenerateNewLocalPath(db, context, dest_url, &root, + &dest_local_path); + if (error != base::File::FILE_OK) + return base::File(error); + + if (base::PathExists(dest_local_path)) { + if (!base::DeleteFile(dest_local_path, true /* recursive */)) + return base::File(base::File::FILE_ERROR_FAILED); + LOG(WARNING) << "A stray file detected"; + InvalidateUsageCache(context, dest_url.origin(), dest_url.type()); + } + + base::File file = NativeFileUtil::CreateOrOpen(dest_local_path, file_flags); + if (!file.IsValid()) + return file.Pass(); + + if (!file.created()) { + file.Close(); + base::DeleteFile(dest_local_path, false /* recursive */); + return base::File(base::File::FILE_ERROR_FAILED); + } + + error = CommitCreateFile(root, dest_local_path, db, dest_file_info); + if (error != base::File::FILE_OK) { + file.Close(); + base::DeleteFile(dest_local_path, false /* recursive */); + return base::File(error); + } + + return file.Pass(); +} + +base::File::Error ObfuscatedFileUtil::CreateFile( + FileSystemOperationContext* context, + const base::FilePath& src_file_path, + const FileSystemURL& dest_url, + FileInfo* dest_file_info) { + SandboxDirectoryDatabase* db = GetDirectoryDatabase(dest_url, true); + + base::FilePath root, dest_local_path; + base::File::Error error = GenerateNewLocalPath(db, context, dest_url, &root, + &dest_local_path); + if (error != base::File::FILE_OK) + return error; + + bool created = false; + if (src_file_path.empty()) { + if (base::PathExists(dest_local_path)) { + if (!base::DeleteFile(dest_local_path, true /* recursive */)) + return base::File::FILE_ERROR_FAILED; + LOG(WARNING) << "A stray file detected"; + InvalidateUsageCache(context, dest_url.origin(), dest_url.type()); + } + + error = NativeFileUtil::EnsureFileExists(dest_local_path, &created); + } else { + error = NativeFileUtil::CopyOrMoveFile( + src_file_path, + dest_local_path, + FileSystemOperation::OPTION_NONE, + storage::NativeFileUtil::CopyOrMoveModeForDestination(dest_url, + true /* copy */)); + created = true; + } + if (error != base::File::FILE_OK) + return error; + if (!created) + return base::File::FILE_ERROR_FAILED; + + return CommitCreateFile(root, dest_local_path, db, dest_file_info); +} + +base::File::Error ObfuscatedFileUtil::CommitCreateFile( + const base::FilePath& root, + const base::FilePath& local_path, + SandboxDirectoryDatabase* db, + FileInfo* dest_file_info) { + // This removes the root, including the trailing slash, leaving a relative + // path. + dest_file_info->data_path = base::FilePath( + local_path.value().substr(root.value().length() + 1)); + + FileId file_id; + base::File::Error error = db->AddFileInfo(*dest_file_info, &file_id); + if (error != base::File::FILE_OK) + return error; + + TouchDirectory(db, dest_file_info->parent_id); + return base::File::FILE_OK; +} + +base::FilePath ObfuscatedFileUtil::DataPathToLocalPath( + const FileSystemURL& url, const base::FilePath& data_path) { + base::File::Error error = base::File::FILE_OK; + base::FilePath root = GetDirectoryForURL(url, false, &error); + if (error != base::File::FILE_OK) + return base::FilePath(); + return root.Append(data_path); +} + +std::string ObfuscatedFileUtil::GetDirectoryDatabaseKey( + const GURL& origin, const std::string& type_string) { + if (type_string.empty()) { + LOG(WARNING) << "Unknown filesystem type requested:" << type_string; + return std::string(); + } + // For isolated origin we just use a type string as a key. + return storage::GetIdentifierFromOrigin(origin) + type_string; +} + +// TODO(ericu): How to do the whole validation-without-creation thing? +// We may not have quota even to create the database. +// Ah, in that case don't even get here? +// Still doesn't answer the quota issue, though. +SandboxDirectoryDatabase* ObfuscatedFileUtil::GetDirectoryDatabase( + const FileSystemURL& url, bool create) { + std::string key = GetDirectoryDatabaseKey( + url.origin(), CallGetTypeStringForURL(url)); + if (key.empty()) + return NULL; + + DirectoryMap::iterator iter = directories_.find(key); + if (iter != directories_.end()) { + MarkUsed(); + return iter->second; + } + + base::File::Error error = base::File::FILE_OK; + base::FilePath path = GetDirectoryForURL(url, create, &error); + if (error != base::File::FILE_OK) { + LOG(WARNING) << "Failed to get origin+type directory: " + << url.DebugString() << " error:" << error; + return NULL; + } + MarkUsed(); + SandboxDirectoryDatabase* database = + new SandboxDirectoryDatabase(path, env_override_); + directories_[key] = database; + return database; +} + +base::FilePath ObfuscatedFileUtil::GetDirectoryForOrigin( + const GURL& origin, bool create, base::File::Error* error_code) { + if (!InitOriginDatabase(origin, create)) { + if (error_code) { + *error_code = create ? + base::File::FILE_ERROR_FAILED : + base::File::FILE_ERROR_NOT_FOUND; + } + return base::FilePath(); + } + base::FilePath directory_name; + std::string id = storage::GetIdentifierFromOrigin(origin); + + bool exists_in_db = origin_database_->HasOriginPath(id); + if (!exists_in_db && !create) { + if (error_code) + *error_code = base::File::FILE_ERROR_NOT_FOUND; + return base::FilePath(); + } + if (!origin_database_->GetPathForOrigin(id, &directory_name)) { + if (error_code) + *error_code = base::File::FILE_ERROR_FAILED; + return base::FilePath(); + } + + base::FilePath path = file_system_directory_.Append(directory_name); + bool exists_in_fs = base::DirectoryExists(path); + if (!exists_in_db && exists_in_fs) { + if (!base::DeleteFile(path, true)) { + if (error_code) + *error_code = base::File::FILE_ERROR_FAILED; + return base::FilePath(); + } + exists_in_fs = false; + } + + if (!exists_in_fs) { + if (!create || !base::CreateDirectory(path)) { + if (error_code) + *error_code = create ? + base::File::FILE_ERROR_FAILED : + base::File::FILE_ERROR_NOT_FOUND; + return base::FilePath(); + } + } + + if (error_code) + *error_code = base::File::FILE_OK; + + return path; +} + +void ObfuscatedFileUtil::InvalidateUsageCache( + FileSystemOperationContext* context, + const GURL& origin, + FileSystemType type) { + if (sandbox_delegate_) + sandbox_delegate_->InvalidateUsageCache(origin, type); +} + +void ObfuscatedFileUtil::MarkUsed() { + if (!timer_) + timer_.reset(new TimedTaskHelper(file_task_runner_.get())); + + if (timer_->IsRunning()) { + timer_->Reset(); + } else { + timer_->Start(FROM_HERE, + base::TimeDelta::FromSeconds(db_flush_delay_seconds_), + base::Bind(&ObfuscatedFileUtil::DropDatabases, + base::Unretained(this))); + } +} + +void ObfuscatedFileUtil::DropDatabases() { + origin_database_.reset(); + STLDeleteContainerPairSecondPointers( + directories_.begin(), directories_.end()); + directories_.clear(); + timer_.reset(); +} + +bool ObfuscatedFileUtil::InitOriginDatabase(const GURL& origin_hint, + bool create) { + if (origin_database_) + return true; + + if (!create && !base::DirectoryExists(file_system_directory_)) + return false; + if (!base::CreateDirectory(file_system_directory_)) { + LOG(WARNING) << "Failed to create FileSystem directory: " << + file_system_directory_.value(); + return false; + } + + SandboxPrioritizedOriginDatabase* prioritized_origin_database = + new SandboxPrioritizedOriginDatabase(file_system_directory_, + env_override_); + origin_database_.reset(prioritized_origin_database); + + if (origin_hint.is_empty() || !HasIsolatedStorage(origin_hint)) + return true; + + const std::string isolated_origin_string = + storage::GetIdentifierFromOrigin(origin_hint); + + // TODO(kinuko): Deprecate this after a few release cycles, e.g. around M33. + base::FilePath isolated_origin_dir = file_system_directory_.Append( + SandboxIsolatedOriginDatabase::kObsoleteOriginDirectory); + if (base::DirectoryExists(isolated_origin_dir) && + prioritized_origin_database->GetSandboxOriginDatabase()) { + SandboxIsolatedOriginDatabase::MigrateBackFromObsoleteOriginDatabase( + isolated_origin_string, + file_system_directory_, + prioritized_origin_database->GetSandboxOriginDatabase()); + } + + prioritized_origin_database->InitializePrimaryOrigin( + isolated_origin_string); + + return true; +} + +base::File::Error ObfuscatedFileUtil::GenerateNewLocalPath( + SandboxDirectoryDatabase* db, + FileSystemOperationContext* context, + const FileSystemURL& url, + base::FilePath* root, + base::FilePath* local_path) { + DCHECK(local_path); + int64 number; + if (!db || !db->GetNextInteger(&number)) + return base::File::FILE_ERROR_FAILED; + + base::File::Error error = base::File::FILE_OK; + *root = GetDirectoryForURL(url, false, &error); + if (error != base::File::FILE_OK) + return error; + + // We use the third- and fourth-to-last digits as the directory. + int64 directory_number = number % 10000 / 100; + base::FilePath new_local_path = root->AppendASCII( + base::StringPrintf("%02" PRId64, directory_number)); + + error = NativeFileUtil::CreateDirectory( + new_local_path, false /* exclusive */, false /* recursive */); + if (error != base::File::FILE_OK) + return error; + + *local_path = + new_local_path.AppendASCII(base::StringPrintf("%08" PRId64, number)); + return base::File::FILE_OK; +} + +base::File ObfuscatedFileUtil::CreateOrOpenInternal( + FileSystemOperationContext* context, + const FileSystemURL& url, int file_flags) { + DCHECK(!(file_flags & (base::File::FLAG_DELETE_ON_CLOSE | + base::File::FLAG_HIDDEN | base::File::FLAG_EXCLUSIVE_READ | + base::File::FLAG_EXCLUSIVE_WRITE))); + SandboxDirectoryDatabase* db = GetDirectoryDatabase(url, true); + if (!db) + return base::File(base::File::FILE_ERROR_FAILED); + FileId file_id; + if (!db->GetFileWithPath(url.path(), &file_id)) { + // The file doesn't exist. + if (!(file_flags & (base::File::FLAG_CREATE | + base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_OPEN_ALWAYS))) { + return base::File(base::File::FILE_ERROR_NOT_FOUND); + } + FileId parent_id; + if (!db->GetFileWithPath(VirtualPath::DirName(url.path()), &parent_id)) + return base::File(base::File::FILE_ERROR_NOT_FOUND); + FileInfo file_info; + InitFileInfo(&file_info, parent_id, + VirtualPath::BaseName(url.path()).value()); + + int64 growth = UsageForPath(file_info.name.size()); + if (!AllocateQuota(context, growth)) + return base::File(base::File::FILE_ERROR_NO_SPACE); + base::File file = CreateAndOpenFile(context, url, &file_info, file_flags); + if (file.IsValid()) { + UpdateUsage(context, url, growth); + context->change_observers()->Notify( + &FileChangeObserver::OnCreateFile, MakeTuple(url)); + } + return file.Pass(); + } + + if (file_flags & base::File::FLAG_CREATE) + return base::File(base::File::FILE_ERROR_EXISTS); + + base::File::Info platform_file_info; + base::FilePath local_path; + FileInfo file_info; + base::File::Error error = GetFileInfoInternal( + db, context, url, file_id, &file_info, &platform_file_info, &local_path); + if (error != base::File::FILE_OK) + return base::File(error); + if (file_info.is_directory()) + return base::File(base::File::FILE_ERROR_NOT_A_FILE); + + int64 delta = 0; + if (file_flags & (base::File::FLAG_CREATE_ALWAYS | + base::File::FLAG_OPEN_TRUNCATED)) { + // The file exists and we're truncating. + delta = -platform_file_info.size; + AllocateQuota(context, delta); + } + + base::File file = NativeFileUtil::CreateOrOpen(local_path, file_flags); + if (!file.IsValid()) { + error = file.error_details(); + if (error == base::File::FILE_ERROR_NOT_FOUND) { + // TODO(tzik): Also invalidate on-memory usage cache in UsageTracker. + // TODO(tzik): Delete database entry after ensuring the file lost. + InvalidateUsageCache(context, url.origin(), url.type()); + LOG(WARNING) << "Lost a backing file."; + return base::File(base::File::FILE_ERROR_FAILED); + } + return file.Pass(); + } + + // If truncating we need to update the usage. + if (delta) { + UpdateUsage(context, url, delta); + context->change_observers()->Notify( + &FileChangeObserver::OnModifyFile, MakeTuple(url)); + } + return file.Pass(); +} + +bool ObfuscatedFileUtil::HasIsolatedStorage(const GURL& origin) { + return special_storage_policy_.get() && + special_storage_policy_->HasIsolatedStorage(origin); +} + +} // namespace storage diff --git a/storage/browser/fileapi/obfuscated_file_util.h b/storage/browser/fileapi/obfuscated_file_util.h new file mode 100644 index 0000000..822a85d --- /dev/null +++ b/storage/browser/fileapi/obfuscated_file_util.h @@ -0,0 +1,359 @@ +// 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. + +#ifndef STORAGE_BROWSER_FILEAPI_OBFUSCATED_FILE_UTIL_H_ +#define STORAGE_BROWSER_FILEAPI_OBFUSCATED_FILE_UTIL_H_ + +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "base/callback_forward.h" +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/files/file_util_proxy.h" +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" +#include "storage/browser/fileapi/file_system_file_util.h" +#include "storage/browser/fileapi/file_system_url.h" +#include "storage/browser/fileapi/sandbox_directory_database.h" +#include "storage/browser/fileapi/sandbox_file_system_backend_delegate.h" +#include "storage/browser/storage_browser_export.h" +#include "storage/common/blob/shareable_file_reference.h" +#include "storage/common/fileapi/file_system_types.h" + +namespace base { +class SequencedTaskRunner; +class TimeTicks; +} + +namespace content { +class ObfuscatedFileUtilTest; +class QuotaBackendImplTest; +} + +namespace storage { +class SpecialStoragePolicy; +} + +class GURL; + +namespace storage { + +class FileSystemOperationContext; +class SandboxOriginDatabaseInterface; +class TimedTaskHelper; + +// This file util stores directory information in LevelDB to obfuscate +// and to neutralize virtual file paths given by arbitrary apps. +// Files are stored with two-level isolation: per-origin and per-type. +// The isolation is done by storing data in separate directory partitions. +// For example, a file in Temporary file system for origin 'www.example.com' +// is stored in a different partition for a file in Persistent file system +// for the same origin, or for Temporary file system for another origin. +// +// * Per-origin directory name information is stored in a separate LevelDB, +// which is maintained by SandboxOriginDatabase. +// * Per-type directory name information is given by +// GetTypeStringForURLCallback that is given in CTOR. +// We use a small static mapping (e.g. 't' for Temporary type) for +// regular sandbox filesystems. +// +// The overall implementation philosophy of this class is that partial failures +// should leave us with an intact database; we'd prefer to leak the occasional +// backing file than have a database entry whose backing file is missing. When +// doing FSCK operations, if you find a loose backing file with no reference, +// you may safely delete it. +// +// This class must be deleted on the FILE thread, because that's where +// DropDatabases needs to be called. +class STORAGE_EXPORT_PRIVATE ObfuscatedFileUtil + : public FileSystemFileUtil { + public: + // Origin enumerator interface. + // An instance of this interface is assumed to be called on the file thread. + class AbstractOriginEnumerator { + public: + virtual ~AbstractOriginEnumerator() {} + + // Returns the next origin. Returns empty if there are no more origins. + virtual GURL Next() = 0; + + // Returns the current origin's information. + // |type_string| must be ascii string. + virtual bool HasTypeDirectory(const std::string& type_string) const = 0; + }; + + typedef base::Callback<std::string(const FileSystemURL&)> + GetTypeStringForURLCallback; + + // |get_type_string_for_url| is user-defined callback that should return + // a type string for the given FileSystemURL. The type string is used + // to provide per-type isolation in the sandboxed filesystem directory. + // Note that this method is called on file_task_runner. + // + // |known_type_strings| are known type string names that this file system + // should care about. + // This info is used to determine whether we could delete the entire + // origin directory or not in DeleteDirectoryForOriginAndType. If no directory + // for any known type exists the origin directory may get deleted when + // one origin/type pair is deleted. + // + ObfuscatedFileUtil(storage::SpecialStoragePolicy* special_storage_policy, + const base::FilePath& file_system_directory, + leveldb::Env* env_override, + base::SequencedTaskRunner* file_task_runner, + const GetTypeStringForURLCallback& get_type_string_for_url, + const std::set<std::string>& known_type_strings, + SandboxFileSystemBackendDelegate* sandbox_delegate); + virtual ~ObfuscatedFileUtil(); + + // FileSystemFileUtil overrides. + virtual base::File CreateOrOpen( + FileSystemOperationContext* context, + const FileSystemURL& url, + int file_flags) OVERRIDE; + virtual base::File::Error EnsureFileExists( + FileSystemOperationContext* context, + const FileSystemURL& url, bool* created) OVERRIDE; + virtual base::File::Error CreateDirectory( + FileSystemOperationContext* context, + const FileSystemURL& url, + bool exclusive, + bool recursive) OVERRIDE; + virtual base::File::Error GetFileInfo( + FileSystemOperationContext* context, + const FileSystemURL& url, + base::File::Info* file_info, + base::FilePath* platform_file) OVERRIDE; + virtual scoped_ptr<AbstractFileEnumerator> CreateFileEnumerator( + FileSystemOperationContext* context, + const FileSystemURL& root_url) OVERRIDE; + virtual base::File::Error GetLocalFilePath( + FileSystemOperationContext* context, + const FileSystemURL& file_system_url, + base::FilePath* local_path) OVERRIDE; + virtual base::File::Error Touch( + FileSystemOperationContext* context, + const FileSystemURL& url, + const base::Time& last_access_time, + const base::Time& last_modified_time) OVERRIDE; + virtual base::File::Error Truncate( + FileSystemOperationContext* context, + const FileSystemURL& url, + int64 length) OVERRIDE; + virtual base::File::Error CopyOrMoveFile( + FileSystemOperationContext* context, + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOption option, + bool copy) OVERRIDE; + virtual base::File::Error CopyInForeignFile( + FileSystemOperationContext* context, + const base::FilePath& src_file_path, + const FileSystemURL& dest_url) OVERRIDE; + virtual base::File::Error DeleteFile( + FileSystemOperationContext* context, + const FileSystemURL& url) OVERRIDE; + virtual base::File::Error DeleteDirectory( + FileSystemOperationContext* context, + const FileSystemURL& url) OVERRIDE; + virtual storage::ScopedFile CreateSnapshotFile( + FileSystemOperationContext* context, + const FileSystemURL& url, + base::File::Error* error, + base::File::Info* file_info, + base::FilePath* platform_path) OVERRIDE; + + // Same as the other CreateFileEnumerator, but with recursive support. + scoped_ptr<AbstractFileEnumerator> CreateFileEnumerator( + FileSystemOperationContext* context, + const FileSystemURL& root_url, + bool recursive); + + // Returns true if the directory |url| is empty. + bool IsDirectoryEmpty( + FileSystemOperationContext* context, + const FileSystemURL& url); + + // Gets the topmost directory specific to this origin and type. This will + // contain both the directory database's files and all the backing file + // subdirectories. + // Returns the topmost origin directory if |type_string| is empty. + // Returns an empty path if the directory is undefined. + // If the directory is defined, it will be returned, even if + // there is a file system error (e.g. the directory doesn't exist on disk and + // |create| is false). Callers should always check |error_code| to make sure + // the returned path is usable. + base::FilePath GetDirectoryForOriginAndType( + const GURL& origin, + const std::string& type_string, + bool create, + base::File::Error* error_code); + + // Deletes the topmost directory specific to this origin and type. This will + // delete its directory database. + // Deletes the topmost origin directory if |type_string| is empty. + bool DeleteDirectoryForOriginAndType( + const GURL& origin, + const std::string& type_string); + + // This method and all methods of its returned class must be called only on + // the FILE thread. The caller is responsible for deleting the returned + // object. + AbstractOriginEnumerator* CreateOriginEnumerator(); + + // Deletes a directory database from the database list in the ObfuscatedFSFU + // and destroys the database on the disk. + bool DestroyDirectoryDatabase(const GURL& origin, + const std::string& type_string); + + // Computes a cost for storing a given file in the obfuscated FSFU. + // As the cost of a file is independent of the cost of its parent directories, + // this ignores all but the BaseName of the supplied path. In order to + // compute the cost of adding a multi-segment directory recursively, call this + // on each path segment and add the results. + static int64 ComputeFilePathCost(const base::FilePath& path); + + // Tries to prepopulate directory database for the given type strings. + // This tries from the first one in the given type_strings and stops + // once it succeeds to do so for one database (i.e. it prepopulates + // at most one database). + void MaybePrepopulateDatabase( + const std::vector<std::string>& type_strings_to_prepopulate); + + private: + typedef SandboxDirectoryDatabase::FileId FileId; + typedef SandboxDirectoryDatabase::FileInfo FileInfo; + + friend class ObfuscatedFileEnumerator; + friend class content::ObfuscatedFileUtilTest; + friend class content::QuotaBackendImplTest; + + // Helper method to create an obfuscated file util for regular + // (temporary, persistent) file systems. Used only for testing. + // Note: this is implemented in sandbox_file_system_backend_delegate.cc. + static ObfuscatedFileUtil* CreateForTesting( + storage::SpecialStoragePolicy* special_storage_policy, + const base::FilePath& file_system_directory, + leveldb::Env* env_override, + base::SequencedTaskRunner* file_task_runner); + + base::FilePath GetDirectoryForURL( + const FileSystemURL& url, + bool create, + base::File::Error* error_code); + + // This just calls get_type_string_for_url_ callback that is given in ctor. + std::string CallGetTypeStringForURL(const FileSystemURL& url); + + base::File::Error GetFileInfoInternal( + SandboxDirectoryDatabase* db, + FileSystemOperationContext* context, + const FileSystemURL& url, + FileId file_id, + FileInfo* local_info, + base::File::Info* file_info, + base::FilePath* platform_file_path); + + // Creates a new file, both the underlying backing file and the entry in the + // database. |dest_file_info| is an in-out parameter. Supply the name and + // parent_id; data_path is ignored. On success, data_path will + // always be set to the relative path [from the root of the type-specific + // filesystem directory] of a NEW backing file. Returns the new file. + base::File CreateAndOpenFile( + FileSystemOperationContext* context, + const FileSystemURL& dest_url, + FileInfo* dest_file_info, + int file_flags); + + // The same as CreateAndOpenFile except that a file is not returned and if a + // path is provided in |source_path|, it will be used as a source from which + // to COPY data. + base::File::Error CreateFile( + FileSystemOperationContext* context, + const base::FilePath& source_file_path, + const FileSystemURL& dest_url, + FileInfo* dest_file_info); + + // Updates |db| and |dest_file_info| at the end of creating a new file. + base::File::Error CommitCreateFile( + const base::FilePath& root, + const base::FilePath& local_path, + SandboxDirectoryDatabase* db, + FileInfo* dest_file_info); + + // This converts from a relative path [as is stored in the FileInfo.data_path + // field] to an absolute platform path that can be given to the native + // filesystem. + base::FilePath DataPathToLocalPath( + const FileSystemURL& url, + const base::FilePath& data_file_path); + + std::string GetDirectoryDatabaseKey(const GURL& origin, + const std::string& type_string); + + // This returns NULL if |create| flag is false and a filesystem does not + // exist for the given |url|. + // For read operations |create| should be false. + SandboxDirectoryDatabase* GetDirectoryDatabase(const FileSystemURL& url, + bool create); + + // Gets the topmost directory specific to this origin. This will + // contain both the filesystem type subdirectories. + base::FilePath GetDirectoryForOrigin(const GURL& origin, + bool create, + base::File::Error* error_code); + + void InvalidateUsageCache(FileSystemOperationContext* context, + const GURL& origin, + FileSystemType type); + + void MarkUsed(); + void DropDatabases(); + + // Initializes the origin database. |origin_hint| may be used as a hint + // for initializing database if it's not empty. + bool InitOriginDatabase(const GURL& origin_hint, bool create); + + base::File::Error GenerateNewLocalPath( + SandboxDirectoryDatabase* db, + FileSystemOperationContext* context, + const FileSystemURL& url, + base::FilePath* root, + base::FilePath* local_path); + + base::File CreateOrOpenInternal( + FileSystemOperationContext* context, + const FileSystemURL& url, + int file_flags); + + bool HasIsolatedStorage(const GURL& origin); + + typedef std::map<std::string, SandboxDirectoryDatabase*> DirectoryMap; + DirectoryMap directories_; + scoped_ptr<SandboxOriginDatabaseInterface> origin_database_; + scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy_; + base::FilePath file_system_directory_; + leveldb::Env* env_override_; + + // Used to delete database after a certain period of inactivity. + int64 db_flush_delay_seconds_; + + scoped_refptr<base::SequencedTaskRunner> file_task_runner_; + scoped_ptr<TimedTaskHelper> timer_; + + GetTypeStringForURLCallback get_type_string_for_url_; + std::set<std::string> known_type_strings_; + + // Not owned. + SandboxFileSystemBackendDelegate* sandbox_delegate_; + + DISALLOW_COPY_AND_ASSIGN(ObfuscatedFileUtil); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_OBFUSCATED_FILE_UTIL_H_ diff --git a/storage/browser/fileapi/open_file_system_mode.h b/storage/browser/fileapi/open_file_system_mode.h new file mode 100644 index 0000000..03943fd --- /dev/null +++ b/storage/browser/fileapi/open_file_system_mode.h @@ -0,0 +1,22 @@ +// Copyright 2013 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. + +#ifndef STORAGE_BROWSER_FILEAPI_OPEN_FILE_SYSTEM_MODE_H_ +#define STORAGE_BROWSER_FILEAPI_OPEN_FILE_SYSTEM_MODE_H_ + +namespace storage { + +// Determines the behavior on OpenFileSystem when a specified +// FileSystem does not exist. +// Specifying CREATE_IF_NONEXISTENT may make actual modification on +// disk (e.g. creating a root directory, setting up a metadata database etc) +// if the filesystem hasn't been initialized. +enum OpenFileSystemMode { + OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT, +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_OPEN_FILE_SYSTEM_MODE_H_ diff --git a/storage/browser/fileapi/plugin_private_file_system_backend.cc b/storage/browser/fileapi/plugin_private_file_system_backend.cc new file mode 100644 index 0000000..6906986 --- /dev/null +++ b/storage/browser/fileapi/plugin_private_file_system_backend.cc @@ -0,0 +1,311 @@ +// Copyright 2013 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 "storage/browser/fileapi/plugin_private_file_system_backend.h" + +#include <map> + +#include "base/stl_util.h" +#include "base/synchronization/lock.h" +#include "base/task_runner_util.h" +#include "net/base/net_util.h" +#include "storage/browser/blob/file_stream_reader.h" +#include "storage/browser/fileapi/async_file_util_adapter.h" +#include "storage/browser/fileapi/file_stream_writer.h" +#include "storage/browser/fileapi/file_system_context.h" +#include "storage/browser/fileapi/file_system_operation.h" +#include "storage/browser/fileapi/file_system_operation_context.h" +#include "storage/browser/fileapi/file_system_options.h" +#include "storage/browser/fileapi/isolated_context.h" +#include "storage/browser/fileapi/obfuscated_file_util.h" +#include "storage/browser/fileapi/quota/quota_reservation.h" +#include "storage/common/fileapi/file_system_util.h" + +namespace storage { + +class PluginPrivateFileSystemBackend::FileSystemIDToPluginMap { + public: + explicit FileSystemIDToPluginMap(base::SequencedTaskRunner* task_runner) + : task_runner_(task_runner) {} + ~FileSystemIDToPluginMap() {} + + std::string GetPluginIDForURL(const FileSystemURL& url) { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + Map::iterator found = map_.find(url.filesystem_id()); + if (url.type() != kFileSystemTypePluginPrivate || found == map_.end()) { + NOTREACHED() << "Unsupported url is given: " << url.DebugString(); + return std::string(); + } + return found->second; + } + + void RegisterFileSystem(const std::string& filesystem_id, + const std::string& plugin_id) { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + DCHECK(!filesystem_id.empty()); + DCHECK(!ContainsKey(map_, filesystem_id)) << filesystem_id; + map_[filesystem_id] = plugin_id; + } + + void RemoveFileSystem(const std::string& filesystem_id) { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + map_.erase(filesystem_id); + } + + private: + typedef std::map<std::string, std::string> Map; + scoped_refptr<base::SequencedTaskRunner> task_runner_; + Map map_; +}; + +namespace { + +const base::FilePath::CharType* kFileSystemDirectory = + SandboxFileSystemBackendDelegate::kFileSystemDirectory; +const base::FilePath::CharType* kPluginPrivateDirectory = + FILE_PATH_LITERAL("Plugins"); + +base::File::Error OpenFileSystemOnFileTaskRunner( + ObfuscatedFileUtil* file_util, + PluginPrivateFileSystemBackend::FileSystemIDToPluginMap* plugin_map, + const GURL& origin_url, + const std::string& filesystem_id, + const std::string& plugin_id, + OpenFileSystemMode mode) { + base::File::Error error = base::File::FILE_ERROR_FAILED; + const bool create = (mode == OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT); + file_util->GetDirectoryForOriginAndType( + origin_url, plugin_id, create, &error); + if (error == base::File::FILE_OK) + plugin_map->RegisterFileSystem(filesystem_id, plugin_id); + return error; +} + +} // namespace + +PluginPrivateFileSystemBackend::PluginPrivateFileSystemBackend( + base::SequencedTaskRunner* file_task_runner, + const base::FilePath& profile_path, + storage::SpecialStoragePolicy* special_storage_policy, + const FileSystemOptions& file_system_options) + : file_task_runner_(file_task_runner), + file_system_options_(file_system_options), + base_path_(profile_path.Append(kFileSystemDirectory) + .Append(kPluginPrivateDirectory)), + plugin_map_(new FileSystemIDToPluginMap(file_task_runner)), + weak_factory_(this) { + file_util_.reset( + new AsyncFileUtilAdapter(new ObfuscatedFileUtil( + special_storage_policy, + base_path_, file_system_options.env_override(), + file_task_runner, + base::Bind(&FileSystemIDToPluginMap::GetPluginIDForURL, + base::Owned(plugin_map_)), + std::set<std::string>(), + NULL))); +} + +PluginPrivateFileSystemBackend::~PluginPrivateFileSystemBackend() { + if (!file_task_runner_->RunsTasksOnCurrentThread()) { + AsyncFileUtil* file_util = file_util_.release(); + if (!file_task_runner_->DeleteSoon(FROM_HERE, file_util)) + delete file_util; + } +} + +void PluginPrivateFileSystemBackend::OpenPrivateFileSystem( + const GURL& origin_url, + FileSystemType type, + const std::string& filesystem_id, + const std::string& plugin_id, + OpenFileSystemMode mode, + const StatusCallback& callback) { + if (!CanHandleType(type) || file_system_options_.is_incognito()) { + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, base::Bind(callback, base::File::FILE_ERROR_SECURITY)); + return; + } + + PostTaskAndReplyWithResult( + file_task_runner_.get(), + FROM_HERE, + base::Bind(&OpenFileSystemOnFileTaskRunner, + obfuscated_file_util(), plugin_map_, + origin_url, filesystem_id, plugin_id, mode), + callback); +} + +bool PluginPrivateFileSystemBackend::CanHandleType(FileSystemType type) const { + return type == kFileSystemTypePluginPrivate; +} + +void PluginPrivateFileSystemBackend::Initialize(FileSystemContext* context) { +} + +void PluginPrivateFileSystemBackend::ResolveURL( + const FileSystemURL& url, + OpenFileSystemMode mode, + const OpenFileSystemCallback& callback) { + // We never allow opening a new plugin-private filesystem via usual + // ResolveURL. + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(callback, GURL(), std::string(), + base::File::FILE_ERROR_SECURITY)); +} + +AsyncFileUtil* +PluginPrivateFileSystemBackend::GetAsyncFileUtil(FileSystemType type) { + return file_util_.get(); +} + +WatcherManager* PluginPrivateFileSystemBackend::GetWatcherManager( + FileSystemType type) { + return NULL; +} + +CopyOrMoveFileValidatorFactory* +PluginPrivateFileSystemBackend::GetCopyOrMoveFileValidatorFactory( + FileSystemType type, + base::File::Error* error_code) { + DCHECK(error_code); + *error_code = base::File::FILE_OK; + return NULL; +} + +FileSystemOperation* PluginPrivateFileSystemBackend::CreateFileSystemOperation( + const FileSystemURL& url, + FileSystemContext* context, + base::File::Error* error_code) const { + scoped_ptr<FileSystemOperationContext> operation_context( + new FileSystemOperationContext(context)); + return FileSystemOperation::Create(url, context, operation_context.Pass()); +} + +bool PluginPrivateFileSystemBackend::SupportsStreaming( + const storage::FileSystemURL& url) const { + return false; +} + +bool PluginPrivateFileSystemBackend::HasInplaceCopyImplementation( + storage::FileSystemType type) const { + return false; +} + +scoped_ptr<storage::FileStreamReader> +PluginPrivateFileSystemBackend::CreateFileStreamReader( + const FileSystemURL& url, + int64 offset, + const base::Time& expected_modification_time, + FileSystemContext* context) const { + return scoped_ptr<storage::FileStreamReader>(); +} + +scoped_ptr<FileStreamWriter> +PluginPrivateFileSystemBackend::CreateFileStreamWriter( + const FileSystemURL& url, + int64 offset, + FileSystemContext* context) const { + return scoped_ptr<FileStreamWriter>(); +} + +FileSystemQuotaUtil* PluginPrivateFileSystemBackend::GetQuotaUtil() { + return this; +} + +base::File::Error +PluginPrivateFileSystemBackend::DeleteOriginDataOnFileTaskRunner( + FileSystemContext* context, + storage::QuotaManagerProxy* proxy, + const GURL& origin_url, + FileSystemType type) { + if (!CanHandleType(type)) + return base::File::FILE_ERROR_SECURITY; + bool result = obfuscated_file_util()->DeleteDirectoryForOriginAndType( + origin_url, std::string()); + if (result) + return base::File::FILE_OK; + return base::File::FILE_ERROR_FAILED; +} + +void PluginPrivateFileSystemBackend::GetOriginsForTypeOnFileTaskRunner( + FileSystemType type, + std::set<GURL>* origins) { + if (!CanHandleType(type)) + return; + scoped_ptr<ObfuscatedFileUtil::AbstractOriginEnumerator> enumerator( + obfuscated_file_util()->CreateOriginEnumerator()); + GURL origin; + while (!(origin = enumerator->Next()).is_empty()) + origins->insert(origin); +} + +void PluginPrivateFileSystemBackend::GetOriginsForHostOnFileTaskRunner( + FileSystemType type, + const std::string& host, + std::set<GURL>* origins) { + if (!CanHandleType(type)) + return; + scoped_ptr<ObfuscatedFileUtil::AbstractOriginEnumerator> enumerator( + obfuscated_file_util()->CreateOriginEnumerator()); + GURL origin; + while (!(origin = enumerator->Next()).is_empty()) { + if (host == net::GetHostOrSpecFromURL(origin)) + origins->insert(origin); + } +} + +int64 PluginPrivateFileSystemBackend::GetOriginUsageOnFileTaskRunner( + FileSystemContext* context, + const GURL& origin_url, + FileSystemType type) { + // We don't track usage on this filesystem. + return 0; +} + +scoped_refptr<QuotaReservation> +PluginPrivateFileSystemBackend::CreateQuotaReservationOnFileTaskRunner( + const GURL& origin_url, + FileSystemType type) { + // We don't track usage on this filesystem. + NOTREACHED(); + return scoped_refptr<QuotaReservation>(); +} + +void PluginPrivateFileSystemBackend::AddFileUpdateObserver( + FileSystemType type, + FileUpdateObserver* observer, + base::SequencedTaskRunner* task_runner) {} + +void PluginPrivateFileSystemBackend::AddFileChangeObserver( + FileSystemType type, + FileChangeObserver* observer, + base::SequencedTaskRunner* task_runner) {} + +void PluginPrivateFileSystemBackend::AddFileAccessObserver( + FileSystemType type, + FileAccessObserver* observer, + base::SequencedTaskRunner* task_runner) {} + +const UpdateObserverList* PluginPrivateFileSystemBackend::GetUpdateObservers( + FileSystemType type) const { + return NULL; +} + +const ChangeObserverList* PluginPrivateFileSystemBackend::GetChangeObservers( + FileSystemType type) const { + return NULL; +} + +const AccessObserverList* PluginPrivateFileSystemBackend::GetAccessObservers( + FileSystemType type) const { + return NULL; +} + +ObfuscatedFileUtil* PluginPrivateFileSystemBackend::obfuscated_file_util() { + return static_cast<ObfuscatedFileUtil*>( + static_cast<AsyncFileUtilAdapter*>(file_util_.get())->sync_file_util()); +} + +} // namespace storage diff --git a/storage/browser/fileapi/plugin_private_file_system_backend.h b/storage/browser/fileapi/plugin_private_file_system_backend.h new file mode 100644 index 0000000..a49e07d --- /dev/null +++ b/storage/browser/fileapi/plugin_private_file_system_backend.h @@ -0,0 +1,149 @@ +// Copyright 2013 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. + +#ifndef STORAGE_BROWSER_FILEAPI_PLUGIN_PRIVATE_FILE_SYSTEM_BACKEND_H_ +#define STORAGE_BROWSER_FILEAPI_PLUGIN_PRIVATE_FILE_SYSTEM_BACKEND_H_ + +#include <set> +#include <string> + +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "storage/browser/fileapi/file_system_backend.h" +#include "storage/browser/fileapi/file_system_options.h" +#include "storage/browser/fileapi/file_system_quota_util.h" + +namespace base { +class SequencedTaskRunner; +} + +namespace content { +class PluginPrivateFileSystemBackendTest; +} + +namespace storage { +class SpecialStoragePolicy; +} + +namespace storage { + +class ObfuscatedFileUtil; +class WatcherManager; + +class STORAGE_EXPORT PluginPrivateFileSystemBackend + : public FileSystemBackend, + public FileSystemQuotaUtil { + public: + class FileSystemIDToPluginMap; + typedef base::Callback<void(base::File::Error result)> StatusCallback; + + PluginPrivateFileSystemBackend( + base::SequencedTaskRunner* file_task_runner, + const base::FilePath& profile_path, + storage::SpecialStoragePolicy* special_storage_policy, + const FileSystemOptions& file_system_options); + virtual ~PluginPrivateFileSystemBackend(); + + // This must be used to open 'private' filesystem instead of regular + // OpenFileSystem. + // |plugin_id| must be an identifier string for per-plugin + // isolation, e.g. name, MIME type etc. + // NOTE: |plugin_id| must be sanitized ASCII string that doesn't + // include *any* dangerous character like '/'. + void OpenPrivateFileSystem( + const GURL& origin_url, + FileSystemType type, + const std::string& filesystem_id, + const std::string& plugin_id, + OpenFileSystemMode mode, + const StatusCallback& callback); + + // FileSystemBackend overrides. + virtual bool CanHandleType(FileSystemType type) const OVERRIDE; + virtual void Initialize(FileSystemContext* context) OVERRIDE; + virtual void ResolveURL(const FileSystemURL& url, + OpenFileSystemMode mode, + const OpenFileSystemCallback& callback) OVERRIDE; + virtual AsyncFileUtil* GetAsyncFileUtil(FileSystemType type) OVERRIDE; + virtual WatcherManager* GetWatcherManager(FileSystemType type) OVERRIDE; + virtual CopyOrMoveFileValidatorFactory* GetCopyOrMoveFileValidatorFactory( + FileSystemType type, + base::File::Error* error_code) OVERRIDE; + virtual FileSystemOperation* CreateFileSystemOperation( + const FileSystemURL& url, + FileSystemContext* context, + base::File::Error* error_code) const OVERRIDE; + virtual bool SupportsStreaming(const FileSystemURL& url) const OVERRIDE; + virtual bool HasInplaceCopyImplementation( + storage::FileSystemType type) const OVERRIDE; + virtual scoped_ptr<storage::FileStreamReader> CreateFileStreamReader( + const FileSystemURL& url, + int64 offset, + const base::Time& expected_modification_time, + FileSystemContext* context) const OVERRIDE; + virtual scoped_ptr<FileStreamWriter> CreateFileStreamWriter( + const FileSystemURL& url, + int64 offset, + FileSystemContext* context) const OVERRIDE; + virtual FileSystemQuotaUtil* GetQuotaUtil() OVERRIDE; + + // FileSystemQuotaUtil overrides. + virtual base::File::Error DeleteOriginDataOnFileTaskRunner( + FileSystemContext* context, + storage::QuotaManagerProxy* proxy, + const GURL& origin_url, + FileSystemType type) OVERRIDE; + virtual void GetOriginsForTypeOnFileTaskRunner( + FileSystemType type, + std::set<GURL>* origins) OVERRIDE; + virtual void GetOriginsForHostOnFileTaskRunner( + FileSystemType type, + const std::string& host, + std::set<GURL>* origins) OVERRIDE; + virtual int64 GetOriginUsageOnFileTaskRunner( + FileSystemContext* context, + const GURL& origin_url, + FileSystemType type) OVERRIDE; + virtual scoped_refptr<QuotaReservation> + CreateQuotaReservationOnFileTaskRunner( + const GURL& origin_url, + FileSystemType type) OVERRIDE; + virtual void AddFileUpdateObserver( + FileSystemType type, + FileUpdateObserver* observer, + base::SequencedTaskRunner* task_runner) OVERRIDE; + virtual void AddFileChangeObserver( + FileSystemType type, + FileChangeObserver* observer, + base::SequencedTaskRunner* task_runner) OVERRIDE; + virtual void AddFileAccessObserver( + FileSystemType type, + FileAccessObserver* observer, + base::SequencedTaskRunner* task_runner) OVERRIDE; + virtual const UpdateObserverList* GetUpdateObservers( + FileSystemType type) const OVERRIDE; + virtual const ChangeObserverList* GetChangeObservers( + FileSystemType type) const OVERRIDE; + virtual const AccessObserverList* GetAccessObservers( + FileSystemType type) const OVERRIDE; + + private: + friend class content::PluginPrivateFileSystemBackendTest; + + ObfuscatedFileUtil* obfuscated_file_util(); + const base::FilePath& base_path() const { return base_path_; } + + scoped_refptr<base::SequencedTaskRunner> file_task_runner_; + const FileSystemOptions file_system_options_; + const base::FilePath base_path_; + scoped_ptr<AsyncFileUtil> file_util_; + FileSystemIDToPluginMap* plugin_map_; // Owned by file_util_. + base::WeakPtrFactory<PluginPrivateFileSystemBackend> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(PluginPrivateFileSystemBackend); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_PLUGIN_PRIVATE_FILE_SYSTEM_BACKEND_H_ diff --git a/storage/browser/fileapi/quota/open_file_handle.cc b/storage/browser/fileapi/quota/open_file_handle.cc new file mode 100644 index 0000000..8842438 --- /dev/null +++ b/storage/browser/fileapi/quota/open_file_handle.cc @@ -0,0 +1,55 @@ +// Copyright 2013 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 "storage/browser/fileapi/quota/open_file_handle.h" + +#include "storage/browser/fileapi/quota/open_file_handle_context.h" +#include "storage/browser/fileapi/quota/quota_reservation.h" + +namespace storage { + +OpenFileHandle::~OpenFileHandle() { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); +} + +void OpenFileHandle::UpdateMaxWrittenOffset(int64 offset) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + + int64 growth = context_->UpdateMaxWrittenOffset(offset); + if (growth > 0) + reservation_->ConsumeReservation(growth); +} + +void OpenFileHandle::AddAppendModeWriteAmount(int64 amount) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + if (amount <= 0) + return; + + context_->AddAppendModeWriteAmount(amount); + reservation_->ConsumeReservation(amount); +} + +int64 OpenFileHandle::GetEstimatedFileSize() const { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + return context_->GetEstimatedFileSize(); +} + +int64 OpenFileHandle::GetMaxWrittenOffset() const { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + return context_->GetMaxWrittenOffset(); +} + +const base::FilePath& OpenFileHandle::platform_path() const { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + return context_->platform_path(); +} + +OpenFileHandle::OpenFileHandle(QuotaReservation* reservation, + OpenFileHandleContext* context) + : reservation_(reservation), + context_(context) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); +} + +} // namespace storage diff --git a/storage/browser/fileapi/quota/open_file_handle.h b/storage/browser/fileapi/quota/open_file_handle.h new file mode 100644 index 0000000..8954b1d --- /dev/null +++ b/storage/browser/fileapi/quota/open_file_handle.h @@ -0,0 +1,70 @@ +// Copyright 2013 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. + +#ifndef STORAGE_BROWSER_FILEAPI_QUOTA_OPEN_FILE_HANDLE_H_ +#define STORAGE_BROWSER_FILEAPI_QUOTA_OPEN_FILE_HANDLE_H_ + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "storage/browser/storage_browser_export.h" + +namespace base { +class FilePath; +} + +namespace storage { + +class QuotaReservation; +class OpenFileHandleContext; +class QuotaReservationBuffer; + +// Represents an open file like a file descriptor. +// This should be alive while a consumer keeps a file opened and should be +// deleted when the plugin closes the file. +class STORAGE_EXPORT OpenFileHandle { + public: + ~OpenFileHandle(); + + // Updates cached file size and consumes quota for that. + // Both this and AddAppendModeWriteAmount should be called for each modified + // file before calling QuotaReservation::RefreshQuota and before closing the + // file. + void UpdateMaxWrittenOffset(int64 offset); + + // Notifies that |amount| of data is written to the file in append mode, and + // consumes quota for that. + // Both this and UpdateMaxWrittenOffset should be called for each modified + // file before calling QuotaReservation::RefreshQuota and before closing the + // file. + void AddAppendModeWriteAmount(int64 amount); + + // Returns the estimated file size for the quota consumption calculation. + // The client must consume its reserved quota when it writes data to the file + // beyond the estimated file size. + // The estimated file size is greater than or equal to actual file size after + // all clients report their file usage, and is monotonically increasing over + // OpenFileHandle object life cycle, so that client may cache the value. + int64 GetEstimatedFileSize() const; + + int64 GetMaxWrittenOffset() const; + const base::FilePath& platform_path() const; + + private: + friend class QuotaReservationBuffer; + + OpenFileHandle(QuotaReservation* reservation, + OpenFileHandleContext* context); + + scoped_refptr<QuotaReservation> reservation_; + scoped_refptr<OpenFileHandleContext> context_; + + base::SequenceChecker sequence_checker_; + + DISALLOW_COPY_AND_ASSIGN(OpenFileHandle); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_QUOTA_OPEN_FILE_HANDLE_H_ diff --git a/storage/browser/fileapi/quota/open_file_handle_context.cc b/storage/browser/fileapi/quota/open_file_handle_context.cc new file mode 100644 index 0000000..816a777 --- /dev/null +++ b/storage/browser/fileapi/quota/open_file_handle_context.cc @@ -0,0 +1,72 @@ +// Copyright 2013 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 "storage/browser/fileapi/quota/open_file_handle_context.h" + +#include "base/file_util.h" +#include "storage/browser/fileapi/quota/quota_reservation_buffer.h" + +namespace storage { + +OpenFileHandleContext::OpenFileHandleContext( + const base::FilePath& platform_path, + QuotaReservationBuffer* reservation_buffer) + : initial_file_size_(0), + maximum_written_offset_(0), + append_mode_write_amount_(0), + platform_path_(platform_path), + reservation_buffer_(reservation_buffer) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + + base::GetFileSize(platform_path, &initial_file_size_); + maximum_written_offset_ = initial_file_size_; +} + +int64 OpenFileHandleContext::UpdateMaxWrittenOffset(int64 offset) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + if (offset <= maximum_written_offset_) + return 0; + + int64 growth = offset - maximum_written_offset_; + maximum_written_offset_ = offset; + return growth; +} + +void OpenFileHandleContext::AddAppendModeWriteAmount(int64 amount) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + append_mode_write_amount_ += amount; +} + +int64 OpenFileHandleContext::GetEstimatedFileSize() const { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + return maximum_written_offset_ + append_mode_write_amount_; +} + +int64 OpenFileHandleContext::GetMaxWrittenOffset() const { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + return maximum_written_offset_; +} + +OpenFileHandleContext::~OpenFileHandleContext() { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + + // TODO(tzik): Optimize this for single operation. + + int64 file_size = 0; + base::GetFileSize(platform_path_, &file_size); + int64 usage_delta = file_size - initial_file_size_; + + // |reserved_quota_consumption| may be greater than the recorded file growth + // when a plugin crashed before reporting its consumption. + // In this case, the reserved quota for the plugin should be handled as + // consumed quota. + int64 reserved_quota_consumption = + std::max(GetEstimatedFileSize(), file_size) - initial_file_size_; + + reservation_buffer_->CommitFileGrowth( + reserved_quota_consumption, usage_delta); + reservation_buffer_->DetachOpenFileHandleContext(this); +} + +} // namespace storage diff --git a/storage/browser/fileapi/quota/open_file_handle_context.h b/storage/browser/fileapi/quota/open_file_handle_context.h new file mode 100644 index 0000000..cb37064 --- /dev/null +++ b/storage/browser/fileapi/quota/open_file_handle_context.h @@ -0,0 +1,61 @@ +// Copyright 2013 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. + +#ifndef STORAGE_BROWSER_FILEAPI_OPEN_FILE_HANDLE_CONTEXT_H_ +#define STORAGE_BROWSER_FILEAPI_OPEN_FILE_HANDLE_CONTEXT_H_ + +#include <map> + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "storage/browser/storage_browser_export.h" +#include "storage/common/fileapi/file_system_types.h" +#include "url/gurl.h" + +namespace storage { + +class QuotaReservationBuffer; + +// This class represents a underlying file of a managed FileSystem file. +// The instance keeps alive while at least one consumer keeps an open file +// handle. +// This class is usually manipulated only via OpenFileHandle. +class OpenFileHandleContext : public base::RefCounted<OpenFileHandleContext> { + public: + OpenFileHandleContext(const base::FilePath& platform_path, + QuotaReservationBuffer* reservation_buffer); + + // Updates the max written offset and returns the amount of growth. + int64 UpdateMaxWrittenOffset(int64 offset); + + void AddAppendModeWriteAmount(int64 amount); + + const base::FilePath& platform_path() const { + return platform_path_; + } + + int64 GetEstimatedFileSize() const; + int64 GetMaxWrittenOffset() const; + + private: + friend class base::RefCounted<OpenFileHandleContext>; + virtual ~OpenFileHandleContext(); + + int64 initial_file_size_; + int64 maximum_written_offset_; + int64 append_mode_write_amount_; + base::FilePath platform_path_; + + scoped_refptr<QuotaReservationBuffer> reservation_buffer_; + + base::SequenceChecker sequence_checker_; + + DISALLOW_COPY_AND_ASSIGN(OpenFileHandleContext); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_OPEN_FILE_HANDLE_CONTEXT_H_ diff --git a/storage/browser/fileapi/quota/quota_backend_impl.cc b/storage/browser/fileapi/quota/quota_backend_impl.cc new file mode 100644 index 0000000..6969f7f --- /dev/null +++ b/storage/browser/fileapi/quota/quota_backend_impl.cc @@ -0,0 +1,172 @@ +// Copyright 2013 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 "storage/browser/fileapi/quota/quota_backend_impl.h" + +#include <string> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/numerics/safe_conversions.h" +#include "base/sequenced_task_runner.h" +#include "storage/browser/fileapi/file_system_usage_cache.h" +#include "storage/browser/quota/quota_client.h" +#include "storage/browser/quota/quota_manager_proxy.h" +#include "storage/common/fileapi/file_system_util.h" + +namespace storage { + +QuotaBackendImpl::QuotaBackendImpl( + base::SequencedTaskRunner* file_task_runner, + ObfuscatedFileUtil* obfuscated_file_util, + FileSystemUsageCache* file_system_usage_cache, + storage::QuotaManagerProxy* quota_manager_proxy) + : file_task_runner_(file_task_runner), + obfuscated_file_util_(obfuscated_file_util), + file_system_usage_cache_(file_system_usage_cache), + quota_manager_proxy_(quota_manager_proxy), + weak_ptr_factory_(this) { +} + +QuotaBackendImpl::~QuotaBackendImpl() { +} + +void QuotaBackendImpl::ReserveQuota(const GURL& origin, + FileSystemType type, + int64 delta, + const ReserveQuotaCallback& callback) { + DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); + DCHECK(origin.is_valid()); + if (!delta) { + callback.Run(base::File::FILE_OK, 0); + return; + } + DCHECK(quota_manager_proxy_.get()); + quota_manager_proxy_->GetUsageAndQuota( + file_task_runner_.get(), + origin, + FileSystemTypeToQuotaStorageType(type), + base::Bind(&QuotaBackendImpl::DidGetUsageAndQuotaForReserveQuota, + weak_ptr_factory_.GetWeakPtr(), + QuotaReservationInfo(origin, type, delta), + callback)); +} + +void QuotaBackendImpl::ReleaseReservedQuota(const GURL& origin, + FileSystemType type, + int64 size) { + DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); + DCHECK(origin.is_valid()); + DCHECK_LE(0, size); + if (!size) + return; + ReserveQuotaInternal(QuotaReservationInfo(origin, type, -size)); +} + +void QuotaBackendImpl::CommitQuotaUsage(const GURL& origin, + FileSystemType type, + int64 delta) { + DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); + DCHECK(origin.is_valid()); + if (!delta) + return; + ReserveQuotaInternal(QuotaReservationInfo(origin, type, delta)); + base::FilePath path; + if (GetUsageCachePath(origin, type, &path) != base::File::FILE_OK) + return; + bool result = file_system_usage_cache_->AtomicUpdateUsageByDelta(path, delta); + DCHECK(result); +} + +void QuotaBackendImpl::IncrementDirtyCount(const GURL& origin, + FileSystemType type) { + DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); + DCHECK(origin.is_valid()); + base::FilePath path; + if (GetUsageCachePath(origin, type, &path) != base::File::FILE_OK) + return; + DCHECK(file_system_usage_cache_); + file_system_usage_cache_->IncrementDirty(path); +} + +void QuotaBackendImpl::DecrementDirtyCount(const GURL& origin, + FileSystemType type) { + DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); + DCHECK(origin.is_valid()); + base::FilePath path; + if (GetUsageCachePath(origin, type, &path) != base::File::FILE_OK) + return; + DCHECK(file_system_usage_cache_); + file_system_usage_cache_->DecrementDirty(path); +} + +void QuotaBackendImpl::DidGetUsageAndQuotaForReserveQuota( + const QuotaReservationInfo& info, + const ReserveQuotaCallback& callback, + storage::QuotaStatusCode status, + int64 usage, + int64 quota) { + DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); + DCHECK(info.origin.is_valid()); + DCHECK_LE(0, usage); + DCHECK_LE(0, quota); + if (status != storage::kQuotaStatusOk) { + callback.Run(base::File::FILE_ERROR_FAILED, 0); + return; + } + + QuotaReservationInfo normalized_info = info; + if (info.delta > 0) { + int64 new_usage = + base::saturated_cast<int64>(usage + static_cast<uint64>(info.delta)); + if (quota < new_usage) + new_usage = quota; + normalized_info.delta = std::max(static_cast<int64>(0), new_usage - usage); + } + + ReserveQuotaInternal(normalized_info); + if (callback.Run(base::File::FILE_OK, normalized_info.delta)) + return; + // The requester could not accept the reserved quota. Revert it. + ReserveQuotaInternal( + QuotaReservationInfo(normalized_info.origin, + normalized_info.type, + -normalized_info.delta)); +} + +void QuotaBackendImpl::ReserveQuotaInternal(const QuotaReservationInfo& info) { + DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); + DCHECK(info.origin.is_valid()); + DCHECK(quota_manager_proxy_.get()); + quota_manager_proxy_->NotifyStorageModified( + storage::QuotaClient::kFileSystem, + info.origin, + FileSystemTypeToQuotaStorageType(info.type), + info.delta); +} + +base::File::Error QuotaBackendImpl::GetUsageCachePath( + const GURL& origin, + FileSystemType type, + base::FilePath* usage_file_path) { + DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); + DCHECK(origin.is_valid()); + DCHECK(usage_file_path); + base::File::Error error = base::File::FILE_OK; + *usage_file_path = + SandboxFileSystemBackendDelegate::GetUsageCachePathForOriginAndType( + obfuscated_file_util_, origin, type, &error); + return error; +} + +QuotaBackendImpl::QuotaReservationInfo::QuotaReservationInfo( + const GURL& origin, FileSystemType type, int64 delta) + : origin(origin), type(type), delta(delta) { +} + +QuotaBackendImpl::QuotaReservationInfo::~QuotaReservationInfo() { +} + +} // namespace storage diff --git a/storage/browser/fileapi/quota/quota_backend_impl.h b/storage/browser/fileapi/quota/quota_backend_impl.h new file mode 100644 index 0000000..ed15157 --- /dev/null +++ b/storage/browser/fileapi/quota/quota_backend_impl.h @@ -0,0 +1,106 @@ +// Copyright 2013 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. + +#ifndef STORAGE_BROWSER_FILEAPI_QUOTA_QUOTA_BACKEND_IMPL_H_ +#define STORAGE_BROWSER_FILEAPI_QUOTA_QUOTA_BACKEND_IMPL_H_ + +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "storage/browser/fileapi/quota/quota_reservation_manager.h" +#include "storage/browser/fileapi/sandbox_file_system_backend_delegate.h" +#include "storage/browser/storage_browser_export.h" +#include "storage/common/quota/quota_status_code.h" + +namespace base { +class SequencedTaskRunner; +} + +namespace content { +class QuotaBackendImplTest; +} + +namespace storage { +class QuotaManagerProxy; +} + +namespace storage { + +class FileSystemUsageCache; +class ObfuscatedFileUtil; + +// An instance of this class is owned by QuotaReservationManager. +class STORAGE_EXPORT QuotaBackendImpl + : public QuotaReservationManager::QuotaBackend { + public: + typedef QuotaReservationManager::ReserveQuotaCallback + ReserveQuotaCallback; + + QuotaBackendImpl(base::SequencedTaskRunner* file_task_runner, + ObfuscatedFileUtil* obfuscated_file_util, + FileSystemUsageCache* file_system_usage_cache, + storage::QuotaManagerProxy* quota_manager_proxy); + virtual ~QuotaBackendImpl(); + + // QuotaReservationManager::QuotaBackend overrides. + virtual void ReserveQuota( + const GURL& origin, + FileSystemType type, + int64 delta, + const ReserveQuotaCallback& callback) OVERRIDE; + virtual void ReleaseReservedQuota( + const GURL& origin, + FileSystemType type, + int64 size) OVERRIDE; + virtual void CommitQuotaUsage( + const GURL& origin, + FileSystemType type, + int64 delta) OVERRIDE; + virtual void IncrementDirtyCount( + const GURL& origin, + FileSystemType type) OVERRIDE; + virtual void DecrementDirtyCount( + const GURL& origin, + FileSystemType type) OVERRIDE; + + private: + friend class content::QuotaBackendImplTest; + + struct QuotaReservationInfo { + QuotaReservationInfo(const GURL& origin, FileSystemType type, int64 delta); + ~QuotaReservationInfo(); + + GURL origin; + FileSystemType type; + int64 delta; + }; + + void DidGetUsageAndQuotaForReserveQuota(const QuotaReservationInfo& info, + const ReserveQuotaCallback& callback, + storage::QuotaStatusCode status, + int64 usage, + int64 quota); + + void ReserveQuotaInternal( + const QuotaReservationInfo& info); + base::File::Error GetUsageCachePath( + const GURL& origin, + FileSystemType type, + base::FilePath* usage_file_path); + + scoped_refptr<base::SequencedTaskRunner> file_task_runner_; + + // Owned by SandboxFileSystemBackendDelegate. + ObfuscatedFileUtil* obfuscated_file_util_; + FileSystemUsageCache* file_system_usage_cache_; + + scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy_; + + base::WeakPtrFactory<QuotaBackendImpl> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(QuotaBackendImpl); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_QUOTA_QUOTA_BACKEND_IMPL_H_ diff --git a/storage/browser/fileapi/quota/quota_reservation.cc b/storage/browser/fileapi/quota/quota_reservation.cc new file mode 100644 index 0000000..fff7f16 --- /dev/null +++ b/storage/browser/fileapi/quota/quota_reservation.cc @@ -0,0 +1,127 @@ +// Copyright 2013 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 "storage/browser/fileapi/quota/quota_reservation.h" + +#include "base/bind.h" +#include "storage/browser/fileapi/quota/open_file_handle.h" +#include "storage/browser/fileapi/quota/quota_reservation_buffer.h" + +namespace storage { + +void QuotaReservation::RefreshReservation( + int64 size, + const StatusCallback& callback) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + DCHECK(!running_refresh_request_); + DCHECK(!client_crashed_); + if (!reservation_manager()) + return; + + running_refresh_request_ = true; + + reservation_manager()->ReserveQuota( + origin(), type(), size - remaining_quota_, + base::Bind(&QuotaReservation::AdaptDidUpdateReservedQuota, + weak_ptr_factory_.GetWeakPtr(), + remaining_quota_, callback)); + + if (running_refresh_request_) + remaining_quota_ = 0; +} + +scoped_ptr<OpenFileHandle> QuotaReservation::GetOpenFileHandle( + const base::FilePath& platform_path) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + DCHECK(!client_crashed_); + return reservation_buffer_->GetOpenFileHandle(this, platform_path); +} + +void QuotaReservation::OnClientCrash() { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + client_crashed_ = true; + + if (remaining_quota_) { + reservation_buffer_->PutReservationToBuffer(remaining_quota_); + remaining_quota_ = 0; + } +} + +void QuotaReservation::ConsumeReservation(int64 size) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + DCHECK_LT(0, size); + DCHECK_LE(size, remaining_quota_); + if (client_crashed_) + return; + + remaining_quota_ -= size; + reservation_buffer_->PutReservationToBuffer(size); +} + +QuotaReservationManager* QuotaReservation::reservation_manager() { + return reservation_buffer_->reservation_manager(); +} + +const GURL& QuotaReservation::origin() const { + return reservation_buffer_->origin(); +} + +FileSystemType QuotaReservation::type() const { + return reservation_buffer_->type(); +} + +QuotaReservation::QuotaReservation( + QuotaReservationBuffer* reservation_buffer) + : client_crashed_(false), + running_refresh_request_(false), + remaining_quota_(0), + reservation_buffer_(reservation_buffer), + weak_ptr_factory_(this) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); +} + +QuotaReservation::~QuotaReservation() { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + + if (remaining_quota_ && reservation_manager()) { + reservation_manager()->ReleaseReservedQuota( + origin(), type(), remaining_quota_); + } +} + +// static +bool QuotaReservation::AdaptDidUpdateReservedQuota( + const base::WeakPtr<QuotaReservation>& reservation, + int64 previous_size, + const StatusCallback& callback, + base::File::Error error, + int64 delta) { + if (!reservation) + return false; + + return reservation->DidUpdateReservedQuota( + previous_size, callback, error, delta); +} + +bool QuotaReservation::DidUpdateReservedQuota( + int64 previous_size, + const StatusCallback& callback, + base::File::Error error, + int64 delta) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + DCHECK(running_refresh_request_); + running_refresh_request_ = false; + + if (client_crashed_) { + callback.Run(base::File::FILE_ERROR_ABORT); + return false; + } + + if (error == base::File::FILE_OK) + remaining_quota_ = previous_size + delta; + callback.Run(error); + return true; +} + +} // namespace storage diff --git a/storage/browser/fileapi/quota/quota_reservation.h b/storage/browser/fileapi/quota/quota_reservation.h new file mode 100644 index 0000000..be35b9e --- /dev/null +++ b/storage/browser/fileapi/quota/quota_reservation.h @@ -0,0 +1,95 @@ +// Copyright 2013 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. + +#ifndef STORAGE_BROWSER_FILEAPI_QUOTA_QUOTA_RESERVATION_H_ +#define STORAGE_BROWSER_FILEAPI_QUOTA_QUOTA_RESERVATION_H_ + +#include "base/basictypes.h" +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "storage/browser/fileapi/quota/quota_reservation_manager.h" +#include "storage/browser/storage_browser_export.h" +#include "storage/common/fileapi/file_system_types.h" + +class GURL; + +namespace storage { + +class QuotaReservationBuffer; +class OpenFileHandle; + +// Represents a unit of quota reservation. +class STORAGE_EXPORT QuotaReservation + : public base::RefCounted<QuotaReservation> { + public: + typedef base::Callback<void(base::File::Error error)> StatusCallback; + + // Reclaims unused quota and reserves another |size| of quota. So that the + // resulting new |remaining_quota_| will be same as |size| as far as available + // space is enough. |remaining_quota_| may be less than |size| if there is + // not enough space available. + // Invokes |callback| upon completion. + void RefreshReservation(int64 size, const StatusCallback& callback); + + // Associates |platform_path| to the QuotaReservation instance. + // Returns an OpenFileHandle instance that represents a quota managed file. + scoped_ptr<OpenFileHandle> GetOpenFileHandle( + const base::FilePath& platform_path); + + // Should be called when the associated client is crashed. + // This implies the client can no longer report its consumption of the + // reserved quota. + // QuotaReservation puts all remaining quota to the QuotaReservationBuffer, so + // that the remaining quota will be reclaimed after all open files associated + // to the origin and type. + void OnClientCrash(); + + // Consumes |size| of reserved quota for a associated file. + // Consumed quota is sent to associated QuotaReservationBuffer for staging. + void ConsumeReservation(int64 size); + + // Returns amount of unused reserved quota. + int64 remaining_quota() const { return remaining_quota_; } + + QuotaReservationManager* reservation_manager(); + const GURL& origin() const; + FileSystemType type() const; + + private: + friend class QuotaReservationBuffer; + + // Use QuotaReservationManager as the entry point. + explicit QuotaReservation(QuotaReservationBuffer* reservation_buffer); + + friend class base::RefCounted<QuotaReservation>; + virtual ~QuotaReservation(); + + static bool AdaptDidUpdateReservedQuota( + const base::WeakPtr<QuotaReservation>& reservation, + int64 previous_size, + const StatusCallback& callback, + base::File::Error error, + int64 delta); + bool DidUpdateReservedQuota(int64 previous_size, + const StatusCallback& callback, + base::File::Error error, + int64 delta); + + bool client_crashed_; + bool running_refresh_request_; + int64 remaining_quota_; + + scoped_refptr<QuotaReservationBuffer> reservation_buffer_; + + base::SequenceChecker sequence_checker_; + base::WeakPtrFactory<QuotaReservation> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(QuotaReservation); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_QUOTA_QUOTA_RESERVATION_H_ diff --git a/storage/browser/fileapi/quota/quota_reservation_buffer.cc b/storage/browser/fileapi/quota/quota_reservation_buffer.cc new file mode 100644 index 0000000..faa70eb --- /dev/null +++ b/storage/browser/fileapi/quota/quota_reservation_buffer.cc @@ -0,0 +1,105 @@ +// Copyright 2013 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 "storage/browser/fileapi/quota/quota_reservation_buffer.h" + +#include "base/bind.h" +#include "storage/browser/fileapi/quota/open_file_handle.h" +#include "storage/browser/fileapi/quota/open_file_handle_context.h" +#include "storage/browser/fileapi/quota/quota_reservation.h" + +namespace storage { + +QuotaReservationBuffer::QuotaReservationBuffer( + base::WeakPtr<QuotaReservationManager> reservation_manager, + const GURL& origin, + FileSystemType type) + : reservation_manager_(reservation_manager), + origin_(origin), + type_(type), + reserved_quota_(0) { + DCHECK(origin.is_valid()); + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + reservation_manager_->IncrementDirtyCount(origin, type); +} + +scoped_refptr<QuotaReservation> QuotaReservationBuffer::CreateReservation() { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + return make_scoped_refptr(new QuotaReservation(this)); +} + +scoped_ptr<OpenFileHandle> QuotaReservationBuffer::GetOpenFileHandle( + QuotaReservation* reservation, + const base::FilePath& platform_path) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + OpenFileHandleContext** open_file = &open_files_[platform_path]; + if (!*open_file) + *open_file = new OpenFileHandleContext(platform_path, this); + return make_scoped_ptr(new OpenFileHandle(reservation, *open_file)); +} + +void QuotaReservationBuffer::CommitFileGrowth(int64 reserved_quota_consumption, + int64 usage_delta) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + if (!reservation_manager_) + return; + reservation_manager_->CommitQuotaUsage(origin_, type_, usage_delta); + + if (reserved_quota_consumption > 0) { + if (reserved_quota_consumption > reserved_quota_) { + LOG(ERROR) << "Detected over consumption of the storage quota beyond its" + << " reservation"; + reserved_quota_consumption = reserved_quota_; + } + + reserved_quota_ -= reserved_quota_consumption; + reservation_manager_->ReleaseReservedQuota( + origin_, type_, reserved_quota_consumption); + } +} + +void QuotaReservationBuffer::DetachOpenFileHandleContext( + OpenFileHandleContext* open_file) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + DCHECK_EQ(open_file, open_files_[open_file->platform_path()]); + open_files_.erase(open_file->platform_path()); +} + +void QuotaReservationBuffer::PutReservationToBuffer(int64 reservation) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + DCHECK_LE(0, reservation); + reserved_quota_ += reservation; +} + +QuotaReservationBuffer::~QuotaReservationBuffer() { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + if (!reservation_manager_) + return; + + DCHECK_LE(0, reserved_quota_); + if (reserved_quota_ && reservation_manager_) { + reservation_manager_->ReserveQuota( + origin_, type_, -reserved_quota_, + base::Bind(&QuotaReservationBuffer::DecrementDirtyCount, + reservation_manager_, origin_, type_)); + } + reservation_manager_->ReleaseReservationBuffer(this); +} + +// static +bool QuotaReservationBuffer::DecrementDirtyCount( + base::WeakPtr<QuotaReservationManager> reservation_manager, + const GURL& origin, + FileSystemType type, + base::File::Error error, + int64 delta_unused) { + DCHECK(origin.is_valid()); + if (error == base::File::FILE_OK && reservation_manager) { + reservation_manager->DecrementDirtyCount(origin, type); + return true; + } + return false; +} + +} // namespace storage diff --git a/storage/browser/fileapi/quota/quota_reservation_buffer.h b/storage/browser/fileapi/quota/quota_reservation_buffer.h new file mode 100644 index 0000000..0e893b6 --- /dev/null +++ b/storage/browser/fileapi/quota/quota_reservation_buffer.h @@ -0,0 +1,87 @@ +// Copyright 2013 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. + +#ifndef STORAGE_BROWSER_FILEAPI_QUOTA_QUOTA_RESERVATION_BUFFER_H_ +#define STORAGE_BROWSER_FILEAPI_QUOTA_QUOTA_RESERVATION_BUFFER_H_ + +#include <map> + +#include "base/basictypes.h" +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "storage/browser/storage_browser_export.h" +#include "storage/common/fileapi/file_system_types.h" +#include "url/gurl.h" + +namespace storage { + +class QuotaReservation; +class OpenFileHandle; +class OpenFileHandleContext; +class QuotaReservationManager; + +// QuotaReservationBuffer manages QuotaReservation instances. All consumed +// quota and leaked quota by associated QuotaReservation will be staged in +// QuotaReservationBuffer, and will be committed on a modified file is closed. +// The instance keeps alive while any associated QuotaReservation or +// OpenFileHandle alive. +// This class is usually manipulated only via OpenFileHandle and +// QuotaReservation. +class QuotaReservationBuffer : public base::RefCounted<QuotaReservationBuffer> { + public: + QuotaReservationBuffer( + base::WeakPtr<QuotaReservationManager> reservation_manager, + const GURL& origin, + FileSystemType type); + + scoped_refptr<QuotaReservation> CreateReservation(); + scoped_ptr<OpenFileHandle> GetOpenFileHandle( + QuotaReservation* reservation, + const base::FilePath& platform_path); + void CommitFileGrowth(int64 quota_consumption, int64 usage_delta); + void DetachOpenFileHandleContext(OpenFileHandleContext* context); + void PutReservationToBuffer(int64 size); + + QuotaReservationManager* reservation_manager() { + return reservation_manager_.get(); + } + + const GURL& origin() const { return origin_; } + FileSystemType type() const { return type_; } + + private: + friend class base::RefCounted<QuotaReservationBuffer>; + virtual ~QuotaReservationBuffer(); + + static bool DecrementDirtyCount( + base::WeakPtr<QuotaReservationManager> reservation_manager, + const GURL& origin, + FileSystemType type, + base::File::Error error, + int64 delta); + + typedef std::map<base::FilePath, OpenFileHandleContext*> + OpenFileHandleContextByPath; + + // Not owned. The destructor of OpenFileHandler should erase itself from + // |open_files_|. + OpenFileHandleContextByPath open_files_; + + base::WeakPtr<QuotaReservationManager> reservation_manager_; + + GURL origin_; + storage::FileSystemType type_; + + int64 reserved_quota_; + + base::SequenceChecker sequence_checker_; + + DISALLOW_COPY_AND_ASSIGN(QuotaReservationBuffer); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_QUOTA_QUOTA_RESERVATION_BUFFER_H_ diff --git a/storage/browser/fileapi/quota/quota_reservation_manager.cc b/storage/browser/fileapi/quota/quota_reservation_manager.cc new file mode 100644 index 0000000..b5ee5d8 --- /dev/null +++ b/storage/browser/fileapi/quota/quota_reservation_manager.cc @@ -0,0 +1,91 @@ +// Copyright 2013 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 "storage/browser/fileapi/quota/quota_reservation_manager.h" + +#include "storage/browser/fileapi/quota/quota_reservation.h" +#include "storage/browser/fileapi/quota/quota_reservation_buffer.h" + +namespace storage { + +QuotaReservationManager::QuotaReservationManager( + scoped_ptr<QuotaBackend> backend) + : backend_(backend.Pass()), + weak_ptr_factory_(this) { + sequence_checker_.DetachFromSequence(); +} + +QuotaReservationManager::~QuotaReservationManager() { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); +} + +void QuotaReservationManager::ReserveQuota( + const GURL& origin, + FileSystemType type, + int64 size, + const ReserveQuotaCallback& callback) { + DCHECK(origin.is_valid()); + backend_->ReserveQuota(origin, type, size, callback); +} + +void QuotaReservationManager::ReleaseReservedQuota( + const GURL& origin, + FileSystemType type, + int64 size) { + DCHECK(origin.is_valid()); + backend_->ReleaseReservedQuota(origin, type, size); +} + +void QuotaReservationManager::CommitQuotaUsage( + const GURL& origin, + FileSystemType type, + int64 delta) { + DCHECK(origin.is_valid()); + backend_->CommitQuotaUsage(origin, type, delta); +} + +void QuotaReservationManager::IncrementDirtyCount(const GURL& origin, + FileSystemType type) { + DCHECK(origin.is_valid()); + backend_->IncrementDirtyCount(origin, type); +} + +void QuotaReservationManager::DecrementDirtyCount(const GURL& origin, + FileSystemType type) { + DCHECK(origin.is_valid()); + backend_->DecrementDirtyCount(origin, type); +} + +scoped_refptr<QuotaReservationBuffer> +QuotaReservationManager::GetReservationBuffer( + const GURL& origin, + FileSystemType type) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + DCHECK(origin.is_valid()); + QuotaReservationBuffer** buffer = + &reservation_buffers_[std::make_pair(origin, type)]; + if (!*buffer) { + *buffer = new QuotaReservationBuffer( + weak_ptr_factory_.GetWeakPtr(), origin, type); + } + return make_scoped_refptr(*buffer); +} + +void QuotaReservationManager::ReleaseReservationBuffer( + QuotaReservationBuffer* reservation_buffer) { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + std::pair<GURL, FileSystemType> key(reservation_buffer->origin(), + reservation_buffer->type()); + DCHECK_EQ(reservation_buffers_[key], reservation_buffer); + reservation_buffers_.erase(key); +} + +scoped_refptr<QuotaReservation> QuotaReservationManager::CreateReservation( + const GURL& origin, + FileSystemType type) { + DCHECK(origin.is_valid()); + return GetReservationBuffer(origin, type)->CreateReservation();; +} + +} // namespace storage diff --git a/storage/browser/fileapi/quota/quota_reservation_manager.h b/storage/browser/fileapi/quota/quota_reservation_manager.h new file mode 100644 index 0000000..ec04245 --- /dev/null +++ b/storage/browser/fileapi/quota/quota_reservation_manager.h @@ -0,0 +1,127 @@ +// Copyright 2013 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. + +#ifndef STORAGE_BROWSER_FILEAPI_QUOTA_QUOTA_RESERVATION_MANAGER_H_ +#define STORAGE_BROWSER_FILEAPI_QUOTA_QUOTA_RESERVATION_MANAGER_H_ + +#include <map> +#include <utility> + +#include "base/basictypes.h" +#include "base/callback_forward.h" +#include "base/files/file.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "storage/browser/storage_browser_export.h" +#include "storage/common/fileapi/file_system_types.h" +#include "url/gurl.h" + +namespace content { +class QuotaReservationManagerTest; +} + +namespace storage { + +class QuotaReservation; +class QuotaReservationBuffer; +class OpenFileHandle; +class OpenFileHandleContext; + +class STORAGE_EXPORT QuotaReservationManager { + public: + // Callback for ReserveQuota. When this callback returns false, ReserveQuota + // operation should be reverted. + typedef base::Callback<bool(base::File::Error error, int64 delta)> + ReserveQuotaCallback; + + // An abstraction of backing quota system. + class STORAGE_EXPORT QuotaBackend { + public: + QuotaBackend() {} + virtual ~QuotaBackend() {} + + // Reserves or reclaims |delta| of quota for |origin| and |type| pair. + // Reserved quota should be counted as usage, but it should be on-memory + // and be cleared by a browser restart. + // Invokes |callback| upon completion with an error code. + // |callback| should return false if it can't accept the reservation, in + // that case, the backend should roll back the reservation. + virtual void ReserveQuota(const GURL& origin, + FileSystemType type, + int64 delta, + const ReserveQuotaCallback& callback) = 0; + + // Reclaims |size| of quota for |origin| and |type|. + virtual void ReleaseReservedQuota(const GURL& origin, + FileSystemType type, + int64 size) = 0; + + // Updates disk usage of |origin| and |type|. + // Invokes |callback| upon completion with an error code. + virtual void CommitQuotaUsage(const GURL& origin, + FileSystemType type, + int64 delta) = 0; + + virtual void IncrementDirtyCount(const GURL& origin, + FileSystemType type) = 0; + virtual void DecrementDirtyCount(const GURL& origin, + FileSystemType type) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(QuotaBackend); + }; + + explicit QuotaReservationManager(scoped_ptr<QuotaBackend> backend); + ~QuotaReservationManager(); + + // The entry point of the quota reservation. Creates new reservation object + // for |origin| and |type|. + scoped_refptr<QuotaReservation> CreateReservation( + const GURL& origin, + FileSystemType type); + + private: + typedef std::map<std::pair<GURL, FileSystemType>, QuotaReservationBuffer*> + ReservationBufferByOriginAndType; + + friend class QuotaReservation; + friend class QuotaReservationBuffer; + friend class content::QuotaReservationManagerTest; + + void ReserveQuota(const GURL& origin, + FileSystemType type, + int64 delta, + const ReserveQuotaCallback& callback); + + void ReleaseReservedQuota(const GURL& origin, + FileSystemType type, + int64 size); + + void CommitQuotaUsage(const GURL& origin, + FileSystemType type, + int64 delta); + + void IncrementDirtyCount(const GURL& origin, FileSystemType type); + void DecrementDirtyCount(const GURL& origin, FileSystemType type); + + scoped_refptr<QuotaReservationBuffer> GetReservationBuffer( + const GURL& origin, + FileSystemType type); + void ReleaseReservationBuffer(QuotaReservationBuffer* reservation_pool); + + scoped_ptr<QuotaBackend> backend_; + + // Not owned. The destructor of ReservationBuffer should erase itself from + // |reservation_buffers_| by calling ReleaseReservationBuffer. + ReservationBufferByOriginAndType reservation_buffers_; + + base::SequenceChecker sequence_checker_; + base::WeakPtrFactory<QuotaReservationManager> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(QuotaReservationManager); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_QUOTA_QUOTA_RESERVATION_MANAGER_H_ diff --git a/storage/browser/fileapi/recursive_operation_delegate.cc b/storage/browser/fileapi/recursive_operation_delegate.cc new file mode 100644 index 0000000..e9d1466 --- /dev/null +++ b/storage/browser/fileapi/recursive_operation_delegate.cc @@ -0,0 +1,239 @@ +// Copyright (c) 2013 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 "storage/browser/fileapi/recursive_operation_delegate.h" + +#include "base/bind.h" +#include "storage/browser/fileapi/file_system_context.h" +#include "storage/browser/fileapi/file_system_operation_runner.h" + +namespace storage { + +namespace { +// Don't start too many inflight operations. +const int kMaxInflightOperations = 5; +} + +RecursiveOperationDelegate::RecursiveOperationDelegate( + FileSystemContext* file_system_context) + : file_system_context_(file_system_context), + inflight_operations_(0), + canceled_(false) { +} + +RecursiveOperationDelegate::~RecursiveOperationDelegate() { +} + +void RecursiveOperationDelegate::Cancel() { + canceled_ = true; + OnCancel(); +} + +void RecursiveOperationDelegate::StartRecursiveOperation( + const FileSystemURL& root, + const StatusCallback& callback) { + DCHECK(pending_directory_stack_.empty()); + DCHECK(pending_files_.empty()); + DCHECK_EQ(0, inflight_operations_); + + callback_ = callback; + ++inflight_operations_; + ProcessFile( + root, + base::Bind(&RecursiveOperationDelegate::DidTryProcessFile, + AsWeakPtr(), root)); +} + +FileSystemOperationRunner* RecursiveOperationDelegate::operation_runner() { + return file_system_context_->operation_runner(); +} + +void RecursiveOperationDelegate::OnCancel() { +} + +void RecursiveOperationDelegate::DidTryProcessFile( + const FileSystemURL& root, + base::File::Error error) { + DCHECK(pending_directory_stack_.empty()); + DCHECK(pending_files_.empty()); + DCHECK_EQ(1, inflight_operations_); + + --inflight_operations_; + if (canceled_ || error != base::File::FILE_ERROR_NOT_A_FILE) { + Done(error); + return; + } + + pending_directory_stack_.push(std::queue<FileSystemURL>()); + pending_directory_stack_.top().push(root); + ProcessNextDirectory(); +} + +void RecursiveOperationDelegate::ProcessNextDirectory() { + DCHECK(pending_files_.empty()); + DCHECK(!pending_directory_stack_.empty()); + DCHECK(!pending_directory_stack_.top().empty()); + DCHECK_EQ(0, inflight_operations_); + + const FileSystemURL& url = pending_directory_stack_.top().front(); + + ++inflight_operations_; + ProcessDirectory( + url, + base::Bind( + &RecursiveOperationDelegate::DidProcessDirectory, AsWeakPtr())); +} + +void RecursiveOperationDelegate::DidProcessDirectory( + base::File::Error error) { + DCHECK(pending_files_.empty()); + DCHECK(!pending_directory_stack_.empty()); + DCHECK(!pending_directory_stack_.top().empty()); + DCHECK_EQ(1, inflight_operations_); + + --inflight_operations_; + if (canceled_ || error != base::File::FILE_OK) { + Done(error); + return; + } + + const FileSystemURL& parent = pending_directory_stack_.top().front(); + pending_directory_stack_.push(std::queue<FileSystemURL>()); + operation_runner()->ReadDirectory( + parent, + base::Bind(&RecursiveOperationDelegate::DidReadDirectory, + AsWeakPtr(), parent)); +} + +void RecursiveOperationDelegate::DidReadDirectory( + const FileSystemURL& parent, + base::File::Error error, + const FileEntryList& entries, + bool has_more) { + DCHECK(!pending_directory_stack_.empty()); + DCHECK_EQ(0, inflight_operations_); + + if (canceled_ || error != base::File::FILE_OK) { + Done(error); + return; + } + + for (size_t i = 0; i < entries.size(); i++) { + FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL( + parent.origin(), + parent.mount_type(), + parent.virtual_path().Append(entries[i].name)); + if (entries[i].is_directory) + pending_directory_stack_.top().push(url); + else + pending_files_.push(url); + } + + // Wait for next entries. + if (has_more) + return; + + ProcessPendingFiles(); +} + +void RecursiveOperationDelegate::ProcessPendingFiles() { + DCHECK(!pending_directory_stack_.empty()); + + if ((pending_files_.empty() || canceled_) && inflight_operations_ == 0) { + ProcessSubDirectory(); + return; + } + + // Do not post any new tasks. + if (canceled_) + return; + + // Run ProcessFile in parallel (upto kMaxInflightOperations). + scoped_refptr<base::MessageLoopProxy> current_message_loop = + base::MessageLoopProxy::current(); + while (!pending_files_.empty() && + inflight_operations_ < kMaxInflightOperations) { + ++inflight_operations_; + current_message_loop->PostTask( + FROM_HERE, + base::Bind(&RecursiveOperationDelegate::ProcessFile, + AsWeakPtr(), pending_files_.front(), + base::Bind(&RecursiveOperationDelegate::DidProcessFile, + AsWeakPtr()))); + pending_files_.pop(); + } +} + +void RecursiveOperationDelegate::DidProcessFile( + base::File::Error error) { + --inflight_operations_; + if (error != base::File::FILE_OK) { + // If an error occurs, invoke Done immediately (even if there remain + // running operations). It is because in the callback, this instance is + // deleted. + Done(error); + return; + } + + ProcessPendingFiles(); +} + +void RecursiveOperationDelegate::ProcessSubDirectory() { + DCHECK(pending_files_.empty()); + DCHECK(!pending_directory_stack_.empty()); + DCHECK_EQ(0, inflight_operations_); + + if (canceled_) { + Done(base::File::FILE_ERROR_ABORT); + return; + } + + if (!pending_directory_stack_.top().empty()) { + // There remain some sub directories. Process them first. + ProcessNextDirectory(); + return; + } + + // All subdirectories are processed. + pending_directory_stack_.pop(); + if (pending_directory_stack_.empty()) { + // All files/directories are processed. + Done(base::File::FILE_OK); + return; + } + + DCHECK(!pending_directory_stack_.top().empty()); + ++inflight_operations_; + PostProcessDirectory( + pending_directory_stack_.top().front(), + base::Bind(&RecursiveOperationDelegate::DidPostProcessDirectory, + AsWeakPtr())); +} + +void RecursiveOperationDelegate::DidPostProcessDirectory( + base::File::Error error) { + DCHECK(pending_files_.empty()); + DCHECK(!pending_directory_stack_.empty()); + DCHECK(!pending_directory_stack_.top().empty()); + DCHECK_EQ(1, inflight_operations_); + + --inflight_operations_; + pending_directory_stack_.top().pop(); + if (canceled_ || error != base::File::FILE_OK) { + Done(error); + return; + } + + ProcessSubDirectory(); +} + +void RecursiveOperationDelegate::Done(base::File::Error error) { + if (canceled_ && error == base::File::FILE_OK) { + callback_.Run(base::File::FILE_ERROR_ABORT); + } else { + callback_.Run(error); + } +} + +} // namespace storage diff --git a/storage/browser/fileapi/recursive_operation_delegate.h b/storage/browser/fileapi/recursive_operation_delegate.h new file mode 100644 index 0000000..f7f07c2 --- /dev/null +++ b/storage/browser/fileapi/recursive_operation_delegate.h @@ -0,0 +1,152 @@ +// Copyright (c) 2013 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. + +#ifndef STORAGE_BROWSER_FILEAPI_RECURSIVE_OPERATION_DELEGATE_H_ +#define STORAGE_BROWSER_FILEAPI_RECURSIVE_OPERATION_DELEGATE_H_ + +#include <queue> +#include <stack> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/memory/weak_ptr.h" +#include "storage/browser/fileapi/file_system_operation.h" +#include "storage/browser/fileapi/file_system_url.h" + +namespace storage { + +class FileSystemContext; +class FileSystemOperationRunner; + +// A base class for recursive operation delegates. +// +// In short, each subclass should override ProcessFile and ProcessDirectory +// to process a directory or a file. To start the recursive operation it +// should also call StartRecursiveOperation. +class STORAGE_EXPORT RecursiveOperationDelegate + : public base::SupportsWeakPtr<RecursiveOperationDelegate> { + public: + typedef FileSystemOperation::StatusCallback StatusCallback; + typedef FileSystemOperation::FileEntryList FileEntryList; + + virtual ~RecursiveOperationDelegate(); + + // This is called when the consumer of this instance starts a non-recursive + // operation. + virtual void Run() = 0; + + // This is called when the consumer of this instance starts a recursive + // operation. + virtual void RunRecursively() = 0; + + // This is called each time a file is found while recursively + // performing an operation. + virtual void ProcessFile(const FileSystemURL& url, + const StatusCallback& callback) = 0; + + // This is called each time a directory is found while recursively + // performing an operation. + virtual void ProcessDirectory(const FileSystemURL& url, + const StatusCallback& callback) = 0; + + + // This is called each time after files and subdirectories for a + // directory is processed while recursively performing an operation. + virtual void PostProcessDirectory(const FileSystemURL& url, + const StatusCallback& callback) = 0; + + // Cancels the currently running operation. + void Cancel(); + + protected: + explicit RecursiveOperationDelegate(FileSystemContext* file_system_context); + + // Starts to process files/directories recursively from the given |root|. + // This will call ProcessFile and ProcessDirectory on each file or directory. + // + // First, this tries to call ProcessFile with |root| regardless whether it is + // actually a file or a directory. If it is a directory, ProcessFile should + // return File::FILE_NOT_A_FILE. + // + // For each directory, the recursive operation works as follows: + // ProcessDirectory is called first for the directory. + // Then the directory contents are read (to obtain its sub directories and + // files in it). + // ProcessFile is called for found files. This may run in parallel. + // The same step is recursively applied to each subdirectory. + // After all files and subdirectories in a directory are processed, + // PostProcessDirectory is called for the directory. + // Here is an example; + // a_dir/ -+- b1_dir/ -+- c1_dir/ -+- d1_file + // | | | + // | +- c2_file +- d2_file + // | + // +- b2_dir/ --- e_dir/ + // | + // +- b3_file + // | + // +- b4_file + // Then traverse order is: + // ProcessFile(a_dir) (This should return File::FILE_NOT_A_FILE). + // ProcessDirectory(a_dir). + // ProcessFile(b3_file), ProcessFile(b4_file). (in parallel). + // ProcessDirectory(b1_dir). + // ProcessFile(c2_file) + // ProcessDirectory(c1_dir). + // ProcessFile(d1_file), ProcessFile(d2_file). (in parallel). + // PostProcessDirectory(c1_dir) + // PostProcessDirectory(b1_dir). + // ProcessDirectory(b2_dir) + // ProcessDirectory(e_dir) + // PostProcessDirectory(e_dir) + // PostProcessDirectory(b2_dir) + // PostProcessDirectory(a_dir) + // + // |callback| is fired with base::File::FILE_OK when every file/directory + // under |root| is processed, or fired earlier when any suboperation fails. + void StartRecursiveOperation(const FileSystemURL& root, + const StatusCallback& callback); + + FileSystemContext* file_system_context() { return file_system_context_; } + const FileSystemContext* file_system_context() const { + return file_system_context_; + } + + FileSystemOperationRunner* operation_runner(); + + // Called when Cancel() is called. This is a hook to do something more + // in a derived class. By default, do nothing. + virtual void OnCancel(); + + private: + void DidTryProcessFile(const FileSystemURL& root, + base::File::Error error); + void ProcessNextDirectory(); + void DidProcessDirectory(base::File::Error error); + void DidReadDirectory(const FileSystemURL& parent, + base::File::Error error, + const FileEntryList& entries, + bool has_more); + void ProcessPendingFiles(); + void DidProcessFile(base::File::Error error); + void ProcessSubDirectory(); + void DidPostProcessDirectory(base::File::Error error); + + // Called when all recursive operation is done (or an error occurs). + void Done(base::File::Error error); + + FileSystemContext* file_system_context_; + StatusCallback callback_; + std::stack<FileSystemURL> pending_directories_; + std::stack<std::queue<FileSystemURL> > pending_directory_stack_; + std::queue<FileSystemURL> pending_files_; + int inflight_operations_; + bool canceled_; + + DISALLOW_COPY_AND_ASSIGN(RecursiveOperationDelegate); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_RECURSIVE_OPERATION_DELEGATE_H_ diff --git a/storage/browser/fileapi/remove_operation_delegate.cc b/storage/browser/fileapi/remove_operation_delegate.cc new file mode 100644 index 0000000..eb28587 --- /dev/null +++ b/storage/browser/fileapi/remove_operation_delegate.cc @@ -0,0 +1,82 @@ +// Copyright (c) 2013 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 "storage/browser/fileapi/remove_operation_delegate.h" + +#include "base/bind.h" +#include "storage/browser/fileapi/file_system_context.h" +#include "storage/browser/fileapi/file_system_operation_runner.h" + +namespace storage { + +RemoveOperationDelegate::RemoveOperationDelegate( + FileSystemContext* file_system_context, + const FileSystemURL& url, + const StatusCallback& callback) + : RecursiveOperationDelegate(file_system_context), + url_(url), + callback_(callback), + weak_factory_(this) { +} + +RemoveOperationDelegate::~RemoveOperationDelegate() {} + +void RemoveOperationDelegate::Run() { + operation_runner()->RemoveFile(url_, base::Bind( + &RemoveOperationDelegate::DidTryRemoveFile, weak_factory_.GetWeakPtr())); +} + +void RemoveOperationDelegate::RunRecursively() { + StartRecursiveOperation(url_, callback_); +} + +void RemoveOperationDelegate::ProcessFile(const FileSystemURL& url, + const StatusCallback& callback) { + operation_runner()->RemoveFile( + url, + base::Bind(&RemoveOperationDelegate::DidRemoveFile, + weak_factory_.GetWeakPtr(), callback)); +} + +void RemoveOperationDelegate::ProcessDirectory(const FileSystemURL& url, + const StatusCallback& callback) { + callback.Run(base::File::FILE_OK); +} + +void RemoveOperationDelegate::PostProcessDirectory( + const FileSystemURL& url, const StatusCallback& callback) { + operation_runner()->RemoveDirectory(url, callback); +} + +void RemoveOperationDelegate::DidTryRemoveFile(base::File::Error error) { + if (error != base::File::FILE_ERROR_NOT_A_FILE && + error != base::File::FILE_ERROR_SECURITY) { + callback_.Run(error); + return; + } + operation_runner()->RemoveDirectory( + url_, + base::Bind(&RemoveOperationDelegate::DidTryRemoveDirectory, + weak_factory_.GetWeakPtr(), error)); +} + +void RemoveOperationDelegate::DidTryRemoveDirectory( + base::File::Error remove_file_error, + base::File::Error remove_directory_error) { + callback_.Run( + remove_directory_error == base::File::FILE_ERROR_NOT_A_DIRECTORY ? + remove_file_error : + remove_directory_error); +} + +void RemoveOperationDelegate::DidRemoveFile(const StatusCallback& callback, + base::File::Error error) { + if (error == base::File::FILE_ERROR_NOT_FOUND) { + callback.Run(base::File::FILE_OK); + return; + } + callback.Run(error); +} + +} // namespace storage diff --git a/storage/browser/fileapi/remove_operation_delegate.h b/storage/browser/fileapi/remove_operation_delegate.h new file mode 100644 index 0000000..436a641 --- /dev/null +++ b/storage/browser/fileapi/remove_operation_delegate.h @@ -0,0 +1,46 @@ +// Copyright (c) 2013 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. + +#ifndef STORAGE_BROWSER_FILEAPI_REMOVE_OPERATION_DELEGATE_H_ +#define STORAGE_BROWSER_FILEAPI_REMOVE_OPERATION_DELEGATE_H_ + +#include <stack> + +#include "storage/browser/fileapi/recursive_operation_delegate.h" + +namespace storage { + +class RemoveOperationDelegate : public RecursiveOperationDelegate { + public: + RemoveOperationDelegate(FileSystemContext* file_system_context, + const FileSystemURL& url, + const StatusCallback& callback); + virtual ~RemoveOperationDelegate(); + + // RecursiveOperationDelegate overrides: + virtual void Run() OVERRIDE; + virtual void RunRecursively() OVERRIDE; + virtual void ProcessFile(const FileSystemURL& url, + const StatusCallback& callback) OVERRIDE; + virtual void ProcessDirectory(const FileSystemURL& url, + const StatusCallback& callback) OVERRIDE; + virtual void PostProcessDirectory(const FileSystemURL& url, + const StatusCallback& callback) OVERRIDE; + + private: + void DidTryRemoveFile(base::File::Error error); + void DidTryRemoveDirectory(base::File::Error remove_file_error, + base::File::Error remove_directory_error); + void DidRemoveFile(const StatusCallback& callback, + base::File::Error error); + + FileSystemURL url_; + StatusCallback callback_; + base::WeakPtrFactory<RemoveOperationDelegate> weak_factory_; + DISALLOW_COPY_AND_ASSIGN(RemoveOperationDelegate); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_REMOVE_OPERATION_DELEGATE_H_ diff --git a/storage/browser/fileapi/sandbox_directory_database.cc b/storage/browser/fileapi/sandbox_directory_database.cc new file mode 100644 index 0000000..e34d324 --- /dev/null +++ b/storage/browser/fileapi/sandbox_directory_database.cc @@ -0,0 +1,939 @@ +// 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 "storage/browser/fileapi/sandbox_directory_database.h" + +#include <math.h> +#include <algorithm> +#include <set> +#include <stack> + +#include "base/files/file_enumerator.h" +#include "base/files/file_util.h" +#include "base/location.h" +#include "base/metrics/histogram.h" +#include "base/pickle.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "storage/browser/fileapi/file_system_usage_cache.h" +#include "storage/common/fileapi/file_system_util.h" +#include "third_party/leveldatabase/src/include/leveldb/db.h" +#include "third_party/leveldatabase/src/include/leveldb/write_batch.h" + +namespace { + +bool PickleFromFileInfo(const storage::SandboxDirectoryDatabase::FileInfo& info, + Pickle* pickle) { + DCHECK(pickle); + std::string data_path; + // Round off here to match the behavior of the filesystem on real files. + base::Time time = + base::Time::FromDoubleT(floor(info.modification_time.ToDoubleT())); + std::string name; + + data_path = storage::FilePathToString(info.data_path); + name = storage::FilePathToString(base::FilePath(info.name)); + + if (pickle->WriteInt64(info.parent_id) && + pickle->WriteString(data_path) && + pickle->WriteString(name) && + pickle->WriteInt64(time.ToInternalValue())) + return true; + + NOTREACHED(); + return false; +} + +bool FileInfoFromPickle(const Pickle& pickle, + storage::SandboxDirectoryDatabase::FileInfo* info) { + PickleIterator iter(pickle); + std::string data_path; + std::string name; + int64 internal_time; + + if (pickle.ReadInt64(&iter, &info->parent_id) && + pickle.ReadString(&iter, &data_path) && + pickle.ReadString(&iter, &name) && + pickle.ReadInt64(&iter, &internal_time)) { + info->data_path = storage::StringToFilePath(data_path); + info->name = storage::StringToFilePath(name).value(); + info->modification_time = base::Time::FromInternalValue(internal_time); + return true; + } + LOG(ERROR) << "Pickle could not be digested!"; + return false; +} + +const base::FilePath::CharType kDirectoryDatabaseName[] = + FILE_PATH_LITERAL("Paths"); +const char kChildLookupPrefix[] = "CHILD_OF:"; +const char kChildLookupSeparator[] = ":"; +const char kLastFileIdKey[] = "LAST_FILE_ID"; +const char kLastIntegerKey[] = "LAST_INTEGER"; +const int64 kMinimumReportIntervalHours = 1; +const char kInitStatusHistogramLabel[] = "FileSystem.DirectoryDatabaseInit"; +const char kDatabaseRepairHistogramLabel[] = + "FileSystem.DirectoryDatabaseRepair"; + +enum InitStatus { + INIT_STATUS_OK = 0, + INIT_STATUS_CORRUPTION, + INIT_STATUS_IO_ERROR, + INIT_STATUS_UNKNOWN_ERROR, + INIT_STATUS_MAX +}; + +enum RepairResult { + DB_REPAIR_SUCCEEDED = 0, + DB_REPAIR_FAILED, + DB_REPAIR_MAX +}; + +std::string GetChildLookupKey( + storage::SandboxDirectoryDatabase::FileId parent_id, + const base::FilePath::StringType& child_name) { + std::string name; + name = storage::FilePathToString(base::FilePath(child_name)); + return std::string(kChildLookupPrefix) + base::Int64ToString(parent_id) + + std::string(kChildLookupSeparator) + name; +} + +std::string GetChildListingKeyPrefix( + storage::SandboxDirectoryDatabase::FileId parent_id) { + return std::string(kChildLookupPrefix) + base::Int64ToString(parent_id) + + std::string(kChildLookupSeparator); +} + +const char* LastFileIdKey() { + return kLastFileIdKey; +} + +const char* LastIntegerKey() { + return kLastIntegerKey; +} + +std::string GetFileLookupKey( + storage::SandboxDirectoryDatabase::FileId file_id) { + return base::Int64ToString(file_id); +} + +// Assumptions: +// - Any database entry is one of: +// - ("CHILD_OF:|parent_id|:<name>", "|file_id|"), +// - ("LAST_FILE_ID", "|last_file_id|"), +// - ("LAST_INTEGER", "|last_integer|"), +// - ("|file_id|", "pickled FileInfo") +// where FileInfo has |parent_id|, |data_path|, |name| and +// |modification_time|, +// Constraints: +// - Each file in the database has unique backing file. +// - Each file in |filesystem_data_directory_| has a database entry. +// - Directory structure is tree, i.e. connected and acyclic. +class DatabaseCheckHelper { + public: + typedef storage::SandboxDirectoryDatabase::FileId FileId; + typedef storage::SandboxDirectoryDatabase::FileInfo FileInfo; + + DatabaseCheckHelper(storage::SandboxDirectoryDatabase* dir_db, + leveldb::DB* db, + const base::FilePath& path); + + bool IsFileSystemConsistent() { + return IsDatabaseEmpty() || + (ScanDatabase() && ScanDirectory() && ScanHierarchy()); + } + + private: + bool IsDatabaseEmpty(); + // These 3 methods need to be called in the order. Each method requires its + // previous method finished successfully. They also require the database is + // not empty. + bool ScanDatabase(); + bool ScanDirectory(); + bool ScanHierarchy(); + + storage::SandboxDirectoryDatabase* dir_db_; + leveldb::DB* db_; + base::FilePath path_; + + std::set<base::FilePath> files_in_db_; + + size_t num_directories_in_db_; + size_t num_files_in_db_; + size_t num_hierarchy_links_in_db_; + + FileId last_file_id_; + FileId last_integer_; +}; + +DatabaseCheckHelper::DatabaseCheckHelper( + storage::SandboxDirectoryDatabase* dir_db, + leveldb::DB* db, + const base::FilePath& path) + : dir_db_(dir_db), + db_(db), + path_(path), + num_directories_in_db_(0), + num_files_in_db_(0), + num_hierarchy_links_in_db_(0), + last_file_id_(-1), + last_integer_(-1) { + DCHECK(dir_db_); + DCHECK(db_); + DCHECK(!path_.empty() && base::DirectoryExists(path_)); +} + +bool DatabaseCheckHelper::IsDatabaseEmpty() { + scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); + itr->SeekToFirst(); + return !itr->Valid(); +} + +bool DatabaseCheckHelper::ScanDatabase() { + // Scans all database entries sequentially to verify each of them has unique + // backing file. + int64 max_file_id = -1; + std::set<FileId> file_ids; + + scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); + for (itr->SeekToFirst(); itr->Valid(); itr->Next()) { + std::string key = itr->key().ToString(); + if (StartsWithASCII(key, kChildLookupPrefix, true)) { + // key: "CHILD_OF:<parent_id>:<name>" + // value: "<child_id>" + ++num_hierarchy_links_in_db_; + } else if (key == kLastFileIdKey) { + // key: "LAST_FILE_ID" + // value: "<last_file_id>" + if (last_file_id_ >= 0 || + !base::StringToInt64(itr->value().ToString(), &last_file_id_)) + return false; + + if (last_file_id_ < 0) + return false; + } else if (key == kLastIntegerKey) { + // key: "LAST_INTEGER" + // value: "<last_integer>" + if (last_integer_ >= 0 || + !base::StringToInt64(itr->value().ToString(), &last_integer_)) + return false; + } else { + // key: "<entry_id>" + // value: "<pickled FileInfo>" + FileInfo file_info; + if (!FileInfoFromPickle( + Pickle(itr->value().data(), itr->value().size()), &file_info)) + return false; + + FileId file_id = -1; + if (!base::StringToInt64(key, &file_id) || file_id < 0) + return false; + + if (max_file_id < file_id) + max_file_id = file_id; + if (!file_ids.insert(file_id).second) + return false; + + if (file_info.is_directory()) { + ++num_directories_in_db_; + DCHECK(file_info.data_path.empty()); + } else { + // Ensure any pair of file entry don't share their data_path. + if (!files_in_db_.insert(file_info.data_path).second) + return false; + + // Ensure the backing file exists as a normal file. + base::File::Info platform_file_info; + if (!base::GetFileInfo( + path_.Append(file_info.data_path), &platform_file_info) || + platform_file_info.is_directory || + platform_file_info.is_symbolic_link) { + // leveldb::Iterator iterates a snapshot of the database. + // So even after RemoveFileInfo() call, we'll visit hierarchy link + // from |parent_id| to |file_id|. + if (!dir_db_->RemoveFileInfo(file_id)) + return false; + --num_hierarchy_links_in_db_; + files_in_db_.erase(file_info.data_path); + } else { + ++num_files_in_db_; + } + } + } + } + + // TODO(tzik): Add constraint for |last_integer_| to avoid possible + // data path confliction on ObfuscatedFileUtil. + return max_file_id <= last_file_id_; +} + +bool DatabaseCheckHelper::ScanDirectory() { + // TODO(kinuko): Scans all local file system entries to verify each of them + // has a database entry. + const base::FilePath kExcludes[] = { + base::FilePath(kDirectoryDatabaseName), + base::FilePath(storage::FileSystemUsageCache::kUsageFileName), + }; + + // Any path in |pending_directories| is relative to |path_|. + std::stack<base::FilePath> pending_directories; + pending_directories.push(base::FilePath()); + + while (!pending_directories.empty()) { + base::FilePath dir_path = pending_directories.top(); + pending_directories.pop(); + + base::FileEnumerator file_enum( + dir_path.empty() ? path_ : path_.Append(dir_path), + false /* not recursive */, + base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES); + + base::FilePath absolute_file_path; + while (!(absolute_file_path = file_enum.Next()).empty()) { + base::FileEnumerator::FileInfo find_info = file_enum.GetInfo(); + + base::FilePath relative_file_path; + if (!path_.AppendRelativePath(absolute_file_path, &relative_file_path)) + return false; + + if (std::find(kExcludes, kExcludes + arraysize(kExcludes), + relative_file_path) != kExcludes + arraysize(kExcludes)) + continue; + + if (find_info.IsDirectory()) { + pending_directories.push(relative_file_path); + continue; + } + + // Check if the file has a database entry. + std::set<base::FilePath>::iterator itr = + files_in_db_.find(relative_file_path); + if (itr == files_in_db_.end()) { + if (!base::DeleteFile(absolute_file_path, false)) + return false; + } else { + files_in_db_.erase(itr); + } + } + } + + return files_in_db_.empty(); +} + +bool DatabaseCheckHelper::ScanHierarchy() { + size_t visited_directories = 0; + size_t visited_files = 0; + size_t visited_links = 0; + + std::stack<FileId> directories; + directories.push(0); + + // Check if the root directory exists as a directory. + FileInfo file_info; + if (!dir_db_->GetFileInfo(0, &file_info)) + return false; + if (file_info.parent_id != 0 || + !file_info.is_directory()) + return false; + + while (!directories.empty()) { + ++visited_directories; + FileId dir_id = directories.top(); + directories.pop(); + + std::vector<FileId> children; + if (!dir_db_->ListChildren(dir_id, &children)) + return false; + for (std::vector<FileId>::iterator itr = children.begin(); + itr != children.end(); + ++itr) { + // Any directory must not have root directory as child. + if (!*itr) + return false; + + // Check if the child knows the parent as its parent. + FileInfo file_info; + if (!dir_db_->GetFileInfo(*itr, &file_info)) + return false; + if (file_info.parent_id != dir_id) + return false; + + // Check if the parent knows the name of its child correctly. + FileId file_id; + if (!dir_db_->GetChildWithName(dir_id, file_info.name, &file_id) || + file_id != *itr) + return false; + + if (file_info.is_directory()) + directories.push(*itr); + else + ++visited_files; + ++visited_links; + } + } + + // Check if we've visited all database entries. + return num_directories_in_db_ == visited_directories && + num_files_in_db_ == visited_files && + num_hierarchy_links_in_db_ == visited_links; +} + +// Returns true if the given |data_path| contains no parent references ("..") +// and does not refer to special system files. +// This is called in GetFileInfo, AddFileInfo and UpdateFileInfo to +// ensure we're only dealing with valid data paths. +bool VerifyDataPath(const base::FilePath& data_path) { + // |data_path| should not contain any ".." and should be a relative path + // (to the filesystem_data_directory_). + if (data_path.ReferencesParent() || data_path.IsAbsolute()) + return false; + // See if it's not pointing to the special system paths. + const base::FilePath kExcludes[] = { + base::FilePath(kDirectoryDatabaseName), + base::FilePath(storage::FileSystemUsageCache::kUsageFileName), + }; + for (size_t i = 0; i < arraysize(kExcludes); ++i) { + if (data_path == kExcludes[i] || kExcludes[i].IsParent(data_path)) + return false; + } + return true; +} + +} // namespace + +namespace storage { + +SandboxDirectoryDatabase::FileInfo::FileInfo() : parent_id(0) { +} + +SandboxDirectoryDatabase::FileInfo::~FileInfo() { +} + +SandboxDirectoryDatabase::SandboxDirectoryDatabase( + const base::FilePath& filesystem_data_directory, + leveldb::Env* env_override) + : filesystem_data_directory_(filesystem_data_directory), + env_override_(env_override) { +} + +SandboxDirectoryDatabase::~SandboxDirectoryDatabase() { +} + +bool SandboxDirectoryDatabase::GetChildWithName( + FileId parent_id, + const base::FilePath::StringType& name, + FileId* child_id) { + if (!Init(REPAIR_ON_CORRUPTION)) + return false; + DCHECK(child_id); + std::string child_key = GetChildLookupKey(parent_id, name); + std::string child_id_string; + leveldb::Status status = + db_->Get(leveldb::ReadOptions(), child_key, &child_id_string); + if (status.IsNotFound()) + return false; + if (status.ok()) { + if (!base::StringToInt64(child_id_string, child_id)) { + LOG(ERROR) << "Hit database corruption!"; + return false; + } + return true; + } + HandleError(FROM_HERE, status); + return false; +} + +bool SandboxDirectoryDatabase::GetFileWithPath( + const base::FilePath& path, FileId* file_id) { + std::vector<base::FilePath::StringType> components; + VirtualPath::GetComponents(path, &components); + FileId local_id = 0; + std::vector<base::FilePath::StringType>::iterator iter; + for (iter = components.begin(); iter != components.end(); ++iter) { + base::FilePath::StringType name; + name = *iter; + if (name == FILE_PATH_LITERAL("/")) + continue; + if (!GetChildWithName(local_id, name, &local_id)) + return false; + } + *file_id = local_id; + return true; +} + +bool SandboxDirectoryDatabase::ListChildren( + FileId parent_id, std::vector<FileId>* children) { + // Check to add later: fail if parent is a file, at least in debug builds. + if (!Init(REPAIR_ON_CORRUPTION)) + return false; + DCHECK(children); + std::string child_key_prefix = GetChildListingKeyPrefix(parent_id); + + scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions())); + iter->Seek(child_key_prefix); + children->clear(); + while (iter->Valid() && + StartsWithASCII(iter->key().ToString(), child_key_prefix, true)) { + std::string child_id_string = iter->value().ToString(); + FileId child_id; + if (!base::StringToInt64(child_id_string, &child_id)) { + LOG(ERROR) << "Hit database corruption!"; + return false; + } + children->push_back(child_id); + iter->Next(); + } + return true; +} + +bool SandboxDirectoryDatabase::GetFileInfo(FileId file_id, FileInfo* info) { + if (!Init(REPAIR_ON_CORRUPTION)) + return false; + DCHECK(info); + std::string file_key = GetFileLookupKey(file_id); + std::string file_data_string; + leveldb::Status status = + db_->Get(leveldb::ReadOptions(), file_key, &file_data_string); + if (status.ok()) { + bool success = FileInfoFromPickle( + Pickle(file_data_string.data(), file_data_string.length()), info); + if (!success) + return false; + if (!VerifyDataPath(info->data_path)) { + LOG(ERROR) << "Resolved data path is invalid: " + << info->data_path.value(); + return false; + } + return true; + } + // Special-case the root, for databases that haven't been initialized yet. + // Without this, a query for the root's file info, made before creating the + // first file in the database, will fail and confuse callers. + if (status.IsNotFound() && !file_id) { + info->name = base::FilePath::StringType(); + info->data_path = base::FilePath(); + info->modification_time = base::Time::Now(); + info->parent_id = 0; + return true; + } + HandleError(FROM_HERE, status); + return false; +} + +base::File::Error SandboxDirectoryDatabase::AddFileInfo( + const FileInfo& info, FileId* file_id) { + if (!Init(REPAIR_ON_CORRUPTION)) + return base::File::FILE_ERROR_FAILED; + DCHECK(file_id); + std::string child_key = GetChildLookupKey(info.parent_id, info.name); + std::string child_id_string; + leveldb::Status status = + db_->Get(leveldb::ReadOptions(), child_key, &child_id_string); + if (status.ok()) { + LOG(ERROR) << "File exists already!"; + return base::File::FILE_ERROR_EXISTS; + } + if (!status.IsNotFound()) { + HandleError(FROM_HERE, status); + return base::File::FILE_ERROR_NOT_FOUND; + } + + if (!IsDirectory(info.parent_id)) { + LOG(ERROR) << "New parent directory is a file!"; + return base::File::FILE_ERROR_NOT_A_DIRECTORY; + } + + // This would be a fine place to limit the number of files in a directory, if + // we decide to add that restriction. + + FileId temp_id; + if (!GetLastFileId(&temp_id)) + return base::File::FILE_ERROR_FAILED; + ++temp_id; + + leveldb::WriteBatch batch; + if (!AddFileInfoHelper(info, temp_id, &batch)) + return base::File::FILE_ERROR_FAILED; + + batch.Put(LastFileIdKey(), base::Int64ToString(temp_id)); + status = db_->Write(leveldb::WriteOptions(), &batch); + if (!status.ok()) { + HandleError(FROM_HERE, status); + return base::File::FILE_ERROR_FAILED; + } + *file_id = temp_id; + return base::File::FILE_OK; +} + +bool SandboxDirectoryDatabase::RemoveFileInfo(FileId file_id) { + if (!Init(REPAIR_ON_CORRUPTION)) + return false; + leveldb::WriteBatch batch; + if (!RemoveFileInfoHelper(file_id, &batch)) + return false; + leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); + if (!status.ok()) { + HandleError(FROM_HERE, status); + return false; + } + return true; +} + +bool SandboxDirectoryDatabase::UpdateFileInfo( + FileId file_id, const FileInfo& new_info) { + // TODO(ericu): We should also check to see that this doesn't create a loop, + // but perhaps only in a debug build. + if (!Init(REPAIR_ON_CORRUPTION)) + return false; + DCHECK(file_id); // You can't remove the root, ever. Just delete the DB. + FileInfo old_info; + if (!GetFileInfo(file_id, &old_info)) + return false; + if (old_info.parent_id != new_info.parent_id && + !IsDirectory(new_info.parent_id)) + return false; + if (old_info.parent_id != new_info.parent_id || + old_info.name != new_info.name) { + // Check for name clashes. + FileId temp_id; + if (GetChildWithName(new_info.parent_id, new_info.name, &temp_id)) { + LOG(ERROR) << "Name collision on move."; + return false; + } + } + leveldb::WriteBatch batch; + if (!RemoveFileInfoHelper(file_id, &batch) || + !AddFileInfoHelper(new_info, file_id, &batch)) + return false; + leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); + if (!status.ok()) { + HandleError(FROM_HERE, status); + return false; + } + return true; +} + +bool SandboxDirectoryDatabase::UpdateModificationTime( + FileId file_id, const base::Time& modification_time) { + FileInfo info; + if (!GetFileInfo(file_id, &info)) + return false; + info.modification_time = modification_time; + Pickle pickle; + if (!PickleFromFileInfo(info, &pickle)) + return false; + leveldb::Status status = db_->Put( + leveldb::WriteOptions(), + GetFileLookupKey(file_id), + leveldb::Slice(reinterpret_cast<const char *>(pickle.data()), + pickle.size())); + if (!status.ok()) { + HandleError(FROM_HERE, status); + return false; + } + return true; +} + +bool SandboxDirectoryDatabase::OverwritingMoveFile( + FileId src_file_id, FileId dest_file_id) { + FileInfo src_file_info; + FileInfo dest_file_info; + + if (!GetFileInfo(src_file_id, &src_file_info)) + return false; + if (!GetFileInfo(dest_file_id, &dest_file_info)) + return false; + if (src_file_info.is_directory() || dest_file_info.is_directory()) + return false; + leveldb::WriteBatch batch; + // This is the only field that really gets moved over; if you add fields to + // FileInfo, e.g. ctime, they might need to be copied here. + dest_file_info.data_path = src_file_info.data_path; + if (!RemoveFileInfoHelper(src_file_id, &batch)) + return false; + Pickle pickle; + if (!PickleFromFileInfo(dest_file_info, &pickle)) + return false; + batch.Put( + GetFileLookupKey(dest_file_id), + leveldb::Slice(reinterpret_cast<const char *>(pickle.data()), + pickle.size())); + leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); + if (!status.ok()) { + HandleError(FROM_HERE, status); + return false; + } + return true; +} + +bool SandboxDirectoryDatabase::GetNextInteger(int64* next) { + if (!Init(REPAIR_ON_CORRUPTION)) + return false; + DCHECK(next); + std::string int_string; + leveldb::Status status = + db_->Get(leveldb::ReadOptions(), LastIntegerKey(), &int_string); + if (status.ok()) { + int64 temp; + if (!base::StringToInt64(int_string, &temp)) { + LOG(ERROR) << "Hit database corruption!"; + return false; + } + ++temp; + status = db_->Put(leveldb::WriteOptions(), LastIntegerKey(), + base::Int64ToString(temp)); + if (!status.ok()) { + HandleError(FROM_HERE, status); + return false; + } + *next = temp; + return true; + } + if (!status.IsNotFound()) { + HandleError(FROM_HERE, status); + return false; + } + // The database must not yet exist; initialize it. + if (!StoreDefaultValues()) + return false; + + return GetNextInteger(next); +} + +// static +bool SandboxDirectoryDatabase::DestroyDatabase(const base::FilePath& path, + leveldb::Env* env_override) { + std::string name = FilePathToString(path.Append(kDirectoryDatabaseName)); + leveldb::Options options; + if (env_override) + options.env = env_override; + leveldb::Status status = leveldb::DestroyDB(name, options); + if (status.ok()) + return true; + LOG(WARNING) << "Failed to destroy a database with status " << + status.ToString(); + return false; +} + +bool SandboxDirectoryDatabase::Init(RecoveryOption recovery_option) { + if (db_) + return true; + + std::string path = + FilePathToString(filesystem_data_directory_.Append( + kDirectoryDatabaseName)); + leveldb::Options options; + options.max_open_files = 0; // Use minimum. + options.create_if_missing = true; + if (env_override_) + options.env = env_override_; + leveldb::DB* db; + leveldb::Status status = leveldb::DB::Open(options, path, &db); + ReportInitStatus(status); + if (status.ok()) { + db_.reset(db); + return true; + } + HandleError(FROM_HERE, status); + + // Corruption due to missing necessary MANIFEST-* file causes IOError instead + // of Corruption error. + // Try to repair database even when IOError case. + if (!status.IsCorruption() && !status.IsIOError()) + return false; + + switch (recovery_option) { + case FAIL_ON_CORRUPTION: + return false; + case REPAIR_ON_CORRUPTION: + LOG(WARNING) << "Corrupted SandboxDirectoryDatabase detected." + << " Attempting to repair."; + if (RepairDatabase(path)) { + UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel, + DB_REPAIR_SUCCEEDED, DB_REPAIR_MAX); + return true; + } + UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel, + DB_REPAIR_FAILED, DB_REPAIR_MAX); + LOG(WARNING) << "Failed to repair SandboxDirectoryDatabase."; + // fall through + case DELETE_ON_CORRUPTION: + LOG(WARNING) << "Clearing SandboxDirectoryDatabase."; + if (!base::DeleteFile(filesystem_data_directory_, true)) + return false; + if (!base::CreateDirectory(filesystem_data_directory_)) + return false; + return Init(FAIL_ON_CORRUPTION); + } + + NOTREACHED(); + return false; +} + +bool SandboxDirectoryDatabase::RepairDatabase(const std::string& db_path) { + DCHECK(!db_.get()); + leveldb::Options options; + options.max_open_files = 0; // Use minimum. + if (env_override_) + options.env = env_override_; + if (!leveldb::RepairDB(db_path, options).ok()) + return false; + if (!Init(FAIL_ON_CORRUPTION)) + return false; + if (IsFileSystemConsistent()) + return true; + db_.reset(); + return false; +} + +bool SandboxDirectoryDatabase::IsDirectory(FileId file_id) { + FileInfo info; + if (!file_id) + return true; // The root is a directory. + if (!GetFileInfo(file_id, &info)) + return false; + if (!info.is_directory()) + return false; + return true; +} + +bool SandboxDirectoryDatabase::IsFileSystemConsistent() { + if (!Init(FAIL_ON_CORRUPTION)) + return false; + DatabaseCheckHelper helper(this, db_.get(), filesystem_data_directory_); + return helper.IsFileSystemConsistent(); +} + +void SandboxDirectoryDatabase::ReportInitStatus( + const leveldb::Status& status) { + base::Time now = base::Time::Now(); + const base::TimeDelta minimum_interval = + base::TimeDelta::FromHours(kMinimumReportIntervalHours); + if (last_reported_time_ + minimum_interval >= now) + return; + last_reported_time_ = now; + + if (status.ok()) { + UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, + INIT_STATUS_OK, INIT_STATUS_MAX); + } else if (status.IsCorruption()) { + UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, + INIT_STATUS_CORRUPTION, INIT_STATUS_MAX); + } else if (status.IsIOError()) { + UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, + INIT_STATUS_IO_ERROR, INIT_STATUS_MAX); + } else { + UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, + INIT_STATUS_UNKNOWN_ERROR, INIT_STATUS_MAX); + } +} + +bool SandboxDirectoryDatabase::StoreDefaultValues() { + // Verify that this is a totally new database, and initialize it. + scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions())); + iter->SeekToFirst(); + if (iter->Valid()) { // DB was not empty--we shouldn't have been called. + LOG(ERROR) << "File system origin database is corrupt!"; + return false; + } + // This is always the first write into the database. If we ever add a + // version number, it should go in this transaction too. + FileInfo root; + root.parent_id = 0; + root.modification_time = base::Time::Now(); + leveldb::WriteBatch batch; + if (!AddFileInfoHelper(root, 0, &batch)) + return false; + batch.Put(LastFileIdKey(), base::Int64ToString(0)); + batch.Put(LastIntegerKey(), base::Int64ToString(-1)); + leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); + if (!status.ok()) { + HandleError(FROM_HERE, status); + return false; + } + return true; +} + +bool SandboxDirectoryDatabase::GetLastFileId(FileId* file_id) { + if (!Init(REPAIR_ON_CORRUPTION)) + return false; + DCHECK(file_id); + std::string id_string; + leveldb::Status status = + db_->Get(leveldb::ReadOptions(), LastFileIdKey(), &id_string); + if (status.ok()) { + if (!base::StringToInt64(id_string, file_id)) { + LOG(ERROR) << "Hit database corruption!"; + return false; + } + return true; + } + if (!status.IsNotFound()) { + HandleError(FROM_HERE, status); + return false; + } + // The database must not yet exist; initialize it. + if (!StoreDefaultValues()) + return false; + *file_id = 0; + return true; +} + +// This does very few safety checks! +bool SandboxDirectoryDatabase::AddFileInfoHelper( + const FileInfo& info, FileId file_id, leveldb::WriteBatch* batch) { + if (!VerifyDataPath(info.data_path)) { + LOG(ERROR) << "Invalid data path is given: " << info.data_path.value(); + return false; + } + std::string id_string = GetFileLookupKey(file_id); + if (!file_id) { + // The root directory doesn't need to be looked up by path from its parent. + DCHECK(!info.parent_id); + DCHECK(info.data_path.empty()); + } else { + std::string child_key = GetChildLookupKey(info.parent_id, info.name); + batch->Put(child_key, id_string); + } + Pickle pickle; + if (!PickleFromFileInfo(info, &pickle)) + return false; + batch->Put( + id_string, + leveldb::Slice(reinterpret_cast<const char *>(pickle.data()), + pickle.size())); + return true; +} + +// This does very few safety checks! +bool SandboxDirectoryDatabase::RemoveFileInfoHelper( + FileId file_id, leveldb::WriteBatch* batch) { + DCHECK(file_id); // You can't remove the root, ever. Just delete the DB. + FileInfo info; + if (!GetFileInfo(file_id, &info)) + return false; + if (info.data_path.empty()) { // It's a directory + std::vector<FileId> children; + // TODO(ericu): Make a faster is-the-directory-empty check. + if (!ListChildren(file_id, &children)) + return false; + if (children.size()) { + LOG(ERROR) << "Can't remove a directory with children."; + return false; + } + } + batch->Delete(GetChildLookupKey(info.parent_id, info.name)); + batch->Delete(GetFileLookupKey(file_id)); + return true; +} + +void SandboxDirectoryDatabase::HandleError( + const tracked_objects::Location& from_here, + const leveldb::Status& status) { + LOG(ERROR) << "SandboxDirectoryDatabase failed at: " + << from_here.ToString() << " with error: " << status.ToString(); + db_.reset(); +} + +} // namespace storage diff --git a/storage/browser/fileapi/sandbox_directory_database.h b/storage/browser/fileapi/sandbox_directory_database.h new file mode 100644 index 0000000..a966811 --- /dev/null +++ b/storage/browser/fileapi/sandbox_directory_database.h @@ -0,0 +1,135 @@ +// 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. + +#ifndef STORAGE_BROWSER_FILEAPI_SANDBOX_DIRECTORY_DATABASE_H_ +#define STORAGE_BROWSER_FILEAPI_SANDBOX_DIRECTORY_DATABASE_H_ + +#include <string> +#include <vector> + +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "base/time/time.h" +#include "storage/browser/storage_browser_export.h" + +namespace content { +class SandboxDirectoryDatabaseTest; +} + +namespace tracked_objects { +class Location; +} + +namespace leveldb { +class DB; +class Env; +class Status; +class WriteBatch; +} + +namespace storage { + +// This class WILL NOT protect you against producing directory loops, giving an +// empty directory a backing data file, giving two files the same backing file, +// or pointing to a nonexistent backing file. It does no file IO other than +// that involved with talking to its underlying database. It does not create or +// in any way touch real files; it only creates path entries in its database. + +// TODO(ericu): Safe mode, which does more checks such as the above on debug +// builds. +// TODO(ericu): Add a method that will give a unique filename for a data file. +class STORAGE_EXPORT_PRIVATE SandboxDirectoryDatabase { + public: + typedef int64 FileId; + + struct STORAGE_EXPORT_PRIVATE FileInfo { + FileInfo(); + ~FileInfo(); + + bool is_directory() const { + return data_path.empty(); + } + + FileId parent_id; + base::FilePath data_path; + base::FilePath::StringType name; + // This modification time is valid only for directories, not files, as + // FileWriter will get the files out of sync. + // For files, look at the modification time of the underlying data_path. + base::Time modification_time; + }; + + SandboxDirectoryDatabase( + const base::FilePath& filesystem_data_directory, + leveldb::Env* env_override); + ~SandboxDirectoryDatabase(); + + bool GetChildWithName( + FileId parent_id, + const base::FilePath::StringType& name, + FileId* child_id); + bool GetFileWithPath(const base::FilePath& path, FileId* file_id); + // ListChildren will succeed, returning 0 children, if parent_id doesn't + // exist. + bool ListChildren(FileId parent_id, std::vector<FileId>* children); + bool GetFileInfo(FileId file_id, FileInfo* info); + base::File::Error AddFileInfo(const FileInfo& info, FileId* file_id); + bool RemoveFileInfo(FileId file_id); + // This does a full update of the FileInfo, and is what you'd use for moves + // and renames. If you just want to update the modification_time, use + // UpdateModificationTime. + bool UpdateFileInfo(FileId file_id, const FileInfo& info); + bool UpdateModificationTime( + FileId file_id, const base::Time& modification_time); + // This is used for an overwriting move of a file [not a directory] on top of + // another file [also not a directory]; we need to alter two files' info in a + // single transaction to avoid weird backing file references in the event of a + // partial failure. + bool OverwritingMoveFile(FileId src_file_id, FileId dest_file_id); + + // This produces the series 0, 1, 2..., starting at 0 when the underlying + // filesystem is first created, and maintaining state across + // creation/destruction of SandboxDirectoryDatabase objects. + bool GetNextInteger(int64* next); + + bool IsDirectory(FileId file_id); + + // Returns true if the database looks consistent with local filesystem. + bool IsFileSystemConsistent(); + + static bool DestroyDatabase(const base::FilePath& path, + leveldb::Env* env_override); + + private: + enum RecoveryOption { + DELETE_ON_CORRUPTION, + REPAIR_ON_CORRUPTION, + FAIL_ON_CORRUPTION, + }; + + friend class content::SandboxDirectoryDatabaseTest; + friend class ObfuscatedFileUtil; + + bool Init(RecoveryOption recovery_option); + bool RepairDatabase(const std::string& db_path); + void ReportInitStatus(const leveldb::Status& status); + bool StoreDefaultValues(); + bool GetLastFileId(FileId* file_id); + bool AddFileInfoHelper( + const FileInfo& info, FileId file_id, leveldb::WriteBatch* batch); + bool RemoveFileInfoHelper(FileId file_id, leveldb::WriteBatch* batch); + void HandleError(const tracked_objects::Location& from_here, + const leveldb::Status& status); + + const base::FilePath filesystem_data_directory_; + leveldb::Env* env_override_; + scoped_ptr<leveldb::DB> db_; + base::Time last_reported_time_; + DISALLOW_COPY_AND_ASSIGN(SandboxDirectoryDatabase); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_SANDBOX_DIRECTORY_DATABASE_H_ diff --git a/storage/browser/fileapi/sandbox_file_stream_writer.cc b/storage/browser/fileapi/sandbox_file_stream_writer.cc new file mode 100644 index 0000000..d87c66d --- /dev/null +++ b/storage/browser/fileapi/sandbox_file_stream_writer.cc @@ -0,0 +1,247 @@ +// 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 "storage/browser/fileapi/sandbox_file_stream_writer.h" + +#include "base/files/file_util_proxy.h" +#include "base/sequenced_task_runner.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "storage/browser/blob/file_stream_reader.h" +#include "storage/browser/fileapi/file_observers.h" +#include "storage/browser/fileapi/file_stream_writer.h" +#include "storage/browser/fileapi/file_system_context.h" +#include "storage/browser/fileapi/file_system_operation_runner.h" +#include "storage/browser/quota/quota_manager_proxy.h" +#include "storage/common/fileapi/file_system_util.h" + +namespace storage { + +namespace { + +// Adjust the |quota| value in overwriting case (i.e. |file_size| > 0 and +// |file_offset| < |file_size|) to make the remaining quota calculation easier. +// Specifically this widens the quota for overlapping range (so that we can +// simply compare written bytes against the adjusted quota). +int64 AdjustQuotaForOverlap(int64 quota, + int64 file_offset, + int64 file_size) { + DCHECK_LE(file_offset, file_size); + if (quota < 0) + quota = 0; + int64 overlap = file_size - file_offset; + if (kint64max - overlap > quota) + quota += overlap; + return quota; +} + +} // namespace + +SandboxFileStreamWriter::SandboxFileStreamWriter( + FileSystemContext* file_system_context, + const FileSystemURL& url, + int64 initial_offset, + const UpdateObserverList& observers) + : file_system_context_(file_system_context), + url_(url), + initial_offset_(initial_offset), + observers_(observers), + file_size_(0), + total_bytes_written_(0), + allowed_bytes_to_write_(0), + has_pending_operation_(false), + default_quota_(kint64max), + weak_factory_(this) { + DCHECK(url_.is_valid()); +} + +SandboxFileStreamWriter::~SandboxFileStreamWriter() {} + +int SandboxFileStreamWriter::Write( + net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) { + has_pending_operation_ = true; + if (local_file_writer_) + return WriteInternal(buf, buf_len, callback); + + net::CompletionCallback write_task = + base::Bind(&SandboxFileStreamWriter::DidInitializeForWrite, + weak_factory_.GetWeakPtr(), + make_scoped_refptr(buf), buf_len, callback); + file_system_context_->operation_runner()->CreateSnapshotFile( + url_, base::Bind(&SandboxFileStreamWriter::DidCreateSnapshotFile, + weak_factory_.GetWeakPtr(), write_task)); + return net::ERR_IO_PENDING; +} + +int SandboxFileStreamWriter::Cancel(const net::CompletionCallback& callback) { + if (!has_pending_operation_) + return net::ERR_UNEXPECTED; + + DCHECK(!callback.is_null()); + cancel_callback_ = callback; + return net::ERR_IO_PENDING; +} + +int SandboxFileStreamWriter::WriteInternal( + net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) { + // allowed_bytes_to_write could be negative if the file size is + // greater than the current (possibly new) quota. + DCHECK(total_bytes_written_ <= allowed_bytes_to_write_ || + allowed_bytes_to_write_ < 0); + if (total_bytes_written_ >= allowed_bytes_to_write_) { + has_pending_operation_ = false; + return net::ERR_FILE_NO_SPACE; + } + + if (buf_len > allowed_bytes_to_write_ - total_bytes_written_) + buf_len = allowed_bytes_to_write_ - total_bytes_written_; + + DCHECK(local_file_writer_.get()); + const int result = local_file_writer_->Write( + buf, buf_len, + base::Bind(&SandboxFileStreamWriter::DidWrite, weak_factory_.GetWeakPtr(), + callback)); + if (result != net::ERR_IO_PENDING) + has_pending_operation_ = false; + return result; +} + +void SandboxFileStreamWriter::DidCreateSnapshotFile( + const net::CompletionCallback& callback, + base::File::Error file_error, + const base::File::Info& file_info, + const base::FilePath& platform_path, + const scoped_refptr<storage::ShareableFileReference>& file_ref) { + DCHECK(!file_ref.get()); + + if (CancelIfRequested()) + return; + if (file_error != base::File::FILE_OK) { + callback.Run(net::FileErrorToNetError(file_error)); + return; + } + if (file_info.is_directory) { + // We should not be writing to a directory. + callback.Run(net::ERR_ACCESS_DENIED); + return; + } + file_size_ = file_info.size; + if (initial_offset_ > file_size_) { + LOG(ERROR) << initial_offset_ << ", " << file_size_; + // This shouldn't happen as long as we check offset in the renderer. + NOTREACHED(); + initial_offset_ = file_size_; + } + DCHECK(!local_file_writer_.get()); + local_file_writer_.reset(FileStreamWriter::CreateForLocalFile( + file_system_context_->default_file_task_runner(), + platform_path, + initial_offset_, + FileStreamWriter::OPEN_EXISTING_FILE)); + + storage::QuotaManagerProxy* quota_manager_proxy = + file_system_context_->quota_manager_proxy(); + if (!quota_manager_proxy) { + // If we don't have the quota manager or the requested filesystem type + // does not support quota, we should be able to let it go. + allowed_bytes_to_write_ = default_quota_; + callback.Run(net::OK); + return; + } + + DCHECK(quota_manager_proxy->quota_manager()); + quota_manager_proxy->quota_manager()->GetUsageAndQuota( + url_.origin(), + FileSystemTypeToQuotaStorageType(url_.type()), + base::Bind(&SandboxFileStreamWriter::DidGetUsageAndQuota, + weak_factory_.GetWeakPtr(), callback)); +} + +void SandboxFileStreamWriter::DidGetUsageAndQuota( + const net::CompletionCallback& callback, + storage::QuotaStatusCode status, + int64 usage, + int64 quota) { + if (CancelIfRequested()) + return; + if (status != storage::kQuotaStatusOk) { + LOG(WARNING) << "Got unexpected quota error : " << status; + callback.Run(net::ERR_FAILED); + return; + } + + allowed_bytes_to_write_ = quota - usage; + callback.Run(net::OK); +} + +void SandboxFileStreamWriter::DidInitializeForWrite( + net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback, + int init_status) { + if (CancelIfRequested()) + return; + if (init_status != net::OK) { + has_pending_operation_ = false; + callback.Run(init_status); + return; + } + allowed_bytes_to_write_ = AdjustQuotaForOverlap( + allowed_bytes_to_write_, initial_offset_, file_size_); + const int result = WriteInternal(buf, buf_len, callback); + if (result != net::ERR_IO_PENDING) + callback.Run(result); +} + +void SandboxFileStreamWriter::DidWrite( + const net::CompletionCallback& callback, + int write_response) { + DCHECK(has_pending_operation_); + has_pending_operation_ = false; + + if (write_response <= 0) { + if (CancelIfRequested()) + return; + callback.Run(write_response); + return; + } + + if (total_bytes_written_ + write_response + initial_offset_ > file_size_) { + int overlapped = file_size_ - total_bytes_written_ - initial_offset_; + if (overlapped < 0) + overlapped = 0; + observers_.Notify(&FileUpdateObserver::OnUpdate, + MakeTuple(url_, write_response - overlapped)); + } + total_bytes_written_ += write_response; + + if (CancelIfRequested()) + return; + callback.Run(write_response); +} + +bool SandboxFileStreamWriter::CancelIfRequested() { + if (cancel_callback_.is_null()) + return false; + + net::CompletionCallback pending_cancel = cancel_callback_; + has_pending_operation_ = false; + cancel_callback_.Reset(); + pending_cancel.Run(net::OK); + return true; +} + +int SandboxFileStreamWriter::Flush(const net::CompletionCallback& callback) { + DCHECK(!has_pending_operation_); + DCHECK(cancel_callback_.is_null()); + + // Write() is not called yet, so there's nothing to flush. + if (!local_file_writer_) + return net::OK; + + return local_file_writer_->Flush(callback); +} + +} // namespace storage diff --git a/storage/browser/fileapi/sandbox_file_stream_writer.h b/storage/browser/fileapi/sandbox_file_stream_writer.h new file mode 100644 index 0000000..d24e5b9 --- /dev/null +++ b/storage/browser/fileapi/sandbox_file_stream_writer.h @@ -0,0 +1,96 @@ +// 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. + +#ifndef STORAGE_BROWSER_FILEAPI_SANDBOX_FILE_STREAM_WRITER_H_ +#define STORAGE_BROWSER_FILEAPI_SANDBOX_FILE_STREAM_WRITER_H_ + +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "storage/browser/fileapi/file_stream_writer.h" +#include "storage/browser/fileapi/file_system_url.h" +#include "storage/browser/fileapi/task_runner_bound_observer_list.h" +#include "storage/browser/storage_browser_export.h" +#include "storage/common/blob/shareable_file_reference.h" +#include "storage/common/fileapi/file_system_types.h" +#include "storage/common/quota/quota_types.h" +#include "url/gurl.h" + +namespace storage { + +class FileSystemContext; +class FileSystemQuotaUtil; +class FileStreamWriter; + +class STORAGE_EXPORT_PRIVATE SandboxFileStreamWriter + : public NON_EXPORTED_BASE(FileStreamWriter) { + public: + SandboxFileStreamWriter(FileSystemContext* file_system_context, + const FileSystemURL& url, + int64 initial_offset, + const UpdateObserverList& observers); + virtual ~SandboxFileStreamWriter(); + + // FileStreamWriter overrides. + virtual int Write(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) OVERRIDE; + virtual int Cancel(const net::CompletionCallback& callback) OVERRIDE; + virtual int Flush(const net::CompletionCallback& callback) OVERRIDE; + + // Used only by tests. + void set_default_quota(int64 quota) { + default_quota_ = quota; + } + + private: + // Performs quota calculation and calls local_file_writer_->Write(). + int WriteInternal(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback); + + // Callbacks that are chained for the first write. This eventually calls + // WriteInternal. + void DidCreateSnapshotFile( + const net::CompletionCallback& callback, + base::File::Error file_error, + const base::File::Info& file_info, + const base::FilePath& platform_path, + const scoped_refptr<storage::ShareableFileReference>& file_ref); + void DidGetUsageAndQuota(const net::CompletionCallback& callback, + storage::QuotaStatusCode status, + int64 usage, + int64 quota); + void DidInitializeForWrite(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback, + int init_status); + + void DidWrite(const net::CompletionCallback& callback, int write_response); + + // Stops the in-flight operation, calls |cancel_callback_| and returns true + // if there's a pending cancel request. + bool CancelIfRequested(); + + scoped_refptr<FileSystemContext> file_system_context_; + FileSystemURL url_; + int64 initial_offset_; + scoped_ptr<FileStreamWriter> local_file_writer_; + net::CompletionCallback cancel_callback_; + + UpdateObserverList observers_; + + base::FilePath file_path_; + int64 file_size_; + int64 total_bytes_written_; + int64 allowed_bytes_to_write_; + bool has_pending_operation_; + + int64 default_quota_; + + base::WeakPtrFactory<SandboxFileStreamWriter> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(SandboxFileStreamWriter); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_SANDBOX_FILE_STREAM_WRITER_H_ diff --git a/storage/browser/fileapi/sandbox_file_system_backend.cc b/storage/browser/fileapi/sandbox_file_system_backend.cc new file mode 100644 index 0000000..888d4f5 --- /dev/null +++ b/storage/browser/fileapi/sandbox_file_system_backend.cc @@ -0,0 +1,166 @@ +// 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 "storage/browser/fileapi/sandbox_file_system_backend.h" + +#include "base/bind.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/metrics/histogram.h" +#include "base/task_runner_util.h" +#include "storage/browser/blob/file_stream_reader.h" +#include "storage/browser/fileapi/async_file_util_adapter.h" +#include "storage/browser/fileapi/copy_or_move_file_validator.h" +#include "storage/browser/fileapi/file_stream_writer.h" +#include "storage/browser/fileapi/file_system_context.h" +#include "storage/browser/fileapi/file_system_operation.h" +#include "storage/browser/fileapi/file_system_operation_context.h" +#include "storage/browser/fileapi/file_system_options.h" +#include "storage/browser/fileapi/file_system_usage_cache.h" +#include "storage/browser/fileapi/obfuscated_file_util.h" +#include "storage/browser/fileapi/sandbox_file_system_backend_delegate.h" +#include "storage/browser/fileapi/sandbox_quota_observer.h" +#include "storage/browser/quota/quota_manager.h" +#include "storage/common/fileapi/file_system_types.h" +#include "storage/common/fileapi/file_system_util.h" +#include "url/gurl.h" + +using storage::QuotaManagerProxy; +using storage::SpecialStoragePolicy; + +namespace storage { + +SandboxFileSystemBackend::SandboxFileSystemBackend( + SandboxFileSystemBackendDelegate* delegate) + : delegate_(delegate), + enable_temporary_file_system_in_incognito_(false) { +} + +SandboxFileSystemBackend::~SandboxFileSystemBackend() { +} + +bool SandboxFileSystemBackend::CanHandleType(FileSystemType type) const { + return type == kFileSystemTypeTemporary || + type == kFileSystemTypePersistent; +} + +void SandboxFileSystemBackend::Initialize(FileSystemContext* context) { + DCHECK(delegate_); + + // Set quota observers. + delegate_->RegisterQuotaUpdateObserver(storage::kFileSystemTypeTemporary); + delegate_->AddFileAccessObserver( + storage::kFileSystemTypeTemporary, delegate_->quota_observer(), NULL); + + delegate_->RegisterQuotaUpdateObserver(storage::kFileSystemTypePersistent); + delegate_->AddFileAccessObserver( + storage::kFileSystemTypePersistent, delegate_->quota_observer(), NULL); +} + +void SandboxFileSystemBackend::ResolveURL( + const FileSystemURL& url, + OpenFileSystemMode mode, + const OpenFileSystemCallback& callback) { + DCHECK(CanHandleType(url.type())); + DCHECK(delegate_); + if (delegate_->file_system_options().is_incognito() && + !(url.type() == kFileSystemTypeTemporary && + enable_temporary_file_system_in_incognito_)) { + // TODO(kinuko): return an isolated temporary directory. + callback.Run(GURL(), std::string(), base::File::FILE_ERROR_SECURITY); + return; + } + + delegate_->OpenFileSystem(url.origin(), + url.type(), + mode, + callback, + GetFileSystemRootURI(url.origin(), url.type())); +} + +AsyncFileUtil* SandboxFileSystemBackend::GetAsyncFileUtil( + FileSystemType type) { + DCHECK(delegate_); + return delegate_->file_util(); +} + +WatcherManager* SandboxFileSystemBackend::GetWatcherManager( + FileSystemType type) { + return NULL; +} + +CopyOrMoveFileValidatorFactory* +SandboxFileSystemBackend::GetCopyOrMoveFileValidatorFactory( + FileSystemType type, + base::File::Error* error_code) { + DCHECK(error_code); + *error_code = base::File::FILE_OK; + return NULL; +} + +FileSystemOperation* SandboxFileSystemBackend::CreateFileSystemOperation( + const FileSystemURL& url, + FileSystemContext* context, + base::File::Error* error_code) const { + DCHECK(CanHandleType(url.type())); + DCHECK(error_code); + + DCHECK(delegate_); + scoped_ptr<FileSystemOperationContext> operation_context = + delegate_->CreateFileSystemOperationContext(url, context, error_code); + if (!operation_context) + return NULL; + + SpecialStoragePolicy* policy = delegate_->special_storage_policy(); + if (policy && policy->IsStorageUnlimited(url.origin())) + operation_context->set_quota_limit_type(storage::kQuotaLimitTypeUnlimited); + else + operation_context->set_quota_limit_type(storage::kQuotaLimitTypeLimited); + + return FileSystemOperation::Create(url, context, operation_context.Pass()); +} + +bool SandboxFileSystemBackend::SupportsStreaming( + const storage::FileSystemURL& url) const { + return false; +} + +bool SandboxFileSystemBackend::HasInplaceCopyImplementation( + storage::FileSystemType type) const { + return true; +} + +scoped_ptr<storage::FileStreamReader> +SandboxFileSystemBackend::CreateFileStreamReader( + const FileSystemURL& url, + int64 offset, + const base::Time& expected_modification_time, + FileSystemContext* context) const { + DCHECK(CanHandleType(url.type())); + DCHECK(delegate_); + return delegate_->CreateFileStreamReader( + url, offset, expected_modification_time, context); +} + +scoped_ptr<storage::FileStreamWriter> +SandboxFileSystemBackend::CreateFileStreamWriter( + const FileSystemURL& url, + int64 offset, + FileSystemContext* context) const { + DCHECK(CanHandleType(url.type())); + DCHECK(delegate_); + return delegate_->CreateFileStreamWriter(url, offset, context, url.type()); +} + +FileSystemQuotaUtil* SandboxFileSystemBackend::GetQuotaUtil() { + return delegate_; +} + +SandboxFileSystemBackendDelegate::OriginEnumerator* +SandboxFileSystemBackend::CreateOriginEnumerator() { + DCHECK(delegate_); + return delegate_->CreateOriginEnumerator(); +} + +} // namespace storage diff --git a/storage/browser/fileapi/sandbox_file_system_backend.h b/storage/browser/fileapi/sandbox_file_system_backend.h new file mode 100644 index 0000000..1f939a5 --- /dev/null +++ b/storage/browser/fileapi/sandbox_file_system_backend.h @@ -0,0 +1,85 @@ +// 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. + +#ifndef STORAGE_BROWSER_FILEAPI_SANDBOX_FILE_SYSTEM_BACKEND_H_ +#define STORAGE_BROWSER_FILEAPI_SANDBOX_FILE_SYSTEM_BACKEND_H_ + +#include <set> +#include <string> + +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "storage/browser/fileapi/file_system_backend.h" +#include "storage/browser/fileapi/file_system_quota_util.h" +#include "storage/browser/fileapi/sandbox_file_system_backend_delegate.h" +#include "storage/browser/fileapi/task_runner_bound_observer_list.h" +#include "storage/browser/quota/special_storage_policy.h" +#include "storage/browser/storage_browser_export.h" + +namespace storage { + +// TEMPORARY or PERSISTENT filesystems, which are placed under the user's +// profile directory in a sandboxed way. +// This interface also lets one enumerate and remove storage for the origins +// that use the filesystem. +class STORAGE_EXPORT SandboxFileSystemBackend + : public FileSystemBackend { + public: + explicit SandboxFileSystemBackend(SandboxFileSystemBackendDelegate* delegate); + virtual ~SandboxFileSystemBackend(); + + // FileSystemBackend overrides. + virtual bool CanHandleType(FileSystemType type) const OVERRIDE; + virtual void Initialize(FileSystemContext* context) OVERRIDE; + virtual void ResolveURL(const FileSystemURL& url, + OpenFileSystemMode mode, + const OpenFileSystemCallback& callback) OVERRIDE; + virtual AsyncFileUtil* GetAsyncFileUtil(FileSystemType type) OVERRIDE; + virtual WatcherManager* GetWatcherManager(FileSystemType type) OVERRIDE; + virtual CopyOrMoveFileValidatorFactory* GetCopyOrMoveFileValidatorFactory( + FileSystemType type, + base::File::Error* error_code) OVERRIDE; + virtual FileSystemOperation* CreateFileSystemOperation( + const FileSystemURL& url, + FileSystemContext* context, + base::File::Error* error_code) const OVERRIDE; + virtual bool SupportsStreaming(const FileSystemURL& url) const OVERRIDE; + virtual bool HasInplaceCopyImplementation( + storage::FileSystemType type) const OVERRIDE; + virtual scoped_ptr<storage::FileStreamReader> CreateFileStreamReader( + const FileSystemURL& url, + int64 offset, + const base::Time& expected_modification_time, + FileSystemContext* context) const OVERRIDE; + virtual scoped_ptr<FileStreamWriter> CreateFileStreamWriter( + const FileSystemURL& url, + int64 offset, + FileSystemContext* context) const OVERRIDE; + virtual FileSystemQuotaUtil* GetQuotaUtil() OVERRIDE; + + // Returns an origin enumerator of this backend. + // This method can only be called on the file thread. + SandboxFileSystemBackendDelegate::OriginEnumerator* CreateOriginEnumerator(); + + void set_enable_temporary_file_system_in_incognito(bool enable) { + enable_temporary_file_system_in_incognito_ = enable; + } + bool enable_temporary_file_system_in_incognito() const { + return enable_temporary_file_system_in_incognito_; + } + + + private: + SandboxFileSystemBackendDelegate* delegate_; // Not owned. + + bool enable_temporary_file_system_in_incognito_; + + DISALLOW_COPY_AND_ASSIGN(SandboxFileSystemBackend); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_SANDBOX_FILE_SYSTEM_BACKEND_H_ diff --git a/storage/browser/fileapi/sandbox_file_system_backend_delegate.cc b/storage/browser/fileapi/sandbox_file_system_backend_delegate.cc new file mode 100644 index 0000000..a5b1f63 --- /dev/null +++ b/storage/browser/fileapi/sandbox_file_system_backend_delegate.cc @@ -0,0 +1,673 @@ +// Copyright 2013 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 "storage/browser/fileapi/sandbox_file_system_backend_delegate.h" + +#include <vector> + +#include "base/command_line.h" +#include "base/files/file_util.h" +#include "base/metrics/histogram.h" +#include "base/stl_util.h" +#include "base/task_runner_util.h" +#include "net/base/net_util.h" +#include "storage/browser/blob/file_stream_reader.h" +#include "storage/browser/fileapi/async_file_util_adapter.h" +#include "storage/browser/fileapi/file_system_context.h" +#include "storage/browser/fileapi/file_system_operation_context.h" +#include "storage/browser/fileapi/file_system_url.h" +#include "storage/browser/fileapi/file_system_usage_cache.h" +#include "storage/browser/fileapi/obfuscated_file_util.h" +#include "storage/browser/fileapi/quota/quota_backend_impl.h" +#include "storage/browser/fileapi/quota/quota_reservation.h" +#include "storage/browser/fileapi/quota/quota_reservation_manager.h" +#include "storage/browser/fileapi/sandbox_file_stream_writer.h" +#include "storage/browser/fileapi/sandbox_file_system_backend.h" +#include "storage/browser/fileapi/sandbox_quota_observer.h" +#include "storage/browser/quota/quota_manager_proxy.h" +#include "storage/common/fileapi/file_system_util.h" + +namespace storage { + +namespace { + +const char kTemporaryOriginsCountLabel[] = "FileSystem.TemporaryOriginsCount"; +const char kPersistentOriginsCountLabel[] = "FileSystem.PersistentOriginsCount"; + +const char kOpenFileSystemLabel[] = "FileSystem.OpenFileSystem"; +const char kOpenFileSystemDetailLabel[] = "FileSystem.OpenFileSystemDetail"; +const char kOpenFileSystemDetailNonThrottledLabel[] = + "FileSystem.OpenFileSystemDetailNonthrottled"; +int64 kMinimumStatsCollectionIntervalHours = 1; + +// For type directory names in ObfuscatedFileUtil. +// TODO(kinuko,nhiroki): Each type string registration should be done +// via its own backend. +const char kTemporaryDirectoryName[] = "t"; +const char kPersistentDirectoryName[] = "p"; +const char kSyncableDirectoryName[] = "s"; + +const char* kPrepopulateTypes[] = { + kPersistentDirectoryName, + kTemporaryDirectoryName +}; + +enum FileSystemError { + kOK = 0, + kIncognito, + kInvalidSchemeError, + kCreateDirectoryError, + kNotFound, + kUnknownError, + kFileSystemErrorMax, +}; + +// Restricted names. +// http://dev.w3.org/2009/dap/file-system/file-dir-sys.html#naming-restrictions +const base::FilePath::CharType* const kRestrictedNames[] = { + FILE_PATH_LITERAL("."), FILE_PATH_LITERAL(".."), +}; + +// Restricted chars. +const base::FilePath::CharType kRestrictedChars[] = { + FILE_PATH_LITERAL('/'), FILE_PATH_LITERAL('\\'), +}; + +std::string GetTypeStringForURL(const FileSystemURL& url) { + return SandboxFileSystemBackendDelegate::GetTypeString(url.type()); +} + +std::set<std::string> GetKnownTypeStrings() { + std::set<std::string> known_type_strings; + known_type_strings.insert(kTemporaryDirectoryName); + known_type_strings.insert(kPersistentDirectoryName); + known_type_strings.insert(kSyncableDirectoryName); + return known_type_strings; +} + +class ObfuscatedOriginEnumerator + : public SandboxFileSystemBackendDelegate::OriginEnumerator { + public: + explicit ObfuscatedOriginEnumerator(ObfuscatedFileUtil* file_util) { + enum_.reset(file_util->CreateOriginEnumerator()); + } + virtual ~ObfuscatedOriginEnumerator() {} + + virtual GURL Next() OVERRIDE { + return enum_->Next(); + } + + virtual bool HasFileSystemType(FileSystemType type) const OVERRIDE { + return enum_->HasTypeDirectory( + SandboxFileSystemBackendDelegate::GetTypeString(type)); + } + + private: + scoped_ptr<ObfuscatedFileUtil::AbstractOriginEnumerator> enum_; +}; + +void OpenFileSystemOnFileTaskRunner( + ObfuscatedFileUtil* file_util, + const GURL& origin_url, + FileSystemType type, + OpenFileSystemMode mode, + base::File::Error* error_ptr) { + DCHECK(error_ptr); + const bool create = (mode == OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT); + file_util->GetDirectoryForOriginAndType( + origin_url, SandboxFileSystemBackendDelegate::GetTypeString(type), + create, error_ptr); + if (*error_ptr != base::File::FILE_OK) { + UMA_HISTOGRAM_ENUMERATION(kOpenFileSystemLabel, + kCreateDirectoryError, + kFileSystemErrorMax); + } else { + UMA_HISTOGRAM_ENUMERATION(kOpenFileSystemLabel, kOK, kFileSystemErrorMax); + } + // The reference of file_util will be derefed on the FILE thread + // when the storage of this callback gets deleted regardless of whether + // this method is called or not. +} + +void DidOpenFileSystem( + base::WeakPtr<SandboxFileSystemBackendDelegate> delegate, + const base::Callback<void(base::File::Error error)>& callback, + base::File::Error* error) { + if (delegate.get()) + delegate.get()->CollectOpenFileSystemMetrics(*error); + callback.Run(*error); +} + +template <typename T> +void DeleteSoon(base::SequencedTaskRunner* runner, T* ptr) { + if (!runner->DeleteSoon(FROM_HERE, ptr)) + delete ptr; +} + +} // namespace + +const base::FilePath::CharType +SandboxFileSystemBackendDelegate::kFileSystemDirectory[] = + FILE_PATH_LITERAL("File System"); + +// static +std::string SandboxFileSystemBackendDelegate::GetTypeString( + FileSystemType type) { + switch (type) { + case kFileSystemTypeTemporary: + return kTemporaryDirectoryName; + case kFileSystemTypePersistent: + return kPersistentDirectoryName; + case kFileSystemTypeSyncable: + case kFileSystemTypeSyncableForInternalSync: + return kSyncableDirectoryName; + case kFileSystemTypeUnknown: + default: + NOTREACHED() << "Unknown filesystem type requested:" << type; + return std::string(); + } +} + +SandboxFileSystemBackendDelegate::SandboxFileSystemBackendDelegate( + storage::QuotaManagerProxy* quota_manager_proxy, + base::SequencedTaskRunner* file_task_runner, + const base::FilePath& profile_path, + storage::SpecialStoragePolicy* special_storage_policy, + const FileSystemOptions& file_system_options) + : file_task_runner_(file_task_runner), + sandbox_file_util_(new AsyncFileUtilAdapter( + new ObfuscatedFileUtil(special_storage_policy, + profile_path.Append(kFileSystemDirectory), + file_system_options.env_override(), + file_task_runner, + base::Bind(&GetTypeStringForURL), + GetKnownTypeStrings(), + this))), + file_system_usage_cache_(new FileSystemUsageCache(file_task_runner)), + quota_observer_(new SandboxQuotaObserver(quota_manager_proxy, + file_task_runner, + obfuscated_file_util(), + usage_cache())), + quota_reservation_manager_(new QuotaReservationManager( + scoped_ptr<QuotaReservationManager::QuotaBackend>( + new QuotaBackendImpl(file_task_runner_.get(), + obfuscated_file_util(), + usage_cache(), + quota_manager_proxy)))), + special_storage_policy_(special_storage_policy), + file_system_options_(file_system_options), + is_filesystem_opened_(false), + weak_factory_(this) { + // Prepopulate database only if it can run asynchronously (i.e. the current + // thread is not file_task_runner). Usually this is the case but may not + // in test code. + if (!file_system_options.is_incognito() && + !file_task_runner_->RunsTasksOnCurrentThread()) { + std::vector<std::string> types_to_prepopulate( + &kPrepopulateTypes[0], + &kPrepopulateTypes[arraysize(kPrepopulateTypes)]); + file_task_runner_->PostTask( + FROM_HERE, + base::Bind(&ObfuscatedFileUtil::MaybePrepopulateDatabase, + base::Unretained(obfuscated_file_util()), + types_to_prepopulate)); + } +} + +SandboxFileSystemBackendDelegate::~SandboxFileSystemBackendDelegate() { + io_thread_checker_.DetachFromThread(); + + if (!file_task_runner_->RunsTasksOnCurrentThread()) { + DeleteSoon(file_task_runner_.get(), quota_reservation_manager_.release()); + DeleteSoon(file_task_runner_.get(), sandbox_file_util_.release()); + DeleteSoon(file_task_runner_.get(), quota_observer_.release()); + DeleteSoon(file_task_runner_.get(), file_system_usage_cache_.release()); + } +} + +SandboxFileSystemBackendDelegate::OriginEnumerator* +SandboxFileSystemBackendDelegate::CreateOriginEnumerator() { + return new ObfuscatedOriginEnumerator(obfuscated_file_util()); +} + +base::FilePath +SandboxFileSystemBackendDelegate::GetBaseDirectoryForOriginAndType( + const GURL& origin_url, + FileSystemType type, + bool create) { + base::File::Error error = base::File::FILE_OK; + base::FilePath path = obfuscated_file_util()->GetDirectoryForOriginAndType( + origin_url, GetTypeString(type), create, &error); + if (error != base::File::FILE_OK) + return base::FilePath(); + return path; +} + +void SandboxFileSystemBackendDelegate::OpenFileSystem( + const GURL& origin_url, + FileSystemType type, + OpenFileSystemMode mode, + const OpenFileSystemCallback& callback, + const GURL& root_url) { + if (!IsAllowedScheme(origin_url)) { + callback.Run(GURL(), std::string(), base::File::FILE_ERROR_SECURITY); + return; + } + + std::string name = GetFileSystemName(origin_url, type); + + base::File::Error* error_ptr = new base::File::Error; + file_task_runner_->PostTaskAndReply( + FROM_HERE, + base::Bind(&OpenFileSystemOnFileTaskRunner, + obfuscated_file_util(), origin_url, type, mode, + base::Unretained(error_ptr)), + base::Bind(&DidOpenFileSystem, + weak_factory_.GetWeakPtr(), + base::Bind(callback, root_url, name), + base::Owned(error_ptr))); + + io_thread_checker_.DetachFromThread(); + is_filesystem_opened_ = true; +} + +scoped_ptr<FileSystemOperationContext> +SandboxFileSystemBackendDelegate::CreateFileSystemOperationContext( + const FileSystemURL& url, + FileSystemContext* context, + base::File::Error* error_code) const { + if (!IsAccessValid(url)) { + *error_code = base::File::FILE_ERROR_SECURITY; + return scoped_ptr<FileSystemOperationContext>(); + } + + const UpdateObserverList* update_observers = GetUpdateObservers(url.type()); + const ChangeObserverList* change_observers = GetChangeObservers(url.type()); + DCHECK(update_observers); + + scoped_ptr<FileSystemOperationContext> operation_context( + new FileSystemOperationContext(context)); + operation_context->set_update_observers(*update_observers); + operation_context->set_change_observers( + change_observers ? *change_observers : ChangeObserverList()); + + return operation_context.Pass(); +} + +scoped_ptr<storage::FileStreamReader> +SandboxFileSystemBackendDelegate::CreateFileStreamReader( + const FileSystemURL& url, + int64 offset, + const base::Time& expected_modification_time, + FileSystemContext* context) const { + if (!IsAccessValid(url)) + return scoped_ptr<storage::FileStreamReader>(); + return scoped_ptr<storage::FileStreamReader>( + storage::FileStreamReader::CreateForFileSystemFile( + context, url, offset, expected_modification_time)); +} + +scoped_ptr<FileStreamWriter> +SandboxFileSystemBackendDelegate::CreateFileStreamWriter( + const FileSystemURL& url, + int64 offset, + FileSystemContext* context, + FileSystemType type) const { + if (!IsAccessValid(url)) + return scoped_ptr<FileStreamWriter>(); + const UpdateObserverList* observers = GetUpdateObservers(type); + DCHECK(observers); + return scoped_ptr<FileStreamWriter>( + new SandboxFileStreamWriter(context, url, offset, *observers)); +} + +base::File::Error +SandboxFileSystemBackendDelegate::DeleteOriginDataOnFileTaskRunner( + FileSystemContext* file_system_context, + storage::QuotaManagerProxy* proxy, + const GURL& origin_url, + FileSystemType type) { + DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); + int64 usage = GetOriginUsageOnFileTaskRunner( + file_system_context, origin_url, type); + usage_cache()->CloseCacheFiles(); + bool result = obfuscated_file_util()->DeleteDirectoryForOriginAndType( + origin_url, GetTypeString(type)); + if (result && proxy) { + proxy->NotifyStorageModified(storage::QuotaClient::kFileSystem, + origin_url, + FileSystemTypeToQuotaStorageType(type), + -usage); + } + + if (result) + return base::File::FILE_OK; + return base::File::FILE_ERROR_FAILED; +} + +void SandboxFileSystemBackendDelegate::GetOriginsForTypeOnFileTaskRunner( + FileSystemType type, std::set<GURL>* origins) { + DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); + DCHECK(origins); + scoped_ptr<OriginEnumerator> enumerator(CreateOriginEnumerator()); + GURL origin; + while (!(origin = enumerator->Next()).is_empty()) { + if (enumerator->HasFileSystemType(type)) + origins->insert(origin); + } + switch (type) { + case kFileSystemTypeTemporary: + UMA_HISTOGRAM_COUNTS(kTemporaryOriginsCountLabel, origins->size()); + break; + case kFileSystemTypePersistent: + UMA_HISTOGRAM_COUNTS(kPersistentOriginsCountLabel, origins->size()); + break; + default: + break; + } +} + +void SandboxFileSystemBackendDelegate::GetOriginsForHostOnFileTaskRunner( + FileSystemType type, const std::string& host, + std::set<GURL>* origins) { + DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); + DCHECK(origins); + scoped_ptr<OriginEnumerator> enumerator(CreateOriginEnumerator()); + GURL origin; + while (!(origin = enumerator->Next()).is_empty()) { + if (host == net::GetHostOrSpecFromURL(origin) && + enumerator->HasFileSystemType(type)) + origins->insert(origin); + } +} + +int64 SandboxFileSystemBackendDelegate::GetOriginUsageOnFileTaskRunner( + FileSystemContext* file_system_context, + const GURL& origin_url, + FileSystemType type) { + DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); + + // Don't use usage cache and return recalculated usage for sticky invalidated + // origins. + if (ContainsKey(sticky_dirty_origins_, std::make_pair(origin_url, type))) + return RecalculateUsage(file_system_context, origin_url, type); + + base::FilePath base_path = + GetBaseDirectoryForOriginAndType(origin_url, type, false); + if (base_path.empty() || !base::DirectoryExists(base_path)) + return 0; + base::FilePath usage_file_path = + base_path.Append(FileSystemUsageCache::kUsageFileName); + + bool is_valid = usage_cache()->IsValid(usage_file_path); + uint32 dirty_status = 0; + bool dirty_status_available = + usage_cache()->GetDirty(usage_file_path, &dirty_status); + bool visited = !visited_origins_.insert(origin_url).second; + if (is_valid && (dirty_status == 0 || (dirty_status_available && visited))) { + // The usage cache is clean (dirty == 0) or the origin is already + // initialized and running. Read the cache file to get the usage. + int64 usage = 0; + return usage_cache()->GetUsage(usage_file_path, &usage) ? usage : -1; + } + // The usage cache has not been initialized or the cache is dirty. + // Get the directory size now and update the cache. + usage_cache()->Delete(usage_file_path); + + int64 usage = RecalculateUsage(file_system_context, origin_url, type); + + // This clears the dirty flag too. + usage_cache()->UpdateUsage(usage_file_path, usage); + return usage; +} + +scoped_refptr<QuotaReservation> +SandboxFileSystemBackendDelegate::CreateQuotaReservationOnFileTaskRunner( + const GURL& origin, + FileSystemType type) { + DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); + DCHECK(quota_reservation_manager_); + return quota_reservation_manager_->CreateReservation(origin, type); +} + +void SandboxFileSystemBackendDelegate::AddFileUpdateObserver( + FileSystemType type, + FileUpdateObserver* observer, + base::SequencedTaskRunner* task_runner) { + DCHECK(!is_filesystem_opened_ || io_thread_checker_.CalledOnValidThread()); + update_observers_[type] = + update_observers_[type].AddObserver(observer, task_runner); +} + +void SandboxFileSystemBackendDelegate::AddFileChangeObserver( + FileSystemType type, + FileChangeObserver* observer, + base::SequencedTaskRunner* task_runner) { + DCHECK(!is_filesystem_opened_ || io_thread_checker_.CalledOnValidThread()); + change_observers_[type] = + change_observers_[type].AddObserver(observer, task_runner); +} + +void SandboxFileSystemBackendDelegate::AddFileAccessObserver( + FileSystemType type, + FileAccessObserver* observer, + base::SequencedTaskRunner* task_runner) { + DCHECK(!is_filesystem_opened_ || io_thread_checker_.CalledOnValidThread()); + access_observers_[type] = + access_observers_[type].AddObserver(observer, task_runner); +} + +const UpdateObserverList* SandboxFileSystemBackendDelegate::GetUpdateObservers( + FileSystemType type) const { + std::map<FileSystemType, UpdateObserverList>::const_iterator iter = + update_observers_.find(type); + if (iter == update_observers_.end()) + return NULL; + return &iter->second; +} + +const ChangeObserverList* SandboxFileSystemBackendDelegate::GetChangeObservers( + FileSystemType type) const { + std::map<FileSystemType, ChangeObserverList>::const_iterator iter = + change_observers_.find(type); + if (iter == change_observers_.end()) + return NULL; + return &iter->second; +} + +const AccessObserverList* SandboxFileSystemBackendDelegate::GetAccessObservers( + FileSystemType type) const { + std::map<FileSystemType, AccessObserverList>::const_iterator iter = + access_observers_.find(type); + if (iter == access_observers_.end()) + return NULL; + return &iter->second; +} + +void SandboxFileSystemBackendDelegate::RegisterQuotaUpdateObserver( + FileSystemType type) { + AddFileUpdateObserver(type, quota_observer_.get(), file_task_runner_.get()); +} + +void SandboxFileSystemBackendDelegate::InvalidateUsageCache( + const GURL& origin, + FileSystemType type) { + base::File::Error error = base::File::FILE_OK; + base::FilePath usage_file_path = GetUsageCachePathForOriginAndType( + obfuscated_file_util(), origin, type, &error); + if (error != base::File::FILE_OK) + return; + usage_cache()->IncrementDirty(usage_file_path); +} + +void SandboxFileSystemBackendDelegate::StickyInvalidateUsageCache( + const GURL& origin, + FileSystemType type) { + sticky_dirty_origins_.insert(std::make_pair(origin, type)); + quota_observer()->SetUsageCacheEnabled(origin, type, false); + InvalidateUsageCache(origin, type); +} + +FileSystemFileUtil* SandboxFileSystemBackendDelegate::sync_file_util() { + return static_cast<AsyncFileUtilAdapter*>(file_util())->sync_file_util(); +} + +bool SandboxFileSystemBackendDelegate::IsAccessValid( + const FileSystemURL& url) const { + if (!IsAllowedScheme(url.origin())) + return false; + + if (url.path().ReferencesParent()) + return false; + + // Return earlier if the path is '/', because VirtualPath::BaseName() + // returns '/' for '/' and we fail the "basename != '/'" check below. + // (We exclude '.' because it's disallowed by spec.) + if (VirtualPath::IsRootPath(url.path()) && + url.path() != base::FilePath(base::FilePath::kCurrentDirectory)) + return true; + + // Restricted names specified in + // http://dev.w3.org/2009/dap/file-system/file-dir-sys.html#naming-restrictions + base::FilePath filename = VirtualPath::BaseName(url.path()); + // See if the name is allowed to create. + for (size_t i = 0; i < arraysize(kRestrictedNames); ++i) { + if (filename.value() == kRestrictedNames[i]) + return false; + } + for (size_t i = 0; i < arraysize(kRestrictedChars); ++i) { + if (filename.value().find(kRestrictedChars[i]) != + base::FilePath::StringType::npos) + return false; + } + + return true; +} + +bool SandboxFileSystemBackendDelegate::IsAllowedScheme(const GURL& url) const { + // Basically we only accept http or https. We allow file:// URLs + // only if --allow-file-access-from-files flag is given. + if (url.SchemeIsHTTPOrHTTPS()) + return true; + if (url.SchemeIsFileSystem()) + return url.inner_url() && IsAllowedScheme(*url.inner_url()); + + for (size_t i = 0; + i < file_system_options_.additional_allowed_schemes().size(); + ++i) { + if (url.SchemeIs( + file_system_options_.additional_allowed_schemes()[i].c_str())) + return true; + } + return false; +} + +base::FilePath +SandboxFileSystemBackendDelegate::GetUsageCachePathForOriginAndType( + const GURL& origin_url, + FileSystemType type) { + base::File::Error error; + base::FilePath path = GetUsageCachePathForOriginAndType( + obfuscated_file_util(), origin_url, type, &error); + if (error != base::File::FILE_OK) + return base::FilePath(); + return path; +} + +// static +base::FilePath +SandboxFileSystemBackendDelegate::GetUsageCachePathForOriginAndType( + ObfuscatedFileUtil* sandbox_file_util, + const GURL& origin_url, + FileSystemType type, + base::File::Error* error_out) { + DCHECK(error_out); + *error_out = base::File::FILE_OK; + base::FilePath base_path = sandbox_file_util->GetDirectoryForOriginAndType( + origin_url, GetTypeString(type), false /* create */, error_out); + if (*error_out != base::File::FILE_OK) + return base::FilePath(); + return base_path.Append(FileSystemUsageCache::kUsageFileName); +} + +int64 SandboxFileSystemBackendDelegate::RecalculateUsage( + FileSystemContext* context, + const GURL& origin, + FileSystemType type) { + FileSystemOperationContext operation_context(context); + FileSystemURL url = context->CreateCrackedFileSystemURL( + origin, type, base::FilePath()); + scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> enumerator( + obfuscated_file_util()->CreateFileEnumerator( + &operation_context, url, true)); + + base::FilePath file_path_each; + int64 usage = 0; + + while (!(file_path_each = enumerator->Next()).empty()) { + usage += enumerator->Size(); + usage += ObfuscatedFileUtil::ComputeFilePathCost(file_path_each); + } + + return usage; +} + +void SandboxFileSystemBackendDelegate::CollectOpenFileSystemMetrics( + base::File::Error error_code) { + base::Time now = base::Time::Now(); + bool throttled = now < next_release_time_for_open_filesystem_stat_; + if (!throttled) { + next_release_time_for_open_filesystem_stat_ = + now + base::TimeDelta::FromHours(kMinimumStatsCollectionIntervalHours); + } + +#define REPORT(report_value) \ + UMA_HISTOGRAM_ENUMERATION(kOpenFileSystemDetailLabel, \ + (report_value), \ + kFileSystemErrorMax); \ + if (!throttled) { \ + UMA_HISTOGRAM_ENUMERATION(kOpenFileSystemDetailNonThrottledLabel, \ + (report_value), \ + kFileSystemErrorMax); \ + } + + switch (error_code) { + case base::File::FILE_OK: + REPORT(kOK); + break; + case base::File::FILE_ERROR_INVALID_URL: + REPORT(kInvalidSchemeError); + break; + case base::File::FILE_ERROR_NOT_FOUND: + REPORT(kNotFound); + break; + case base::File::FILE_ERROR_FAILED: + default: + REPORT(kUnknownError); + break; + } +#undef REPORT +} + +ObfuscatedFileUtil* SandboxFileSystemBackendDelegate::obfuscated_file_util() { + return static_cast<ObfuscatedFileUtil*>(sync_file_util()); +} + +// Declared in obfuscated_file_util.h. +// static +ObfuscatedFileUtil* ObfuscatedFileUtil::CreateForTesting( + storage::SpecialStoragePolicy* special_storage_policy, + const base::FilePath& file_system_directory, + leveldb::Env* env_override, + base::SequencedTaskRunner* file_task_runner) { + return new ObfuscatedFileUtil(special_storage_policy, + file_system_directory, + env_override, + file_task_runner, + base::Bind(&GetTypeStringForURL), + GetKnownTypeStrings(), + NULL); +} + +} // namespace storage diff --git a/storage/browser/fileapi/sandbox_file_system_backend_delegate.h b/storage/browser/fileapi/sandbox_file_system_backend_delegate.h new file mode 100644 index 0000000..d93bada --- /dev/null +++ b/storage/browser/fileapi/sandbox_file_system_backend_delegate.h @@ -0,0 +1,260 @@ +// Copyright 2013 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. + +#ifndef STORAGE_BROWSER_FILEAPI_SANDBOX_FILE_SYSTEM_BACKEND_DELEGATE_H_ +#define STORAGE_BROWSER_FILEAPI_SANDBOX_FILE_SYSTEM_BACKEND_DELEGATE_H_ + +#include <map> +#include <set> +#include <string> +#include <utility> + +#include "base/files/file_path.h" +#include "base/gtest_prod_util.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread_checker.h" +#include "base/time/time.h" +#include "storage/browser/fileapi/file_system_backend.h" +#include "storage/browser/fileapi/file_system_options.h" +#include "storage/browser/fileapi/file_system_quota_util.h" +#include "storage/browser/storage_browser_export.h" + +namespace base { +class SequencedTaskRunner; +} + +namespace content { +class SandboxFileSystemBackendDelegateTest; +class SandboxFileSystemTestHelper; +} + +namespace storage { +class QuotaManagerProxy; +class SpecialStoragePolicy; +} + +namespace storage { +class FileStreamReader; +} + +namespace storage { + +class AsyncFileUtil; +class FileStreamWriter; +class FileSystemFileUtil; +class FileSystemOperationContext; +class FileSystemURL; +class FileSystemUsageCache; +class ObfuscatedFileUtil; +class QuotaReservationManager; +class SandboxFileSystemBackend; +class SandboxQuotaObserver; + +// Delegate implementation of the some methods in Sandbox/SyncFileSystemBackend. +// An instance of this class is created and owned by FileSystemContext. +class STORAGE_EXPORT SandboxFileSystemBackendDelegate + : public FileSystemQuotaUtil { + public: + typedef FileSystemBackend::OpenFileSystemCallback OpenFileSystemCallback; + + // The FileSystem directory name. + static const base::FilePath::CharType kFileSystemDirectory[]; + + // Origin enumerator interface. + // An instance of this interface is assumed to be called on the file thread. + class OriginEnumerator { + public: + virtual ~OriginEnumerator() {} + + // Returns the next origin. Returns empty if there are no more origins. + virtual GURL Next() = 0; + + // Returns the current origin's information. + virtual bool HasFileSystemType(FileSystemType type) const = 0; + }; + + // Returns the type directory name in sandbox directory for given |type|. + static std::string GetTypeString(FileSystemType type); + + SandboxFileSystemBackendDelegate( + storage::QuotaManagerProxy* quota_manager_proxy, + base::SequencedTaskRunner* file_task_runner, + const base::FilePath& profile_path, + storage::SpecialStoragePolicy* special_storage_policy, + const FileSystemOptions& file_system_options); + + virtual ~SandboxFileSystemBackendDelegate(); + + // Returns an origin enumerator of sandbox filesystem. + // This method can only be called on the file thread. + OriginEnumerator* CreateOriginEnumerator(); + + // Gets a base directory path of the sandboxed filesystem that is + // specified by |origin_url| and |type|. + // (The path is similar to the origin's root path but doesn't contain + // the 'unique' part.) + // Returns an empty path if the given type is invalid. + // This method can only be called on the file thread. + base::FilePath GetBaseDirectoryForOriginAndType( + const GURL& origin_url, + FileSystemType type, + bool create); + + // FileSystemBackend helpers. + void OpenFileSystem( + const GURL& origin_url, + FileSystemType type, + OpenFileSystemMode mode, + const OpenFileSystemCallback& callback, + const GURL& root_url); + scoped_ptr<FileSystemOperationContext> CreateFileSystemOperationContext( + const FileSystemURL& url, + FileSystemContext* context, + base::File::Error* error_code) const; + scoped_ptr<storage::FileStreamReader> CreateFileStreamReader( + const FileSystemURL& url, + int64 offset, + const base::Time& expected_modification_time, + FileSystemContext* context) const; + scoped_ptr<FileStreamWriter> CreateFileStreamWriter( + const FileSystemURL& url, + int64 offset, + FileSystemContext* context, + FileSystemType type) const; + + // FileSystemQuotaUtil overrides. + virtual base::File::Error DeleteOriginDataOnFileTaskRunner( + FileSystemContext* context, + storage::QuotaManagerProxy* proxy, + const GURL& origin_url, + FileSystemType type) OVERRIDE; + virtual void GetOriginsForTypeOnFileTaskRunner( + FileSystemType type, + std::set<GURL>* origins) OVERRIDE; + virtual void GetOriginsForHostOnFileTaskRunner( + FileSystemType type, + const std::string& host, + std::set<GURL>* origins) OVERRIDE; + virtual int64 GetOriginUsageOnFileTaskRunner( + FileSystemContext* context, + const GURL& origin_url, + FileSystemType type) OVERRIDE; + virtual scoped_refptr<QuotaReservation> + CreateQuotaReservationOnFileTaskRunner( + const GURL& origin_url, + FileSystemType type) OVERRIDE; + virtual void AddFileUpdateObserver( + FileSystemType type, + FileUpdateObserver* observer, + base::SequencedTaskRunner* task_runner) OVERRIDE; + virtual void AddFileChangeObserver( + FileSystemType type, + FileChangeObserver* observer, + base::SequencedTaskRunner* task_runner) OVERRIDE; + virtual void AddFileAccessObserver( + FileSystemType type, + FileAccessObserver* observer, + base::SequencedTaskRunner* task_runner) OVERRIDE; + virtual const UpdateObserverList* GetUpdateObservers( + FileSystemType type) const OVERRIDE; + virtual const ChangeObserverList* GetChangeObservers( + FileSystemType type) const OVERRIDE; + virtual const AccessObserverList* GetAccessObservers( + FileSystemType type) const OVERRIDE; + + // Registers quota observer for file updates on filesystem of |type|. + void RegisterQuotaUpdateObserver(FileSystemType type); + + void InvalidateUsageCache(const GURL& origin_url, + FileSystemType type); + void StickyInvalidateUsageCache(const GURL& origin_url, + FileSystemType type); + + void CollectOpenFileSystemMetrics(base::File::Error error_code); + + base::SequencedTaskRunner* file_task_runner() { + return file_task_runner_.get(); + } + + AsyncFileUtil* file_util() { return sandbox_file_util_.get(); } + FileSystemUsageCache* usage_cache() { return file_system_usage_cache_.get(); } + SandboxQuotaObserver* quota_observer() { return quota_observer_.get(); } + + storage::SpecialStoragePolicy* special_storage_policy() { + return special_storage_policy_.get(); + } + + const FileSystemOptions& file_system_options() const { + return file_system_options_; + } + + FileSystemFileUtil* sync_file_util(); + + private: + friend class QuotaBackendImpl; + friend class SandboxQuotaObserver; + friend class content::SandboxFileSystemBackendDelegateTest; + friend class content::SandboxFileSystemTestHelper; + + // Performs API-specific validity checks on the given path |url|. + // Returns true if access to |url| is valid in this filesystem. + bool IsAccessValid(const FileSystemURL& url) const; + + // Returns true if the given |url|'s scheme is allowed to access + // filesystem. + bool IsAllowedScheme(const GURL& url) const; + + // Returns a path to the usage cache file. + base::FilePath GetUsageCachePathForOriginAndType( + const GURL& origin_url, + FileSystemType type); + + // Returns a path to the usage cache file (static version). + static base::FilePath GetUsageCachePathForOriginAndType( + ObfuscatedFileUtil* sandbox_file_util, + const GURL& origin_url, + FileSystemType type, + base::File::Error* error_out); + + int64 RecalculateUsage(FileSystemContext* context, + const GURL& origin, + FileSystemType type); + + ObfuscatedFileUtil* obfuscated_file_util(); + + scoped_refptr<base::SequencedTaskRunner> file_task_runner_; + + scoped_ptr<AsyncFileUtil> sandbox_file_util_; + scoped_ptr<FileSystemUsageCache> file_system_usage_cache_; + scoped_ptr<SandboxQuotaObserver> quota_observer_; + scoped_ptr<QuotaReservationManager> quota_reservation_manager_; + + scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy_; + + FileSystemOptions file_system_options_; + + bool is_filesystem_opened_; + base::ThreadChecker io_thread_checker_; + + // Accessed only on the file thread. + std::set<GURL> visited_origins_; + + std::set<std::pair<GURL, FileSystemType> > sticky_dirty_origins_; + + std::map<FileSystemType, UpdateObserverList> update_observers_; + std::map<FileSystemType, ChangeObserverList> change_observers_; + std::map<FileSystemType, AccessObserverList> access_observers_; + + base::Time next_release_time_for_open_filesystem_stat_; + + base::WeakPtrFactory<SandboxFileSystemBackendDelegate> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(SandboxFileSystemBackendDelegate); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_SANDBOX_FILE_SYSTEM_BACKEND_DELEGATE_H_ diff --git a/storage/browser/fileapi/sandbox_isolated_origin_database.cc b/storage/browser/fileapi/sandbox_isolated_origin_database.cc new file mode 100644 index 0000000..3e3c218 --- /dev/null +++ b/storage/browser/fileapi/sandbox_isolated_origin_database.cc @@ -0,0 +1,80 @@ +// Copyright 2013 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 "storage/browser/fileapi/sandbox_isolated_origin_database.h" + +#include "base/files/file_util.h" +#include "base/logging.h" +#include "storage/browser/fileapi/sandbox_origin_database.h" + +namespace storage { + +// Special directory name for isolated origin. +const base::FilePath::CharType +SandboxIsolatedOriginDatabase::kObsoleteOriginDirectory[] = + FILE_PATH_LITERAL("iso"); + +SandboxIsolatedOriginDatabase::SandboxIsolatedOriginDatabase( + const std::string& origin, + const base::FilePath& file_system_directory, + const base::FilePath& origin_directory) + : migration_checked_(false), + origin_(origin), + file_system_directory_(file_system_directory), + origin_directory_(origin_directory) { +} + +SandboxIsolatedOriginDatabase::~SandboxIsolatedOriginDatabase() { +} + +bool SandboxIsolatedOriginDatabase::HasOriginPath( + const std::string& origin) { + return (origin_ == origin); +} + +bool SandboxIsolatedOriginDatabase::GetPathForOrigin( + const std::string& origin, base::FilePath* directory) { + if (origin != origin_) + return false; + *directory = origin_directory_; + return true; +} + +bool SandboxIsolatedOriginDatabase::RemovePathForOrigin( + const std::string& origin) { + return true; +} + +bool SandboxIsolatedOriginDatabase::ListAllOrigins( + std::vector<OriginRecord>* origins) { + origins->push_back(OriginRecord(origin_, origin_directory_)); + return true; +} + +void SandboxIsolatedOriginDatabase::DropDatabase() { +} + +void SandboxIsolatedOriginDatabase::MigrateBackFromObsoleteOriginDatabase( + const std::string& origin, + const base::FilePath& file_system_directory, + SandboxOriginDatabase* database) { + base::FilePath isolated_directory = + file_system_directory.Append(kObsoleteOriginDirectory); + + if (database->HasOriginPath(origin)) { + // Don't bother. + base::DeleteFile(isolated_directory, true /* recursive */); + return; + } + + base::FilePath directory_name; + if (database->GetPathForOrigin(origin, &directory_name)) { + base::FilePath origin_directory = + file_system_directory.Append(directory_name); + base::DeleteFile(origin_directory, true /* recursive */); + base::Move(isolated_directory, origin_directory); + } +} + +} // namespace storage diff --git a/storage/browser/fileapi/sandbox_isolated_origin_database.h b/storage/browser/fileapi/sandbox_isolated_origin_database.h new file mode 100644 index 0000000..f3ee49c --- /dev/null +++ b/storage/browser/fileapi/sandbox_isolated_origin_database.h @@ -0,0 +1,61 @@ +// Copyright 2013 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. + +#ifndef STORAGE_BROWSER_FILEAPI_SANDBOX_ISOLATED_ORIGIN_DATABASE_H_ +#define STORAGE_BROWSER_FILEAPI_SANDBOX_ISOLATED_ORIGIN_DATABASE_H_ + +#include <string> +#include <vector> + +#include "storage/browser/fileapi/sandbox_origin_database_interface.h" + +namespace storage { + +class SandboxOriginDatabase; + +// This origin database implementation supports only one origin +// (therefore is expected to run very fast). +class STORAGE_EXPORT_PRIVATE SandboxIsolatedOriginDatabase + : public SandboxOriginDatabaseInterface { + public: + static const base::FilePath::CharType kObsoleteOriginDirectory[]; + + // Initialize this database for |origin| which makes GetPathForOrigin return + // |origin_directory| (in |file_system_directory|). + SandboxIsolatedOriginDatabase( + const std::string& origin, + const base::FilePath& file_system_directory, + const base::FilePath& origin_directory); + virtual ~SandboxIsolatedOriginDatabase(); + + // SandboxOriginDatabaseInterface overrides. + virtual bool HasOriginPath(const std::string& origin) OVERRIDE; + virtual bool GetPathForOrigin(const std::string& origin, + base::FilePath* directory) OVERRIDE; + virtual bool RemovePathForOrigin(const std::string& origin) OVERRIDE; + virtual bool ListAllOrigins(std::vector<OriginRecord>* origins) OVERRIDE; + virtual void DropDatabase() OVERRIDE; + + // TODO(kinuko): Deprecate this after a few release cycles, e.g. around M33. + static void MigrateBackFromObsoleteOriginDatabase( + const std::string& origin, + const base::FilePath& file_system_directory, + SandboxOriginDatabase* origin_database); + + const std::string& origin() const { return origin_; } + + private: + void MigrateDatabaseIfNeeded(); + + bool migration_checked_; + const std::string origin_; + const base::FilePath file_system_directory_; + const base::FilePath origin_directory_; + + DISALLOW_COPY_AND_ASSIGN(SandboxIsolatedOriginDatabase); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_SANDBOX_ISOLATED_ORIGIN_DATABASE_H_ diff --git a/storage/browser/fileapi/sandbox_origin_database.cc b/storage/browser/fileapi/sandbox_origin_database.cc new file mode 100644 index 0000000..fa0521f --- /dev/null +++ b/storage/browser/fileapi/sandbox_origin_database.cc @@ -0,0 +1,347 @@ +// 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 "storage/browser/fileapi/sandbox_origin_database.h" + +#include <set> +#include <utility> + +#include "base/files/file_enumerator.h" +#include "base/files/file_util.h" +#include "base/format_macros.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/metrics/histogram.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "storage/common/fileapi/file_system_util.h" +#include "third_party/leveldatabase/src/include/leveldb/db.h" +#include "third_party/leveldatabase/src/include/leveldb/write_batch.h" + +namespace { + +const base::FilePath::CharType kOriginDatabaseName[] = + FILE_PATH_LITERAL("Origins"); +const char kOriginKeyPrefix[] = "ORIGIN:"; +const char kLastPathKey[] = "LAST_PATH"; +const int64 kMinimumReportIntervalHours = 1; +const char kInitStatusHistogramLabel[] = "FileSystem.OriginDatabaseInit"; +const char kDatabaseRepairHistogramLabel[] = "FileSystem.OriginDatabaseRepair"; + +enum InitStatus { + INIT_STATUS_OK = 0, + INIT_STATUS_CORRUPTION, + INIT_STATUS_IO_ERROR, + INIT_STATUS_UNKNOWN_ERROR, + INIT_STATUS_MAX +}; + +enum RepairResult { + DB_REPAIR_SUCCEEDED = 0, + DB_REPAIR_FAILED, + DB_REPAIR_MAX +}; + +std::string OriginToOriginKey(const std::string& origin) { + std::string key(kOriginKeyPrefix); + return key + origin; +} + +const char* LastPathKey() { + return kLastPathKey; +} + +} // namespace + +namespace storage { + +SandboxOriginDatabase::SandboxOriginDatabase( + const base::FilePath& file_system_directory, + leveldb::Env* env_override) + : file_system_directory_(file_system_directory), + env_override_(env_override) { +} + +SandboxOriginDatabase::~SandboxOriginDatabase() { +} + +bool SandboxOriginDatabase::Init(InitOption init_option, + RecoveryOption recovery_option) { + if (db_) + return true; + + base::FilePath db_path = GetDatabasePath(); + if (init_option == FAIL_IF_NONEXISTENT && !base::PathExists(db_path)) + return false; + + std::string path = FilePathToString(db_path); + leveldb::Options options; + options.max_open_files = 0; // Use minimum. + options.create_if_missing = true; + if (env_override_) + options.env = env_override_; + leveldb::DB* db; + leveldb::Status status = leveldb::DB::Open(options, path, &db); + ReportInitStatus(status); + if (status.ok()) { + db_.reset(db); + return true; + } + HandleError(FROM_HERE, status); + + // Corruption due to missing necessary MANIFEST-* file causes IOError instead + // of Corruption error. + // Try to repair database even when IOError case. + if (!status.IsCorruption() && !status.IsIOError()) + return false; + + switch (recovery_option) { + case FAIL_ON_CORRUPTION: + return false; + case REPAIR_ON_CORRUPTION: + LOG(WARNING) << "Attempting to repair SandboxOriginDatabase."; + + if (RepairDatabase(path)) { + UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel, + DB_REPAIR_SUCCEEDED, DB_REPAIR_MAX); + LOG(WARNING) << "Repairing SandboxOriginDatabase completed."; + return true; + } + UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel, + DB_REPAIR_FAILED, DB_REPAIR_MAX); + // fall through + case DELETE_ON_CORRUPTION: + if (!base::DeleteFile(file_system_directory_, true)) + return false; + if (!base::CreateDirectory(file_system_directory_)) + return false; + return Init(init_option, FAIL_ON_CORRUPTION); + } + NOTREACHED(); + return false; +} + +bool SandboxOriginDatabase::RepairDatabase(const std::string& db_path) { + DCHECK(!db_.get()); + leveldb::Options options; + options.max_open_files = 0; // Use minimum. + if (env_override_) + options.env = env_override_; + if (!leveldb::RepairDB(db_path, options).ok() || + !Init(FAIL_IF_NONEXISTENT, FAIL_ON_CORRUPTION)) { + LOG(WARNING) << "Failed to repair SandboxOriginDatabase."; + return false; + } + + // See if the repaired entries match with what we have on disk. + std::set<base::FilePath> directories; + base::FileEnumerator file_enum(file_system_directory_, + false /* recursive */, + base::FileEnumerator::DIRECTORIES); + base::FilePath path_each; + while (!(path_each = file_enum.Next()).empty()) + directories.insert(path_each.BaseName()); + std::set<base::FilePath>::iterator db_dir_itr = + directories.find(base::FilePath(kOriginDatabaseName)); + // Make sure we have the database file in its directory and therefore we are + // working on the correct path. + DCHECK(db_dir_itr != directories.end()); + directories.erase(db_dir_itr); + + std::vector<OriginRecord> origins; + if (!ListAllOrigins(&origins)) { + DropDatabase(); + return false; + } + + // Delete any obsolete entries from the origins database. + for (std::vector<OriginRecord>::iterator db_origin_itr = origins.begin(); + db_origin_itr != origins.end(); + ++db_origin_itr) { + std::set<base::FilePath>::iterator dir_itr = + directories.find(db_origin_itr->path); + if (dir_itr == directories.end()) { + if (!RemovePathForOrigin(db_origin_itr->origin)) { + DropDatabase(); + return false; + } + } else { + directories.erase(dir_itr); + } + } + + // Delete any directories not listed in the origins database. + for (std::set<base::FilePath>::iterator dir_itr = directories.begin(); + dir_itr != directories.end(); + ++dir_itr) { + if (!base::DeleteFile(file_system_directory_.Append(*dir_itr), + true /* recursive */)) { + DropDatabase(); + return false; + } + } + + return true; +} + +void SandboxOriginDatabase::HandleError( + const tracked_objects::Location& from_here, + const leveldb::Status& status) { + db_.reset(); + LOG(ERROR) << "SandboxOriginDatabase failed at: " + << from_here.ToString() << " with error: " << status.ToString(); +} + +void SandboxOriginDatabase::ReportInitStatus(const leveldb::Status& status) { + base::Time now = base::Time::Now(); + base::TimeDelta minimum_interval = + base::TimeDelta::FromHours(kMinimumReportIntervalHours); + if (last_reported_time_ + minimum_interval >= now) + return; + last_reported_time_ = now; + + if (status.ok()) { + UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, + INIT_STATUS_OK, INIT_STATUS_MAX); + } else if (status.IsCorruption()) { + UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, + INIT_STATUS_CORRUPTION, INIT_STATUS_MAX); + } else if (status.IsIOError()) { + UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, + INIT_STATUS_IO_ERROR, INIT_STATUS_MAX); + } else { + UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, + INIT_STATUS_UNKNOWN_ERROR, INIT_STATUS_MAX); + } +} + +bool SandboxOriginDatabase::HasOriginPath(const std::string& origin) { + if (!Init(FAIL_IF_NONEXISTENT, REPAIR_ON_CORRUPTION)) + return false; + if (origin.empty()) + return false; + std::string path; + leveldb::Status status = + db_->Get(leveldb::ReadOptions(), OriginToOriginKey(origin), &path); + if (status.ok()) + return true; + if (status.IsNotFound()) + return false; + HandleError(FROM_HERE, status); + return false; +} + +bool SandboxOriginDatabase::GetPathForOrigin( + const std::string& origin, base::FilePath* directory) { + if (!Init(CREATE_IF_NONEXISTENT, REPAIR_ON_CORRUPTION)) + return false; + DCHECK(directory); + if (origin.empty()) + return false; + std::string path_string; + std::string origin_key = OriginToOriginKey(origin); + leveldb::Status status = + db_->Get(leveldb::ReadOptions(), origin_key, &path_string); + if (status.IsNotFound()) { + int last_path_number; + if (!GetLastPathNumber(&last_path_number)) + return false; + path_string = base::StringPrintf("%03u", last_path_number + 1); + // store both back as a single transaction + leveldb::WriteBatch batch; + batch.Put(LastPathKey(), path_string); + batch.Put(origin_key, path_string); + status = db_->Write(leveldb::WriteOptions(), &batch); + if (!status.ok()) { + HandleError(FROM_HERE, status); + return false; + } + } + if (status.ok()) { + *directory = StringToFilePath(path_string); + return true; + } + HandleError(FROM_HERE, status); + return false; +} + +bool SandboxOriginDatabase::RemovePathForOrigin(const std::string& origin) { + if (!Init(CREATE_IF_NONEXISTENT, REPAIR_ON_CORRUPTION)) + return false; + leveldb::Status status = + db_->Delete(leveldb::WriteOptions(), OriginToOriginKey(origin)); + if (status.ok() || status.IsNotFound()) + return true; + HandleError(FROM_HERE, status); + return false; +} + +bool SandboxOriginDatabase::ListAllOrigins( + std::vector<OriginRecord>* origins) { + DCHECK(origins); + if (!Init(CREATE_IF_NONEXISTENT, REPAIR_ON_CORRUPTION)) { + origins->clear(); + return false; + } + scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions())); + std::string origin_key_prefix = OriginToOriginKey(std::string()); + iter->Seek(origin_key_prefix); + origins->clear(); + while (iter->Valid() && + StartsWithASCII(iter->key().ToString(), origin_key_prefix, true)) { + std::string origin = + iter->key().ToString().substr(origin_key_prefix.length()); + base::FilePath path = StringToFilePath(iter->value().ToString()); + origins->push_back(OriginRecord(origin, path)); + iter->Next(); + } + return true; +} + +void SandboxOriginDatabase::DropDatabase() { + db_.reset(); +} + +base::FilePath SandboxOriginDatabase::GetDatabasePath() const { + return file_system_directory_.Append(kOriginDatabaseName); +} + +void SandboxOriginDatabase::RemoveDatabase() { + DropDatabase(); + base::DeleteFile(GetDatabasePath(), true /* recursive */); +} + +bool SandboxOriginDatabase::GetLastPathNumber(int* number) { + DCHECK(db_); + DCHECK(number); + std::string number_string; + leveldb::Status status = + db_->Get(leveldb::ReadOptions(), LastPathKey(), &number_string); + if (status.ok()) + return base::StringToInt(number_string, number); + if (!status.IsNotFound()) { + HandleError(FROM_HERE, status); + return false; + } + // Verify that this is a totally new database, and initialize it. + scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions())); + iter->SeekToFirst(); + if (iter->Valid()) { // DB was not empty, but had no last path number! + LOG(ERROR) << "File system origin database is corrupt!"; + return false; + } + // This is always the first write into the database. If we ever add a + // version number, they should go in in a single transaction. + status = + db_->Put(leveldb::WriteOptions(), LastPathKey(), std::string("-1")); + if (!status.ok()) { + HandleError(FROM_HERE, status); + return false; + } + *number = -1; + return true; +} + +} // namespace storage diff --git a/storage/browser/fileapi/sandbox_origin_database.h b/storage/browser/fileapi/sandbox_origin_database.h new file mode 100644 index 0000000..21779d5 --- /dev/null +++ b/storage/browser/fileapi/sandbox_origin_database.h @@ -0,0 +1,77 @@ +// 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. + +#ifndef STORAGE_BROWSER_FILEAPI_SANDBOX_ORIGIN_DATABASE_H_ +#define STORAGE_BROWSER_FILEAPI_SANDBOX_ORIGIN_DATABASE_H_ + +#include <string> +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "base/time/time.h" +#include "storage/browser/fileapi/sandbox_origin_database_interface.h" + +namespace leveldb { +class DB; +class Env; +class Status; +} + +namespace tracked_objects { +class Location; +} + +namespace storage { + +// All methods of this class other than the constructor may be used only from +// the browser's FILE thread. The constructor may be used on any thread. +class STORAGE_EXPORT_PRIVATE SandboxOriginDatabase + : public SandboxOriginDatabaseInterface { + public: + // Only one instance of SandboxOriginDatabase should exist for a given path + // at a given time. + SandboxOriginDatabase(const base::FilePath& file_system_directory, + leveldb::Env* env_override); + virtual ~SandboxOriginDatabase(); + + // SandboxOriginDatabaseInterface overrides. + virtual bool HasOriginPath(const std::string& origin) OVERRIDE; + virtual bool GetPathForOrigin(const std::string& origin, + base::FilePath* directory) OVERRIDE; + virtual bool RemovePathForOrigin(const std::string& origin) OVERRIDE; + virtual bool ListAllOrigins(std::vector<OriginRecord>* origins) OVERRIDE; + virtual void DropDatabase() OVERRIDE; + + base::FilePath GetDatabasePath() const; + void RemoveDatabase(); + + private: + enum RecoveryOption { + REPAIR_ON_CORRUPTION, + DELETE_ON_CORRUPTION, + FAIL_ON_CORRUPTION, + }; + + enum InitOption { + CREATE_IF_NONEXISTENT, + FAIL_IF_NONEXISTENT, + }; + + bool Init(InitOption init_option, RecoveryOption recovery_option); + bool RepairDatabase(const std::string& db_path); + void HandleError(const tracked_objects::Location& from_here, + const leveldb::Status& status); + void ReportInitStatus(const leveldb::Status& status); + bool GetLastPathNumber(int* number); + + base::FilePath file_system_directory_; + leveldb::Env* env_override_; + scoped_ptr<leveldb::DB> db_; + base::Time last_reported_time_; + DISALLOW_COPY_AND_ASSIGN(SandboxOriginDatabase); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_SANDBOX_ORIGIN_DATABASE_H_ diff --git a/storage/browser/fileapi/sandbox_origin_database_interface.cc b/storage/browser/fileapi/sandbox_origin_database_interface.cc new file mode 100644 index 0000000..25ba1dc --- /dev/null +++ b/storage/browser/fileapi/sandbox_origin_database_interface.cc @@ -0,0 +1,20 @@ +// Copyright 2013 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 "storage/browser/fileapi/sandbox_origin_database_interface.h" + +namespace storage { + +SandboxOriginDatabaseInterface::OriginRecord::OriginRecord() { +} + +SandboxOriginDatabaseInterface::OriginRecord::OriginRecord( + const std::string& origin_in, const base::FilePath& path_in) + : origin(origin_in), path(path_in) { +} + +SandboxOriginDatabaseInterface::OriginRecord::~OriginRecord() { +} + +} // namespace storage diff --git a/storage/browser/fileapi/sandbox_origin_database_interface.h b/storage/browser/fileapi/sandbox_origin_database_interface.h new file mode 100644 index 0000000..4a01e43 --- /dev/null +++ b/storage/browser/fileapi/sandbox_origin_database_interface.h @@ -0,0 +1,55 @@ +// Copyright 2013 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. + +#ifndef STORAGE_BROWSER_FILEAPI_SANDBOX_ORIGIN_DATABASE_INTERFACE_H_ +#define STORAGE_BROWSER_FILEAPI_SANDBOX_ORIGIN_DATABASE_INTERFACE_H_ + +#include <string> +#include <vector> + +#include "base/files/file_path.h" +#include "storage/browser/storage_browser_export.h" + +namespace storage { + +class STORAGE_EXPORT_PRIVATE SandboxOriginDatabaseInterface { + public: + struct STORAGE_EXPORT_PRIVATE OriginRecord { + std::string origin; + base::FilePath path; + + OriginRecord(); + OriginRecord(const std::string& origin, const base::FilePath& path); + ~OriginRecord(); + }; + + virtual ~SandboxOriginDatabaseInterface() {} + + // Returns true if the origin's path is included in this database. + virtual bool HasOriginPath(const std::string& origin) = 0; + + // This will produce a unique path and add it to its database, if it's not + // already present. + virtual bool GetPathForOrigin(const std::string& origin, + base::FilePath* directory) = 0; + + // Removes the origin's path from the database. + // Returns success if the origin has been successfully removed, or + // the origin is not found. + // (This doesn't remove the actual path). + virtual bool RemovePathForOrigin(const std::string& origin) = 0; + + // Lists all origins in this database. + virtual bool ListAllOrigins(std::vector<OriginRecord>* origins) = 0; + + // This will release all database resources in use; call it to save memory. + virtual void DropDatabase() = 0; + + protected: + SandboxOriginDatabaseInterface() {} +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_SANDBOX_ORIGIN_DATABASE_INTERFACE_H_ diff --git a/storage/browser/fileapi/sandbox_prioritized_origin_database.cc b/storage/browser/fileapi/sandbox_prioritized_origin_database.cc new file mode 100644 index 0000000..80ccd27 --- /dev/null +++ b/storage/browser/fileapi/sandbox_prioritized_origin_database.cc @@ -0,0 +1,223 @@ +// Copyright 2013 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 "storage/browser/fileapi/sandbox_prioritized_origin_database.h" + +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/pickle.h" +#include "storage/browser/fileapi/sandbox_isolated_origin_database.h" +#include "storage/browser/fileapi/sandbox_origin_database.h" + +namespace storage { + +namespace { + +const base::FilePath::CharType kPrimaryDirectory[] = + FILE_PATH_LITERAL("primary"); +const base::FilePath::CharType kPrimaryOriginFile[] = + FILE_PATH_LITERAL("primary.origin"); + +bool WritePrimaryOriginFile(const base::FilePath& path, + const std::string& origin) { + base::File file(path, base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_WRITE); + if (!file.IsValid()) + return false; + if (!file.created()) + file.SetLength(0); + Pickle pickle; + pickle.WriteString(origin); + file.Write(0, static_cast<const char*>(pickle.data()), pickle.size()); + file.Flush(); + return true; +} + +bool ReadPrimaryOriginFile(const base::FilePath& path, + std::string* origin) { + std::string buffer; + if (!base::ReadFileToString(path, &buffer)) + return false; + Pickle pickle(buffer.data(), buffer.size()); + PickleIterator iter(pickle); + return pickle.ReadString(&iter, origin) && !origin->empty(); +} + +} // namespace + +SandboxPrioritizedOriginDatabase::SandboxPrioritizedOriginDatabase( + const base::FilePath& file_system_directory, + leveldb::Env* env_override) + : file_system_directory_(file_system_directory), + env_override_(env_override), + primary_origin_file_( + file_system_directory_.Append(kPrimaryOriginFile)) { +} + +SandboxPrioritizedOriginDatabase::~SandboxPrioritizedOriginDatabase() { +} + +bool SandboxPrioritizedOriginDatabase::InitializePrimaryOrigin( + const std::string& origin) { + if (!primary_origin_database_) { + if (!MaybeLoadPrimaryOrigin() && ResetPrimaryOrigin(origin)) { + MaybeMigrateDatabase(origin); + primary_origin_database_.reset( + new SandboxIsolatedOriginDatabase( + origin, + file_system_directory_, + base::FilePath(kPrimaryDirectory))); + return true; + } + } + + if (primary_origin_database_) + return primary_origin_database_->HasOriginPath(origin); + + return false; +} + +std::string SandboxPrioritizedOriginDatabase::GetPrimaryOrigin() { + MaybeLoadPrimaryOrigin(); + if (primary_origin_database_) + return primary_origin_database_->origin(); + return std::string(); +} + +bool SandboxPrioritizedOriginDatabase::HasOriginPath( + const std::string& origin) { + MaybeInitializeDatabases(false); + if (primary_origin_database_ && + primary_origin_database_->HasOriginPath(origin)) + return true; + if (origin_database_) + return origin_database_->HasOriginPath(origin); + return false; +} + +bool SandboxPrioritizedOriginDatabase::GetPathForOrigin( + const std::string& origin, base::FilePath* directory) { + MaybeInitializeDatabases(true); + if (primary_origin_database_ && + primary_origin_database_->GetPathForOrigin(origin, directory)) + return true; + DCHECK(origin_database_); + return origin_database_->GetPathForOrigin(origin, directory); +} + +bool SandboxPrioritizedOriginDatabase::RemovePathForOrigin( + const std::string& origin) { + MaybeInitializeDatabases(false); + if (primary_origin_database_ && + primary_origin_database_->HasOriginPath(origin)) { + primary_origin_database_.reset(); + base::DeleteFile(file_system_directory_.Append(kPrimaryOriginFile), + true /* recursive */); + return true; + } + if (origin_database_) + return origin_database_->RemovePathForOrigin(origin); + return true; +} + +bool SandboxPrioritizedOriginDatabase::ListAllOrigins( + std::vector<OriginRecord>* origins) { + // SandboxOriginDatabase may clear the |origins|, so call this before + // primary_origin_database_. + MaybeInitializeDatabases(false); + if (origin_database_ && !origin_database_->ListAllOrigins(origins)) + return false; + if (primary_origin_database_) + return primary_origin_database_->ListAllOrigins(origins); + return true; +} + +void SandboxPrioritizedOriginDatabase::DropDatabase() { + primary_origin_database_.reset(); + origin_database_.reset(); +} + +bool SandboxPrioritizedOriginDatabase::MaybeLoadPrimaryOrigin() { + if (primary_origin_database_) + return true; + std::string saved_origin; + if (!ReadPrimaryOriginFile(primary_origin_file_, &saved_origin)) + return false; + primary_origin_database_.reset( + new SandboxIsolatedOriginDatabase( + saved_origin, + file_system_directory_, + base::FilePath(kPrimaryDirectory))); + return true; +} + +bool SandboxPrioritizedOriginDatabase::ResetPrimaryOrigin( + const std::string& origin) { + DCHECK(!primary_origin_database_); + if (!WritePrimaryOriginFile(primary_origin_file_, origin)) + return false; + // We reset the primary origin directory too. + // (This means the origin file corruption causes data loss + // We could keep the directory there as the same origin will likely + // become the primary origin, but let's play conservatively.) + base::DeleteFile(file_system_directory_.Append(kPrimaryDirectory), + true /* recursive */); + return true; +} + +void SandboxPrioritizedOriginDatabase::MaybeMigrateDatabase( + const std::string& origin) { + MaybeInitializeNonPrimaryDatabase(false); + if (!origin_database_) + return; + if (origin_database_->HasOriginPath(origin)) { + base::FilePath directory_name; + if (origin_database_->GetPathForOrigin(origin, &directory_name) && + directory_name != base::FilePath(kPrimaryOriginFile)) { + base::FilePath from_path = file_system_directory_.Append(directory_name); + base::FilePath to_path = file_system_directory_.Append(kPrimaryDirectory); + + if (base::PathExists(to_path)) + base::DeleteFile(to_path, true /* recursive */); + base::Move(from_path, to_path); + } + + origin_database_->RemovePathForOrigin(origin); + } + + std::vector<OriginRecord> origins; + origin_database_->ListAllOrigins(&origins); + if (origins.empty()) { + origin_database_->RemoveDatabase(); + origin_database_.reset(); + } +} + +void SandboxPrioritizedOriginDatabase::MaybeInitializeDatabases( + bool create) { + MaybeLoadPrimaryOrigin(); + MaybeInitializeNonPrimaryDatabase(create); +} + +void SandboxPrioritizedOriginDatabase::MaybeInitializeNonPrimaryDatabase( + bool create) { + if (origin_database_) + return; + + origin_database_.reset(new SandboxOriginDatabase(file_system_directory_, + env_override_)); + if (!create && !base::DirectoryExists(origin_database_->GetDatabasePath())) { + origin_database_.reset(); + return; + } +} + +SandboxOriginDatabase* +SandboxPrioritizedOriginDatabase::GetSandboxOriginDatabase() { + MaybeInitializeNonPrimaryDatabase(true); + return origin_database_.get(); +} + +} // namespace storage diff --git a/storage/browser/fileapi/sandbox_prioritized_origin_database.h b/storage/browser/fileapi/sandbox_prioritized_origin_database.h new file mode 100644 index 0000000..bbe1420 --- /dev/null +++ b/storage/browser/fileapi/sandbox_prioritized_origin_database.h @@ -0,0 +1,72 @@ +// Copyright 2013 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. + +#ifndef STORAGE_BROWSER_FILEAPI_SANDBOX_PRIORITIZED_ORIGIN_DATABASE_H_ +#define STORAGE_BROWSER_FILEAPI_SANDBOX_PRIORITIZED_ORIGIN_DATABASE_H_ + +#include <string> +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "storage/browser/fileapi/sandbox_origin_database_interface.h" + +namespace leveldb { +class Env; +} + +namespace storage { + +class ObfuscatedFileUtil; +class SandboxIsolatedOriginDatabase; +class SandboxOriginDatabase; + +class STORAGE_EXPORT_PRIVATE SandboxPrioritizedOriginDatabase + : public SandboxOriginDatabaseInterface { + public: + SandboxPrioritizedOriginDatabase(const base::FilePath& file_system_directory, + leveldb::Env* env_override); + virtual ~SandboxPrioritizedOriginDatabase(); + + // Sets |origin| as primary origin in this database (e.g. may + // allow faster access). + // Returns false if this database already has a primary origin + // which is different from |origin|. + bool InitializePrimaryOrigin(const std::string& origin); + std::string GetPrimaryOrigin(); + + // SandboxOriginDatabaseInterface overrides. + virtual bool HasOriginPath(const std::string& origin) OVERRIDE; + virtual bool GetPathForOrigin(const std::string& origin, + base::FilePath* directory) OVERRIDE; + virtual bool RemovePathForOrigin(const std::string& origin) OVERRIDE; + virtual bool ListAllOrigins(std::vector<OriginRecord>* origins) OVERRIDE; + virtual void DropDatabase() OVERRIDE; + + const base::FilePath& primary_origin_file() const { + return primary_origin_file_; + } + + private: + bool MaybeLoadPrimaryOrigin(); + bool ResetPrimaryOrigin(const std::string& origin); + void MaybeMigrateDatabase(const std::string& origin); + void MaybeInitializeDatabases(bool create); + void MaybeInitializeNonPrimaryDatabase(bool create); + + // For migration. + friend class ObfuscatedFileUtil; + SandboxOriginDatabase* GetSandboxOriginDatabase(); + + const base::FilePath file_system_directory_; + leveldb::Env* env_override_; + const base::FilePath primary_origin_file_; + scoped_ptr<SandboxOriginDatabase> origin_database_; + scoped_ptr<SandboxIsolatedOriginDatabase> primary_origin_database_; + + DISALLOW_COPY_AND_ASSIGN(SandboxPrioritizedOriginDatabase); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_SANDBOX_PRIORITIZED_ORIGIN_DATABASE_H_ diff --git a/storage/browser/fileapi/sandbox_quota_observer.cc b/storage/browser/fileapi/sandbox_quota_observer.cc new file mode 100644 index 0000000..0c39464 --- /dev/null +++ b/storage/browser/fileapi/sandbox_quota_observer.cc @@ -0,0 +1,140 @@ +// 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 "storage/browser/fileapi/sandbox_quota_observer.h" + +#include "base/sequenced_task_runner.h" +#include "storage/browser/fileapi/file_system_url.h" +#include "storage/browser/fileapi/file_system_usage_cache.h" +#include "storage/browser/fileapi/sandbox_file_system_backend_delegate.h" +#include "storage/browser/fileapi/timed_task_helper.h" +#include "storage/browser/quota/quota_client.h" +#include "storage/browser/quota/quota_manager_proxy.h" +#include "storage/common/fileapi/file_system_util.h" + +namespace storage { + +SandboxQuotaObserver::SandboxQuotaObserver( + storage::QuotaManagerProxy* quota_manager_proxy, + base::SequencedTaskRunner* update_notify_runner, + ObfuscatedFileUtil* sandbox_file_util, + FileSystemUsageCache* file_system_usage_cache) + : quota_manager_proxy_(quota_manager_proxy), + update_notify_runner_(update_notify_runner), + sandbox_file_util_(sandbox_file_util), + file_system_usage_cache_(file_system_usage_cache) { +} + +SandboxQuotaObserver::~SandboxQuotaObserver() {} + +void SandboxQuotaObserver::OnStartUpdate(const FileSystemURL& url) { + DCHECK(update_notify_runner_->RunsTasksOnCurrentThread()); + base::FilePath usage_file_path = GetUsageCachePath(url); + if (usage_file_path.empty()) + return; + file_system_usage_cache_->IncrementDirty(usage_file_path); +} + +void SandboxQuotaObserver::OnUpdate(const FileSystemURL& url, + int64 delta) { + DCHECK(update_notify_runner_->RunsTasksOnCurrentThread()); + + if (quota_manager_proxy_.get()) { + quota_manager_proxy_->NotifyStorageModified( + storage::QuotaClient::kFileSystem, + url.origin(), + FileSystemTypeToQuotaStorageType(url.type()), + delta); + } + + base::FilePath usage_file_path = GetUsageCachePath(url); + if (usage_file_path.empty()) + return; + + pending_update_notification_[usage_file_path] += delta; + if (!delayed_cache_update_helper_) { + delayed_cache_update_helper_.reset( + new TimedTaskHelper(update_notify_runner_.get())); + delayed_cache_update_helper_->Start( + FROM_HERE, + base::TimeDelta(), // No delay. + base::Bind(&SandboxQuotaObserver::ApplyPendingUsageUpdate, + base::Unretained(this))); + } +} + +void SandboxQuotaObserver::OnEndUpdate(const FileSystemURL& url) { + DCHECK(update_notify_runner_->RunsTasksOnCurrentThread()); + + base::FilePath usage_file_path = GetUsageCachePath(url); + if (usage_file_path.empty()) + return; + + PendingUpdateNotificationMap::iterator found = + pending_update_notification_.find(usage_file_path); + if (found != pending_update_notification_.end()) { + UpdateUsageCacheFile(found->first, found->second); + pending_update_notification_.erase(found); + } + + file_system_usage_cache_->DecrementDirty(usage_file_path); +} + +void SandboxQuotaObserver::OnAccess(const FileSystemURL& url) { + if (quota_manager_proxy_.get()) { + quota_manager_proxy_->NotifyStorageAccessed( + storage::QuotaClient::kFileSystem, + url.origin(), + FileSystemTypeToQuotaStorageType(url.type())); + } +} + +void SandboxQuotaObserver::SetUsageCacheEnabled( + const GURL& origin, + FileSystemType type, + bool enabled) { + if (quota_manager_proxy_.get()) { + quota_manager_proxy_->SetUsageCacheEnabled( + storage::QuotaClient::kFileSystem, + origin, + FileSystemTypeToQuotaStorageType(type), + enabled); + } +} + +base::FilePath SandboxQuotaObserver::GetUsageCachePath( + const FileSystemURL& url) { + DCHECK(sandbox_file_util_); + base::File::Error error = base::File::FILE_OK; + base::FilePath path = + SandboxFileSystemBackendDelegate::GetUsageCachePathForOriginAndType( + sandbox_file_util_, url.origin(), url.type(), &error); + if (error != base::File::FILE_OK) { + LOG(WARNING) << "Could not get usage cache path for: " + << url.DebugString(); + return base::FilePath(); + } + return path; +} + +void SandboxQuotaObserver::ApplyPendingUsageUpdate() { + delayed_cache_update_helper_.reset(); + for (PendingUpdateNotificationMap::iterator itr = + pending_update_notification_.begin(); + itr != pending_update_notification_.end(); + ++itr) { + UpdateUsageCacheFile(itr->first, itr->second); + } + pending_update_notification_.clear(); +} + +void SandboxQuotaObserver::UpdateUsageCacheFile( + const base::FilePath& usage_file_path, + int64 delta) { + DCHECK(!usage_file_path.empty()); + if (!usage_file_path.empty() && delta != 0) + file_system_usage_cache_->AtomicUpdateUsageByDelta(usage_file_path, delta); +} + +} // namespace storage diff --git a/storage/browser/fileapi/sandbox_quota_observer.h b/storage/browser/fileapi/sandbox_quota_observer.h new file mode 100644 index 0000000..4ae2cf9 --- /dev/null +++ b/storage/browser/fileapi/sandbox_quota_observer.h @@ -0,0 +1,81 @@ +// 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. + +#ifndef STORAGE_BROWSER_FILEAPI_SANDBOX_QUOTA_OBSERVER_H_ +#define STORAGE_BROWSER_FILEAPI_SANDBOX_QUOTA_OBSERVER_H_ + +#include <map> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "storage/browser/fileapi/file_observers.h" +#include "storage/browser/fileapi/file_system_url.h" + +namespace base { +class SequencedTaskRunner; +} + +namespace storage { +class QuotaManagerProxy; +} + +namespace storage { + +class FileSystemUsageCache; +class FileSystemURL; +class TimedTaskHelper; +class ObfuscatedFileUtil; + +class SandboxQuotaObserver + : public FileUpdateObserver, + public FileAccessObserver { + public: + typedef std::map<base::FilePath, int64> PendingUpdateNotificationMap; + + SandboxQuotaObserver(storage::QuotaManagerProxy* quota_manager_proxy, + base::SequencedTaskRunner* update_notify_runner, + ObfuscatedFileUtil* sandbox_file_util, + FileSystemUsageCache* file_system_usage_cache_); + virtual ~SandboxQuotaObserver(); + + // FileUpdateObserver overrides. + virtual void OnStartUpdate(const FileSystemURL& url) OVERRIDE; + virtual void OnUpdate(const FileSystemURL& url, int64 delta) OVERRIDE; + virtual void OnEndUpdate(const FileSystemURL& url) OVERRIDE; + + // FileAccessObserver overrides. + virtual void OnAccess(const FileSystemURL& url) OVERRIDE; + + void SetUsageCacheEnabled(const GURL& origin, + FileSystemType type, + bool enabled); + + private: + void ApplyPendingUsageUpdate(); + void UpdateUsageCacheFile(const base::FilePath& usage_file_path, int64 delta); + + base::FilePath GetUsageCachePath(const FileSystemURL& url); + + scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy_; + scoped_refptr<base::SequencedTaskRunner> update_notify_runner_; + + // Not owned; sandbox_file_util_ should have identical lifetime with this. + ObfuscatedFileUtil* sandbox_file_util_; + + // Not owned; file_system_usage_cache_ should have longer lifetime than this. + FileSystemUsageCache* file_system_usage_cache_; + + PendingUpdateNotificationMap pending_update_notification_; + scoped_ptr<TimedTaskHelper> delayed_cache_update_helper_; + + DISALLOW_COPY_AND_ASSIGN(SandboxQuotaObserver); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_SANDBOX_QUOTA_OBSERVER_H_ diff --git a/storage/browser/fileapi/task_runner_bound_observer_list.h b/storage/browser/fileapi/task_runner_bound_observer_list.h new file mode 100644 index 0000000..304eccb --- /dev/null +++ b/storage/browser/fileapi/task_runner_bound_observer_list.h @@ -0,0 +1,99 @@ +// 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. + +#ifndef STORAGE_BROWSER_FILEAPI_TASK_RUNNER_BOUND_OBSERVER_LIST_H_ +#define STORAGE_BROWSER_FILEAPI_TASK_RUNNER_BOUND_OBSERVER_LIST_H_ + +#include <map> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/memory/ref_counted.h" +#include "base/sequenced_task_runner.h" +#include "base/threading/thread.h" + +namespace storage { + +// A wrapper for dispatching method. +template <class T, class Method, class Params> +void NotifyWrapper(T obj, Method m, const Params& p) { + DispatchToMethod(base::internal::UnwrapTraits<T>::Unwrap(obj), m, p); +} + +// An observer list helper to notify on a given task runner. +// Observer pointers (stored as ObserverStoreType) must be kept alive +// until this list dispatches all the notifications. +// +// Unlike regular ObserverList or ObserverListThreadSafe internal observer +// list is immutable (though not declared const) and cannot be modified after +// constructed. +// +// It is ok to specify scoped_refptr<Observer> as ObserverStoreType to +// explicitly keep references if necessary. +template <class Observer, class ObserverStoreType = Observer*> +class TaskRunnerBoundObserverList { + public: + typedef scoped_refptr<base::SequencedTaskRunner> TaskRunnerPtr; + typedef std::map<ObserverStoreType, TaskRunnerPtr> ObserversListMap; + + // Creates an empty list. + TaskRunnerBoundObserverList<Observer, ObserverStoreType>() {} + + // Creates a new list with given |observers|. + explicit TaskRunnerBoundObserverList<Observer, ObserverStoreType>( + const ObserversListMap& observers) + : observers_(observers) {} + + virtual ~TaskRunnerBoundObserverList<Observer, ObserverStoreType>() {} + + // Returns a new observer list with given observer. + // It is valid to give NULL as |runner_to_notify|, and in that case + // notifications are dispatched on the current runner. + // Note that this is a const method and does NOT change 'this' observer + // list but returns a new list. + TaskRunnerBoundObserverList<Observer, ObserverStoreType> AddObserver( + Observer* observer, + base::SequencedTaskRunner* runner_to_notify) const { + ObserversListMap observers = observers_; + observers.insert(std::make_pair(observer, runner_to_notify)); + return TaskRunnerBoundObserverList<Observer, ObserverStoreType>(observers); + } + + // Notify on the task runner that is given to AddObserver. + // If we're already on the runner this just dispatches the method. + template <class Method, class Params> + void Notify(Method method, const Params& params) const { + COMPILE_ASSERT( + (base::internal::ParamsUseScopedRefptrCorrectly<Params>::value), + badunboundmethodparams); + for (typename ObserversListMap::const_iterator it = observers_.begin(); + it != observers_.end(); ++it) { + if (!it->second.get() || it->second->RunsTasksOnCurrentThread()) { + DispatchToMethod(UnwrapTraits::Unwrap(it->first), method, params); + continue; + } + it->second->PostTask( + FROM_HERE, + base::Bind(&NotifyWrapper<ObserverStoreType, Method, Params>, + it->first, method, params)); + } + } + + private: + typedef base::internal::UnwrapTraits<ObserverStoreType> UnwrapTraits; + + ObserversListMap observers_; +}; + +class FileAccessObserver; +class FileChangeObserver; +class FileUpdateObserver; + +typedef TaskRunnerBoundObserverList<FileAccessObserver> AccessObserverList; +typedef TaskRunnerBoundObserverList<FileChangeObserver> ChangeObserverList; +typedef TaskRunnerBoundObserverList<FileUpdateObserver> UpdateObserverList; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_TASK_RUNNER_BOUND_OBSERVER_LIST_H_ diff --git a/storage/browser/fileapi/timed_task_helper.cc b/storage/browser/fileapi/timed_task_helper.cc new file mode 100644 index 0000000..950df76 --- /dev/null +++ b/storage/browser/fileapi/timed_task_helper.cc @@ -0,0 +1,92 @@ +// Copyright 2013 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 "storage/browser/fileapi/timed_task_helper.h" + +#include "base/bind.h" +#include "base/logging.h" +#include "base/sequenced_task_runner.h" + +namespace storage { + +struct TimedTaskHelper::Tracker { + explicit Tracker(TimedTaskHelper* timer) : timer(timer) {} + + ~Tracker() { + if (timer) + timer->tracker_ = NULL; + } + + TimedTaskHelper* timer; +}; + +TimedTaskHelper::TimedTaskHelper(base::SequencedTaskRunner* task_runner) + : task_runner_(task_runner), + tracker_(NULL) { +} + +TimedTaskHelper::~TimedTaskHelper() { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + if (tracker_) + tracker_->timer = NULL; +} + +bool TimedTaskHelper::IsRunning() const { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + return tracker_ != NULL; +} + +void TimedTaskHelper::Start( + const tracked_objects::Location& posted_from, + base::TimeDelta delay, + const base::Closure& user_task) { + posted_from_ = posted_from; + delay_ = delay; + user_task_ = user_task; + Reset(); +} + +void TimedTaskHelper::Reset() { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + DCHECK(!user_task_.is_null()); + desired_run_time_ = base::TimeTicks::Now() + delay_; + + if (tracker_) + return; + + // Initialize the tracker for the first time. + tracker_ = new Tracker(this); + PostDelayedTask(make_scoped_ptr(tracker_), delay_); +} + +// static +void TimedTaskHelper::Fired(scoped_ptr<Tracker> tracker) { + if (!tracker->timer) + return; + TimedTaskHelper* timer = tracker->timer; + timer->OnFired(tracker.Pass()); +} + +void TimedTaskHelper::OnFired(scoped_ptr<Tracker> tracker) { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + base::TimeTicks now = base::TimeTicks::Now(); + if (desired_run_time_ > now) { + PostDelayedTask(tracker.Pass(), desired_run_time_ - now); + return; + } + tracker.reset(); + base::Closure task = user_task_; + user_task_.Reset(); + task.Run(); +} + +void TimedTaskHelper::PostDelayedTask(scoped_ptr<Tracker> tracker, + base::TimeDelta delay) { + task_runner_->PostDelayedTask( + posted_from_, + base::Bind(&TimedTaskHelper::Fired, base::Passed(&tracker)), + delay); +} + +} // namespace storage diff --git a/storage/browser/fileapi/timed_task_helper.h b/storage/browser/fileapi/timed_task_helper.h new file mode 100644 index 0000000..4663501 --- /dev/null +++ b/storage/browser/fileapi/timed_task_helper.h @@ -0,0 +1,59 @@ +// Copyright 2013 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. + +#ifndef STORAGE_BROWSER_FILEAPI_TIMED_TASK_HELPER_H_ +#define STORAGE_BROWSER_FILEAPI_TIMED_TASK_HELPER_H_ + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/location.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/time/time.h" +#include "storage/browser/storage_browser_export.h" + +namespace base { +class SequencedTaskRunner; +} + +namespace storage { + +// Works similarly as base::Timer, but takes SequencedTaskRunner and +// runs tasks on it (instead of implicitly bound to a thread). +// TODO(kinuko): This has nothing to do with fileapi. Move somewhere +// more common place. +class STORAGE_EXPORT TimedTaskHelper { + public: + explicit TimedTaskHelper(base::SequencedTaskRunner* task_runner); + ~TimedTaskHelper(); + + bool IsRunning() const; + void Start(const tracked_objects::Location& posted_from, + base::TimeDelta delay, + const base::Closure& user_task); + void Reset(); + + private: + struct Tracker; + static void Fired(scoped_ptr<Tracker> tracker); + + void OnFired(scoped_ptr<Tracker> tracker); + void PostDelayedTask(scoped_ptr<Tracker> tracker, base::TimeDelta delay); + + scoped_refptr<base::SequencedTaskRunner> task_runner_; + tracked_objects::Location posted_from_; + base::TimeDelta delay_; + base::Closure user_task_; + + base::TimeTicks desired_run_time_; + + // This is set to non-null and owned by a timer task while timer is running. + Tracker* tracker_; + + DISALLOW_COPY_AND_ASSIGN(TimedTaskHelper); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_TIMED_TASK_HELPER_H_ diff --git a/storage/browser/fileapi/transient_file_util.cc b/storage/browser/fileapi/transient_file_util.cc new file mode 100644 index 0000000..c0c2a6b --- /dev/null +++ b/storage/browser/fileapi/transient_file_util.cc @@ -0,0 +1,55 @@ +// Copyright (c) 2013 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 "storage/browser/fileapi/transient_file_util.h" + +#include <string> + +#include "base/bind.h" +#include "base/files/file_path.h" +#include "storage/browser/fileapi/file_system_operation_context.h" +#include "storage/browser/fileapi/file_system_url.h" +#include "storage/browser/fileapi/isolated_context.h" + +using storage::ScopedFile; + +namespace storage { + +namespace { + +void RevokeFileSystem(const std::string& filesystem_id, + const base::FilePath& /*path*/) { + IsolatedContext::GetInstance()->RevokeFileSystem(filesystem_id); +} + +} // namespace + +ScopedFile TransientFileUtil::CreateSnapshotFile( + FileSystemOperationContext* context, + const FileSystemURL& url, + base::File::Error* error, + base::File::Info* file_info, + base::FilePath* platform_path) { + DCHECK(file_info); + *error = GetFileInfo(context, url, file_info, platform_path); + if (*error == base::File::FILE_OK && file_info->is_directory) + *error = base::File::FILE_ERROR_NOT_A_FILE; + if (*error != base::File::FILE_OK) + return ScopedFile(); + + // Sets-up a transient filesystem. + DCHECK(!platform_path->empty()); + DCHECK(!url.filesystem_id().empty()); + + ScopedFile scoped_file( + *platform_path, + ScopedFile::DELETE_ON_SCOPE_OUT, + context->task_runner()); + scoped_file.AddScopeOutCallback( + base::Bind(&RevokeFileSystem, url.filesystem_id()), NULL); + + return scoped_file.Pass(); +} + +} // namespace storage diff --git a/storage/browser/fileapi/transient_file_util.h b/storage/browser/fileapi/transient_file_util.h new file mode 100644 index 0000000..16d710e --- /dev/null +++ b/storage/browser/fileapi/transient_file_util.h @@ -0,0 +1,36 @@ +// Copyright (c) 2013 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. + +#ifndef STORAGE_BROWSER_FILEAPI_TRANSIENT_FILE_UTIL_H_ +#define STORAGE_BROWSER_FILEAPI_TRANSIENT_FILE_UTIL_H_ + +#include "base/memory/scoped_ptr.h" +#include "storage/browser/fileapi/local_file_util.h" +#include "storage/browser/storage_browser_export.h" + +namespace storage { + +class FileSystemOperationContext; + +class STORAGE_EXPORT_PRIVATE TransientFileUtil + : public LocalFileUtil { + public: + TransientFileUtil() {} + virtual ~TransientFileUtil() {} + + // LocalFileUtil overrides. + virtual storage::ScopedFile CreateSnapshotFile( + FileSystemOperationContext* context, + const FileSystemURL& url, + base::File::Error* error, + base::File::Info* file_info, + base::FilePath* platform_path) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(TransientFileUtil); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_TRANSIENT_FILE_UTIL_H_ diff --git a/storage/browser/fileapi/watcher_manager.h b/storage/browser/fileapi/watcher_manager.h new file mode 100644 index 0000000..40f3733 --- /dev/null +++ b/storage/browser/fileapi/watcher_manager.h @@ -0,0 +1,67 @@ +// Copyright 2014 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. + +#ifndef STORAGE_BROWSER_FILEAPI_WATCHER_MANAGER_H_ +#define STORAGE_BROWSER_FILEAPI_WATCHER_MANAGER_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/callback_forward.h" +#include "base/files/file.h" + +namespace base { +class Time; +} + +namespace storage { + +class FileSystemOperationContext; +class FileSystemURL; + +// An interface for providing entry observing capability for file system +// backends. +// +// It is NOT valid to give null callback to this class, and implementors +// can assume that they don't get any null callbacks. +class WatcherManager { + public: + typedef base::Callback<void(base::File::Error result)> StatusCallback; + + // Observes watched entries. + class Observer { + public: + Observer() {} + virtual ~Observer() {} + + // Notifies about an entry represented by |url| being changed. + virtual void OnEntryChanged(const FileSystemURL& url) = 0; + + // Notifies about an entry represented by |url| being removed. + virtual void OnEntryRemoved(const FileSystemURL& url) = 0; + }; + + virtual ~WatcherManager() {} + + virtual void AddObserver(Observer* observer) = 0; + virtual void RemoveObserver(Observer* observer) = 0; + virtual bool HasObserver(Observer* observer) const = 0; + + // Observes a directory entry. If the |recursive| mode is not supported then + // FILE_ERROR_INVALID_OPERATION must be returned as an error. If the |url| is + // already watched, or setting up the watcher fails, then |callback| + // must be called with a specific error code. Otherwise |callback| must be + // called with the FILE_OK error code. + virtual void WatchDirectory(const FileSystemURL& url, + bool recursive, + const StatusCallback& callback) = 0; + + // Stops observing an entry represented by |url|. + virtual void UnwatchEntry(const FileSystemURL& url, + const StatusCallback& callback) = 0; +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILEAPI_WATCHER_MANAGER_H_ diff --git a/storage/browser/quota/OWNERS b/storage/browser/quota/OWNERS new file mode 100644 index 0000000..66ba5d2 --- /dev/null +++ b/storage/browser/quota/OWNERS @@ -0,0 +1 @@ +tzik@chromium.org diff --git a/storage/browser/quota/quota_callbacks.h b/storage/browser/quota/quota_callbacks.h new file mode 100644 index 0000000..b499922 --- /dev/null +++ b/storage/browser/quota/quota_callbacks.h @@ -0,0 +1,129 @@ +// Copyright 2013 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. + +#ifndef STORAGE_BROWSER_QUOTA_QUOTA_CALLBACKS_H_ +#define STORAGE_BROWSER_QUOTA_QUOTA_CALLBACKS_H_ + +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/tuple.h" +#include "storage/common/quota/quota_status_code.h" +#include "storage/common/quota/quota_types.h" + +class GURL; + +namespace storage { + +struct UsageInfo; +typedef std::vector<UsageInfo> UsageInfoEntries; + +// Common callback types that are used throughout in the quota module. +typedef base::Callback<void(int64 usage, + int64 unlimited_usage)> GlobalUsageCallback; +typedef base::Callback<void(QuotaStatusCode status, int64 quota)> QuotaCallback; +typedef base::Callback<void(int64 usage)> UsageCallback; +typedef base::Callback<void(QuotaStatusCode, int64)> AvailableSpaceCallback; +typedef base::Callback<void(QuotaStatusCode)> StatusCallback; +typedef base::Callback<void(const std::set<GURL>& origins, + StorageType type)> GetOriginsCallback; +typedef base::Callback<void(const UsageInfoEntries&)> GetUsageInfoCallback; + +template<typename CallbackType, typename Args> +void DispatchToCallback(const CallbackType& callback, + const Args& args) { + DispatchToMethod(&callback, &CallbackType::Run, args); +} + +// Simple template wrapper for a callback queue. +template <typename CallbackType, typename Args> +class CallbackQueue { + public: + // Returns true if the given |callback| is the first one added to the queue. + bool Add(const CallbackType& callback) { + callbacks_.push_back(callback); + return (callbacks_.size() == 1); + } + + bool HasCallbacks() const { + return !callbacks_.empty(); + } + + // Runs the callbacks added to the queue and clears the queue. + void Run(const Args& args) { + typedef typename std::vector<CallbackType>::iterator iterator; + for (iterator iter = callbacks_.begin(); + iter != callbacks_.end(); ++iter) + DispatchToCallback(*iter, args); + callbacks_.clear(); + } + + private: + std::vector<CallbackType> callbacks_; +}; + +typedef CallbackQueue<GlobalUsageCallback, + Tuple2<int64, int64> > + GlobalUsageCallbackQueue; +typedef CallbackQueue<UsageCallback, Tuple1<int64> > + UsageCallbackQueue; +typedef CallbackQueue<AvailableSpaceCallback, + Tuple2<QuotaStatusCode, int64> > + AvailableSpaceCallbackQueue; +typedef CallbackQueue<QuotaCallback, + Tuple2<QuotaStatusCode, int64> > + GlobalQuotaCallbackQueue; +typedef CallbackQueue<base::Closure, Tuple0> ClosureQueue; + +template <typename CallbackType, typename Key, typename Args> +class CallbackQueueMap { + public: + typedef CallbackQueue<CallbackType, Args> CallbackQueueType; + typedef std::map<Key, CallbackQueueType> CallbackMap; + typedef typename CallbackMap::iterator iterator; + + bool Add(const Key& key, const CallbackType& callback) { + return callback_map_[key].Add(callback); + } + + bool HasCallbacks(const Key& key) const { + return (callback_map_.find(key) != callback_map_.end()); + } + + bool HasAnyCallbacks() const { + return !callback_map_.empty(); + } + + iterator Begin() { return callback_map_.begin(); } + iterator End() { return callback_map_.end(); } + + void Clear() { callback_map_.clear(); } + + // Runs the callbacks added for the given |key| and clears the key + // from the map. + void Run(const Key& key, const Args& args) { + if (!this->HasCallbacks(key)) + return; + CallbackQueueType& queue = callback_map_[key]; + queue.Run(args); + callback_map_.erase(key); + } + + private: + CallbackMap callback_map_; +}; + +typedef CallbackQueueMap<UsageCallback, std::string, Tuple1<int64> > + HostUsageCallbackMap; +typedef CallbackQueueMap<QuotaCallback, std::string, + Tuple2<QuotaStatusCode, int64> > + HostQuotaCallbackMap; + +} // namespace storage + +#endif // STORAGE_QUOTA_QUOTA_TYPES_H_ diff --git a/storage/browser/quota/quota_client.h b/storage/browser/quota/quota_client.h new file mode 100644 index 0000000..8a6a294 --- /dev/null +++ b/storage/browser/quota/quota_client.h @@ -0,0 +1,81 @@ +// Copyright 2013 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. + +#ifndef STORAGE_BROWSER_QUOTA_QUOTA_CLIENT_H_ +#define STORAGE_BROWSER_QUOTA_QUOTA_CLIENT_H_ + +#include <list> +#include <set> +#include <string> + +#include "base/callback.h" +#include "storage/browser/storage_browser_export.h" +#include "storage/common/quota/quota_types.h" +#include "url/gurl.h" + +namespace storage { + +// An abstract interface for quota manager clients. +// Each storage API must provide an implementation of this interface and +// register it to the quota manager. +// All the methods are assumed to be called on the IO thread in the browser. +class STORAGE_EXPORT QuotaClient { + public: + typedef base::Callback<void(int64 usage)> GetUsageCallback; + typedef base::Callback<void(const std::set<GURL>& origins)> + GetOriginsCallback; + typedef base::Callback<void(QuotaStatusCode status)> DeletionCallback; + + virtual ~QuotaClient() {} + + enum ID { + kUnknown = 1 << 0, + kFileSystem = 1 << 1, + kDatabase = 1 << 2, + kAppcache = 1 << 3, + kIndexedDatabase = 1 << 4, + kAllClientsMask = -1, + }; + + virtual ID id() const = 0; + + // Called when the quota manager is destroyed. + virtual void OnQuotaManagerDestroyed() = 0; + + // Called by the QuotaManager. + // Gets the amount of data stored in the storage specified by + // |origin_url| and |type|. + // Note it is safe to fire the callback after the QuotaClient is destructed. + virtual void GetOriginUsage(const GURL& origin_url, + StorageType type, + const GetUsageCallback& callback) = 0; + + // Called by the QuotaManager. + // Returns a list of origins that has data in the |type| storage. + // Note it is safe to fire the callback after the QuotaClient is destructed. + virtual void GetOriginsForType(StorageType type, + const GetOriginsCallback& callback) = 0; + + // Called by the QuotaManager. + // Returns a list of origins that match the |host|. + // Note it is safe to fire the callback after the QuotaClient is destructed. + virtual void GetOriginsForHost(StorageType type, + const std::string& host, + const GetOriginsCallback& callback) = 0; + + // Called by the QuotaManager. + // Note it is safe to fire the callback after the QuotaClient is destructed. + virtual void DeleteOriginData(const GURL& origin, + StorageType type, + const DeletionCallback& callback) = 0; + + virtual bool DoesSupport(StorageType type) const = 0; +}; + +// TODO(dmikurube): Replace it to std::vector for efficiency. +typedef std::list<QuotaClient*> QuotaClientList; + +} // namespace storage + +#endif // STORAGE_BROWSER_QUOTA_QUOTA_CLIENT_H_ diff --git a/storage/browser/quota/quota_database.cc b/storage/browser/quota/quota_database.cc new file mode 100644 index 0000000..d6c94b3 --- /dev/null +++ b/storage/browser/quota/quota_database.cc @@ -0,0 +1,657 @@ +// Copyright 2013 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 "storage/browser/quota/quota_database.h" + +#include <string> +#include <vector> + +#include "base/auto_reset.h" +#include "base/bind.h" +#include "base/files/file_util.h" +#include "base/time/time.h" +#include "sql/connection.h" +#include "sql/meta_table.h" +#include "sql/statement.h" +#include "sql/transaction.h" +#include "storage/browser/quota/special_storage_policy.h" +#include "url/gurl.h" + +namespace storage { +namespace { + +// Definitions for database schema. + +const int kCurrentVersion = 4; +const int kCompatibleVersion = 2; + +const char kHostQuotaTable[] = "HostQuotaTable"; +const char kOriginInfoTable[] = "OriginInfoTable"; +const char kIsOriginTableBootstrapped[] = "IsOriginTableBootstrapped"; + +bool VerifyValidQuotaConfig(const char* key) { + return (key != NULL && + (!strcmp(key, QuotaDatabase::kDesiredAvailableSpaceKey) || + !strcmp(key, QuotaDatabase::kTemporaryQuotaOverrideKey))); +} + +const int kCommitIntervalMs = 30000; + +} // anonymous namespace + +// static +const char QuotaDatabase::kDesiredAvailableSpaceKey[] = "DesiredAvailableSpace"; +const char QuotaDatabase::kTemporaryQuotaOverrideKey[] = + "TemporaryQuotaOverride"; + +const QuotaDatabase::TableSchema QuotaDatabase::kTables[] = { + { kHostQuotaTable, + "(host TEXT NOT NULL," + " type INTEGER NOT NULL," + " quota INTEGER DEFAULT 0," + " UNIQUE(host, type))" }, + { kOriginInfoTable, + "(origin TEXT NOT NULL," + " type INTEGER NOT NULL," + " used_count INTEGER DEFAULT 0," + " last_access_time INTEGER DEFAULT 0," + " last_modified_time INTEGER DEFAULT 0," + " UNIQUE(origin, type))" }, +}; + +// static +const QuotaDatabase::IndexSchema QuotaDatabase::kIndexes[] = { + { "HostIndex", + kHostQuotaTable, + "(host)", + false }, + { "OriginInfoIndex", + kOriginInfoTable, + "(origin)", + false }, + { "OriginLastAccessTimeIndex", + kOriginInfoTable, + "(last_access_time)", + false }, + { "OriginLastModifiedTimeIndex", + kOriginInfoTable, + "(last_modified_time)", + false }, +}; + +struct QuotaDatabase::QuotaTableImporter { + bool Append(const QuotaTableEntry& entry) { + entries.push_back(entry); + return true; + } + std::vector<QuotaTableEntry> entries; +}; + +// Clang requires explicit out-of-line constructors for them. +QuotaDatabase::QuotaTableEntry::QuotaTableEntry() + : type(kStorageTypeUnknown), + quota(0) { +} + +QuotaDatabase::QuotaTableEntry::QuotaTableEntry( + const std::string& host, + StorageType type, + int64 quota) + : host(host), + type(type), + quota(quota) { +} + +QuotaDatabase::OriginInfoTableEntry::OriginInfoTableEntry() + : type(kStorageTypeUnknown), + used_count(0) { +} + +QuotaDatabase::OriginInfoTableEntry::OriginInfoTableEntry( + const GURL& origin, + StorageType type, + int used_count, + const base::Time& last_access_time, + const base::Time& last_modified_time) + : origin(origin), + type(type), + used_count(used_count), + last_access_time(last_access_time), + last_modified_time(last_modified_time) { +} + +// QuotaDatabase ------------------------------------------------------------ +QuotaDatabase::QuotaDatabase(const base::FilePath& path) + : db_file_path_(path), + is_recreating_(false), + is_disabled_(false) { +} + +QuotaDatabase::~QuotaDatabase() { + if (db_) { + db_->CommitTransaction(); + } +} + +void QuotaDatabase::CloseConnection() { + meta_table_.reset(); + db_.reset(); +} + +bool QuotaDatabase::GetHostQuota( + const std::string& host, StorageType type, int64* quota) { + DCHECK(quota); + if (!LazyOpen(false)) + return false; + + const char* kSql = + "SELECT quota" + " FROM HostQuotaTable" + " WHERE host = ? AND type = ?"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindString(0, host); + statement.BindInt(1, static_cast<int>(type)); + + if (!statement.Step()) + return false; + + *quota = statement.ColumnInt64(0); + return true; +} + +bool QuotaDatabase::SetHostQuota( + const std::string& host, StorageType type, int64 quota) { + DCHECK_GE(quota, 0); + if (!LazyOpen(true)) + return false; + + const char* kSql = + "INSERT OR REPLACE INTO HostQuotaTable" + " (quota, host, type)" + " VALUES (?, ?, ?)"; + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindInt64(0, quota); + statement.BindString(1, host); + statement.BindInt(2, static_cast<int>(type)); + + if (!statement.Run()) + return false; + + ScheduleCommit(); + return true; +} + +bool QuotaDatabase::SetOriginLastAccessTime( + const GURL& origin, StorageType type, base::Time last_access_time) { + if (!LazyOpen(true)) + return false; + + sql::Statement statement; + + int used_count = 1; + if (FindOriginUsedCount(origin, type, &used_count)) { + ++used_count; + const char* kSql = + "UPDATE OriginInfoTable" + " SET used_count = ?, last_access_time = ?" + " WHERE origin = ? AND type = ?"; + statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + } else { + const char* kSql = + "INSERT INTO OriginInfoTable" + " (used_count, last_access_time, origin, type)" + " VALUES (?, ?, ?, ?)"; + statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + } + statement.BindInt(0, used_count); + statement.BindInt64(1, last_access_time.ToInternalValue()); + statement.BindString(2, origin.spec()); + statement.BindInt(3, static_cast<int>(type)); + + if (!statement.Run()) + return false; + + ScheduleCommit(); + return true; +} + +bool QuotaDatabase::SetOriginLastModifiedTime( + const GURL& origin, StorageType type, base::Time last_modified_time) { + if (!LazyOpen(true)) + return false; + + sql::Statement statement; + + int dummy; + if (FindOriginUsedCount(origin, type, &dummy)) { + const char* kSql = + "UPDATE OriginInfoTable" + " SET last_modified_time = ?" + " WHERE origin = ? AND type = ?"; + statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + } else { + const char* kSql = + "INSERT INTO OriginInfoTable" + " (last_modified_time, origin, type) VALUES (?, ?, ?)"; + statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + } + statement.BindInt64(0, last_modified_time.ToInternalValue()); + statement.BindString(1, origin.spec()); + statement.BindInt(2, static_cast<int>(type)); + + if (!statement.Run()) + return false; + + ScheduleCommit(); + return true; +} + +bool QuotaDatabase::RegisterInitialOriginInfo( + const std::set<GURL>& origins, StorageType type) { + if (!LazyOpen(true)) + return false; + + typedef std::set<GURL>::const_iterator itr_type; + for (itr_type itr = origins.begin(), end = origins.end(); + itr != end; ++itr) { + const char* kSql = + "INSERT OR IGNORE INTO OriginInfoTable" + " (origin, type) VALUES (?, ?)"; + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindString(0, itr->spec()); + statement.BindInt(1, static_cast<int>(type)); + + if (!statement.Run()) + return false; + } + + ScheduleCommit(); + return true; +} + +bool QuotaDatabase::DeleteHostQuota( + const std::string& host, StorageType type) { + if (!LazyOpen(false)) + return false; + + const char* kSql = + "DELETE FROM HostQuotaTable" + " WHERE host = ? AND type = ?"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindString(0, host); + statement.BindInt(1, static_cast<int>(type)); + + if (!statement.Run()) + return false; + + ScheduleCommit(); + return true; +} + +bool QuotaDatabase::DeleteOriginInfo( + const GURL& origin, StorageType type) { + if (!LazyOpen(false)) + return false; + + const char* kSql = + "DELETE FROM OriginInfoTable" + " WHERE origin = ? AND type = ?"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindString(0, origin.spec()); + statement.BindInt(1, static_cast<int>(type)); + + if (!statement.Run()) + return false; + + ScheduleCommit(); + return true; +} + +bool QuotaDatabase::GetQuotaConfigValue(const char* key, int64* value) { + if (!LazyOpen(false)) + return false; + DCHECK(VerifyValidQuotaConfig(key)); + return meta_table_->GetValue(key, value); +} + +bool QuotaDatabase::SetQuotaConfigValue(const char* key, int64 value) { + if (!LazyOpen(true)) + return false; + DCHECK(VerifyValidQuotaConfig(key)); + return meta_table_->SetValue(key, value); +} + +bool QuotaDatabase::GetLRUOrigin( + StorageType type, + const std::set<GURL>& exceptions, + SpecialStoragePolicy* special_storage_policy, + GURL* origin) { + DCHECK(origin); + if (!LazyOpen(false)) + return false; + + const char* kSql = "SELECT origin FROM OriginInfoTable" + " WHERE type = ?" + " ORDER BY last_access_time ASC"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindInt(0, static_cast<int>(type)); + + while (statement.Step()) { + GURL url(statement.ColumnString(0)); + if (exceptions.find(url) != exceptions.end()) + continue; + if (special_storage_policy && + special_storage_policy->IsStorageUnlimited(url)) + continue; + *origin = url; + return true; + } + + *origin = GURL(); + return statement.Succeeded(); +} + +bool QuotaDatabase::GetOriginsModifiedSince( + StorageType type, std::set<GURL>* origins, base::Time modified_since) { + DCHECK(origins); + if (!LazyOpen(false)) + return false; + + const char* kSql = "SELECT origin FROM OriginInfoTable" + " WHERE type = ? AND last_modified_time >= ?"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindInt(0, static_cast<int>(type)); + statement.BindInt64(1, modified_since.ToInternalValue()); + + origins->clear(); + while (statement.Step()) + origins->insert(GURL(statement.ColumnString(0))); + + return statement.Succeeded(); +} + +bool QuotaDatabase::IsOriginDatabaseBootstrapped() { + if (!LazyOpen(true)) + return false; + + int flag = 0; + return meta_table_->GetValue(kIsOriginTableBootstrapped, &flag) && flag; +} + +bool QuotaDatabase::SetOriginDatabaseBootstrapped(bool bootstrap_flag) { + if (!LazyOpen(true)) + return false; + + return meta_table_->SetValue(kIsOriginTableBootstrapped, bootstrap_flag); +} + +void QuotaDatabase::Commit() { + if (!db_) + return; + + if (timer_.IsRunning()) + timer_.Stop(); + + db_->CommitTransaction(); + db_->BeginTransaction(); +} + +void QuotaDatabase::ScheduleCommit() { + if (timer_.IsRunning()) + return; + timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(kCommitIntervalMs), + this, &QuotaDatabase::Commit); +} + +bool QuotaDatabase::FindOriginUsedCount( + const GURL& origin, StorageType type, int* used_count) { + DCHECK(used_count); + if (!LazyOpen(false)) + return false; + + const char* kSql = + "SELECT used_count FROM OriginInfoTable" + " WHERE origin = ? AND type = ?"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindString(0, origin.spec()); + statement.BindInt(1, static_cast<int>(type)); + + if (!statement.Step()) + return false; + + *used_count = statement.ColumnInt(0); + return true; +} + +bool QuotaDatabase::LazyOpen(bool create_if_needed) { + if (db_) + return true; + + // If we tried and failed once, don't try again in the same session + // to avoid creating an incoherent mess on disk. + if (is_disabled_) + return false; + + bool in_memory_only = db_file_path_.empty(); + if (!create_if_needed && + (in_memory_only || !base::PathExists(db_file_path_))) { + return false; + } + + db_.reset(new sql::Connection); + meta_table_.reset(new sql::MetaTable); + + db_->set_histogram_tag("Quota"); + + bool opened = false; + if (in_memory_only) { + opened = db_->OpenInMemory(); + } else if (!base::CreateDirectory(db_file_path_.DirName())) { + LOG(ERROR) << "Failed to create quota database directory."; + } else { + opened = db_->Open(db_file_path_); + if (opened) + db_->Preload(); + } + + if (!opened || !EnsureDatabaseVersion()) { + LOG(ERROR) << "Failed to open the quota database."; + is_disabled_ = true; + db_.reset(); + meta_table_.reset(); + return false; + } + + // Start a long-running transaction. + db_->BeginTransaction(); + + return true; +} + +bool QuotaDatabase::EnsureDatabaseVersion() { + static const size_t kTableCount = ARRAYSIZE_UNSAFE(kTables); + static const size_t kIndexCount = ARRAYSIZE_UNSAFE(kIndexes); + if (!sql::MetaTable::DoesTableExist(db_.get())) + return CreateSchema(db_.get(), meta_table_.get(), + kCurrentVersion, kCompatibleVersion, + kTables, kTableCount, + kIndexes, kIndexCount); + + if (!meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion)) + return false; + + if (meta_table_->GetCompatibleVersionNumber() > kCurrentVersion) { + LOG(WARNING) << "Quota database is too new."; + return false; + } + + if (meta_table_->GetVersionNumber() < kCurrentVersion) { + if (!UpgradeSchema(meta_table_->GetVersionNumber())) + return ResetSchema(); + } + +#ifndef NDEBUG + DCHECK(sql::MetaTable::DoesTableExist(db_.get())); + for (size_t i = 0; i < kTableCount; ++i) { + DCHECK(db_->DoesTableExist(kTables[i].table_name)); + } +#endif + + return true; +} + +// static +bool QuotaDatabase::CreateSchema( + sql::Connection* database, + sql::MetaTable* meta_table, + int schema_version, int compatible_version, + const TableSchema* tables, size_t tables_size, + const IndexSchema* indexes, size_t indexes_size) { + // TODO(kinuko): Factor out the common code to create databases. + sql::Transaction transaction(database); + if (!transaction.Begin()) + return false; + + if (!meta_table->Init(database, schema_version, compatible_version)) + return false; + + for (size_t i = 0; i < tables_size; ++i) { + std::string sql("CREATE TABLE "); + sql += tables[i].table_name; + sql += tables[i].columns; + if (!database->Execute(sql.c_str())) { + VLOG(1) << "Failed to execute " << sql; + return false; + } + } + + for (size_t i = 0; i < indexes_size; ++i) { + std::string sql; + if (indexes[i].unique) + sql += "CREATE UNIQUE INDEX "; + else + sql += "CREATE INDEX "; + sql += indexes[i].index_name; + sql += " ON "; + sql += indexes[i].table_name; + sql += indexes[i].columns; + if (!database->Execute(sql.c_str())) { + VLOG(1) << "Failed to execute " << sql; + return false; + } + } + + return transaction.Commit(); +} + +bool QuotaDatabase::ResetSchema() { + DCHECK(!db_file_path_.empty()); + DCHECK(base::PathExists(db_file_path_)); + VLOG(1) << "Deleting existing quota data and starting over."; + + db_.reset(); + meta_table_.reset(); + + if (!sql::Connection::Delete(db_file_path_)) + return false; + + // So we can't go recursive. + if (is_recreating_) + return false; + + base::AutoReset<bool> auto_reset(&is_recreating_, true); + return LazyOpen(true); +} + +bool QuotaDatabase::UpgradeSchema(int current_version) { + if (current_version == 2) { + QuotaTableImporter importer; + typedef std::vector<QuotaTableEntry> QuotaTableEntries; + if (!DumpQuotaTable(base::Bind(&QuotaTableImporter::Append, + base::Unretained(&importer)))) { + return false; + } + ResetSchema(); + for (QuotaTableEntries::const_iterator iter = importer.entries.begin(); + iter != importer.entries.end(); ++iter) { + if (!SetHostQuota(iter->host, iter->type, iter->quota)) + return false; + } + Commit(); + return true; + } + return false; +} + +bool QuotaDatabase::DumpQuotaTable(const QuotaTableCallback& callback) { + if (!LazyOpen(true)) + return false; + + const char* kSql = "SELECT * FROM HostQuotaTable"; + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + + while (statement.Step()) { + QuotaTableEntry entry = QuotaTableEntry( + statement.ColumnString(0), + static_cast<StorageType>(statement.ColumnInt(1)), + statement.ColumnInt64(2)); + + if (!callback.Run(entry)) + return true; + } + + return statement.Succeeded(); +} + +bool QuotaDatabase::DumpOriginInfoTable( + const OriginInfoTableCallback& callback) { + + if (!LazyOpen(true)) + return false; + + const char* kSql = "SELECT * FROM OriginInfoTable"; + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + + while (statement.Step()) { + OriginInfoTableEntry entry( + GURL(statement.ColumnString(0)), + static_cast<StorageType>(statement.ColumnInt(1)), + statement.ColumnInt(2), + base::Time::FromInternalValue(statement.ColumnInt64(3)), + base::Time::FromInternalValue(statement.ColumnInt64(4))); + + if (!callback.Run(entry)) + return true; + } + + return statement.Succeeded(); +} + +bool operator<(const QuotaDatabase::QuotaTableEntry& lhs, + const QuotaDatabase::QuotaTableEntry& rhs) { + if (lhs.host < rhs.host) return true; + if (rhs.host < lhs.host) return false; + if (lhs.type < rhs.type) return true; + if (rhs.type < lhs.type) return false; + return lhs.quota < rhs.quota; +} + +bool operator<(const QuotaDatabase::OriginInfoTableEntry& lhs, + const QuotaDatabase::OriginInfoTableEntry& rhs) { + if (lhs.origin < rhs.origin) return true; + if (rhs.origin < lhs.origin) return false; + if (lhs.type < rhs.type) return true; + if (rhs.type < lhs.type) return false; + if (lhs.used_count < rhs.used_count) return true; + if (rhs.used_count < lhs.used_count) return false; + return lhs.last_access_time < rhs.last_access_time; +} + +} // namespace storage diff --git a/storage/browser/quota/quota_database.h b/storage/browser/quota/quota_database.h new file mode 100644 index 0000000..6ca5651 --- /dev/null +++ b/storage/browser/quota/quota_database.h @@ -0,0 +1,190 @@ +// Copyright 2013 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. + +#ifndef STORAGE_BROWSER_QUOTA_QUOTA_DATABASE_H_ +#define STORAGE_BROWSER_QUOTA_QUOTA_DATABASE_H_ + +#include <set> +#include <string> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/time/time.h" +#include "base/timer/timer.h" +#include "storage/browser/storage_browser_export.h" +#include "storage/common/quota/quota_types.h" +#include "url/gurl.h" + +namespace content { +class QuotaDatabaseTest; +} + +namespace sql { +class Connection; +class MetaTable; +} + +class GURL; + +namespace storage { + +class SpecialStoragePolicy; + +// All the methods of this class must run on the DB thread. +class STORAGE_EXPORT_PRIVATE QuotaDatabase { + public: + // Constants for {Get,Set}QuotaConfigValue keys. + static const char kDesiredAvailableSpaceKey[]; + static const char kTemporaryQuotaOverrideKey[]; + + // If 'path' is empty, an in memory database will be used. + explicit QuotaDatabase(const base::FilePath& path); + ~QuotaDatabase(); + + void CloseConnection(); + + bool GetHostQuota(const std::string& host, StorageType type, int64* quota); + bool SetHostQuota(const std::string& host, StorageType type, int64 quota); + bool DeleteHostQuota(const std::string& host, StorageType type); + + bool SetOriginLastAccessTime(const GURL& origin, + StorageType type, + base::Time last_access_time); + + bool SetOriginLastModifiedTime(const GURL& origin, + StorageType type, + base::Time last_modified_time); + + // Register initial |origins| info |type| to the database. + // This method is assumed to be called only after the installation or + // the database schema reset. + bool RegisterInitialOriginInfo( + const std::set<GURL>& origins, StorageType type); + + bool DeleteOriginInfo(const GURL& origin, StorageType type); + + bool GetQuotaConfigValue(const char* key, int64* value); + bool SetQuotaConfigValue(const char* key, int64 value); + + // Sets |origin| to the least recently used origin of origins not included + // in |exceptions| and not granted the special unlimited storage right. + // It returns false when it failed in accessing the database. + // |origin| is set to empty when there is no matching origin. + bool GetLRUOrigin(StorageType type, + const std::set<GURL>& exceptions, + SpecialStoragePolicy* special_storage_policy, + GURL* origin); + + // Populates |origins| with the ones that have been modified since + // the |modified_since|. + bool GetOriginsModifiedSince(StorageType type, + std::set<GURL>* origins, + base::Time modified_since); + + // Returns false if SetOriginDatabaseBootstrapped has never + // been called before, which means existing origins may not have been + // registered. + bool IsOriginDatabaseBootstrapped(); + bool SetOriginDatabaseBootstrapped(bool bootstrap_flag); + + private: + struct STORAGE_EXPORT_PRIVATE QuotaTableEntry { + QuotaTableEntry(); + QuotaTableEntry( + const std::string& host, + StorageType type, + int64 quota); + std::string host; + StorageType type; + int64 quota; + }; + friend STORAGE_EXPORT_PRIVATE bool operator <( + const QuotaTableEntry& lhs, const QuotaTableEntry& rhs); + + struct STORAGE_EXPORT_PRIVATE OriginInfoTableEntry { + OriginInfoTableEntry(); + OriginInfoTableEntry( + const GURL& origin, + StorageType type, + int used_count, + const base::Time& last_access_time, + const base::Time& last_modified_time); + GURL origin; + StorageType type; + int used_count; + base::Time last_access_time; + base::Time last_modified_time; + }; + friend STORAGE_EXPORT_PRIVATE bool operator <( + const OriginInfoTableEntry& lhs, const OriginInfoTableEntry& rhs); + + // Structures used for CreateSchema. + struct TableSchema { + const char* table_name; + const char* columns; + }; + struct IndexSchema { + const char* index_name; + const char* table_name; + const char* columns; + bool unique; + }; + + typedef base::Callback<bool (const QuotaTableEntry&)> QuotaTableCallback; + typedef base::Callback<bool (const OriginInfoTableEntry&)> + OriginInfoTableCallback; + + struct QuotaTableImporter; + + // For long-running transactions support. We always keep a transaction open + // so that multiple transactions can be batched. They are flushed + // with a delay after a modification has been made. We support neither + // nested transactions nor rollback (as we don't need them for now). + void Commit(); + void ScheduleCommit(); + + bool FindOriginUsedCount(const GURL& origin, + StorageType type, + int* used_count); + + bool LazyOpen(bool create_if_needed); + bool EnsureDatabaseVersion(); + bool ResetSchema(); + bool UpgradeSchema(int current_version); + + static bool CreateSchema( + sql::Connection* database, + sql::MetaTable* meta_table, + int schema_version, int compatible_version, + const TableSchema* tables, size_t tables_size, + const IndexSchema* indexes, size_t indexes_size); + + // |callback| may return false to stop reading data. + bool DumpQuotaTable(const QuotaTableCallback& callback); + bool DumpOriginInfoTable(const OriginInfoTableCallback& callback); + + base::FilePath db_file_path_; + + scoped_ptr<sql::Connection> db_; + scoped_ptr<sql::MetaTable> meta_table_; + bool is_recreating_; + bool is_disabled_; + + base::OneShotTimer<QuotaDatabase> timer_; + + friend class content::QuotaDatabaseTest; + friend class QuotaManager; + + static const TableSchema kTables[]; + static const IndexSchema kIndexes[]; + + DISALLOW_COPY_AND_ASSIGN(QuotaDatabase); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_QUOTA_QUOTA_DATABASE_H_ diff --git a/storage/browser/quota/quota_manager.cc b/storage/browser/quota/quota_manager.cc new file mode 100644 index 0000000..e0f3b14 --- /dev/null +++ b/storage/browser/quota/quota_manager.cc @@ -0,0 +1,1631 @@ +// Copyright 2013 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 "storage/browser/quota/quota_manager.h" + +#include <algorithm> +#include <deque> +#include <functional> +#include <set> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback.h" +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/metrics/histogram.h" +#include "base/sequenced_task_runner.h" +#include "base/single_thread_task_runner.h" +#include "base/strings/string_number_conversions.h" +#include "base/sys_info.h" +#include "base/task_runner_util.h" +#include "base/time/time.h" +#include "net/base/net_util.h" +#include "storage/browser/quota/quota_database.h" +#include "storage/browser/quota/quota_manager_proxy.h" +#include "storage/browser/quota/quota_temporary_storage_evictor.h" +#include "storage/browser/quota/storage_monitor.h" +#include "storage/browser/quota/usage_tracker.h" +#include "storage/common/quota/quota_types.h" + +#define UMA_HISTOGRAM_MBYTES(name, sample) \ + UMA_HISTOGRAM_CUSTOM_COUNTS( \ + (name), static_cast<int>((sample) / kMBytes), \ + 1, 10 * 1024 * 1024 /* 10TB */, 100) + +namespace storage { + +namespace { + +const int64 kMBytes = 1024 * 1024; +const int kMinutesInMilliSeconds = 60 * 1000; + +const int64 kReportHistogramInterval = 60 * 60 * 1000; // 1 hour +const double kTemporaryQuotaRatioToAvail = 1.0 / 3.0; // 33% + +} // namespace + +// Arbitrary for now, but must be reasonably small so that +// in-memory databases can fit. +// TODO(kinuko): Refer SysInfo::AmountOfPhysicalMemory() to determine this. +const int64 QuotaManager::kIncognitoDefaultQuotaLimit = 100 * kMBytes; + +const int64 QuotaManager::kNoLimit = kint64max; + +const int QuotaManager::kPerHostTemporaryPortion = 5; // 20% + +// Cap size for per-host persistent quota determined by the histogram. +// This is a bit lax value because the histogram says nothing about per-host +// persistent storage usage and we determined by global persistent storage +// usage that is less than 10GB for almost all users. +const int64 QuotaManager::kPerHostPersistentQuotaLimit = 10 * 1024 * kMBytes; + +const char QuotaManager::kDatabaseName[] = "QuotaManager"; + +const int QuotaManager::kThresholdOfErrorsToBeBlacklisted = 3; + +// Preserve kMinimumPreserveForSystem disk space for system book-keeping +// when returning the quota to unlimited apps/extensions. +// TODO(kinuko): This should be like 10% of the actual disk space. +// For now we simply use a constant as getting the disk size needs +// platform-dependent code. (http://crbug.com/178976) +int64 QuotaManager::kMinimumPreserveForSystem = 1024 * kMBytes; + +const int QuotaManager::kEvictionIntervalInMilliSeconds = + 30 * kMinutesInMilliSeconds; + +// Heuristics: assuming average cloud server allows a few Gigs storage +// on the server side and the storage needs to be shared for user data +// and by multiple apps. +int64 QuotaManager::kSyncableStorageDefaultHostQuota = 500 * kMBytes; + +namespace { + +void CountOriginType(const std::set<GURL>& origins, + SpecialStoragePolicy* policy, + size_t* protected_origins, + size_t* unlimited_origins) { + DCHECK(protected_origins); + DCHECK(unlimited_origins); + *protected_origins = 0; + *unlimited_origins = 0; + if (!policy) + return; + for (std::set<GURL>::const_iterator itr = origins.begin(); + itr != origins.end(); + ++itr) { + if (policy->IsStorageProtected(*itr)) + ++*protected_origins; + if (policy->IsStorageUnlimited(*itr)) + ++*unlimited_origins; + } +} + +bool SetTemporaryGlobalOverrideQuotaOnDBThread(int64* new_quota, + QuotaDatabase* database) { + DCHECK(database); + if (!database->SetQuotaConfigValue( + QuotaDatabase::kTemporaryQuotaOverrideKey, *new_quota)) { + *new_quota = -1; + return false; + } + return true; +} + +bool GetPersistentHostQuotaOnDBThread(const std::string& host, + int64* quota, + QuotaDatabase* database) { + DCHECK(database); + database->GetHostQuota(host, kStorageTypePersistent, quota); + return true; +} + +bool SetPersistentHostQuotaOnDBThread(const std::string& host, + int64* new_quota, + QuotaDatabase* database) { + DCHECK(database); + if (database->SetHostQuota(host, kStorageTypePersistent, *new_quota)) + return true; + *new_quota = 0; + return false; +} + +bool InitializeOnDBThread(int64* temporary_quota_override, + int64* desired_available_space, + QuotaDatabase* database) { + DCHECK(database); + database->GetQuotaConfigValue(QuotaDatabase::kTemporaryQuotaOverrideKey, + temporary_quota_override); + database->GetQuotaConfigValue(QuotaDatabase::kDesiredAvailableSpaceKey, + desired_available_space); + return true; +} + +bool GetLRUOriginOnDBThread(StorageType type, + std::set<GURL>* exceptions, + SpecialStoragePolicy* policy, + GURL* url, + QuotaDatabase* database) { + DCHECK(database); + database->GetLRUOrigin(type, *exceptions, policy, url); + return true; +} + +bool DeleteOriginInfoOnDBThread(const GURL& origin, + StorageType type, + QuotaDatabase* database) { + DCHECK(database); + return database->DeleteOriginInfo(origin, type); +} + +bool InitializeTemporaryOriginsInfoOnDBThread(const std::set<GURL>* origins, + QuotaDatabase* database) { + DCHECK(database); + if (database->IsOriginDatabaseBootstrapped()) + return true; + + // Register existing origins with 0 last time access. + if (database->RegisterInitialOriginInfo(*origins, kStorageTypeTemporary)) { + database->SetOriginDatabaseBootstrapped(true); + return true; + } + return false; +} + +bool UpdateAccessTimeOnDBThread(const GURL& origin, + StorageType type, + base::Time accessed_time, + QuotaDatabase* database) { + DCHECK(database); + return database->SetOriginLastAccessTime(origin, type, accessed_time); +} + +bool UpdateModifiedTimeOnDBThread(const GURL& origin, + StorageType type, + base::Time modified_time, + QuotaDatabase* database) { + DCHECK(database); + return database->SetOriginLastModifiedTime(origin, type, modified_time); +} + +int64 CallSystemGetAmountOfFreeDiskSpace(const base::FilePath& profile_path) { + // Ensure the profile path exists. + if (!base::CreateDirectory(profile_path)) { + LOG(WARNING) << "Create directory failed for path" << profile_path.value(); + return 0; + } + return base::SysInfo::AmountOfFreeDiskSpace(profile_path); +} + +int64 CalculateTemporaryGlobalQuota(int64 global_limited_usage, + int64 available_space) { + DCHECK_GE(global_limited_usage, 0); + int64 avail_space = available_space; + if (avail_space < kint64max - global_limited_usage) { + // We basically calculate the temporary quota by + // [available_space + space_used_for_temp] * kTempQuotaRatio, + // but make sure we'll have no overflow. + avail_space += global_limited_usage; + } + return avail_space * kTemporaryQuotaRatioToAvail; +} + +void DispatchTemporaryGlobalQuotaCallback( + const QuotaCallback& callback, + QuotaStatusCode status, + const UsageAndQuota& usage_and_quota) { + if (status != kQuotaStatusOk) { + callback.Run(status, 0); + return; + } + + callback.Run(status, CalculateTemporaryGlobalQuota( + usage_and_quota.global_limited_usage, + usage_and_quota.available_disk_space)); +} + +int64 CalculateQuotaWithDiskSpace( + int64 available_disk_space, int64 usage, int64 quota) { + if (available_disk_space < QuotaManager::kMinimumPreserveForSystem) { + LOG(WARNING) + << "Running out of disk space for profile." + << " QuotaManager starts forbidding further quota consumption."; + return usage; + } + + if (quota < usage) { + // No more space; cap the quota to the current usage. + return usage; + } + + available_disk_space -= QuotaManager::kMinimumPreserveForSystem; + if (available_disk_space < quota - usage) + return available_disk_space + usage; + + return quota; +} + +int64 CalculateTemporaryHostQuota(int64 host_usage, + int64 global_quota, + int64 global_limited_usage) { + DCHECK_GE(global_limited_usage, 0); + int64 host_quota = global_quota / QuotaManager::kPerHostTemporaryPortion; + if (global_limited_usage > global_quota) + host_quota = std::min(host_quota, host_usage); + return host_quota; +} + +void DispatchUsageAndQuotaForWebApps( + StorageType type, + bool is_incognito, + bool is_unlimited, + bool can_query_disk_size, + const QuotaManager::GetUsageAndQuotaCallback& callback, + QuotaStatusCode status, + const UsageAndQuota& usage_and_quota) { + if (status != kQuotaStatusOk) { + callback.Run(status, 0, 0); + return; + } + + int64 usage = usage_and_quota.usage; + int64 quota = usage_and_quota.quota; + + if (type == kStorageTypeTemporary && !is_unlimited) { + quota = CalculateTemporaryHostQuota( + usage, quota, usage_and_quota.global_limited_usage); + } + + if (is_incognito) { + quota = std::min(quota, QuotaManager::kIncognitoDefaultQuotaLimit); + callback.Run(status, usage, quota); + return; + } + + // For apps with unlimited permission or can_query_disk_size is true (and not + // in incognito mode). + // We assume we can expose the actual disk size for them and cap the quota by + // the available disk space. + if (is_unlimited || can_query_disk_size) { + callback.Run( + status, usage, + CalculateQuotaWithDiskSpace( + usage_and_quota.available_disk_space, + usage, quota)); + return; + } + + callback.Run(status, usage, quota); +} + +} // namespace + +UsageAndQuota::UsageAndQuota() + : usage(0), + global_limited_usage(0), + quota(0), + available_disk_space(0) { +} + +UsageAndQuota::UsageAndQuota( + int64 usage, + int64 global_limited_usage, + int64 quota, + int64 available_disk_space) + : usage(usage), + global_limited_usage(global_limited_usage), + quota(quota), + available_disk_space(available_disk_space) { +} + +class UsageAndQuotaCallbackDispatcher + : public QuotaTask, + public base::SupportsWeakPtr<UsageAndQuotaCallbackDispatcher> { + public: + explicit UsageAndQuotaCallbackDispatcher(QuotaManager* manager) + : QuotaTask(manager), + has_usage_(false), + has_global_limited_usage_(false), + has_quota_(false), + has_available_disk_space_(false), + status_(kQuotaStatusUnknown), + usage_and_quota_(-1, -1, -1, -1), + waiting_callbacks_(1) {} + + virtual ~UsageAndQuotaCallbackDispatcher() {} + + void WaitForResults(const QuotaManager::UsageAndQuotaCallback& callback) { + callback_ = callback; + Start(); + } + + void set_usage(int64 usage) { + usage_and_quota_.usage = usage; + has_usage_ = true; + } + + void set_global_limited_usage(int64 global_limited_usage) { + usage_and_quota_.global_limited_usage = global_limited_usage; + has_global_limited_usage_ = true; + } + + void set_quota(int64 quota) { + usage_and_quota_.quota = quota; + has_quota_ = true; + } + + void set_available_disk_space(int64 available_disk_space) { + usage_and_quota_.available_disk_space = available_disk_space; + has_available_disk_space_ = true; + } + + UsageCallback GetHostUsageCallback() { + ++waiting_callbacks_; + has_usage_ = true; + return base::Bind(&UsageAndQuotaCallbackDispatcher::DidGetHostUsage, + AsWeakPtr()); + } + + UsageCallback GetGlobalLimitedUsageCallback() { + ++waiting_callbacks_; + has_global_limited_usage_ = true; + return base::Bind( + &UsageAndQuotaCallbackDispatcher::DidGetGlobalLimitedUsage, + AsWeakPtr()); + } + + QuotaCallback GetQuotaCallback() { + ++waiting_callbacks_; + has_quota_ = true; + return base::Bind(&UsageAndQuotaCallbackDispatcher::DidGetQuota, + AsWeakPtr()); + } + + QuotaCallback GetAvailableSpaceCallback() { + ++waiting_callbacks_; + has_available_disk_space_ = true; + return base::Bind(&UsageAndQuotaCallbackDispatcher::DidGetAvailableSpace, + AsWeakPtr()); + } + + private: + void DidGetHostUsage(int64 usage) { + if (status_ == kQuotaStatusUnknown) + status_ = kQuotaStatusOk; + usage_and_quota_.usage = usage; + CheckCompleted(); + } + + void DidGetGlobalLimitedUsage(int64 limited_usage) { + if (status_ == kQuotaStatusUnknown) + status_ = kQuotaStatusOk; + usage_and_quota_.global_limited_usage = limited_usage; + CheckCompleted(); + } + + void DidGetQuota(QuotaStatusCode status, int64 quota) { + if (status_ == kQuotaStatusUnknown || status_ == kQuotaStatusOk) + status_ = status; + usage_and_quota_.quota = quota; + CheckCompleted(); + } + + void DidGetAvailableSpace(QuotaStatusCode status, int64 space) { + DCHECK_GE(space, 0); + if (status_ == kQuotaStatusUnknown || status_ == kQuotaStatusOk) + status_ = status; + usage_and_quota_.available_disk_space = space; + CheckCompleted(); + } + + virtual void Run() OVERRIDE { + // We initialize waiting_callbacks to 1 so that we won't run + // the completion callback until here even some of the callbacks + // are dispatched synchronously. + CheckCompleted(); + } + + virtual void Aborted() OVERRIDE { + callback_.Run(kQuotaErrorAbort, UsageAndQuota()); + DeleteSoon(); + } + + virtual void Completed() OVERRIDE { + DCHECK(!has_usage_ || usage_and_quota_.usage >= 0); + DCHECK(!has_global_limited_usage_ || + usage_and_quota_.global_limited_usage >= 0); + DCHECK(!has_quota_ || usage_and_quota_.quota >= 0); + DCHECK(!has_available_disk_space_ || + usage_and_quota_.available_disk_space >= 0); + + callback_.Run(status_, usage_and_quota_); + DeleteSoon(); + } + + void CheckCompleted() { + if (--waiting_callbacks_ <= 0) + CallCompleted(); + } + + // For sanity checks, they're checked only when DCHECK is on. + bool has_usage_; + bool has_global_limited_usage_; + bool has_quota_; + bool has_available_disk_space_; + + QuotaStatusCode status_; + UsageAndQuota usage_and_quota_; + QuotaManager::UsageAndQuotaCallback callback_; + int waiting_callbacks_; + + DISALLOW_COPY_AND_ASSIGN(UsageAndQuotaCallbackDispatcher); +}; + +class QuotaManager::GetUsageInfoTask : public QuotaTask { + public: + GetUsageInfoTask( + QuotaManager* manager, + const GetUsageInfoCallback& callback) + : QuotaTask(manager), + callback_(callback), + weak_factory_(this) { + } + + protected: + virtual void Run() OVERRIDE { + remaining_trackers_ = 3; + // This will populate cached hosts and usage info. + manager()->GetUsageTracker(kStorageTypeTemporary)->GetGlobalUsage( + base::Bind(&GetUsageInfoTask::DidGetGlobalUsage, + weak_factory_.GetWeakPtr(), + kStorageTypeTemporary)); + manager()->GetUsageTracker(kStorageTypePersistent)->GetGlobalUsage( + base::Bind(&GetUsageInfoTask::DidGetGlobalUsage, + weak_factory_.GetWeakPtr(), + kStorageTypePersistent)); + manager()->GetUsageTracker(kStorageTypeSyncable)->GetGlobalUsage( + base::Bind(&GetUsageInfoTask::DidGetGlobalUsage, + weak_factory_.GetWeakPtr(), + kStorageTypeSyncable)); + } + + virtual void Completed() OVERRIDE { + callback_.Run(entries_); + DeleteSoon(); + } + + virtual void Aborted() OVERRIDE { + callback_.Run(UsageInfoEntries()); + DeleteSoon(); + } + + private: + void AddEntries(StorageType type, UsageTracker* tracker) { + std::map<std::string, int64> host_usage; + tracker->GetCachedHostsUsage(&host_usage); + for (std::map<std::string, int64>::const_iterator iter = host_usage.begin(); + iter != host_usage.end(); + ++iter) { + entries_.push_back(UsageInfo(iter->first, type, iter->second)); + } + if (--remaining_trackers_ == 0) + CallCompleted(); + } + + void DidGetGlobalUsage(StorageType type, int64, int64) { + DCHECK(manager()->GetUsageTracker(type)); + AddEntries(type, manager()->GetUsageTracker(type)); + } + + QuotaManager* manager() const { + return static_cast<QuotaManager*>(observer()); + } + + GetUsageInfoCallback callback_; + UsageInfoEntries entries_; + int remaining_trackers_; + base::WeakPtrFactory<GetUsageInfoTask> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(GetUsageInfoTask); +}; + +class QuotaManager::OriginDataDeleter : public QuotaTask { + public: + OriginDataDeleter(QuotaManager* manager, + const GURL& origin, + StorageType type, + int quota_client_mask, + const StatusCallback& callback) + : QuotaTask(manager), + origin_(origin), + type_(type), + quota_client_mask_(quota_client_mask), + error_count_(0), + remaining_clients_(-1), + skipped_clients_(0), + callback_(callback), + weak_factory_(this) {} + + protected: + virtual void Run() OVERRIDE { + error_count_ = 0; + remaining_clients_ = manager()->clients_.size(); + for (QuotaClientList::iterator iter = manager()->clients_.begin(); + iter != manager()->clients_.end(); ++iter) { + if (quota_client_mask_ & (*iter)->id()) { + (*iter)->DeleteOriginData( + origin_, type_, + base::Bind(&OriginDataDeleter::DidDeleteOriginData, + weak_factory_.GetWeakPtr())); + } else { + ++skipped_clients_; + if (--remaining_clients_ == 0) + CallCompleted(); + } + } + } + + virtual void Completed() OVERRIDE { + if (error_count_ == 0) { + // Only remove the entire origin if we didn't skip any client types. + if (skipped_clients_ == 0) + manager()->DeleteOriginFromDatabase(origin_, type_); + callback_.Run(kQuotaStatusOk); + } else { + callback_.Run(kQuotaErrorInvalidModification); + } + DeleteSoon(); + } + + virtual void Aborted() OVERRIDE { + callback_.Run(kQuotaErrorAbort); + DeleteSoon(); + } + + private: + void DidDeleteOriginData(QuotaStatusCode status) { + DCHECK_GT(remaining_clients_, 0); + + if (status != kQuotaStatusOk) + ++error_count_; + + if (--remaining_clients_ == 0) + CallCompleted(); + } + + QuotaManager* manager() const { + return static_cast<QuotaManager*>(observer()); + } + + GURL origin_; + StorageType type_; + int quota_client_mask_; + int error_count_; + int remaining_clients_; + int skipped_clients_; + StatusCallback callback_; + + base::WeakPtrFactory<OriginDataDeleter> weak_factory_; + DISALLOW_COPY_AND_ASSIGN(OriginDataDeleter); +}; + +class QuotaManager::HostDataDeleter : public QuotaTask { + public: + HostDataDeleter(QuotaManager* manager, + const std::string& host, + StorageType type, + int quota_client_mask, + const StatusCallback& callback) + : QuotaTask(manager), + host_(host), + type_(type), + quota_client_mask_(quota_client_mask), + error_count_(0), + remaining_clients_(-1), + remaining_deleters_(-1), + callback_(callback), + weak_factory_(this) {} + + protected: + virtual void Run() OVERRIDE { + error_count_ = 0; + remaining_clients_ = manager()->clients_.size(); + for (QuotaClientList::iterator iter = manager()->clients_.begin(); + iter != manager()->clients_.end(); ++iter) { + (*iter)->GetOriginsForHost( + type_, host_, + base::Bind(&HostDataDeleter::DidGetOriginsForHost, + weak_factory_.GetWeakPtr())); + } + } + + virtual void Completed() OVERRIDE { + if (error_count_ == 0) { + callback_.Run(kQuotaStatusOk); + } else { + callback_.Run(kQuotaErrorInvalidModification); + } + DeleteSoon(); + } + + virtual void Aborted() OVERRIDE { + callback_.Run(kQuotaErrorAbort); + DeleteSoon(); + } + + private: + void DidGetOriginsForHost(const std::set<GURL>& origins) { + DCHECK_GT(remaining_clients_, 0); + + origins_.insert(origins.begin(), origins.end()); + + if (--remaining_clients_ == 0) { + if (!origins_.empty()) + ScheduleOriginsDeletion(); + else + CallCompleted(); + } + } + + void ScheduleOriginsDeletion() { + remaining_deleters_ = origins_.size(); + for (std::set<GURL>::const_iterator p = origins_.begin(); + p != origins_.end(); + ++p) { + OriginDataDeleter* deleter = + new OriginDataDeleter( + manager(), *p, type_, quota_client_mask_, + base::Bind(&HostDataDeleter::DidDeleteOriginData, + weak_factory_.GetWeakPtr())); + deleter->Start(); + } + } + + void DidDeleteOriginData(QuotaStatusCode status) { + DCHECK_GT(remaining_deleters_, 0); + + if (status != kQuotaStatusOk) + ++error_count_; + + if (--remaining_deleters_ == 0) + CallCompleted(); + } + + QuotaManager* manager() const { + return static_cast<QuotaManager*>(observer()); + } + + std::string host_; + StorageType type_; + int quota_client_mask_; + std::set<GURL> origins_; + int error_count_; + int remaining_clients_; + int remaining_deleters_; + StatusCallback callback_; + + base::WeakPtrFactory<HostDataDeleter> weak_factory_; + DISALLOW_COPY_AND_ASSIGN(HostDataDeleter); +}; + +class QuotaManager::GetModifiedSinceHelper { + public: + bool GetModifiedSinceOnDBThread(StorageType type, + base::Time modified_since, + QuotaDatabase* database) { + DCHECK(database); + return database->GetOriginsModifiedSince(type, &origins_, modified_since); + } + + void DidGetModifiedSince(const base::WeakPtr<QuotaManager>& manager, + const GetOriginsCallback& callback, + StorageType type, + bool success) { + if (!manager) { + // The operation was aborted. + callback.Run(std::set<GURL>(), type); + return; + } + manager->DidDatabaseWork(success); + callback.Run(origins_, type); + } + + private: + std::set<GURL> origins_; +}; + +class QuotaManager::DumpQuotaTableHelper { + public: + bool DumpQuotaTableOnDBThread(QuotaDatabase* database) { + DCHECK(database); + return database->DumpQuotaTable( + base::Bind(&DumpQuotaTableHelper::AppendEntry, base::Unretained(this))); + } + + void DidDumpQuotaTable(const base::WeakPtr<QuotaManager>& manager, + const DumpQuotaTableCallback& callback, + bool success) { + if (!manager) { + // The operation was aborted. + callback.Run(QuotaTableEntries()); + return; + } + manager->DidDatabaseWork(success); + callback.Run(entries_); + } + + private: + bool AppendEntry(const QuotaTableEntry& entry) { + entries_.push_back(entry); + return true; + } + + QuotaTableEntries entries_; +}; + +class QuotaManager::DumpOriginInfoTableHelper { + public: + bool DumpOriginInfoTableOnDBThread(QuotaDatabase* database) { + DCHECK(database); + return database->DumpOriginInfoTable( + base::Bind(&DumpOriginInfoTableHelper::AppendEntry, + base::Unretained(this))); + } + + void DidDumpOriginInfoTable(const base::WeakPtr<QuotaManager>& manager, + const DumpOriginInfoTableCallback& callback, + bool success) { + if (!manager) { + // The operation was aborted. + callback.Run(OriginInfoTableEntries()); + return; + } + manager->DidDatabaseWork(success); + callback.Run(entries_); + } + + private: + bool AppendEntry(const OriginInfoTableEntry& entry) { + entries_.push_back(entry); + return true; + } + + OriginInfoTableEntries entries_; +}; + +// QuotaManager --------------------------------------------------------------- + +QuotaManager::QuotaManager( + bool is_incognito, + const base::FilePath& profile_path, + const scoped_refptr<base::SingleThreadTaskRunner>& io_thread, + const scoped_refptr<base::SequencedTaskRunner>& db_thread, + const scoped_refptr<SpecialStoragePolicy>& special_storage_policy) + : is_incognito_(is_incognito), + profile_path_(profile_path), + proxy_(new QuotaManagerProxy(this, io_thread)), + db_disabled_(false), + eviction_disabled_(false), + io_thread_(io_thread), + db_thread_(db_thread), + temporary_quota_initialized_(false), + temporary_quota_override_(-1), + desired_available_space_(-1), + special_storage_policy_(special_storage_policy), + get_disk_space_fn_(&CallSystemGetAmountOfFreeDiskSpace), + storage_monitor_(new StorageMonitor(this)), + weak_factory_(this) { +} + +void QuotaManager::GetUsageInfo(const GetUsageInfoCallback& callback) { + LazyInitialize(); + GetUsageInfoTask* get_usage_info = new GetUsageInfoTask(this, callback); + get_usage_info->Start(); +} + +void QuotaManager::GetUsageAndQuotaForWebApps( + const GURL& origin, + StorageType type, + const GetUsageAndQuotaCallback& callback) { + if (type != kStorageTypeTemporary && + type != kStorageTypePersistent && + type != kStorageTypeSyncable) { + callback.Run(kQuotaErrorNotSupported, 0, 0); + return; + } + + DCHECK(origin == origin.GetOrigin()); + LazyInitialize(); + + bool unlimited = IsStorageUnlimited(origin, type); + bool can_query_disk_size = CanQueryDiskSize(origin); + + UsageAndQuotaCallbackDispatcher* dispatcher = + new UsageAndQuotaCallbackDispatcher(this); + + UsageAndQuota usage_and_quota; + if (unlimited) { + dispatcher->set_quota(kNoLimit); + } else { + if (type == kStorageTypeTemporary) { + GetUsageTracker(type)->GetGlobalLimitedUsage( + dispatcher->GetGlobalLimitedUsageCallback()); + GetTemporaryGlobalQuota(dispatcher->GetQuotaCallback()); + } else if (type == kStorageTypePersistent) { + GetPersistentHostQuota(net::GetHostOrSpecFromURL(origin), + dispatcher->GetQuotaCallback()); + } else { + dispatcher->set_quota(kSyncableStorageDefaultHostQuota); + } + } + + DCHECK(GetUsageTracker(type)); + GetUsageTracker(type)->GetHostUsage(net::GetHostOrSpecFromURL(origin), + dispatcher->GetHostUsageCallback()); + + if (!is_incognito_ && (unlimited || can_query_disk_size)) + GetAvailableSpace(dispatcher->GetAvailableSpaceCallback()); + + dispatcher->WaitForResults(base::Bind( + &DispatchUsageAndQuotaForWebApps, + type, is_incognito_, unlimited, can_query_disk_size, + callback)); +} + +void QuotaManager::GetUsageAndQuota( + const GURL& origin, StorageType type, + const GetUsageAndQuotaCallback& callback) { + DCHECK(origin == origin.GetOrigin()); + + if (IsStorageUnlimited(origin, type)) { + callback.Run(kQuotaStatusOk, 0, kNoLimit); + return; + } + + GetUsageAndQuotaForWebApps(origin, type, callback); +} + +void QuotaManager::NotifyStorageAccessed( + QuotaClient::ID client_id, + const GURL& origin, StorageType type) { + DCHECK(origin == origin.GetOrigin()); + NotifyStorageAccessedInternal(client_id, origin, type, base::Time::Now()); +} + +void QuotaManager::NotifyStorageModified( + QuotaClient::ID client_id, + const GURL& origin, StorageType type, int64 delta) { + DCHECK(origin == origin.GetOrigin()); + NotifyStorageModifiedInternal(client_id, origin, type, delta, + base::Time::Now()); +} + +void QuotaManager::NotifyOriginInUse(const GURL& origin) { + DCHECK(io_thread_->BelongsToCurrentThread()); + origins_in_use_[origin]++; +} + +void QuotaManager::NotifyOriginNoLongerInUse(const GURL& origin) { + DCHECK(io_thread_->BelongsToCurrentThread()); + DCHECK(IsOriginInUse(origin)); + int& count = origins_in_use_[origin]; + if (--count == 0) + origins_in_use_.erase(origin); +} + +void QuotaManager::SetUsageCacheEnabled(QuotaClient::ID client_id, + const GURL& origin, + StorageType type, + bool enabled) { + LazyInitialize(); + DCHECK(GetUsageTracker(type)); + GetUsageTracker(type)->SetUsageCacheEnabled(client_id, origin, enabled); +} + +void QuotaManager::DeleteOriginData( + const GURL& origin, StorageType type, int quota_client_mask, + const StatusCallback& callback) { + LazyInitialize(); + + if (origin.is_empty() || clients_.empty()) { + callback.Run(kQuotaStatusOk); + return; + } + + DCHECK(origin == origin.GetOrigin()); + OriginDataDeleter* deleter = + new OriginDataDeleter(this, origin, type, quota_client_mask, callback); + deleter->Start(); +} + +void QuotaManager::DeleteHostData(const std::string& host, + StorageType type, + int quota_client_mask, + const StatusCallback& callback) { + LazyInitialize(); + + if (host.empty() || clients_.empty()) { + callback.Run(kQuotaStatusOk); + return; + } + + HostDataDeleter* deleter = + new HostDataDeleter(this, host, type, quota_client_mask, callback); + deleter->Start(); +} + +void QuotaManager::GetAvailableSpace(const AvailableSpaceCallback& callback) { + if (!available_space_callbacks_.Add(callback)) + return; + + PostTaskAndReplyWithResult(db_thread_.get(), + FROM_HERE, + base::Bind(get_disk_space_fn_, profile_path_), + base::Bind(&QuotaManager::DidGetAvailableSpace, + weak_factory_.GetWeakPtr())); +} + +void QuotaManager::GetTemporaryGlobalQuota(const QuotaCallback& callback) { + LazyInitialize(); + if (!temporary_quota_initialized_) { + db_initialization_callbacks_.Add(base::Bind( + &QuotaManager::GetTemporaryGlobalQuota, + weak_factory_.GetWeakPtr(), callback)); + return; + } + + if (temporary_quota_override_ > 0) { + callback.Run(kQuotaStatusOk, temporary_quota_override_); + return; + } + + UsageAndQuotaCallbackDispatcher* dispatcher = + new UsageAndQuotaCallbackDispatcher(this); + GetUsageTracker(kStorageTypeTemporary)-> + GetGlobalLimitedUsage(dispatcher->GetGlobalLimitedUsageCallback()); + GetAvailableSpace(dispatcher->GetAvailableSpaceCallback()); + dispatcher->WaitForResults( + base::Bind(&DispatchTemporaryGlobalQuotaCallback, callback)); +} + +void QuotaManager::SetTemporaryGlobalOverrideQuota( + int64 new_quota, const QuotaCallback& callback) { + LazyInitialize(); + + if (new_quota < 0) { + if (!callback.is_null()) + callback.Run(kQuotaErrorInvalidModification, -1); + return; + } + + if (db_disabled_) { + if (!callback.is_null()) + callback.Run(kQuotaErrorInvalidAccess, -1); + return; + } + + int64* new_quota_ptr = new int64(new_quota); + PostTaskAndReplyWithResultForDBThread( + FROM_HERE, + base::Bind(&SetTemporaryGlobalOverrideQuotaOnDBThread, + base::Unretained(new_quota_ptr)), + base::Bind(&QuotaManager::DidSetTemporaryGlobalOverrideQuota, + weak_factory_.GetWeakPtr(), + callback, + base::Owned(new_quota_ptr))); +} + +void QuotaManager::GetPersistentHostQuota(const std::string& host, + const QuotaCallback& callback) { + LazyInitialize(); + if (host.empty()) { + // This could happen if we are called on file:///. + // TODO(kinuko) We may want to respect --allow-file-access-from-files + // command line switch. + callback.Run(kQuotaStatusOk, 0); + return; + } + + if (!persistent_host_quota_callbacks_.Add(host, callback)) + return; + + int64* quota_ptr = new int64(0); + PostTaskAndReplyWithResultForDBThread( + FROM_HERE, + base::Bind(&GetPersistentHostQuotaOnDBThread, + host, + base::Unretained(quota_ptr)), + base::Bind(&QuotaManager::DidGetPersistentHostQuota, + weak_factory_.GetWeakPtr(), + host, + base::Owned(quota_ptr))); +} + +void QuotaManager::SetPersistentHostQuota(const std::string& host, + int64 new_quota, + const QuotaCallback& callback) { + LazyInitialize(); + if (host.empty()) { + // This could happen if we are called on file:///. + callback.Run(kQuotaErrorNotSupported, 0); + return; + } + + if (new_quota < 0) { + callback.Run(kQuotaErrorInvalidModification, -1); + return; + } + + if (kPerHostPersistentQuotaLimit < new_quota) { + // Cap the requested size at the per-host quota limit. + new_quota = kPerHostPersistentQuotaLimit; + } + + if (db_disabled_) { + callback.Run(kQuotaErrorInvalidAccess, -1); + return; + } + + int64* new_quota_ptr = new int64(new_quota); + PostTaskAndReplyWithResultForDBThread( + FROM_HERE, + base::Bind(&SetPersistentHostQuotaOnDBThread, + host, + base::Unretained(new_quota_ptr)), + base::Bind(&QuotaManager::DidSetPersistentHostQuota, + weak_factory_.GetWeakPtr(), + host, + callback, + base::Owned(new_quota_ptr))); +} + +void QuotaManager::GetGlobalUsage(StorageType type, + const GlobalUsageCallback& callback) { + LazyInitialize(); + DCHECK(GetUsageTracker(type)); + GetUsageTracker(type)->GetGlobalUsage(callback); +} + +void QuotaManager::GetHostUsage(const std::string& host, + StorageType type, + const UsageCallback& callback) { + LazyInitialize(); + DCHECK(GetUsageTracker(type)); + GetUsageTracker(type)->GetHostUsage(host, callback); +} + +void QuotaManager::GetHostUsage(const std::string& host, + StorageType type, + QuotaClient::ID client_id, + const UsageCallback& callback) { + LazyInitialize(); + DCHECK(GetUsageTracker(type)); + ClientUsageTracker* tracker = + GetUsageTracker(type)->GetClientTracker(client_id); + if (!tracker) { + callback.Run(0); + return; + } + tracker->GetHostUsage(host, callback); +} + +bool QuotaManager::IsTrackingHostUsage(StorageType type, + QuotaClient::ID client_id) const { + UsageTracker* tracker = GetUsageTracker(type); + return tracker && tracker->GetClientTracker(client_id); +} + +void QuotaManager::GetStatistics( + std::map<std::string, std::string>* statistics) { + DCHECK(statistics); + if (temporary_storage_evictor_) { + std::map<std::string, int64> stats; + temporary_storage_evictor_->GetStatistics(&stats); + for (std::map<std::string, int64>::iterator p = stats.begin(); + p != stats.end(); + ++p) + (*statistics)[p->first] = base::Int64ToString(p->second); + } +} + +bool QuotaManager::IsStorageUnlimited(const GURL& origin, + StorageType type) const { + // For syncable storage we should always enforce quota (since the + // quota must be capped by the server limit). + if (type == kStorageTypeSyncable) + return false; + if (type == kStorageTypeQuotaNotManaged) + return true; + return special_storage_policy_.get() && + special_storage_policy_->IsStorageUnlimited(origin); +} + +void QuotaManager::GetOriginsModifiedSince(StorageType type, + base::Time modified_since, + const GetOriginsCallback& callback) { + LazyInitialize(); + GetModifiedSinceHelper* helper = new GetModifiedSinceHelper; + PostTaskAndReplyWithResultForDBThread( + FROM_HERE, + base::Bind(&GetModifiedSinceHelper::GetModifiedSinceOnDBThread, + base::Unretained(helper), + type, + modified_since), + base::Bind(&GetModifiedSinceHelper::DidGetModifiedSince, + base::Owned(helper), + weak_factory_.GetWeakPtr(), + callback, + type)); +} + +bool QuotaManager::ResetUsageTracker(StorageType type) { + DCHECK(GetUsageTracker(type)); + if (GetUsageTracker(type)->IsWorking()) + return false; + switch (type) { + case kStorageTypeTemporary: + temporary_usage_tracker_.reset(new UsageTracker( + clients_, kStorageTypeTemporary, special_storage_policy_.get(), + storage_monitor_.get())); + return true; + case kStorageTypePersistent: + persistent_usage_tracker_.reset(new UsageTracker( + clients_, kStorageTypePersistent, special_storage_policy_.get(), + storage_monitor_.get())); + return true; + case kStorageTypeSyncable: + syncable_usage_tracker_.reset(new UsageTracker( + clients_, kStorageTypeSyncable, special_storage_policy_.get(), + storage_monitor_.get())); + return true; + default: + NOTREACHED(); + } + return true; +} + +void QuotaManager::AddStorageObserver( + StorageObserver* observer, const StorageObserver::MonitorParams& params) { + DCHECK(observer); + storage_monitor_->AddObserver(observer, params); +} + +void QuotaManager::RemoveStorageObserver(StorageObserver* observer) { + DCHECK(observer); + storage_monitor_->RemoveObserver(observer); +} + +void QuotaManager::RemoveStorageObserverForFilter( + StorageObserver* observer, const StorageObserver::Filter& filter) { + DCHECK(observer); + storage_monitor_->RemoveObserverForFilter(observer, filter); +} + +QuotaManager::~QuotaManager() { + proxy_->manager_ = NULL; + std::for_each(clients_.begin(), clients_.end(), + std::mem_fun(&QuotaClient::OnQuotaManagerDestroyed)); + if (database_) + db_thread_->DeleteSoon(FROM_HERE, database_.release()); +} + +QuotaManager::EvictionContext::EvictionContext() + : evicted_type(kStorageTypeUnknown) { +} + +QuotaManager::EvictionContext::~EvictionContext() { +} + +void QuotaManager::LazyInitialize() { + DCHECK(io_thread_->BelongsToCurrentThread()); + if (database_) { + // Initialization seems to be done already. + return; + } + + // Use an empty path to open an in-memory only databse for incognito. + database_.reset(new QuotaDatabase(is_incognito_ ? base::FilePath() : + profile_path_.AppendASCII(kDatabaseName))); + + temporary_usage_tracker_.reset(new UsageTracker( + clients_, kStorageTypeTemporary, special_storage_policy_.get(), + storage_monitor_.get())); + persistent_usage_tracker_.reset(new UsageTracker( + clients_, kStorageTypePersistent, special_storage_policy_.get(), + storage_monitor_.get())); + syncable_usage_tracker_.reset(new UsageTracker( + clients_, kStorageTypeSyncable, special_storage_policy_.get(), + storage_monitor_.get())); + + int64* temporary_quota_override = new int64(-1); + int64* desired_available_space = new int64(-1); + PostTaskAndReplyWithResultForDBThread( + FROM_HERE, + base::Bind(&InitializeOnDBThread, + base::Unretained(temporary_quota_override), + base::Unretained(desired_available_space)), + base::Bind(&QuotaManager::DidInitialize, + weak_factory_.GetWeakPtr(), + base::Owned(temporary_quota_override), + base::Owned(desired_available_space))); +} + +void QuotaManager::RegisterClient(QuotaClient* client) { + DCHECK(!database_.get()); + clients_.push_back(client); +} + +UsageTracker* QuotaManager::GetUsageTracker(StorageType type) const { + switch (type) { + case kStorageTypeTemporary: + return temporary_usage_tracker_.get(); + case kStorageTypePersistent: + return persistent_usage_tracker_.get(); + case kStorageTypeSyncable: + return syncable_usage_tracker_.get(); + case kStorageTypeQuotaNotManaged: + return NULL; + case kStorageTypeUnknown: + NOTREACHED(); + } + return NULL; +} + +void QuotaManager::GetCachedOrigins( + StorageType type, std::set<GURL>* origins) { + DCHECK(origins); + LazyInitialize(); + DCHECK(GetUsageTracker(type)); + GetUsageTracker(type)->GetCachedOrigins(origins); +} + +void QuotaManager::NotifyStorageAccessedInternal( + QuotaClient::ID client_id, + const GURL& origin, StorageType type, + base::Time accessed_time) { + LazyInitialize(); + if (type == kStorageTypeTemporary && !lru_origin_callback_.is_null()) { + // Record the accessed origins while GetLRUOrigin task is runing + // to filter out them from eviction. + access_notified_origins_.insert(origin); + } + + if (db_disabled_) + return; + PostTaskAndReplyWithResultForDBThread( + FROM_HERE, + base::Bind(&UpdateAccessTimeOnDBThread, origin, type, accessed_time), + base::Bind(&QuotaManager::DidDatabaseWork, + weak_factory_.GetWeakPtr())); +} + +void QuotaManager::NotifyStorageModifiedInternal( + QuotaClient::ID client_id, + const GURL& origin, + StorageType type, + int64 delta, + base::Time modified_time) { + LazyInitialize(); + DCHECK(GetUsageTracker(type)); + GetUsageTracker(type)->UpdateUsageCache(client_id, origin, delta); + + PostTaskAndReplyWithResultForDBThread( + FROM_HERE, + base::Bind(&UpdateModifiedTimeOnDBThread, origin, type, modified_time), + base::Bind(&QuotaManager::DidDatabaseWork, + weak_factory_.GetWeakPtr())); +} + +void QuotaManager::DumpQuotaTable(const DumpQuotaTableCallback& callback) { + DumpQuotaTableHelper* helper = new DumpQuotaTableHelper; + PostTaskAndReplyWithResultForDBThread( + FROM_HERE, + base::Bind(&DumpQuotaTableHelper::DumpQuotaTableOnDBThread, + base::Unretained(helper)), + base::Bind(&DumpQuotaTableHelper::DidDumpQuotaTable, + base::Owned(helper), + weak_factory_.GetWeakPtr(), + callback)); +} + +void QuotaManager::DumpOriginInfoTable( + const DumpOriginInfoTableCallback& callback) { + DumpOriginInfoTableHelper* helper = new DumpOriginInfoTableHelper; + PostTaskAndReplyWithResultForDBThread( + FROM_HERE, + base::Bind(&DumpOriginInfoTableHelper::DumpOriginInfoTableOnDBThread, + base::Unretained(helper)), + base::Bind(&DumpOriginInfoTableHelper::DidDumpOriginInfoTable, + base::Owned(helper), + weak_factory_.GetWeakPtr(), + callback)); +} + +void QuotaManager::StartEviction() { + DCHECK(!temporary_storage_evictor_.get()); + temporary_storage_evictor_.reset(new QuotaTemporaryStorageEvictor( + this, kEvictionIntervalInMilliSeconds)); + if (desired_available_space_ >= 0) + temporary_storage_evictor_->set_min_available_disk_space_to_start_eviction( + desired_available_space_); + temporary_storage_evictor_->Start(); +} + +void QuotaManager::DeleteOriginFromDatabase( + const GURL& origin, StorageType type) { + LazyInitialize(); + if (db_disabled_) + return; + + PostTaskAndReplyWithResultForDBThread( + FROM_HERE, + base::Bind(&DeleteOriginInfoOnDBThread, origin, type), + base::Bind(&QuotaManager::DidDatabaseWork, + weak_factory_.GetWeakPtr())); +} + +void QuotaManager::DidOriginDataEvicted(QuotaStatusCode status) { + DCHECK(io_thread_->BelongsToCurrentThread()); + + // We only try evict origins that are not in use, so basically + // deletion attempt for eviction should not fail. Let's record + // the origin if we get error and exclude it from future eviction + // if the error happens consistently (> kThresholdOfErrorsToBeBlacklisted). + if (status != kQuotaStatusOk) + origins_in_error_[eviction_context_.evicted_origin]++; + + eviction_context_.evict_origin_data_callback.Run(status); + eviction_context_.evict_origin_data_callback.Reset(); +} + +void QuotaManager::ReportHistogram() { + GetGlobalUsage(kStorageTypeTemporary, + base::Bind( + &QuotaManager::DidGetTemporaryGlobalUsageForHistogram, + weak_factory_.GetWeakPtr())); + GetGlobalUsage(kStorageTypePersistent, + base::Bind( + &QuotaManager::DidGetPersistentGlobalUsageForHistogram, + weak_factory_.GetWeakPtr())); +} + +void QuotaManager::DidGetTemporaryGlobalUsageForHistogram( + int64 usage, + int64 unlimited_usage) { + UMA_HISTOGRAM_MBYTES("Quota.GlobalUsageOfTemporaryStorage", usage); + + std::set<GURL> origins; + GetCachedOrigins(kStorageTypeTemporary, &origins); + + size_t num_origins = origins.size(); + size_t protected_origins = 0; + size_t unlimited_origins = 0; + CountOriginType(origins, + special_storage_policy_.get(), + &protected_origins, + &unlimited_origins); + + UMA_HISTOGRAM_COUNTS("Quota.NumberOfTemporaryStorageOrigins", + num_origins); + UMA_HISTOGRAM_COUNTS("Quota.NumberOfProtectedTemporaryStorageOrigins", + protected_origins); + UMA_HISTOGRAM_COUNTS("Quota.NumberOfUnlimitedTemporaryStorageOrigins", + unlimited_origins); +} + +void QuotaManager::DidGetPersistentGlobalUsageForHistogram( + int64 usage, + int64 unlimited_usage) { + UMA_HISTOGRAM_MBYTES("Quota.GlobalUsageOfPersistentStorage", usage); + + std::set<GURL> origins; + GetCachedOrigins(kStorageTypePersistent, &origins); + + size_t num_origins = origins.size(); + size_t protected_origins = 0; + size_t unlimited_origins = 0; + CountOriginType(origins, + special_storage_policy_.get(), + &protected_origins, + &unlimited_origins); + + UMA_HISTOGRAM_COUNTS("Quota.NumberOfPersistentStorageOrigins", + num_origins); + UMA_HISTOGRAM_COUNTS("Quota.NumberOfProtectedPersistentStorageOrigins", + protected_origins); + UMA_HISTOGRAM_COUNTS("Quota.NumberOfUnlimitedPersistentStorageOrigins", + unlimited_origins); +} + +void QuotaManager::GetLRUOrigin( + StorageType type, + const GetLRUOriginCallback& callback) { + LazyInitialize(); + // This must not be called while there's an in-flight task. + DCHECK(lru_origin_callback_.is_null()); + lru_origin_callback_ = callback; + if (db_disabled_) { + lru_origin_callback_.Run(GURL()); + lru_origin_callback_.Reset(); + return; + } + + std::set<GURL>* exceptions = new std::set<GURL>; + for (std::map<GURL, int>::const_iterator p = origins_in_use_.begin(); + p != origins_in_use_.end(); + ++p) { + if (p->second > 0) + exceptions->insert(p->first); + } + for (std::map<GURL, int>::const_iterator p = origins_in_error_.begin(); + p != origins_in_error_.end(); + ++p) { + if (p->second > QuotaManager::kThresholdOfErrorsToBeBlacklisted) + exceptions->insert(p->first); + } + + GURL* url = new GURL; + PostTaskAndReplyWithResultForDBThread( + FROM_HERE, + base::Bind(&GetLRUOriginOnDBThread, + type, + base::Owned(exceptions), + special_storage_policy_, + base::Unretained(url)), + base::Bind(&QuotaManager::DidGetLRUOrigin, + weak_factory_.GetWeakPtr(), + base::Owned(url))); +} + +void QuotaManager::EvictOriginData( + const GURL& origin, + StorageType type, + const EvictOriginDataCallback& callback) { + DCHECK(io_thread_->BelongsToCurrentThread()); + DCHECK_EQ(type, kStorageTypeTemporary); + + eviction_context_.evicted_origin = origin; + eviction_context_.evicted_type = type; + eviction_context_.evict_origin_data_callback = callback; + + DeleteOriginData(origin, type, QuotaClient::kAllClientsMask, + base::Bind(&QuotaManager::DidOriginDataEvicted, + weak_factory_.GetWeakPtr())); +} + +void QuotaManager::GetUsageAndQuotaForEviction( + const UsageAndQuotaCallback& callback) { + DCHECK(io_thread_->BelongsToCurrentThread()); + LazyInitialize(); + + UsageAndQuotaCallbackDispatcher* dispatcher = + new UsageAndQuotaCallbackDispatcher(this); + GetUsageTracker(kStorageTypeTemporary)-> + GetGlobalLimitedUsage(dispatcher->GetGlobalLimitedUsageCallback()); + GetTemporaryGlobalQuota(dispatcher->GetQuotaCallback()); + GetAvailableSpace(dispatcher->GetAvailableSpaceCallback()); + dispatcher->WaitForResults(callback); +} + +void QuotaManager::DidSetTemporaryGlobalOverrideQuota( + const QuotaCallback& callback, + const int64* new_quota, + bool success) { + QuotaStatusCode status = kQuotaErrorInvalidAccess; + DidDatabaseWork(success); + if (success) { + temporary_quota_override_ = *new_quota; + status = kQuotaStatusOk; + } + + if (callback.is_null()) + return; + + callback.Run(status, *new_quota); +} + +void QuotaManager::DidGetPersistentHostQuota(const std::string& host, + const int64* quota, + bool success) { + DidDatabaseWork(success); + persistent_host_quota_callbacks_.Run( + host, MakeTuple(kQuotaStatusOk, *quota)); +} + +void QuotaManager::DidSetPersistentHostQuota(const std::string& host, + const QuotaCallback& callback, + const int64* new_quota, + bool success) { + DidDatabaseWork(success); + callback.Run(success ? kQuotaStatusOk : kQuotaErrorInvalidAccess, *new_quota); +} + +void QuotaManager::DidInitialize(int64* temporary_quota_override, + int64* desired_available_space, + bool success) { + temporary_quota_override_ = *temporary_quota_override; + desired_available_space_ = *desired_available_space; + temporary_quota_initialized_ = true; + DidDatabaseWork(success); + + histogram_timer_.Start(FROM_HERE, + base::TimeDelta::FromMilliseconds( + kReportHistogramInterval), + this, &QuotaManager::ReportHistogram); + + db_initialization_callbacks_.Run(MakeTuple()); + GetTemporaryGlobalQuota( + base::Bind(&QuotaManager::DidGetInitialTemporaryGlobalQuota, + weak_factory_.GetWeakPtr())); +} + +void QuotaManager::DidGetLRUOrigin(const GURL* origin, + bool success) { + DidDatabaseWork(success); + // Make sure the returned origin is (still) not in the origin_in_use_ set + // and has not been accessed since we posted the task. + if (origins_in_use_.find(*origin) != origins_in_use_.end() || + access_notified_origins_.find(*origin) != access_notified_origins_.end()) + lru_origin_callback_.Run(GURL()); + else + lru_origin_callback_.Run(*origin); + access_notified_origins_.clear(); + lru_origin_callback_.Reset(); +} + +void QuotaManager::DidGetInitialTemporaryGlobalQuota( + QuotaStatusCode status, int64 quota_unused) { + if (eviction_disabled_) + return; + + std::set<GURL>* origins = new std::set<GURL>; + temporary_usage_tracker_->GetCachedOrigins(origins); + // This will call the StartEviction() when initial origin registration + // is completed. + PostTaskAndReplyWithResultForDBThread( + FROM_HERE, + base::Bind(&InitializeTemporaryOriginsInfoOnDBThread, + base::Owned(origins)), + base::Bind(&QuotaManager::DidInitializeTemporaryOriginsInfo, + weak_factory_.GetWeakPtr())); +} + +void QuotaManager::DidInitializeTemporaryOriginsInfo(bool success) { + DidDatabaseWork(success); + if (success) + StartEviction(); +} + +void QuotaManager::DidGetAvailableSpace(int64 space) { + available_space_callbacks_.Run(MakeTuple(kQuotaStatusOk, space)); +} + +void QuotaManager::DidDatabaseWork(bool success) { + db_disabled_ = !success; +} + +void QuotaManager::DeleteOnCorrectThread() const { + if (!io_thread_->BelongsToCurrentThread() && + io_thread_->DeleteSoon(FROM_HERE, this)) { + return; + } + delete this; +} + +void QuotaManager::PostTaskAndReplyWithResultForDBThread( + const tracked_objects::Location& from_here, + const base::Callback<bool(QuotaDatabase*)>& task, + const base::Callback<void(bool)>& reply) { + // Deleting manager will post another task to DB thread to delete + // |database_|, therefore we can be sure that database_ is alive when this + // task runs. + base::PostTaskAndReplyWithResult( + db_thread_.get(), + from_here, + base::Bind(task, base::Unretained(database_.get())), + reply); +} + +} // namespace storage diff --git a/storage/browser/quota/quota_manager.h b/storage/browser/quota/quota_manager.h new file mode 100644 index 0000000..98a9415 --- /dev/null +++ b/storage/browser/quota/quota_manager.h @@ -0,0 +1,460 @@ +// Copyright 2013 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. + +#ifndef STORAGE_BROWSER_QUOTA_QUOTA_MANAGER_H_ +#define STORAGE_BROWSER_QUOTA_QUOTA_MANAGER_H_ + +#include <deque> +#include <list> +#include <map> +#include <set> +#include <string> +#include <utility> +#include <vector> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/sequenced_task_runner_helpers.h" +#include "storage/browser/quota/quota_callbacks.h" +#include "storage/browser/quota/quota_client.h" +#include "storage/browser/quota/quota_database.h" +#include "storage/browser/quota/quota_task.h" +#include "storage/browser/quota/special_storage_policy.h" +#include "storage/browser/quota/storage_observer.h" +#include "storage/browser/storage_browser_export.h" + +namespace base { +class FilePath; +class SequencedTaskRunner; +class SingleThreadTaskRunner; +} + +namespace quota_internals { +class QuotaInternalsProxy; +} + +namespace content { +class MockQuotaManager; +class MockStorageClient; +class QuotaManagerTest; +class StorageMonitorTest; + +} + +namespace storage { + +class QuotaDatabase; +class QuotaManagerProxy; +class QuotaTemporaryStorageEvictor; +class StorageMonitor; +class UsageTracker; + +struct QuotaManagerDeleter; + +struct STORAGE_EXPORT UsageAndQuota { + int64 usage; + int64 global_limited_usage; + int64 quota; + int64 available_disk_space; + + UsageAndQuota(); + UsageAndQuota(int64 usage, + int64 global_limited_usage, + int64 quota, + int64 available_disk_space); +}; + +// An interface called by QuotaTemporaryStorageEvictor. +class STORAGE_EXPORT QuotaEvictionHandler { + public: + typedef base::Callback<void(const GURL&)> GetLRUOriginCallback; + typedef StatusCallback EvictOriginDataCallback; + typedef base::Callback<void(QuotaStatusCode status, + const UsageAndQuota& usage_and_quota)> + UsageAndQuotaCallback; + + // Returns the least recently used origin. It might return empty + // GURL when there are no evictable origins. + virtual void GetLRUOrigin( + StorageType type, + const GetLRUOriginCallback& callback) = 0; + + virtual void EvictOriginData( + const GURL& origin, + StorageType type, + const EvictOriginDataCallback& callback) = 0; + + virtual void GetUsageAndQuotaForEviction( + const UsageAndQuotaCallback& callback) = 0; + + protected: + virtual ~QuotaEvictionHandler() {} +}; + +struct UsageInfo { + UsageInfo(const std::string& host, StorageType type, int64 usage) + : host(host), + type(type), + usage(usage) {} + std::string host; + StorageType type; + int64 usage; +}; + +// The quota manager class. This class is instantiated per profile and +// held by the profile. With the exception of the constructor and the +// proxy() method, all methods should only be called on the IO thread. +class STORAGE_EXPORT QuotaManager + : public QuotaTaskObserver, + public QuotaEvictionHandler, + public base::RefCountedThreadSafe<QuotaManager, QuotaManagerDeleter> { + public: + typedef base::Callback<void(QuotaStatusCode, + int64 /* usage */, + int64 /* quota */)> + GetUsageAndQuotaCallback; + + static const int64 kIncognitoDefaultQuotaLimit; + static const int64 kNoLimit; + + QuotaManager( + bool is_incognito, + const base::FilePath& profile_path, + const scoped_refptr<base::SingleThreadTaskRunner>& io_thread, + const scoped_refptr<base::SequencedTaskRunner>& db_thread, + const scoped_refptr<SpecialStoragePolicy>& special_storage_policy); + + // Returns a proxy object that can be used on any thread. + QuotaManagerProxy* proxy() { return proxy_.get(); } + + // Called by clients or webapps. Returns usage per host. + void GetUsageInfo(const GetUsageInfoCallback& callback); + + // Called by Web Apps. + // This method is declared as virtual to allow test code to override it. + virtual void GetUsageAndQuotaForWebApps( + const GURL& origin, + StorageType type, + const GetUsageAndQuotaCallback& callback); + + // Called by StorageClients. + // This method is declared as virtual to allow test code to override it. + // + // For UnlimitedStorage origins, this version skips usage and quota handling + // to avoid extra query cost. + // Do not call this method for apps/user-facing code. + virtual void GetUsageAndQuota( + const GURL& origin, + StorageType type, + const GetUsageAndQuotaCallback& callback); + + // Called by clients via proxy. + // Client storage should call this method when storage is accessed. + // Used to maintain LRU ordering. + void NotifyStorageAccessed(QuotaClient::ID client_id, + const GURL& origin, + StorageType type); + + // Called by clients via proxy. + // Client storage must call this method whenever they have made any + // modifications that change the amount of data stored in their storage. + void NotifyStorageModified(QuotaClient::ID client_id, + const GURL& origin, + StorageType type, + int64 delta); + + // Used to avoid evicting origins with open pages. + // A call to NotifyOriginInUse must be balanced by a later call + // to NotifyOriginNoLongerInUse. + void NotifyOriginInUse(const GURL& origin); + void NotifyOriginNoLongerInUse(const GURL& origin); + bool IsOriginInUse(const GURL& origin) const { + return origins_in_use_.find(origin) != origins_in_use_.end(); + } + + void SetUsageCacheEnabled(QuotaClient::ID client_id, + const GURL& origin, + StorageType type, + bool enabled); + + // DeleteOriginData and DeleteHostData (surprisingly enough) delete data of a + // particular StorageType associated with either a specific origin or set of + // origins. Each method additionally requires a |quota_client_mask| which + // specifies the types of QuotaClients to delete from the origin. This is + // specified by the caller as a bitmask built from QuotaClient::IDs. Setting + // the mask to QuotaClient::kAllClientsMask will remove all clients from the + // origin, regardless of type. + virtual void DeleteOriginData(const GURL& origin, + StorageType type, + int quota_client_mask, + const StatusCallback& callback); + void DeleteHostData(const std::string& host, + StorageType type, + int quota_client_mask, + const StatusCallback& callback); + + // Called by UI and internal modules. + void GetAvailableSpace(const AvailableSpaceCallback& callback); + void GetTemporaryGlobalQuota(const QuotaCallback& callback); + + // Ok to call with NULL callback. + void SetTemporaryGlobalOverrideQuota(int64 new_quota, + const QuotaCallback& callback); + + void GetPersistentHostQuota(const std::string& host, + const QuotaCallback& callback); + void SetPersistentHostQuota(const std::string& host, + int64 new_quota, + const QuotaCallback& callback); + void GetGlobalUsage(StorageType type, const GlobalUsageCallback& callback); + void GetHostUsage(const std::string& host, StorageType type, + const UsageCallback& callback); + void GetHostUsage(const std::string& host, StorageType type, + QuotaClient::ID client_id, + const UsageCallback& callback); + + bool IsTrackingHostUsage(StorageType type, QuotaClient::ID client_id) const; + + void GetStatistics(std::map<std::string, std::string>* statistics); + + bool IsStorageUnlimited(const GURL& origin, StorageType type) const; + + bool CanQueryDiskSize(const GURL& origin) const { + return special_storage_policy_.get() && + special_storage_policy_->CanQueryDiskSize(origin); + } + + virtual void GetOriginsModifiedSince(StorageType type, + base::Time modified_since, + const GetOriginsCallback& callback); + + bool ResetUsageTracker(StorageType type); + + // Used to register/deregister observers that wish to monitor storage events. + void AddStorageObserver(StorageObserver* observer, + const StorageObserver::MonitorParams& params); + void RemoveStorageObserver(StorageObserver* observer); + void RemoveStorageObserverForFilter(StorageObserver* observer, + const StorageObserver::Filter& filter); + + // Determines the portion of the temp pool that can be + // utilized by a single host (ie. 5 for 20%). + static const int kPerHostTemporaryPortion; + + static const int64 kPerHostPersistentQuotaLimit; + + static const char kDatabaseName[]; + + static const int kThresholdOfErrorsToBeBlacklisted; + + static const int kEvictionIntervalInMilliSeconds; + + // These are kept non-const so that test code can change the value. + // TODO(kinuko): Make this a real const value and add a proper way to set + // the quota for syncable storage. (http://crbug.com/155488) + static int64 kMinimumPreserveForSystem; + static int64 kSyncableStorageDefaultHostQuota; + + protected: + virtual ~QuotaManager(); + + private: + friend class base::DeleteHelper<QuotaManager>; + friend class base::RefCountedThreadSafe<QuotaManager, QuotaManagerDeleter>; + friend class content::QuotaManagerTest; + friend class content::StorageMonitorTest; + friend class content::MockQuotaManager; + friend class content::MockStorageClient; + friend class quota_internals::QuotaInternalsProxy; + friend class QuotaManagerProxy; + friend class QuotaTemporaryStorageEvictor; + friend struct QuotaManagerDeleter; + + class GetUsageInfoTask; + + class OriginDataDeleter; + class HostDataDeleter; + + class GetModifiedSinceHelper; + class DumpQuotaTableHelper; + class DumpOriginInfoTableHelper; + + typedef QuotaDatabase::QuotaTableEntry QuotaTableEntry; + typedef QuotaDatabase::OriginInfoTableEntry OriginInfoTableEntry; + typedef std::vector<QuotaTableEntry> QuotaTableEntries; + typedef std::vector<OriginInfoTableEntry> OriginInfoTableEntries; + + // Function pointer type used to store the function which returns the + // available disk space for the disk containing the given FilePath. + typedef int64 (*GetAvailableDiskSpaceFn)(const base::FilePath&); + + typedef base::Callback<void(const QuotaTableEntries&)> + DumpQuotaTableCallback; + typedef base::Callback<void(const OriginInfoTableEntries&)> + DumpOriginInfoTableCallback; + + struct EvictionContext { + EvictionContext(); + virtual ~EvictionContext(); + GURL evicted_origin; + StorageType evicted_type; + + EvictOriginDataCallback evict_origin_data_callback; + }; + + typedef QuotaEvictionHandler::UsageAndQuotaCallback + UsageAndQuotaDispatcherCallback; + + // This initialization method is lazily called on the IO thread + // when the first quota manager API is called. + // Initialize must be called after all quota clients are added to the + // manager by RegisterStorage. + void LazyInitialize(); + + // Called by clients via proxy. + // Registers a quota client to the manager. + // The client must remain valid until OnQuotaManagerDestored is called. + void RegisterClient(QuotaClient* client); + + UsageTracker* GetUsageTracker(StorageType type) const; + + // Extract cached origins list from the usage tracker. + // (Might return empty list if no origin is tracked by the tracker.) + void GetCachedOrigins(StorageType type, std::set<GURL>* origins); + + // These internal methods are separately defined mainly for testing. + void NotifyStorageAccessedInternal( + QuotaClient::ID client_id, + const GURL& origin, + StorageType type, + base::Time accessed_time); + void NotifyStorageModifiedInternal( + QuotaClient::ID client_id, + const GURL& origin, + StorageType type, + int64 delta, + base::Time modified_time); + + void DumpQuotaTable(const DumpQuotaTableCallback& callback); + void DumpOriginInfoTable(const DumpOriginInfoTableCallback& callback); + + // Methods for eviction logic. + void StartEviction(); + void DeleteOriginFromDatabase(const GURL& origin, StorageType type); + + void DidOriginDataEvicted(QuotaStatusCode status); + + void ReportHistogram(); + void DidGetTemporaryGlobalUsageForHistogram(int64 usage, + int64 unlimited_usage); + void DidGetPersistentGlobalUsageForHistogram(int64 usage, + int64 unlimited_usage); + + // QuotaEvictionHandler. + virtual void GetLRUOrigin( + StorageType type, + const GetLRUOriginCallback& callback) OVERRIDE; + virtual void EvictOriginData( + const GURL& origin, + StorageType type, + const EvictOriginDataCallback& callback) OVERRIDE; + virtual void GetUsageAndQuotaForEviction( + const UsageAndQuotaCallback& callback) OVERRIDE; + + void DidSetTemporaryGlobalOverrideQuota(const QuotaCallback& callback, + const int64* new_quota, + bool success); + void DidGetPersistentHostQuota(const std::string& host, + const int64* quota, + bool success); + void DidSetPersistentHostQuota(const std::string& host, + const QuotaCallback& callback, + const int64* new_quota, + bool success); + void DidInitialize(int64* temporary_quota_override, + int64* desired_available_space, + bool success); + void DidGetLRUOrigin(const GURL* origin, + bool success); + void DidGetInitialTemporaryGlobalQuota(QuotaStatusCode status, + int64 quota_unused); + void DidInitializeTemporaryOriginsInfo(bool success); + void DidGetAvailableSpace(int64 space); + void DidDatabaseWork(bool success); + + void DeleteOnCorrectThread() const; + + void PostTaskAndReplyWithResultForDBThread( + const tracked_objects::Location& from_here, + const base::Callback<bool(QuotaDatabase*)>& task, + const base::Callback<void(bool)>& reply); + + const bool is_incognito_; + const base::FilePath profile_path_; + + scoped_refptr<QuotaManagerProxy> proxy_; + bool db_disabled_; + bool eviction_disabled_; + scoped_refptr<base::SingleThreadTaskRunner> io_thread_; + scoped_refptr<base::SequencedTaskRunner> db_thread_; + mutable scoped_ptr<QuotaDatabase> database_; + + GetLRUOriginCallback lru_origin_callback_; + std::set<GURL> access_notified_origins_; + + QuotaClientList clients_; + + scoped_ptr<UsageTracker> temporary_usage_tracker_; + scoped_ptr<UsageTracker> persistent_usage_tracker_; + scoped_ptr<UsageTracker> syncable_usage_tracker_; + // TODO(michaeln): Need a way to clear the cache, drop and + // reinstantiate the trackers when they're not handling requests. + + scoped_ptr<QuotaTemporaryStorageEvictor> temporary_storage_evictor_; + EvictionContext eviction_context_; + + ClosureQueue db_initialization_callbacks_; + AvailableSpaceCallbackQueue available_space_callbacks_; + GlobalQuotaCallbackQueue temporary_global_quota_callbacks_; + HostQuotaCallbackMap persistent_host_quota_callbacks_; + + bool temporary_quota_initialized_; + int64 temporary_quota_override_; + + int64 desired_available_space_; + + // Map from origin to count. + std::map<GURL, int> origins_in_use_; + // Map from origin to error count. + std::map<GURL, int> origins_in_error_; + + scoped_refptr<SpecialStoragePolicy> special_storage_policy_; + + base::RepeatingTimer<QuotaManager> histogram_timer_; + + // Pointer to the function used to get the available disk space. This is + // overwritten by QuotaManagerTest in order to attain a deterministic reported + // value. The default value points to base::SysInfo::AmountOfFreeDiskSpace. + GetAvailableDiskSpaceFn get_disk_space_fn_; + + scoped_ptr<StorageMonitor> storage_monitor_; + + base::WeakPtrFactory<QuotaManager> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(QuotaManager); +}; + +struct QuotaManagerDeleter { + static void Destruct(const QuotaManager* manager) { + manager->DeleteOnCorrectThread(); + } +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_QUOTA_QUOTA_MANAGER_H_ diff --git a/storage/browser/quota/quota_manager_proxy.cc b/storage/browser/quota/quota_manager_proxy.cc new file mode 100644 index 0000000..c044258 --- /dev/null +++ b/storage/browser/quota/quota_manager_proxy.cc @@ -0,0 +1,161 @@ +// Copyright 2014 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 "storage/browser/quota/quota_manager_proxy.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback.h" +#include "base/sequenced_task_runner.h" +#include "base/single_thread_task_runner.h" +#include "base/strings/string_number_conversions.h" +#include "base/task_runner_util.h" + +namespace storage { + +namespace { + +void DidGetUsageAndQuota( + base::SequencedTaskRunner* original_task_runner, + const QuotaManagerProxy::GetUsageAndQuotaCallback& callback, + QuotaStatusCode status, int64 usage, int64 quota) { + if (!original_task_runner->RunsTasksOnCurrentThread()) { + original_task_runner->PostTask( + FROM_HERE, + base::Bind(&DidGetUsageAndQuota, + make_scoped_refptr(original_task_runner), + callback, status, usage, quota)); + return; + } + callback.Run(status, usage, quota); +} + +} // namespace + +void QuotaManagerProxy::RegisterClient(QuotaClient* client) { + if (!io_thread_->BelongsToCurrentThread() && + io_thread_->PostTask( + FROM_HERE, + base::Bind(&QuotaManagerProxy::RegisterClient, this, client))) { + return; + } + + if (manager_) + manager_->RegisterClient(client); + else + client->OnQuotaManagerDestroyed(); +} + +void QuotaManagerProxy::NotifyStorageAccessed( + QuotaClient::ID client_id, + const GURL& origin, + StorageType type) { + if (!io_thread_->BelongsToCurrentThread()) { + io_thread_->PostTask( + FROM_HERE, + base::Bind(&QuotaManagerProxy::NotifyStorageAccessed, this, client_id, + origin, type)); + return; + } + + if (manager_) + manager_->NotifyStorageAccessed(client_id, origin, type); +} + +void QuotaManagerProxy::NotifyStorageModified( + QuotaClient::ID client_id, + const GURL& origin, + StorageType type, + int64 delta) { + if (!io_thread_->BelongsToCurrentThread()) { + io_thread_->PostTask( + FROM_HERE, + base::Bind(&QuotaManagerProxy::NotifyStorageModified, this, client_id, + origin, type, delta)); + return; + } + + if (manager_) + manager_->NotifyStorageModified(client_id, origin, type, delta); +} + +void QuotaManagerProxy::NotifyOriginInUse( + const GURL& origin) { + if (!io_thread_->BelongsToCurrentThread()) { + io_thread_->PostTask( + FROM_HERE, + base::Bind(&QuotaManagerProxy::NotifyOriginInUse, this, origin)); + return; + } + + if (manager_) + manager_->NotifyOriginInUse(origin); +} + +void QuotaManagerProxy::NotifyOriginNoLongerInUse( + const GURL& origin) { + if (!io_thread_->BelongsToCurrentThread()) { + io_thread_->PostTask( + FROM_HERE, + base::Bind(&QuotaManagerProxy::NotifyOriginNoLongerInUse, this, + origin)); + return; + } + if (manager_) + manager_->NotifyOriginNoLongerInUse(origin); +} + +void QuotaManagerProxy::SetUsageCacheEnabled(QuotaClient::ID client_id, + const GURL& origin, + StorageType type, + bool enabled) { + if (!io_thread_->BelongsToCurrentThread()) { + io_thread_->PostTask( + FROM_HERE, + base::Bind(&QuotaManagerProxy::SetUsageCacheEnabled, this, + client_id, origin, type, enabled)); + return; + } + if (manager_) + manager_->SetUsageCacheEnabled(client_id, origin, type, enabled); +} + +void QuotaManagerProxy::GetUsageAndQuota( + base::SequencedTaskRunner* original_task_runner, + const GURL& origin, + StorageType type, + const GetUsageAndQuotaCallback& callback) { + if (!io_thread_->BelongsToCurrentThread()) { + io_thread_->PostTask( + FROM_HERE, + base::Bind(&QuotaManagerProxy::GetUsageAndQuota, this, + make_scoped_refptr(original_task_runner), + origin, type, callback)); + return; + } + if (!manager_) { + DidGetUsageAndQuota(original_task_runner, callback, kQuotaErrorAbort, 0, 0); + return; + } + manager_->GetUsageAndQuota( + origin, type, + base::Bind(&DidGetUsageAndQuota, + make_scoped_refptr(original_task_runner), callback)); +} + +QuotaManager* QuotaManagerProxy::quota_manager() const { + DCHECK(!io_thread_.get() || io_thread_->BelongsToCurrentThread()); + return manager_; +} + +QuotaManagerProxy::QuotaManagerProxy( + QuotaManager* manager, + const scoped_refptr<base::SingleThreadTaskRunner>& io_thread) + : manager_(manager), io_thread_(io_thread) { +} + +QuotaManagerProxy::~QuotaManagerProxy() { +} + +} // namespace storage diff --git a/storage/browser/quota/quota_manager_proxy.h b/storage/browser/quota/quota_manager_proxy.h new file mode 100644 index 0000000..bf9d9ca --- /dev/null +++ b/storage/browser/quota/quota_manager_proxy.h @@ -0,0 +1,79 @@ +// Copyright 2014 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. + +#ifndef STORAGE_BROWSER_QUOTA_QUOTA_MANAGER_PROXY_H_ +#define STORAGE_BROWSER_QUOTA_QUOTA_MANAGER_PROXY_H_ + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/sequenced_task_runner_helpers.h" +#include "storage/browser/quota/quota_callbacks.h" +#include "storage/browser/quota/quota_client.h" +#include "storage/browser/quota/quota_database.h" +#include "storage/browser/quota/quota_manager.h" +#include "storage/browser/quota/quota_task.h" +#include "storage/browser/quota/special_storage_policy.h" +#include "storage/browser/storage_browser_export.h" + +namespace base { +class SequencedTaskRunner; +class SingleThreadTaskRunner; +} + +namespace storage { + +// The proxy may be called and finally released on any thread. +class STORAGE_EXPORT QuotaManagerProxy + : public base::RefCountedThreadSafe<QuotaManagerProxy> { + public: + typedef QuotaManager::GetUsageAndQuotaCallback + GetUsageAndQuotaCallback; + + virtual void RegisterClient(QuotaClient* client); + virtual void NotifyStorageAccessed(QuotaClient::ID client_id, + const GURL& origin, + StorageType type); + virtual void NotifyStorageModified(QuotaClient::ID client_id, + const GURL& origin, + StorageType type, + int64 delta); + virtual void NotifyOriginInUse(const GURL& origin); + virtual void NotifyOriginNoLongerInUse(const GURL& origin); + + virtual void SetUsageCacheEnabled(QuotaClient::ID client_id, + const GURL& origin, + StorageType type, + bool enabled); + virtual void GetUsageAndQuota( + base::SequencedTaskRunner* original_task_runner, + const GURL& origin, + StorageType type, + const GetUsageAndQuotaCallback& callback); + + // This method may only be called on the IO thread. + // It may return NULL if the manager has already been deleted. + QuotaManager* quota_manager() const; + + protected: + friend class QuotaManager; + friend class base::RefCountedThreadSafe<QuotaManagerProxy>; + + QuotaManagerProxy( + QuotaManager* manager, + const scoped_refptr<base::SingleThreadTaskRunner>& io_thread); + virtual ~QuotaManagerProxy(); + + QuotaManager* manager_; // only accessed on the io thread + scoped_refptr<base::SingleThreadTaskRunner> io_thread_; + + DISALLOW_COPY_AND_ASSIGN(QuotaManagerProxy); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_QUOTA_QUOTA_MANAGER_PROXY_H_ diff --git a/storage/browser/quota/quota_task.cc b/storage/browser/quota/quota_task.cc new file mode 100644 index 0000000..b8d4ed1 --- /dev/null +++ b/storage/browser/quota/quota_task.cc @@ -0,0 +1,79 @@ +// Copyright 2013 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 "storage/browser/quota/quota_task.h" + +#include <algorithm> +#include <functional> + +#include "base/bind.h" +#include "base/location.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/single_thread_task_runner.h" + +using base::TaskRunner; + +namespace storage { + +// QuotaTask --------------------------------------------------------------- + +QuotaTask::~QuotaTask() { +} + +void QuotaTask::Start() { + DCHECK(observer_); + observer()->RegisterTask(this); + Run(); +} + +QuotaTask::QuotaTask(QuotaTaskObserver* observer) + : observer_(observer), + original_task_runner_(base::MessageLoopProxy::current()), + delete_scheduled_(false) { +} + +void QuotaTask::CallCompleted() { + DCHECK(original_task_runner_->BelongsToCurrentThread()); + if (observer_) { + observer_->UnregisterTask(this); + Completed(); + } +} + +void QuotaTask::Abort() { + DCHECK(original_task_runner_->BelongsToCurrentThread()); + observer_ = NULL; + Aborted(); +} + +void QuotaTask::DeleteSoon() { + DCHECK(original_task_runner_->BelongsToCurrentThread()); + if (delete_scheduled_) + return; + delete_scheduled_ = true; + base::MessageLoop::current()->DeleteSoon(FROM_HERE, this); +} + +// QuotaTaskObserver ------------------------------------------------------- + +QuotaTaskObserver::~QuotaTaskObserver() { + std::for_each(running_quota_tasks_.begin(), + running_quota_tasks_.end(), + std::mem_fun(&QuotaTask::Abort)); +} + +QuotaTaskObserver::QuotaTaskObserver() { +} + +void QuotaTaskObserver::RegisterTask(QuotaTask* task) { + running_quota_tasks_.insert(task); +} + +void QuotaTaskObserver::UnregisterTask(QuotaTask* task) { + DCHECK(running_quota_tasks_.find(task) != running_quota_tasks_.end()); + running_quota_tasks_.erase(task); +} + +} // namespace storage diff --git a/storage/browser/quota/quota_task.h b/storage/browser/quota/quota_task.h new file mode 100644 index 0000000..35e004e --- /dev/null +++ b/storage/browser/quota/quota_task.h @@ -0,0 +1,79 @@ +// Copyright 2013 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. + +#ifndef STORAGE_BROWSER_QUOTA_QUOTA_TASK_H_ +#define STORAGE_BROWSER_QUOTA_QUOTA_TASK_H_ + +#include <set> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/sequenced_task_runner_helpers.h" +#include "storage/browser/storage_browser_export.h" + +namespace base { +class SingleThreadTaskRunner; +class TaskRunner; +} + +namespace storage { + +class QuotaTaskObserver; + +// A base class for quota tasks. +// TODO(kinuko): Revise this using base::Callback. +class QuotaTask { + public: + void Start(); + + protected: + explicit QuotaTask(QuotaTaskObserver* observer); + virtual ~QuotaTask(); + + // The task body. + virtual void Run() = 0; + + // Called upon completion, on the original message loop. + virtual void Completed() = 0; + + // Called when the task is aborted. + virtual void Aborted() {} + + void CallCompleted(); + + // Call this to delete itself. + void DeleteSoon(); + + QuotaTaskObserver* observer() const { return observer_; } + base::SingleThreadTaskRunner* original_task_runner() const { + return original_task_runner_.get(); + } + + private: + friend class base::DeleteHelper<QuotaTask>; + friend class QuotaTaskObserver; + + void Abort(); + QuotaTaskObserver* observer_; + scoped_refptr<base::SingleThreadTaskRunner> original_task_runner_; + bool delete_scheduled_; +}; + +class STORAGE_EXPORT QuotaTaskObserver { + protected: + friend class QuotaTask; + + QuotaTaskObserver(); + virtual ~QuotaTaskObserver(); + + void RegisterTask(QuotaTask* task); + void UnregisterTask(QuotaTask* task); + + typedef std::set<QuotaTask*> TaskSet; + TaskSet running_quota_tasks_; +}; +} + +#endif // STORAGE_BROWSER_QUOTA_QUOTA_TASK_H_ diff --git a/storage/browser/quota/quota_temporary_storage_evictor.cc b/storage/browser/quota/quota_temporary_storage_evictor.cc new file mode 100644 index 0000000..cb4c504 --- /dev/null +++ b/storage/browser/quota/quota_temporary_storage_evictor.cc @@ -0,0 +1,261 @@ +// Copyright 2013 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 "storage/browser/quota/quota_temporary_storage_evictor.h" + +#include <algorithm> + +#include "base/bind.h" +#include "base/metrics/histogram.h" +#include "storage/browser/quota/quota_manager.h" +#include "url/gurl.h" + +#define UMA_HISTOGRAM_MBYTES(name, sample) \ + UMA_HISTOGRAM_CUSTOM_COUNTS( \ + (name), static_cast<int>((sample) / kMBytes), \ + 1, 10 * 1024 * 1024 /* 10TB */, 100) + +#define UMA_HISTOGRAM_MINUTES(name, sample) \ + UMA_HISTOGRAM_CUSTOM_TIMES( \ + (name), (sample), \ + base::TimeDelta::FromMinutes(1), \ + base::TimeDelta::FromDays(1), 50) + +namespace { +const int64 kMBytes = 1024 * 1024; +const double kUsageRatioToStartEviction = 0.7; +const int kThresholdOfErrorsToStopEviction = 5; +const int kHistogramReportIntervalMinutes = 60; +} + +namespace storage { + +const int QuotaTemporaryStorageEvictor:: + kMinAvailableDiskSpaceToStartEvictionNotSpecified = -1; + +QuotaTemporaryStorageEvictor::EvictionRoundStatistics::EvictionRoundStatistics() + : in_round(false), + is_initialized(false), + usage_overage_at_round(-1), + diskspace_shortage_at_round(-1), + usage_on_beginning_of_round(-1), + usage_on_end_of_round(-1), + num_evicted_origins_in_round(0) { +} + +QuotaTemporaryStorageEvictor::QuotaTemporaryStorageEvictor( + QuotaEvictionHandler* quota_eviction_handler, + int64 interval_ms) + : min_available_disk_space_to_start_eviction_( + kMinAvailableDiskSpaceToStartEvictionNotSpecified), + quota_eviction_handler_(quota_eviction_handler), + interval_ms_(interval_ms), + repeated_eviction_(true), + weak_factory_(this) { + DCHECK(quota_eviction_handler); +} + +QuotaTemporaryStorageEvictor::~QuotaTemporaryStorageEvictor() { +} + +void QuotaTemporaryStorageEvictor::GetStatistics( + std::map<std::string, int64>* statistics) { + DCHECK(statistics); + + (*statistics)["errors-on-evicting-origin"] = + statistics_.num_errors_on_evicting_origin; + (*statistics)["errors-on-getting-usage-and-quota"] = + statistics_.num_errors_on_getting_usage_and_quota; + (*statistics)["evicted-origins"] = + statistics_.num_evicted_origins; + (*statistics)["eviction-rounds"] = + statistics_.num_eviction_rounds; + (*statistics)["skipped-eviction-rounds"] = + statistics_.num_skipped_eviction_rounds; +} + +void QuotaTemporaryStorageEvictor::ReportPerRoundHistogram() { + DCHECK(round_statistics_.in_round); + DCHECK(round_statistics_.is_initialized); + + base::Time now = base::Time::Now(); + UMA_HISTOGRAM_TIMES("Quota.TimeSpentToAEvictionRound", + now - round_statistics_.start_time); + if (!time_of_end_of_last_round_.is_null()) + UMA_HISTOGRAM_MINUTES("Quota.TimeDeltaOfEvictionRounds", + now - time_of_end_of_last_round_); + UMA_HISTOGRAM_MBYTES("Quota.UsageOverageOfTemporaryGlobalStorage", + round_statistics_.usage_overage_at_round); + UMA_HISTOGRAM_MBYTES("Quota.DiskspaceShortage", + round_statistics_.diskspace_shortage_at_round); + UMA_HISTOGRAM_MBYTES("Quota.EvictedBytesPerRound", + round_statistics_.usage_on_beginning_of_round - + round_statistics_.usage_on_end_of_round); + UMA_HISTOGRAM_COUNTS("Quota.NumberOfEvictedOriginsPerRound", + round_statistics_.num_evicted_origins_in_round); +} + +void QuotaTemporaryStorageEvictor::ReportPerHourHistogram() { + Statistics stats_in_hour(statistics_); + stats_in_hour.subtract_assign(previous_statistics_); + previous_statistics_ = statistics_; + + UMA_HISTOGRAM_COUNTS("Quota.ErrorsOnEvictingOriginPerHour", + stats_in_hour.num_errors_on_evicting_origin); + UMA_HISTOGRAM_COUNTS("Quota.ErrorsOnGettingUsageAndQuotaPerHour", + stats_in_hour.num_errors_on_getting_usage_and_quota); + UMA_HISTOGRAM_COUNTS("Quota.EvictedOriginsPerHour", + stats_in_hour.num_evicted_origins); + UMA_HISTOGRAM_COUNTS("Quota.EvictionRoundsPerHour", + stats_in_hour.num_eviction_rounds); + UMA_HISTOGRAM_COUNTS("Quota.SkippedEvictionRoundsPerHour", + stats_in_hour.num_skipped_eviction_rounds); +} + +void QuotaTemporaryStorageEvictor::OnEvictionRoundStarted() { + if (round_statistics_.in_round) + return; + round_statistics_.in_round = true; + round_statistics_.start_time = base::Time::Now(); + ++statistics_.num_eviction_rounds; +} + +void QuotaTemporaryStorageEvictor::OnEvictionRoundFinished() { + // Check if skipped round + if (round_statistics_.num_evicted_origins_in_round) { + ReportPerRoundHistogram(); + time_of_end_of_last_nonskipped_round_ = base::Time::Now(); + } else { + ++statistics_.num_skipped_eviction_rounds; + } + // Reset stats for next round. + round_statistics_ = EvictionRoundStatistics(); +} + +void QuotaTemporaryStorageEvictor::Start() { + DCHECK(CalledOnValidThread()); + StartEvictionTimerWithDelay(0); + + if (histogram_timer_.IsRunning()) + return; + + histogram_timer_.Start( + FROM_HERE, base::TimeDelta::FromMinutes(kHistogramReportIntervalMinutes), + this, &QuotaTemporaryStorageEvictor::ReportPerHourHistogram); +} + +void QuotaTemporaryStorageEvictor::StartEvictionTimerWithDelay(int delay_ms) { + if (eviction_timer_.IsRunning()) + return; + eviction_timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(delay_ms), + this, &QuotaTemporaryStorageEvictor::ConsiderEviction); +} + +void QuotaTemporaryStorageEvictor::ConsiderEviction() { + OnEvictionRoundStarted(); + + // Get usage and disk space, then continue. + quota_eviction_handler_->GetUsageAndQuotaForEviction( + base::Bind(&QuotaTemporaryStorageEvictor::OnGotUsageAndQuotaForEviction, + weak_factory_.GetWeakPtr())); +} + +void QuotaTemporaryStorageEvictor::OnGotUsageAndQuotaForEviction( + QuotaStatusCode status, + const UsageAndQuota& qau) { + DCHECK(CalledOnValidThread()); + + int64 usage = qau.global_limited_usage; + DCHECK_GE(usage, 0); + + if (status != kQuotaStatusOk) + ++statistics_.num_errors_on_getting_usage_and_quota; + + int64 usage_overage = std::max( + static_cast<int64>(0), + usage - static_cast<int64>(qau.quota * kUsageRatioToStartEviction)); + + // min_available_disk_space_to_start_eviction_ might be < 0 if no value + // is explicitly configured yet. + int64 diskspace_shortage = std::max( + static_cast<int64>(0), + min_available_disk_space_to_start_eviction_ - qau.available_disk_space); + + if (!round_statistics_.is_initialized) { + round_statistics_.usage_overage_at_round = usage_overage; + round_statistics_.diskspace_shortage_at_round = diskspace_shortage; + round_statistics_.usage_on_beginning_of_round = usage; + round_statistics_.is_initialized = true; + } + round_statistics_.usage_on_end_of_round = usage; + + int64 amount_to_evict = std::max(usage_overage, diskspace_shortage); + if (status == kQuotaStatusOk && amount_to_evict > 0) { + // Space is getting tight. Get the least recently used origin and continue. + // TODO(michaeln): if the reason for eviction is low physical disk space, + // make 'unlimited' origins subject to eviction too. + quota_eviction_handler_->GetLRUOrigin( + kStorageTypeTemporary, + base::Bind(&QuotaTemporaryStorageEvictor::OnGotLRUOrigin, + weak_factory_.GetWeakPtr())); + } else { + if (repeated_eviction_) { + // No action required, sleep for a while and check again later. + if (statistics_.num_errors_on_getting_usage_and_quota < + kThresholdOfErrorsToStopEviction) { + StartEvictionTimerWithDelay(interval_ms_); + } else { + // TODO(dmikurube): Try restarting eviction after a while. + LOG(WARNING) << "Stopped eviction of temporary storage due to errors " + "in GetUsageAndQuotaForEviction."; + } + } + OnEvictionRoundFinished(); + } + + // TODO(dmikurube): Add error handling for the case status != kQuotaStatusOk. +} + +void QuotaTemporaryStorageEvictor::OnGotLRUOrigin(const GURL& origin) { + DCHECK(CalledOnValidThread()); + + if (origin.is_empty()) { + if (repeated_eviction_) + StartEvictionTimerWithDelay(interval_ms_); + OnEvictionRoundFinished(); + return; + } + + quota_eviction_handler_->EvictOriginData(origin, kStorageTypeTemporary, + base::Bind( + &QuotaTemporaryStorageEvictor::OnEvictionComplete, + weak_factory_.GetWeakPtr())); +} + +void QuotaTemporaryStorageEvictor::OnEvictionComplete( + QuotaStatusCode status) { + DCHECK(CalledOnValidThread()); + + // Just calling ConsiderEviction() or StartEvictionTimerWithDelay() here is + // ok. No need to deal with the case that all of the Delete operations fail + // for a certain origin. It doesn't result in trying to evict the same + // origin permanently. The evictor skips origins which had deletion errors + // a few times. + + if (status == kQuotaStatusOk) { + ++statistics_.num_evicted_origins; + ++round_statistics_.num_evicted_origins_in_round; + // We many need to get rid of more space so reconsider immediately. + ConsiderEviction(); + } else { + ++statistics_.num_errors_on_evicting_origin; + if (repeated_eviction_) { + // Sleep for a while and retry again until we see too many errors. + StartEvictionTimerWithDelay(interval_ms_); + } + OnEvictionRoundFinished(); + } +} + +} // namespace storage diff --git a/storage/browser/quota/quota_temporary_storage_evictor.h b/storage/browser/quota/quota_temporary_storage_evictor.h new file mode 100644 index 0000000..706355d --- /dev/null +++ b/storage/browser/quota/quota_temporary_storage_evictor.h @@ -0,0 +1,134 @@ +// Copyright 2013 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. + +#ifndef STORAGE_BROWSER_QUOTA_QUOTA_TEMPORARY_STORAGE_EVICTOR_H_ +#define STORAGE_BROWSER_QUOTA_QUOTA_TEMPORARY_STORAGE_EVICTOR_H_ + +#include <map> +#include <string> + +#include "base/memory/weak_ptr.h" +#include "base/threading/non_thread_safe.h" +#include "base/timer/timer.h" +#include "storage/browser/storage_browser_export.h" +#include "storage/common/quota/quota_types.h" + +class GURL; + +namespace content { +class QuotaTemporaryStorageEvictorTest; +} + +namespace storage { + +class QuotaEvictionHandler; +struct UsageAndQuota; + +class STORAGE_EXPORT_PRIVATE QuotaTemporaryStorageEvictor + : public base::NonThreadSafe { + public: + struct Statistics { + Statistics() + : num_errors_on_evicting_origin(0), + num_errors_on_getting_usage_and_quota(0), + num_evicted_origins(0), + num_eviction_rounds(0), + num_skipped_eviction_rounds(0) {} + int64 num_errors_on_evicting_origin; + int64 num_errors_on_getting_usage_and_quota; + int64 num_evicted_origins; + int64 num_eviction_rounds; + int64 num_skipped_eviction_rounds; + + void subtract_assign(const Statistics& rhs) { + num_errors_on_evicting_origin -= rhs.num_errors_on_evicting_origin; + num_errors_on_getting_usage_and_quota -= + rhs.num_errors_on_getting_usage_and_quota; + num_evicted_origins -= rhs.num_evicted_origins; + num_eviction_rounds -= rhs.num_eviction_rounds; + num_skipped_eviction_rounds -= rhs.num_skipped_eviction_rounds; + } + }; + + struct EvictionRoundStatistics { + EvictionRoundStatistics(); + + bool in_round; + bool is_initialized; + + base::Time start_time; + int64 usage_overage_at_round; + int64 diskspace_shortage_at_round; + + int64 usage_on_beginning_of_round; + int64 usage_on_end_of_round; + int64 num_evicted_origins_in_round; + }; + + QuotaTemporaryStorageEvictor( + QuotaEvictionHandler* quota_eviction_handler, + int64 interval_ms); + virtual ~QuotaTemporaryStorageEvictor(); + + void GetStatistics(std::map<std::string, int64>* statistics); + void ReportPerRoundHistogram(); + void ReportPerHourHistogram(); + void Start(); + + int64 min_available_disk_space_to_start_eviction() { + return min_available_disk_space_to_start_eviction_; + } + void reset_min_available_disk_space_to_start_eviction() { + min_available_disk_space_to_start_eviction_ = + kMinAvailableDiskSpaceToStartEvictionNotSpecified; + } + void set_min_available_disk_space_to_start_eviction(int64 value) { + min_available_disk_space_to_start_eviction_ = value; + } + + private: + friend class content::QuotaTemporaryStorageEvictorTest; + + void StartEvictionTimerWithDelay(int delay_ms); + void ConsiderEviction(); + void OnGotUsageAndQuotaForEviction( + QuotaStatusCode status, + const UsageAndQuota& quota_and_usage); + void OnGotLRUOrigin(const GURL& origin); + void OnEvictionComplete(QuotaStatusCode status); + + void OnEvictionRoundStarted(); + void OnEvictionRoundFinished(); + + // This is only used for tests. + void set_repeated_eviction(bool repeated_eviction) { + repeated_eviction_ = repeated_eviction; + } + + static const int kMinAvailableDiskSpaceToStartEvictionNotSpecified; + + int64 min_available_disk_space_to_start_eviction_; + + // Not owned; quota_eviction_handler owns us. + QuotaEvictionHandler* quota_eviction_handler_; + + Statistics statistics_; + Statistics previous_statistics_; + EvictionRoundStatistics round_statistics_; + base::Time time_of_end_of_last_nonskipped_round_; + base::Time time_of_end_of_last_round_; + + int64 interval_ms_; + bool repeated_eviction_; + + base::OneShotTimer<QuotaTemporaryStorageEvictor> eviction_timer_; + base::RepeatingTimer<QuotaTemporaryStorageEvictor> histogram_timer_; + base::WeakPtrFactory<QuotaTemporaryStorageEvictor> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(QuotaTemporaryStorageEvictor); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_QUOTA_QUOTA_TEMPORARY_STORAGE_EVICTOR_H_ diff --git a/storage/browser/quota/special_storage_policy.cc b/storage/browser/quota/special_storage_policy.cc new file mode 100644 index 0000000..6e77450 --- /dev/null +++ b/storage/browser/quota/special_storage_policy.cc @@ -0,0 +1,38 @@ +// Copyright 2013 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 "storage/browser/quota/special_storage_policy.h" + +namespace storage { + +SpecialStoragePolicy::Observer::~Observer() {} + +SpecialStoragePolicy::SpecialStoragePolicy() {} + +SpecialStoragePolicy::~SpecialStoragePolicy() {} + +void SpecialStoragePolicy::AddObserver(Observer* observer) { + observers_.AddObserver(observer); +} + +void SpecialStoragePolicy::RemoveObserver(Observer* observer) { + observers_.RemoveObserver(observer); +} + +void SpecialStoragePolicy::NotifyGranted(const GURL& origin, int change_flags) { + scoped_refptr<SpecialStoragePolicy> protect(this); + FOR_EACH_OBSERVER(Observer, observers_, OnGranted(origin, change_flags)); +} + +void SpecialStoragePolicy::NotifyRevoked(const GURL& origin, int change_flags) { + scoped_refptr<SpecialStoragePolicy> protect(this); + FOR_EACH_OBSERVER(Observer, observers_, OnRevoked(origin, change_flags)); +} + +void SpecialStoragePolicy::NotifyCleared() { + scoped_refptr<SpecialStoragePolicy> protect(this); + FOR_EACH_OBSERVER(Observer, observers_, OnCleared()); +} + +} // namespace storage diff --git a/storage/browser/quota/special_storage_policy.h b/storage/browser/quota/special_storage_policy.h new file mode 100644 index 0000000..e6c37b5 --- /dev/null +++ b/storage/browser/quota/special_storage_policy.h @@ -0,0 +1,86 @@ +// Copyright 2013 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. + +#ifndef STORAGE_BROWSER_QUOTA_SPECIAL_STORAGE_POLICY_H_ +#define STORAGE_BROWSER_QUOTA_SPECIAL_STORAGE_POLICY_H_ + +#include <string> + +#include "base/memory/ref_counted.h" +#include "base/observer_list.h" +#include "storage/browser/storage_browser_export.h" + +class GURL; + +namespace storage { + +// Special rights are granted to 'extensions' and 'applications'. The +// storage subsystems query this interface to determine which origins +// have these rights. Chrome provides an impl that is cognizant of what +// is currently installed in the extensions system. +// The IsSomething() methods must be thread-safe, however Observers should +// only be notified, added, and removed on the IO thead. +class STORAGE_EXPORT SpecialStoragePolicy + : public base::RefCountedThreadSafe<SpecialStoragePolicy> { + public: + typedef int StoragePolicy; + enum ChangeFlags { + STORAGE_PROTECTED = 1 << 0, + STORAGE_UNLIMITED = 1 << 1, + }; + + class STORAGE_EXPORT Observer { + public: + virtual void OnGranted(const GURL& origin, int change_flags) = 0; + virtual void OnRevoked(const GURL& origin, int change_flags) = 0; + virtual void OnCleared() = 0; + + protected: + virtual ~Observer(); + }; + + SpecialStoragePolicy(); + + // Protected storage is not subject to removal by the browsing data remover. + virtual bool IsStorageProtected(const GURL& origin) = 0; + + // Unlimited storage is not subject to 'quotas'. + virtual bool IsStorageUnlimited(const GURL& origin) = 0; + + // Some origins (e.g. installed apps) have access to the size of the remaining + // disk capacity. + virtual bool CanQueryDiskSize(const GURL& origin) = 0; + + // Checks if extension identified with |extension_id| is registered as + // file handler. + virtual bool IsFileHandler(const std::string& extension_id) = 0; + + // Checks if the origin contains per-site isolated storage. + virtual bool HasIsolatedStorage(const GURL& origin) = 0; + + // Some origins are only allowed to store session-only data which is deleted + // when the session ends. + virtual bool IsStorageSessionOnly(const GURL& origin) = 0; + + // Returns true if some origins are only allowed session-only storage. + virtual bool HasSessionOnlyOrigins() = 0; + + // Adds/removes an observer, the policy does not take + // ownership of the observer. Should only be called on the IO thread. + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + + protected: + friend class base::RefCountedThreadSafe<SpecialStoragePolicy>; + virtual ~SpecialStoragePolicy(); + void NotifyGranted(const GURL& origin, int change_flags); + void NotifyRevoked(const GURL& origin, int change_flags); + void NotifyCleared(); + + ObserverList<Observer> observers_; +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_QUOTA_SPECIAL_STORAGE_POLICY_H_ diff --git a/storage/browser/quota/storage_monitor.cc b/storage/browser/quota/storage_monitor.cc new file mode 100644 index 0000000..f312bd3 --- /dev/null +++ b/storage/browser/quota/storage_monitor.cc @@ -0,0 +1,379 @@ +// Copyright 2014 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 "storage/browser/quota/storage_monitor.h" + +#include <algorithm> + +#include "base/stl_util.h" +#include "net/base/net_util.h" +#include "storage/browser/quota/quota_manager.h" +#include "storage/common/quota/quota_status_code.h" + +namespace storage { + +// StorageObserverList: + +StorageObserverList::ObserverState::ObserverState() + : requires_update(false) { +} + +StorageObserverList::StorageObserverList() {} + +StorageObserverList::~StorageObserverList() {} + +void StorageObserverList::AddObserver( + StorageObserver* observer, const StorageObserver::MonitorParams& params) { + ObserverState& observer_state = observers_[observer]; + observer_state.origin = params.filter.origin; + observer_state.rate = params.rate; +} + +void StorageObserverList::RemoveObserver(StorageObserver* observer) { + observers_.erase(observer); +} + +int StorageObserverList::ObserverCount() const { + return observers_.size(); +} + +void StorageObserverList::OnStorageChange(const StorageObserver::Event& event) { + for (StorageObserverStateMap::iterator it = observers_.begin(); + it != observers_.end(); ++it) { + it->second.requires_update = true; + } + + MaybeDispatchEvent(event); +} + +void StorageObserverList::MaybeDispatchEvent( + const StorageObserver::Event& event) { + notification_timer_.Stop(); + base::TimeDelta min_delay = base::TimeDelta::Max(); + bool all_observers_notified = true; + + for (StorageObserverStateMap::iterator it = observers_.begin(); + it != observers_.end(); ++it) { + if (!it->second.requires_update) + continue; + + base::TimeTicks current_time = base::TimeTicks::Now(); + base::TimeDelta delta = current_time - it->second.last_notification_time; + if (it->second.last_notification_time.is_null() || + delta >= it->second.rate) { + it->second.requires_update = false; + it->second.last_notification_time = current_time; + + if (it->second.origin == event.filter.origin) { + it->first->OnStorageEvent(event); + } else { + // When the quota and usage of an origin is requested, QuotaManager + // returns the quota and usage of the host. Multiple origins can map to + // to the same host, so ensure the |origin| field in the dispatched + // event matches the |origin| specified by the observer when it was + // registered. + StorageObserver::Event dispatch_event(event); + dispatch_event.filter.origin = it->second.origin; + it->first->OnStorageEvent(dispatch_event); + } + } else { + all_observers_notified = false; + base::TimeDelta delay = it->second.rate - delta; + if (delay < min_delay) + min_delay = delay; + } + } + + // We need to respect the notification rate specified by observers. So if it + // is too soon to dispatch an event to an observer, save the event and + // dispatch it after a delay. If we simply drop the event, another one may + // not arrive anytime soon and the observer will miss the most recent event. + if (!all_observers_notified) { + pending_event_ = event; + notification_timer_.Start( + FROM_HERE, + min_delay, + this, + &StorageObserverList::DispatchPendingEvent); + } +} + +void StorageObserverList::ScheduleUpdateForObserver(StorageObserver* observer) { + DCHECK(ContainsKey(observers_, observer)); + observers_[observer].requires_update = true; +} + +void StorageObserverList::DispatchPendingEvent() { + MaybeDispatchEvent(pending_event_); +} + + +// HostStorageObservers: + +HostStorageObservers::HostStorageObservers(QuotaManager* quota_manager) + : quota_manager_(quota_manager), + initialized_(false), + initializing_(false), + event_occurred_before_init_(false), + usage_deltas_during_init_(0), + cached_usage_(0), + cached_quota_(0), + weak_factory_(this) { +} + +HostStorageObservers::~HostStorageObservers() {} + +void HostStorageObservers::AddObserver( + StorageObserver* observer, + const StorageObserver::MonitorParams& params) { + observers_.AddObserver(observer, params); + + if (!params.dispatch_initial_state) + return; + + if (initialized_) { + StorageObserver::Event event(params.filter, + std::max<int64>(cached_usage_, 0), + std::max<int64>(cached_quota_, 0)); + observer->OnStorageEvent(event); + return; + } + + // Ensure the observer receives the initial storage state once initialization + // is complete. + observers_.ScheduleUpdateForObserver(observer); + StartInitialization(params.filter); +} + +void HostStorageObservers::RemoveObserver(StorageObserver* observer) { + observers_.RemoveObserver(observer); +} + +bool HostStorageObservers::ContainsObservers() const { + return observers_.ObserverCount() > 0; +} + +void HostStorageObservers::NotifyUsageChange( + const StorageObserver::Filter& filter, int64 delta) { + if (initialized_) { + cached_usage_ += delta; + DispatchEvent(filter, true); + return; + } + + // If a storage change occurs before initialization, ensure all observers will + // receive an event once initialization is complete. + event_occurred_before_init_ = true; + + // During QuotaManager::GetUsageAndQuotaForWebApps(), cached data is read + // synchronously, but other data may be retrieved asynchronously. A usage + // change may occur between the function call and callback. These deltas need + // to be added to the usage received by GotHostUsageAndQuota() to ensure + // |cached_usage_| is correctly initialized. + if (initializing_) { + usage_deltas_during_init_ += delta; + return; + } + + StartInitialization(filter); +} + +void HostStorageObservers::StartInitialization( + const StorageObserver::Filter& filter) { + if (initialized_ || initializing_) + return; + + initializing_ = true; + quota_manager_->GetUsageAndQuotaForWebApps( + filter.origin, + filter.storage_type, + base::Bind(&HostStorageObservers::GotHostUsageAndQuota, + weak_factory_.GetWeakPtr(), + filter)); +} + +void HostStorageObservers::GotHostUsageAndQuota( + const StorageObserver::Filter& filter, + QuotaStatusCode status, + int64 usage, + int64 quota) { + initializing_ = false; + if (status != kQuotaStatusOk) + return; + + initialized_ = true; + cached_quota_ = quota; + cached_usage_ = usage + usage_deltas_during_init_; + DispatchEvent(filter, event_occurred_before_init_); +} + +void HostStorageObservers::DispatchEvent( + const StorageObserver::Filter& filter, bool is_update) { + StorageObserver::Event event(filter, + std::max<int64>(cached_usage_, 0), + std::max<int64>(cached_quota_, 0)); + if (is_update) + observers_.OnStorageChange(event); + else + observers_.MaybeDispatchEvent(event); +} + + +// StorageTypeObservers: + +StorageTypeObservers::StorageTypeObservers(QuotaManager* quota_manager) + : quota_manager_(quota_manager) { +} + +StorageTypeObservers::~StorageTypeObservers() { + STLDeleteValues(&host_observers_map_); +} + +void StorageTypeObservers::AddObserver( + StorageObserver* observer, const StorageObserver::MonitorParams& params) { + std::string host = net::GetHostOrSpecFromURL(params.filter.origin); + if (host.empty()) + return; + + HostStorageObservers* host_observers = NULL; + HostObserversMap::iterator it = host_observers_map_.find(host); + if (it == host_observers_map_.end()) { + host_observers = new HostStorageObservers(quota_manager_); + host_observers_map_[host] = host_observers; + } else { + host_observers = it->second; + } + + host_observers->AddObserver(observer, params); +} + +void StorageTypeObservers::RemoveObserver(StorageObserver* observer) { + for (HostObserversMap::iterator it = host_observers_map_.begin(); + it != host_observers_map_.end(); ) { + it->second->RemoveObserver(observer); + if (!it->second->ContainsObservers()) { + delete it->second; + host_observers_map_.erase(it++); + } else { + ++it; + } + } +} + +void StorageTypeObservers::RemoveObserverForFilter( + StorageObserver* observer, const StorageObserver::Filter& filter) { + std::string host = net::GetHostOrSpecFromURL(filter.origin); + HostObserversMap::iterator it = host_observers_map_.find(host); + if (it == host_observers_map_.end()) + return; + + it->second->RemoveObserver(observer); + if (!it->second->ContainsObservers()) { + delete it->second; + host_observers_map_.erase(it); + } +} + +const HostStorageObservers* StorageTypeObservers::GetHostObservers( + const std::string& host) const { + HostObserversMap::const_iterator it = host_observers_map_.find(host); + if (it != host_observers_map_.end()) + return it->second; + + return NULL; +} + +void StorageTypeObservers::NotifyUsageChange( + const StorageObserver::Filter& filter, int64 delta) { + std::string host = net::GetHostOrSpecFromURL(filter.origin); + HostObserversMap::iterator it = host_observers_map_.find(host); + if (it == host_observers_map_.end()) + return; + + it->second->NotifyUsageChange(filter, delta); +} + + +// StorageMonitor: + +StorageMonitor::StorageMonitor(QuotaManager* quota_manager) + : quota_manager_(quota_manager) { +} + +StorageMonitor::~StorageMonitor() { + STLDeleteValues(&storage_type_observers_map_); +} + +void StorageMonitor::AddObserver( + StorageObserver* observer, const StorageObserver::MonitorParams& params) { + DCHECK(observer); + + // Check preconditions. + if (params.filter.storage_type == kStorageTypeUnknown || + params.filter.storage_type == kStorageTypeQuotaNotManaged || + params.filter.origin.is_empty()) { + NOTREACHED(); + return; + } + + StorageTypeObservers* type_observers = NULL; + StorageTypeObserversMap::iterator it = + storage_type_observers_map_.find(params.filter.storage_type); + if (it == storage_type_observers_map_.end()) { + type_observers = new StorageTypeObservers(quota_manager_); + storage_type_observers_map_[params.filter.storage_type] = type_observers; + } else { + type_observers = it->second; + } + + type_observers->AddObserver(observer, params); +} + +void StorageMonitor::RemoveObserver(StorageObserver* observer) { + for (StorageTypeObserversMap::iterator it = + storage_type_observers_map_.begin(); + it != storage_type_observers_map_.end(); ++it) { + it->second->RemoveObserver(observer); + } +} + +void StorageMonitor::RemoveObserverForFilter( + StorageObserver* observer, const StorageObserver::Filter& filter) { + StorageTypeObserversMap::iterator it = + storage_type_observers_map_.find(filter.storage_type); + if (it == storage_type_observers_map_.end()) + return; + + it->second->RemoveObserverForFilter(observer, filter); +} + +const StorageTypeObservers* StorageMonitor::GetStorageTypeObservers( + StorageType storage_type) const { + StorageTypeObserversMap::const_iterator it = + storage_type_observers_map_.find(storage_type); + if (it != storage_type_observers_map_.end()) + return it->second; + + return NULL; +} + +void StorageMonitor::NotifyUsageChange( + const StorageObserver::Filter& filter, int64 delta) { + // Check preconditions. + if (filter.storage_type == kStorageTypeUnknown || + filter.storage_type == kStorageTypeQuotaNotManaged || + filter.origin.is_empty()) { + NOTREACHED(); + return; + } + + StorageTypeObserversMap::iterator it = + storage_type_observers_map_.find(filter.storage_type); + if (it == storage_type_observers_map_.end()) + return; + + it->second->NotifyUsageChange(filter, delta); +} + +} // namespace storage diff --git a/storage/browser/quota/storage_monitor.h b/storage/browser/quota/storage_monitor.h new file mode 100644 index 0000000..650c7b1 --- /dev/null +++ b/storage/browser/quota/storage_monitor.h @@ -0,0 +1,180 @@ +// Copyright 2014 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. + +#ifndef STORAGE_BROWSER_QUOTA_STORAGE_MONITOR_H_ +#define STORAGE_BROWSER_QUOTA_STORAGE_MONITOR_H_ + +#include <map> + +#include "base/memory/weak_ptr.h" +#include "base/time/time.h" +#include "base/timer/timer.h" +#include "storage/browser/quota/storage_observer.h" + +namespace content { +class StorageMonitorTestBase; +} + +namespace storage { + +class QuotaManager; + +// This class dispatches storage events to observers of a common +// StorageObserver::Filter. +class STORAGE_EXPORT_PRIVATE StorageObserverList { + public: + StorageObserverList(); + virtual ~StorageObserverList(); + + // Adds/removes an observer. + void AddObserver(StorageObserver* observer, + const StorageObserver::MonitorParams& params); + void RemoveObserver(StorageObserver* observer); + + // Returns the number of observers. + int ObserverCount() const; + + // Forwards a storage change to observers. The event may be dispatched + // immediately to an observer or after a delay, depending on the desired event + // rate of the observer. + void OnStorageChange(const StorageObserver::Event& event); + + // Dispatch an event to observers that require it. + void MaybeDispatchEvent(const StorageObserver::Event& event); + + // Ensure the specified observer receives the next dispatched event. + void ScheduleUpdateForObserver(StorageObserver* observer); + + private: + struct STORAGE_EXPORT_PRIVATE ObserverState { + GURL origin; + base::TimeTicks last_notification_time; + base::TimeDelta rate; + bool requires_update; + + ObserverState(); + }; + typedef std::map<StorageObserver*, ObserverState> StorageObserverStateMap; + + void DispatchPendingEvent(); + + StorageObserverStateMap observers_; + base::OneShotTimer<StorageObserverList> notification_timer_; + StorageObserver::Event pending_event_; + + friend class content::StorageMonitorTestBase; + + DISALLOW_COPY_AND_ASSIGN(StorageObserverList); +}; + + +// Manages the storage observers of a common host. Caches the usage and quota of +// the host to avoid accumulating for every change. +class STORAGE_EXPORT_PRIVATE HostStorageObservers { + public: + explicit HostStorageObservers(QuotaManager* quota_manager); + virtual ~HostStorageObservers(); + + bool is_initialized() const { return initialized_; } + + // Adds/removes an observer. + void AddObserver( + StorageObserver* observer, + const StorageObserver::MonitorParams& params); + void RemoveObserver(StorageObserver* observer); + bool ContainsObservers() const; + + // Handles a usage change. + void NotifyUsageChange(const StorageObserver::Filter& filter, int64 delta); + + private: + void StartInitialization(const StorageObserver::Filter& filter); + void GotHostUsageAndQuota(const StorageObserver::Filter& filter, + QuotaStatusCode status, + int64 usage, + int64 quota); + void DispatchEvent(const StorageObserver::Filter& filter, bool is_update); + + QuotaManager* quota_manager_; + StorageObserverList observers_; + + // Flags used during initialization of the cached properties. + bool initialized_; + bool initializing_; + bool event_occurred_before_init_; + int64 usage_deltas_during_init_; + + // Cached accumulated usage and quota for the host. + int64 cached_usage_; + int64 cached_quota_; + + base::WeakPtrFactory<HostStorageObservers> weak_factory_; + + friend class content::StorageMonitorTestBase; + + DISALLOW_COPY_AND_ASSIGN(HostStorageObservers); +}; + + +// Manages the observers of a common storage type. +class STORAGE_EXPORT_PRIVATE StorageTypeObservers { + public: + explicit StorageTypeObservers(QuotaManager* quota_manager); + virtual ~StorageTypeObservers(); + + // Adds and removes an observer. + void AddObserver(StorageObserver* observer, + const StorageObserver::MonitorParams& params); + void RemoveObserver(StorageObserver* observer); + void RemoveObserverForFilter(StorageObserver* observer, + const StorageObserver::Filter& filter); + + // Returns the observers of a specific host. + const HostStorageObservers* GetHostObservers(const std::string& host) const; + + // Handles a usage change. + void NotifyUsageChange(const StorageObserver::Filter& filter, int64 delta); + + private: + typedef std::map<std::string, HostStorageObservers*> HostObserversMap; + + QuotaManager* quota_manager_; + HostObserversMap host_observers_map_; + + DISALLOW_COPY_AND_ASSIGN(StorageTypeObservers); +}; + + +// Storage monitor manages observers and dispatches storage events to them. +class STORAGE_EXPORT_PRIVATE StorageMonitor { + public: + explicit StorageMonitor(QuotaManager* quota_manager); + virtual ~StorageMonitor(); + + // Adds and removes an observer. + void AddObserver(StorageObserver* observer, + const StorageObserver::MonitorParams& params); + void RemoveObserver(StorageObserver* observer); + void RemoveObserverForFilter(StorageObserver* observer, + const StorageObserver::Filter& filter); + + // Returns the observers of a specific storage type. + const StorageTypeObservers* GetStorageTypeObservers( + StorageType storage_type) const; + + // Handles a usage change. + void NotifyUsageChange(const StorageObserver::Filter& filter, int64 delta); + + private: + typedef std::map<StorageType, StorageTypeObservers*> StorageTypeObserversMap; + + QuotaManager* quota_manager_; + StorageTypeObserversMap storage_type_observers_map_; + + DISALLOW_COPY_AND_ASSIGN(StorageMonitor); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_QUOTA_STORAGE_MONITOR_H_ diff --git a/storage/browser/quota/storage_observer.cc b/storage/browser/quota/storage_observer.cc new file mode 100644 index 0000000..986b639 --- /dev/null +++ b/storage/browser/quota/storage_observer.cc @@ -0,0 +1,65 @@ +// Copyright 2014 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 "storage/browser/quota/storage_observer.h" + +namespace storage { + +// StorageObserver::Filter + +StorageObserver::Filter::Filter() + : storage_type(kStorageTypeUnknown) { +} + +StorageObserver::Filter::Filter(StorageType storage_type, const GURL& origin) + : storage_type(storage_type), origin(origin) { +} + +bool StorageObserver::Filter::operator==(const Filter& other) const { + return storage_type == other.storage_type && + origin == other.origin; +} + +// StorageObserver::MonitorParams + +StorageObserver::MonitorParams::MonitorParams() + : dispatch_initial_state(false) { +} + +StorageObserver::MonitorParams::MonitorParams( + StorageType storage_type, + const GURL& origin, + const base::TimeDelta& rate, + bool get_initial_state) + : filter(storage_type, origin), + rate(rate), + dispatch_initial_state(get_initial_state) { +} + +StorageObserver::MonitorParams::MonitorParams( + const Filter& filter, + const base::TimeDelta& rate, + bool get_initial_state) + : filter(filter), + rate(rate), + dispatch_initial_state(get_initial_state) { +} + +// StorageObserver::Event + +StorageObserver::Event::Event() + : usage(0), quota(0) { +} + +StorageObserver::Event::Event(const Filter& filter, int64 usage, int64 quota) + : filter(filter), usage(usage), quota(quota) { +} + +bool StorageObserver::Event::operator==(const Event& other) const { + return filter == other.filter && + usage == other.usage && + quota == other.quota; +} + +} // namespace storage diff --git a/storage/browser/quota/storage_observer.h b/storage/browser/quota/storage_observer.h new file mode 100644 index 0000000..729dadd --- /dev/null +++ b/storage/browser/quota/storage_observer.h @@ -0,0 +1,79 @@ +// Copyright 2014 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. + +#ifndef STORAGE_BROWSER_QUOTA_STORAGE_OBSERVER_H_ +#define STORAGE_BROWSER_QUOTA_STORAGE_OBSERVER_H_ + +#include "base/basictypes.h" +#include "base/time/time.h" +#include "storage/browser/quota/quota_client.h" +#include "storage/common/quota/quota_types.h" +#include "url/gurl.h" + +namespace storage { + +// This interface is implemented by observers that wish to monitor storage +// events, such as changes in quota or usage. +class STORAGE_EXPORT StorageObserver { + public: + struct STORAGE_EXPORT Filter { + // The storage type to monitor. This must not be kStorageTypeUnknown or + // kStorageTypeQuotaNotManaged. + StorageType storage_type; + + // The origin to monitor usage for. Must be specified. + GURL origin; + + Filter(); + Filter(StorageType storage_type, const GURL& origin); + bool operator==(const Filter& other) const; + }; + + struct STORAGE_EXPORT MonitorParams { + // Storage type and origin to monitor. + Filter filter; + + // The rate at which storage events will be fired. Events will be fired at + // approximately this rate, or when a storage status change has been + // detected, whichever is the least frequent. + base::TimeDelta rate; + + // If set to true, the observer will be dispatched an event when added. + bool dispatch_initial_state; + + MonitorParams(); + MonitorParams(StorageType storage_type, + const GURL& origin, + const base::TimeDelta& rate, + bool get_initial_state); + MonitorParams(const Filter& filter, + const base::TimeDelta& rate, + bool get_initial_state); + }; + + struct STORAGE_EXPORT Event { + // The storage type and origin monitored. + Filter filter; + + // The current usage corresponding to the filter. + int64 usage; + + // The quota corresponding to the filter. + int64 quota; + + Event(); + Event(const Filter& filter, int64 usage, int64 quota); + bool operator==(const Event& other) const; + }; + + // Will be called on the IO thread when a storage event occurs. + virtual void OnStorageEvent(const Event& event) = 0; + + protected: + virtual ~StorageObserver() {} +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_QUOTA_STORAGE_OBSERVER_H_ diff --git a/storage/browser/quota/usage_tracker.cc b/storage/browser/quota/usage_tracker.cc new file mode 100644 index 0000000..1c222bc --- /dev/null +++ b/storage/browser/quota/usage_tracker.cc @@ -0,0 +1,694 @@ +// Copyright 2013 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 "storage/browser/quota/usage_tracker.h" + +#include <algorithm> +#include <deque> +#include <set> +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/stl_util.h" +#include "net/base/net_util.h" +#include "storage/browser/quota/storage_monitor.h" +#include "storage/browser/quota/storage_observer.h" + +namespace storage { + +namespace { + +typedef ClientUsageTracker::OriginUsageAccumulator OriginUsageAccumulator; +typedef ClientUsageTracker::OriginSetByHost OriginSetByHost; + +void DidGetOriginUsage(const OriginUsageAccumulator& accumulator, + const GURL& origin, + int64 usage) { + accumulator.Run(origin, usage); +} + +void DidGetHostUsage(const UsageCallback& callback, + int64 limited_usage, + int64 unlimited_usage) { + DCHECK_GE(limited_usage, 0); + DCHECK_GE(unlimited_usage, 0); + callback.Run(limited_usage + unlimited_usage); +} + +bool EraseOriginFromOriginSet(OriginSetByHost* origins_by_host, + const std::string& host, + const GURL& origin) { + OriginSetByHost::iterator found = origins_by_host->find(host); + if (found == origins_by_host->end()) + return false; + + if (!found->second.erase(origin)) + return false; + + if (found->second.empty()) + origins_by_host->erase(host); + return true; +} + +bool OriginSetContainsOrigin(const OriginSetByHost& origins, + const std::string& host, + const GURL& origin) { + OriginSetByHost::const_iterator itr = origins.find(host); + return itr != origins.end() && ContainsKey(itr->second, origin); +} + +void DidGetGlobalUsageForLimitedGlobalUsage(const UsageCallback& callback, + int64 total_global_usage, + int64 global_unlimited_usage) { + callback.Run(total_global_usage - global_unlimited_usage); +} + +} // namespace + +// UsageTracker ---------------------------------------------------------- + +UsageTracker::UsageTracker(const QuotaClientList& clients, + StorageType type, + SpecialStoragePolicy* special_storage_policy, + StorageMonitor* storage_monitor) + : type_(type), + storage_monitor_(storage_monitor), + weak_factory_(this) { + for (QuotaClientList::const_iterator iter = clients.begin(); + iter != clients.end(); + ++iter) { + if ((*iter)->DoesSupport(type)) { + client_tracker_map_[(*iter)->id()] = + new ClientUsageTracker(this, *iter, type, special_storage_policy, + storage_monitor_); + } + } +} + +UsageTracker::~UsageTracker() { + STLDeleteValues(&client_tracker_map_); +} + +ClientUsageTracker* UsageTracker::GetClientTracker(QuotaClient::ID client_id) { + ClientTrackerMap::iterator found = client_tracker_map_.find(client_id); + if (found != client_tracker_map_.end()) + return found->second; + return NULL; +} + +void UsageTracker::GetGlobalLimitedUsage(const UsageCallback& callback) { + if (global_usage_callbacks_.HasCallbacks()) { + global_usage_callbacks_.Add(base::Bind( + &DidGetGlobalUsageForLimitedGlobalUsage, callback)); + return; + } + + if (!global_limited_usage_callbacks_.Add(callback)) + return; + + AccumulateInfo* info = new AccumulateInfo; + // Calling GetGlobalLimitedUsage(accumulator) may synchronously + // return if the usage is cached, which may in turn dispatch + // the completion callback before we finish looping over + // all clients (because info->pending_clients may reach 0 + // during the loop). + // To avoid this, we add one more pending client as a sentinel + // and fire the sentinel callback at the end. + info->pending_clients = client_tracker_map_.size() + 1; + UsageCallback accumulator = base::Bind( + &UsageTracker::AccumulateClientGlobalLimitedUsage, + weak_factory_.GetWeakPtr(), base::Owned(info)); + + for (ClientTrackerMap::iterator iter = client_tracker_map_.begin(); + iter != client_tracker_map_.end(); + ++iter) + iter->second->GetGlobalLimitedUsage(accumulator); + + // Fire the sentinel as we've now called GetGlobalUsage for all clients. + accumulator.Run(0); +} + +void UsageTracker::GetGlobalUsage(const GlobalUsageCallback& callback) { + if (!global_usage_callbacks_.Add(callback)) + return; + + AccumulateInfo* info = new AccumulateInfo; + // Calling GetGlobalUsage(accumulator) may synchronously + // return if the usage is cached, which may in turn dispatch + // the completion callback before we finish looping over + // all clients (because info->pending_clients may reach 0 + // during the loop). + // To avoid this, we add one more pending client as a sentinel + // and fire the sentinel callback at the end. + info->pending_clients = client_tracker_map_.size() + 1; + GlobalUsageCallback accumulator = base::Bind( + &UsageTracker::AccumulateClientGlobalUsage, weak_factory_.GetWeakPtr(), + base::Owned(info)); + + for (ClientTrackerMap::iterator iter = client_tracker_map_.begin(); + iter != client_tracker_map_.end(); + ++iter) + iter->second->GetGlobalUsage(accumulator); + + // Fire the sentinel as we've now called GetGlobalUsage for all clients. + accumulator.Run(0, 0); +} + +void UsageTracker::GetHostUsage(const std::string& host, + const UsageCallback& callback) { + if (!host_usage_callbacks_.Add(host, callback)) + return; + + AccumulateInfo* info = new AccumulateInfo; + // Calling GetHostUsage(accumulator) may synchronously + // return if the usage is cached, which may in turn dispatch + // the completion callback before we finish looping over + // all clients (because info->pending_clients may reach 0 + // during the loop). + // To avoid this, we add one more pending client as a sentinel + // and fire the sentinel callback at the end. + info->pending_clients = client_tracker_map_.size() + 1; + UsageCallback accumulator = base::Bind( + &UsageTracker::AccumulateClientHostUsage, weak_factory_.GetWeakPtr(), + base::Owned(info), host); + + for (ClientTrackerMap::iterator iter = client_tracker_map_.begin(); + iter != client_tracker_map_.end(); + ++iter) + iter->second->GetHostUsage(host, accumulator); + + // Fire the sentinel as we've now called GetHostUsage for all clients. + accumulator.Run(0); +} + +void UsageTracker::UpdateUsageCache( + QuotaClient::ID client_id, const GURL& origin, int64 delta) { + ClientUsageTracker* client_tracker = GetClientTracker(client_id); + DCHECK(client_tracker); + client_tracker->UpdateUsageCache(origin, delta); +} + +void UsageTracker::GetCachedHostsUsage( + std::map<std::string, int64>* host_usage) const { + DCHECK(host_usage); + host_usage->clear(); + for (ClientTrackerMap::const_iterator iter = client_tracker_map_.begin(); + iter != client_tracker_map_.end(); ++iter) { + iter->second->GetCachedHostsUsage(host_usage); + } +} + +void UsageTracker::GetCachedOrigins(std::set<GURL>* origins) const { + DCHECK(origins); + origins->clear(); + for (ClientTrackerMap::const_iterator iter = client_tracker_map_.begin(); + iter != client_tracker_map_.end(); ++iter) { + iter->second->GetCachedOrigins(origins); + } +} + +void UsageTracker::SetUsageCacheEnabled(QuotaClient::ID client_id, + const GURL& origin, + bool enabled) { + ClientUsageTracker* client_tracker = GetClientTracker(client_id); + DCHECK(client_tracker); + + client_tracker->SetUsageCacheEnabled(origin, enabled); +} + +void UsageTracker::AccumulateClientGlobalLimitedUsage(AccumulateInfo* info, + int64 limited_usage) { + info->usage += limited_usage; + if (--info->pending_clients) + return; + + // All the clients have returned their usage data. Dispatch the + // pending callbacks. + global_limited_usage_callbacks_.Run(MakeTuple(info->usage)); +} + +void UsageTracker::AccumulateClientGlobalUsage(AccumulateInfo* info, + int64 usage, + int64 unlimited_usage) { + info->usage += usage; + info->unlimited_usage += unlimited_usage; + if (--info->pending_clients) + return; + + // Defend against confusing inputs from clients. + if (info->usage < 0) + info->usage = 0; + + // TODO(michaeln): The unlimited number is not trustworthy, it + // can get out of whack when apps are installed or uninstalled. + if (info->unlimited_usage > info->usage) + info->unlimited_usage = info->usage; + else if (info->unlimited_usage < 0) + info->unlimited_usage = 0; + + // All the clients have returned their usage data. Dispatch the + // pending callbacks. + global_usage_callbacks_.Run(MakeTuple(info->usage, info->unlimited_usage)); +} + +void UsageTracker::AccumulateClientHostUsage(AccumulateInfo* info, + const std::string& host, + int64 usage) { + info->usage += usage; + if (--info->pending_clients) + return; + + // Defend against confusing inputs from clients. + if (info->usage < 0) + info->usage = 0; + + // All the clients have returned their usage data. Dispatch the + // pending callbacks. + host_usage_callbacks_.Run(host, MakeTuple(info->usage)); +} + +// ClientUsageTracker ---------------------------------------------------- + +ClientUsageTracker::ClientUsageTracker( + UsageTracker* tracker, QuotaClient* client, StorageType type, + SpecialStoragePolicy* special_storage_policy, + StorageMonitor* storage_monitor) + : tracker_(tracker), + client_(client), + type_(type), + storage_monitor_(storage_monitor), + global_limited_usage_(0), + global_unlimited_usage_(0), + global_usage_retrieved_(false), + special_storage_policy_(special_storage_policy) { + DCHECK(tracker_); + DCHECK(client_); + if (special_storage_policy_.get()) + special_storage_policy_->AddObserver(this); +} + +ClientUsageTracker::~ClientUsageTracker() { + if (special_storage_policy_.get()) + special_storage_policy_->RemoveObserver(this); +} + +void ClientUsageTracker::GetGlobalLimitedUsage(const UsageCallback& callback) { + if (!global_usage_retrieved_) { + GetGlobalUsage(base::Bind(&DidGetGlobalUsageForLimitedGlobalUsage, + callback)); + return; + } + + if (non_cached_limited_origins_by_host_.empty()) { + callback.Run(global_limited_usage_); + return; + } + + AccumulateInfo* info = new AccumulateInfo; + info->pending_jobs = non_cached_limited_origins_by_host_.size() + 1; + UsageCallback accumulator = base::Bind( + &ClientUsageTracker::AccumulateLimitedOriginUsage, AsWeakPtr(), + base::Owned(info), callback); + + for (OriginSetByHost::iterator host_itr = + non_cached_limited_origins_by_host_.begin(); + host_itr != non_cached_limited_origins_by_host_.end(); ++host_itr) { + for (std::set<GURL>::iterator origin_itr = host_itr->second.begin(); + origin_itr != host_itr->second.end(); ++origin_itr) + client_->GetOriginUsage(*origin_itr, type_, accumulator); + } + + accumulator.Run(global_limited_usage_); +} + +void ClientUsageTracker::GetGlobalUsage(const GlobalUsageCallback& callback) { + if (global_usage_retrieved_ && + non_cached_limited_origins_by_host_.empty() && + non_cached_unlimited_origins_by_host_.empty()) { + callback.Run(global_limited_usage_ + global_unlimited_usage_, + global_unlimited_usage_); + return; + } + + client_->GetOriginsForType(type_, base::Bind( + &ClientUsageTracker::DidGetOriginsForGlobalUsage, AsWeakPtr(), + callback)); +} + +void ClientUsageTracker::GetHostUsage( + const std::string& host, const UsageCallback& callback) { + if (ContainsKey(cached_hosts_, host) && + !ContainsKey(non_cached_limited_origins_by_host_, host) && + !ContainsKey(non_cached_unlimited_origins_by_host_, host)) { + // TODO(kinuko): Drop host_usage_map_ cache periodically. + callback.Run(GetCachedHostUsage(host)); + return; + } + + if (!host_usage_accumulators_.Add( + host, base::Bind(&DidGetHostUsage, callback))) + return; + client_->GetOriginsForHost(type_, host, base::Bind( + &ClientUsageTracker::DidGetOriginsForHostUsage, AsWeakPtr(), host)); +} + +void ClientUsageTracker::UpdateUsageCache( + const GURL& origin, int64 delta) { + std::string host = net::GetHostOrSpecFromURL(origin); + if (cached_hosts_.find(host) != cached_hosts_.end()) { + if (!IsUsageCacheEnabledForOrigin(origin)) + return; + + cached_usage_by_host_[host][origin] += delta; + if (IsStorageUnlimited(origin)) + global_unlimited_usage_ += delta; + else + global_limited_usage_ += delta; + DCHECK_GE(cached_usage_by_host_[host][origin], 0); + DCHECK_GE(global_limited_usage_, 0); + + // Notify the usage monitor that usage has changed. The storage monitor may + // be NULL during tests. + if (storage_monitor_) { + StorageObserver::Filter filter(type_, origin); + storage_monitor_->NotifyUsageChange(filter, delta); + } + return; + } + + // We don't know about this host yet, so populate our cache for it. + GetHostUsage(host, base::Bind(&ClientUsageTracker::DidGetHostUsageAfterUpdate, + AsWeakPtr(), origin)); +} + +void ClientUsageTracker::GetCachedHostsUsage( + std::map<std::string, int64>* host_usage) const { + DCHECK(host_usage); + for (HostUsageMap::const_iterator host_iter = cached_usage_by_host_.begin(); + host_iter != cached_usage_by_host_.end(); host_iter++) { + const std::string& host = host_iter->first; + (*host_usage)[host] += GetCachedHostUsage(host); + } +} + +void ClientUsageTracker::GetCachedOrigins(std::set<GURL>* origins) const { + DCHECK(origins); + for (HostUsageMap::const_iterator host_iter = cached_usage_by_host_.begin(); + host_iter != cached_usage_by_host_.end(); host_iter++) { + const UsageMap& origin_map = host_iter->second; + for (UsageMap::const_iterator origin_iter = origin_map.begin(); + origin_iter != origin_map.end(); origin_iter++) { + origins->insert(origin_iter->first); + } + } +} + +void ClientUsageTracker::SetUsageCacheEnabled(const GURL& origin, + bool enabled) { + std::string host = net::GetHostOrSpecFromURL(origin); + if (!enabled) { + // Erase |origin| from cache and subtract its usage. + HostUsageMap::iterator found_host = cached_usage_by_host_.find(host); + if (found_host != cached_usage_by_host_.end()) { + UsageMap& cached_usage_for_host = found_host->second; + + UsageMap::iterator found = cached_usage_for_host.find(origin); + if (found != cached_usage_for_host.end()) { + int64 usage = found->second; + UpdateUsageCache(origin, -usage); + cached_usage_for_host.erase(found); + if (cached_usage_for_host.empty()) { + cached_usage_by_host_.erase(found_host); + cached_hosts_.erase(host); + } + } + } + + if (IsStorageUnlimited(origin)) + non_cached_unlimited_origins_by_host_[host].insert(origin); + else + non_cached_limited_origins_by_host_[host].insert(origin); + } else { + // Erase |origin| from |non_cached_origins_| and invalidate the usage cache + // for the host. + if (EraseOriginFromOriginSet(&non_cached_limited_origins_by_host_, + host, origin) || + EraseOriginFromOriginSet(&non_cached_unlimited_origins_by_host_, + host, origin)) { + cached_hosts_.erase(host); + global_usage_retrieved_ = false; + } + } +} + +void ClientUsageTracker::AccumulateLimitedOriginUsage( + AccumulateInfo* info, + const UsageCallback& callback, + int64 usage) { + info->limited_usage += usage; + if (--info->pending_jobs) + return; + + callback.Run(info->limited_usage); +} + +void ClientUsageTracker::DidGetOriginsForGlobalUsage( + const GlobalUsageCallback& callback, + const std::set<GURL>& origins) { + OriginSetByHost origins_by_host; + for (std::set<GURL>::const_iterator itr = origins.begin(); + itr != origins.end(); ++itr) + origins_by_host[net::GetHostOrSpecFromURL(*itr)].insert(*itr); + + AccumulateInfo* info = new AccumulateInfo; + // Getting host usage may synchronously return the result if the usage is + // cached, which may in turn dispatch the completion callback before we finish + // looping over all hosts (because info->pending_jobs may reach 0 during the + // loop). To avoid this, we add one more pending host as a sentinel and + // fire the sentinel callback at the end. + info->pending_jobs = origins_by_host.size() + 1; + HostUsageAccumulator accumulator = + base::Bind(&ClientUsageTracker::AccumulateHostUsage, AsWeakPtr(), + base::Owned(info), callback); + + for (OriginSetByHost::iterator itr = origins_by_host.begin(); + itr != origins_by_host.end(); ++itr) { + if (host_usage_accumulators_.Add(itr->first, accumulator)) + GetUsageForOrigins(itr->first, itr->second); + } + + // Fire the sentinel as we've now called GetUsageForOrigins for all clients. + accumulator.Run(0, 0); +} + +void ClientUsageTracker::AccumulateHostUsage( + AccumulateInfo* info, + const GlobalUsageCallback& callback, + int64 limited_usage, + int64 unlimited_usage) { + info->limited_usage += limited_usage; + info->unlimited_usage += unlimited_usage; + if (--info->pending_jobs) + return; + + DCHECK_GE(info->limited_usage, 0); + DCHECK_GE(info->unlimited_usage, 0); + + global_usage_retrieved_ = true; + callback.Run(info->limited_usage + info->unlimited_usage, + info->unlimited_usage); +} + +void ClientUsageTracker::DidGetOriginsForHostUsage( + const std::string& host, + const std::set<GURL>& origins) { + GetUsageForOrigins(host, origins); +} + +void ClientUsageTracker::GetUsageForOrigins( + const std::string& host, + const std::set<GURL>& origins) { + AccumulateInfo* info = new AccumulateInfo; + // Getting origin usage may synchronously return the result if the usage is + // cached, which may in turn dispatch the completion callback before we finish + // looping over all origins (because info->pending_jobs may reach 0 during the + // loop). To avoid this, we add one more pending origin as a sentinel and + // fire the sentinel callback at the end. + info->pending_jobs = origins.size() + 1; + OriginUsageAccumulator accumulator = + base::Bind(&ClientUsageTracker::AccumulateOriginUsage, AsWeakPtr(), + base::Owned(info), host); + + for (std::set<GURL>::const_iterator itr = origins.begin(); + itr != origins.end(); ++itr) { + DCHECK_EQ(host, net::GetHostOrSpecFromURL(*itr)); + + int64 origin_usage = 0; + if (GetCachedOriginUsage(*itr, &origin_usage)) { + accumulator.Run(*itr, origin_usage); + } else { + client_->GetOriginUsage(*itr, type_, base::Bind( + &DidGetOriginUsage, accumulator, *itr)); + } + } + + // Fire the sentinel as we've now called GetOriginUsage for all clients. + accumulator.Run(GURL(), 0); +} + +void ClientUsageTracker::AccumulateOriginUsage(AccumulateInfo* info, + const std::string& host, + const GURL& origin, + int64 usage) { + if (!origin.is_empty()) { + if (usage < 0) + usage = 0; + + if (IsStorageUnlimited(origin)) + info->unlimited_usage += usage; + else + info->limited_usage += usage; + if (IsUsageCacheEnabledForOrigin(origin)) + AddCachedOrigin(origin, usage); + } + if (--info->pending_jobs) + return; + + AddCachedHost(host); + host_usage_accumulators_.Run( + host, MakeTuple(info->limited_usage, info->unlimited_usage)); +} + +void ClientUsageTracker::DidGetHostUsageAfterUpdate( + const GURL& origin, int64 usage) { + if (!storage_monitor_) + return; + + StorageObserver::Filter filter(type_, origin); + storage_monitor_->NotifyUsageChange(filter, 0); +} + +void ClientUsageTracker::AddCachedOrigin( + const GURL& origin, int64 new_usage) { + DCHECK(IsUsageCacheEnabledForOrigin(origin)); + + std::string host = net::GetHostOrSpecFromURL(origin); + int64* usage = &cached_usage_by_host_[host][origin]; + int64 delta = new_usage - *usage; + *usage = new_usage; + if (delta) { + if (IsStorageUnlimited(origin)) + global_unlimited_usage_ += delta; + else + global_limited_usage_ += delta; + } + DCHECK_GE(*usage, 0); + DCHECK_GE(global_limited_usage_, 0); +} + +void ClientUsageTracker::AddCachedHost(const std::string& host) { + cached_hosts_.insert(host); +} + +int64 ClientUsageTracker::GetCachedHostUsage(const std::string& host) const { + HostUsageMap::const_iterator found = cached_usage_by_host_.find(host); + if (found == cached_usage_by_host_.end()) + return 0; + + int64 usage = 0; + const UsageMap& map = found->second; + for (UsageMap::const_iterator iter = map.begin(); + iter != map.end(); ++iter) { + usage += iter->second; + } + return usage; +} + +bool ClientUsageTracker::GetCachedOriginUsage( + const GURL& origin, + int64* usage) const { + std::string host = net::GetHostOrSpecFromURL(origin); + HostUsageMap::const_iterator found_host = cached_usage_by_host_.find(host); + if (found_host == cached_usage_by_host_.end()) + return false; + + UsageMap::const_iterator found = found_host->second.find(origin); + if (found == found_host->second.end()) + return false; + + DCHECK(IsUsageCacheEnabledForOrigin(origin)); + *usage = found->second; + return true; +} + +bool ClientUsageTracker::IsUsageCacheEnabledForOrigin( + const GURL& origin) const { + std::string host = net::GetHostOrSpecFromURL(origin); + return !OriginSetContainsOrigin(non_cached_limited_origins_by_host_, + host, origin) && + !OriginSetContainsOrigin(non_cached_unlimited_origins_by_host_, + host, origin); +} + +void ClientUsageTracker::OnGranted(const GURL& origin, + int change_flags) { + DCHECK(CalledOnValidThread()); + if (change_flags & SpecialStoragePolicy::STORAGE_UNLIMITED) { + int64 usage = 0; + if (GetCachedOriginUsage(origin, &usage)) { + global_unlimited_usage_ += usage; + global_limited_usage_ -= usage; + } + + std::string host = net::GetHostOrSpecFromURL(origin); + if (EraseOriginFromOriginSet(&non_cached_limited_origins_by_host_, + host, origin)) + non_cached_unlimited_origins_by_host_[host].insert(origin); + } +} + +void ClientUsageTracker::OnRevoked(const GURL& origin, + int change_flags) { + DCHECK(CalledOnValidThread()); + if (change_flags & SpecialStoragePolicy::STORAGE_UNLIMITED) { + int64 usage = 0; + if (GetCachedOriginUsage(origin, &usage)) { + global_unlimited_usage_ -= usage; + global_limited_usage_ += usage; + } + + std::string host = net::GetHostOrSpecFromURL(origin); + if (EraseOriginFromOriginSet(&non_cached_unlimited_origins_by_host_, + host, origin)) + non_cached_limited_origins_by_host_[host].insert(origin); + } +} + +void ClientUsageTracker::OnCleared() { + DCHECK(CalledOnValidThread()); + global_limited_usage_ += global_unlimited_usage_; + global_unlimited_usage_ = 0; + + for (OriginSetByHost::const_iterator host_itr = + non_cached_unlimited_origins_by_host_.begin(); + host_itr != non_cached_unlimited_origins_by_host_.end(); + ++host_itr) { + for (std::set<GURL>::const_iterator origin_itr = host_itr->second.begin(); + origin_itr != host_itr->second.end(); + ++origin_itr) + non_cached_limited_origins_by_host_[host_itr->first].insert(*origin_itr); + } + non_cached_unlimited_origins_by_host_.clear(); +} + +bool ClientUsageTracker::IsStorageUnlimited(const GURL& origin) const { + if (type_ == kStorageTypeSyncable) + return false; + return special_storage_policy_.get() && + special_storage_policy_->IsStorageUnlimited(origin); +} + +} // namespace storage diff --git a/storage/browser/quota/usage_tracker.h b/storage/browser/quota/usage_tracker.h new file mode 100644 index 0000000..fda1151 --- /dev/null +++ b/storage/browser/quota/usage_tracker.h @@ -0,0 +1,202 @@ +// Copyright 2013 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. + +#ifndef STORAGE_BROWSER_QUOTA_USAGE_TRACKER_H_ +#define STORAGE_BROWSER_QUOTA_USAGE_TRACKER_H_ + +#include <list> +#include <map> +#include <set> +#include <string> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/non_thread_safe.h" +#include "storage/browser/quota/quota_callbacks.h" +#include "storage/browser/quota/quota_client.h" +#include "storage/browser/quota/quota_task.h" +#include "storage/browser/quota/special_storage_policy.h" +#include "storage/browser/storage_browser_export.h" +#include "storage/common/quota/quota_types.h" +#include "url/gurl.h" + +namespace storage { + +class ClientUsageTracker; +class StorageMonitor; + +// A helper class that gathers and tracks the amount of data stored in +// all quota clients. +// An instance of this class is created per storage type. +class STORAGE_EXPORT UsageTracker : public QuotaTaskObserver { + public: + UsageTracker(const QuotaClientList& clients, StorageType type, + SpecialStoragePolicy* special_storage_policy, + StorageMonitor* storage_monitor); + virtual ~UsageTracker(); + + StorageType type() const { return type_; } + ClientUsageTracker* GetClientTracker(QuotaClient::ID client_id); + + void GetGlobalLimitedUsage(const UsageCallback& callback); + void GetGlobalUsage(const GlobalUsageCallback& callback); + void GetHostUsage(const std::string& host, const UsageCallback& callback); + void UpdateUsageCache(QuotaClient::ID client_id, + const GURL& origin, + int64 delta); + void GetCachedHostsUsage(std::map<std::string, int64>* host_usage) const; + void GetCachedOrigins(std::set<GURL>* origins) const; + bool IsWorking() const { + return global_usage_callbacks_.HasCallbacks() || + host_usage_callbacks_.HasAnyCallbacks(); + } + + void SetUsageCacheEnabled(QuotaClient::ID client_id, + const GURL& origin, + bool enabled); + + private: + struct AccumulateInfo { + AccumulateInfo() : pending_clients(0), usage(0), unlimited_usage(0) {} + int pending_clients; + int64 usage; + int64 unlimited_usage; + }; + + typedef std::map<QuotaClient::ID, ClientUsageTracker*> ClientTrackerMap; + + friend class ClientUsageTracker; + void AccumulateClientGlobalLimitedUsage(AccumulateInfo* info, + int64 limited_usage); + void AccumulateClientGlobalUsage(AccumulateInfo* info, + int64 usage, + int64 unlimited_usage); + void AccumulateClientHostUsage(AccumulateInfo* info, + const std::string& host, + int64 usage); + + const StorageType type_; + ClientTrackerMap client_tracker_map_; + + UsageCallbackQueue global_limited_usage_callbacks_; + GlobalUsageCallbackQueue global_usage_callbacks_; + HostUsageCallbackMap host_usage_callbacks_; + + StorageMonitor* storage_monitor_; + + base::WeakPtrFactory<UsageTracker> weak_factory_; + DISALLOW_COPY_AND_ASSIGN(UsageTracker); +}; + +// This class holds per-client usage tracking information and caches per-host +// usage data. An instance of this class is created per client. +class ClientUsageTracker : public SpecialStoragePolicy::Observer, + public base::NonThreadSafe, + public base::SupportsWeakPtr<ClientUsageTracker> { + public: + typedef base::Callback<void(int64 limited_usage, + int64 unlimited_usage)> HostUsageAccumulator; + typedef base::Callback<void(const GURL& origin, + int64 usage)> OriginUsageAccumulator; + typedef std::map<std::string, std::set<GURL> > OriginSetByHost; + + ClientUsageTracker(UsageTracker* tracker, + QuotaClient* client, + StorageType type, + SpecialStoragePolicy* special_storage_policy, + StorageMonitor* storage_monitor); + virtual ~ClientUsageTracker(); + + void GetGlobalLimitedUsage(const UsageCallback& callback); + void GetGlobalUsage(const GlobalUsageCallback& callback); + void GetHostUsage(const std::string& host, const UsageCallback& callback); + void UpdateUsageCache(const GURL& origin, int64 delta); + void GetCachedHostsUsage(std::map<std::string, int64>* host_usage) const; + void GetCachedOrigins(std::set<GURL>* origins) const; + int64 GetCachedOriginsUsage(const std::set<GURL>& origins, + std::vector<GURL>* origins_not_in_cache); + bool IsUsageCacheEnabledForOrigin(const GURL& origin) const; + void SetUsageCacheEnabled(const GURL& origin, bool enabled); + + private: + typedef CallbackQueueMap<HostUsageAccumulator, std::string, + Tuple2<int64, int64> > HostUsageAccumulatorMap; + + typedef std::set<std::string> HostSet; + typedef std::map<GURL, int64> UsageMap; + typedef std::map<std::string, UsageMap> HostUsageMap; + + struct AccumulateInfo { + int pending_jobs; + int64 limited_usage; + int64 unlimited_usage; + + AccumulateInfo() + : pending_jobs(0), limited_usage(0), unlimited_usage(0) {} + }; + + void AccumulateLimitedOriginUsage(AccumulateInfo* info, + const UsageCallback& callback, + int64 usage); + void DidGetOriginsForGlobalUsage(const GlobalUsageCallback& callback, + const std::set<GURL>& origins); + void AccumulateHostUsage(AccumulateInfo* info, + const GlobalUsageCallback& callback, + int64 limited_usage, + int64 unlimited_usage); + + void DidGetOriginsForHostUsage(const std::string& host, + const std::set<GURL>& origins); + + void GetUsageForOrigins(const std::string& host, + const std::set<GURL>& origins); + void AccumulateOriginUsage(AccumulateInfo* info, + const std::string& host, + const GURL& origin, + int64 usage); + + void DidGetHostUsageAfterUpdate(const GURL& origin, int64 usage); + + // Methods used by our GatherUsage tasks, as a task makes progress + // origins and hosts are added incrementally to the cache. + void AddCachedOrigin(const GURL& origin, int64 usage); + void AddCachedHost(const std::string& host); + + int64 GetCachedHostUsage(const std::string& host) const; + int64 GetCachedGlobalUnlimitedUsage(); + bool GetCachedOriginUsage(const GURL& origin, int64* usage) const; + + // SpecialStoragePolicy::Observer overrides + virtual void OnGranted(const GURL& origin, int change_flags) OVERRIDE; + virtual void OnRevoked(const GURL& origin, int change_flags) OVERRIDE; + virtual void OnCleared() OVERRIDE; + + bool IsStorageUnlimited(const GURL& origin) const; + + UsageTracker* tracker_; + QuotaClient* client_; + const StorageType type_; + StorageMonitor* storage_monitor_; + + int64 global_limited_usage_; + int64 global_unlimited_usage_; + bool global_usage_retrieved_; + HostSet cached_hosts_; + HostUsageMap cached_usage_by_host_; + + OriginSetByHost non_cached_limited_origins_by_host_; + OriginSetByHost non_cached_unlimited_origins_by_host_; + + GlobalUsageCallbackQueue global_usage_callback_; + HostUsageAccumulatorMap host_usage_accumulators_; + + scoped_refptr<SpecialStoragePolicy> special_storage_policy_; + + DISALLOW_COPY_AND_ASSIGN(ClientUsageTracker); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_QUOTA_USAGE_TRACKER_H_ diff --git a/storage/browser/storage_browser_export.h b/storage/browser/storage_browser_export.h new file mode 100644 index 0000000..c6d6a37 --- /dev/null +++ b/storage/browser/storage_browser_export.h @@ -0,0 +1,34 @@ +// Copyright (c) 2013 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. + +#ifndef STORAGE_BROWSER_STORAGE_BROWSER_EXPORT_H__ +#define STORAGE_BROWSER_STORAGE_BROWSER_EXPORT_H__ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(STORAGE_BROWSER_IMPLEMENTATION) +#define STORAGE_EXPORT __declspec(dllexport) +#define STORAGE_EXPORT_PRIVATE __declspec(dllexport) +#else +#define STORAGE_EXPORT __declspec(dllimport) +#define STORAGE_EXPORT_PRIVATE __declspec(dllimport) +#endif // defined(STORAGE_BROWSER_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(STORAGE_BROWSER_IMPLEMENTATION) +#define STORAGE_EXPORT __attribute__((visibility("default"))) +#define STORAGE_EXPORT_PRIVATE __attribute__((visibility("default"))) +#else +#define STORAGE_EXPORT +#define STORAGE_EXPORT_PRIVATE +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define STORAGE_EXPORT +#define STORAGE_EXPORT_PRIVATE +#endif + +#endif // STORAGE_BROWSER_STORAGE_BROWSER_EXPORT_H__ diff --git a/storage/storage_browser.gyp b/storage/storage_browser.gyp new file mode 100644 index 0000000..afa0b47 --- /dev/null +++ b/storage/storage_browser.gyp @@ -0,0 +1,199 @@ +# Copyright 2013 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. + +{ + 'variables': { + 'chromium_code': 1, + }, + 'targets': [ + { + # GN version: //storage/browser + 'target_name': 'storage', + 'type': '<(component)', + 'variables': { 'enable_wexit_time_destructors': 1, }, + 'dependencies': [ + '<(DEPTH)/base/base.gyp:base', + '<(DEPTH)/base/base.gyp:base_i18n', + '<(DEPTH)/base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', + '<(DEPTH)/net/net.gyp:net', + '<(DEPTH)/sql/sql.gyp:sql', + '<(DEPTH)/storage/storage_common.gyp:storage_common', + '<(DEPTH)/third_party/leveldatabase/leveldatabase.gyp:leveldatabase', + '<(DEPTH)/third_party/sqlite/sqlite.gyp:sqlite', + '<(DEPTH)/url/url.gyp:url_lib', + ], + 'defines': ['STORAGE_BROWSER_IMPLEMENTATION'], + 'sources': [ + 'browser/blob/blob_data_handle.cc', + 'browser/blob/blob_data_handle.h', + 'browser/blob/blob_storage_context.cc', + 'browser/blob/blob_storage_context.h', + 'browser/blob/blob_url_request_job.cc', + 'browser/blob/blob_url_request_job.h', + 'browser/blob/blob_url_request_job_factory.cc', + 'browser/blob/blob_url_request_job_factory.h', + 'browser/blob/file_stream_reader.cc', + 'browser/blob/file_stream_reader.h', + 'browser/blob/local_file_stream_reader.cc', + 'browser/blob/local_file_stream_reader.h', + 'browser/blob/view_blob_internals_job.cc', + 'browser/blob/view_blob_internals_job.h', + 'browser/database/database_quota_client.cc', + 'browser/database/database_quota_client.h', + 'browser/database/database_tracker.cc', + 'browser/database/database_tracker.h', + 'browser/database/database_util.cc', + 'browser/database/database_util.h', + 'browser/database/databases_table.cc', + 'browser/database/databases_table.h', + 'browser/database/vfs_backend.cc', + 'browser/database/vfs_backend.h', + 'browser/fileapi/async_file_util.h', + 'browser/fileapi/async_file_util_adapter.cc', + 'browser/fileapi/async_file_util_adapter.h', + 'browser/fileapi/copy_or_move_file_validator.h', + 'browser/fileapi/copy_or_move_operation_delegate.cc', + 'browser/fileapi/copy_or_move_operation_delegate.h', + 'browser/fileapi/dragged_file_util.cc', + 'browser/fileapi/dragged_file_util.h', + 'browser/fileapi/external_mount_points.cc', + 'browser/fileapi/external_mount_points.h', + 'browser/fileapi/file_observers.h', + 'browser/fileapi/file_permission_policy.h', + 'browser/fileapi/file_stream_writer.h', + 'browser/fileapi/file_system_backend.h', + 'browser/fileapi/file_system_context.cc', + 'browser/fileapi/file_system_context.h', + 'browser/fileapi/file_system_dir_url_request_job.cc', + 'browser/fileapi/file_system_dir_url_request_job.h', + 'browser/fileapi/file_system_file_stream_reader.cc', + 'browser/fileapi/file_system_file_stream_reader.h', + 'browser/fileapi/file_system_file_util.cc', + 'browser/fileapi/file_system_file_util.h', + 'browser/fileapi/file_system_operation.h', + 'browser/fileapi/file_system_operation_context.cc', + 'browser/fileapi/file_system_operation_context.h', + 'browser/fileapi/file_system_operation_impl.cc', + 'browser/fileapi/file_system_operation_impl.h', + 'browser/fileapi/file_system_operation_runner.cc', + 'browser/fileapi/file_system_operation_runner.h', + 'browser/fileapi/file_system_options.cc', + 'browser/fileapi/file_system_options.h', + 'browser/fileapi/file_system_quota_client.cc', + 'browser/fileapi/file_system_quota_client.h', + 'browser/fileapi/file_system_quota_util.h', + 'browser/fileapi/file_system_url.cc', + 'browser/fileapi/file_system_url.h', + 'browser/fileapi/file_system_url_request_job.cc', + 'browser/fileapi/file_system_url_request_job.h', + 'browser/fileapi/file_system_url_request_job_factory.cc', + 'browser/fileapi/file_system_url_request_job_factory.h', + 'browser/fileapi/file_system_usage_cache.cc', + 'browser/fileapi/file_system_usage_cache.h', + 'browser/fileapi/file_writer_delegate.cc', + 'browser/fileapi/file_writer_delegate.h', + 'browser/fileapi/isolated_context.cc', + 'browser/fileapi/isolated_context.h', + 'browser/fileapi/isolated_file_system_backend.cc', + 'browser/fileapi/isolated_file_system_backend.h', + 'browser/fileapi/local_file_stream_writer.cc', + 'browser/fileapi/local_file_stream_writer.h', + 'browser/fileapi/local_file_util.cc', + 'browser/fileapi/local_file_util.h', + 'browser/fileapi/mount_points.cc', + 'browser/fileapi/mount_points.h', + 'browser/fileapi/native_file_util.cc', + 'browser/fileapi/native_file_util.h', + 'browser/fileapi/obfuscated_file_util.cc', + 'browser/fileapi/obfuscated_file_util.h', + 'browser/fileapi/open_file_system_mode.h', + 'browser/fileapi/plugin_private_file_system_backend.cc', + 'browser/fileapi/plugin_private_file_system_backend.h', + 'browser/fileapi/quota/open_file_handle.cc', + 'browser/fileapi/quota/open_file_handle.h', + 'browser/fileapi/quota/open_file_handle_context.cc', + 'browser/fileapi/quota/open_file_handle_context.h', + 'browser/fileapi/quota/quota_backend_impl.cc', + 'browser/fileapi/quota/quota_backend_impl.h', + 'browser/fileapi/quota/quota_reservation.cc', + 'browser/fileapi/quota/quota_reservation.h', + 'browser/fileapi/quota/quota_reservation_buffer.cc', + 'browser/fileapi/quota/quota_reservation_buffer.h', + 'browser/fileapi/quota/quota_reservation_manager.cc', + 'browser/fileapi/quota/quota_reservation_manager.h', + 'browser/fileapi/recursive_operation_delegate.cc', + 'browser/fileapi/recursive_operation_delegate.h', + 'browser/fileapi/remove_operation_delegate.cc', + 'browser/fileapi/remove_operation_delegate.h', + 'browser/fileapi/sandbox_directory_database.cc', + 'browser/fileapi/sandbox_directory_database.h', + 'browser/fileapi/sandbox_file_stream_writer.cc', + 'browser/fileapi/sandbox_file_stream_writer.h', + 'browser/fileapi/sandbox_file_system_backend.cc', + 'browser/fileapi/sandbox_file_system_backend.h', + 'browser/fileapi/sandbox_file_system_backend_delegate.cc', + 'browser/fileapi/sandbox_file_system_backend_delegate.h', + 'browser/fileapi/sandbox_isolated_origin_database.cc', + 'browser/fileapi/sandbox_isolated_origin_database.h', + 'browser/fileapi/sandbox_origin_database.cc', + 'browser/fileapi/sandbox_origin_database.h', + 'browser/fileapi/sandbox_origin_database_interface.cc', + 'browser/fileapi/sandbox_origin_database_interface.h', + 'browser/fileapi/sandbox_prioritized_origin_database.cc', + 'browser/fileapi/sandbox_prioritized_origin_database.h', + 'browser/fileapi/sandbox_quota_observer.cc', + 'browser/fileapi/sandbox_quota_observer.h', + 'browser/fileapi/task_runner_bound_observer_list.h', + 'browser/fileapi/timed_task_helper.cc', + 'browser/fileapi/timed_task_helper.h', + 'browser/fileapi/transient_file_util.cc', + 'browser/fileapi/transient_file_util.h', + 'browser/quota/quota_callbacks.h', + 'browser/quota/quota_client.h', + 'browser/quota/quota_database.cc', + 'browser/quota/quota_database.h', + 'browser/quota/quota_manager.cc', + 'browser/quota/quota_manager.h', + 'browser/quota/quota_manager_proxy.cc', + 'browser/quota/quota_manager_proxy.h', + 'browser/quota/quota_task.cc', + 'browser/quota/quota_task.h', + 'browser/quota/quota_temporary_storage_evictor.cc', + 'browser/quota/quota_temporary_storage_evictor.h', + 'browser/quota/special_storage_policy.cc', + 'browser/quota/special_storage_policy.h', + 'browser/quota/storage_monitor.cc', + 'browser/quota/storage_monitor.h', + 'browser/quota/storage_observer.cc', + 'browser/quota/storage_observer.h', + 'browser/quota/usage_tracker.cc', + 'browser/quota/usage_tracker.h', + 'browser/storage_browser_export.h', + ], + # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. + 'msvs_disabled_warnings': [ 4267, ], + + 'conditions': [ + ['chrome_multiple_dll!=1', { + 'dependencies': [ + '<(DEPTH)/third_party/WebKit/public/blink.gyp:blink', + ], + }], + ], + }, + { + # GN version: //webkit/browser:dump_file_system + 'target_name': 'dump_file_system', + 'type': 'executable', + 'sources': [ + 'browser/fileapi/dump_file_system.cc', + ], + 'dependencies': [ + '<(DEPTH)/base/base.gyp:base', + '<(DEPTH)/storage/storage_common.gyp:storage_common', + 'storage', + ], + }, + ], +} |