diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2022-05-17 17:24:03 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2022-06-22 07:51:41 +0000 |
commit | 774f54339e5db91f785733232d3950366db65d07 (patch) | |
tree | 068e1b47bd1af94d77094ed12b604a6b83d9c22a /chromium/storage | |
parent | f7eaed5286974984ba5f9e3189d8f49d03e99f81 (diff) | |
download | qtwebengine-chromium-774f54339e5db91f785733232d3950366db65d07.tar.gz |
BASELINE: Update Chromium to 102.0.5005.57
Change-Id: I885f714bb40ee724c28f94ca6bd8dbdb39915158
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/storage')
97 files changed, 2721 insertions, 1266 deletions
diff --git a/chromium/storage/OWNERS b/chromium/storage/OWNERS index 7ccd317ff28..16d75f83b76 100644 --- a/chromium/storage/OWNERS +++ b/chromium/storage/OWNERS @@ -1,6 +1,9 @@ set noparent + +# Primary +ayui@chromium.org + +# Secondary +asully@chromium.org dmurph@chromium.org jsbell@chromium.org -pwnall@chromium.org -mek@chromium.org -kinuko@chromium.org diff --git a/chromium/storage/browser/BUILD.gn b/chromium/storage/browser/BUILD.gn index 5e80213ac73..088adb899b0 100644 --- a/chromium/storage/browser/BUILD.gn +++ b/chromium/storage/browser/BUILD.gn @@ -75,6 +75,8 @@ component("browser") { "file_system/async_file_util_adapter.cc", "file_system/async_file_util_adapter.h", "file_system/copy_or_move_file_validator.h", + "file_system/copy_or_move_hook_delegate.cc", + "file_system/copy_or_move_hook_delegate.h", "file_system/copy_or_move_operation_delegate.cc", "file_system/copy_or_move_operation_delegate.h", "file_system/dragged_file_util.cc", @@ -212,6 +214,8 @@ component("browser") { "quota/quota_temporary_storage_evictor.h", "quota/special_storage_policy.cc", "quota/special_storage_policy.h", + "quota/storage_directory.cc", + "quota/storage_directory.h", "quota/storage_policy_observer.cc", "quota/storage_policy_observer.h", "quota/usage_tracker.cc", @@ -327,9 +331,11 @@ source_set("unittests") { "file_system/transient_file_util_unittest.cc", "quota/quota_database_migrations_unittest.cc", "quota/quota_database_unittest.cc", + "quota/quota_manager_proxy_unittest.cc", "quota/quota_manager_unittest.cc", "quota/quota_settings_unittest.cc", "quota/quota_temporary_storage_evictor_unittest.cc", + "quota/storage_directory_unittest.cc", "quota/storage_policy_observer_unittest.cc", "quota/usage_tracker_unittest.cc", "test/mock_quota_manager_unittest.cc", @@ -347,6 +353,7 @@ source_set("unittests") { "//services/network/public/cpp", "//services/network/public/mojom", "//sql:test_support", + "//storage/browser/quota:mojo_bindings", "//testing/gtest", "//third_party/blink/public/common", "//third_party/leveldatabase", @@ -382,8 +389,6 @@ static_library("test_support") { "test/mock_file_update_observer.h", "test/mock_quota_client.cc", "test/mock_quota_client.h", - "test/mock_quota_database.cc", - "test/mock_quota_database.h", "test/mock_quota_manager.cc", "test/mock_quota_manager.h", "test/mock_quota_manager_proxy.cc", diff --git a/chromium/storage/browser/blob/OWNERS b/chromium/storage/browser/blob/OWNERS deleted file mode 100644 index 85cf2580291..00000000000 --- a/chromium/storage/browser/blob/OWNERS +++ /dev/null @@ -1,3 +0,0 @@ -dmurph@chromium.org -mek@chromium.org -jianli@chromium.org diff --git a/chromium/storage/browser/blob/blob_builder_from_stream.cc b/chromium/storage/browser/blob/blob_builder_from_stream.cc index a39af4e716c..8abde343d40 100644 --- a/chromium/storage/browser/blob/blob_builder_from_stream.cc +++ b/chromium/storage/browser/blob/blob_builder_from_stream.cc @@ -8,8 +8,8 @@ #include "base/containers/span.h" #include "base/guid.h" #include "base/metrics/histogram_macros.h" -#include "base/task/post_task.h" #include "base/task/thread_pool.h" +#include "base/time/time.h" #include "mojo/public/c/system/types.h" #include "mojo/public/cpp/bindings/associated_remote.h" #include "storage/browser/blob/blob_data_item.h" diff --git a/chromium/storage/browser/blob/blob_builder_from_stream_unittest.cc b/chromium/storage/browser/blob/blob_builder_from_stream_unittest.cc index 050db95440d..134a614c981 100644 --- a/chromium/storage/browser/blob/blob_builder_from_stream_unittest.cc +++ b/chromium/storage/browser/blob/blob_builder_from_stream_unittest.cc @@ -12,7 +12,6 @@ #include "base/files/scoped_temp_dir.h" #include "base/rand_util.h" #include "base/run_loop.h" -#include "base/task/post_task.h" #include "base/task/thread_pool.h" #include "base/task/thread_pool/thread_pool_instance.h" #include "base/test/bind.h" diff --git a/chromium/storage/browser/blob/blob_data_item.h b/chromium/storage/browser/blob/blob_data_item.h index aa681aceb61..138a038a5c5 100644 --- a/chromium/storage/browser/blob/blob_data_item.h +++ b/chromium/storage/browser/blob/blob_data_item.h @@ -15,6 +15,7 @@ #include "base/containers/span.h" #include "base/files/file_path.h" #include "base/memory/ref_counted.h" +#include "base/time/time.h" #include "components/services/storage/public/mojom/blob_storage_context.mojom.h" #include "net/base/io_buffer.h" #include "storage/browser/blob/shareable_file_reference.h" diff --git a/chromium/storage/browser/blob/blob_impl.cc b/chromium/storage/browser/blob/blob_impl.cc index d46565ac953..6b6590451de 100644 --- a/chromium/storage/browser/blob/blob_impl.cc +++ b/chromium/storage/browser/blob/blob_impl.cc @@ -12,8 +12,8 @@ #include "base/bind.h" #include "base/containers/span.h" #include "base/files/file_util.h" -#include "base/task/post_task.h" #include "base/task/thread_pool.h" +#include "base/time/time.h" #include "mojo/public/cpp/bindings/remote.h" #include "net/base/io_buffer.h" #include "storage/browser/blob/blob_data_handle.h" diff --git a/chromium/storage/browser/blob/blob_impl_unittest.cc b/chromium/storage/browser/blob/blob_impl_unittest.cc index 3b88a924b7d..b67b6cfe563 100644 --- a/chromium/storage/browser/blob/blob_impl_unittest.cc +++ b/chromium/storage/browser/blob/blob_impl_unittest.cc @@ -12,7 +12,6 @@ #include "base/bind.h" #include "base/memory/raw_ptr.h" #include "base/run_loop.h" -#include "base/task/post_task.h" #include "base/test/task_environment.h" #include "mojo/public/cpp/bindings/pending_remote.h" #include "mojo/public/cpp/bindings/receiver.h" diff --git a/chromium/storage/browser/blob/blob_reader.cc b/chromium/storage/browser/blob/blob_reader.cc index 78a6fbd2fe2..a3eede480cd 100644 --- a/chromium/storage/browser/blob/blob_reader.cc +++ b/chromium/storage/browser/blob/blob_reader.cc @@ -15,9 +15,7 @@ #include "base/bind.h" #include "base/callback_helpers.h" #include "base/memory/ptr_util.h" -#include "base/task/post_task.h" #include "base/task/thread_pool.h" -#include "base/time/time.h" #include "base/trace_event/trace_event.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" diff --git a/chromium/storage/browser/blob/blob_registry_impl.cc b/chromium/storage/browser/blob/blob_registry_impl.cc index 3d565c5aa08..4f73ddcf259 100644 --- a/chromium/storage/browser/blob/blob_registry_impl.cc +++ b/chromium/storage/browser/blob/blob_registry_impl.cc @@ -501,10 +501,12 @@ bool BlobRegistryImpl::BlobUnderConstruction::ContainsCycles( BlobRegistryImpl::BlobRegistryImpl( base::WeakPtr<BlobStorageContext> context, base::WeakPtr<BlobUrlRegistry> url_registry, + scoped_refptr<base::TaskRunner> url_registry_runner, scoped_refptr<FileSystemContext> file_system_context) : context_(std::move(context)), + file_system_context_(std::move(file_system_context)), url_registry_(std::move(url_registry)), - file_system_context_(std::move(file_system_context)) {} + url_registry_runner_(std::move(url_registry_runner)) {} BlobRegistryImpl::~BlobRegistryImpl() { // BlobBuilderFromStream needs to be aborted before it can be destroyed, but @@ -632,6 +634,7 @@ void BlobRegistryImpl::GetBlobFromUUID( return; } if (!context_->registry().HasEntry(uuid)) { + LOG(ERROR) << "Invalid UUID: " << uuid; // TODO(mek): Log histogram, old code logs Storage.Blob.InvalidReference std::move(callback).Run(); return; @@ -651,11 +654,22 @@ void BlobRegistryImpl::URLStoreForOrigin( "BlobRegistryImpl::URLStoreForOrigin"); return; } - auto self_owned_associated_receiver = mojo::MakeSelfOwnedAssociatedReceiver( - std::make_unique<BlobURLStoreImpl>(origin, url_registry_), - std::move(receiver)); - if (g_url_store_creation_hook) - g_url_store_creation_hook->Run(self_owned_associated_receiver); + url_registry_runner_->PostTask( + FROM_HERE, + base::BindOnce( + [](const url::Origin& origin, + mojo::PendingAssociatedReceiver<blink::mojom::BlobURLStore> + receiver, + base::WeakPtr<BlobUrlRegistry> url_registry) { + auto self_owned_associated_receiver = + mojo::MakeSelfOwnedAssociatedReceiver( + std::make_unique<BlobURLStoreImpl>(origin, + std::move(url_registry)), + std::move(receiver)); + if (g_url_store_creation_hook) + g_url_store_creation_hook->Run(self_owned_associated_receiver); + }, + origin, std::move(receiver), url_registry_)); } // static diff --git a/chromium/storage/browser/blob/blob_registry_impl.h b/chromium/storage/browser/blob/blob_registry_impl.h index 35ae9b60213..866dac0921d 100644 --- a/chromium/storage/browser/blob/blob_registry_impl.h +++ b/chromium/storage/browser/blob/blob_registry_impl.h @@ -38,6 +38,7 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) BlobRegistryImpl BlobRegistryImpl(base::WeakPtr<BlobStorageContext> context, base::WeakPtr<BlobUrlRegistry> url_registry, + scoped_refptr<base::TaskRunner> url_registry_runner, scoped_refptr<FileSystemContext> file_system_context); BlobRegistryImpl(const BlobRegistryImpl&) = delete; @@ -95,9 +96,12 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) BlobRegistryImpl std::unique_ptr<BlobDataHandle> result); base::WeakPtr<BlobStorageContext> context_; - base::WeakPtr<BlobUrlRegistry> url_registry_; scoped_refptr<FileSystemContext> file_system_context_; + // `url_registry_` should only be accessed on `url_registry_runner_`. + base::WeakPtr<BlobUrlRegistry> url_registry_; + scoped_refptr<base::TaskRunner> url_registry_runner_; + mojo::ReceiverSet<blink::mojom::BlobRegistry, std::unique_ptr<Delegate>> receivers_; diff --git a/chromium/storage/browser/blob/blob_registry_impl_unittest.cc b/chromium/storage/browser/blob/blob_registry_impl_unittest.cc index 90a5b6b002d..6d84af4a8a8 100644 --- a/chromium/storage/browser/blob/blob_registry_impl_unittest.cc +++ b/chromium/storage/browser/blob/blob_registry_impl_unittest.cc @@ -17,7 +17,6 @@ #include "base/memory/raw_ptr.h" #include "base/rand_util.h" #include "base/run_loop.h" -#include "base/task/post_task.h" #include "base/task/sequenced_task_runner.h" #include "base/task/thread_pool.h" #include "base/test/bind.h" @@ -82,11 +81,13 @@ class BlobRegistryImplTest : public testing::Test { /*quota_manager_proxy=*/nullptr, std::vector<std::unique_ptr<FileSystemBackend>>(), std::vector<URLRequestAutoMountHandler>(), data_dir_.GetPath(), + data_dir_.GetPath(), FileSystemOptions(FileSystemOptions::PROFILE_MODE_INCOGNITO, /*force_in_memory=*/false, std::vector<std::string>())); registry_impl_ = std::make_unique<BlobRegistryImpl>( - context_->AsWeakPtr(), url_registry_.AsWeakPtr(), file_system_context_); + context_->AsWeakPtr(), url_registry_.AsWeakPtr(), + base::SequencedTaskRunnerHandle::Get(), file_system_context_); auto delegate = std::make_unique<MockBlobRegistryDelegate>(); delegate_ptr_ = delegate.get(); registry_impl_->Bind(registry_.BindNewPipeAndPassReceiver(), diff --git a/chromium/storage/browser/blob/blob_storage_context.cc b/chromium/storage/browser/blob/blob_storage_context.cc index f69576532bb..147e2adc554 100644 --- a/chromium/storage/browser/blob/blob_storage_context.cc +++ b/chromium/storage/browser/blob/blob_storage_context.cc @@ -21,10 +21,10 @@ #include "base/numerics/safe_conversions.h" #include "base/numerics/safe_math.h" #include "base/strings/stringprintf.h" -#include "base/task/post_task.h" #include "base/task/task_runner.h" #include "base/task/thread_pool.h" #include "base/threading/thread_task_runner_handle.h" +#include "base/time/time.h" #include "base/trace_event/memory_dump_manager.h" #include "base/trace_event/trace_event.h" #include "mojo/public/cpp/bindings/callback_helpers.h" diff --git a/chromium/storage/browser/blob/blob_storage_context_mojo_unittest.cc b/chromium/storage/browser/blob/blob_storage_context_mojo_unittest.cc index f2b5409b815..589378ffb75 100644 --- a/chromium/storage/browser/blob/blob_storage_context_mojo_unittest.cc +++ b/chromium/storage/browser/blob/blob_storage_context_mojo_unittest.cc @@ -12,13 +12,13 @@ #include "base/containers/span.h" #include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" -#include "base/task/post_task.h" #include "base/task/task_traits.h" #include "base/task/thread_pool.h" #include "base/test/bind.h" #include "base/test/task_environment.h" #include "base/test/test_simple_task_runner.h" #include "base/threading/thread_restrictions.h" +#include "base/time/time.h" #include "components/services/storage/public/mojom/blob_storage_context.mojom.h" #include "mojo/public/cpp/base/big_buffer.h" #include "mojo/public/cpp/bindings/pending_remote.h" diff --git a/chromium/storage/browser/blob/blob_transport_strategy_unittest.cc b/chromium/storage/browser/blob/blob_transport_strategy_unittest.cc index 0f24c79baf3..ffda454761f 100644 --- a/chromium/storage/browser/blob/blob_transport_strategy_unittest.cc +++ b/chromium/storage/browser/blob/blob_transport_strategy_unittest.cc @@ -16,11 +16,11 @@ #include "base/files/scoped_temp_dir.h" #include "base/rand_util.h" #include "base/run_loop.h" -#include "base/task/post_task.h" #include "base/task/sequenced_task_runner.h" #include "base/task/thread_pool.h" #include "base/test/task_environment.h" #include "base/threading/thread_restrictions.h" +#include "base/time/time.h" #include "mojo/public/cpp/bindings/self_owned_receiver.h" #include "mojo/public/cpp/system/functions.h" #include "storage/browser/blob/blob_data_builder.h" diff --git a/chromium/storage/browser/blob/write_blob_to_file.cc b/chromium/storage/browser/blob/write_blob_to_file.cc index 4fd0c3d3137..758c964e4e0 100644 --- a/chromium/storage/browser/blob/write_blob_to_file.cc +++ b/chromium/storage/browser/blob/write_blob_to_file.cc @@ -16,7 +16,6 @@ #include "base/files/file_util.h" #include "base/numerics/checked_math.h" #include "base/numerics/safe_conversions.h" -#include "base/task/post_task.h" #include "base/task/task_traits.h" #include "base/task/thread_pool.h" #include "storage/browser/blob/blob_data_handle.h" diff --git a/chromium/storage/browser/database/OWNERS b/chromium/storage/browser/database/OWNERS index dd55f956da2..e674dc93584 100644 --- a/chromium/storage/browser/database/OWNERS +++ b/chromium/storage/browser/database/OWNERS @@ -1,5 +1,5 @@ # Primary -pwnall@chromium.org +asully@chromium.org # Seconday -mek@chromium.org +ayui@chromium.org diff --git a/chromium/storage/browser/database/database_quota_client_unittest.cc b/chromium/storage/browser/database/database_quota_client_unittest.cc index 17fe31dbd1b..168bcf88939 100644 --- a/chromium/storage/browser/database/database_quota_client_unittest.cc +++ b/chromium/storage/browser/database/database_quota_client_unittest.cc @@ -18,6 +18,7 @@ #include "base/location.h" #include "base/memory/scoped_refptr.h" #include "base/run_loop.h" +#include "base/sequence_checker_impl.h" #include "base/strings/utf_string_conversions.h" #include "base/task/single_thread_task_runner.h" #include "base/test/bind.h" @@ -35,6 +36,7 @@ #include "storage/browser/quota/quota_manager.h" #include "storage/browser/quota/quota_manager_proxy.h" #include "storage/browser/quota/special_storage_policy.h" +#include "storage/browser/test/mock_special_storage_policy.h" #include "storage/common/database/database_identifier.h" #include "testing/gmock/include/gmock/gmock-matchers.h" #include "testing/gtest/include/gtest/gtest.h" @@ -52,13 +54,15 @@ static const blink::mojom::StorageType kTemp = // Mocks DatabaseTracker methods used by DatabaseQuotaClient. class MockDatabaseTracker : public DatabaseTracker { public: - MockDatabaseTracker(const base::FilePath& path, - bool is_incognito, - scoped_refptr<QuotaManagerProxy> quota_manager_proxy) + MockDatabaseTracker( + const base::FilePath& path, + bool is_incognito, + scoped_refptr<QuotaManagerProxy> quota_manager_proxy, + scoped_refptr<SpecialStoragePolicy> special_storage_policy) : DatabaseTracker(path, is_incognito, - /*special_storage_policy=*/nullptr, - quota_manager_proxy, + std::move(special_storage_policy), + std::move(quota_manager_proxy), DatabaseTracker::CreatePassKey()) {} bool GetOriginInfo(const std::string& origin_identifier, @@ -143,18 +147,24 @@ class DatabaseQuotaClientTest : public testing::TestWithParam<bool> { : kStorageKeyA( blink::StorageKey::CreateFromStringForTesting("http://host")), kStorageKeyB( - blink::StorageKey::CreateFromStringForTesting("http://host:8000")) { - } + blink::StorageKey::CreateFromStringForTesting("http://host:8000")), + special_storage_policy_( + base::MakeRefCounted<MockSpecialStoragePolicy>()) {} + ~DatabaseQuotaClientTest() override = default; + + DatabaseQuotaClientTest(const DatabaseQuotaClientTest&) = delete; + DatabaseQuotaClientTest& operator=(const DatabaseQuotaClientTest&) = delete; void SetUp() override { ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); quota_manager_ = base::MakeRefCounted<QuotaManager>( /*is_incognito_=*/false, data_dir_.GetPath(), base::ThreadTaskRunnerHandle::Get(), - /*quota_change_callback=*/base::DoNothing(), - /*special_storage_policy=*/nullptr, GetQuotaSettingsFunc()); + /*quota_change_callback=*/base::DoNothing(), special_storage_policy_, + GetQuotaSettingsFunc()); mock_tracker_ = base::MakeRefCounted<MockDatabaseTracker>( - data_dir_.GetPath(), is_incognito(), quota_manager_->proxy()); + data_dir_.GetPath(), is_incognito(), quota_manager_->proxy(), + special_storage_policy_); } void TearDown() override { @@ -211,7 +221,10 @@ class DatabaseQuotaClientTest : public testing::TestWithParam<bool> { return delete_future.Get(); } + scoped_refptr<MockSpecialStoragePolicy> special_storage_policy_; + base::ScopedTempDir data_dir_; + base::test::TaskEnvironment task_environment_; scoped_refptr<MockDatabaseTracker> mock_tracker_; scoped_refptr<QuotaManager> quota_manager_; diff --git a/chromium/storage/browser/database/database_tracker.cc b/chromium/storage/browser/database/database_tracker.cc index 74423c6f847..8abec7e8e4b 100644 --- a/chromium/storage/browser/database/database_tracker.cc +++ b/chromium/storage/browser/database/database_tracker.cc @@ -20,9 +20,9 @@ #include "base/location.h" #include "base/memory/scoped_refptr.h" #include "base/metrics/user_metrics.h" +#include "base/observer_list.h" #include "base/strings/string_number_conversions.h" #include "base/strings/utf_string_conversions.h" -#include "base/task/post_task.h" #include "base/task/thread_pool.h" #include "base/time/time.h" #include "base/types/pass_key.h" diff --git a/chromium/storage/browser/database/database_tracker.h b/chromium/storage/browser/database/database_tracker.h index 63d2c3acc86..95a867e27ca 100644 --- a/chromium/storage/browser/database/database_tracker.h +++ b/chromium/storage/browser/database/database_tracker.h @@ -148,8 +148,8 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) DatabaseTracker virtual bool GetAllOriginsInfo(std::vector<OriginInfo>* origins_info); // Thread-safe getter. - QuotaManagerProxy* quota_manager_proxy() const { - return quota_manager_proxy_.get(); + const scoped_refptr<QuotaManagerProxy>& quota_manager_proxy() const { + return quota_manager_proxy_; } bool IsDatabaseScheduledForDeletion(const std::string& origin_identifier, diff --git a/chromium/storage/browser/database/database_tracker_unittest.cc b/chromium/storage/browser/database/database_tracker_unittest.cc index 32b315b90b9..aab6a5a2ea7 100644 --- a/chromium/storage/browser/database/database_tracker_unittest.cc +++ b/chromium/storage/browser/database/database_tracker_unittest.cc @@ -103,7 +103,8 @@ class TestQuotaManagerProxy : public QuotaManagerProxy { TestQuotaManagerProxy() : QuotaManagerProxy( /*quota_manager_impl=*/nullptr, - base::SequencedTaskRunnerHandle::Get()) {} + base::SequencedTaskRunnerHandle::Get(), + /*profile_path=*/base::FilePath()) {} void RegisterClient( mojo::PendingRemote<mojom::QuotaClient> client, diff --git a/chromium/storage/browser/file_system/async_file_util.h b/chromium/storage/browser/file_system/async_file_util.h index 3f1b41a5f62..7c6a896a4f8 100644 --- a/chromium/storage/browser/file_system/async_file_util.h +++ b/chromium/storage/browser/file_system/async_file_util.h @@ -49,9 +49,11 @@ class AsyncFileUtil { public: using StatusCallback = base::OnceCallback<void(base::File::Error result)>; - // |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. + // Used for CreateOrOpen(). File util implementations can specify an + // `on_close_callback` if an operation is needed after closing a file. If + // non-null, CreateOrOpen() callers must run the callback (on the IO thread) + // after the file closes. If the file is duped, the callback should not be run + // until all dups of the file have been closed. using CreateOrOpenCallback = base::OnceCallback<void(base::File file, base::OnceClosure on_close_callback)>; diff --git a/chromium/storage/browser/file_system/async_file_util_adapter.cc b/chromium/storage/browser/file_system/async_file_util_adapter.cc index d1f31d8149f..a6e33abef35 100644 --- a/chromium/storage/browser/file_system/async_file_util_adapter.cc +++ b/chromium/storage/browser/file_system/async_file_util_adapter.cc @@ -12,6 +12,7 @@ #include <vector> #include "base/bind.h" +#include "base/callback.h" #include "base/task/sequenced_task_runner.h" #include "base/task/task_runner_util.h" #include "base/threading/thread_task_runner_handle.h" diff --git a/chromium/storage/browser/file_system/copy_or_move_hook_delegate.cc b/chromium/storage/browser/file_system/copy_or_move_hook_delegate.cc new file mode 100644 index 00000000000..f65f4865524 --- /dev/null +++ b/chromium/storage/browser/file_system/copy_or_move_hook_delegate.cc @@ -0,0 +1,61 @@ +// Copyright 2022 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/file_system/copy_or_move_hook_delegate.h" + +#include "base/callback.h" +#include "base/files/file.h" +#include "base/sequence_checker.h" +#include "storage/browser/file_system/file_system_url.h" + +namespace storage { + +CopyOrMoveHookDelegate::CopyOrMoveHookDelegate() { + DETACH_FROM_SEQUENCE(sequence_checker_); +} + +void CopyOrMoveHookDelegate::OnBeginProcessFile( + const FileSystemURL& source_url, + const FileSystemURL& destination_url, + StatusCallback callback) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + std::move(callback).Run(base::File::FILE_OK); +} + +void CopyOrMoveHookDelegate::OnBeginProcessDirectory( + const FileSystemURL& source_url, + const FileSystemURL& destination_url, + StatusCallback callback) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + std::move(callback).Run(base::File::FILE_OK); +} + +void CopyOrMoveHookDelegate::OnProgress(const FileSystemURL& source_url, + const FileSystemURL& destination_url, + int64_t size) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); +} + +void CopyOrMoveHookDelegate::OnError(const FileSystemURL& source_url, + const FileSystemURL& destination_url, + base::File::Error error) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); +} + +void CopyOrMoveHookDelegate::OnEndCopy(const FileSystemURL& source_url, + const FileSystemURL& destination_url) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); +} + +void CopyOrMoveHookDelegate::OnEndMove(const FileSystemURL& source_url, + const FileSystemURL& destination_url) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); +} + +void CopyOrMoveHookDelegate::OnEndRemoveSource( + const FileSystemURL& source_url) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); +} + +} // namespace storage diff --git a/chromium/storage/browser/file_system/copy_or_move_hook_delegate.h b/chromium/storage/browser/file_system/copy_or_move_hook_delegate.h new file mode 100644 index 00000000000..1c2937bfdfe --- /dev/null +++ b/chromium/storage/browser/file_system/copy_or_move_hook_delegate.h @@ -0,0 +1,160 @@ +// Copyright 2022 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_FILE_SYSTEM_COPY_OR_MOVE_HOOK_DELEGATE_H_ +#define STORAGE_BROWSER_FILE_SYSTEM_COPY_OR_MOVE_HOOK_DELEGATE_H_ + +#include "base/callback_forward.h" +#include "base/component_export.h" +#include "base/files/file.h" +#include "base/memory/weak_ptr.h" +#include "base/sequence_checker.h" + +namespace storage { +class FileSystemURL; + +// A delegate to handle different hooks of the CopyOrMove operation. +// Its At* functions take a callback that is called when the function +// finishes, potentially notifying of any errors during the execution of +// the function. +// The Notify* functions do not take a callback and can asynchronously notify of +// any progress or errors. +// Used for progress updates, etc. in FileSystemOperation::Copy() and Move(). +// +// Note that Move() has both a same-filesystem (1) and a cross-filesystem (2) +// implementation. +// 1) Requires metadata updates. Depending on the underlying implementation: +// - we either only update the metadata of (or in other words, rename) the +// moving directory +// - or the directories are recursively copied + deleted, while the files are +// moved by having their metadata updated. +// 2) Degrades into copy + delete: each entry is copied and deleted +// recursively. +// +// OnBeginProcessFile, resp. OnBeginProcessDirectory is called at the start of +// each copy or move operation for a file, resp. a directory. The `source_url` +// and the `destination_url` are the URLs of the source and the destination +// entries. Note that for the root directory, OnBeginProcessFile is called +// instead of OnBeginProcessDirectory. This resembles the call order of the +// RecursiveOperationDelegate. +// +// OnProgress is called periodically during file transfer (not called for +// same-filesystem move and directory copy/move). +// The `source_url` and the `destination_url` are the URLs of the source and +// the destination entries. `size` is the number of cumulative copied bytes +// for the currently copied file. Both at beginning and ending of file +// transfer, PROGRESS event should be called. At beginning, `size` should be +// 0. At ending, `size` should be the size of the file. +// +// NotifyError is called for any occurring error. +// The `source_url` and the `destination_url` are the URLs of the source and +// the destination entries. `error` is the base::File::Error that was noticed. +// NotifyError is also called if an OnBeginProcessFile or +// OnBeginProcessDirectory function passes an error to their respective +// callbacks. +// +// OnEndCopy is called for each destination entry that has been successfully +// copied (for both file and directory). The `source_url` and the +// `destination_url` are the URLs of the source and the destination entries. +// +// OnEndMove is called for each entry that has been successfully moved (for +// both file and directory), in the case of a same-filesystem move. The +// `source_url` and the `destination_url` are the URLs of the source and the +// destination entries. +// +// OnEndRemoveSource, applies in the Move() case only, and is called for +// each source entry that has been successfully removed from its source location +// (for both file and directory). The `source_url` is the URL of the source +// entry. +// +// When moving files, the expected events are as follows. +// Copy: OnBeginProcessFile -> OnProgress -> ... -> OnProgress -> +// OnEndCopy. Move (same-filesystem): OnBeginProcessFile -> OnEndMove. +// Move (cross-filesystem): OnBeginProcessFile -> OnProgress -> ... -> +// OnProgress -> OnEndCopy -> OnEndRemoveSource. +// +// Here is an example callback sequence of for a copy or a cross-filesystem +// move. Suppose there are a/b/c.txt (100 bytes) and a/b/d.txt (200 bytes), +// and trying to transfer a to x recursively, then the progress update +// sequence will be (Note that for the root directory, OnBeginProcessFile is +// called instead of OnBeginProcessDirectory): +// +// OnBeginProcessFile a x/a (starting create "a" directory in x/). +// OnEndCopy a x/a (creating "a" directory in x/ is finished). +// +// OnBeginProcessDirectory a/b x/a/b (starting create "b" directory in x/a). +// OnEndCopy a/b x/a/b (creating "b" directory in x/a/ is finished). +// +// OnBeginProcessFile a/b/c.txt x/a/b/c.txt (starting to transfer "c.txt" in +// x/a/b/). +// OnProgress a/b/c.txt x/a/b/c.txt 0 (The first OnProgress's `size` +// should be 0). +// OnProgress a/b/c.txt x/a/b/c.txt 10 +// : +// OnProgress a/b/c.txt x/a/b/c.txt 90 +// OnProgress a/b/c.txt x/a/b/c.txt 100 (The last OnProgress's `size` +// should be the size of the file). +// OnEndCopy a/b/c.txt x/a/b/c.txt (transferring "c.txt" is finished). +// OnEndRemoveSource a/b/c.txt ("copy + delete" move case). +// +// OnBeginProcessFile a/b/d.txt x/a/b/d.txt (starting to transfer "d.txt" in +// x/a/b). +// OnProgress a/b/d.txt x/a/b/d.txt 0 (The first OnProgress's +// `size` should be 0). +// OnProgress a/b/d.txt x/a/b/d.txt 10 +// : +// OnProgress a/b/d.txt x/a/b/d.txt 190 +// OnProgress a/b/d.txt x/a/b/d.txt 200 (The last OnProgress's `size` +// should be the size of the file). +// OnEndCopy a/b/d.txt x/a/b/d.txt (transferring "d.txt" is finished). +// OnEndRemoveSource a/b/d.txt ("copy + delete" move case). +// +// OnEndRemoveSource a/b ("copy + delete" move case). +// +// OnEndRemoveSource a ("copy + delete" move case). +// +// 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 OnProgress events are +// optional, so they may not appear. All the progress callback invocations +// should be done before StatusCallback given to the Copy is called. Especially +// if an error is found before the first progress callback invocation, the +// progress callback may NOT be invoked for the copy. +// +class COMPONENT_EXPORT(STORAGE_BROWSER) CopyOrMoveHookDelegate + : public base::SupportsWeakPtr<CopyOrMoveHookDelegate> { + public: + using StatusCallback = base::OnceCallback<void(base::File::Error result)>; + + CopyOrMoveHookDelegate(); + + virtual ~CopyOrMoveHookDelegate() = default; + + virtual void OnBeginProcessFile(const FileSystemURL& source_url, + const FileSystemURL& destination_url, + StatusCallback callback); + + virtual void OnBeginProcessDirectory(const FileSystemURL& source_url, + const FileSystemURL& destination_url, + StatusCallback callback); + + virtual void OnProgress(const FileSystemURL& source_url, + const FileSystemURL& destination_url, + int64_t size); + virtual void OnError(const FileSystemURL& source_url, + const FileSystemURL& destination_url, + base::File::Error error); + + virtual void OnEndCopy(const FileSystemURL& source_url, + const FileSystemURL& destination_url); + virtual void OnEndMove(const FileSystemURL& source_url, + const FileSystemURL& destination_url); + virtual void OnEndRemoveSource(const FileSystemURL& source_url); + + protected: + SEQUENCE_CHECKER(sequence_checker_); +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILE_SYSTEM_COPY_OR_MOVE_HOOK_DELEGATE_H_ diff --git a/chromium/storage/browser/file_system/copy_or_move_operation_delegate.cc b/chromium/storage/browser/file_system/copy_or_move_operation_delegate.cc index 05a61084e32..b7d0cbd0649 100644 --- a/chromium/storage/browser/file_system/copy_or_move_operation_delegate.cc +++ b/chromium/storage/browser/file_system/copy_or_move_operation_delegate.cc @@ -4,24 +4,28 @@ #include "storage/browser/file_system/copy_or_move_operation_delegate.h" -#include <stdint.h> - +#include <cstdint> #include <memory> #include <tuple> #include <utility> +#include "base/auto_reset.h" #include "base/bind.h" #include "base/callback_helpers.h" +#include "base/files/file.h" #include "base/files/file_path.h" #include "base/memory/raw_ptr.h" +#include "base/memory/weak_ptr.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" #include "storage/browser/blob/shareable_file_reference.h" #include "storage/browser/file_system/copy_or_move_file_validator.h" +#include "storage/browser/file_system/copy_or_move_hook_delegate.h" #include "storage/browser/file_system/file_observers.h" #include "storage/browser/file_system/file_stream_reader.h" #include "storage/browser/file_system/file_stream_writer.h" #include "storage/browser/file_system/file_system_context.h" +#include "storage/browser/file_system/file_system_operation.h" #include "storage/browser/file_system/file_system_operation_runner.h" #include "storage/browser/file_system/file_system_url.h" #include "storage/common/file_system/file_system_util.h" @@ -50,38 +54,40 @@ class CopyOrMoveOperationDelegate::CopyOrMoveImpl { const FileSystemURL& src_url, const FileSystemURL& dest_url, const CopyOrMoveOperationDelegate::CopyOrMoveOptionSet options, - FileSystemOperation::CopyOrMoveProgressCallback progress_callback) + base::WeakPtr<storage::CopyOrMoveHookDelegate> + copy_or_move_hook_delegate_weak_ptr) : operation_runner_(operation_runner), operation_type_(operation_type), src_url_(src_url), dest_url_(dest_url), options_(options), - progress_callback_(std::move(progress_callback)) {} + copy_or_move_hook_delegate_weak_ptr_( + copy_or_move_hook_delegate_weak_ptr) {} // Callback for sending progress events with the current number of processed // bytes. void OnCopyOrMoveFileProgress(int64_t size) { - if (!progress_callback_.is_null()) { - progress_callback_.Run( - FileSystemOperation::CopyOrMoveProgressType::kProgress, src_url_, - dest_url_, size); - } + base::SequencedTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(&CopyOrMoveHookDelegate::OnProgress, + copy_or_move_hook_delegate_weak_ptr_, + src_url_, dest_url_, size)); } // Callback for sending progress events notifying the end of a copy, for a // copy operation or a cross-filesystem move. void DidEndCopy(CopyOrMoveOperationDelegate::StatusCallback callback, base::File::Error error) { - if (!progress_callback_.is_null()) { - if (error == base::File::FILE_OK) { - progress_callback_.Run( - FileSystemOperation::CopyOrMoveProgressType::kEndCopy, src_url_, - dest_url_, 0); - } else if (error != base::File::FILE_ERROR_NOT_A_FILE) { - progress_callback_.Run( - FileSystemOperation::CopyOrMoveProgressType::kError, src_url_, - dest_url_, 0); - } + if (error == base::File::FILE_OK) { + base::SequencedTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(&CopyOrMoveHookDelegate::OnEndCopy, + copy_or_move_hook_delegate_weak_ptr_, + src_url_, dest_url_)); + + } else if (error != base::File::FILE_ERROR_NOT_A_FILE) { + base::SequencedTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(&CopyOrMoveHookDelegate::OnError, + copy_or_move_hook_delegate_weak_ptr_, + src_url_, dest_url_, error)); } if (options_.Has(FileSystemOperation::CopyOrMoveOption:: @@ -108,17 +114,18 @@ class CopyOrMoveOperationDelegate::CopyOrMoveImpl { // in the case of a local (same-filesystem) move. void DidEndMove(CopyOrMoveOperationDelegate::StatusCallback callback, base::File::Error error) { - if (!progress_callback_.is_null()) { - if (error == base::File::FILE_OK) { - progress_callback_.Run( - FileSystemOperation::CopyOrMoveProgressType::kEndMove, src_url_, - dest_url_, 0); - } else { - progress_callback_.Run( - FileSystemOperation::CopyOrMoveProgressType::kError, src_url_, - dest_url_, 0); - } + if (error == base::File::FILE_OK) { + base::SequencedTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(&CopyOrMoveHookDelegate::OnEndMove, + copy_or_move_hook_delegate_weak_ptr_, + src_url_, dest_url_)); + } else { + base::SequencedTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(&CopyOrMoveHookDelegate::OnError, + copy_or_move_hook_delegate_weak_ptr_, + src_url_, dest_url_, error)); } + std::move(callback).Run(error); } @@ -127,17 +134,18 @@ class CopyOrMoveOperationDelegate::CopyOrMoveImpl { void DidEndRemoveSourceForMove( CopyOrMoveOperationDelegate::StatusCallback callback, base::File::Error error) { - if (!progress_callback_.is_null()) { - if (error == base::File::FILE_OK) { - progress_callback_.Run( - FileSystemOperation::CopyOrMoveProgressType::kEndRemoveSource, - src_url_, FileSystemURL(), 0); - } else { - progress_callback_.Run( - FileSystemOperation::CopyOrMoveProgressType::kError, src_url_, - dest_url_, 0); - } + if (error == base::File::FILE_OK) { + base::SequencedTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(&CopyOrMoveHookDelegate::OnEndRemoveSource, + copy_or_move_hook_delegate_weak_ptr_, src_url_)); + } else { + base::SequencedTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(&CopyOrMoveHookDelegate::OnError, + copy_or_move_hook_delegate_weak_ptr_, + src_url_, dest_url_, error)); } + std::move(callback).Run(error); } @@ -165,7 +173,8 @@ class CopyOrMoveOperationDelegate::CopyOrMoveImpl { bool force_error_for_test_ = false; private: - const FileSystemOperation::CopyOrMoveProgressCallback progress_callback_; + base::WeakPtr<storage::CopyOrMoveHookDelegate> + copy_or_move_hook_delegate_weak_ptr_; base::WeakPtrFactory<CopyOrMoveImpl> weak_factory_{this}; }; @@ -182,13 +191,14 @@ class CopyOrMoveOnSameFileSystemImpl const FileSystemURL& src_url, const FileSystemURL& dest_url, const CopyOrMoveOperationDelegate::CopyOrMoveOptionSet options, - FileSystemOperation::CopyOrMoveProgressCallback progress_callback) + base::WeakPtr<storage::CopyOrMoveHookDelegate> + copy_or_move_hook_delegate_weak_ptr) : CopyOrMoveImpl(operation_runner, operation_type, src_url, dest_url, options, - progress_callback) {} + copy_or_move_hook_delegate_weak_ptr) {} CopyOrMoveOnSameFileSystemImpl(const CopyOrMoveOnSameFileSystemImpl&) = delete; @@ -236,13 +246,14 @@ class SnapshotCopyOrMoveImpl const FileSystemURL& dest_url, CopyOrMoveOperationDelegate::CopyOrMoveOptionSet options, CopyOrMoveFileValidatorFactory* validator_factory, - FileSystemOperation::CopyOrMoveProgressCallback progress_callback) + base::WeakPtr<storage::CopyOrMoveHookDelegate> + copy_or_move_hook_delegate_weak_ptr) : CopyOrMoveImpl(operation_runner, operation_type, src_url, dest_url, options, - progress_callback), + copy_or_move_hook_delegate_weak_ptr), validator_factory_(validator_factory), cancel_requested_(false) {} @@ -506,13 +517,14 @@ class StreamCopyOrMoveImpl CopyOrMoveOperationDelegate::CopyOrMoveOptionSet options, std::unique_ptr<FileStreamReader> reader, std::unique_ptr<FileStreamWriter> writer, - FileSystemOperation::CopyOrMoveProgressCallback progress_callback) + base::WeakPtr<storage::CopyOrMoveHookDelegate> + copy_or_move_hook_delegate_weak_ptr) : CopyOrMoveImpl(operation_runner, operation_type, src_url, dest_url, options, - progress_callback), + copy_or_move_hook_delegate_weak_ptr), file_system_context_(file_system_context), reader_(std::move(reader)), writer_(std::move(writer)), @@ -866,7 +878,7 @@ CopyOrMoveOperationDelegate::CopyOrMoveOperationDelegate( OperationType operation_type, CopyOrMoveOptionSet options, ErrorBehavior error_behavior, - const CopyOrMoveProgressCallback& progress_callback, + std::unique_ptr<CopyOrMoveHookDelegate> copy_or_move_hook_delegate, StatusCallback callback) : RecursiveOperationDelegate(file_system_context), src_root_(src_root), @@ -874,8 +886,9 @@ CopyOrMoveOperationDelegate::CopyOrMoveOperationDelegate( operation_type_(operation_type), options_(options), error_behavior_(error_behavior), - progress_callback_(progress_callback), + copy_or_move_hook_delegate_(std::move(copy_or_move_hook_delegate)), callback_(std::move(callback)) { + DCHECK(copy_or_move_hook_delegate_); // Force same_file_system_ = false if options include kForceCrossFilesystem. same_file_system_ = !options.Has( @@ -915,16 +928,40 @@ void CopyOrMoveOperationDelegate::RunRecursively() { // 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_, error_behavior_, std::move(callback_)); + StartRecursiveOperation( + src_root_, error_behavior_, + base::BindOnce(&CopyOrMoveOperationDelegate::FinishOperation, + weak_factory_.GetWeakPtr())); +} + +void CopyOrMoveOperationDelegate::FinishOperation(base::File::Error error) { + // We post the callback as a task to ensure that other posted tasks are + // completed before finishing the operation. + PostTask(base::BindOnce(std::move(callback_), error)); } void CopyOrMoveOperationDelegate::ProcessFile(const FileSystemURL& src_url, StatusCallback callback) { - FileSystemURL dest_url = CreateDestURL(src_url); + const FileSystemURL dest_url = CreateDestURL(src_url); + + PostTask(base::BindOnce( + &CopyOrMoveHookDelegate::OnBeginProcessFile, + copy_or_move_hook_delegate_->AsWeakPtr(), src_url, dest_url, + base::BindOnce(&CopyOrMoveOperationDelegate::DoProcessFile, + weak_factory_.GetWeakPtr(), src_url, dest_url, + std::move(callback)))); +} - if (!progress_callback_.is_null()) { - progress_callback_.Run(FileSystemOperation::CopyOrMoveProgressType::kBegin, - src_url, dest_url, 0); +void CopyOrMoveOperationDelegate::DoProcessFile(const FileSystemURL& src_url, + FileSystemURL dest_url, + StatusCallback callback, + base::File::Error error) { + if (error != base::File::FILE_OK) { + PostTask(base::BindOnce(&CopyOrMoveHookDelegate::OnError, + copy_or_move_hook_delegate_->AsWeakPtr(), src_url, + dest_url, error)); + std::move(callback).Run(error); + return; } std::unique_ptr<CopyOrMoveImpl> impl; @@ -935,7 +972,7 @@ void CopyOrMoveOperationDelegate::ProcessFile(const FileSystemURL& src_url, operation_type_ == OPERATION_MOVE)) { impl = std::make_unique<CopyOrMoveOnSameFileSystemImpl>( operation_runner(), operation_type_, src_url, dest_url, options_, - progress_callback_); + copy_or_move_hook_delegate_->AsWeakPtr()); } else { // Cross filesystem case. base::File::Error error = base::File::FILE_ERROR_FAILED; @@ -943,10 +980,9 @@ void CopyOrMoveOperationDelegate::ProcessFile(const FileSystemURL& src_url, file_system_context()->GetCopyOrMoveFileValidatorFactory( dest_root_.type(), &error); if (error != base::File::FILE_OK) { - if (!progress_callback_.is_null()) - progress_callback_.Run( - FileSystemOperation::CopyOrMoveProgressType::kError, src_url, - dest_url, 0); + PostTask(base::BindOnce(&CopyOrMoveHookDelegate::OnError, + copy_or_move_hook_delegate_->AsWeakPtr(), src_url, + dest_url, error)); std::move(callback).Run(error); return; @@ -962,14 +998,14 @@ void CopyOrMoveOperationDelegate::ProcessFile(const FileSystemURL& src_url, impl = std::make_unique<StreamCopyOrMoveImpl>( operation_runner(), file_system_context(), operation_type_, src_url, dest_url, options_, std::move(reader), std::move(writer), - progress_callback_); + copy_or_move_hook_delegate_->AsWeakPtr()); } } if (!impl) { impl = std::make_unique<SnapshotCopyOrMoveImpl>( operation_runner(), operation_type_, src_url, dest_url, options_, - validator_factory, progress_callback_); + validator_factory, copy_or_move_hook_delegate_->AsWeakPtr()); } } @@ -999,14 +1035,14 @@ void CopyOrMoveOperationDelegate::ProcessDirectory(const FileSystemURL& src_url, return; } - FileSystemURL dest_url = CreateDestURL(src_url); - - if (!progress_callback_.is_null()) { - progress_callback_.Run(FileSystemOperation::CopyOrMoveProgressType::kBegin, - src_url, dest_url, 0); - } + const FileSystemURL dest_url = CreateDestURL(src_url); - ProcessDirectoryInternal(src_url, dest_url, std::move(callback)); + PostTask(base::BindOnce( + &CopyOrMoveHookDelegate::OnBeginProcessDirectory, + copy_or_move_hook_delegate_->AsWeakPtr(), src_url, dest_url, + base::BindOnce(&CopyOrMoveOperationDelegate::ProcessDirectoryInternal, + weak_factory_.GetWeakPtr(), src_url, dest_url, + std::move(callback)))); } void CopyOrMoveOperationDelegate::PostProcessDirectory( @@ -1025,16 +1061,20 @@ void CopyOrMoveOperationDelegate::PostProcessDirectory( weak_factory_.GetWeakPtr(), src_url, std::move(callback))); } +void CopyOrMoveOperationDelegate::PostTask(base::OnceClosure closure) { + base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE, + std::move(closure)); +} + void CopyOrMoveOperationDelegate::OnCancel() { // Request to cancel all running Copy/Move file. for (auto& job : running_copy_set_) job.first->Cancel(); } -void CopyOrMoveOperationDelegate::DidCopyOrMoveFile( - StatusCallback callback, - CopyOrMoveImpl* impl, - base::File::Error error) { +void CopyOrMoveOperationDelegate::DidCopyOrMoveFile(StatusCallback callback, + CopyOrMoveImpl* impl, + base::File::Error error) { running_copy_set_.erase(impl); std::move(callback).Run(error); @@ -1053,13 +1093,23 @@ void CopyOrMoveOperationDelegate::DidTryRemoveDestRoot( return; } - ProcessDirectoryInternal(src_root_, dest_root_, std::move(callback)); + ProcessDirectoryInternal(src_root_, dest_root_, std::move(callback), + base::File::FILE_OK); } void CopyOrMoveOperationDelegate::ProcessDirectoryInternal( const FileSystemURL& src_url, const FileSystemURL& dest_url, - StatusCallback callback) { + StatusCallback callback, + base::File::Error error) { + if (error != base::File::FILE_OK) { + PostTask(base::BindOnce(&CopyOrMoveHookDelegate::OnError, + copy_or_move_hook_delegate_->AsWeakPtr(), src_url, + dest_url, error)); + std::move(callback).Run(error); + return; + } + // If operation_type == Move we may need to record directories and // restore directory timestamps in the end, though it may have // negative performance impact. @@ -1076,10 +1126,14 @@ void CopyOrMoveOperationDelegate::DidCreateDirectory( const FileSystemURL& dest_url, StatusCallback callback, base::File::Error error) { - if (!progress_callback_.is_null() && error == base::File::FILE_OK) { - progress_callback_.Run( - FileSystemOperation::CopyOrMoveProgressType::kEndCopy, src_url, - dest_url, 0); + if (error == base::File::FILE_OK) { + PostTask(base::BindOnce(&CopyOrMoveHookDelegate::OnEndCopy, + copy_or_move_hook_delegate_->AsWeakPtr(), src_url, + dest_url)); + } else { + PostTask(base::BindOnce(&CopyOrMoveHookDelegate::OnError, + copy_or_move_hook_delegate_->AsWeakPtr(), src_url, + dest_url, error)); } std::move(callback).Run(error); @@ -1130,17 +1184,14 @@ void CopyOrMoveOperationDelegate::DidRemoveSourceForMove( const FileSystemURL& src_url, StatusCallback callback, base::File::Error error) { - if (!progress_callback_.is_null()) { - if (error == base::File::FILE_OK || - error == base::File::FILE_ERROR_NOT_FOUND) { - progress_callback_.Run( - FileSystemOperation::CopyOrMoveProgressType::kEndRemoveSource, - src_url, FileSystemURL(), 0); - } else { - progress_callback_.Run( - FileSystemOperation::CopyOrMoveProgressType::kError, src_url, - FileSystemURL(), 0); - } + if (error == base::File::FILE_OK || + error == base::File::FILE_ERROR_NOT_FOUND) { + PostTask(base::BindOnce(&CopyOrMoveHookDelegate::OnEndRemoveSource, + copy_or_move_hook_delegate_->AsWeakPtr(), src_url)); + } else { + PostTask(base::BindOnce(&CopyOrMoveHookDelegate::OnError, + copy_or_move_hook_delegate_->AsWeakPtr(), src_url, + FileSystemURL(), error)); } std::move(callback).Run(error); } diff --git a/chromium/storage/browser/file_system/copy_or_move_operation_delegate.h b/chromium/storage/browser/file_system/copy_or_move_operation_delegate.h index ec3a3db7965..eb87c43226b 100644 --- a/chromium/storage/browser/file_system/copy_or_move_operation_delegate.h +++ b/chromium/storage/browser/file_system/copy_or_move_operation_delegate.h @@ -10,9 +10,12 @@ #include <map> #include <memory> +#include "base/callback_forward.h" #include "base/component_export.h" #include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" #include "base/time/time.h" +#include "storage/browser/file_system/copy_or_move_hook_delegate.h" #include "storage/browser/file_system/recursive_operation_delegate.h" namespace net { @@ -31,8 +34,7 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) CopyOrMoveOperationDelegate : public RecursiveOperationDelegate { public: class CopyOrMoveImpl; - using CopyOrMoveProgressCallback = - FileSystemOperation::CopyOrMoveProgressCallback; + using CopyOrMoveOptionSet = FileSystemOperation::CopyOrMoveOptionSet; using ErrorBehavior = FileSystemOperation::ErrorBehavior; @@ -95,7 +97,7 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) CopyOrMoveOperationDelegate OperationType operation_type, CopyOrMoveOptionSet options, ErrorBehavior error_behavior, - const CopyOrMoveProgressCallback& progress_callback, + std::unique_ptr<CopyOrMoveHookDelegate> copy_or_move_hook_delegate, StatusCallback callback); CopyOrMoveOperationDelegate(const CopyOrMoveOperationDelegate&) = delete; @@ -118,17 +120,24 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) CopyOrMoveOperationDelegate error_url_for_test_ = url; } + void PostTask(base::OnceClosure closure); + protected: void OnCancel() override; private: + void DoProcessFile(const FileSystemURL& url, + FileSystemURL dest, + StatusCallback callback, + base::File::Error error); void DidCopyOrMoveFile(StatusCallback callback, CopyOrMoveImpl* impl, base::File::Error error); void DidTryRemoveDestRoot(StatusCallback callback, base::File::Error error); void ProcessDirectoryInternal(const FileSystemURL& src_url, const FileSystemURL& dest_url, - StatusCallback callback); + StatusCallback callback, + base::File::Error error); void DidCreateDirectory(const FileSystemURL& src_url, const FileSystemURL& dest_url, StatusCallback callback, @@ -144,6 +153,8 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) CopyOrMoveOperationDelegate StatusCallback callback, base::File::Error error); + void FinishOperation(base::File::Error error); + FileSystemURL CreateDestURL(const FileSystemURL& src_url) const; #if DCHECK_IS_ON() @@ -156,7 +167,7 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) CopyOrMoveOperationDelegate const OperationType operation_type_; const CopyOrMoveOptionSet options_; const ErrorBehavior error_behavior_; - const CopyOrMoveProgressCallback progress_callback_; + std::unique_ptr<CopyOrMoveHookDelegate> copy_or_move_hook_delegate_; StatusCallback callback_; FileSystemURL error_url_for_test_; diff --git a/chromium/storage/browser/file_system/copy_or_move_operation_delegate_unittest.cc b/chromium/storage/browser/file_system/copy_or_move_operation_delegate_unittest.cc index a5c5349b6a5..5718abe4063 100644 --- a/chromium/storage/browser/file_system/copy_or_move_operation_delegate_unittest.cc +++ b/chromium/storage/browser/file_system/copy_or_move_operation_delegate_unittest.cc @@ -6,6 +6,7 @@ #include <stdint.h> #include <map> +#include <memory> #include <string> #include <utility> #include <vector> @@ -22,9 +23,12 @@ #include "base/run_loop.h" #include "base/task/single_thread_task_runner.h" #include "base/test/task_environment.h" +#include "base/threading/thread.h" #include "base/threading/thread_task_runner_handle.h" +#include "base/time/time.h" #include "components/services/filesystem/public/mojom/types.mojom.h" #include "storage/browser/file_system/copy_or_move_file_validator.h" +#include "storage/browser/file_system/copy_or_move_hook_delegate.h" #include "storage/browser/file_system/copy_or_move_operation_delegate.h" #include "storage/browser/file_system/file_stream_reader.h" #include "storage/browser/file_system/file_stream_writer.h" @@ -38,6 +42,7 @@ #include "storage/browser/test/file_system_test_file_set.h" #include "storage/browser/test/mock_quota_manager.h" #include "storage/browser/test/mock_quota_manager_proxy.h" +#include "storage/browser/test/mock_special_storage_policy.h" #include "storage/browser/test/test_file_system_backend.h" #include "storage/browser/test/test_file_system_context.h" #include "storage/common/file_system/file_system_mount_option.h" @@ -49,10 +54,10 @@ namespace storage { -using FileEntryList = FileSystemOperation::FileEntryList; - namespace { +using FileEntryList = FileSystemOperation::FileEntryList; + constexpr int64_t kDefaultFileSize = 10; void ExpectOk(const GURL& origin_url, @@ -116,26 +121,102 @@ class TestValidatorFactory : public CopyOrMoveFileValidatorFactory { }; }; -// Records CopyOrMoveProgressCallback invocations. -struct ProgressRecord { - FileSystemOperation::CopyOrMoveProgressType type; - FileSystemURL source_url; - FileSystemURL dest_url; - int64_t size; -}; +class CopyOrMoveRecordDelegate : public CopyOrMoveHookDelegate { + public: + // Records method invocations. + struct ProgressRecord { + enum class Type { + kBeginFile = 0, + kBeginDirectory, + kProgress, + kEndCopy, + kEndMove, + kEndRemoveSource, + kError, + } type; + FileSystemURL source_url; + FileSystemURL dest_url; + int64_t size; + base::File::Error error; + }; -void RecordProgressCallback(std::vector<ProgressRecord>* records, - FileSystemOperation::CopyOrMoveProgressType type, - const FileSystemURL& source_url, - const FileSystemURL& dest_url, - int64_t size) { - ProgressRecord record; - record.type = type; - record.source_url = source_url; - record.dest_url = dest_url; - record.size = size; - records->push_back(record); -} + using StatusCallback = FileSystemOperation::StatusCallback; + + explicit CopyOrMoveRecordDelegate(std::vector<ProgressRecord>* records) + : records_(records) { + DCHECK(records_); + } + + ~CopyOrMoveRecordDelegate() override = default; + + void OnBeginProcessFile(const FileSystemURL& source_url, + const FileSystemURL& destination_url, + StatusCallback callback) override { + AddRecord(ProgressRecord::Type::kBeginFile, source_url, destination_url, 0, + base::File::FILE_OK); + + std::move(callback).Run(base::File::FILE_OK); + } + + void OnBeginProcessDirectory(const FileSystemURL& source_url, + const FileSystemURL& destination_url, + StatusCallback callback) override { + AddRecord(ProgressRecord::Type::kBeginDirectory, source_url, + destination_url, 0, base::File::FILE_OK); + + std::move(callback).Run(base::File::FILE_OK); + } + + void OnProgress(const FileSystemURL& source_url, + const FileSystemURL& destination_url, + int64_t size) override { + AddRecord(ProgressRecord::Type::kProgress, source_url, destination_url, + size, base::File::FILE_OK); + } + + void OnError(const FileSystemURL& source_url, + const FileSystemURL& destination_url, + base::File::Error error) override { + AddRecord(ProgressRecord::Type::kError, source_url, destination_url, 0, + error); + } + + void OnEndCopy(const FileSystemURL& source_url, + const FileSystemURL& destination_url) override { + AddRecord(ProgressRecord::Type::kEndCopy, source_url, destination_url, 0, + base::File::FILE_OK); + } + + void OnEndMove(const FileSystemURL& source_url, + const FileSystemURL& destination_url) override { + AddRecord(ProgressRecord::Type::kEndMove, source_url, destination_url, 0, + base::File::FILE_OK); + } + + void OnEndRemoveSource(const FileSystemURL& source_url) override { + AddRecord(ProgressRecord::Type::kEndRemoveSource, source_url, + FileSystemURL(), 0, base::File::FILE_OK); + } + + private: + void AddRecord(ProgressRecord::Type type, + const FileSystemURL& source_url, + const FileSystemURL& dest_url, + int64_t size, + base::File::Error error) { + ProgressRecord record; + record.type = type; + record.source_url = source_url; + record.dest_url = dest_url; + record.size = size; + record.error = error; + records_->push_back(record); + } + + // Raw ptr safe here, because the records will be destructed at end of test, + // i.e., after the CopyOrMove operation has finished. + base::raw_ptr<std::vector<ProgressRecord>> records_; +}; void RecordFileProgressCallback(std::vector<int64_t>* records, int64_t progress) { @@ -183,6 +264,8 @@ class CopyOrMoveOperationTestHelper { : origin_(url::Origin::Create(GURL(origin))), src_type_(src_type), dest_type_(dest_type), + special_storage_policy_( + base::MakeRefCounted<MockSpecialStoragePolicy>()), task_environment_(base::test::TaskEnvironment::MainThreadType::IO) {} CopyOrMoveOperationTestHelper(const CopyOrMoveOperationTestHelper&) = delete; @@ -205,13 +288,12 @@ class CopyOrMoveOperationTestHelper { ASSERT_TRUE(base_.CreateUniqueTempDir()); base::FilePath base_dir = base_.GetPath(); quota_manager_ = base::MakeRefCounted<MockQuotaManager>( - false /* is_incognito */, base_dir, - base::ThreadTaskRunnerHandle::Get().get(), - nullptr /* special storage policy */); + false /* is_incognito */, base_dir, base::ThreadTaskRunnerHandle::Get(), + special_storage_policy_); quota_manager_proxy_ = base::MakeRefCounted<MockQuotaManagerProxy>( quota_manager_.get(), base::ThreadTaskRunnerHandle::Get()); file_system_context_ = - CreateFileSystemContextForTesting(quota_manager_proxy_.get(), base_dir); + CreateFileSystemContextForTesting(quota_manager_proxy_, base_dir); // Prepare the origin's root directory. FileSystemBackend* backend = @@ -274,26 +356,28 @@ class CopyOrMoveOperationTestHelper { return AsyncFileTestHelper::Copy(file_system_context_.get(), src, dest); } - base::File::Error CopyWithProgress( + base::File::Error CopyWithHookDelegate( const FileSystemURL& src, const FileSystemURL& dest, - const AsyncFileTestHelper::CopyOrMoveProgressCallback& - progress_callback) { - return AsyncFileTestHelper::CopyWithProgress(file_system_context_.get(), - src, dest, progress_callback); + std::unique_ptr<storage::CopyOrMoveHookDelegate> + copy_or_move_hook_delegate) { + return AsyncFileTestHelper::CopyWithHookDelegate( + file_system_context_.get(), src, dest, + std::move(copy_or_move_hook_delegate)); } base::File::Error Move(const FileSystemURL& src, const FileSystemURL& dest) { return AsyncFileTestHelper::Move(file_system_context_.get(), src, dest); } - base::File::Error MoveWithProgress( + base::File::Error MoveWithHookDelegate( const FileSystemURL& src, const FileSystemURL& dest, - const AsyncFileTestHelper::CopyOrMoveProgressCallback& - progress_callback) { - return AsyncFileTestHelper::MoveWithProgress(file_system_context_.get(), - src, dest, progress_callback); + std::unique_ptr<storage::CopyOrMoveHookDelegate> + copy_or_move_hook_delegate) { + return AsyncFileTestHelper::MoveWithHookDelegate( + file_system_context_.get(), src, dest, + std::move(copy_or_move_hook_delegate)); } base::File::Error SetUpTestCaseFiles( @@ -397,13 +481,15 @@ class CopyOrMoveOperationTestHelper { } private: - base::ScopedTempDir base_; - const url::Origin origin_; const FileSystemType src_type_; const FileSystemType dest_type_; + scoped_refptr<MockSpecialStoragePolicy> special_storage_policy_; + + base::ScopedTempDir base_; base::test::TaskEnvironment task_environment_; + scoped_refptr<FileSystemContext> file_system_context_; scoped_refptr<MockQuotaManagerProxy> quota_manager_proxy_; scoped_refptr<MockQuotaManager> quota_manager_; @@ -578,8 +664,8 @@ TEST(LocalFileSystemCopyOrMoveOperationTest, CopyDirectory) { // Copy it. ASSERT_EQ(base::File::FILE_OK, - helper.CopyWithProgress( - src, dest, AsyncFileTestHelper::CopyOrMoveProgressCallback())); + helper.CopyWithHookDelegate( + src, dest, std::make_unique<CopyOrMoveHookDelegate>())); // Verify. ASSERT_TRUE(helper.DirectoryExists(src)); @@ -659,7 +745,7 @@ TEST(LocalFileSystemCopyOrMoveOperationTest, }; helper.VerifyTestCaseFiles(dest, kMoveDirResultCases, - base::size(kMoveDirResultCases)); + std::size(kMoveDirResultCases)); } TEST(LocalFileSystemCopyOrMoveOperationTest, CopySingleFileNoValidator) { @@ -679,7 +765,7 @@ TEST(LocalFileSystemCopyOrMoveOperationTest, CopySingleFileNoValidator) { ASSERT_EQ(base::File::FILE_ERROR_SECURITY, helper.Copy(src, dest)); } -TEST(LocalFileSystemCopyOrMoveOperationTest, CopyProgressCallback) { +TEST(LocalFileSystemCopyOrMoveOperationTest, CopyProgress) { CopyOrMoveOperationTestHelper helper("http://foo", kFileSystemTypeTemporary, kFileSystemTypePersistent); helper.SetUp(); @@ -693,14 +779,22 @@ TEST(LocalFileSystemCopyOrMoveOperationTest, CopyProgressCallback) { helper.SetUpTestCaseFiles(src, kRegularFileSystemTestCases, kRegularFileSystemTestCaseSize)); - std::vector<ProgressRecord> records; + std::vector<CopyOrMoveRecordDelegate::ProgressRecord> records; ASSERT_EQ( base::File::FILE_OK, - helper.CopyWithProgress(src, dest, - base::BindRepeating(&RecordProgressCallback, - base::Unretained(&records)))); + helper.CopyWithHookDelegate( + src, dest, std::make_unique<CopyOrMoveRecordDelegate>(&records))); + + // Verify that for `src` kBeginFile is called. + // This behavior is expected, because for the src entry, ProcessFile is always + // called independent of whether it is a directory or not. + // Note: This might change if the behavior of RecursiveOperationDelegate is + // changed. + EXPECT_EQ(CopyOrMoveRecordDelegate::ProgressRecord::Type::kBeginFile, + records[0].type); + EXPECT_EQ(dest, records[0].dest_url); - // Verify progress callback. + // Verify progress records. for (size_t i = 0; i < kRegularFileSystemTestCaseSize; ++i) { const FileSystemTestCaseRecord& test_case = kRegularFileSystemTestCases[i]; @@ -725,10 +819,15 @@ TEST(LocalFileSystemCopyOrMoveOperationTest, CopyProgressCallback) { ASSERT_NE(end_index, records.size()); ASSERT_NE(begin_index, end_index); - EXPECT_EQ(FileSystemOperation::CopyOrMoveProgressType::kBegin, - records[begin_index].type); + if (test_case.is_directory) { + EXPECT_EQ(CopyOrMoveRecordDelegate::ProgressRecord::Type::kBeginDirectory, + records[begin_index].type); + } else { + EXPECT_EQ(CopyOrMoveRecordDelegate::ProgressRecord::Type::kBeginFile, + records[begin_index].type); + } EXPECT_EQ(dest_url, records[begin_index].dest_url); - EXPECT_EQ(FileSystemOperation::CopyOrMoveProgressType::kEndCopy, + EXPECT_EQ(CopyOrMoveRecordDelegate::ProgressRecord::Type::kEndCopy, records[end_index].type); EXPECT_EQ(dest_url, records[end_index].dest_url); @@ -740,7 +839,7 @@ TEST(LocalFileSystemCopyOrMoveOperationTest, CopyProgressCallback) { int64_t current_size = 0; for (size_t j = begin_index + 1; j < end_index; ++j) { if (records[j].source_url == src_url) { - EXPECT_EQ(FileSystemOperation::CopyOrMoveProgressType::kProgress, + EXPECT_EQ(CopyOrMoveRecordDelegate::ProgressRecord::Type::kProgress, records[j].type); EXPECT_EQ(dest_url, records[j].dest_url); EXPECT_GE(records[j].size, current_size); @@ -751,7 +850,7 @@ TEST(LocalFileSystemCopyOrMoveOperationTest, CopyProgressCallback) { } } -TEST(LocalFileSystemCopyOrMoveOperationTest, MoveProgressCallback) { +TEST(LocalFileSystemCopyOrMoveOperationTest, MoveProgress) { CopyOrMoveOperationTestHelper helper("http://foo", kFileSystemTypeTemporary, kFileSystemTypePersistent); helper.SetUp(); @@ -765,14 +864,13 @@ TEST(LocalFileSystemCopyOrMoveOperationTest, MoveProgressCallback) { helper.SetUpTestCaseFiles(src, kRegularFileSystemTestCases, kRegularFileSystemTestCaseSize)); - std::vector<ProgressRecord> records; + std::vector<CopyOrMoveRecordDelegate::ProgressRecord> records; ASSERT_EQ( base::File::FILE_OK, - helper.MoveWithProgress(src, dest, - base::BindRepeating(&RecordProgressCallback, - base::Unretained(&records)))); + helper.MoveWithHookDelegate( + src, dest, std::make_unique<CopyOrMoveRecordDelegate>(&records))); - // Verify progress callback. + // Verify progress records. for (size_t i = 0; i < kRegularFileSystemTestCaseSize; ++i) { const FileSystemTestCaseRecord& test_case = kRegularFileSystemTestCases[i]; @@ -799,27 +897,28 @@ TEST(LocalFileSystemCopyOrMoveOperationTest, MoveProgressCallback) { if (test_case.is_directory) { // A directory move starts with kBegin and kEndCopy. - EXPECT_EQ(FileSystemOperation::CopyOrMoveProgressType::kBegin, + EXPECT_EQ(CopyOrMoveRecordDelegate::ProgressRecord::Type::kBeginDirectory, records[begin_index].type); EXPECT_EQ(dest_url, records[begin_index].dest_url); - EXPECT_EQ(FileSystemOperation::CopyOrMoveProgressType::kEndCopy, + EXPECT_EQ(CopyOrMoveRecordDelegate::ProgressRecord::Type::kEndCopy, records[begin_index + 1].type); EXPECT_EQ(dest_url, records[begin_index + 1].dest_url); // A directory move ends with kEndRemoveSource, after the contents of the // directory has been copied. - EXPECT_EQ(FileSystemOperation::CopyOrMoveProgressType::kEndRemoveSource, - records[end_index].type); + EXPECT_EQ( + CopyOrMoveRecordDelegate::ProgressRecord::Type::kEndRemoveSource, + records[end_index].type); EXPECT_FALSE(records[end_index].dest_url.is_valid()); } else { - // A file move starts with kBegin. - EXPECT_EQ(FileSystemOperation::CopyOrMoveProgressType::kBegin, + // A file move starts with kBeginFile. + EXPECT_EQ(CopyOrMoveRecordDelegate::ProgressRecord::Type::kBeginFile, records[begin_index].type); EXPECT_EQ(dest_url, records[begin_index].dest_url); // PROGRESS event's size should be ascending order. int64_t current_size = 0; for (size_t j = begin_index + 1; j < end_index - 1; ++j) { if (records[j].source_url == src_url) { - EXPECT_EQ(FileSystemOperation::CopyOrMoveProgressType::kProgress, + EXPECT_EQ(CopyOrMoveRecordDelegate::ProgressRecord::Type::kProgress, records[j].type); EXPECT_EQ(dest_url, records[j].dest_url); EXPECT_GE(records[j].size, current_size); @@ -827,17 +926,18 @@ TEST(LocalFileSystemCopyOrMoveOperationTest, MoveProgressCallback) { } } // A file move ends with kEndCopy and kEndRemoveSource. - EXPECT_EQ(FileSystemOperation::CopyOrMoveProgressType::kEndCopy, + EXPECT_EQ(CopyOrMoveRecordDelegate::ProgressRecord::Type::kEndCopy, records[end_index - 1].type); EXPECT_EQ(dest_url, records[end_index - 1].dest_url); - EXPECT_EQ(FileSystemOperation::CopyOrMoveProgressType::kEndRemoveSource, - records[end_index].type); + EXPECT_EQ( + CopyOrMoveRecordDelegate::ProgressRecord::Type::kEndRemoveSource, + records[end_index].type); EXPECT_FALSE(records[end_index].dest_url.is_valid()); } } } -TEST(LocalFileSystemCopyOrMoveOperationTest, MoveFileLocalProgressCallback) { +TEST(LocalFileSystemCopyOrMoveOperationTest, MoveFileLocalProgress) { CopyOrMoveOperationTestHelper helper("http://foo", kFileSystemTypePersistent, kFileSystemTypePersistent); helper.SetUp(); @@ -848,22 +948,20 @@ TEST(LocalFileSystemCopyOrMoveOperationTest, MoveFileLocalProgressCallback) { // Set up a source file. ASSERT_EQ(base::File::FILE_OK, helper.CreateFile(src, 10)); - std::vector<ProgressRecord> records; + std::vector<CopyOrMoveRecordDelegate::ProgressRecord> records; ASSERT_EQ( base::File::FILE_OK, - helper.MoveWithProgress(src, dest, - base::BindRepeating(&RecordProgressCallback, - base::Unretained(&records)))); - - // There should be 2 records, for kBegin and kEndMove. No progress should be - // reported. - EXPECT_EQ(records.size(), (uint64_t)2); + helper.MoveWithHookDelegate( + src, dest, std::make_unique<CopyOrMoveRecordDelegate>(&records))); + // There should be 2 records, for kBeginFile and kEndMove. No progress should + // be reported. + EXPECT_EQ(records.size(), 2u); - EXPECT_EQ(FileSystemOperation::CopyOrMoveProgressType::kBegin, + EXPECT_EQ(CopyOrMoveRecordDelegate::ProgressRecord::Type::kBeginFile, records[0].type); EXPECT_EQ(src, records[0].source_url); EXPECT_EQ(dest, records[0].dest_url); - EXPECT_EQ(FileSystemOperation::CopyOrMoveProgressType::kEndMove, + EXPECT_EQ(CopyOrMoveRecordDelegate::ProgressRecord::Type::kEndMove, records[1].type); EXPECT_EQ(src, records[1].source_url); EXPECT_EQ(dest, records[1].dest_url); @@ -1116,7 +1214,7 @@ class CopyOrMoveOperationDelegateTestHelper { file_system_context_.get(), src, dest, CopyOrMoveOperationDelegate::OPERATION_COPY, options_, FileSystemOperation::ERROR_BEHAVIOR_ABORT, - FileSystemOperation::CopyOrMoveProgressCallback(), + std::make_unique<storage::CopyOrMoveHookDelegate>(), base::BindOnce(&AssignAndQuit, &run_loop, base::Unretained(&result))); if (error_url_.is_valid()) { copy_or_move_operation_delegate.SetErrorUrlForTest(error_url_); @@ -1134,7 +1232,7 @@ class CopyOrMoveOperationDelegateTestHelper { file_system_context_.get(), src, dest, CopyOrMoveOperationDelegate::OPERATION_MOVE, options_, FileSystemOperation::ERROR_BEHAVIOR_ABORT, - FileSystemOperation::CopyOrMoveProgressCallback(), + std::make_unique<storage::CopyOrMoveHookDelegate>(), base::BindOnce(&AssignAndQuit, &run_loop, base::Unretained(&result))); if (error_url_.is_valid()) { copy_or_move_operation_delegate.SetErrorUrlForTest(error_url_); diff --git a/chromium/storage/browser/file_system/dragged_file_util_unittest.cc b/chromium/storage/browser/file_system/dragged_file_util_unittest.cc index 22681a159b1..a226ea684f5 100644 --- a/chromium/storage/browser/file_system/dragged_file_util_unittest.cc +++ b/chromium/storage/browser/file_system/dragged_file_util_unittest.cc @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "storage/browser/file_system/dragged_file_util.h" + #include <stddef.h> #include <map> @@ -12,7 +14,6 @@ #include "base/check.h" #include "base/containers/queue.h" -#include "base/cxx17_backports.h" #include "base/files/file_enumerator.h" #include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" @@ -20,7 +21,6 @@ #include "base/time/time.h" #include "build/build_config.h" #include "components/services/filesystem/public/mojom/types.mojom.h" -#include "storage/browser/file_system/dragged_file_util.h" #include "storage/browser/file_system/file_system_context.h" #include "storage/browser/file_system/file_system_operation_context.h" #include "storage/browser/file_system/isolated_context.h" @@ -260,7 +260,7 @@ class DraggedFileUtilTest : public testing::Test { // to simulate a drop with multiple directories. if (toplevel_root_map_.find(toplevel) == toplevel_root_map_.end()) { base::FilePath root = root_path().Append( - kRootPaths[(root_path_index++) % base::size(kRootPaths)]); + kRootPaths[(root_path_index++) % std::size(kRootPaths)]); toplevel_root_map_[toplevel] = root; toplevels.AddPath(root.Append(path), nullptr); } @@ -316,7 +316,7 @@ TEST_F(DraggedFileUtilTest, UnregisteredPathsTest) { {false, FILE_PATH_LITERAL("bar"), 20}, }; - for (size_t i = 0; i < base::size(kUnregisteredCases); ++i) { + for (size_t i = 0; i < std::size(kUnregisteredCases); ++i) { SCOPED_TRACE(testing::Message() << "Creating kUnregisteredCases " << i); const FileSystemTestCaseRecord& test_case = kUnregisteredCases[i]; @@ -331,7 +331,7 @@ TEST_F(DraggedFileUtilTest, UnregisteredPathsTest) { ASSERT_EQ(test_case.is_directory, info.is_directory); } - for (size_t i = 0; i < base::size(kUnregisteredCases); ++i) { + for (size_t i = 0; i < std::size(kUnregisteredCases); ++i) { SCOPED_TRACE(testing::Message() << "Creating kUnregisteredCases " << i); const FileSystemTestCaseRecord& test_case = kUnregisteredCases[i]; FileSystemURL url = GetFileSystemURL(base::FilePath(test_case.path)); diff --git a/chromium/storage/browser/file_system/external_mount_points_unittest.cc b/chromium/storage/browser/file_system/external_mount_points_unittest.cc index c4f0b7fe4e9..16c3b81acf8 100644 --- a/chromium/storage/browser/file_system/external_mount_points_unittest.cc +++ b/chromium/storage/browser/file_system/external_mount_points_unittest.cc @@ -8,7 +8,6 @@ #include <string> -#include "base/cxx17_backports.h" #include "base/files/file_path.h" #include "storage/browser/file_system/file_system_url.h" #include "storage/common/file_system/file_system_mount_option.h" @@ -121,7 +120,7 @@ TEST(ExternalMountPointsTest, AddMountPoint) { } // Test that final mount point presence state is as expected. - for (size_t i = 0; i < base::size(kTestCases); ++i) { + for (size_t i = 0; i < std::size(kTestCases); ++i) { base::FilePath found_path; EXPECT_EQ(kTestCases[i].registered_path != nullptr, mount_points->GetRegisteredPath(kTestCases[i].name, &found_path)) @@ -329,7 +328,7 @@ TEST(ExternalMountPointsTest, CreateCrackedFileSystemURL) { #endif }; - for (size_t i = 0; i < base::size(kTestCases); ++i) { + for (size_t i = 0; i < std::size(kTestCases); ++i) { FileSystemURL cracked = mount_points->CreateCrackedFileSystemURL( kTestStorageKey, kFileSystemTypeExternal, base::FilePath(kTestCases[i].path)); @@ -412,7 +411,7 @@ TEST(ExternalMountPointsTest, CrackVirtualPath) { #endif }; - for (size_t i = 0; i < base::size(kTestCases); ++i) { + for (size_t i = 0; i < std::size(kTestCases); ++i) { std::string cracked_name; FileSystemType cracked_type; std::string cracked_id; diff --git a/chromium/storage/browser/file_system/file_stream_reader_test.h b/chromium/storage/browser/file_system/file_stream_reader_test.h index 4517ced6992..0bb8057abe4 100644 --- a/chromium/storage/browser/file_system/file_stream_reader_test.h +++ b/chromium/storage/browser/file_system/file_stream_reader_test.h @@ -10,6 +10,7 @@ #include "base/task/single_thread_task_runner.h" #include "base/test/task_environment.h" #include "base/threading/thread.h" +#include "base/time/time.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" #include "net/base/test_completion_callback.h" @@ -52,7 +53,10 @@ class FileStreamReaderTest : public testing::Test { static void NeverCalled(int unused) { ADD_FAILURE(); } - private: + protected: + // Must be listed before base::test::TaskEnvironment. + base::ScopedTempDir dir_; + // FileSystemContext queries QuotaDatabase, and even with MockQuotaManager // (which really fakes parts of QuotaManagerImpl), a thread pool is created // that requires TaskEnvironment. diff --git a/chromium/storage/browser/file_system/file_stream_writer_test.h b/chromium/storage/browser/file_system/file_stream_writer_test.h index 9f4fcbdd3c6..21cda9254d2 100644 --- a/chromium/storage/browser/file_system/file_stream_writer_test.h +++ b/chromium/storage/browser/file_system/file_stream_writer_test.h @@ -39,7 +39,10 @@ class FileStreamWriterTest : public testing::Test { static void NeverCalled(int unused) { ADD_FAILURE(); } - private: + protected: + // Must be listed before base::test::TaskEnvironment. + base::ScopedTempDir dir_; + base::test::TaskEnvironment task_environment_{ base::test::TaskEnvironment::MainThreadType::IO}; }; diff --git a/chromium/storage/browser/file_system/file_system_context.cc b/chromium/storage/browser/file_system/file_system_context.cc index 808295dbe43..afb79af2dea 100644 --- a/chromium/storage/browser/file_system/file_system_context.cc +++ b/chromium/storage/browser/file_system/file_system_context.cc @@ -154,6 +154,7 @@ scoped_refptr<FileSystemContext> FileSystemContext::Create( std::vector<std::unique_ptr<FileSystemBackend>> additional_backends, const std::vector<URLRequestAutoMountHandler>& auto_mount_handlers, const base::FilePath& partition_path, + const base::FilePath& bucket_base_path, const FileSystemOptions& options) { bool force_override_incognito = base::FeatureList::IsEnabled( features::kIncognitoFileSystemContextForTesting); @@ -168,8 +169,8 @@ scoped_refptr<FileSystemContext> FileSystemContext::Create( std::move(io_task_runner), std::move(file_task_runner), std::move(external_mount_points), std::move(special_storage_policy), std::move(quota_manager_proxy), std::move(additional_backends), - auto_mount_handlers, partition_path, maybe_overridden_options, - base::PassKey<FileSystemContext>()); + auto_mount_handlers, partition_path, bucket_base_path, + maybe_overridden_options, base::PassKey<FileSystemContext>()); context->Initialize(); return context; } @@ -183,6 +184,7 @@ FileSystemContext::FileSystemContext( std::vector<std::unique_ptr<FileSystemBackend>> additional_backends, const std::vector<URLRequestAutoMountHandler>& auto_mount_handlers, const base::FilePath& partition_path, + const base::FilePath& bucket_base_path, const FileSystemOptions& options, base::PassKey<FileSystemContext>) : base::RefCountedDeleteOnSequence<FileSystemContext>(io_task_runner), @@ -200,6 +202,7 @@ FileSystemContext::FileSystemContext( quota_manager_proxy_.get(), default_file_task_runner_.get(), partition_path, + bucket_base_path, special_storage_policy, options, env_override_.get())), @@ -208,6 +211,7 @@ FileSystemContext::FileSystemContext( plugin_private_backend_(std::make_unique<PluginPrivateFileSystemBackend>( default_file_task_runner_, partition_path, + bucket_base_path, std::move(special_storage_policy), options, env_override_.get())), @@ -215,6 +219,7 @@ FileSystemContext::FileSystemContext( auto_mount_handlers_(auto_mount_handlers), external_mount_points_(std::move(external_mount_points)), partition_path_(partition_path), + bucket_base_path_(bucket_base_path), is_incognito_(options.is_incognito()), operation_runner_(std::make_unique<FileSystemOperationRunner>( base::PassKey<FileSystemContext>(), @@ -292,7 +297,7 @@ bool FileSystemContext::DeleteDataForStorageKeyOnFileTaskRunner( if (!backend->GetQuotaUtil()) continue; if (backend->GetQuotaUtil()->DeleteStorageKeyDataOnFileTaskRunner( - this, quota_manager_proxy(), storage_key, + this, quota_manager_proxy().get(), storage_key, type_backend_pair.first) != base::File::FILE_OK) { // Continue the loop, but record the failure. success = false; @@ -553,7 +558,7 @@ void FileSystemContext::DeleteFileSystem(const blink::StorageKey& storage_key, base::BindOnce( &FileSystemQuotaUtil::DeleteStorageKeyDataOnFileTaskRunner, base::Unretained(backend->GetQuotaUtil()), base::RetainedRef(this), - base::Unretained(quota_manager_proxy()), storage_key, type), + base::Unretained(quota_manager_proxy().get()), storage_key, type), std::move(callback)); } diff --git a/chromium/storage/browser/file_system/file_system_context.h b/chromium/storage/browser/file_system/file_system_context.h index 15b2f72ef95..a60757851a3 100644 --- a/chromium/storage/browser/file_system/file_system_context.h +++ b/chromium/storage/browser/file_system/file_system_context.h @@ -135,6 +135,7 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) FileSystemContext std::vector<std::unique_ptr<FileSystemBackend>> additional_backends, const std::vector<URLRequestAutoMountHandler>& auto_mount_handlers, const base::FilePath& partition_path, + const base::FilePath& bucket_base_path, const FileSystemOptions& options); // Exposed for base::MakeRefCounted(). Instances should be obtained from the @@ -148,6 +149,7 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) FileSystemContext std::vector<std::unique_ptr<FileSystemBackend>> additional_backends, const std::vector<URLRequestAutoMountHandler>& auto_mount_handlers, const base::FilePath& partition_path, + const base::FilePath& bucket_base_path, const FileSystemOptions& options, base::PassKey<FileSystemContext>); @@ -162,8 +164,8 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) FileSystemContext const blink::StorageKey& storage_key, FileSystemType type); - QuotaManagerProxy* quota_manager_proxy() const { - return quota_manager_proxy_.get(); + const scoped_refptr<QuotaManagerProxy>& quota_manager_proxy() const { + return quota_manager_proxy_; } // Discards inflight operations in the operation runner. @@ -305,6 +307,8 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) FileSystemContext const base::FilePath& partition_path() const { return partition_path_; } + const base::FilePath& bucket_base_path() const { return bucket_base_path_; } + // Same as `CrackFileSystemURL`, but cracks FileSystemURL created from `url` // and `storage_key`. FileSystemURL CrackURL(const GURL& url, @@ -346,6 +350,12 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) FileSystemContext bool is_incognito() { return is_incognito_; } + // TODO(com/1231162): Remove this. Used only by test code and to migrate media + // license data to the new backend. + PluginPrivateFileSystemBackend* plugin_private_backend() const { + return plugin_private_backend_.get(); + } + private: // For CreateFileSystemOperation. friend class FileSystemOperationRunner; @@ -424,11 +434,6 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) FileSystemContext return sandbox_backend_.get(); } - // Used only by test code. - PluginPrivateFileSystemBackend* plugin_private_backend() const { - return plugin_private_backend_.get(); - } - // Override the default leveldb Env with `env_override_` if set. std::unique_ptr<leveldb::Env> env_override_; @@ -470,6 +475,10 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) FileSystemContext // The base path of the storage partition for this context. const base::FilePath partition_path_; + // The base path of the file directory where StorageBucket data is stored for + // this context. + const base::FilePath bucket_base_path_; + const bool is_incognito_; const std::unique_ptr<FileSystemOperationRunner> operation_runner_; diff --git a/chromium/storage/browser/file_system/file_system_context_unittest.cc b/chromium/storage/browser/file_system/file_system_context_unittest.cc index 339ba57520b..8ada21dd10a 100644 --- a/chromium/storage/browser/file_system/file_system_context_unittest.cc +++ b/chromium/storage/browser/file_system/file_system_context_unittest.cc @@ -6,7 +6,6 @@ #include <stddef.h> -#include "base/cxx17_backports.h" #include "base/files/scoped_temp_dir.h" #include "base/memory/scoped_refptr.h" #include "base/strings/stringprintf.h" @@ -68,7 +67,7 @@ class FileSystemContextTest : public testing::Test { storage_policy_, mock_quota_manager_->proxy(), std::vector<std::unique_ptr<FileSystemBackend>>(), std::vector<URLRequestAutoMountHandler>(), data_dir_.GetPath(), - CreateAllowFileAccessOptions()); + data_dir_.GetPath(), CreateAllowFileAccessOptions()); } // Verifies a *valid* filesystem url has expected values. @@ -260,7 +259,7 @@ TEST_F(FileSystemContextTest, CrackFileSystemURL) { kFileSystemTypeUnknown, kFileSystemTypeUnknown, FPL(""), std::string()}, }; - for (size_t i = 0; i < base::size(kTestCases); ++i) { + for (size_t i = 0; i < std::size(kTestCases); ++i) { const base::FilePath virtual_path = base::FilePath::FromASCII(kTestCases[i].root) .Append(kVirtualPathNoRoot); diff --git a/chromium/storage/browser/file_system/file_system_operation.h b/chromium/storage/browser/file_system/file_system_operation.h index 5dc9c635da3..c3faa33578b 100644 --- a/chromium/storage/browser/file_system/file_system_operation.h +++ b/chromium/storage/browser/file_system/file_system_operation.h @@ -26,14 +26,12 @@ class Time; } namespace storage { -class ShareableFileReference; -} - -namespace storage { +class CopyOrMoveHookDelegate; class FileSystemContext; class FileSystemURL; class FileWriterDelegate; +class ShareableFileReference; // The interface class for FileSystemOperation implementations. // @@ -77,9 +75,11 @@ class FileSystemOperation { base::OnceCallback<void(base::File::Error result, const base::File::Info& file_info)>; - // 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. + // Used for OpenFile(). File system implementations can specify an + // `on_close_callback` if an operation is needed after closing a file. If + // non-null, OpenFile() callers must run the callback (on the IO thread) + // after the file closes. If the file is duped, the callback should not be run + // until all dups of the file have been closed. using OpenFileCallback = base::OnceCallback<void(base::File file, base::OnceClosure on_close_callback)>; @@ -127,115 +127,6 @@ class FileSystemOperation { // fails some of the operations. enum ErrorBehavior { ERROR_BEHAVIOR_ABORT, ERROR_BEHAVIOR_SKIP }; - // Used for progress update callback for Copy() and Move(). - // - // Note that Move() has both a same-filesystem (1) and a cross-filesystem (2) - // implementation. - // 1) Requires metadata updates. Depending on the underlying implementation: - // - we either only update the metadata of (or in other words, rename) the - // moving directory - // - or the directories are recursively copied + deleted, while the files are - // moved by having their metadata updated. - // 2) Degrades into copy + delete: each entry is copied and deleted - // recursively. - // - // kBegin is fired at the start of each copy or move operation (for - // both file and directory). The |source_url| and the |destination_url| are - // the URLs of the source and the destination entries. |size| should not be - // used. - // - // kProgress is fired periodically during file transfer (not fired for - // same-filesystem move and directory copy/move). - // The |source_url| and the |destination_url| are the URLs of the source and - // the destination entries. |size| is the number of cumulative copied bytes - // for the currently copied file. Both at beginning and ending of file - // transfer, PROGRESS event should be called. At beginning, |size| should be - // 0. At ending, |size| should be the size of the file. - // - // kEndCopy is fired for each destination entry that has been successfully - // copied (for both file and directory). The |source_url| and the - // |destination_url| are the URLs of the source and the destination entries. - // |size| should not be used. - // - // kEndMove is fired for each entry that has been successfully moved (for both - // file and directory), in the case of a same-filesystem move. The - // |source_url| and the |destination_url| are the URLs of the source and the - // destination entries. |size| should not be used. - // - // kEndRemoveSource, applies in the Move() case only, and is fired for each - // source entry that has been successfully removed from its source location - // (for both file and directory). The |source_url| is the URL of the source - // entry. |destination_url| and |size| should not be used. - // - // When moving files, the expected events are as follows. - // Copy: kBegin -> kProgress -> ... -> kProgress -> kEndCopy. - // Move (same-filesystem): kBegin -> kEndMove. - // Move (cross-filesystem): kBegin -> kProgress -> ... -> kProgress -> - // kEndCopy -> kEndRemoveSource. - // - // Here is an example callback sequence of for a copy or a cross-filesystem - // move. Suppose there are a/b/c.txt (100 bytes) and a/b/d.txt (200 bytes), - // and trying to transfer a to x recursively, then the progress update - // sequence will be: - // - // kBegin a x/a (starting create "a" directory in x/). - // kEndCopy a x/a (creating "a" directory in x/ is finished). - // - // kBegin a/b x/a/b (starting create "b" directory in x/a). - // kEndCopy a/b x/a/b (creating "b" directory in x/a/ is - // finished). - // - // kBegin a/b/c.txt x/a/b/c.txt (starting to transfer "c.txt" in - // x/a/b/). - // kProgress a/b/c.txt x/a/b/c.txt 0 (The first kProgress's |size| - // should be 0). - // kProgress a/b/c.txt x/a/b/c.txt 10 - // : - // kProgress a/b/c.txt x/a/b/c.txt 90 - // kProgress a/b/c.txt x/a/b/c.txt 100 (The last kProgress's |size| should be - // the size of the file). - // kEndCopy a/b/c.txt x/a/b/c.txt (transferring "c.txt" is - // finished). - // kEndRemoveSource a/b/c.txt ("copy + delete" move case). - // - // kBegin a/b/d.txt x/a/b/d.txt (starting to transfer "d.txt" in x/a/b). - // kProgress a/b/d.txt x/a/b/d.txt 0 (The first kProgress's |size| should be - // 0). - // kProgress a/b/d.txt x/a/b/d.txt 10 - // : - // kProgress a/b/d.txt x/a/b/d.txt 190 - // kProgress a/b/d.txt x/a/b/d.txt 200 (The last kProgress's |size| should be - // the size of the file). - // kEndCopy a/b/d.txt x/a/b/d.txt (transferring "d.txt" is - // finished). - // kEndRemoveSource a/b/d.txt ("copy + delete" move case). - // - // kEndRemoveSource a/b ("copy + delete" move case). - // - // kEndRemoveSource a ("copy + delete" move case). - // - // 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 kProgress 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. - // - enum class CopyOrMoveProgressType { - kBegin = 0, - kProgress, - kEndCopy, - kEndMove, - kEndRemoveSource, - kError, - }; - using CopyOrMoveProgressCallback = - base::RepeatingCallback<void(CopyOrMoveProgressType type, - const FileSystemURL& source_url, - const FileSystemURL& destination_url, - int64_t size)>; - // 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, @@ -326,9 +217,9 @@ class FileSystemOperation { // comment for details. // |error_behavior| specifies whether this continues operation after it // failed an operation or not. - // |progress_callback| is periodically called to report the progress - // update. See also the comment of CopyOrMoveProgressCallback. This callback - // is optional. + // |copy_or_move_hook_delegate|'s functions are periodically called to report + // the current state of the operation. See also the comments of + // CopyOrMoveHookDelegate. |copy_or_move_hook_delegate| is required. // // For recursive case this internally creates new FileSystemOperations and // calls: @@ -338,12 +229,13 @@ class FileSystemOperation { // CopyInForeignFile and CreateDirectory on dest filesystem // for cross-filesystem case. // - virtual void Copy(const FileSystemURL& src_path, - const FileSystemURL& dest_path, - CopyOrMoveOptionSet options, - ErrorBehavior error_behavior, - const CopyOrMoveProgressCallback& progress_callback, - StatusCallback callback) = 0; + virtual void Copy( + const FileSystemURL& src_path, + const FileSystemURL& dest_path, + CopyOrMoveOptionSet options, + ErrorBehavior error_behavior, + std::unique_ptr<CopyOrMoveHookDelegate> copy_or_move_hook_delegate, + 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. @@ -351,9 +243,9 @@ class FileSystemOperation { // comment for details. // |error_behavior| specifies whether this continues operation after it // failed an operation or not. - // |progress_callback| is periodically called to report the progress - // update. See also the comment of CopyProgressCallback. This callback is - // optional. + // |copy_or_move_hook_delegate|'s functions are periodically called to report + // the current state of the operation. See also the comments of + // CopyOrMoveHookDelegate. |copy_or_move_hook_delegate| is required. // // For recursive case this internally creates new FileSystemOperations and // calls: @@ -365,12 +257,13 @@ class FileSystemOperation { // // TODO(crbug.com/171284): Restore directory timestamps after the Move // operation. - virtual void Move(const FileSystemURL& src_path, - const FileSystemURL& dest_path, - CopyOrMoveOptionSet options, - ErrorBehavior error_behavior, - const CopyOrMoveProgressCallback& progress_callback, - StatusCallback callback) = 0; + virtual void Move( + const FileSystemURL& src_path, + const FileSystemURL& dest_path, + CopyOrMoveOptionSet options, + ErrorBehavior error_behavior, + std::unique_ptr<CopyOrMoveHookDelegate> copy_or_move_hook_delegate, + StatusCallback callback) = 0; // Checks if a directory is present at |path|. virtual void DirectoryExists(const FileSystemURL& path, diff --git a/chromium/storage/browser/file_system/file_system_operation_impl.cc b/chromium/storage/browser/file_system/file_system_operation_impl.cc index 14cc1980aba..97f5c14c452 100644 --- a/chromium/storage/browser/file_system/file_system_operation_impl.cc +++ b/chromium/storage/browser/file_system/file_system_operation_impl.cc @@ -12,6 +12,7 @@ #include <utility> #include "base/bind.h" +#include "base/callback.h" #include "base/callback_helpers.h" #include "base/task/single_thread_task_runner.h" #include "base/threading/sequenced_task_runner_handle.h" @@ -21,6 +22,7 @@ #include "net/url_request/url_request.h" #include "storage/browser/blob/shareable_file_reference.h" #include "storage/browser/file_system/async_file_util.h" +#include "storage/browser/file_system/copy_or_move_hook_delegate.h" #include "storage/browser/file_system/copy_or_move_operation_delegate.h" #include "storage/browser/file_system/file_observers.h" #include "storage/browser/file_system/file_system_backend.h" @@ -103,15 +105,16 @@ void FileSystemOperationImpl::Copy( const FileSystemURL& dest_url, CopyOrMoveOptionSet options, ErrorBehavior error_behavior, - const CopyOrMoveProgressCallback& progress_callback, + std::unique_ptr<CopyOrMoveHookDelegate> copy_or_move_hook_delegate, StatusCallback callback) { + DCHECK(copy_or_move_hook_delegate); DCHECK(SetPendingOperationType(kOperationCopy)); DCHECK(!recursive_operation_delegate_); recursive_operation_delegate_ = std::make_unique<CopyOrMoveOperationDelegate>( file_system_context(), src_url, dest_url, CopyOrMoveOperationDelegate::OPERATION_COPY, options, error_behavior, - progress_callback, + std::move(copy_or_move_hook_delegate), base::BindOnce(&FileSystemOperationImpl::DidFinishOperation, weak_factory_.GetWeakPtr(), std::move(callback))); recursive_operation_delegate_->RunRecursively(); @@ -122,14 +125,15 @@ void FileSystemOperationImpl::Move( const FileSystemURL& dest_url, CopyOrMoveOptionSet options, ErrorBehavior error_behavior, - const CopyOrMoveProgressCallback& progress_callback, + std::unique_ptr<CopyOrMoveHookDelegate> copy_or_move_hook_delegate, StatusCallback callback) { + DCHECK(copy_or_move_hook_delegate); DCHECK(SetPendingOperationType(kOperationMove)); DCHECK(!recursive_operation_delegate_); recursive_operation_delegate_ = std::make_unique<CopyOrMoveOperationDelegate>( file_system_context(), src_url, dest_url, CopyOrMoveOperationDelegate::OPERATION_MOVE, options, error_behavior, - progress_callback, + std::move(copy_or_move_hook_delegate), base::BindOnce(&FileSystemOperationImpl::DidFinishOperation, weak_factory_.GetWeakPtr(), std::move(callback))); recursive_operation_delegate_->RunRecursively(); @@ -409,7 +413,7 @@ void FileSystemOperationImpl::GetUsageAndQuotaThenRunTask( const FileSystemURL& url, base::OnceClosure task, base::OnceClosure error_callback) { - QuotaManagerProxy* quota_manager_proxy = + const scoped_refptr<QuotaManagerProxy>& quota_manager_proxy = file_system_context()->quota_manager_proxy(); if (!quota_manager_proxy || !file_system_context()->GetQuotaUtil(url.type())) { diff --git a/chromium/storage/browser/file_system/file_system_operation_impl.h b/chromium/storage/browser/file_system/file_system_operation_impl.h index 419d0b5b3fe..119d598a87b 100644 --- a/chromium/storage/browser/file_system/file_system_operation_impl.h +++ b/chromium/storage/browser/file_system/file_system_operation_impl.h @@ -58,13 +58,13 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) FileSystemOperationImpl const FileSystemURL& dest_url, CopyOrMoveOptionSet options, ErrorBehavior error_behavior, - const CopyOrMoveProgressCallback& progress_callback, + std::unique_ptr<CopyOrMoveHookDelegate> copy_or_move_hook_delegate, StatusCallback callback) override; void Move(const FileSystemURL& src_url, const FileSystemURL& dest_url, CopyOrMoveOptionSet options, ErrorBehavior error_behavior, - const CopyOrMoveProgressCallback& progress_callback, + std::unique_ptr<CopyOrMoveHookDelegate> copy_or_move_hook_delegate, StatusCallback callback) override; void DirectoryExists(const FileSystemURL& url, StatusCallback callback) override; diff --git a/chromium/storage/browser/file_system/file_system_operation_impl_unittest.cc b/chromium/storage/browser/file_system/file_system_operation_impl_unittest.cc index 9cd93ac877b..ad8abd50bbc 100644 --- a/chromium/storage/browser/file_system/file_system_operation_impl_unittest.cc +++ b/chromium/storage/browser/file_system/file_system_operation_impl_unittest.cc @@ -12,7 +12,6 @@ #include <utility> #include "base/bind.h" -#include "base/cxx17_backports.h" #include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" #include "base/memory/ptr_util.h" @@ -22,8 +21,10 @@ #include "base/strings/stringprintf.h" #include "base/test/task_environment.h" #include "base/threading/thread_task_runner_handle.h" +#include "base/time/time.h" #include "components/services/filesystem/public/mojom/types.mojom.h" #include "storage/browser/blob/shareable_file_reference.h" +#include "storage/browser/file_system/copy_or_move_hook_delegate.h" #include "storage/browser/file_system/file_system_context.h" #include "storage/browser/file_system/file_system_file_util.h" #include "storage/browser/file_system/file_system_operation_context.h" @@ -36,6 +37,7 @@ #include "storage/browser/test/mock_file_update_observer.h" #include "storage/browser/test/mock_quota_manager.h" #include "storage/browser/test/mock_quota_manager_proxy.h" +#include "storage/browser/test/mock_special_storage_policy.h" #include "storage/browser/test/sandbox_file_system_test_helper.h" #include "storage/common/file_system/file_system_util.h" #include "testing/gtest/include/gtest/gtest.h" @@ -47,13 +49,14 @@ namespace storage { class FileSystemOperationImplTest : public testing::Test { public: FileSystemOperationImplTest() - : task_environment_(base::test::TaskEnvironment::MainThreadType::IO) {} + : special_storage_policy_( + base::MakeRefCounted<MockSpecialStoragePolicy>()), + task_environment_(base::test::TaskEnvironment::MainThreadType::IO) {} FileSystemOperationImplTest(const FileSystemOperationImplTest&) = delete; FileSystemOperationImplTest& operator=(const FileSystemOperationImplTest&) = delete; - protected: void SetUp() override { EXPECT_TRUE(base_.CreateUniqueTempDir()); change_observers_ = MockFileChangeObserver::CreateList(&change_observer_); @@ -62,8 +65,7 @@ class FileSystemOperationImplTest : public testing::Test { base::FilePath base_dir = base_.GetPath().AppendASCII("filesystem"); quota_manager_ = base::MakeRefCounted<MockQuotaManager>( /* is_incognito= */ false, base_dir, - base::ThreadTaskRunnerHandle::Get().get(), - /* special storage policy= */ nullptr); + base::ThreadTaskRunnerHandle::Get(), special_storage_policy_); quota_manager_proxy_ = base::MakeRefCounted<MockQuotaManagerProxy>( quota_manager(), base::ThreadTaskRunnerHandle::Get()); sandbox_file_system_.SetUp(base_dir, quota_manager_proxy_.get()); @@ -285,8 +287,8 @@ class FileSystemOperationImplTest : public testing::Test { base::RunLoop run_loop; update_observer_.Enable(); operation_runner()->Move( - src, dest, options, storage::FileSystemOperation::ERROR_BEHAVIOR_ABORT, - storage::FileSystemOperation::CopyOrMoveProgressCallback(), + src, dest, options, FileSystemOperation::ERROR_BEHAVIOR_ABORT, + std::make_unique<CopyOrMoveHookDelegate>(), RecordStatusCallback(run_loop.QuitClosure(), &status)); run_loop.Run(); update_observer_.Disable(); @@ -301,7 +303,7 @@ class FileSystemOperationImplTest : public testing::Test { update_observer_.Enable(); operation_runner()->Copy( src, dest, options, FileSystemOperation::ERROR_BEHAVIOR_ABORT, - FileSystemOperation::CopyOrMoveProgressCallback(), + std::make_unique<CopyOrMoveHookDelegate>(), RecordStatusCallback(run_loop.QuitClosure(), &status)); run_loop.Run(); update_observer_.Disable(); @@ -436,15 +438,17 @@ class FileSystemOperationImplTest : public testing::Test { return status; } + protected: + scoped_refptr<MockSpecialStoragePolicy> special_storage_policy_; + + // Common temp base for nondestructive uses. + base::ScopedTempDir base_; + base::test::TaskEnvironment task_environment_; - private: scoped_refptr<QuotaManager> quota_manager_; scoped_refptr<QuotaManagerProxy> quota_manager_proxy_; - // Common temp base for nondestructive uses. - base::ScopedTempDir base_; - SandboxFileSystemTestHelper sandbox_file_system_; // For post-operation status. @@ -811,7 +815,7 @@ TEST_F(FileSystemOperationImplTest, TestCopyInForeignFileSuccess) { base::FilePath src_local_disk_file_path; base::CreateTemporaryFile(&src_local_disk_file_path); const char test_data[] = "foo"; - int data_size = base::size(test_data); + int data_size = std::size(test_data); base::WriteFile(src_local_disk_file_path, test_data, data_size); FileSystemURL dest_dir(CreateDirectory("dest")); @@ -841,7 +845,7 @@ TEST_F(FileSystemOperationImplTest, TestCopyInForeignFileFailureByQuota) { base::FilePath src_local_disk_file_path; base::CreateTemporaryFile(&src_local_disk_file_path); const char test_data[] = "foo"; - base::WriteFile(src_local_disk_file_path, test_data, base::size(test_data)); + base::WriteFile(src_local_disk_file_path, test_data, std::size(test_data)); FileSystemURL dest_dir(CreateDirectory("dest")); diff --git a/chromium/storage/browser/file_system/file_system_operation_impl_write_unittest.cc b/chromium/storage/browser/file_system/file_system_operation_impl_write_unittest.cc index 55fe648ad22..4bff94324f5 100644 --- a/chromium/storage/browser/file_system/file_system_operation_impl_write_unittest.cc +++ b/chromium/storage/browser/file_system/file_system_operation_impl_write_unittest.cc @@ -27,6 +27,7 @@ #include "storage/browser/test/mock_blob_util.h" #include "storage/browser/test/mock_file_change_observer.h" #include "storage/browser/test/mock_quota_manager.h" +#include "storage/browser/test/mock_special_storage_policy.h" #include "storage/browser/test/test_file_system_backend.h" #include "storage/browser/test/test_file_system_context.h" #include "storage/common/file_system/file_system_util.h" @@ -51,7 +52,10 @@ void AssertStatusEq(base::File::Error expected, base::File::Error actual) { class FileSystemOperationImplWriteTest : public testing::Test { public: FileSystemOperationImplWriteTest() - : task_environment_(base::test::TaskEnvironment::MainThreadType::IO), + : special_storage_policy_( + base::MakeRefCounted<MockSpecialStoragePolicy>()), + task_environment_(base::test::TaskEnvironment::MainThreadType::IO), + virtual_path_(FILE_PATH_LITERAL("temporary file")), status_(base::File::FILE_OK), cancel_status_(base::File::FILE_ERROR_FAILED), bytes_written_(0), @@ -65,16 +69,14 @@ class FileSystemOperationImplWriteTest : public testing::Test { const FileSystemOperationImplWriteTest&) = delete; void SetUp() override { - ASSERT_TRUE(dir_.CreateUniqueTempDir()); + ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); quota_manager_ = base::MakeRefCounted<MockQuotaManager>( - /* is_incognito= */ false, dir_.GetPath(), - base::ThreadTaskRunnerHandle::Get().get(), - /* special storage policy= */ nullptr); - virtual_path_ = base::FilePath(FILE_PATH_LITERAL("temporary file")); + /* is_incognito= */ false, data_dir_.GetPath(), + base::ThreadTaskRunnerHandle::Get(), special_storage_policy_); file_system_context_ = CreateFileSystemContextForTesting( - quota_manager_->proxy(), dir_.GetPath()); + quota_manager_->proxy(), data_dir_.GetPath()); blob_storage_context_ = std::make_unique<BlobStorageContext>(); file_system_context_->operation_runner()->CreateFile( @@ -147,13 +149,15 @@ class FileSystemOperationImplWriteTest : public testing::Test { return blob_storage_context_.get(); } + scoped_refptr<MockSpecialStoragePolicy> special_storage_policy_; + + base::ScopedTempDir data_dir_; base::test::TaskEnvironment task_environment_; scoped_refptr<FileSystemContext> file_system_context_; scoped_refptr<MockQuotaManager> quota_manager_; - base::ScopedTempDir dir_; - base::FilePath virtual_path_; + const base::FilePath virtual_path_; // For post-operation status. base::File::Error status_; diff --git a/chromium/storage/browser/file_system/file_system_operation_runner.cc b/chromium/storage/browser/file_system/file_system_operation_runner.cc index c910fe401a9..70672c6f525 100644 --- a/chromium/storage/browser/file_system/file_system_operation_runner.cc +++ b/chromium/storage/browser/file_system/file_system_operation_runner.cc @@ -12,14 +12,17 @@ #include "base/auto_reset.h" #include "base/bind.h" +#include "base/callback.h" #include "base/containers/contains.h" #include "base/memory/ptr_util.h" #include "base/threading/thread_task_runner_handle.h" #include "net/url_request/url_request_context.h" #include "storage/browser/blob/shareable_file_reference.h" +#include "storage/browser/file_system/copy_or_move_hook_delegate.h" #include "storage/browser/file_system/file_observers.h" #include "storage/browser/file_system/file_stream_writer.h" #include "storage/browser/file_system/file_system_context.h" +#include "storage/browser/file_system/file_system_operation.h" #include "storage/browser/file_system/file_writer_delegate.h" namespace storage { @@ -93,8 +96,9 @@ OperationID FileSystemOperationRunner::Copy( const FileSystemURL& dest_url, CopyOrMoveOptionSet options, ErrorBehavior error_behavior, - const CopyOrMoveProgressCallback& progress_callback, + std::unique_ptr<CopyOrMoveHookDelegate> copy_or_move_hook_delegate, StatusCallback callback) { + DCHECK(copy_or_move_hook_delegate); base::File::Error error = base::File::FILE_OK; std::unique_ptr<FileSystemOperation> operation = file_system_context_->CreateFileSystemOperation(dest_url, &error); @@ -107,14 +111,10 @@ OperationID FileSystemOperationRunner::Copy( } PrepareForWrite(id, dest_url); PrepareForRead(id, src_url); - operation_raw->Copy( - src_url, dest_url, options, error_behavior, - progress_callback.is_null() - ? CopyOrMoveProgressCallback() - : base::BindRepeating(&FileSystemOperationRunner::OnCopyProgress, - weak_ptr_, id, progress_callback), - base::BindOnce(&FileSystemOperationRunner::DidFinish, weak_ptr_, id, - std::move(callback))); + operation_raw->Copy(src_url, dest_url, options, error_behavior, + std::move(copy_or_move_hook_delegate), + base::BindOnce(&FileSystemOperationRunner::DidFinish, + weak_ptr_, id, std::move(callback))); return id; } @@ -123,8 +123,9 @@ OperationID FileSystemOperationRunner::Move( const FileSystemURL& dest_url, CopyOrMoveOptionSet options, ErrorBehavior error_behavior, - const CopyOrMoveProgressCallback& progress_callback, + std::unique_ptr<CopyOrMoveHookDelegate> copy_or_move_hook_delegate, StatusCallback callback) { + DCHECK(copy_or_move_hook_delegate); base::File::Error error = base::File::FILE_OK; std::unique_ptr<FileSystemOperation> operation = file_system_context_->CreateFileSystemOperation(dest_url, &error); @@ -137,14 +138,10 @@ OperationID FileSystemOperationRunner::Move( } PrepareForWrite(id, dest_url); PrepareForWrite(id, src_url); - operation_raw->Move( - src_url, dest_url, options, error_behavior, - progress_callback.is_null() - ? CopyOrMoveProgressCallback() - : base::BindRepeating(&FileSystemOperationRunner::OnCopyProgress, - weak_ptr_, id, progress_callback), - base::BindOnce(&FileSystemOperationRunner::DidFinish, weak_ptr_, id, - std::move(callback))); + operation_raw->Move(src_url, dest_url, options, error_behavior, + std::move(copy_or_move_hook_delegate), + base::BindOnce(&FileSystemOperationRunner::DidFinish, + weak_ptr_, id, std::move(callback))); return id; } @@ -696,23 +693,6 @@ void FileSystemOperationRunner::DidCreateSnapshot( FinishOperation(id); } -void FileSystemOperationRunner::OnCopyProgress( - const OperationID id, - const CopyOrMoveProgressCallback& callback, - FileSystemOperation::CopyOrMoveProgressType type, - const FileSystemURL& source_url, - const FileSystemURL& dest_url, - int64_t size) { - if (is_beginning_operation_) { - base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, - base::BindOnce(&FileSystemOperationRunner::OnCopyProgress, weak_ptr_, - id, 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())) { diff --git a/chromium/storage/browser/file_system/file_system_operation_runner.h b/chromium/storage/browser/file_system/file_system_operation_runner.h index 290f7b0c5b8..b93bfd24032 100644 --- a/chromium/storage/browser/file_system/file_system_operation_runner.h +++ b/chromium/storage/browser/file_system/file_system_operation_runner.h @@ -43,8 +43,6 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) FileSystemOperationRunner { using WriteCallback = FileSystemOperation::WriteCallback; using OpenFileCallback = FileSystemOperation::OpenFileCallback; using ErrorBehavior = FileSystemOperation::ErrorBehavior; - using CopyOrMoveProgressCallback = - FileSystemOperation::CopyOrMoveProgressCallback; using CopyFileProgressCallback = FileSystemOperation::CopyFileProgressCallback; using CopyOrMoveOptionSet = FileSystemOperation::CopyOrMoveOptionSet; @@ -84,24 +82,27 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) FileSystemOperationRunner { // |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, - CopyOrMoveOptionSet options, - ErrorBehavior error_behavior, - const CopyOrMoveProgressCallback& progress_callback, - StatusCallback callback); + // For |option| and |copy_or_move_hook_delegate|, see file_system_operation.h + // for details. + OperationID Copy( + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOptionSet options, + ErrorBehavior error_behavior, + std::unique_ptr<CopyOrMoveHookDelegate> copy_or_move_hook_delegate, + 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, - CopyOrMoveOptionSet options, - ErrorBehavior error_behavior, - const CopyOrMoveProgressCallback& progress_callback, - StatusCallback callback); + // For |option| and |copy_or_move_hook_delegate|, see file_system_operation.h + // for details. + OperationID Move( + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + CopyOrMoveOptionSet options, + ErrorBehavior error_behavior, + std::unique_ptr<CopyOrMoveHookDelegate> copy_or_move_hook_delegate, + StatusCallback callback); // Checks if a directory is present at |url|. OperationID DirectoryExists(const FileSystemURL& url, @@ -287,13 +288,6 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) FileSystemOperationRunner { const base::FilePath& platform_path, scoped_refptr<ShareableFileReference> file_ref); - void OnCopyProgress(const OperationID id, - const CopyOrMoveProgressCallback& callback, - FileSystemOperation::CopyOrMoveProgressType type, - const FileSystemURL& source_url, - const FileSystemURL& dest_url, - int64_t size); - void PrepareForWrite(OperationID id, const FileSystemURL& url); void PrepareForRead(OperationID id, const FileSystemURL& url); diff --git a/chromium/storage/browser/file_system/file_system_quota_client.cc b/chromium/storage/browser/file_system/file_system_quota_client.cc index 7268996ae35..fa2ad05238f 100644 --- a/chromium/storage/browser/file_system/file_system_quota_client.cc +++ b/chromium/storage/browser/file_system/file_system_quota_client.cc @@ -102,7 +102,7 @@ blink::mojom::QuotaStatusCode DeleteStorageKeyOnFileTaskRunner( return blink::mojom::QuotaStatusCode::kErrorNotSupported; base::File::Error result = provider->GetQuotaUtil()->DeleteStorageKeyDataOnFileTaskRunner( - context, context->quota_manager_proxy(), storage_key, type); + context, context->quota_manager_proxy().get(), storage_key, type); if (result == base::File::FILE_OK) return blink::mojom::QuotaStatusCode::kOk; return blink::mojom::QuotaStatusCode::kErrorInvalidModification; @@ -114,7 +114,7 @@ void PerformStorageCleanupOnFileTaskRunner(FileSystemContext* context, if (!provider || !provider->GetQuotaUtil()) return; provider->GetQuotaUtil()->PerformStorageCleanupOnFileTaskRunner( - context, context->quota_manager_proxy(), type); + context, context->quota_manager_proxy().get(), type); } } // namespace diff --git a/chromium/storage/browser/file_system/file_system_quota_client_unittest.cc b/chromium/storage/browser/file_system/file_system_quota_client_unittest.cc index b7bb5a5caec..d7840d136b1 100644 --- a/chromium/storage/browser/file_system/file_system_quota_client_unittest.cc +++ b/chromium/storage/browser/file_system/file_system_quota_client_unittest.cc @@ -28,6 +28,7 @@ #include "storage/browser/test/async_file_test_helper.h" #include "storage/browser/test/mock_quota_manager.h" #include "storage/browser/test/mock_quota_manager_proxy.h" +#include "storage/browser/test/mock_special_storage_policy.h" #include "storage/browser/test/test_file_system_context.h" #include "storage/common/file_system/file_system_types.h" #include "storage/common/file_system/file_system_util.h" @@ -57,7 +58,10 @@ const StorageType kPersistent = StorageType::kPersistent; class FileSystemQuotaClientTest : public testing::TestWithParam<bool> { public: - FileSystemQuotaClientTest() = default; + FileSystemQuotaClientTest() + : special_storage_policy_( + base::MakeRefCounted<MockSpecialStoragePolicy>()), + task_environment_(base::test::TaskEnvironment::MainThreadType::IO) {} ~FileSystemQuotaClientTest() override = default; void SetUp() override { @@ -72,8 +76,7 @@ class FileSystemQuotaClientTest : public testing::TestWithParam<bool> { quota_manager_ = base::MakeRefCounted<MockQuotaManager>( /*is_incognito_=*/false, data_dir_.GetPath(), - base::ThreadTaskRunnerHandle::Get(), - /*special_storage_policy=*/nullptr); + base::ThreadTaskRunnerHandle::Get(), special_storage_policy_); quota_manager_proxy_ = base::MakeRefCounted<storage::MockQuotaManagerProxy>( quota_manager_.get(), base::ThreadTaskRunnerHandle::Get()); @@ -91,7 +94,6 @@ class FileSystemQuotaClientTest : public testing::TestWithParam<bool> { FileSystemType type; }; - protected: storage::FileSystemContext* GetFileSystemContext() { return file_system_context_.get(); } @@ -256,9 +258,13 @@ class FileSystemQuotaClientTest : public testing::TestWithParam<bool> { ++additional_callback_count_; } + protected: + scoped_refptr<MockSpecialStoragePolicy> special_storage_policy_; + base::test::ScopedFeatureList feature_list_; base::ScopedTempDir data_dir_; base::test::TaskEnvironment task_environment_; + scoped_refptr<FileSystemContext> file_system_context_; scoped_refptr<MockQuotaManager> quota_manager_; scoped_refptr<storage::MockQuotaManagerProxy> quota_manager_proxy_; diff --git a/chromium/storage/browser/file_system/file_system_url_unittest.cc b/chromium/storage/browser/file_system/file_system_url_unittest.cc index d0c85a6445e..3a818c4856e 100644 --- a/chromium/storage/browser/file_system/file_system_url_unittest.cc +++ b/chromium/storage/browser/file_system/file_system_url_unittest.cc @@ -8,7 +8,6 @@ #include <utility> -#include "base/cxx17_backports.h" #include "base/files/file_path.h" #include "storage/common/file_system/file_system_types.h" #include "storage/common/file_system/file_system_util.h" @@ -111,8 +110,8 @@ TEST(FileSystemURLTest, CompareURLs) { GURL("filesystem:https://chromium.org/temporary/dir a/file a")}; FileSystemURL::Comparator compare; - for (size_t i = 0; i < base::size(urls); ++i) { - for (size_t j = 0; j < base::size(urls); ++j) { + for (size_t i = 0; i < std::size(urls); ++i) { + for (size_t j = 0; j < std::size(urls); ++j) { SCOPED_TRACE(testing::Message() << i << " < " << j); EXPECT_EQ(urls[i] < urls[j], compare(FileSystemURL::CreateForTest(urls[i]), diff --git a/chromium/storage/browser/file_system/file_writer_delegate_unittest.cc b/chromium/storage/browser/file_system/file_writer_delegate_unittest.cc index 6a1d9675e2a..e36d0fdf09c 100644 --- a/chromium/storage/browser/file_system/file_writer_delegate_unittest.cc +++ b/chromium/storage/browser/file_system/file_writer_delegate_unittest.cc @@ -2,7 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "storage/browser/file_system/file_writer_delegate.h" + #include <stdint.h> + #include <limits> #include <string> #include <utility> @@ -10,7 +13,6 @@ #include "base/bind.h" #include "base/callback_helpers.h" -#include "base/cxx17_backports.h" #include "base/files/scoped_temp_dir.h" #include "base/location.h" #include "base/memory/weak_ptr.h" @@ -30,7 +32,6 @@ #include "storage/browser/blob/blob_storage_context.h" #include "storage/browser/file_system/file_system_context.h" #include "storage/browser/file_system/file_system_quota_util.h" -#include "storage/browser/file_system/file_writer_delegate.h" #include "storage/browser/file_system/sandbox_file_stream_writer.h" #include "storage/browser/quota/quota_manager_proxy.h" #include "storage/browser/test/async_file_test_helper.h" @@ -49,7 +50,7 @@ const char kOrigin[] = "http://example.com"; const FileSystemType kFileSystemType = kFileSystemTypeTest; const char kData[] = "The quick brown fox jumps over the lazy dog.\n"; -const int kDataSize = base::size(kData) - 1; +const int kDataSize = std::size(kData) - 1; class Result { public: diff --git a/chromium/storage/browser/file_system/filesystem_proxy_file_stream_reader_unittest.cc b/chromium/storage/browser/file_system/filesystem_proxy_file_stream_reader_unittest.cc index 417b141dcc3..8d953bbf401 100644 --- a/chromium/storage/browser/file_system/filesystem_proxy_file_stream_reader_unittest.cc +++ b/chromium/storage/browser/file_system/filesystem_proxy_file_stream_reader_unittest.cc @@ -21,6 +21,7 @@ #include "base/task/single_thread_task_runner.h" #include "base/test/task_environment.h" #include "base/threading/thread.h" +#include "base/time/time.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" #include "net/base/test_completion_callback.h" diff --git a/chromium/storage/browser/file_system/isolated_context_unittest.cc b/chromium/storage/browser/file_system/isolated_context_unittest.cc index 58f5fab2dee..1d2e9b58ccd 100644 --- a/chromium/storage/browser/file_system/isolated_context_unittest.cc +++ b/chromium/storage/browser/file_system/isolated_context_unittest.cc @@ -2,13 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "storage/browser/file_system/isolated_context.h" + #include <stddef.h> #include <string> -#include "base/cxx17_backports.h" #include "storage/browser/file_system/file_system_url.h" -#include "storage/browser/file_system/isolated_context.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/public/common/storage_key/storage_key.h" @@ -91,7 +91,7 @@ TEST_F(IsolatedContextTest, RegisterAndRevokeTest) { // See if the name of each registered kTestPaths (that is what we // register in SetUp() by RegisterDraggedFileSystem) is properly cracked as // a valid virtual path in the isolated filesystem. - for (size_t i = 0; i < base::size(kTestPaths); ++i) { + for (size_t i = 0; i < std::size(kTestPaths); ++i) { base::FilePath virtual_path = isolated_context()->CreateVirtualRootPath(id_).AppendASCII(names_[i]); std::string cracked_id; @@ -191,8 +191,8 @@ TEST_F(IsolatedContextTest, CrackWithRelativePaths) { {FPL("foo/..\\baz"), SHOULD_FAIL_WITH_WIN_SEPARATORS}, }; - for (size_t i = 0; i < base::size(kTestPaths); ++i) { - for (size_t j = 0; j < base::size(relatives); ++j) { + for (size_t i = 0; i < std::size(kTestPaths); ++i) { + for (size_t j = 0; j < std::size(relatives); ++j) { SCOPED_TRACE(testing::Message() << "Testing " << kTestPaths[i].value() << " " << relatives[j].path); base::FilePath virtual_path = isolated_context() @@ -244,8 +244,8 @@ TEST_F(IsolatedContextTest, CrackURLWithRelativePaths) { {FPL("foo/..\\baz"), SHOULD_FAIL_WITH_WIN_SEPARATORS}, }; - for (size_t i = 0; i < base::size(kTestPaths); ++i) { - for (size_t j = 0; j < base::size(relatives); ++j) { + for (size_t i = 0; i < std::size(kTestPaths); ++i) { + for (size_t j = 0; j < std::size(relatives); ++j) { SCOPED_TRACE(testing::Message() << "Testing " << kTestPaths[i].value() << " " << relatives[j].path); base::FilePath virtual_path = isolated_context() diff --git a/chromium/storage/browser/file_system/local_file_stream_reader_unittest.cc b/chromium/storage/browser/file_system/local_file_stream_reader_unittest.cc index 2e2ff8ead6c..c7396bdaf77 100644 --- a/chromium/storage/browser/file_system/local_file_stream_reader_unittest.cc +++ b/chromium/storage/browser/file_system/local_file_stream_reader_unittest.cc @@ -21,6 +21,7 @@ #include "base/task/single_thread_task_runner.h" #include "base/test/task_environment.h" #include "base/threading/thread.h" +#include "base/time/time.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" #include "net/base/test_completion_callback.h" diff --git a/chromium/storage/browser/file_system/native_file_util.cc b/chromium/storage/browser/file_system/native_file_util.cc index 4d0f7df58e5..d2ea7638059 100644 --- a/chromium/storage/browser/file_system/native_file_util.cc +++ b/chromium/storage/browser/file_system/native_file_util.cc @@ -10,6 +10,7 @@ #include "base/files/file_enumerator.h" #include "base/files/file_util.h" +#include "base/time/time.h" #include "build/build_config.h" #include "build/chromeos_buildflags.h" #include "storage/browser/file_system/file_system_operation_context.h" diff --git a/chromium/storage/browser/file_system/native_file_util_unittest.cc b/chromium/storage/browser/file_system/native_file_util_unittest.cc index 602751dcb45..4038989040c 100644 --- a/chromium/storage/browser/file_system/native_file_util_unittest.cc +++ b/chromium/storage/browser/file_system/native_file_util_unittest.cc @@ -507,6 +507,7 @@ TEST_F(NativeFileUtilTest, PreserveLastModified) { EXPECT_EQ(file_info1.last_modified, file_info2.last_modified); } +// This test is disabled on Fuchsia because file permissions are not supported. #if BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_WIN) TEST_F(NativeFileUtilTest, PreserveDestinationPermissions) { // Ensure both the src and dest files exist. @@ -584,6 +585,7 @@ TEST_F(NativeFileUtilTest, PreserveDestinationPermissions) { } #endif // BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_WIN) +// This test is disabled on Fuchsia because file permissions are not supported. #if BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_WIN) TEST_F(NativeFileUtilTest, PreserveLastModifiedAndDestinationPermissions) { base::FilePath from_file = Path("fromfile"); diff --git a/chromium/storage/browser/file_system/obfuscated_file_util.cc b/chromium/storage/browser/file_system/obfuscated_file_util.cc index a3840c4b0ba..df2a3a49d70 100644 --- a/chromium/storage/browser/file_system/obfuscated_file_util.cc +++ b/chromium/storage/browser/file_system/obfuscated_file_util.cc @@ -13,6 +13,7 @@ #include "base/bind.h" #include "base/containers/queue.h" +#include "base/files/file_error_or.h" #include "base/files/file_util.h" #include "base/format_macros.h" #include "base/logging.h" @@ -272,6 +273,7 @@ class ObfuscatedStorageKeyEnumerator ObfuscatedFileUtil::ObfuscatedFileUtil( scoped_refptr<SpecialStoragePolicy> special_storage_policy, const base::FilePath& file_system_directory, + const base::FilePath& bucket_base_path, leveldb::Env* env_override, GetTypeStringForURLCallback get_type_string_for_url, const std::set<std::string>& known_type_strings, @@ -279,6 +281,7 @@ ObfuscatedFileUtil::ObfuscatedFileUtil( bool is_incognito) : special_storage_policy_(std::move(special_storage_policy)), file_system_directory_(file_system_directory), + bucket_base_path_(bucket_base_path), env_override_(env_override), is_incognito_(is_incognito), db_flush_delay_seconds_(10 * 60), // 10 mins. @@ -997,13 +1000,24 @@ int64_t ObfuscatedFileUtil::ComputeFilePathCost(const base::FilePath& path) { return UsageForPath(VirtualPath::BaseName(path).value().size()); } -base::FilePath ObfuscatedFileUtil::GetDirectoryForURL( +base::FileErrorOr<base::FilePath> ObfuscatedFileUtil::GetDirectoryForURL( const FileSystemURL& url, - bool create, - base::File::Error* error_code) { + bool create) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - return GetDirectoryForStorageKeyAndType( - url.storage_key(), CallGetTypeStringForURL(url), create, error_code); + if (!url.bucket().has_value() || url.storage_key().IsFirstPartyContext()) { + // Access the SandboxDirectoryDatabase to construct the file path. + // TODO(https://crbug.com/1310361): refactor GetDirectoryForStorageKey and + // its related functions to return a base::FileErrorOr<base::FilePath>. + base::File::Error error = base::File::FILE_OK; + base::FilePath path = GetDirectoryForStorageKeyAndType( + url.storage_key(), CallGetTypeStringForURL(url), create, &error); + if (error != base::File::FILE_OK) + return error; + return path; + } + // Construct the file path using non-default bucket information. + return GetDirectoryWithBucket(create, url.bucket().value(), + CallGetTypeStringForURL(url)); } std::string ObfuscatedFileUtil::CallGetTypeStringForURL( @@ -1175,11 +1189,10 @@ base::FilePath ObfuscatedFileUtil::DataPathToLocalPath( const FileSystemURL& url, const base::FilePath& data_path) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - base::File::Error error = base::File::FILE_OK; - base::FilePath root = GetDirectoryForURL(url, false, &error); - if (error != base::File::FILE_OK) + base::FileErrorOr<base::FilePath> root = GetDirectoryForURL(url, false); + if (root.is_error()) return base::FilePath(); - return root.Append(data_path); + return root.value().Append(data_path); } std::string ObfuscatedFileUtil::GetDirectoryDatabaseKey( @@ -1210,16 +1223,15 @@ SandboxDirectoryDatabase* ObfuscatedFileUtil::GetDirectoryDatabase( return iter->second.get(); } - base::File::Error error = base::File::FILE_OK; - base::FilePath path = GetDirectoryForURL(url, create, &error); - if (error != base::File::FILE_OK) { + base::FileErrorOr<base::FilePath> path = GetDirectoryForURL(url, create); + if (path.is_error()) { LOG(WARNING) << "Failed to get origin+type directory: " << url.DebugString() - << " error:" << error; + << " error:" << path.error(); return nullptr; } MarkUsed(); directories_[key] = - std::make_unique<SandboxDirectoryDatabase>(path, env_override_); + std::make_unique<SandboxDirectoryDatabase>(path.value(), env_override_); return directories_[key].get(); } @@ -1278,6 +1290,24 @@ base::FilePath ObfuscatedFileUtil::GetDirectoryForStorageKey( return path; } +base::FileErrorOr<base::FilePath> ObfuscatedFileUtil::GetDirectoryWithBucket( + bool create, + BucketLocator bucket, + std::string file_type) { + base::FilePath path = + sandbox_delegate_->quota_manager_proxy()->GetClientBucketPath( + bucket, QuotaClientType::kFileSystem); + // Verify the directory is valid. + if (!delegate_->DirectoryExists(path) && + (!create || delegate_->CreateDirectory(path, false /* exclusive */, + true /* recursive */) != + base::File::FILE_OK)) { + return create ? base::File::FILE_ERROR_FAILED + : base::File::FILE_ERROR_NOT_FOUND; + } + return path; +} + void ObfuscatedFileUtil::InvalidateUsageCache( FileSystemOperationContext* context, const blink::StorageKey& storage_key, @@ -1343,16 +1373,18 @@ base::File::Error ObfuscatedFileUtil::GenerateNewLocalPath( 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; + base::FileErrorOr<base::FilePath> directory_for_url = + GetDirectoryForURL(url, false); + if (directory_for_url.is_error()) + return directory_for_url.error(); + *root = directory_for_url.value(); // We use the third- and fourth-to-last digits as the directory. int64_t directory_number = number % 10000 / 100; base::FilePath new_local_path = root->AppendASCII(base::StringPrintf("%02" PRId64, directory_number)); + base::File::Error error = base::File::FILE_OK; error = delegate_->CreateDirectory(new_local_path, false /* exclusive */, false /* recursive */); if (error != base::File::FILE_OK) diff --git a/chromium/storage/browser/file_system/obfuscated_file_util.h b/chromium/storage/browser/file_system/obfuscated_file_util.h index 69b70676251..ddede7e9eb7 100644 --- a/chromium/storage/browser/file_system/obfuscated_file_util.h +++ b/chromium/storage/browser/file_system/obfuscated_file_util.h @@ -16,6 +16,7 @@ #include "base/callback_forward.h" #include "base/component_export.h" #include "base/files/file.h" +#include "base/files/file_error_or.h" #include "base/files/file_path.h" #include "base/memory/raw_ptr.h" #include "base/memory/scoped_refptr.h" @@ -111,6 +112,7 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) ObfuscatedFileUtil // deleted when one StorageKey/type pair is deleted. ObfuscatedFileUtil(scoped_refptr<SpecialStoragePolicy> special_storage_policy, const base::FilePath& file_system_directory, + const base::FilePath& bucket_base_path, leveldb::Env* env_override, GetTypeStringForURLCallback get_type_string_for_url, const std::set<std::string>& known_type_strings, @@ -236,12 +238,12 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) ObfuscatedFileUtil static std::unique_ptr<ObfuscatedFileUtil> CreateForTesting( scoped_refptr<SpecialStoragePolicy> special_storage_policy, const base::FilePath& file_system_directory, + const base::FilePath& bucket_base_path, leveldb::Env* env_override, bool is_incognito); - base::FilePath GetDirectoryForURL(const FileSystemURL& url, - bool create, - base::File::Error* error_code); + base::FileErrorOr<base::FilePath> GetDirectoryForURL(const FileSystemURL& url, + bool create); // This just calls get_type_string_for_url_ callback that is given in ctor. std::string CallGetTypeStringForURL(const FileSystemURL& url); @@ -302,6 +304,14 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) ObfuscatedFileUtil bool create, base::File::Error* error_code); + // Returns a valid file path to the directory corresponding to the specified + // non-default `bucket` and `file_type.` Will return a FileError if an invalid + // file path is found. + base::FileErrorOr<base::FilePath> GetDirectoryWithBucket( + bool create, + BucketLocator bucket, + std::string file_type); + void InvalidateUsageCache(FileSystemOperationContext* context, const blink::StorageKey& storage_key, FileSystemType type); @@ -331,6 +341,7 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) ObfuscatedFileUtil std::unique_ptr<SandboxOriginDatabaseInterface> origin_database_; scoped_refptr<SpecialStoragePolicy> special_storage_policy_; base::FilePath file_system_directory_; + base::FilePath bucket_base_path_; raw_ptr<leveldb::Env> env_override_; bool is_incognito_; diff --git a/chromium/storage/browser/file_system/obfuscated_file_util_unittest.cc b/chromium/storage/browser/file_system/obfuscated_file_util_unittest.cc index 604674b3d16..49f700b7ade 100644 --- a/chromium/storage/browser/file_system/obfuscated_file_util_unittest.cc +++ b/chromium/storage/browser/file_system/obfuscated_file_util_unittest.cc @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "storage/browser/file_system/obfuscated_file_util.h" + #include <stddef.h> #include <stdint.h> @@ -14,7 +16,6 @@ #include "base/bind.h" #include "base/callback_helpers.h" -#include "base/cxx17_backports.h" #include "base/files/file.h" #include "base/files/file_path.h" #include "base/files/file_util.h" @@ -33,7 +34,6 @@ #include "storage/browser/file_system/file_system_operation_context.h" #include "storage/browser/file_system/file_system_url.h" #include "storage/browser/file_system/file_system_usage_cache.h" -#include "storage/browser/file_system/obfuscated_file_util.h" #include "storage/browser/file_system/obfuscated_file_util_memory_delegate.h" #include "storage/browser/file_system/sandbox_directory_database.h" #include "storage/browser/file_system/sandbox_file_system_backend_delegate.h" @@ -267,7 +267,7 @@ class ObfuscatedFileUtilTest : public testing::Test, std::unique_ptr<ObfuscatedFileUtil> CreateObfuscatedFileUtil( scoped_refptr<SpecialStoragePolicy> storage_policy) { return ObfuscatedFileUtil::CreateForTesting( - std::move(storage_policy), data_dir_path(), + std::move(storage_policy), data_dir_path(), data_dir_path(), is_incognito() ? incognito_leveldb_environment_.get() : nullptr, is_incognito()); } @@ -372,7 +372,7 @@ class ObfuscatedFileUtilTest : public testing::Test, EXPECT_EQ(!is_incognito(), FileExists(data_path)); const char data[] = "test data"; - const int length = base::size(data) - 1; + const int length = std::size(data) - 1; base::File file = ofu()->CreateOrOpen( context.get(), url, base::File::FLAG_WRITE | base::File::FLAG_OPEN); @@ -1342,7 +1342,7 @@ TEST_P(ObfuscatedFileUtilTest, TestCopyOrMoveFileSuccess) { const int64_t kSourceLength = 5; const int64_t kDestLength = 50; - for (size_t i = 0; i < base::size(kCopyMoveTestCases); ++i) { + for (size_t i = 0; i < std::size(kCopyMoveTestCases); ++i) { SCOPED_TRACE(testing::Message() << "kCopyMoveTestCase " << i); const CopyMoveTestCaseRecord& test_case = kCopyMoveTestCases[i]; SCOPED_TRACE(testing::Message() @@ -1588,7 +1588,7 @@ TEST_P(ObfuscatedFileUtilTest, TestStorageKeyEnumerator) { std::set<blink::StorageKey> storage_keys_expected; storage_keys_expected.insert(storage_key()); - for (size_t i = 0; i < base::size(kOriginEnumerationTestRecords); ++i) { + for (size_t i = 0; i < std::size(kOriginEnumerationTestRecords); ++i) { SCOPED_TRACE(testing::Message() << "Validating kOriginEnumerationTestRecords " << i); const OriginEnumerationTestRecord& record = @@ -1802,7 +1802,7 @@ TEST_P(ObfuscatedFileUtilTest, TestIncompleteDirectoryReading) { EXPECT_EQ(base::File::FILE_OK, AsyncFileTestHelper::ReadDirectory(file_system_context(), empty_path, &entries)); - EXPECT_EQ(base::size(kPath) - 1, entries.size()); + EXPECT_EQ(std::size(kPath) - 1, entries.size()); } TEST_P(ObfuscatedFileUtilTest, TestDirectoryTimestampForCreation) { @@ -2040,13 +2040,7 @@ TEST_P(ObfuscatedFileUtilTest, TestFileEnumeratorTimestamp) { EXPECT_EQ(2, count); } -// crbug.com/176470 -#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_ANDROID) -#define MAYBE_TestQuotaOnCopyFile DISABLED_TestQuotaOnCopyFile -#else -#define MAYBE_TestQuotaOnCopyFile TestQuotaOnCopyFile -#endif -TEST_P(ObfuscatedFileUtilTest, MAYBE_TestQuotaOnCopyFile) { +TEST_P(ObfuscatedFileUtilTest, TestQuotaOnCopyFile) { FileSystemURL from_file(CreateURLFromUTF8("fromfile")); FileSystemURL obstacle_file(CreateURLFromUTF8("obstaclefile")); FileSystemURL to_file1(CreateURLFromUTF8("tofile1")); diff --git a/chromium/storage/browser/file_system/plugin_private_file_system_backend.cc b/chromium/storage/browser/file_system/plugin_private_file_system_backend.cc index c3f943b01a5..f64ae7f8ddb 100644 --- a/chromium/storage/browser/file_system/plugin_private_file_system_backend.cc +++ b/chromium/storage/browser/file_system/plugin_private_file_system_backend.cc @@ -9,6 +9,7 @@ #include <map> #include <memory> #include <utility> +#include <vector> #include "base/bind.h" #include "base/containers/contains.h" @@ -18,6 +19,7 @@ #include "base/synchronization/lock.h" #include "base/task/task_runner_util.h" #include "base/threading/thread_task_runner_handle.h" +#include "base/time/time.h" #include "storage/browser/file_system/async_file_util_adapter.h" #include "storage/browser/file_system/file_system_context.h" #include "storage/browser/file_system/file_system_operation.h" @@ -99,9 +101,20 @@ base::File::Error OpenFileSystemOnFileTaskRunner( } // namespace +PluginPrivateFileSystemBackend::CdmFileInfo::CdmFileInfo( + const std::string& name, + const std::string& legacy_file_system_id) + : name(name), legacy_file_system_id(legacy_file_system_id) {} +PluginPrivateFileSystemBackend::CdmFileInfo::CdmFileInfo(const CdmFileInfo&) = + default; +PluginPrivateFileSystemBackend::CdmFileInfo::CdmFileInfo(CdmFileInfo&&) = + default; +PluginPrivateFileSystemBackend::CdmFileInfo::~CdmFileInfo() = default; + PluginPrivateFileSystemBackend::PluginPrivateFileSystemBackend( scoped_refptr<base::SequencedTaskRunner> file_task_runner, const base::FilePath& profile_path, + const base::FilePath& bucket_base_path, scoped_refptr<SpecialStoragePolicy> special_storage_policy, const FileSystemOptions& file_system_options, leveldb::Env* env_override) @@ -112,7 +125,8 @@ PluginPrivateFileSystemBackend::PluginPrivateFileSystemBackend( plugin_map_(new FileSystemIDToPluginMap(file_task_runner_)) { file_util_ = std::make_unique<AsyncFileUtilAdapter>( std::make_unique<ObfuscatedFileUtil>( - std::move(special_storage_policy), base_path_, env_override, + std::move(special_storage_policy), base_path_, bucket_base_path, + env_override, base::BindRepeating(&FileSystemIDToPluginMap::GetPluginIDForURL, base::Owned(plugin_map_.get())), std::set<std::string>(), nullptr, @@ -301,11 +315,12 @@ void PluginPrivateFileSystemBackend::GetOriginDetailsOnFileTaskRunner( *last_modified_time = base::Time::UnixEpoch(); std::string fsid = IsolatedContext::GetInstance()->RegisterFileSystemForVirtualPath( - kFileSystemTypePluginPrivate, "pluginprivate", base::FilePath()); + kFileSystemTypePluginPrivate, kPluginPrivateRootName, + base::FilePath()); DCHECK(ValidateIsolatedFileSystemId(fsid)); std::string root = GetIsolatedFileSystemRootURIString(origin.GetURL(), fsid, - "pluginprivate"); + kPluginPrivateRootName); std::unique_ptr<FileSystemOperationContext> operation_context( std::make_unique<FileSystemOperationContext>(context)); @@ -356,6 +371,65 @@ void PluginPrivateFileSystemBackend::GetOriginDetailsOnFileTaskRunner( } } +std::vector<PluginPrivateFileSystemBackend::CdmFileInfo> +PluginPrivateFileSystemBackend::GetMediaLicenseFilesForOriginOnFileTaskRunner( + FileSystemContext* context, + const url::Origin& origin) { + DCHECK(file_task_runner_->RunsTasksInCurrentSequence()); + + std::unique_ptr<FileSystemOperationContext> operation_context( + std::make_unique<FileSystemOperationContext>(context)); + + // Determine the available plugin private filesystem directories for this + // origin. Currently the plugin private filesystem is only used by Encrypted + // Media Content Decryption Modules. Each CDM gets a directory based on the + // mimetype (e.g. plugin application/x-ppapi-widevine-cdm uses directory + // application_x-ppapi-widevine-cdm). Enumerate through the set of + // directories so that data from any CDM used by this origin is counted. + base::File::Error error; + base::FilePath path = + obfuscated_file_util()->GetDirectoryForStorageKeyAndType( + blink::StorageKey(origin), "", false, &error); + if (error != base::File::FILE_OK) + return {}; + + std::vector<CdmFileInfo> cdm_files; + base::FileEnumerator directory_enumerator(path, false, + base::FileEnumerator::DIRECTORIES); + base::FilePath plugin_path; + while (!(plugin_path = directory_enumerator.Next()).empty()) { + std::string plugin_name = plugin_path.BaseName().MaybeAsASCII(); + + std::string fsid = + IsolatedContext::GetInstance()->RegisterFileSystemForVirtualPath( + kFileSystemTypePluginPrivate, kPluginPrivateRootName, + base::FilePath()); + DCHECK(ValidateIsolatedFileSystemId(fsid)); + std::string root = GetIsolatedFileSystemRootURIString( + origin.GetURL(), fsid, kPluginPrivateRootName); + + if (OpenFileSystemOnFileTaskRunner( + obfuscated_file_util(), plugin_map_, origin, fsid, plugin_name, + OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT) != base::File::FILE_OK) { + continue; + } + std::unique_ptr<FileSystemFileUtil::AbstractFileEnumerator> enumerator( + obfuscated_file_util()->CreateFileEnumerator( + operation_context.get(), + context->CrackURL( + GURL(root), blink::StorageKey(url::Origin::Create(GURL(root)))), + true)); + + base::FilePath cdm_file_path; + while (!(cdm_file_path = enumerator->Next()).empty()) { + cdm_files.emplace_back(cdm_file_path.BaseName().AsUTF8Unsafe(), + plugin_path.BaseName().AsUTF8Unsafe()); + } + } + + return cdm_files; +} + scoped_refptr<QuotaReservation> PluginPrivateFileSystemBackend::CreateQuotaReservationOnFileTaskRunner( const blink::StorageKey& storage_key, diff --git a/chromium/storage/browser/file_system/plugin_private_file_system_backend.h b/chromium/storage/browser/file_system/plugin_private_file_system_backend.h index 5b36b8826f4..9b1aab71334 100644 --- a/chromium/storage/browser/file_system/plugin_private_file_system_backend.h +++ b/chromium/storage/browser/file_system/plugin_private_file_system_backend.h @@ -54,9 +54,22 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) PluginPrivateFileSystemBackend class FileSystemIDToPluginMap; using StatusCallback = base::OnceCallback<void(base::File::Error result)>; + // Used to migrate media license data to the new backend. + struct COMPONENT_EXPORT(STORAGE_BROWSER) CdmFileInfo { + CdmFileInfo(const std::string& name, + const std::string& legacy_file_system_id); + CdmFileInfo(const CdmFileInfo&); + CdmFileInfo(CdmFileInfo&&); + ~CdmFileInfo(); + + const std::string name; + const std::string legacy_file_system_id; + }; + PluginPrivateFileSystemBackend( scoped_refptr<base::SequencedTaskRunner> file_task_runner, const base::FilePath& profile_path, + const base::FilePath& bucket_base_path, scoped_refptr<SpecialStoragePolicy> special_storage_policy, const FileSystemOptions& file_system_options, leveldb::Env* env_override); @@ -144,13 +157,20 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) PluginPrivateFileSystemBackend int64_t* total_size, base::Time* last_modified_time); + // Used to migrate media license data to the new backend. + // TODO(crbug.com/1231162): Once all media license data has been migrated, the + // PPFS will have no more consumers and we can remove it entirely. + std::vector<CdmFileInfo> GetMediaLicenseFilesForOriginOnFileTaskRunner( + FileSystemContext* context, + const url::Origin& origin); + ObfuscatedFileUtilMemoryDelegate* obfuscated_file_util_memory_delegate(); + const base::FilePath& base_path() const { return base_path_; } private: friend class PluginPrivateFileSystemBackendTest; ObfuscatedFileUtil* obfuscated_file_util(); - const base::FilePath& base_path() const { return base_path_; } const scoped_refptr<base::SequencedTaskRunner> file_task_runner_; const FileSystemOptions file_system_options_; diff --git a/chromium/storage/browser/file_system/quota/quota_backend_impl_unittest.cc b/chromium/storage/browser/file_system/quota/quota_backend_impl_unittest.cc index f03a7b5b5e8..43ce6b8d6c8 100644 --- a/chromium/storage/browser/file_system/quota/quota_backend_impl_unittest.cc +++ b/chromium/storage/browser/file_system/quota/quota_backend_impl_unittest.cc @@ -44,7 +44,9 @@ bool DidReserveQuota(bool accepted, class MockQuotaManagerProxy : public QuotaManagerProxy { public: MockQuotaManagerProxy() - : QuotaManagerProxy(nullptr, base::ThreadTaskRunnerHandle::Get()), + : QuotaManagerProxy(/*quota_manager_impl=*/nullptr, + base::ThreadTaskRunnerHandle::Get(), + /*profile_path=*/base::FilePath()), storage_modified_count_(0), usage_(0), quota_(0) {} @@ -114,7 +116,7 @@ class QuotaBackendImplTest : public testing::Test, in_memory_env_ = leveldb_chrome::NewMemEnv("quota"); file_util_ = ObfuscatedFileUtil::CreateForTesting( /*special_storage_policy=*/nullptr, data_dir_.GetPath(), - in_memory_env_.get(), is_incognito()); + data_dir_.GetPath(), in_memory_env_.get(), is_incognito()); backend_ = std::make_unique<QuotaBackendImpl>( file_task_runner(), file_util_.get(), &file_system_usage_cache_, quota_manager_proxy_.get()); diff --git a/chromium/storage/browser/file_system/sandbox_directory_database.cc b/chromium/storage/browser/file_system/sandbox_directory_database.cc index 9f6bb1bff35..7f6674348d8 100644 --- a/chromium/storage/browser/file_system/sandbox_directory_database.cc +++ b/chromium/storage/browser/file_system/sandbox_directory_database.cc @@ -13,7 +13,6 @@ #include <set> #include "base/containers/stack.h" -#include "base/cxx17_backports.h" #include "base/files/file_enumerator.h" #include "base/files/file_util.h" #include "base/location.h" @@ -305,8 +304,8 @@ bool DatabaseCheckHelper::ScanDirectory() { if (!path_.AppendRelativePath(absolute_file_path, &relative_file_path)) return false; - if (std::find(kExcludes, kExcludes + base::size(kExcludes), - relative_file_path) != kExcludes + base::size(kExcludes)) + if (std::find(kExcludes, kExcludes + std::size(kExcludes), + relative_file_path) != kExcludes + std::size(kExcludes)) continue; if (find_info.IsDirectory()) { diff --git a/chromium/storage/browser/file_system/sandbox_file_stream_reader_unittest.cc b/chromium/storage/browser/file_system/sandbox_file_stream_reader_unittest.cc index 1aca0a236d1..b3763ad382f 100644 --- a/chromium/storage/browser/file_system/sandbox_file_stream_reader_unittest.cc +++ b/chromium/storage/browser/file_system/sandbox_file_stream_reader_unittest.cc @@ -13,6 +13,7 @@ #include "base/bind.h" #include "base/files/scoped_temp_dir.h" +#include "base/memory/scoped_refptr.h" #include "base/run_loop.h" #include "base/test/task_environment.h" #include "net/base/io_buffer.h" @@ -41,17 +42,18 @@ const char kURLOrigin[] = "http://remote/"; class SandboxFileStreamReaderTest : public FileStreamReaderTest { public: - SandboxFileStreamReaderTest() = default; + SandboxFileStreamReaderTest() + : special_storage_policy_( + base::MakeRefCounted<MockSpecialStoragePolicy>()) {} void SetUp() override { ASSERT_TRUE(dir_.CreateUniqueTempDir()); quota_manager_ = base::MakeRefCounted<storage::MockQuotaManager>( /*is_incognito=*/false, dir_.GetPath(), - base::ThreadTaskRunnerHandle::Get(), - /*special_storage_policy=*/nullptr); + base::ThreadTaskRunnerHandle::Get(), special_storage_policy_); quota_manager_proxy_ = base::MakeRefCounted<storage::MockQuotaManagerProxy>( - quota_manager_.get(), base::ThreadTaskRunnerHandle::Get().get()); + quota_manager_.get(), base::ThreadTaskRunnerHandle::Get()); file_system_context_ = CreateFileSystemContextForTesting( quota_manager_proxy_.get(), dir_.GetPath()); @@ -114,8 +116,9 @@ class SandboxFileStreamReaderTest : public FileStreamReaderTest { kFileSystemTypeTemporary, base::FilePath().AppendASCII(file_name)); } - private: - base::ScopedTempDir dir_; + protected: + scoped_refptr<MockSpecialStoragePolicy> special_storage_policy_; + scoped_refptr<FileSystemContext> file_system_context_; scoped_refptr<MockQuotaManager> quota_manager_; scoped_refptr<MockQuotaManagerProxy> quota_manager_proxy_; diff --git a/chromium/storage/browser/file_system/sandbox_file_stream_writer.cc b/chromium/storage/browser/file_system/sandbox_file_stream_writer.cc index e640397e0fa..f82f43bb54a 100644 --- a/chromium/storage/browser/file_system/sandbox_file_stream_writer.cc +++ b/chromium/storage/browser/file_system/sandbox_file_stream_writer.cc @@ -167,7 +167,7 @@ void SandboxFileStreamWriter::DidCreateSnapshotFile( file_system_context_->default_file_task_runner(), platform_path, initial_offset_, FileStreamWriter::OPEN_EXISTING_FILE); } - QuotaManagerProxy* quota_manager_proxy = + const scoped_refptr<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 @@ -227,7 +227,7 @@ void SandboxFileStreamWriter::DidWrite(int write_response) { if (write_response <= 0) { // TODO(crbug.com/1091792): Consider listening explicitly for out // of space errors instead of surfacing all write errors to quota. - QuotaManagerProxy* quota_manager_proxy = + const scoped_refptr<QuotaManagerProxy>& quota_manager_proxy = file_system_context_->quota_manager_proxy(); if (quota_manager_proxy) { quota_manager_proxy->NotifyWriteFailed(url_.storage_key()); diff --git a/chromium/storage/browser/file_system/sandbox_file_stream_writer_unittest.cc b/chromium/storage/browser/file_system/sandbox_file_stream_writer_unittest.cc index 6e173b48c6b..c02bd5e1a49 100644 --- a/chromium/storage/browser/file_system/sandbox_file_stream_writer_unittest.cc +++ b/chromium/storage/browser/file_system/sandbox_file_stream_writer_unittest.cc @@ -42,16 +42,19 @@ const char kURLOrigin[] = "http://remote/"; class SandboxFileStreamWriterTest : public FileStreamWriterTest { public: - SandboxFileStreamWriterTest() = default; + SandboxFileStreamWriterTest() + : special_storage_policy_( + base::MakeRefCounted<MockSpecialStoragePolicy>()) {} + ~SandboxFileStreamWriterTest() override = default; void SetUp() override { ASSERT_TRUE(dir_.CreateUniqueTempDir()); quota_manager_ = base::MakeRefCounted<storage::MockQuotaManager>( is_incognito(), dir_.GetPath(), base::ThreadTaskRunnerHandle::Get(), - nullptr); + special_storage_policy_); quota_manager_proxy_ = base::MakeRefCounted<storage::MockQuotaManagerProxy>( - quota_manager_.get(), base::ThreadTaskRunnerHandle::Get().get()); + quota_manager_.get(), base::ThreadTaskRunnerHandle::Get()); file_system_context_ = CreateFileSystemContext(quota_manager_proxy_.get(), dir_); @@ -75,7 +78,8 @@ class SandboxFileStreamWriterTest : public FileStreamWriterTest { } protected: - base::ScopedTempDir dir_; + scoped_refptr<MockSpecialStoragePolicy> special_storage_policy_; + scoped_refptr<FileSystemContext> file_system_context_; scoped_refptr<MockQuotaManager> quota_manager_; scoped_refptr<MockQuotaManagerProxy> quota_manager_proxy_; diff --git a/chromium/storage/browser/file_system/sandbox_file_system_backend_delegate.cc b/chromium/storage/browser/file_system/sandbox_file_system_backend_delegate.cc index f409b5885d9..c78d0586aa0 100644 --- a/chromium/storage/browser/file_system/sandbox_file_system_backend_delegate.cc +++ b/chromium/storage/browser/file_system/sandbox_file_system_backend_delegate.cc @@ -182,6 +182,7 @@ SandboxFileSystemBackendDelegate::SandboxFileSystemBackendDelegate( scoped_refptr<QuotaManagerProxy> quota_manager_proxy, scoped_refptr<base::SequencedTaskRunner> file_task_runner, const base::FilePath& profile_path, + const base::FilePath& bucket_base_path, scoped_refptr<SpecialStoragePolicy> special_storage_policy, const FileSystemOptions& file_system_options, leveldb::Env* env_override) @@ -191,6 +192,7 @@ SandboxFileSystemBackendDelegate::SandboxFileSystemBackendDelegate( std::make_unique<ObfuscatedFileUtil>( special_storage_policy, profile_path.Append(kFileSystemDirectory), + bucket_base_path, env_override, base::BindRepeating(&GetTypeStringForURL), GetKnownTypeStrings(), @@ -675,11 +677,13 @@ SandboxFileSystemBackendDelegate::memory_file_util_delegate() { std::unique_ptr<ObfuscatedFileUtil> ObfuscatedFileUtil::CreateForTesting( scoped_refptr<SpecialStoragePolicy> special_storage_policy, const base::FilePath& file_system_directory, + const base::FilePath& bucket_base_path, leveldb::Env* env_override, bool is_incognito) { return std::make_unique<ObfuscatedFileUtil>( - std::move(special_storage_policy), file_system_directory, env_override, - base::BindRepeating(&GetTypeStringForURL), GetKnownTypeStrings(), + std::move(special_storage_policy), file_system_directory, + bucket_base_path, env_override, base::BindRepeating(&GetTypeStringForURL), + GetKnownTypeStrings(), /*sandbox_delegate=*/nullptr, is_incognito); } diff --git a/chromium/storage/browser/file_system/sandbox_file_system_backend_delegate.h b/chromium/storage/browser/file_system/sandbox_file_system_backend_delegate.h index 7abbc1a959e..b02945f4603 100644 --- a/chromium/storage/browser/file_system/sandbox_file_system_backend_delegate.h +++ b/chromium/storage/browser/file_system/sandbox_file_system_backend_delegate.h @@ -24,6 +24,7 @@ #include "storage/browser/file_system/file_system_options.h" #include "storage/browser/file_system/file_system_quota_util.h" #include "storage/browser/file_system/task_runner_bound_observer_list.h" +#include "storage/browser/quota/quota_manager_proxy.h" #include "third_party/abseil-cpp/absl/types/optional.h" namespace base { @@ -99,6 +100,7 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) SandboxFileSystemBackendDelegate scoped_refptr<QuotaManagerProxy> quota_manager_proxy, scoped_refptr<base::SequencedTaskRunner> file_task_runner, const base::FilePath& profile_path, + const base::FilePath& bucket_base_path, scoped_refptr<SpecialStoragePolicy> special_storage_policy, const FileSystemOptions& file_system_options, leveldb::Env* env_override); @@ -210,6 +212,10 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) SandboxFileSystemBackendDelegate return file_system_options_; } + const scoped_refptr<QuotaManagerProxy> quota_manager_proxy() const { + return quota_manager_proxy_; + } + FileSystemFileUtil* sync_file_util(); base::WeakPtr<ObfuscatedFileUtilMemoryDelegate> memory_file_util_delegate(); diff --git a/chromium/storage/browser/file_system/sandbox_file_system_backend_delegate_unittest.cc b/chromium/storage/browser/file_system/sandbox_file_system_backend_delegate_unittest.cc index c6fb104ae60..0f6a8f00bed 100644 --- a/chromium/storage/browser/file_system/sandbox_file_system_backend_delegate_unittest.cc +++ b/chromium/storage/browser/file_system/sandbox_file_system_backend_delegate_unittest.cc @@ -40,8 +40,9 @@ class SandboxFileSystemBackendDelegateTest : public testing::Test { nullptr, base::ThreadTaskRunnerHandle::Get()); delegate_ = std::make_unique<SandboxFileSystemBackendDelegate>( quota_manager_proxy_.get(), base::ThreadTaskRunnerHandle::Get().get(), - data_dir_.GetPath(), /*special_storage_policy=*/nullptr, - CreateAllowFileAccessOptions(), /*env_override=*/nullptr); + data_dir_.GetPath(), data_dir_.GetPath(), + /*special_storage_policy=*/nullptr, CreateAllowFileAccessOptions(), + /*env_override=*/nullptr); } bool IsAccessValid(const FileSystemURL& url) const { diff --git a/chromium/storage/browser/file_system/sandbox_file_system_backend_unittest.cc b/chromium/storage/browser/file_system/sandbox_file_system_backend_unittest.cc index 1367e29082a..e1f98e46379 100644 --- a/chromium/storage/browser/file_system/sandbox_file_system_backend_unittest.cc +++ b/chromium/storage/browser/file_system/sandbox_file_system_backend_unittest.cc @@ -11,7 +11,6 @@ #include <vector> #include "base/bind.h" -#include "base/cxx17_backports.h" #include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" #include "base/run_loop.h" @@ -93,7 +92,7 @@ class SandboxFileSystemBackendTest incognito_env_override_ = leveldb_chrome::NewMemEnv("FileSystem"); delegate_ = std::make_unique<SandboxFileSystemBackendDelegate>( /*quota_manager_proxy=*/nullptr, base::ThreadTaskRunnerHandle::Get(), - data_dir_.GetPath(), + data_dir_.GetPath(), data_dir_.GetPath(), /*special_storage_policy=*/nullptr, options, options.is_in_memory() ? incognito_env_override_.get() : nullptr); } @@ -173,8 +172,8 @@ TEST_P(SandboxFileSystemBackendTest, EnumerateOrigins) { "http://www.foo.com:8080/", "http://www.foo.com:80/", }; - size_t temporary_size = base::size(temporary_origins); - size_t persistent_size = base::size(persistent_origins); + size_t temporary_size = std::size(temporary_origins); + size_t persistent_size = std::size(persistent_origins); std::set<blink::StorageKey> temporary_set, persistent_set; for (size_t i = 0; i < temporary_size; ++i) { CreateOriginTypeDirectory(temporary_origins[i], kFileSystemTypeTemporary); @@ -211,12 +210,11 @@ TEST_P(SandboxFileSystemBackendTest, EnumerateOrigins) { } TEST_P(SandboxFileSystemBackendTest, GetRootPathCreateAndExamine) { - std::vector<base::FilePath> returned_root_path( - base::size(kRootPathTestCases)); + std::vector<base::FilePath> returned_root_path(std::size(kRootPathTestCases)); SetUpNewBackend(CreateAllowFileAccessOptions()); // Create a new root directory. - for (size_t i = 0; i < base::size(kRootPathTestCases); ++i) { + for (size_t i = 0; i < std::size(kRootPathTestCases); ++i) { SCOPED_TRACE(testing::Message() << "RootPath (create) #" << i << " " << kRootPathTestCases[i].expected_path); @@ -235,7 +233,7 @@ TEST_P(SandboxFileSystemBackendTest, GetRootPathCreateAndExamine) { // Get the root directory with create=false and see if we get the // same directory. - for (size_t i = 0; i < base::size(kRootPathTestCases); ++i) { + for (size_t i = 0; i < std::size(kRootPathTestCases); ++i) { SCOPED_TRACE(testing::Message() << "RootPath (get) #" << i << " " << kRootPathTestCases[i].expected_path); @@ -250,8 +248,7 @@ TEST_P(SandboxFileSystemBackendTest, GetRootPathCreateAndExamine) { TEST_P(SandboxFileSystemBackendTest, GetRootPathCreateAndExamineWithNewBackend) { - std::vector<base::FilePath> returned_root_path( - base::size(kRootPathTestCases)); + std::vector<base::FilePath> returned_root_path(std::size(kRootPathTestCases)); SetUpNewBackend(CreateAllowFileAccessOptions()); base::FilePath root_path1; @@ -270,7 +267,7 @@ TEST_P(SandboxFileSystemBackendTest, GetRootPathGetWithoutCreate) { SetUpNewBackend(CreateDisallowFileAccessOptions()); // Try to get a root directory without creating. - for (size_t i = 0; i < base::size(kRootPathTestCases); ++i) { + for (size_t i = 0; i < std::size(kRootPathTestCases); ++i) { SCOPED_TRACE(testing::Message() << "RootPath (create=false) #" << i << " " << kRootPathTestCases[i].expected_path); EXPECT_FALSE(GetRootPath(kRootPathTestCases[i].origin_url, @@ -283,7 +280,7 @@ TEST_P(SandboxFileSystemBackendTest, GetRootPathInIncognito) { SetUpNewBackend(CreateIncognitoFileSystemOptions()); // Try to get a root directory. - for (size_t i = 0; i < base::size(kRootPathTestCases); ++i) { + for (size_t i = 0; i < std::size(kRootPathTestCases); ++i) { SCOPED_TRACE(testing::Message() << "RootPath (incognito) #" << i << " " << kRootPathTestCases[i].expected_path); EXPECT_EQ(IsPersistentFileSystemEnabledIncognito() || @@ -296,7 +293,7 @@ TEST_P(SandboxFileSystemBackendTest, GetRootPathInIncognito) { TEST_P(SandboxFileSystemBackendTest, GetRootPathFileURI) { SetUpNewBackend(CreateDisallowFileAccessOptions()); - for (size_t i = 0; i < base::size(kRootPathFileURITestCases); ++i) { + for (size_t i = 0; i < std::size(kRootPathFileURITestCases); ++i) { SCOPED_TRACE(testing::Message() << "RootPathFileURI (disallow) #" << i << " " << kRootPathFileURITestCases[i].expected_path); @@ -308,7 +305,7 @@ TEST_P(SandboxFileSystemBackendTest, GetRootPathFileURI) { TEST_P(SandboxFileSystemBackendTest, GetRootPathFileURIWithAllowFlag) { SetUpNewBackend(CreateAllowFileAccessOptions()); - for (size_t i = 0; i < base::size(kRootPathFileURITestCases); ++i) { + for (size_t i = 0; i < std::size(kRootPathFileURITestCases); ++i) { SCOPED_TRACE(testing::Message() << "RootPathFileURI (allow) #" << i << " " << kRootPathFileURITestCases[i].expected_path); diff --git a/chromium/storage/browser/file_system/sandbox_origin_database_unittest.cc b/chromium/storage/browser/file_system/sandbox_origin_database_unittest.cc index 8f9c19880d4..95fa91ca997 100644 --- a/chromium/storage/browser/file_system/sandbox_origin_database_unittest.cc +++ b/chromium/storage/browser/file_system/sandbox_origin_database_unittest.cc @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "storage/browser/file_system/sandbox_origin_database.h" + #include <stddef.h> #include <algorithm> @@ -11,12 +13,10 @@ #include <string> #include <vector> -#include "base/cxx17_backports.h" #include "base/files/file.h" #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" -#include "storage/browser/file_system/sandbox_origin_database.h" #include "storage/browser/test/sandbox_database_test_helper.h" #include "storage/common/file_system/file_system_util.h" #include "testing/gtest/include/gtest/gtest.h" @@ -208,7 +208,7 @@ TEST(SandboxOriginDatabaseTest, DatabaseRecoveryTest) { }; auto database = std::make_unique<SandboxOriginDatabase>(kFSDir, nullptr); - for (size_t i = 0; i < base::size(kOrigins); ++i) { + for (size_t i = 0; i < std::size(kOrigins); ++i) { base::FilePath path; EXPECT_FALSE(database->HasOriginPath(kOrigins[i])); EXPECT_TRUE(database->GetPathForOrigin(kOrigins[i], &path)); @@ -242,7 +242,7 @@ TEST(SandboxOriginDatabaseTest, DatabaseRecoveryTest) { // Expect all but last added origin will be repaired back, and kOrigins[1] // should be dropped due to absence of backing directory. - EXPECT_EQ(base::size(kOrigins) - 2, origins_in_db.size()); + EXPECT_EQ(std::size(kOrigins) - 2, origins_in_db.size()); const std::string kOrigin("piyo.example.org"); EXPECT_FALSE(database->HasOriginPath(kOrigin)); diff --git a/chromium/storage/browser/file_system/task_runner_bound_observer_list.h b/chromium/storage/browser/file_system/task_runner_bound_observer_list.h index 6f960a09dad..43cd2c210fc 100644 --- a/chromium/storage/browser/file_system/task_runner_bound_observer_list.h +++ b/chromium/storage/browser/file_system/task_runner_bound_observer_list.h @@ -12,7 +12,6 @@ #include "base/location.h" #include "base/memory/scoped_refptr.h" #include "base/task/sequenced_task_runner.h" -#include "base/threading/thread.h" namespace storage { diff --git a/chromium/storage/browser/quota/OWNERS b/chromium/storage/browser/quota/OWNERS index 96896d848b8..2d495dc641c 100644 --- a/chromium/storage/browser/quota/OWNERS +++ b/chromium/storage/browser/quota/OWNERS @@ -2,11 +2,9 @@ ayui@chromium.org # Secondary +asully@chromium.org jarrydg@chromium.org jsbell@chromium.org -kinuko@chromium.org -mek@chromium.org -pwnall@chromium.org per-file *.mojom=set noparent -per-file *.mojom=file://ipc/SECURITY_OWNERS
\ No newline at end of file +per-file *.mojom=file://ipc/SECURITY_OWNERS diff --git a/chromium/storage/browser/quota/quota_callbacks.h b/chromium/storage/browser/quota/quota_callbacks.h index 5598c143d84..18eda095da5 100644 --- a/chromium/storage/browser/quota/quota_callbacks.h +++ b/chromium/storage/browser/quota/quota_callbacks.h @@ -29,7 +29,6 @@ struct UsageInfo; using UsageInfoEntries = std::vector<UsageInfo>; // Common callback types that are used throughout in the quota module. -using AddChangeListenerCallback = base::OnceCallback<void()>; using UsageCallback = base::OnceCallback<void(int64_t usage, int64_t unlimited_usage)>; using QuotaCallback = diff --git a/chromium/storage/browser/quota/quota_client_type.cc b/chromium/storage/browser/quota/quota_client_type.cc index cb737075211..02c4e4f1b5b 100644 --- a/chromium/storage/browser/quota/quota_client_type.cc +++ b/chromium/storage/browser/quota/quota_client_type.cc @@ -17,6 +17,7 @@ const QuotaClientTypes& AllQuotaClientTypes() { QuotaClientType::kServiceWorker, QuotaClientType::kBackgroundFetch, QuotaClientType::kNativeIO, + QuotaClientType::kMediaLicense, }}; return *all; } diff --git a/chromium/storage/browser/quota/quota_client_type.h b/chromium/storage/browser/quota/quota_client_type.h index d90cf800803..59a245bad41 100644 --- a/chromium/storage/browser/quota/quota_client_type.h +++ b/chromium/storage/browser/quota/quota_client_type.h @@ -22,6 +22,7 @@ enum class QuotaClientType { kServiceWorker = 5, kBackgroundFetch = 6, kNativeIO = 7, + kMediaLicense = 8, }; // Set of QuotaClientType values. diff --git a/chromium/storage/browser/quota/quota_database.cc b/chromium/storage/browser/quota/quota_database.cc index dc62026cc97..38bbb9f343c 100644 --- a/chromium/storage/browser/quota/quota_database.cc +++ b/chromium/storage/browser/quota/quota_database.cc @@ -17,10 +17,12 @@ #include "base/dcheck_is_on.h" #include "base/files/file_util.h" #include "base/metrics/histogram_functions.h" +#include "base/sequence_checker.h" #include "components/services/storage/public/cpp/buckets/constants.h" +#include "components/services/storage/public/cpp/quota_error_or.h" #include "sql/database.h" -#include "sql/error_metrics.h" #include "sql/meta_table.h" +#include "sql/sqlite_result_code.h" #include "sql/statement.h" #include "sql/transaction.h" #include "storage/browser/quota/quota_database_migrations.h" @@ -89,7 +91,7 @@ const QuotaDatabase::TableSchema QuotaDatabase::kTables[] = { " last_modified INTEGER NOT NULL," " expiration INTEGER NOT NULL," " quota INTEGER NOT NULL)"}}; -const size_t QuotaDatabase::kTableCount = base::size(QuotaDatabase::kTables); +const size_t QuotaDatabase::kTableCount = std::size(QuotaDatabase::kTables); // static const QuotaDatabase::IndexSchema QuotaDatabase::kIndexes[] = { @@ -99,7 +101,7 @@ const QuotaDatabase::IndexSchema QuotaDatabase::kIndexes[] = { {"buckets_by_last_modified", kBucketTable, "(type, last_modified)", false}, {"buckets_by_expiration", kBucketTable, "(expiration)", false}, }; -const size_t QuotaDatabase::kIndexCount = base::size(QuotaDatabase::kIndexes); +const size_t QuotaDatabase::kIndexCount = std::size(QuotaDatabase::kIndexes); QuotaDatabase::BucketTableEntry::BucketTableEntry() = default; @@ -127,7 +129,18 @@ QuotaDatabase::BucketTableEntry::BucketTableEntry( last_modified(last_modified) {} // QuotaDatabase ------------------------------------------------------------ -QuotaDatabase::QuotaDatabase(const base::FilePath& path) : db_file_path_(path) { +QuotaDatabase::QuotaDatabase(const base::FilePath& profile_path) + : storage_directory_( + profile_path.empty() + ? nullptr + : std::make_unique<StorageDirectory>(profile_path)), + db_file_path_( + profile_path.empty() + ? base::FilePath() + : storage_directory_->path().AppendASCII(kDatabaseName)), + legacy_db_file_path_(profile_path.empty() + ? base::FilePath() + : profile_path.AppendASCII(kDatabaseName)) { DETACH_FROM_SEQUENCE(sequence_checker_); } @@ -138,10 +151,12 @@ QuotaDatabase::~QuotaDatabase() { } } +constexpr char QuotaDatabase::kDatabaseName[]; + QuotaErrorOr<int64_t> QuotaDatabase::GetHostQuota(const std::string& host, StorageType type) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - QuotaError open_error = EnsureOpened(EnsureOpenedMode::kFailIfNotFound); + QuotaError open_error = EnsureOpened(); if (open_error != QuotaError::kNone) return open_error; @@ -163,7 +178,7 @@ QuotaError QuotaDatabase::SetHostQuota(const std::string& host, int64_t quota) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_GE(quota, 0); - QuotaError open_error = EnsureOpened(EnsureOpenedMode::kCreateIfNotFound); + QuotaError open_error = EnsureOpened(); if (open_error != QuotaError::kNone) return open_error; @@ -228,16 +243,6 @@ QuotaErrorOr<BucketInfo> QuotaDatabase::CreateBucketForTesting( blink::mojom::StorageType storage_type) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - // TODO(crbug/1210252): Update to not execute 2 sql statements on creation. - QuotaErrorOr<BucketInfo> bucket_result = - GetBucket(storage_key, bucket_name, storage_type); - - if (bucket_result.ok()) - return QuotaError::kEntryExistsError; - - if (bucket_result.error() != QuotaError::kNotFound) - return bucket_result.error(); - base::Time now = base::Time::Now(); return CreateBucketInternal(storage_key, storage_type, bucket_name, /*use_count=*/0, now, now); @@ -248,7 +253,7 @@ QuotaErrorOr<BucketInfo> QuotaDatabase::GetBucket( const std::string& bucket_name, blink::mojom::StorageType storage_type) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - QuotaError open_error = EnsureOpened(EnsureOpenedMode::kFailIfNotFound); + QuotaError open_error = EnsureOpened(); if (open_error != QuotaError::kNone) return open_error; @@ -273,10 +278,41 @@ QuotaErrorOr<BucketInfo> QuotaDatabase::GetBucket( statement.ColumnInt(2)); } +QuotaErrorOr<BucketInfo> QuotaDatabase::GetBucketById(BucketId bucket_id) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + QuotaError open_error = EnsureOpened(); + if (open_error != QuotaError::kNone) + return open_error; + + static constexpr char kSql[] = + // clang-format off + "SELECT storage_key, type, name, expiration, quota " + "FROM buckets " + "WHERE id = ?"; + // clang-format on + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindInt64(0, bucket_id.value()); + + if (!statement.Step()) { + return statement.Succeeded() ? QuotaError::kNotFound + : QuotaError::kDatabaseError; + } + + absl::optional<StorageKey> storage_key = + StorageKey::Deserialize(statement.ColumnString(0)); + if (!storage_key.has_value()) + return QuotaError::kNotFound; + + return BucketInfo(bucket_id, storage_key.value(), + static_cast<StorageType>(statement.ColumnInt(1)), + statement.ColumnString(2), statement.ColumnTime(3), + statement.ColumnInt(4)); +} + QuotaErrorOr<std::set<BucketLocator>> QuotaDatabase::GetBucketsForType( StorageType type) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - QuotaError open_error = EnsureOpened(EnsureOpenedMode::kFailIfNotFound); + QuotaError open_error = EnsureOpened(); if (open_error != QuotaError::kNone) return open_error; @@ -303,7 +339,7 @@ QuotaErrorOr<std::set<BucketLocator>> QuotaDatabase::GetBucketsForHost( const std::string& host, blink::mojom::StorageType storage_type) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - QuotaError open_error = EnsureOpened(EnsureOpenedMode::kFailIfNotFound); + QuotaError open_error = EnsureOpened(); if (open_error != QuotaError::kNone) return open_error; @@ -331,7 +367,7 @@ QuotaErrorOr<std::set<BucketLocator>> QuotaDatabase::GetBucketsForStorageKey( const StorageKey& storage_key, StorageType type) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - QuotaError open_error = EnsureOpened(EnsureOpenedMode::kFailIfNotFound); + QuotaError open_error = EnsureOpened(); if (open_error != QuotaError::kNone) return open_error; @@ -355,27 +391,21 @@ QuotaError QuotaDatabase::SetStorageKeyLastAccessTime( StorageType type, base::Time last_accessed) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - QuotaError open_error = EnsureOpened(EnsureOpenedMode::kFailIfNotFound); + QuotaError open_error = EnsureOpened(); if (open_error != QuotaError::kNone) return open_error; - // Check if bucket exists first. Running an update statement on a bucket that - // doesn't exist fails DCHECK and crashes. - // TODO(crbug/1210252): Update to not execute 2 sql statements. - QuotaErrorOr<BucketInfo> result = - GetBucket(storage_key, kDefaultBucketName, type); - if (!result.ok()) - return result.error(); - // clang-format off static constexpr char kSql[] = "UPDATE buckets " "SET use_count = use_count + 1, last_accessed = ? " - "WHERE id = ?"; + "WHERE storage_key = ? AND type = ? AND name = ?"; // clang-format on sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); statement.BindTime(0, last_accessed); - statement.BindInt64(1, result->id.value()); + statement.BindString(1, storage_key.Serialize()); + statement.BindInt(2, static_cast<int>(type)); + statement.BindString(3, kDefaultBucketName); if (!statement.Run()) return QuotaError::kDatabaseError; @@ -388,17 +418,10 @@ QuotaError QuotaDatabase::SetBucketLastAccessTime(BucketId bucket_id, base::Time last_accessed) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(!bucket_id.is_null()); - QuotaError open_error = EnsureOpened(EnsureOpenedMode::kFailIfNotFound); + QuotaError open_error = EnsureOpened(); if (open_error != QuotaError::kNone) return open_error; - // Check if bucket exists first. Running an update statement on a bucket that - // doesn't exist fails DCHECK and crashes. - // TODO(crbug/1210252): Update to not execute 2 sql statements. - QuotaErrorOr<BucketTableEntry> entry = GetBucketInfo(bucket_id); - if (!entry.ok()) - return entry.error(); - // clang-format off static constexpr char kSql[] = "UPDATE buckets " @@ -420,17 +443,10 @@ QuotaError QuotaDatabase::SetBucketLastModifiedTime(BucketId bucket_id, base::Time last_modified) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(!bucket_id.is_null()); - QuotaError open_error = EnsureOpened(EnsureOpenedMode::kFailIfNotFound); + QuotaError open_error = EnsureOpened(); if (open_error != QuotaError::kNone) return open_error; - // Check if bucket exists first. Running an update statement on a bucket that - // doesn't exist fails DCHECK and crashes. - // TODO(crbug/1210252): Update to not execute 2 sql statements. - QuotaErrorOr<BucketTableEntry> entry = GetBucketInfo(bucket_id); - if (!entry.ok()) - return entry.error(); - static constexpr char kSql[] = "UPDATE buckets SET last_modified = ? WHERE id = ?"; sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); @@ -447,7 +463,7 @@ QuotaError QuotaDatabase::SetBucketLastModifiedTime(BucketId bucket_id, QuotaError QuotaDatabase::RegisterInitialStorageKeyInfo( base::flat_map<StorageType, std::set<StorageKey>> storage_keys_by_type) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - QuotaError open_error = EnsureOpened(EnsureOpenedMode::kCreateIfNotFound); + QuotaError open_error = EnsureOpened(); if (open_error != QuotaError::kNone) return open_error; @@ -487,7 +503,7 @@ QuotaErrorOr<QuotaDatabase::BucketTableEntry> QuotaDatabase::GetBucketInfo( BucketId bucket_id) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(!bucket_id.is_null()); - QuotaError open_error = EnsureOpened(EnsureOpenedMode::kFailIfNotFound); + QuotaError open_error = EnsureOpened(); if (open_error != QuotaError::kNone) return open_error; @@ -525,7 +541,7 @@ QuotaErrorOr<QuotaDatabase::BucketTableEntry> QuotaDatabase::GetBucketInfo( QuotaError QuotaDatabase::DeleteHostQuota(const std::string& host, StorageType type) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - QuotaError open_error = EnsureOpened(EnsureOpenedMode::kFailIfNotFound); + QuotaError open_error = EnsureOpened(); if (open_error != QuotaError::kNone) return open_error; @@ -545,7 +561,7 @@ QuotaError QuotaDatabase::DeleteHostQuota(const std::string& host, QuotaError QuotaDatabase::DeleteBucketInfo(BucketId bucket_id) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(!bucket_id.is_null()); - QuotaError open_error = EnsureOpened(EnsureOpenedMode::kFailIfNotFound); + QuotaError open_error = EnsureOpened(); if (open_error != QuotaError::kNone) return open_error; @@ -556,7 +572,19 @@ QuotaError QuotaDatabase::DeleteBucketInfo(BucketId bucket_id) { if (!statement.Run()) return QuotaError::kDatabaseError; + // Scheduling this commit introduces the chance of inconsistencies + // between the buckets table and data stored on disk in the file system. + // If there is a crash or a battery failure before the transaction is + // committed, the bucket directory may be deleted from the file system, + // while an entry still may exist in the database. + // + // While this is not ideal, this does not introduce any new edge case. + // We should check that bucket IDs have existing associated directories, + // because database corruption could result in invalid bucket IDs. + // TODO(crbug.com/1314567): For handling inconsistencies between the db and + // the file system. ScheduleCommit(); + return QuotaError::kNone; } @@ -565,7 +593,7 @@ QuotaErrorOr<BucketLocator> QuotaDatabase::GetLRUBucket( const std::set<BucketId>& bucket_exceptions, SpecialStoragePolicy* special_storage_policy) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - QuotaError open_error = EnsureOpened(EnsureOpenedMode::kFailIfNotFound); + QuotaError open_error = EnsureOpened(); if (open_error != QuotaError::kNone) return open_error; @@ -606,7 +634,7 @@ QuotaErrorOr<BucketLocator> QuotaDatabase::GetLRUBucket( QuotaErrorOr<std::set<StorageKey>> QuotaDatabase::GetStorageKeysForType( StorageType type) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - QuotaError open_error = EnsureOpened(EnsureOpenedMode::kFailIfNotFound); + QuotaError open_error = EnsureOpened(); if (open_error != QuotaError::kNone) return open_error; @@ -632,7 +660,7 @@ QuotaErrorOr<std::set<BucketLocator>> QuotaDatabase::GetBucketsModifiedBetween( base::Time begin, base::Time end) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - QuotaError open_error = EnsureOpened(EnsureOpenedMode::kFailIfNotFound); + QuotaError open_error = EnsureOpened(); if (open_error != QuotaError::kNone) return open_error; @@ -664,7 +692,7 @@ QuotaErrorOr<std::set<BucketLocator>> QuotaDatabase::GetBucketsModifiedBetween( bool QuotaDatabase::IsBootstrapped() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - if (EnsureOpened(EnsureOpenedMode::kCreateIfNotFound) != QuotaError::kNone) + if (EnsureOpened() != QuotaError::kNone) return false; int flag = 0; @@ -673,7 +701,7 @@ bool QuotaDatabase::IsBootstrapped() { QuotaError QuotaDatabase::SetIsBootstrapped(bool bootstrap_flag) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - QuotaError open_error = EnsureOpened(EnsureOpenedMode::kCreateIfNotFound); + QuotaError open_error = EnsureOpened(); if (open_error != QuotaError::kNone) return open_error; @@ -687,6 +715,30 @@ QuotaError QuotaDatabase::SetIsBootstrapped(bool bootstrap_flag) { : QuotaError::kDatabaseError; } +QuotaError QuotaDatabase::RazeAndReopen() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + // Try creating a database one last time if there isn't one. + if (!db_) { + if (!db_file_path_.empty()) { + DCHECK(!legacy_db_file_path_.empty()); + sql::Database::Delete(db_file_path_); + sql::Database::Delete(legacy_db_file_path_); + } + return EnsureOpened(); + } + + // Abort the long-running transaction. + db_->RollbackTransaction(); + + // Raze and close the database. Reset `db_` to nullptr so EnsureOpened will + // recreate the database. + if (!db_->Raze()) + return QuotaError::kDatabaseError; + db_ = nullptr; + + return EnsureOpened(); +} + QuotaError QuotaDatabase::CorruptForTesting( base::OnceCallback<void(const base::FilePath&)> corrupter) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); @@ -710,6 +762,11 @@ QuotaError QuotaDatabase::CorruptForTesting( return QuotaError::kNone; } +void QuotaDatabase::SetDisabledForTesting(bool disable) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + is_disabled_ = disable; +} + void QuotaDatabase::Commit() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (!db_) @@ -733,7 +790,7 @@ void QuotaDatabase::ScheduleCommit() { &QuotaDatabase::Commit); } -QuotaError QuotaDatabase::EnsureOpened(EnsureOpenedMode mode) { +QuotaError QuotaDatabase::EnsureOpened() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (db_) return QuotaError::kNone; @@ -743,14 +800,12 @@ QuotaError QuotaDatabase::EnsureOpened(EnsureOpenedMode mode) { if (is_disabled_) return QuotaError::kDatabaseError; - bool in_memory_only = db_file_path_.empty(); - if (mode == EnsureOpenedMode::kFailIfNotFound && - (in_memory_only || !base::PathExists(db_file_path_))) { - return QuotaError::kNotFound; - } - db_ = std::make_unique<sql::Database>(sql::DatabaseOptions{ .exclusive_locking = true, + // The quota database is a critical storage component. If it's corrupted, + // all client-side storage APIs fail, because they don't know where their + // data is stored. + .flush_to_media = true, .page_size = 4096, .cache_size = 500, }); @@ -765,9 +820,21 @@ QuotaError QuotaDatabase::EnsureOpened(EnsureOpenedMode mode) { sqlite_error_code); })); + // Migrate an existing database from the old path. + if (!db_file_path_.empty() && !MoveLegacyDatabase()) { + if (!ResetStorage()) { + is_disabled_ = true; + db_.reset(); + meta_table_.reset(); + return QuotaError::kDatabaseError; + } + // ResetStorage() has succeeded and database is already open. + return QuotaError::kNone; + } + if (!OpenDatabase() || !EnsureDatabaseVersion()) { LOG(ERROR) << "Could not open the quota database, resetting."; - if (!ResetSchema()) { + if (db_file_path_.empty() || !ResetStorage()) { LOG(ERROR) << "Failed to reset the quota database."; is_disabled_ = true; db_.reset(); @@ -782,7 +849,36 @@ QuotaError QuotaDatabase::EnsureOpened(EnsureOpenedMode mode) { return QuotaError::kNone; } +bool QuotaDatabase::MoveLegacyDatabase() { + // Migration was added on 04/2022 (https://crrev.com/c/3513545). + // Cleanup after enough time has passed. + if (base::PathExists(db_file_path_) || + !base::PathExists(legacy_db_file_path_)) { + return true; + } + + if (!base::CreateDirectory(db_file_path_.DirName()) || + !base::CopyFile(legacy_db_file_path_, db_file_path_)) { + sql::Database::Delete(db_file_path_); + return false; + } + + base::FilePath legacy_journal_path = + sql::Database::JournalPath(legacy_db_file_path_); + if (base::PathExists(legacy_journal_path) && + !base::CopyFile(legacy_journal_path, + sql::Database::JournalPath(db_file_path_))) { + sql::Database::Delete(db_file_path_); + return false; + } + + sql::Database::Delete(legacy_db_file_path_); + return true; +} + bool QuotaDatabase::OpenDatabase() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + // Open in memory database. if (db_file_path_.empty()) { if (db_->OpenInMemory()) @@ -844,6 +940,8 @@ bool QuotaDatabase::EnsureDatabaseVersion() { } bool QuotaDatabase::CreateSchema() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + // TODO(kinuko): Factor out the common code to create databases. sql::Transaction transaction(db_.get()); if (!transaction.Begin()) @@ -868,6 +966,8 @@ bool QuotaDatabase::CreateSchema() { } bool QuotaDatabase::CreateTable(const TableSchema& table) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + std::string sql("CREATE TABLE "); sql += table.table_name; sql += table.columns; @@ -879,6 +979,8 @@ bool QuotaDatabase::CreateTable(const TableSchema& table) { } bool QuotaDatabase::CreateIndex(const IndexSchema& index) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + std::string sql; if (index.unique) sql += "CREATE UNIQUE INDEX "; @@ -895,51 +997,39 @@ bool QuotaDatabase::CreateIndex(const IndexSchema& index) { return true; } -bool QuotaDatabase::ResetSchema() { +bool QuotaDatabase::ResetStorage() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(!db_file_path_.empty()); - DCHECK(base::PathExists(db_file_path_)); + DCHECK(storage_directory_); DCHECK(!db_ || !db_->transaction_nesting()); VLOG(1) << "Deleting existing quota data and starting over."; db_.reset(); meta_table_.reset(); - if (!sql::Database::Delete(db_file_path_)) - return false; + sql::Database::Delete(legacy_db_file_path_); + sql::Database::Delete(db_file_path_); + + // Explicit file deletion to try and get consistent deletion across platforms. + base::DeleteFile(legacy_db_file_path_); + base::DeleteFile(db_file_path_); + base::DeleteFile(sql::Database::JournalPath(legacy_db_file_path_)); + base::DeleteFile(sql::Database::JournalPath(db_file_path_)); + + storage_directory_->Doom(); + storage_directory_->ClearDoomed(); // So we can't go recursive. if (is_recreating_) return false; base::AutoReset<bool> auto_reset(&is_recreating_, true); - return EnsureOpened(EnsureOpenedMode::kCreateIfNotFound) == QuotaError::kNone; -} - -QuotaError QuotaDatabase::DumpQuotaTable(const QuotaTableCallback& callback) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - QuotaError open_error = EnsureOpened(EnsureOpenedMode::kCreateIfNotFound); - if (open_error != QuotaError::kNone) - return open_error; - - static constexpr char kSql[] = "SELECT * FROM quota"; - sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); - - while (statement.Step()) { - QuotaTableEntry entry = { - .host = statement.ColumnString(0), - .type = static_cast<StorageType>(statement.ColumnInt(1)), - .quota = statement.ColumnInt64(2)}; - - if (!callback.Run(entry)) - return QuotaError::kNone; - } - return statement.Succeeded() ? QuotaError::kNone : QuotaError::kDatabaseError; + return EnsureOpened() == QuotaError::kNone; } QuotaError QuotaDatabase::DumpBucketTable(const BucketTableCallback& callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - QuotaError open_error = EnsureOpened(EnsureOpenedMode::kCreateIfNotFound); + QuotaError open_error = EnsureOpened(); if (open_error != QuotaError::kNone) return open_error; @@ -983,7 +1073,7 @@ QuotaErrorOr<BucketInfo> QuotaDatabase::CreateBucketInternal( base::Time last_modified) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); // TODO(crbug/1210259): Add DCHECKs for input validation. - QuotaError open_error = EnsureOpened(EnsureOpenedMode::kCreateIfNotFound); + QuotaError open_error = EnsureOpened(); if (open_error != QuotaError::kNone) return open_error; @@ -1014,24 +1104,16 @@ QuotaErrorOr<BucketInfo> QuotaDatabase::CreateBucketInternal( if (!statement.Run()) return QuotaError::kDatabaseError; - ScheduleCommit(); - int64_t bucket_id = db_->GetLastInsertRowId(); DCHECK_GT(bucket_id, 0); - return BucketInfo(BucketId(bucket_id), storage_key, type, bucket_name, - base::Time::Max(), 0); -} -bool operator==(const QuotaDatabase::QuotaTableEntry& lhs, - const QuotaDatabase::QuotaTableEntry& rhs) { - return std::tie(lhs.host, lhs.type, lhs.quota) == - std::tie(rhs.host, rhs.type, rhs.quota); -} + // Commit immediately so that we persist the bucket metadata to disk before we + // inform other services / web apps (via the Buckets API) that we did so. + // Once informed, that promise should persist across power failures. + Commit(); -bool operator<(const QuotaDatabase::QuotaTableEntry& lhs, - const QuotaDatabase::QuotaTableEntry& rhs) { - return std::tie(lhs.host, lhs.type, lhs.quota) < - std::tie(rhs.host, rhs.type, rhs.quota); + return BucketInfo(BucketId(bucket_id), storage_key, type, bucket_name, + base::Time::Max(), 0); } bool operator<(const QuotaDatabase::BucketTableEntry& lhs, diff --git a/chromium/storage/browser/quota/quota_database.h b/chromium/storage/browser/quota/quota_database.h index 75de1871c82..5b59290c3e4 100644 --- a/chromium/storage/browser/quota/quota_database.h +++ b/chromium/storage/browser/quota/quota_database.h @@ -16,6 +16,7 @@ #include "base/component_export.h" #include "base/files/file_path.h" #include "base/sequence_checker.h" +#include "base/thread_annotations.h" #include "base/time/time.h" #include "base/timer/timer.h" #include "base/types/id_type.h" @@ -24,6 +25,7 @@ #include "components/services/storage/public/cpp/buckets/bucket_locator.h" #include "components/services/storage/public/cpp/buckets/constants.h" #include "components/services/storage/public/cpp/quota_error_or.h" +#include "storage/browser/quota/storage_directory.h" #include "third_party/abseil-cpp/absl/types/optional.h" #include "third_party/blink/public/common/storage_key/storage_key.h" #include "third_party/blink/public/mojom/quota/quota_types.mojom-shared.h" @@ -55,8 +57,7 @@ enum class DatabaseResetReason { // // Instances are owned by QuotaManagerImpl. There is one instance per // QuotaManagerImpl instance. All the methods of this class, except the -// constructor, must called on the DB thread. QuotaDatabase should only be -// subclassed in tests. +// constructor, must called on the DB thread. class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaDatabase { public: struct COMPONENT_EXPORT(STORAGE_BROWSER) BucketTableEntry { @@ -82,13 +83,15 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaDatabase { base::Time last_modified; }; - // If 'path' is empty, an in memory database will be used. - explicit QuotaDatabase(const base::FilePath& path); + static constexpr char kDatabaseName[] = "QuotaManager"; + + // If `profile_path` is empty, an in-memory database will be used. + explicit QuotaDatabase(const base::FilePath& profile_path); QuotaDatabase(const QuotaDatabase&) = delete; QuotaDatabase& operator=(const QuotaDatabase&) = delete; - virtual ~QuotaDatabase(); + ~QuotaDatabase(); // Returns quota if entry is found. Returns QuotaError::kNotFound no entry if // found. @@ -134,6 +137,11 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaDatabase { const std::string& bucket_name, blink::mojom::StorageType storage_type); + // Retrieves BucketInfo of the bucket with `bucket_id`. + // Returns a QuotaError::kEntryNotFound if the bucket does not exist, or + // a QuotaError::kDatabaseError if the operation has failed. + QuotaErrorOr<BucketInfo> GetBucketById(BucketId bucket_id); + // Returns all buckets for `type` in the buckets table. Returns a QuotaError // if the operation has failed. QuotaErrorOr<std::set<BucketLocator>> GetBucketsForType( @@ -181,8 +189,8 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaDatabase { // QuotaError if not found or the operation has failed. QuotaErrorOr<BucketTableEntry> GetBucketInfo(BucketId bucket_id); - // Deletes the specified bucket. This method is virtual for testing. - virtual QuotaError DeleteBucketInfo(BucketId bucket_id); + // Deletes the specified bucket. + QuotaError DeleteBucketInfo(BucketId bucket_id); // Returns the BucketLocator for the least recently used bucket. Will exclude // buckets with ids in `bucket_exceptions` and origins that have the special @@ -203,6 +211,8 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaDatabase { base::Time begin, base::Time end); + base::FilePath GetStoragePath() const { return storage_directory_->path(); } + // Returns false if SetIsBootstrapped() has never been called before, which // means existing storage keys may not have been registered. Bootstrapping // ensures that there is a bucket entry in the buckets table for all storage @@ -210,17 +220,27 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaDatabase { bool IsBootstrapped(); QuotaError SetIsBootstrapped(bool bootstrap_flag); + // Razes and re-opens the database. Will try to open a database again if + // one doesn't exist. + QuotaError RazeAndReopen(); + + // Testing support for database corruption handling. + // + // Runs `corrupter` on the same sequence used to do database I/O, + // guaranteeing that no other database operation is performed at the same + // time. `corrupter` receives the path to the underlying SQLite database as an + // argument. The underlying SQLite database is closed while `corrupter` runs, + // and reopened afterwards. + // Returns QuotaError::kNone if the database was successfully reopened after // `corrupter` was run, or QuotaError::kDatabaseError otherwise. QuotaError CorruptForTesting( base::OnceCallback<void(const base::FilePath&)> corrupter); // Manually disable database to test database error scenarios for testing. - void SetDisabledForTesting(bool disable) { is_disabled_ = disable; } + void SetDisabledForTesting(bool disable); private: - enum class EnsureOpenedMode { kCreateIfNotFound, kFailIfNotFound }; - struct COMPONENT_EXPORT(STORAGE_BROWSER) QuotaTableEntry { std::string host; blink::mojom::StorageType type = blink::mojom::StorageType::kUnknown; @@ -260,11 +280,11 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaDatabase { void Commit(); void ScheduleCommit(); - QuotaError EnsureOpened(EnsureOpenedMode mode); + QuotaError EnsureOpened(); + bool MoveLegacyDatabase(); bool OpenDatabase(); bool EnsureDatabaseVersion(); - bool ResetSchema(); - bool UpgradeSchema(int current_version); + bool ResetStorage(); bool CreateSchema(); bool CreateTable(const TableSchema& table); @@ -285,14 +305,19 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaDatabase { base::Time last_accessed, base::Time last_modified); + SEQUENCE_CHECKER(sequence_checker_); + + const std::unique_ptr<StorageDirectory> storage_directory_; const base::FilePath db_file_path_; + const base::FilePath legacy_db_file_path_; - std::unique_ptr<sql::Database> db_; - std::unique_ptr<sql::MetaTable> meta_table_; - bool is_recreating_ = false; - bool is_disabled_ = false; + std::unique_ptr<sql::Database> db_ GUARDED_BY_CONTEXT(sequence_checker_); + std::unique_ptr<sql::MetaTable> meta_table_ + GUARDED_BY_CONTEXT(sequence_checker_); + bool is_recreating_ GUARDED_BY_CONTEXT(sequence_checker_) = false; + bool is_disabled_ GUARDED_BY_CONTEXT(sequence_checker_) = false; - base::OneShotTimer timer_; + base::OneShotTimer timer_ GUARDED_BY_CONTEXT(sequence_checker_); friend class QuotaDatabaseTest; friend class QuotaDatabaseMigrations; @@ -303,8 +328,6 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaDatabase { static const size_t kTableCount; static const IndexSchema kIndexes[]; static const size_t kIndexCount; - - SEQUENCE_CHECKER(sequence_checker_); }; } // namespace storage diff --git a/chromium/storage/browser/quota/quota_database_migrations.cc b/chromium/storage/browser/quota/quota_database_migrations.cc index 31780a3f513..9402fb2b1ad 100644 --- a/chromium/storage/browser/quota/quota_database_migrations.cc +++ b/chromium/storage/browser/quota/quota_database_migrations.cc @@ -6,6 +6,7 @@ #include <string> +#include "base/sequence_checker.h" #include "components/services/storage/public/cpp/buckets/bucket_id.h" #include "sql/database.h" #include "sql/meta_table.h" @@ -18,11 +19,12 @@ namespace storage { // static bool QuotaDatabaseMigrations::UpgradeSchema(QuotaDatabase& quota_database) { + DCHECK_CALLED_ON_VALID_SEQUENCE(quota_database.sequence_checker_); DCHECK_EQ(0, quota_database.db_->transaction_nesting()); // Reset tables for versions lower than 5 since they are unsupported. if (quota_database.meta_table_->GetVersionNumber() < 5) - return quota_database.ResetSchema(); + return quota_database.ResetStorage(); if (quota_database.meta_table_->GetVersionNumber() == 5) { if (!MigrateFromVersion5ToVersion7(quota_database)) @@ -44,6 +46,8 @@ bool QuotaDatabaseMigrations::UpgradeSchema(QuotaDatabase& quota_database) { bool QuotaDatabaseMigrations::MigrateFromVersion5ToVersion7( QuotaDatabase& quota_database) { + DCHECK_CALLED_ON_VALID_SEQUENCE(quota_database.sequence_checker_); + sql::Database* db = quota_database.db_.get(); sql::Transaction transaction(db); if (!transaction.Begin()) @@ -147,6 +151,8 @@ bool QuotaDatabaseMigrations::MigrateFromVersion5ToVersion7( bool QuotaDatabaseMigrations::MigrateFromVersion6ToVersion7( QuotaDatabase& quota_database) { + DCHECK_CALLED_ON_VALID_SEQUENCE(quota_database.sequence_checker_); + sql::Database* db = quota_database.db_.get(); sql::Transaction transaction(db); if (!transaction.Begin()) @@ -164,6 +170,8 @@ bool QuotaDatabaseMigrations::MigrateFromVersion6ToVersion7( bool QuotaDatabaseMigrations::MigrateFromVersion7ToVersion8( QuotaDatabase& quota_database) { + DCHECK_CALLED_ON_VALID_SEQUENCE(quota_database.sequence_checker_); + sql::Database* db = quota_database.db_.get(); sql::Transaction transaction(db); if (!transaction.Begin()) diff --git a/chromium/storage/browser/quota/quota_database_migrations_unittest.cc b/chromium/storage/browser/quota/quota_database_migrations_unittest.cc index 436552e8cf9..220d4d29286 100644 --- a/chromium/storage/browser/quota/quota_database_migrations_unittest.cc +++ b/chromium/storage/browser/quota/quota_database_migrations_unittest.cc @@ -5,6 +5,7 @@ #include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" #include "base/path_service.h" +#include "components/services/storage/public/cpp/constants.h" #include "sql/database.h" #include "sql/meta_table.h" #include "sql/statement.h" @@ -31,8 +32,12 @@ class QuotaDatabaseMigrationsTest : public testing::Test { public: void SetUp() override { ASSERT_TRUE(temp_directory_.CreateUniqueTempDir()); } + base::FilePath ProfilePath() { return temp_directory_.GetPath(); } + base::FilePath DbPath() { - return temp_directory_.GetPath().AppendASCII("quota_manager.db"); + return ProfilePath() + .Append(kWebStorageDirectory) + .AppendASCII("QuotaManager"); } protected: @@ -58,16 +63,17 @@ class QuotaDatabaseMigrationsTest : public testing::Test { return false; sql::Database db; - if (!db.Open(db_path) || !db.Execute(contents.data())) + if (!base::CreateDirectory(db_path.DirName()) || !db.Open(db_path) || + !db.Execute(contents.data())) return false; return true; } void MigrateDatabase() { - QuotaDatabase db(DbPath()); - EXPECT_EQ( - db.EnsureOpened(QuotaDatabase::EnsureOpenedMode::kCreateIfNotFound), - QuotaError::kNone); + QuotaDatabase db(ProfilePath()); + EXPECT_EQ(db.EnsureOpened(), QuotaError::kNone); + + DCHECK_CALLED_ON_VALID_SEQUENCE(db.sequence_checker_); EXPECT_TRUE(db.db_.get()); } diff --git a/chromium/storage/browser/quota/quota_database_unittest.cc b/chromium/storage/browser/quota/quota_database_unittest.cc index 18f6f282e6d..5f67178302f 100644 --- a/chromium/storage/browser/quota/quota_database_unittest.cc +++ b/chromium/storage/browser/quota/quota_database_unittest.cc @@ -7,6 +7,7 @@ #include <algorithm> #include <iterator> +#include <memory> #include <set> #include "base/bind.h" @@ -15,14 +16,17 @@ #include "base/containers/flat_map.h" #include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" +#include "base/sequence_checker.h" #include "base/test/metrics/histogram_tester.h" #include "base/test/task_environment.h" #include "build/build_config.h" #include "components/services/storage/public/cpp/buckets/bucket_locator.h" #include "components/services/storage/public/cpp/buckets/constants.h" +#include "components/services/storage/public/cpp/constants.h" #include "sql/database.h" -#include "sql/error_metrics.h" #include "sql/meta_table.h" +#include "sql/sqlite_result_code.h" +#include "sql/sqlite_result_code_values.h" #include "sql/statement.h" #include "sql/test/scoped_error_expecter.h" #include "sql/test/test_helpers.h" @@ -44,6 +48,8 @@ static const blink::mojom::StorageType kTemp = static const blink::mojom::StorageType kPerm = blink::mojom::StorageType::kPersistent; +static constexpr char kDatabaseName[] = "QuotaManager"; + bool ContainsBucket(const std::set<BucketLocator>& buckets, const BucketInfo& target_bucket) { auto it = buckets.find(target_bucket.ToBucketLocator()); @@ -56,9 +62,7 @@ bool ContainsBucket(const std::set<BucketLocator>& buckets, // mode. True will create the database in memory. class QuotaDatabaseTest : public testing::TestWithParam<bool> { protected: - using QuotaTableEntry = QuotaDatabase::QuotaTableEntry; using BucketTableEntry = QuotaDatabase::BucketTableEntry; - using EnsureOpenedMode = QuotaDatabase::EnsureOpenedMode; void SetUp() override { ASSERT_TRUE(temp_directory_.CreateUniqueTempDir()); } @@ -66,12 +70,21 @@ class QuotaDatabaseTest : public testing::TestWithParam<bool> { bool use_in_memory_db() const { return GetParam(); } + base::FilePath ProfilePath() { return temp_directory_.GetPath(); } + base::FilePath DbPath() { - return temp_directory_.GetPath().AppendASCII("quota_manager.db"); + return ProfilePath() + .Append(kWebStorageDirectory) + .AppendASCII(kDatabaseName); + } + + std::unique_ptr<QuotaDatabase> CreateDatabase(bool is_incognito) { + return std::make_unique<QuotaDatabase>(is_incognito ? base::FilePath() + : ProfilePath()); } - bool EnsureOpened(QuotaDatabase* db, EnsureOpenedMode mode) { - return db->EnsureOpened(mode) == QuotaError::kNone; + bool EnsureOpened(QuotaDatabase* db) { + return db->EnsureOpened() == QuotaError::kNone; } template <typename EntryType> @@ -123,7 +136,8 @@ class QuotaDatabaseTest : public testing::TestWithParam<bool> { template <typename Container> void AssignBucketTable(QuotaDatabase* quota_database, Container&& entries) { - ASSERT_NE(quota_database->db_.get(), (sql::Database*)nullptr); + DCHECK_CALLED_ON_VALID_SEQUENCE(quota_database->sequence_checker_); + ASSERT_TRUE(quota_database->db_); for (const auto& entry : entries) { const char* kSql = // clang-format off @@ -164,9 +178,21 @@ class QuotaDatabaseTest : public testing::TestWithParam<bool> { }; TEST_P(QuotaDatabaseTest, EnsureOpened) { + auto db = CreateDatabase(use_in_memory_db()); + EXPECT_TRUE(EnsureOpened(db.get())); + + if (GetParam()) { + // Path should not exist for incognito mode. + ASSERT_FALSE(base::PathExists(DbPath())); + } else { + ASSERT_TRUE(base::PathExists(DbPath())); + } +} + +TEST_P(QuotaDatabaseTest, RazeAndReopenWithNoDb) { QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); - EXPECT_FALSE(EnsureOpened(&db, EnsureOpenedMode::kFailIfNotFound)); - EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); + // RazeAndReopen() with no db tries to create the db one last time. + EXPECT_EQ(db.RazeAndReopen(), QuotaError::kNone); if (GetParam()) { // Path should not exist for incognito mode. @@ -177,59 +203,59 @@ TEST_P(QuotaDatabaseTest, EnsureOpened) { } TEST_P(QuotaDatabaseTest, HostQuota) { - QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); - EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); + auto db = CreateDatabase(use_in_memory_db()); + EXPECT_TRUE(EnsureOpened(db.get())); const char* kHost = "foo.com"; const int kQuota1 = 13579; const int kQuota2 = kQuota1 + 1024; - QuotaErrorOr<int64_t> result = db.GetHostQuota(kHost, kTemp); + QuotaErrorOr<int64_t> result = db->GetHostQuota(kHost, kTemp); EXPECT_FALSE(result.ok()); EXPECT_EQ(result.error(), QuotaError::kNotFound); - result = db.GetHostQuota(kHost, kPerm); + result = db->GetHostQuota(kHost, kPerm); EXPECT_FALSE(result.ok()); EXPECT_EQ(result.error(), QuotaError::kNotFound); // Insert quota for temporary. - EXPECT_EQ(db.SetHostQuota(kHost, kTemp, kQuota1), QuotaError::kNone); - result = db.GetHostQuota(kHost, kTemp); + EXPECT_EQ(db->SetHostQuota(kHost, kTemp, kQuota1), QuotaError::kNone); + result = db->GetHostQuota(kHost, kTemp); EXPECT_TRUE(result.ok()); EXPECT_EQ(kQuota1, result.value()); // Update quota for temporary. - EXPECT_EQ(db.SetHostQuota(kHost, kTemp, kQuota2), QuotaError::kNone); - result = db.GetHostQuota(kHost, kTemp); + EXPECT_EQ(db->SetHostQuota(kHost, kTemp, kQuota2), QuotaError::kNone); + result = db->GetHostQuota(kHost, kTemp); EXPECT_TRUE(result.ok()); EXPECT_EQ(kQuota2, result.value()); // Quota for persistent must not be updated. - result = db.GetHostQuota(kHost, kPerm); + result = db->GetHostQuota(kHost, kPerm); EXPECT_FALSE(result.ok()); EXPECT_EQ(result.error(), QuotaError::kNotFound); // Delete temporary storage quota. - EXPECT_EQ(db.DeleteHostQuota(kHost, kTemp), QuotaError::kNone); - result = db.GetHostQuota(kHost, kTemp); + EXPECT_EQ(db->DeleteHostQuota(kHost, kTemp), QuotaError::kNone); + result = db->GetHostQuota(kHost, kTemp); EXPECT_FALSE(result.ok()); EXPECT_EQ(result.error(), QuotaError::kNotFound); // Delete persistent quota by setting it to zero. - EXPECT_EQ(db.SetHostQuota(kHost, kPerm, 0), QuotaError::kNone); - result = db.GetHostQuota(kHost, kPerm); + EXPECT_EQ(db->SetHostQuota(kHost, kPerm, 0), QuotaError::kNone); + result = db->GetHostQuota(kHost, kPerm); EXPECT_FALSE(result.ok()); EXPECT_EQ(result.error(), QuotaError::kNotFound); } TEST_P(QuotaDatabaseTest, GetOrCreateBucket) { - QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); - EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); + auto db = CreateDatabase(use_in_memory_db()); + EXPECT_TRUE(EnsureOpened(db.get())); StorageKey storage_key = StorageKey::CreateFromStringForTesting("http://google/"); std::string bucket_name = "google_bucket"; QuotaErrorOr<BucketInfo> result = - db.GetOrCreateBucket(storage_key, bucket_name); + db->GetOrCreateBucket(storage_key, bucket_name); ASSERT_TRUE(result.ok()); BucketInfo created_bucket = result.value(); @@ -239,7 +265,7 @@ TEST_P(QuotaDatabaseTest, GetOrCreateBucket) { ASSERT_EQ(created_bucket.type, kTemp); // Should return the same bucket when querying again. - result = db.GetOrCreateBucket(storage_key, bucket_name); + result = db->GetOrCreateBucket(storage_key, bucket_name); ASSERT_TRUE(result.ok()); BucketInfo retrieved_bucket = result.value(); @@ -250,14 +276,14 @@ TEST_P(QuotaDatabaseTest, GetOrCreateBucket) { } TEST_P(QuotaDatabaseTest, GetOrCreateBucketDeprecated) { - QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); - EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); + auto db = CreateDatabase(use_in_memory_db()); + EXPECT_TRUE(EnsureOpened(db.get())); StorageKey storage_key = StorageKey::CreateFromStringForTesting("http://google/"); std::string bucket_name = "google_bucket"; QuotaErrorOr<BucketInfo> result = - db.GetOrCreateBucketDeprecated(storage_key, bucket_name, kPerm); + db->GetOrCreateBucketDeprecated(storage_key, bucket_name, kPerm); ASSERT_TRUE(result.ok()); BucketInfo created_bucket = result.value(); @@ -267,7 +293,7 @@ TEST_P(QuotaDatabaseTest, GetOrCreateBucketDeprecated) { ASSERT_EQ(created_bucket.type, kPerm); // Should return the same bucket when querying again. - result = db.GetOrCreateBucketDeprecated(storage_key, bucket_name, kPerm); + result = db->GetOrCreateBucketDeprecated(storage_key, bucket_name, kPerm); ASSERT_TRUE(result.ok()); BucketInfo retrieved_bucket = result.value(); @@ -278,15 +304,15 @@ TEST_P(QuotaDatabaseTest, GetOrCreateBucketDeprecated) { } TEST_P(QuotaDatabaseTest, GetBucket) { - QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); - EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); + auto db = CreateDatabase(use_in_memory_db()); + EXPECT_TRUE(EnsureOpened(db.get())); // Add a bucket entry into the bucket table. StorageKey storage_key = StorageKey::CreateFromStringForTesting("http://google/"); std::string bucket_name = "google_bucket"; QuotaErrorOr<BucketInfo> result = - db.CreateBucketForTesting(storage_key, bucket_name, kPerm); + db->CreateBucketForTesting(storage_key, bucket_name, kPerm); ASSERT_TRUE(result.ok()); BucketInfo created_bucket = result.value(); @@ -295,7 +321,7 @@ TEST_P(QuotaDatabaseTest, GetBucket) { ASSERT_EQ(created_bucket.storage_key, storage_key); ASSERT_EQ(created_bucket.type, kPerm); - result = db.GetBucket(storage_key, bucket_name, kPerm); + result = db->GetBucket(storage_key, bucket_name, kPerm); ASSERT_TRUE(result.ok()); EXPECT_EQ(result.value().id, created_bucket.id); EXPECT_EQ(result.value().name, created_bucket.name); @@ -303,21 +329,51 @@ TEST_P(QuotaDatabaseTest, GetBucket) { ASSERT_EQ(result.value().type, created_bucket.type); // Can't retrieve buckets with name mismatch. - result = db.GetBucket(storage_key, "does_not_exist", kPerm); + result = db->GetBucket(storage_key, "does_not_exist", kPerm); ASSERT_FALSE(result.ok()); EXPECT_EQ(result.error(), QuotaError::kNotFound); // Can't retrieve buckets with StorageKey mismatch. result = - db.GetBucket(StorageKey::CreateFromStringForTesting("http://example/"), - bucket_name, kPerm); + db->GetBucket(StorageKey::CreateFromStringForTesting("http://example/"), + bucket_name, kPerm); + ASSERT_FALSE(result.ok()); + EXPECT_EQ(result.error(), QuotaError::kNotFound); +} + +TEST_P(QuotaDatabaseTest, GetBucketById) { + auto db = CreateDatabase(use_in_memory_db()); + EXPECT_TRUE(EnsureOpened(db.get())); + + // Add a bucket entry into the bucket table. + StorageKey storage_key = + StorageKey::CreateFromStringForTesting("http://google/"); + std::string bucket_name = "google_bucket"; + QuotaErrorOr<BucketInfo> result = + db->CreateBucketForTesting(storage_key, bucket_name, kPerm); + ASSERT_TRUE(result.ok()); + + BucketInfo created_bucket = result.value(); + ASSERT_GT(created_bucket.id.value(), 0); + ASSERT_EQ(created_bucket.name, bucket_name); + ASSERT_EQ(created_bucket.storage_key, storage_key); + ASSERT_EQ(created_bucket.type, kPerm); + + result = db->GetBucketById(created_bucket.id); + ASSERT_TRUE(result.ok()); + EXPECT_EQ(result.value().name, created_bucket.name); + EXPECT_EQ(result.value().storage_key, created_bucket.storage_key); + ASSERT_EQ(result.value().type, created_bucket.type); + + constexpr BucketId kNonExistentBucketId(7777); + result = db->GetBucketById(BucketId(kNonExistentBucketId)); ASSERT_FALSE(result.ok()); EXPECT_EQ(result.error(), QuotaError::kNotFound); } TEST_P(QuotaDatabaseTest, GetBucketsForType) { - QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); - EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); + auto db = CreateDatabase(use_in_memory_db()); + EXPECT_TRUE(EnsureOpened(db.get())); const StorageKey storage_key1 = StorageKey::CreateFromStringForTesting("http://example-a/"); @@ -327,30 +383,33 @@ TEST_P(QuotaDatabaseTest, GetBucketsForType) { StorageKey::CreateFromStringForTesting("http://example-c/"); QuotaErrorOr<BucketInfo> bucket_result = - db.CreateBucketForTesting(storage_key1, "temp_bucket", kTemp); + db->CreateBucketForTesting(storage_key1, "temp_bucket", kTemp); ASSERT_TRUE(bucket_result.ok()); BucketInfo temp_bucket1 = bucket_result.value(); - bucket_result = db.CreateBucketForTesting(storage_key2, "temp_bucket", kTemp); + bucket_result = + db->CreateBucketForTesting(storage_key2, "temp_bucket", kTemp); ASSERT_TRUE(bucket_result.ok()); BucketInfo temp_bucket2 = bucket_result.value(); - bucket_result = db.CreateBucketForTesting(storage_key1, "perm_bucket", kPerm); + bucket_result = + db->CreateBucketForTesting(storage_key1, "perm_bucket", kPerm); ASSERT_TRUE(bucket_result.ok()); BucketInfo perm_bucket1 = bucket_result.value(); - bucket_result = db.CreateBucketForTesting(storage_key3, "perm_bucket", kPerm); + bucket_result = + db->CreateBucketForTesting(storage_key3, "perm_bucket", kPerm); ASSERT_TRUE(bucket_result.ok()); BucketInfo perm_bucket2 = bucket_result.value(); - QuotaErrorOr<std::set<BucketLocator>> result = db.GetBucketsForType(kTemp); + QuotaErrorOr<std::set<BucketLocator>> result = db->GetBucketsForType(kTemp); ASSERT_TRUE(result.ok()); std::set<BucketLocator> buckets = result.value(); ASSERT_EQ(2U, buckets.size()); EXPECT_TRUE(ContainsBucket(buckets, temp_bucket1)); EXPECT_TRUE(ContainsBucket(buckets, temp_bucket2)); - result = db.GetBucketsForType(kPerm); + result = db->GetBucketsForType(kPerm); ASSERT_TRUE(result.ok()); buckets = result.value(); ASSERT_EQ(2U, buckets.size()); @@ -359,47 +418,47 @@ TEST_P(QuotaDatabaseTest, GetBucketsForType) { } TEST_P(QuotaDatabaseTest, GetBucketsForHost) { - QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); - EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); + auto db = CreateDatabase(use_in_memory_db()); + EXPECT_TRUE(EnsureOpened(db.get())); - QuotaErrorOr<BucketInfo> temp_example_bucket1 = db.CreateBucketForTesting( + QuotaErrorOr<BucketInfo> temp_example_bucket1 = db->CreateBucketForTesting( StorageKey::CreateFromStringForTesting("https://example.com/"), "default", kTemp); - QuotaErrorOr<BucketInfo> temp_example_bucket2 = db.CreateBucketForTesting( + QuotaErrorOr<BucketInfo> temp_example_bucket2 = db->CreateBucketForTesting( StorageKey::CreateFromStringForTesting("http://example.com:123/"), "default", kTemp); - QuotaErrorOr<BucketInfo> perm_google_bucket1 = db.CreateBucketForTesting( + QuotaErrorOr<BucketInfo> perm_google_bucket1 = db->CreateBucketForTesting( StorageKey::CreateFromStringForTesting("http://google.com/"), "default", kPerm); - QuotaErrorOr<BucketInfo> temp_google_bucket2 = db.CreateBucketForTesting( + QuotaErrorOr<BucketInfo> temp_google_bucket2 = db->CreateBucketForTesting( StorageKey::CreateFromStringForTesting("http://google.com:123/"), "default", kTemp); QuotaErrorOr<std::set<BucketLocator>> result = - db.GetBucketsForHost("example.com", kTemp); + db->GetBucketsForHost("example.com", kTemp); ASSERT_TRUE(result.ok()); ASSERT_EQ(result->size(), 2U); EXPECT_TRUE(ContainsBucket(result.value(), temp_example_bucket1.value())); EXPECT_TRUE(ContainsBucket(result.value(), temp_example_bucket2.value())); - result = db.GetBucketsForHost("example.com", kPerm); + result = db->GetBucketsForHost("example.com", kPerm); ASSERT_TRUE(result.ok()); ASSERT_EQ(result->size(), 0U); - result = db.GetBucketsForHost("google.com", kPerm); + result = db->GetBucketsForHost("google.com", kPerm); ASSERT_TRUE(result.ok()); ASSERT_EQ(result->size(), 1U); EXPECT_TRUE(ContainsBucket(result.value(), perm_google_bucket1.value())); - result = db.GetBucketsForHost("google.com", kTemp); + result = db->GetBucketsForHost("google.com", kTemp); ASSERT_TRUE(result.ok()); ASSERT_EQ(result->size(), 1U); EXPECT_TRUE(ContainsBucket(result.value(), temp_google_bucket2.value())); } TEST_P(QuotaDatabaseTest, GetBucketsForStorageKey) { - QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); - EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); + auto db = CreateDatabase(use_in_memory_db()); + EXPECT_TRUE(EnsureOpened(db.get())); const StorageKey storage_key1 = StorageKey::CreateFromStringForTesting("http://example-a/"); @@ -407,84 +466,44 @@ TEST_P(QuotaDatabaseTest, GetBucketsForStorageKey) { StorageKey::CreateFromStringForTesting("http://example-b/"); QuotaErrorOr<BucketInfo> bucket_result = - db.CreateBucketForTesting(storage_key1, "temp_test1", kTemp); + db->CreateBucketForTesting(storage_key1, "temp_test1", kTemp); ASSERT_TRUE(bucket_result.ok()); BucketInfo temp_bucket1 = bucket_result.value(); - bucket_result = db.CreateBucketForTesting(storage_key1, "temp_test2", kTemp); + bucket_result = db->CreateBucketForTesting(storage_key1, "temp_test2", kTemp); ASSERT_TRUE(bucket_result.ok()); BucketInfo temp_bucket2 = bucket_result.value(); - bucket_result = db.CreateBucketForTesting(storage_key1, "perm_test", kPerm); + bucket_result = db->CreateBucketForTesting(storage_key1, "perm_test", kPerm); ASSERT_TRUE(bucket_result.ok()); BucketInfo perm_bucket1 = bucket_result.value(); - bucket_result = db.CreateBucketForTesting(storage_key2, "perm_test", kPerm); + bucket_result = db->CreateBucketForTesting(storage_key2, "perm_test", kPerm); ASSERT_TRUE(bucket_result.ok()); BucketInfo perm_bucket2 = bucket_result.value(); QuotaErrorOr<std::set<BucketLocator>> result = - db.GetBucketsForStorageKey(storage_key1, kTemp); + db->GetBucketsForStorageKey(storage_key1, kTemp); ASSERT_TRUE(result.ok()); std::set<BucketLocator> buckets = result.value(); ASSERT_EQ(2U, buckets.size()); EXPECT_TRUE(ContainsBucket(buckets, temp_bucket1)); EXPECT_TRUE(ContainsBucket(buckets, temp_bucket2)); - result = db.GetBucketsForStorageKey(storage_key2, kPerm); + result = db->GetBucketsForStorageKey(storage_key2, kPerm); ASSERT_TRUE(result.ok()); buckets = result.value(); ASSERT_EQ(1U, buckets.size()); EXPECT_TRUE(ContainsBucket(buckets, perm_bucket2)); } -TEST_P(QuotaDatabaseTest, GetBucketWithNoDb) { - QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); - EXPECT_FALSE(EnsureOpened(&db, EnsureOpenedMode::kFailIfNotFound)); - - StorageKey storage_key = - StorageKey::CreateFromStringForTesting("http://google/"); - std::string bucket_name = "google_bucket"; - QuotaErrorOr<BucketInfo> result = - db.GetBucket(storage_key, bucket_name, kTemp); - ASSERT_FALSE(result.ok()); - EXPECT_EQ(result.error(), QuotaError::kNotFound); -} - -// TODO(crbug.com/1216094): Update test to have its behavior on Fuchsia/Win -// match with other platforms, and enable test on all platforms. -#if !BUILDFLAG(IS_FUCHSIA) && !BUILDFLAG(IS_WIN) -TEST_F(QuotaDatabaseTest, GetBucketWithOpenDatabaseError) { - base::HistogramTester histograms; - sql::test::ScopedErrorExpecter expecter; - expecter.ExpectError(SQLITE_CANTOPEN); - - base::ScopedTempDir temp_dir; - ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); - QuotaDatabase db(temp_dir.GetPath()); - - StorageKey storage_key = - StorageKey::CreateFromStringForTesting("http://google/"); - std::string bucket_name = "google_bucket"; - QuotaErrorOr<BucketInfo> result = - db.GetBucket(storage_key, bucket_name, kTemp); - ASSERT_FALSE(result.ok()); - EXPECT_EQ(result.error(), QuotaError::kDatabaseError); - - EXPECT_TRUE(expecter.SawExpectedErrors()); - histograms.ExpectTotalCount("Quota.QuotaDatabaseReset", 1); - histograms.ExpectBucketCount("Quota.QuotaDatabaseReset", - DatabaseResetReason::kOpenDatabase, 1); -} -#endif // !BUILDFLAG(IS_FUCHSIA) && !BUILDFLAG(IS_WIN) - TEST_P(QuotaDatabaseTest, BucketLastAccessTimeLRU) { - QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); - EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); + auto db = CreateDatabase(use_in_memory_db()); + EXPECT_TRUE(EnsureOpened(db.get())); std::set<BucketId> bucket_exceptions; QuotaErrorOr<BucketLocator> result = - db.GetLRUBucket(kTemp, bucket_exceptions, nullptr); + db->GetLRUBucket(kTemp, bucket_exceptions, nullptr); EXPECT_FALSE(result.ok()); EXPECT_EQ(result.error(), QuotaError::kNotFound); @@ -504,25 +523,30 @@ TEST_P(QuotaDatabaseTest, BucketLastAccessTimeLRU) { BucketId(4), StorageKey::CreateFromStringForTesting("http://example-d/"), kPerm, "bucket_d", 5, now, now); Entry kTableEntries[] = {bucket1, bucket2, bucket3, bucket4}; - AssignBucketTable(&db, kTableEntries); + AssignBucketTable(db.get(), kTableEntries); // Update access time for three temporary storages, and - EXPECT_EQ(db.SetBucketLastAccessTime(bucket1.bucket_id, - base::Time::FromJavaTime(10)), + EXPECT_EQ(db->SetBucketLastAccessTime(bucket1.bucket_id, + base::Time::FromJavaTime(10)), QuotaError::kNone); - EXPECT_EQ(db.SetBucketLastAccessTime(bucket2.bucket_id, - base::Time::FromJavaTime(20)), + EXPECT_EQ(db->SetBucketLastAccessTime(bucket2.bucket_id, + base::Time::FromJavaTime(20)), QuotaError::kNone); - EXPECT_EQ(db.SetBucketLastAccessTime(bucket3.bucket_id, - base::Time::FromJavaTime(30)), + EXPECT_EQ(db->SetBucketLastAccessTime(bucket3.bucket_id, + base::Time::FromJavaTime(30)), QuotaError::kNone); - // one persistent. - EXPECT_EQ(db.SetBucketLastAccessTime(bucket4.bucket_id, - base::Time::FromJavaTime(40)), + // One persistent. + EXPECT_EQ(db->SetBucketLastAccessTime(bucket4.bucket_id, + base::Time::FromJavaTime(40)), QuotaError::kNone); - result = db.GetLRUBucket(kTemp, bucket_exceptions, nullptr); + // One non-existent. + EXPECT_EQ( + db->SetBucketLastAccessTime(BucketId(777), base::Time::FromJavaTime(40)), + QuotaError::kNone); + + result = db->GetLRUBucket(kTemp, bucket_exceptions, nullptr); EXPECT_TRUE(result.ok()); EXPECT_EQ(bucket1.bucket_id, result.value().id); @@ -531,79 +555,79 @@ TEST_P(QuotaDatabaseTest, BucketLastAccessTimeLRU) { auto policy = base::MakeRefCounted<MockSpecialStoragePolicy>(); policy->AddUnlimited(bucket1.storage_key.origin().GetURL()); policy->AddProtected(bucket2.storage_key.origin().GetURL()); - result = db.GetLRUBucket(kTemp, bucket_exceptions, policy.get()); + result = db->GetLRUBucket(kTemp, bucket_exceptions, policy.get()); EXPECT_TRUE(result.ok()); EXPECT_EQ(bucket2.bucket_id, result.value().id); // Test that durable origins are excluded from eviction. policy->AddDurable(bucket2.storage_key.origin().GetURL()); - result = db.GetLRUBucket(kTemp, bucket_exceptions, policy.get()); + result = db->GetLRUBucket(kTemp, bucket_exceptions, policy.get()); EXPECT_TRUE(result.ok()); EXPECT_EQ(bucket3.bucket_id, result.value().id); // Bucket exceptions exclude specified buckets. bucket_exceptions.insert(bucket1.bucket_id); - result = db.GetLRUBucket(kTemp, bucket_exceptions, nullptr); + result = db->GetLRUBucket(kTemp, bucket_exceptions, nullptr); EXPECT_TRUE(result.ok()); EXPECT_EQ(bucket2.bucket_id, result.value().id); bucket_exceptions.insert(bucket2.bucket_id); - result = db.GetLRUBucket(kTemp, bucket_exceptions, nullptr); + result = db->GetLRUBucket(kTemp, bucket_exceptions, nullptr); EXPECT_TRUE(result.ok()); EXPECT_EQ(bucket3.bucket_id, result.value().id); bucket_exceptions.insert(bucket3.bucket_id); - result = db.GetLRUBucket(kTemp, bucket_exceptions, nullptr); + result = db->GetLRUBucket(kTemp, bucket_exceptions, nullptr); EXPECT_FALSE(result.ok()); EXPECT_EQ(result.error(), QuotaError::kNotFound); - EXPECT_EQ(db.SetBucketLastAccessTime(bucket1.bucket_id, base::Time::Now()), + EXPECT_EQ(db->SetBucketLastAccessTime(bucket1.bucket_id, base::Time::Now()), QuotaError::kNone); // Delete storage_key/type last access time information. - EXPECT_EQ(db.DeleteBucketInfo(bucket3.bucket_id), QuotaError::kNone); + EXPECT_EQ(db->DeleteBucketInfo(bucket3.bucket_id), QuotaError::kNone); // Querying again to see if the deletion has worked. bucket_exceptions.clear(); - result = db.GetLRUBucket(kTemp, bucket_exceptions, nullptr); + result = db->GetLRUBucket(kTemp, bucket_exceptions, nullptr); EXPECT_TRUE(result.ok()); EXPECT_EQ(bucket2.bucket_id, result.value().id); bucket_exceptions.insert(bucket1.bucket_id); bucket_exceptions.insert(bucket2.bucket_id); - result = db.GetLRUBucket(kTemp, bucket_exceptions, nullptr); + result = db->GetLRUBucket(kTemp, bucket_exceptions, nullptr); EXPECT_FALSE(result.ok()); EXPECT_EQ(result.error(), QuotaError::kNotFound); } TEST_P(QuotaDatabaseTest, SetStorageKeyLastAccessTime) { - QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); - EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); + auto db = CreateDatabase(use_in_memory_db()); + EXPECT_TRUE(EnsureOpened(db.get())); const StorageKey storage_key = StorageKey::CreateFromStringForTesting("http://example/"); base::Time now = base::Time::Now(); - // Should error if bucket doesn't exist. - EXPECT_EQ(db.SetStorageKeyLastAccessTime(storage_key, kTemp, now), - QuotaError::kNotFound); + // Doesn't error if bucket doesn't exist. + EXPECT_EQ(db->SetStorageKeyLastAccessTime(storage_key, kTemp, now), + QuotaError::kNone); QuotaErrorOr<BucketInfo> bucket = - db.CreateBucketForTesting(storage_key, kDefaultBucketName, kTemp); + db->CreateBucketForTesting(storage_key, kDefaultBucketName, kTemp); - EXPECT_EQ(db.SetStorageKeyLastAccessTime(storage_key, kTemp, now), + EXPECT_EQ(db->SetStorageKeyLastAccessTime(storage_key, kTemp, now), QuotaError::kNone); QuotaErrorOr<QuotaDatabase::BucketTableEntry> info = - db.GetBucketInfo(bucket->id); + db->GetBucketInfo(bucket->id); EXPECT_TRUE(info.ok()); EXPECT_EQ(now, info->last_accessed); EXPECT_EQ(1, info->use_count); } TEST_P(QuotaDatabaseTest, GetStorageKeysForType) { - QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); - EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); + auto db = CreateDatabase(use_in_memory_db()); + EXPECT_TRUE(EnsureOpened(db.get())); const StorageKey storage_key1 = StorageKey::CreateFromStringForTesting("http://example-a/"); @@ -612,18 +636,18 @@ TEST_P(QuotaDatabaseTest, GetStorageKeysForType) { const StorageKey storage_key3 = StorageKey::CreateFromStringForTesting("http://example-c/"); - db.CreateBucketForTesting(storage_key1, "bucket_a", kTemp); - db.CreateBucketForTesting(storage_key2, "bucket_b", kTemp); - db.CreateBucketForTesting(storage_key2, "bucket_b", kPerm); - db.CreateBucketForTesting(storage_key3, "bucket_c", kPerm); + db->CreateBucketForTesting(storage_key1, "bucket_a", kTemp); + db->CreateBucketForTesting(storage_key2, "bucket_b", kTemp); + db->CreateBucketForTesting(storage_key2, "bucket_b", kPerm); + db->CreateBucketForTesting(storage_key3, "bucket_c", kPerm); - QuotaErrorOr<std::set<StorageKey>> result = db.GetStorageKeysForType(kTemp); + QuotaErrorOr<std::set<StorageKey>> result = db->GetStorageKeysForType(kTemp); ASSERT_TRUE(result.ok()); ASSERT_TRUE(base::Contains(result.value(), storage_key1)); ASSERT_TRUE(base::Contains(result.value(), storage_key2)); ASSERT_FALSE(base::Contains(result.value(), storage_key3)); - result = db.GetStorageKeysForType(kPerm); + result = db->GetStorageKeysForType(kPerm); ASSERT_TRUE(result.ok()); ASSERT_FALSE(base::Contains(result.value(), storage_key1)); ASSERT_TRUE(base::Contains(result.value(), storage_key2)); @@ -631,31 +655,31 @@ TEST_P(QuotaDatabaseTest, GetStorageKeysForType) { } TEST_P(QuotaDatabaseTest, BucketLastModifiedBetween) { - QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); - EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); + auto db = CreateDatabase(use_in_memory_db()); + EXPECT_TRUE(EnsureOpened(db.get())); QuotaErrorOr<std::set<BucketLocator>> result = - db.GetBucketsModifiedBetween(kTemp, base::Time(), base::Time::Max()); + db->GetBucketsModifiedBetween(kTemp, base::Time(), base::Time::Max()); EXPECT_TRUE(result.ok()); std::set<BucketLocator> buckets = result.value(); EXPECT_TRUE(buckets.empty()); - QuotaErrorOr<BucketInfo> result1 = db.CreateBucketForTesting( + QuotaErrorOr<BucketInfo> result1 = db->CreateBucketForTesting( StorageKey::CreateFromStringForTesting("http://example-a/"), "bucket_a", kTemp); EXPECT_TRUE(result1.ok()); BucketInfo bucket1 = result1.value(); - QuotaErrorOr<BucketInfo> result2 = db.CreateBucketForTesting( + QuotaErrorOr<BucketInfo> result2 = db->CreateBucketForTesting( StorageKey::CreateFromStringForTesting("http://example-b/"), "bucket_b", kTemp); EXPECT_TRUE(result2.ok()); BucketInfo bucket2 = result2.value(); - QuotaErrorOr<BucketInfo> result3 = db.CreateBucketForTesting( + QuotaErrorOr<BucketInfo> result3 = db->CreateBucketForTesting( StorageKey::CreateFromStringForTesting("http://example-c/"), "bucket_c", kTemp); EXPECT_TRUE(result3.ok()); BucketInfo bucket3 = result3.value(); - QuotaErrorOr<BucketInfo> result4 = db.CreateBucketForTesting( + QuotaErrorOr<BucketInfo> result4 = db->CreateBucketForTesting( StorageKey::CreateFromStringForTesting("http://example-d/"), "bucket_d", kPerm); EXPECT_TRUE(result4.ok()); @@ -663,19 +687,25 @@ TEST_P(QuotaDatabaseTest, BucketLastModifiedBetween) { // Report last modified time for the buckets. EXPECT_EQ( - db.SetBucketLastModifiedTime(bucket1.id, base::Time::FromJavaTime(0)), + db->SetBucketLastModifiedTime(bucket1.id, base::Time::FromJavaTime(0)), QuotaError::kNone); EXPECT_EQ( - db.SetBucketLastModifiedTime(bucket2.id, base::Time::FromJavaTime(10)), + db->SetBucketLastModifiedTime(bucket2.id, base::Time::FromJavaTime(10)), QuotaError::kNone); EXPECT_EQ( - db.SetBucketLastModifiedTime(bucket3.id, base::Time::FromJavaTime(20)), + db->SetBucketLastModifiedTime(bucket3.id, base::Time::FromJavaTime(20)), QuotaError::kNone); EXPECT_EQ( - db.SetBucketLastModifiedTime(bucket4.id, base::Time::FromJavaTime(30)), + db->SetBucketLastModifiedTime(bucket4.id, base::Time::FromJavaTime(30)), QuotaError::kNone); - result = db.GetBucketsModifiedBetween(kTemp, base::Time(), base::Time::Max()); + // Non-existent bucket. + EXPECT_EQ( + db->SetBucketLastModifiedTime(BucketId(777), base::Time::FromJavaTime(0)), + QuotaError::kNone); + + result = + db->GetBucketsModifiedBetween(kTemp, base::Time(), base::Time::Max()); EXPECT_TRUE(result.ok()); buckets = result.value(); EXPECT_EQ(3U, buckets.size()); @@ -684,8 +714,8 @@ TEST_P(QuotaDatabaseTest, BucketLastModifiedBetween) { EXPECT_TRUE(ContainsBucket(buckets, bucket3)); EXPECT_FALSE(ContainsBucket(buckets, bucket4)); - result = db.GetBucketsModifiedBetween(kTemp, base::Time::FromJavaTime(5), - base::Time::Max()); + result = db->GetBucketsModifiedBetween(kTemp, base::Time::FromJavaTime(5), + base::Time::Max()); EXPECT_TRUE(result.ok()); buckets = result.value(); EXPECT_EQ(2U, buckets.size()); @@ -694,8 +724,8 @@ TEST_P(QuotaDatabaseTest, BucketLastModifiedBetween) { EXPECT_TRUE(ContainsBucket(buckets, bucket3)); EXPECT_FALSE(ContainsBucket(buckets, bucket4)); - result = db.GetBucketsModifiedBetween(kTemp, base::Time::FromJavaTime(15), - base::Time::Max()); + result = db->GetBucketsModifiedBetween(kTemp, base::Time::FromJavaTime(15), + base::Time::Max()); EXPECT_TRUE(result.ok()); buckets = result.value(); EXPECT_EQ(1U, buckets.size()); @@ -704,14 +734,14 @@ TEST_P(QuotaDatabaseTest, BucketLastModifiedBetween) { EXPECT_TRUE(ContainsBucket(buckets, bucket3)); EXPECT_FALSE(ContainsBucket(buckets, bucket4)); - result = db.GetBucketsModifiedBetween(kTemp, base::Time::FromJavaTime(25), - base::Time::Max()); + result = db->GetBucketsModifiedBetween(kTemp, base::Time::FromJavaTime(25), + base::Time::Max()); EXPECT_TRUE(result.ok()); buckets = result.value(); EXPECT_TRUE(buckets.empty()); - result = db.GetBucketsModifiedBetween(kTemp, base::Time::FromJavaTime(5), - base::Time::FromJavaTime(15)); + result = db->GetBucketsModifiedBetween(kTemp, base::Time::FromJavaTime(5), + base::Time::FromJavaTime(15)); EXPECT_TRUE(result.ok()); buckets = result.value(); EXPECT_EQ(1U, buckets.size()); @@ -720,8 +750,8 @@ TEST_P(QuotaDatabaseTest, BucketLastModifiedBetween) { EXPECT_FALSE(ContainsBucket(buckets, bucket3)); EXPECT_FALSE(ContainsBucket(buckets, bucket4)); - result = db.GetBucketsModifiedBetween(kTemp, base::Time::FromJavaTime(0), - base::Time::FromJavaTime(20)); + result = db->GetBucketsModifiedBetween(kTemp, base::Time::FromJavaTime(0), + base::Time::FromJavaTime(20)); EXPECT_TRUE(result.ok()); buckets = result.value(); EXPECT_EQ(2U, buckets.size()); @@ -730,8 +760,8 @@ TEST_P(QuotaDatabaseTest, BucketLastModifiedBetween) { EXPECT_FALSE(ContainsBucket(buckets, bucket3)); EXPECT_FALSE(ContainsBucket(buckets, bucket4)); - result = db.GetBucketsModifiedBetween(kPerm, base::Time::FromJavaTime(0), - base::Time::FromJavaTime(35)); + result = db->GetBucketsModifiedBetween(kPerm, base::Time::FromJavaTime(0), + base::Time::FromJavaTime(35)); EXPECT_TRUE(result.ok()); buckets = result.value(); EXPECT_EQ(1U, buckets.size()); @@ -742,7 +772,7 @@ TEST_P(QuotaDatabaseTest, BucketLastModifiedBetween) { } TEST_P(QuotaDatabaseTest, RegisterInitialStorageKeyInfo) { - QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); + auto db = CreateDatabase(use_in_memory_db()); base::flat_map<blink::mojom::StorageType, std::set<StorageKey>> storage_keys_by_type; @@ -755,54 +785,35 @@ TEST_P(QuotaDatabaseTest, RegisterInitialStorageKeyInfo) { storage_keys_by_type.emplace( kPerm, std::set<StorageKey>(kStorageKeys, std::end(kStorageKeys))); - EXPECT_EQ(db.RegisterInitialStorageKeyInfo(storage_keys_by_type), + EXPECT_EQ(db->RegisterInitialStorageKeyInfo(storage_keys_by_type), QuotaError::kNone); QuotaErrorOr<BucketInfo> bucket_result = - db.GetBucket(StorageKey::CreateFromStringForTesting("http://a/"), - kDefaultBucketName, kTemp); + db->GetBucket(StorageKey::CreateFromStringForTesting("http://a/"), + kDefaultBucketName, kTemp); ASSERT_TRUE(bucket_result.ok()); QuotaErrorOr<QuotaDatabase::BucketTableEntry> info = - db.GetBucketInfo(bucket_result->id); + db->GetBucketInfo(bucket_result->id); EXPECT_TRUE(info.ok()); EXPECT_EQ(0, info->use_count); - EXPECT_EQ(db.SetStorageKeyLastAccessTime( + EXPECT_EQ(db->SetStorageKeyLastAccessTime( StorageKey::CreateFromStringForTesting("http://a/"), kTemp, base::Time::FromDoubleT(1.0)), QuotaError::kNone); - info = db.GetBucketInfo(bucket_result->id); + info = db->GetBucketInfo(bucket_result->id); EXPECT_TRUE(info.ok()); EXPECT_EQ(1, info->use_count); - EXPECT_EQ(db.RegisterInitialStorageKeyInfo(storage_keys_by_type), + EXPECT_EQ(db->RegisterInitialStorageKeyInfo(storage_keys_by_type), QuotaError::kNone); - info = db.GetBucketInfo(bucket_result->id); + info = db->GetBucketInfo(bucket_result->id); EXPECT_TRUE(info.ok()); EXPECT_EQ(1, info->use_count); } -TEST_P(QuotaDatabaseTest, DumpQuotaTable) { - QuotaTableEntry kTableEntries[] = { - {.host = "http://go/", .type = kTemp, .quota = 1}, - {.host = "http://oo/", .type = kTemp, .quota = 2}, - {.host = "http://gle/", .type = kPerm, .quota = 3}}; - - QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); - EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); - AssignQuotaTable(&db, kTableEntries); - - using Verifier = EntryVerifier<QuotaTableEntry>; - Verifier verifier(kTableEntries, std::end(kTableEntries)); - EXPECT_EQ( - DumpQuotaTable(&db, base::BindRepeating(&Verifier::Run, - base::Unretained(&verifier))), - QuotaError::kNone); - EXPECT_TRUE(verifier.table.empty()); -} - TEST_P(QuotaDatabaseTest, DumpBucketTable) { base::Time now = base::Time::Now(); using Entry = QuotaDatabase::BucketTableEntry; @@ -815,16 +826,16 @@ TEST_P(QuotaDatabaseTest, DumpBucketTable) { kTemp, kDefaultBucketName, 1, now, now), }; - QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); - EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); - AssignBucketTable(&db, kTableEntries); + auto db = CreateDatabase(use_in_memory_db()); + EXPECT_TRUE(EnsureOpened(db.get())); + AssignBucketTable(db.get(), kTableEntries); using Verifier = EntryVerifier<Entry>; Verifier verifier(kTableEntries, std::end(kTableEntries)); - EXPECT_EQ( - DumpBucketTable(&db, base::BindRepeating(&Verifier::Run, - base::Unretained(&verifier))), - QuotaError::kNone); + EXPECT_EQ(DumpBucketTable(db.get(), + base::BindRepeating(&Verifier::Run, + base::Unretained(&verifier))), + QuotaError::kNone); EXPECT_TRUE(verifier.table.empty()); } @@ -834,13 +845,13 @@ TEST_P(QuotaDatabaseTest, GetBucketInfo) { Entry(BucketId(123), StorageKey::CreateFromStringForTesting("http://go/"), kTemp, "test_bucket", 100, base::Time(), base::Time())}; - QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath()); - EXPECT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); - AssignBucketTable(&db, kTableEntries); + auto db = CreateDatabase(use_in_memory_db()); + EXPECT_TRUE(EnsureOpened(db.get())); + AssignBucketTable(db.get(), kTableEntries); { QuotaErrorOr<QuotaDatabase::BucketTableEntry> entry = - db.GetBucketInfo(kTableEntries[0].bucket_id); + db->GetBucketInfo(kTableEntries[0].bucket_id); EXPECT_TRUE(entry.ok()); EXPECT_EQ(kTableEntries[0].bucket_id, entry->bucket_id); EXPECT_EQ(kTableEntries[0].type, entry->type); @@ -854,37 +865,47 @@ TEST_P(QuotaDatabaseTest, GetBucketInfo) { { // BucketId 456 is not in the database. QuotaErrorOr<QuotaDatabase::BucketTableEntry> entry = - db.GetBucketInfo(BucketId(456)); + db->GetBucketInfo(BucketId(456)); EXPECT_FALSE(entry.ok()); } } // Non-parameterized tests. TEST_F(QuotaDatabaseTest, BootstrapFlag) { - QuotaDatabase db(DbPath()); + auto db = CreateDatabase(/*is_incognito=*/false); - EXPECT_FALSE(db.IsBootstrapped()); - EXPECT_EQ(db.SetIsBootstrapped(true), QuotaError::kNone); - EXPECT_TRUE(db.IsBootstrapped()); - EXPECT_EQ(db.SetIsBootstrapped(false), QuotaError::kNone); - EXPECT_FALSE(db.IsBootstrapped()); + EXPECT_FALSE(db->IsBootstrapped()); + EXPECT_EQ(db->SetIsBootstrapped(true), QuotaError::kNone); + EXPECT_TRUE(db->IsBootstrapped()); + EXPECT_EQ(db->SetIsBootstrapped(false), QuotaError::kNone); + EXPECT_FALSE(db->IsBootstrapped()); } TEST_F(QuotaDatabaseTest, OpenCorruptedDatabase) { base::HistogramTester histograms; // Create database, force corruption and close db by leaving scope. { - QuotaDatabase db(DbPath()); - ASSERT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kCreateIfNotFound)); + auto db = CreateDatabase(/*is_incognito=*/false); + ASSERT_TRUE(EnsureOpened(db.get())); ASSERT_TRUE(sql::test::CorruptSizeInHeader(DbPath())); + + // Add fake data into storage directory. + base::FilePath storage_path = db->GetStoragePath(); + ASSERT_TRUE(base::CreateDirectory(storage_path)); + ASSERT_TRUE(base::WriteFile(storage_path.AppendASCII("FakeStorage"), + "dummy_content")); } // Reopen database and verify schema reset on reopen. { sql::test::ScopedErrorExpecter expecter; expecter.ExpectError(SQLITE_CORRUPT); - QuotaDatabase db(DbPath()); - ASSERT_TRUE(EnsureOpened(&db, EnsureOpenedMode::kFailIfNotFound)); + auto db = CreateDatabase(/*is_incognito=*/false); + ASSERT_TRUE(EnsureOpened(db.get())); EXPECT_TRUE(expecter.SawExpectedErrors()); + + // Ensure data is deleted. + base::FilePath storage_path = db->GetStoragePath(); + EXPECT_FALSE(base::IsDirectoryEmpty(storage_path)); } histograms.ExpectTotalCount("Quota.QuotaDatabaseReset", 1); @@ -897,8 +918,105 @@ TEST_F(QuotaDatabaseTest, OpenCorruptedDatabase) { 1); } +TEST_F(QuotaDatabaseTest, QuotaDatabasePathMigration) { + const base::FilePath kLegacyFilePath = + ProfilePath().AppendASCII(kDatabaseName); + const StorageKey kStorageKey = + StorageKey::CreateFromStringForTesting("http://google/"); + const std::string kBucketName = "google_bucket"; + // Create database, add bucket and close by leaving scope. + { + auto db = CreateDatabase(/*is_incognito=*/false); + auto result = db->GetOrCreateBucket(kStorageKey, kBucketName); + ASSERT_TRUE(result.ok()); + } + // Move db file paths to legacy file path for path migration test setup. + { + base::Move(DbPath(), kLegacyFilePath); + base::Move(sql::Database::JournalPath(DbPath()), + sql::Database::JournalPath(kLegacyFilePath)); + } + // Reopen database, check that db is migrated to new path with bucket data. + { + auto db = CreateDatabase(/*is_incognito=*/false); + auto result = db->GetBucket(kStorageKey, kBucketName, kTemp); + EXPECT_TRUE(result.ok()); + EXPECT_FALSE(base::PathExists(kLegacyFilePath)); + EXPECT_TRUE(base::PathExists(DbPath())); + } +} + +// Test for crbug.com/1316581. +TEST_F(QuotaDatabaseTest, QuotaDatabasePathBadMigration) { + const base::FilePath kLegacyFilePath = + ProfilePath().AppendASCII(kDatabaseName); + const StorageKey kStorageKey = + StorageKey::CreateFromStringForTesting("http://google/"); + const std::string kBucketName = "google_bucket"; + // Create database, add bucket and close by leaving scope. + { + auto db = CreateDatabase(/*is_incognito=*/false); + auto result = db->GetOrCreateBucket(kStorageKey, kBucketName); + ASSERT_TRUE(result.ok()); + } + // Copy db file paths to legacy file path to mimic bad migration state. + base::CopyFile(DbPath(), kLegacyFilePath); + + // Reopen database, check that db is migrated and is in a good state. + { + auto db = CreateDatabase(/*is_incognito=*/false); + auto result = db->GetBucket(kStorageKey, kBucketName, kTemp); + EXPECT_TRUE(result.ok()); + EXPECT_TRUE(base::PathExists(DbPath())); + } +} + +// Test for crbug.com/1322375. +// +// base::CreateDirectory behaves differently on Mac and allows directory +// migration to succeed when we expect failure. +#if !BUILDFLAG(IS_MAC) +TEST_F(QuotaDatabaseTest, QuotaDatabaseDirectoryMigrationError) { + const base::FilePath kLegacyFilePath = + ProfilePath().AppendASCII(kDatabaseName); + const StorageKey kGoogleStorageKey = + StorageKey::CreateFromStringForTesting("http://google/"); + const StorageKey kExampleStorageKey = + StorageKey::CreateFromStringForTesting("http://example/"); + BucketId example_id; + // Create database, add bucket and close by leaving scope. + { + auto db = CreateDatabase(/*is_incognito=*/false); + // Create two buckets to check that ids are different after database reset. + auto result = db->GetOrCreateBucket(kGoogleStorageKey, kDefaultBucketName); + ASSERT_TRUE(result.ok()); + result = db->GetOrCreateBucket(kExampleStorageKey, kDefaultBucketName); + ASSERT_TRUE(result.ok()); + example_id = result->id; + } + { + // Delete database files to force a bad migration state. + base::DeleteFile(DbPath()); + base::DeleteFile(sql::Database::JournalPath(DbPath())); + + // Create a directory with the database file path to force directory + // migration to fail. + base::CreateDirectory(kLegacyFilePath); + } + { + // Open database to trigger migration. Migration failure forces a database + // reset. + auto db = CreateDatabase(/*is_incognito=*/false); + auto result = db->GetOrCreateBucket(kExampleStorageKey, kDefaultBucketName); + ASSERT_TRUE(result.ok()); + // Validate database reset by checking that bucket id doesn't match. + EXPECT_NE(result->id, example_id); + } +} +#endif // !BUILDFLAG(IS_MAC) + TEST_F(QuotaDatabaseTest, GetOrCreateBucket_CorruptedDatabase) { - QuotaDatabase db(DbPath()); + QuotaDatabase db(ProfilePath()); StorageKey storage_key = StorageKey::CreateFromStringForTesting("http://google/"); std::string bucket_name = "google_bucket"; diff --git a/chromium/storage/browser/quota/quota_features.cc b/chromium/storage/browser/quota/quota_features.cc index 4d6dee2ee72..c2cb26451d9 100644 --- a/chromium/storage/browser/quota/quota_features.cc +++ b/chromium/storage/browser/quota/quota_features.cc @@ -42,7 +42,7 @@ constexpr base::FeatureParam<double> kShouldRemainAvailableRatio{ // Whether the StoragePolicyObserver only sends updates for modified origins. const base::Feature kOnlySendStoragePolicyUpdatesForModifiedOrigins{ "OnlySendStoragePolicyUpdatesForModifiedOrigins", - base::FEATURE_DISABLED_BY_DEFAULT}; + base::FEATURE_ENABLED_BY_DEFAULT}; } // namespace features } // namespace storage diff --git a/chromium/storage/browser/quota/quota_internals.mojom b/chromium/storage/browser/quota/quota_internals.mojom index 226b34d91b7..fc6b351ca57 100644 --- a/chromium/storage/browser/quota/quota_internals.mojom +++ b/chromium/storage/browser/quota/quota_internals.mojom @@ -4,10 +4,31 @@ module storage.mojom; +import "mojo/public/mojom/base/time.mojom"; import "url/mojom/origin.mojom"; +// Represents a single Storage Bucket entry. +struct BucketTableEntry { + int64 bucket_id; + string storage_key; + string host; + string type; + string name; + int64 use_count; + mojo_base.mojom.Time last_accessed; + mojo_base.mojom.Time last_modified; +}; + +// Represents the Storage Type for a given host. +// This is a subset of blink::mojom::StorageType. +enum StorageType { + kTemporary = 0, + kPersistent = 1, + kSyncable = 2, +}; + // Interface for controlling Quota Internals. -// Hosted on chrome://quota-internals" for WebUI content::QuotaInternalsUI. +// Hosted on "chrome://quota-internals" for WebUI content::QuotaInternalsUI. interface QuotaInternalsHandler { // Returns the total and available disk space in bits for a user, // which is then converted to bytes and displayed on the Quota Internals UI. @@ -21,4 +42,15 @@ interface QuotaInternalsHandler { // Used by the quota-internals page to test behavior of the storage pressure // callback and trigger a storage pressure notification. SimulateStoragePressure(url.mojom.Origin origin_url); + + // Returns an array of Storage Bucket entries stored in the QuotaDatabase. + RetrieveBucketsTable() => (array<BucketTableEntry> entries); + + // Returns a host's usage for a given storage type. + GetHostUsageForInternals(string host, StorageType storage_type) => + (int64 host_usage); + + // Returns the global usage and unlimited usage for a given storage type. + GetGlobalUsageForInternals(StorageType storage_type) => + (int64 usage, int64 unlimited_usage); };
\ No newline at end of file diff --git a/chromium/storage/browser/quota/quota_manager_impl.cc b/chromium/storage/browser/quota/quota_manager_impl.cc index cada5f89dd9..f58e4f1b4f4 100644 --- a/chromium/storage/browser/quota/quota_manager_impl.cc +++ b/chromium/storage/browser/quota/quota_manager_impl.cc @@ -8,6 +8,7 @@ #include <stdint.h> #include <algorithm> +#include <cstdint> #include <functional> #include <limits> #include <memory> @@ -16,6 +17,7 @@ #include "base/barrier_closure.h" #include "base/bind.h" #include "base/callback.h" +#include "base/callback_forward.h" #include "base/callback_helpers.h" #include "base/command_line.h" #include "base/containers/contains.h" @@ -31,7 +33,6 @@ #include "base/strings/strcat.h" #include "base/strings/string_number_conversions.h" #include "base/system/sys_info.h" -#include "base/task/post_task.h" #include "base/task/sequenced_task_runner.h" #include "base/task/single_thread_task_runner.h" #include "base/task/task_runner_util.h" @@ -47,6 +48,7 @@ #include "components/services/storage/public/mojom/quota_client.mojom.h" #include "mojo/public/cpp/bindings/pending_remote.h" #include "storage/browser/quota/client_usage_tracker.h" +#include "storage/browser/quota/quota_callbacks.h" #include "storage/browser/quota/quota_client_type.h" #include "storage/browser/quota/quota_features.h" #include "storage/browser/quota/quota_macros.h" @@ -100,152 +102,30 @@ bool IsSupportedIncognitoType(StorageType type) { return type == StorageType::kTemporary || type == StorageType::kPersistent; } -QuotaErrorOr<BucketInfo> GetOrCreateBucketOnDBThread( - const StorageKey& storage_key, - const std::string& bucket_name, - QuotaDatabase* database) { - DCHECK(database); - return database->GetOrCreateBucket(storage_key, bucket_name); -} - -QuotaErrorOr<BucketInfo> GetOrCreateBucketDeprecatedOnDBThread( - const StorageKey& storage_key, - const std::string& bucket_name, - blink::mojom::StorageType storage_type, - QuotaDatabase* database) { - DCHECK(database); - return database->GetOrCreateBucketDeprecated(storage_key, bucket_name, - storage_type); -} - -QuotaErrorOr<BucketInfo> CreateBucketOnDBThread( - const StorageKey& storage_key, - const std::string& bucket_name, - blink::mojom::StorageType storage_type, - QuotaDatabase* database) { - DCHECK(database); - return database->CreateBucketForTesting(storage_key, bucket_name, // IN-TEST - storage_type); -} - -QuotaErrorOr<BucketInfo> GetBucketOnDBThread(const StorageKey& storage_key, - const std::string& bucket_name, - blink::mojom::StorageType type, - QuotaDatabase* database) { - DCHECK(database); - return database->GetBucket(storage_key, bucket_name, type); -} - -QuotaErrorOr<std::set<StorageKey>> GetStorageKeysForTypeOnDBThread( - StorageType type, - QuotaDatabase* database) { - DCHECK(database); - return database->GetStorageKeysForType(type); -} - -QuotaErrorOr<std::set<BucketLocator>> GetBucketsForTypeOnDBThread( - StorageType type, - QuotaDatabase* database) { - DCHECK(database); - return database->GetBucketsForType(type); -} - -QuotaErrorOr<std::set<BucketLocator>> GetBucketsForHostOnDBThread( - const std::string& host, - StorageType type, - QuotaDatabase* database) { - DCHECK(database); - return database->GetBucketsForHost(host, type); -} - -QuotaErrorOr<std::set<BucketLocator>> GetBucketsForStorageKeyOnDBThread( - const StorageKey& storage_key, - StorageType type, - QuotaDatabase* database) { - DCHECK(database); - return database->GetBucketsForStorageKey(storage_key, type); -} - -QuotaErrorOr<std::set<BucketLocator>> GetModifiedBetweenOnDBThread( - StorageType type, - base::Time begin, - base::Time end, - QuotaDatabase* database) { - DCHECK(database); - return database->GetBucketsModifiedBetween(type, begin, end); -} - -QuotaErrorOr<int64_t> GetPersistentHostQuotaOnDBThread( - const std::string& host, - QuotaDatabase* database) { - DCHECK(database); - return database->GetHostQuota(host, StorageType::kPersistent); -} - -QuotaError SetPersistentHostQuotaOnDBThread(const std::string& host, - int64_t* new_quota, - QuotaDatabase* database) { - DCHECK(database); - return database->SetHostQuota(host, StorageType::kPersistent, *new_quota); -} - -QuotaError SetDatabaseBootstrappedOnDBThread(QuotaDatabase* database) { - DCHECK(database); - return database->SetIsBootstrapped(true); -} - -QuotaErrorOr<BucketLocator> GetLRUBucketOnDBThread( - StorageType type, - const std::set<BucketId>& bucket_exceptions, - SpecialStoragePolicy* policy, - QuotaDatabase* database) { - DCHECK(database); - return database->GetLRUBucket(type, bucket_exceptions, policy); -} - -QuotaError DeleteBucketInfoOnDBThread(BucketId bucket_id, - QuotaDatabase* database) { - DCHECK(database); - return database->DeleteBucketInfo(bucket_id); -} - -QuotaErrorOr<QuotaDatabase::BucketTableEntry> -GetBucketInfoForEvictionOnDBThread(BucketId bucket_id, - QuotaDatabase* database) { - DCHECK(database); - return database->GetBucketInfo(bucket_id); -} - -QuotaError BootstrapDatabaseOnDBThread( - base::flat_map<StorageType, std::set<StorageKey>> storage_keys_by_type, - QuotaDatabase* database) { - DCHECK(database); - // Register existing storage keys with 0 last time access. - return database->RegisterInitialStorageKeyInfo( - std::move(storage_keys_by_type)); -} - -QuotaError UpdateAccessTimeOnDBThread(const StorageKey& storage_key, - StorageType type, - base::Time accessed_time, - QuotaDatabase* database) { - DCHECK(database); - return database->SetStorageKeyLastAccessTime(storage_key, type, - accessed_time); -} - -QuotaError UpdateBucketAccessTimeOnDBThread(BucketId bucket_id, - base::Time accessed_time, - QuotaDatabase* database) { - DCHECK(database); - return database->SetBucketLastAccessTime(bucket_id, accessed_time); +std::string StorageTypeEnumToString(StorageType type) { + switch (type) { + case StorageType::kTemporary: + return "temporary"; + case StorageType::kPersistent: + return "persistent"; + case StorageType::kSyncable: + return "syncable"; + case StorageType::kQuotaNotManaged: + return "quota-not-managed"; + case StorageType::kUnknown: + return "unknown"; + } } -QuotaError UpdateBucketModifiedTimeOnDBThread(BucketId bucket_id, - base::Time modified_time, - QuotaDatabase* database) { - DCHECK(database); - return database->SetBucketLastModifiedTime(bucket_id, modified_time); +StorageType GetBlinkStorageType(storage::mojom::StorageType type) { + switch (type) { + case storage::mojom::StorageType::kTemporary: + return StorageType::kTemporary; + case storage::mojom::StorageType::kPersistent: + return StorageType::kPersistent; + case storage::mojom::StorageType::kSyncable: + return StorageType::kSyncable; + } } void DidGetUsageAndQuotaStripBreakdown( @@ -1075,43 +955,7 @@ class QuotaManagerImpl::StorageCleanupHelper : public QuotaTask { // Gather storage key info table for quota-internals page. // // This class is granted ownership of itself when it is passed to -// DidDumpQuotaTable() via base::Owned(). When the closure for said function -// goes out of scope, the object is deleted. -// This class is not thread-safe because there can be a race when entries_ is -// modified. -class QuotaManagerImpl::DumpQuotaTableHelper { - public: - QuotaError DumpQuotaTableOnDBThread(QuotaDatabase* database) { - DCHECK(database); - return database->DumpQuotaTable(base::BindRepeating( - &DumpQuotaTableHelper::AppendEntry, base::Unretained(this))); - } - - void DidDumpQuotaTable(const base::WeakPtr<QuotaManagerImpl>& manager, - DumpQuotaTableCallback callback, - QuotaError error) { - if (!manager) { - // The operation was aborted. - std::move(callback).Run(QuotaTableEntries()); - return; - } - manager->DidDatabaseWork(error != QuotaError::kDatabaseError); - std::move(callback).Run(entries_); - } - - private: - bool AppendEntry(const QuotaTableEntry& entry) { - entries_.push_back(entry); - return true; - } - - QuotaTableEntries entries_; -}; - -// Gather storage key info table for quota-internals page. -// -// This class is granted ownership of itself when it is passed to -// DidDumpQuotaTable() via base::Owned(). When the closure for said function +// DidDumpBucketTable() via base::Owned(). When the closure for said function // goes out of scope, the object is deleted. // This class is not thread-safe because there can be races when entries_ is // modified. @@ -1156,7 +1000,9 @@ QuotaManagerImpl::QuotaManagerImpl( : RefCountedDeleteOnSequence<QuotaManagerImpl>(io_thread), is_incognito_(is_incognito), profile_path_(profile_path), - proxy_(base::MakeRefCounted<QuotaManagerProxy>(this, io_thread)), + proxy_(base::MakeRefCounted<QuotaManagerProxy>(this, + io_thread, + profile_path)), io_thread_(std::move(io_thread)), db_runner_(base::ThreadPool::CreateSequencedTaskRunner( {base::MayBlock(), base::TaskPriority::USER_VISIBLE, @@ -1195,7 +1041,13 @@ void QuotaManagerImpl::GetOrCreateBucket( return; } PostTaskAndReplyWithResultForDBThread( - base::BindOnce(&GetOrCreateBucketOnDBThread, storage_key, bucket_name), + base::BindOnce( + [](const StorageKey& storage_key, const std::string& bucket_name, + QuotaDatabase* database) { + DCHECK(database); + return database->GetOrCreateBucket(storage_key, bucket_name); + }, + storage_key, bucket_name), base::BindOnce(&QuotaManagerImpl::DidGetBucket, weak_factory_.GetWeakPtr(), std::move(callback))); } @@ -1214,8 +1066,14 @@ void QuotaManagerImpl::GetOrCreateBucketDeprecated( return; } PostTaskAndReplyWithResultForDBThread( - base::BindOnce(&GetOrCreateBucketDeprecatedOnDBThread, storage_key, - bucket_name, storage_type), + base::BindOnce( + [](const StorageKey& storage_key, const std::string& bucket_name, + blink::mojom::StorageType storage_type, QuotaDatabase* database) { + DCHECK(database); + return database->GetOrCreateBucketDeprecated( + storage_key, bucket_name, storage_type); + }, + storage_key, bucket_name, storage_type), base::BindOnce(&QuotaManagerImpl::DidGetBucket, weak_factory_.GetWeakPtr(), std::move(callback))); } @@ -1234,8 +1092,14 @@ void QuotaManagerImpl::CreateBucketForTesting( return; } PostTaskAndReplyWithResultForDBThread( - base::BindOnce(&CreateBucketOnDBThread, storage_key, bucket_name, - storage_type), + base::BindOnce( + [](const StorageKey& storage_key, const std::string& bucket_name, + blink::mojom::StorageType storage_type, QuotaDatabase* database) { + DCHECK(database); + return database->CreateBucketForTesting( // IN-TEST + storage_key, bucket_name, storage_type); + }, + storage_key, bucket_name, storage_type), base::BindOnce(&QuotaManagerImpl::DidGetBucket, weak_factory_.GetWeakPtr(), std::move(callback))); } @@ -1254,7 +1118,13 @@ void QuotaManagerImpl::GetBucket( return; } PostTaskAndReplyWithResultForDBThread( - base::BindOnce(&GetBucketOnDBThread, storage_key, bucket_name, type), + base::BindOnce( + [](const StorageKey& storage_key, const std::string& bucket_name, + blink::mojom::StorageType type, QuotaDatabase* database) { + DCHECK(database); + return database->GetBucket(storage_key, bucket_name, type); + }, + storage_key, bucket_name, type), base::BindOnce(&QuotaManagerImpl::DidGetBucket, weak_factory_.GetWeakPtr(), std::move(callback))); } @@ -1270,7 +1140,12 @@ void QuotaManagerImpl::GetStorageKeysForType(blink::mojom::StorageType type, return; } PostTaskAndReplyWithResultForDBThread( - base::BindOnce(&GetStorageKeysForTypeOnDBThread, type), + base::BindOnce( + [](blink::mojom::StorageType type, QuotaDatabase* database) { + DCHECK(database); + return database->GetStorageKeysForType(type); + }, + type), base::BindOnce(&QuotaManagerImpl::DidGetStorageKeys, weak_factory_.GetWeakPtr(), std::move(callback))); } @@ -1287,7 +1162,12 @@ void QuotaManagerImpl::GetBucketsForType( return; } PostTaskAndReplyWithResultForDBThread( - base::BindOnce(&GetBucketsForTypeOnDBThread, type), + base::BindOnce( + [](StorageType type, QuotaDatabase* database) { + DCHECK(database); + return database->GetBucketsForType(type); + }, + type), base::BindOnce(&QuotaManagerImpl::DidGetBuckets, weak_factory_.GetWeakPtr(), std::move(callback))); } @@ -1305,7 +1185,13 @@ void QuotaManagerImpl::GetBucketsForHost( return; } PostTaskAndReplyWithResultForDBThread( - base::BindOnce(&GetBucketsForHostOnDBThread, host, type), + base::BindOnce( + [](const std::string& host, StorageType type, + QuotaDatabase* database) { + DCHECK(database); + return database->GetBucketsForHost(host, type); + }, + host, type), base::BindOnce(&QuotaManagerImpl::DidGetBuckets, weak_factory_.GetWeakPtr(), std::move(callback))); } @@ -1323,7 +1209,13 @@ void QuotaManagerImpl::GetBucketsForStorageKey( return; } PostTaskAndReplyWithResultForDBThread( - base::BindOnce(&GetBucketsForStorageKeyOnDBThread, storage_key, type), + base::BindOnce( + [](const StorageKey& storage_key, StorageType type, + QuotaDatabase* database) { + DCHECK(database); + return database->GetBucketsForStorageKey(storage_key, type); + }, + storage_key, type), base::BindOnce(&QuotaManagerImpl::DidGetBuckets, weak_factory_.GetWeakPtr(), std::move(callback))); } @@ -1489,8 +1381,13 @@ void QuotaManagerImpl::FindAndDeleteBucketData(const StorageKey& storage_key, return; } PostTaskAndReplyWithResultForDBThread( - base::BindOnce(&GetBucketOnDBThread, storage_key, bucket_name, - StorageType::kTemporary), + base::BindOnce( + [](const StorageKey& storage_key, const std::string& bucket_name, + blink::mojom::StorageType type, QuotaDatabase* database) { + DCHECK(database); + return database->GetBucket(storage_key, bucket_name, type); + }, + storage_key, bucket_name, StorageType::kTemporary), base::BindOnce(&QuotaManagerImpl::DidGetBucketForDeletion, weak_factory_.GetWeakPtr(), std::move(callback))); } @@ -1599,7 +1496,12 @@ void QuotaManagerImpl::GetPersistentHostQuota(const std::string& host, return; PostTaskAndReplyWithResultForDBThread( - base::BindOnce(&GetPersistentHostQuotaOnDBThread, host), + base::BindOnce( + [](const std::string& host, QuotaDatabase* database) { + DCHECK(database); + return database->GetHostQuota(host, StorageType::kPersistent); + }, + host), base::BindOnce(&QuotaManagerImpl::DidGetPersistentHostQuota, weak_factory_.GetWeakPtr(), host)); } @@ -1635,8 +1537,14 @@ void QuotaManagerImpl::SetPersistentHostQuota(const std::string& host, } int64_t* new_quota_ptr = new int64_t(new_quota); PostTaskAndReplyWithResultForDBThread( - base::BindOnce(&SetPersistentHostQuotaOnDBThread, host, - base::Unretained(new_quota_ptr)), + base::BindOnce( + [](const std::string& host, int64_t* new_quota, + QuotaDatabase* database) { + DCHECK(database); + return database->SetHostQuota(host, StorageType::kPersistent, + *new_quota); + }, + host, base::Unretained(new_quota_ptr)), base::BindOnce(&QuotaManagerImpl::DidSetPersistentHostQuota, weak_factory_.GetWeakPtr(), host, std::move(callback), base::Owned(new_quota_ptr))); @@ -1653,6 +1561,19 @@ void QuotaManagerImpl::GetGlobalUsage(StorageType type, usage_tracker->GetGlobalUsage(std::move(callback)); } +void QuotaManagerImpl::GetGlobalUsageForInternals( + storage::mojom::StorageType storage_type, + GetGlobalUsageForInternalsCallback callback) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(callback); + EnsureDatabaseOpened(); + + StorageType type = GetBlinkStorageType(storage_type); + UsageTracker* usage_tracker = GetUsageTracker(type); + DCHECK(usage_tracker); + usage_tracker->GetGlobalUsage(std::move(callback)); +} + void QuotaManagerImpl::GetHostUsageWithBreakdown( const std::string& host, StorageType type, @@ -1665,6 +1586,22 @@ void QuotaManagerImpl::GetHostUsageWithBreakdown( usage_tracker->GetHostUsageWithBreakdown(host, std::move(callback)); } +void QuotaManagerImpl::GetHostUsageForInternals( + const std::string& host, + storage::mojom::StorageType storage_type, + GetHostUsageForInternalsCallback callback) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + EnsureDatabaseOpened(); + + StorageType type = GetBlinkStorageType(storage_type); + UsageTracker* usage_tracker = GetUsageTracker(type); + DCHECK(usage_tracker); + + usage_tracker->GetHostUsageWithBreakdown( + host, base::BindOnce(&QuotaManagerImpl::OnGetHostUsageForInternals, + weak_factory_.GetWeakPtr(), std::move(callback))); +} + bool QuotaManagerImpl::IsStorageUnlimited(const StorageKey& storage_key, StorageType type) const { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); @@ -1694,7 +1631,13 @@ void QuotaManagerImpl::GetBucketsModifiedBetween(StorageType type, } PostTaskAndReplyWithResultForDBThread( - base::BindOnce(&GetModifiedBetweenOnDBThread, type, begin, end), + base::BindOnce( + [](blink::mojom::StorageType type, base::Time begin, base::Time end, + QuotaDatabase* database) { + DCHECK(database); + return database->GetBucketsModifiedBetween(type, begin, end); + }, + type, begin, end), base::BindOnce(&QuotaManagerImpl::DidGetModifiedBetween, weak_factory_.GetWeakPtr(), std::move(callback), type)); } @@ -1744,9 +1687,8 @@ void QuotaManagerImpl::EnsureDatabaseOpened() { } // Use an empty path to open an in-memory only database for incognito. - database_ = std::make_unique<QuotaDatabase>( - is_incognito_ ? base::FilePath() - : profile_path_.AppendASCII(kDatabaseName)); + database_ = std::make_unique<QuotaDatabase>(is_incognito_ ? base::FilePath() + : profile_path_); temporary_usage_tracker_ = std::make_unique<UsageTracker>( this, client_types_[StorageType::kTemporary], StorageType::kTemporary, @@ -1804,8 +1746,15 @@ void QuotaManagerImpl::DidGetStorageKeysForBootstrap( storage_key_gatherer_.reset(); PostTaskAndReplyWithResultForDBThread( - base::BindOnce(&BootstrapDatabaseOnDBThread, - std::move(storage_keys_by_type)), + base::BindOnce( + [](base::flat_map<StorageType, std::set<StorageKey>> + storage_keys_by_type, + QuotaDatabase* database) { + DCHECK(database); + return database->RegisterInitialStorageKeyInfo( + std::move(storage_keys_by_type)); + }, + std::move(storage_keys_by_type)), base::BindOnce(&QuotaManagerImpl::DidBootstrapDatabase, weak_factory_.GetWeakPtr()), FROM_HERE, @@ -1814,10 +1763,14 @@ void QuotaManagerImpl::DidGetStorageKeysForBootstrap( void QuotaManagerImpl::DidBootstrapDatabase(QuotaError error) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - DidDatabaseWork(error != QuotaError::kDatabaseError); + DidDatabaseWork(error != QuotaError::kDatabaseError, + /*is_bootstrap_work=*/true); PostTaskAndReplyWithResultForDBThread( - base::BindOnce(&SetDatabaseBootstrappedOnDBThread), + base::BindOnce([](QuotaDatabase* database) { + DCHECK(database); + return database->SetIsBootstrapped(true); + }), base::BindOnce(&QuotaManagerImpl::DidSetDatabaseBootstrapped, weak_factory_.GetWeakPtr()), FROM_HERE, @@ -1828,7 +1781,8 @@ void QuotaManagerImpl::DidSetDatabaseBootstrapped(QuotaError error) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(is_bootstrapping_database_); is_bootstrapping_database_ = false; - DidDatabaseWork(error != QuotaError::kDatabaseError); + DidDatabaseWork(error != QuotaError::kDatabaseError, + /*is_bootstrap_work=*/true); RunDatabaseCallbacks(); StartEviction(); @@ -1872,6 +1826,16 @@ UsageTracker* QuotaManagerImpl::GetUsageTracker(StorageType type) const { return nullptr; } +void QuotaManagerImpl::OnGetHostUsageForInternals( + GetHostUsageForInternalsCallback callback, + int64_t usage, + blink::mojom::UsageBreakdownPtr usage_breakdown) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK_GE(usage, -1); + + std::move(callback).Run(usage); +} + void QuotaManagerImpl::NotifyStorageAccessed(const StorageKey& storage_key, StorageType type, base::Time access_time) { @@ -1886,8 +1850,14 @@ void QuotaManagerImpl::NotifyStorageAccessed(const StorageKey& storage_key, if (db_disabled_) return; PostTaskAndReplyWithResultForDBThread( - base::BindOnce(&UpdateAccessTimeOnDBThread, storage_key, type, - access_time), + base::BindOnce( + [](const StorageKey& storage_key, StorageType type, + base::Time accessed_time, QuotaDatabase* database) { + DCHECK(database); + return database->SetStorageKeyLastAccessTime(storage_key, type, + accessed_time); + }, + storage_key, type, access_time), base::BindOnce(&QuotaManagerImpl::OnComplete, weak_factory_.GetWeakPtr())); } @@ -1905,7 +1875,13 @@ void QuotaManagerImpl::NotifyBucketAccessed(BucketId bucket_id, if (db_disabled_) return; PostTaskAndReplyWithResultForDBThread( - base::BindOnce(&UpdateBucketAccessTimeOnDBThread, bucket_id, access_time), + base::BindOnce( + [](BucketId bucket_id, base::Time accessed_time, + QuotaDatabase* database) { + DCHECK(database); + return database->SetBucketLastAccessTime(bucket_id, accessed_time); + }, + bucket_id, access_time), base::BindOnce(&QuotaManagerImpl::OnComplete, weak_factory_.GetWeakPtr())); } @@ -1928,8 +1904,13 @@ void QuotaManagerImpl::NotifyStorageModified(QuotaClientType client_id, } PostTaskAndReplyWithResultForDBThread( - base::BindOnce(&GetBucketOnDBThread, storage_key, kDefaultBucketName, - type), + base::BindOnce( + [](const StorageKey& storage_key, const std::string& bucket_name, + blink::mojom::StorageType type, QuotaDatabase* database) { + DCHECK(database); + return database->GetBucket(storage_key, bucket_name, type); + }, + storage_key, kDefaultBucketName, type), base::BindOnce(&QuotaManagerImpl::DidGetBucketForUsage, weak_factory_.GetWeakPtr(), client_id, delta, modification_time, std::move(callback))); @@ -1944,57 +1925,69 @@ void QuotaManagerImpl::NotifyBucketModified(QuotaClientType client_id, DCHECK(callback); EnsureDatabaseOpened(); - if (callback) - std::move(callback).Run(); - - if (db_disabled_) - return; - - // TODO(crbug.com/1199417): Update bucket usage in UsageTracker once - // QuotaClient & UsageTracker operate by bucket. UsageTracker should be - // updated after ensuring there is a entry in the QuotaDatabase. - // Run `callback` on completion. - PostTaskAndReplyWithResultForDBThread( - base::BindOnce(&UpdateBucketModifiedTimeOnDBThread, bucket_id, - modification_time), - base::BindOnce(&QuotaManagerImpl::OnComplete, - weak_factory_.GetWeakPtr())); + base::BindOnce( + [](BucketId bucket_id, QuotaDatabase* database) { + DCHECK(database); + return database->GetBucketById(bucket_id); + }, + bucket_id), + base::BindOnce(&QuotaManagerImpl::DidGetBucketForUsage, + weak_factory_.GetWeakPtr(), client_id, delta, + modification_time, std::move(callback))); } -void QuotaManagerImpl::DumpQuotaTable(DumpQuotaTableCallback callback) { +void QuotaManagerImpl::DumpBucketTable(DumpBucketTableCallback callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(callback); - if (db_disabled_) { - std::move(callback).Run(QuotaTableEntries()); + if (db_disabled_ || !database_) { + std::move(callback).Run(BucketTableEntries()); return; } - - DumpQuotaTableHelper* helper = new DumpQuotaTableHelper; + DumpBucketTableHelper* helper = new DumpBucketTableHelper; PostTaskAndReplyWithResultForDBThread( - base::BindOnce(&DumpQuotaTableHelper::DumpQuotaTableOnDBThread, + base::BindOnce(&DumpBucketTableHelper::DumpBucketTableOnDBThread, base::Unretained(helper)), - base::BindOnce(&DumpQuotaTableHelper::DidDumpQuotaTable, + base::BindOnce(&DumpBucketTableHelper::DidDumpBucketTable, base::Owned(helper), weak_factory_.GetWeakPtr(), std::move(callback))); } +void QuotaManagerImpl::DidRetrieveBucketsTable( + RetrieveBucketsTableCallback callback, + const BucketTableEntries& entries) { + std::vector<storage::mojom::BucketTableEntryPtr> mojo_entries; -void QuotaManagerImpl::DumpBucketTable(DumpBucketTableCallback callback) { + for (auto& n : entries) { + DCHECK(IsSupportedType(n.type)); + storage::mojom::BucketTableEntryPtr entry = + storage::mojom::BucketTableEntry::New(); + entry->bucket_id = n.bucket_id.value(); + entry->storage_key = n.storage_key.Serialize(); + entry->host = n.storage_key.origin().host(); + entry->type = StorageTypeEnumToString(n.type); + entry->name = n.name; + entry->use_count = n.use_count; + entry->last_accessed = n.last_accessed; + entry->last_modified = n.last_modified; + mojo_entries.push_back(std::move(entry)); + } + std::move(callback).Run(std::move(mojo_entries)); +} + +void QuotaManagerImpl::RetrieveBucketsTable( + RetrieveBucketsTableCallback callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(callback); if (db_disabled_) { - std::move(callback).Run(BucketTableEntries()); + std::move(callback).Run({}); return; } - DumpBucketTableHelper* helper = new DumpBucketTableHelper; - PostTaskAndReplyWithResultForDBThread( - base::BindOnce(&DumpBucketTableHelper::DumpBucketTableOnDBThread, - base::Unretained(helper)), - base::BindOnce(&DumpBucketTableHelper::DidDumpBucketTable, - base::Owned(helper), weak_factory_.GetWeakPtr(), - std::move(callback))); + + DumpBucketTable(base::BindOnce(&QuotaManagerImpl::DidRetrieveBucketsTable, + weak_factory_.GetWeakPtr(), + std::move(callback))); } void QuotaManagerImpl::StartEviction() { @@ -2021,7 +2014,12 @@ void QuotaManagerImpl::DeleteBucketFromDatabase( } PostTaskAndReplyWithResultForDBThread( - base::BindOnce(&DeleteBucketInfoOnDBThread, bucket_id), + base::BindOnce( + [](BucketId bucket_id, QuotaDatabase* database) { + DCHECK(database); + return database->DeleteBucketInfo(bucket_id); + }, + bucket_id), std::move(callback)); } @@ -2208,21 +2206,18 @@ void QuotaManagerImpl::SetQuotaChangeCallbackForTesting( quota_change_callback_ = std::move(storage_pressure_event_callback); } -void QuotaManagerImpl::SetQuotaDatabaseForTesting( - std::unique_ptr<QuotaDatabase> database) { - DCHECK(database); - database_ = std::move(database); - - // Initialize usage trackers after database is set. - temporary_usage_tracker_ = std::make_unique<UsageTracker>( - this, client_types_[StorageType::kTemporary], StorageType::kTemporary, - special_storage_policy_.get()); - persistent_usage_tracker_ = std::make_unique<UsageTracker>( - this, client_types_[StorageType::kPersistent], StorageType::kPersistent, - special_storage_policy_.get()); - syncable_usage_tracker_ = std::make_unique<UsageTracker>( - this, client_types_[StorageType::kSyncable], StorageType::kSyncable, - special_storage_policy_.get()); +void QuotaManagerImpl::CorruptDatabaseForTesting( + base::OnceCallback<void(const base::FilePath&)> corrupter, + base::OnceCallback<void(QuotaError)> callback) { + PostTaskAndReplyWithResultForDBThread( + base::BindOnce( + [](base::OnceCallback<void(const base::FilePath&)> corrupter, + QuotaDatabase* database) { + return database->CorruptForTesting( // IN-TEST + std::move(corrupter)); + }, + std::move(corrupter)), + std::move(callback)); } void QuotaManagerImpl::ReportHistogram() { @@ -2382,7 +2377,12 @@ void QuotaManagerImpl::EvictBucketData(const BucketLocator& bucket, eviction_context_.evict_bucket_data_callback = std::move(callback); PostTaskAndReplyWithResultForDBThread( - base::BindOnce(&GetBucketInfoForEvictionOnDBThread, bucket.id), + base::BindOnce( + [](BucketId bucket_id, QuotaDatabase* database) { + DCHECK(database); + return database->GetBucketInfo(bucket_id); + }, + bucket.id), base::BindOnce(&QuotaManagerImpl::DidGetBucketInfoForEviction, weak_factory_.GetWeakPtr(), bucket)); } @@ -2439,9 +2439,14 @@ void QuotaManagerImpl::GetLRUBucket(StorageType type, } PostTaskAndReplyWithResultForDBThread( - base::BindOnce(&GetLRUBucketOnDBThread, type, - GetEvictionBucketExceptions(), - base::RetainedRef(special_storage_policy_)), + base::BindOnce( + [](StorageType type, const std::set<BucketId>& bucket_exceptions, + SpecialStoragePolicy* policy, QuotaDatabase* database) { + DCHECK(database); + return database->GetLRUBucket(type, bucket_exceptions, policy); + }, + type, GetEvictionBucketExceptions(), + base::RetainedRef(special_storage_policy_)), base::BindOnce(&QuotaManagerImpl::DidGetLRUBucket, weak_factory_.GetWeakPtr())); } @@ -2592,14 +2597,58 @@ void QuotaManagerImpl::DidGetStorageCapacity( DetermineStoragePressure(total_space, available_space); } -void QuotaManagerImpl::DidDatabaseWork(bool success) { +void QuotaManagerImpl::DidDatabaseWork(bool success, bool is_bootstrap_work) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (success) return; + // Ignore any errors that happen while a new bootstrap attempt is already in + // progress or queued. + if (is_bootstrapping_database_) + return; + db_error_count_++; - if (db_error_count_ >= QuotaManagerImpl::kThresholdOfErrorsToDisableDatabase) - db_disabled_ = true; + + if (db_error_count_ >= + QuotaManagerImpl::kThresholdOfErrorsToDisableDatabase) { + if (bootstrap_disabled_for_testing_ || is_bootstrap_work) { + // If we got an error during bootstrapping there is no point in + // immediately trying again. Disable the database instead. + db_disabled_ = true; + return; + } + + // Start another bootstrapping process. Pause eviction while bootstrapping + // is in progress. When bootstrapping finishes a new Evictor will be + // created. + is_bootstrapping_database_ = true; + temporary_storage_evictor_ = nullptr; + db_error_count_ = 0; + + // Wipe the database before triggering another bootstrap. + base::PostTaskAndReplyWithResult( + db_runner_.get(), FROM_HERE, + base::BindOnce(&QuotaDatabase::RazeAndReopen, + base::Unretained(database_.get())), + base::BindOnce(&QuotaManagerImpl::DidRazeForReBootstrap, + weak_factory_.GetWeakPtr())); + } +} + +void QuotaManagerImpl::DidRazeForReBootstrap( + QuotaError raze_and_reopen_result) { + if (raze_and_reopen_result == QuotaError::kNone) { + BootstrapDatabase(); + return; + } + + // Deleting the database failed. Disable the database and hope we'll recover + // after Chrome restarts instead. + db_disabled_ = true; + is_bootstrapping_database_ = false; + RunDatabaseCallbacks(); + // No reason to restart eviction here. Without a working database there is + // nothing to evict. } void QuotaManagerImpl::OnComplete(QuotaError result) { @@ -2666,8 +2715,14 @@ void QuotaManagerImpl::DidGetBucketForUsage(QuotaClientType client_type, std::move(callback).Run(); PostTaskAndReplyWithResultForDBThread( - base::BindOnce(&UpdateBucketModifiedTimeOnDBThread, bucket.id, - modification_time), + base::BindOnce( + [](BucketId bucket_id, base::Time modified_time, + QuotaDatabase* database) { + DCHECK(database); + return database->SetBucketLastModifiedTime(bucket_id, + modified_time); + }, + bucket.id, modification_time), base::BindOnce(&QuotaManagerImpl::OnComplete, weak_factory_.GetWeakPtr())); return; diff --git a/chromium/storage/browser/quota/quota_manager_impl.h b/chromium/storage/browser/quota/quota_manager_impl.h index bc2eb685441..e64592831f7 100644 --- a/chromium/storage/browser/quota/quota_manager_impl.h +++ b/chromium/storage/browser/quota/quota_manager_impl.h @@ -354,6 +354,14 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaManagerImpl // storage::mojom::QuotaInternalsHandler implementation void GetDiskAvailability(GetDiskAvailabilityCallback callback) override; void GetStatistics(GetStatisticsCallback callback) override; + void RetrieveBucketsTable(RetrieveBucketsTableCallback callback) override; + void GetHostUsageForInternals( + const std::string& host, + storage::mojom::StorageType storage_type, + GetHostUsageForInternalsCallback callback) override; + void GetGlobalUsageForInternals( + storage::mojom::StorageType storage_type, + GetGlobalUsageForInternalsCallback callback) override; // Called by UI and internal modules. void GetPersistentHostQuota(const std::string& host, QuotaCallback callback); @@ -424,12 +432,31 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaManagerImpl eviction_disabled_ = disable; } - void SetQuotaDatabaseForTesting(std::unique_ptr<QuotaDatabase> database); + // Testing support for handling corruption in the underlying database. + // + // Runs `corrupter` on the same sequence used to do database I/O, + // guaranteeing that no other database operation is performed at the same + // time. `corrupter` receives the path to the underlying SQLite database as an + // argument. The underlying SQLite database is closed while `corrupter` runs, + // and reopened afterwards. + // + // `callback` is called with QuotaError::kNone if the database was + // successfully reopened after `corrupter` was run, or with + // QuotaError::kDatabaseError otherwise. + void CorruptDatabaseForTesting( + base::OnceCallback<void(const base::FilePath&)> corrupter, + base::OnceCallback<void(QuotaError)> callback); void SetBootstrapDisabledForTesting(bool disable) { bootstrap_disabled_for_testing_ = disable; } + bool is_bootstrapping_database_for_testing() { + return is_bootstrapping_database_; + } + + bool is_db_disabled_for_testing() { return db_disabled_; } + protected: ~QuotaManagerImpl() override; void SetQuotaChangeCallbackForTesting( @@ -451,7 +478,6 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaManagerImpl class StorageKeyGathererTask; class BucketDataDeleter; class HostDataDeleter; - class DumpQuotaTableHelper; class DumpBucketTableHelper; class StorageCleanupHelper; @@ -468,17 +494,13 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaManagerImpl std::set<int> active_override_session_ids; }; - using QuotaTableEntry = QuotaDatabase::QuotaTableEntry; using BucketTableEntry = QuotaDatabase::BucketTableEntry; - using QuotaTableEntries = std::vector<QuotaTableEntry>; using BucketTableEntries = std::vector<BucketTableEntry>; using StorageKeysByType = base::flat_map<blink::mojom::StorageType, std::set<blink::StorageKey>>; using QuotaSettingsCallback = base::OnceCallback<void(const QuotaSettings&)>; - using DumpQuotaTableCallback = - base::OnceCallback<void(const QuotaTableEntries&)>; using DumpBucketTableCallback = base::OnceCallback<void(const BucketTableEntries&)>; @@ -520,8 +542,13 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaManagerImpl UsageTracker* GetUsageTracker(blink::mojom::StorageType type) const; - void DumpQuotaTable(DumpQuotaTableCallback callback); void DumpBucketTable(DumpBucketTableCallback callback); + void DidRetrieveBucketsTable(RetrieveBucketsTableCallback callback, + const BucketTableEntries& entries); + void OnGetHostUsageForInternals( + GetHostUsageForInternalsCallback callback, + int64_t usage, + blink::mojom::UsageBreakdownPtr usage_breakdown); // Runs BucketDataDeleter which calls QuotaClients to clear data for the // bucket. Once the task is complete, calls the QuotaDatabase to delete the @@ -601,7 +628,8 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaManagerImpl void DidGetStorageCapacity( const std::tuple<int64_t, int64_t>& total_and_available); - void DidDatabaseWork(bool success); + void DidDatabaseWork(bool success, bool is_bootstrap_work = false); + void DidRazeForReBootstrap(QuotaError raze_and_reopen_result); void OnComplete(QuotaError result); void DidGetBucket(base::OnceCallback<void(QuotaErrorOr<BucketInfo>)> callback, @@ -636,7 +664,6 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaManagerImpl // pressure event dispatch if appropriate. // TODO(crbug.com/1088004): Implement UsageAndQuotaInfoGatherer::Completed() // to use DetermineStoragePressure(). - // TODO(crbug.com/1102433): Define and explain StoragePressure in the README. void DetermineStoragePressure(int64_t free_space, int64_t total_space); absl::optional<int64_t> GetQuotaOverrideForStorageKey( @@ -660,12 +687,6 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaManagerImpl const base::FilePath& path); static std::tuple<int64_t, int64_t> GetVolumeInfo(const base::FilePath& path); - bool is_bootstrapping_database_for_testing() { - return is_bootstrapping_database_; - } - - bool is_db_disabled_for_testing() { return db_disabled_; } - const bool is_incognito_; const base::FilePath profile_path_; diff --git a/chromium/storage/browser/quota/quota_manager_proxy.cc b/chromium/storage/browser/quota/quota_manager_proxy.cc index 8cce82b9af9..c4c37ec1bd0 100644 --- a/chromium/storage/browser/quota/quota_manager_proxy.cc +++ b/chromium/storage/browser/quota/quota_manager_proxy.cc @@ -15,9 +15,13 @@ #include "base/location.h" #include "base/memory/scoped_refptr.h" #include "base/sequence_checker.h" +#include "base/strings/string_number_conversions.h" +#include "base/synchronization/waitable_event.h" #include "base/task/sequenced_task_runner.h" #include "base/threading/thread_task_runner_handle.h" #include "base/time/time.h" +#include "components/services/storage/public/cpp/buckets/bucket_locator.h" +#include "components/services/storage/public/cpp/constants.h" #include "components/services/storage/public/mojom/quota_client.mojom.h" #include "mojo/public/cpp/bindings/pending_remote.h" #include "storage/browser/quota/quota_client_type.h" @@ -65,15 +69,45 @@ void DidGetStatus( QuotaManagerProxy::QuotaManagerProxy( QuotaManagerImpl* quota_manager_impl, - scoped_refptr<base::SequencedTaskRunner> quota_manager_impl_task_runner) + scoped_refptr<base::SequencedTaskRunner> quota_manager_impl_task_runner, + const base::FilePath& profile_path) : quota_manager_impl_(quota_manager_impl), quota_manager_impl_task_runner_( - std::move(quota_manager_impl_task_runner)) { + std::move(quota_manager_impl_task_runner)), + profile_path_(profile_path) { DCHECK(quota_manager_impl_task_runner_.get()); DETACH_FROM_SEQUENCE(quota_manager_impl_sequence_checker_); } +base::FilePath QuotaManagerProxy::GetBucketPath(const BucketLocator& bucket) { + return profile_path_.Append(kWebStorageDirectory) + .AppendASCII(base::NumberToString(bucket.id.value())); +} + +base::FilePath QuotaManagerProxy::GetClientBucketPath( + const BucketLocator& bucket, + QuotaClientType client_type) { + base::FilePath bucket_directory = GetBucketPath(bucket); + + switch (client_type) { + case QuotaClientType::kFileSystem: + return bucket_directory.Append(kFileSystemDirectory); + case QuotaClientType::kIndexedDatabase: + return bucket_directory.Append(kIndexedDbDirectory); + case QuotaClientType::kBackgroundFetch: + case QuotaClientType::kServiceWorkerCache: + return bucket_directory.Append(kCacheStorageDirectory); + case QuotaClientType::kServiceWorker: + return bucket_directory.Append(kScriptCacheDirectory); + case QuotaClientType::kMediaLicense: + return bucket_directory.Append(kMediaLicenseDirectory); + default: + NOTREACHED() << "Unsupported QuotaClientType"; + return base::FilePath(); + } +} + void QuotaManagerProxy::RegisterClient( mojo::PendingRemote<mojom::QuotaClient> client, QuotaClientType client_type, @@ -136,6 +170,28 @@ void QuotaManagerProxy::GetOrCreateBucket( std::move(callback))); } +QuotaErrorOr<BucketInfo> QuotaManagerProxy::GetOrCreateBucketSync( + const StorageKey& storage_key, + const std::string& bucket_name) { + // Ensure that the task runner we want is free and can be blocked on. + DCHECK(!quota_manager_impl_task_runner_->RunsTasksInCurrentSequence()); + QuotaErrorOr<BucketInfo> bucket; + base::WaitableEvent waiter(base::WaitableEvent::ResetPolicy::AUTOMATIC, + base::WaitableEvent::InitialState::NOT_SIGNALED); + // Call the async GetOrCreateBucket but block until it completes. + GetOrCreateBucket( + storage_key, bucket_name, quota_manager_impl_task_runner_, + base::BindOnce( + [](base::WaitableEvent* waiter, QuotaErrorOr<BucketInfo>* sync_bucket, + QuotaErrorOr<BucketInfo> result_bucket) { + *sync_bucket = std::move(result_bucket); + waiter->Signal(); + }, + &waiter, &bucket)); + waiter.Wait(); + return bucket; +} + void QuotaManagerProxy::GetOrCreateBucketDeprecated( const StorageKey& storage_key, const std::string& bucket_name, diff --git a/chromium/storage/browser/quota/quota_manager_proxy.h b/chromium/storage/browser/quota/quota_manager_proxy.h index 1b68cc3e359..d6d3baa02ed 100644 --- a/chromium/storage/browser/quota/quota_manager_proxy.h +++ b/chromium/storage/browser/quota/quota_manager_proxy.h @@ -38,6 +38,7 @@ class SequencedTaskRunner; namespace storage { +struct BucketLocator; class QuotaOverrideHandle; // Thread-safe proxy for QuotaManagerImpl. @@ -57,7 +58,8 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaManagerProxy // `quota_manager_impl` isn't a base::WeakPtr<QuotaManagerImpl>. QuotaManagerProxy( QuotaManagerImpl* quota_manager_impl, - scoped_refptr<base::SequencedTaskRunner> quota_manager_impl_task_runner); + scoped_refptr<base::SequencedTaskRunner> quota_manager_impl_task_runner, + const base::FilePath& profile_path); QuotaManagerProxy(const QuotaManagerProxy&) = delete; QuotaManagerProxy& operator=(const QuotaManagerProxy&) = delete; @@ -70,6 +72,15 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaManagerProxy virtual void BindInternalsHandler( mojo::PendingReceiver<mojom::QuotaInternalsHandler> receiver); + // Constructs path where `bucket` data is persisted to disk for partitioned + // storage. + base::FilePath GetBucketPath(const BucketLocator& bucket); + + // Constructs path where `bucket` and `client_type` data is persisted to disk + // for partitioned storage. + base::FilePath GetClientBucketPath(const BucketLocator& bucket, + QuotaClientType client_type); + // Gets the bucket with `bucket_name` for the `storage_key` for StorageType // kTemporary and returns the BucketInfo. If one doesn't exist, it creates // a new bucket with the specified policies. Returns a QuotaError if the @@ -80,6 +91,20 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaManagerProxy scoped_refptr<base::SequencedTaskRunner> callback_task_runner, base::OnceCallback<void(QuotaErrorOr<BucketInfo>)> callback); + // Synchronously gets the bucket with `bucket_name` for the `storage_key` for + // StorageType kTemporary and returns the BucketInfo. If one doesn't exist, it + // creates a new bucket with the specified policies. Returns a QuotaError if + // the operation has failed. This function calls the asynchronous + // GetOrCreateBucket function but blocks until completion. + // + // NOTE: this function cannot be called from the + // quota_manager_impl_task_runner. Additionally, the asychonrous version of + // this method `GetOrCreateBucket` is preferred; only use this synchronous + // version where asynchronous bucket retrieval is not possible. + virtual QuotaErrorOr<BucketInfo> GetOrCreateBucketSync( + const blink::StorageKey& storage_key, + const std::string& bucket_name); + // Same as GetOrCreateBucket but takes in StorageType. This should only be // used by FileSystem, and is expected to be removed when // StorageType::kSyncable and StorageType::kPersistent are deprecated. @@ -240,6 +265,8 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaManagerProxy // to the same object), and the object it points to is thread-safe. const scoped_refptr<base::SequencedTaskRunner> quota_manager_impl_task_runner_; + + const base::FilePath profile_path_; }; } // namespace storage diff --git a/chromium/storage/browser/quota/quota_manager_proxy_unittest.cc b/chromium/storage/browser/quota/quota_manager_proxy_unittest.cc new file mode 100644 index 00000000000..7037150d138 --- /dev/null +++ b/chromium/storage/browser/quota/quota_manager_proxy_unittest.cc @@ -0,0 +1,109 @@ +// Copyright 2022 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/files/scoped_temp_dir.h" +#include "base/memory/scoped_refptr.h" +#include "base/strings/string_number_conversions.h" +#include "base/test/task_environment.h" +#include "base/test/test_future.h" +#include "base/threading/thread_task_runner_handle.h" +#include "components/services/storage/public/cpp/buckets/bucket_info.h" +#include "components/services/storage/public/cpp/constants.h" +#include "components/services/storage/public/cpp/quota_error_or.h" +#include "storage/browser/quota/quota_client_type.h" +#include "storage/browser/quota/quota_manager_impl.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/common/storage_key/storage_key.h" + +namespace storage { + +class QuotaManagerProxyTest : public testing::Test { + public: + void SetUp() override { + EXPECT_TRUE(profile_path_.CreateUniqueTempDir()); + quota_manager_ = base::MakeRefCounted<QuotaManagerImpl>( + /*is_incognito*/ false, profile_path_.GetPath(), + base::ThreadTaskRunnerHandle::Get().get(), + /*quota_change_callback=*/base::DoNothing(), + /*storage_policy=*/nullptr, GetQuotaSettingsFunc()); + quota_manager_proxy_ = base::MakeRefCounted<QuotaManagerProxy>( + quota_manager_.get(), base::ThreadTaskRunnerHandle::Get(), + profile_path_.GetPath()); + } + + void TearDown() override { + quota_manager_proxy_ = nullptr; + quota_manager_ = nullptr; + } + + protected: + base::ScopedTempDir profile_path_; + scoped_refptr<QuotaManagerImpl> quota_manager_; + scoped_refptr<QuotaManagerProxy> quota_manager_proxy_; + + base::test::TaskEnvironment task_environment_; +}; + +TEST_F(QuotaManagerProxyTest, GetBucketPath) { + base::test::TestFuture<storage::QuotaErrorOr<storage::BucketInfo>> future; + quota_manager_proxy_->GetOrCreateBucket( + blink::StorageKey::CreateFromStringForTesting("http://example.com"), + "draft_bucket", base::ThreadTaskRunnerHandle::Get(), + future.GetCallback()); + auto bucket = future.Take(); + EXPECT_TRUE(bucket.ok()); + + base::FilePath expected_path = + profile_path_.GetPath() + .AppendASCII("WebStorage") + .AppendASCII(base::NumberToString(bucket->id.value())); + EXPECT_EQ(quota_manager_proxy_->GetBucketPath(bucket->ToBucketLocator()), + expected_path); +} + +TEST_F(QuotaManagerProxyTest, GetClientBucketPath) { + base::test::TestFuture<storage::QuotaErrorOr<storage::BucketInfo>> future; + quota_manager_proxy_->GetOrCreateBucket( + blink::StorageKey::CreateFromStringForTesting("http://example.com"), + "draft_bucket", base::ThreadTaskRunnerHandle::Get(), + future.GetCallback()); + auto bucket = future.Take(); + EXPECT_TRUE(bucket.ok()); + + base::FilePath bucket_path = + profile_path_.GetPath() + .AppendASCII("WebStorage") + .AppendASCII(base::NumberToString(bucket->id.value())); + + // FileSystem + base::FilePath expected_path = bucket_path.AppendASCII("FileSystem"); + EXPECT_EQ(quota_manager_proxy_->GetClientBucketPath( + bucket->ToBucketLocator(), QuotaClientType::kFileSystem), + expected_path); + + // IndexedDb + expected_path = bucket_path.AppendASCII("IndexedDB"); + EXPECT_EQ(quota_manager_proxy_->GetClientBucketPath( + bucket->ToBucketLocator(), QuotaClientType::kIndexedDatabase), + expected_path); + + // BackgroundFetch/CacheStorage + expected_path = bucket_path.AppendASCII("CacheStorage"); + EXPECT_EQ(quota_manager_proxy_->GetClientBucketPath( + bucket->ToBucketLocator(), QuotaClientType::kBackgroundFetch), + expected_path); + EXPECT_EQ( + quota_manager_proxy_->GetClientBucketPath( + bucket->ToBucketLocator(), QuotaClientType::kServiceWorkerCache), + expected_path); + + // ServiceWorker + expected_path = bucket_path.AppendASCII("ScriptCache"); + EXPECT_EQ(quota_manager_proxy_->GetClientBucketPath( + bucket->ToBucketLocator(), QuotaClientType::kServiceWorker), + expected_path); +} + +} // namespace storage diff --git a/chromium/storage/browser/quota/quota_manager_unittest.cc b/chromium/storage/browser/quota/quota_manager_unittest.cc index 96ab2a51517..70e037bdef8 100644 --- a/chromium/storage/browser/quota/quota_manager_unittest.cc +++ b/chromium/storage/browser/quota/quota_manager_unittest.cc @@ -24,6 +24,7 @@ #include "base/memory/weak_ptr.h" #include "base/run_loop.h" #include "base/system/sys_info.h" +#include "base/task/thread_pool.h" #include "base/test/bind.h" #include "base/test/metrics/histogram_tester.h" #include "base/test/scoped_feature_list.h" @@ -36,14 +37,15 @@ #include "components/services/storage/public/mojom/quota_client.mojom.h" #include "mojo/public/cpp/bindings/pending_remote.h" #include "mojo/public/cpp/bindings/self_owned_receiver.h" +#include "sql/test/test_helpers.h" #include "storage/browser/quota/quota_client_type.h" #include "storage/browser/quota/quota_database.h" #include "storage/browser/quota/quota_features.h" +#include "storage/browser/quota/quota_internals.mojom.h" #include "storage/browser/quota/quota_manager_impl.h" #include "storage/browser/quota/quota_manager_proxy.h" #include "storage/browser/quota/quota_override_handle.h" #include "storage/browser/test/mock_quota_client.h" -#include "storage/browser/test/mock_quota_database.h" #include "storage/browser/test/mock_special_storage_policy.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -110,6 +112,19 @@ StorageKey ToStorageKey(const std::string& url) { return StorageKey::CreateFromStringForTesting(url); } +const storage::mojom::BucketTableEntry* FindBucketTableEntry( + const std::vector<storage::mojom::BucketTableEntryPtr>& bucket_entries, + BucketId& id) { + auto it = base::ranges::find_if( + bucket_entries, [id](const storage::mojom::BucketTableEntryPtr& entry) { + return entry->bucket_id == id.value(); + }); + if (it == bucket_entries.end()) { + return nullptr; + } + return it->get(); +} + MATCHER_P3(MatchesBucketTableEntry, storage_key, type, use_count, "") { return testing::ExplainMatchResult(storage_key, arg.storage_key, result_listener) && @@ -121,8 +136,6 @@ MATCHER_P3(MatchesBucketTableEntry, storage_key, type, use_count, "") { class QuotaManagerImplTest : public testing::Test { protected: - using QuotaTableEntry = QuotaManagerImpl::QuotaTableEntry; - using QuotaTableEntries = QuotaManagerImpl::QuotaTableEntries; using BucketTableEntries = QuotaManagerImpl::BucketTableEntries; public: @@ -422,13 +435,6 @@ class QuotaManagerImplTest : public testing::Test { return future.Get<0>(); } - QuotaTableEntries DumpQuotaTable() { - base::test::TestFuture<QuotaTableEntries> future; - quota_manager_impl_->DumpQuotaTable( - future.GetCallback<const QuotaTableEntries&>()); - return future.Get(); - } - BucketTableEntries DumpBucketTable() { base::test::TestFuture<BucketTableEntries> future; quota_manager_impl_->DumpBucketTable( @@ -436,6 +442,13 @@ class QuotaManagerImplTest : public testing::Test { return future.Get(); } + std::vector<storage::mojom::BucketTableEntryPtr> RetrieveBucketsTable() { + base::test::TestFuture<std::vector<storage::mojom::BucketTableEntryPtr>> + future; + quota_manager_impl_->RetrieveBucketsTable(future.GetCallback()); + return future.Take(); + } + void DidGetUsageAndQuotaWithBreakdown( base::OnceClosure quit_closure, QuotaStatusCode status, @@ -519,8 +532,12 @@ class QuotaManagerImplTest : public testing::Test { quota_manager_impl_->SetQuotaChangeCallbackForTesting(std::move(cb)); } - void SetQuotaDatabase(std::unique_ptr<QuotaDatabase> database) { - quota_manager_impl_->SetQuotaDatabaseForTesting(std::move(database)); + QuotaError CorruptDatabaseForTesting( + base::OnceCallback<void(const base::FilePath&)> corrupter) { + base::test::TestFuture<QuotaError> corruption_future; + quota_manager_impl_->CorruptDatabaseForTesting( + std::move(corrupter), corruption_future.GetCallback()); + return corruption_future.Get(); } bool is_db_bootstrapping() { @@ -531,8 +548,16 @@ class QuotaManagerImplTest : public testing::Test { return quota_manager_impl_->is_db_disabled_for_testing(); } - void disable_quota_database(bool disable) { - quota_manager_impl_->database_->SetDisabledForTesting(disable); + void DisableQuotaDatabase() { + base::RunLoop run_loop; + quota_manager_impl_->PostTaskAndReplyWithResultForDBThread( + base::BindLambdaForTesting([&](QuotaDatabase* db) { + db->SetDisabledForTesting(true); + return QuotaError::kNone; + }), + base::BindLambdaForTesting([&](QuotaError error) { run_loop.Quit(); }), + FROM_HERE, /*is_bootstrap_task=*/false); + run_loop.Run(); } void disable_database_bootstrap(bool disable) { @@ -625,6 +650,74 @@ TEST_F(QuotaManagerImplTest, QuotaDatabaseBootstrap) { ASSERT_TRUE(bucket.ok()); } +TEST_F(QuotaManagerImplTest, CorruptionRecovery) { + // Setup clients with both unmigrated and migrated data. Before corruption the + // bucket data will be used, while after corruption recovery data should be + // migrated again. + static const ClientBucketData kData1[] = { + {"http://foo.com/", kDefaultBucketName, kTemp, 10}, + {"http://foo.com:8080/", kDefaultBucketName, kTemp, 15}, + }; + static const UnmigratedStorageKeyData kUnmigratedData1[] = { + {"http://foo.com/", kTemp, 10}, + {"http://foo.com:8080/", kTemp, 15}, + }; + static const ClientBucketData kData2[] = { + {"https://foo.com/", kDefaultBucketName, kTemp, 30}, + {"https://foo.com:8081/", kDefaultBucketName, kTemp, 35}, + }; + static const UnmigratedStorageKeyData kUnmigratedData2[] = { + {"https://foo.com/", kTemp, 30}, + {"https://foo.com:8081/", kTemp, 35}, + }; + MockQuotaClient* fs_client = CreateAndRegisterClient( + QuotaClientType::kFileSystem, {kTemp, kPerm}, kUnmigratedData1); + MockQuotaClient* database_client = CreateAndRegisterClient( + QuotaClientType::kDatabase, {kTemp, kPerm}, kUnmigratedData2); + RegisterClientBucketData(fs_client, kData1); + RegisterClientBucketData(database_client, kData2); + + // Basic sanity checks, make sure setup worked correctly. + auto bucket = + GetBucket(ToStorageKey("http://foo.com/"), kDefaultBucketName, kTemp); + ASSERT_TRUE(bucket.ok()); + bucket = GetBucket(ToStorageKey("http://foo.com:8080/"), kDefaultBucketName, + kTemp); + ASSERT_TRUE(bucket.ok()); + bucket = GetBucket(ToStorageKey("https://foo.com:8081/"), kDefaultBucketName, + kTemp); + ASSERT_TRUE(bucket.ok()); + + // Corrupt the database to make bucket lookup fail. + QuotaError corruption_error = CorruptDatabaseForTesting( + base::BindOnce([](const base::FilePath& db_path) { + ASSERT_TRUE( + sql::test::CorruptIndexRootPage(db_path, "buckets_by_storage_key")); + })); + ASSERT_EQ(QuotaError::kNone, corruption_error); + + // Try to lookup a bucket, this should fail until the error threshold is + // reached. + for (int i = 0; i < QuotaManagerImpl::kThresholdOfErrorsToDisableDatabase; + ++i) { + EXPECT_FALSE(quota_manager_impl_->is_db_disabled_for_testing()); + EXPECT_FALSE(is_db_bootstrapping()); + + bucket = + GetBucket(ToStorageKey("http://foo.com/"), kDefaultBucketName, kTemp); + ASSERT_FALSE(bucket.ok()); + EXPECT_EQ(QuotaError::kDatabaseError, bucket.error()); + } + + // The last lookup attempt should have started another bootstrap attempt. + EXPECT_TRUE(is_db_bootstrapping()); + + // And with that bucket lookup should be working again. + bucket = + GetBucket(ToStorageKey("http://foo.com/"), kDefaultBucketName, kTemp); + ASSERT_TRUE(bucket.ok()); +} + TEST_F(QuotaManagerImplTest, GetUsageInfo) { static const ClientBucketData kData1[] = { {"http://foo.com/", kDefaultBucketName, kTemp, 10}, @@ -661,7 +754,7 @@ TEST_F(QuotaManagerImplTest, DatabaseDisabledAfterThreshold) { OpenDatabase(); // Disable quota database for database error behavior. - disable_quota_database(true); + DisableQuotaDatabase(); ASSERT_FALSE(is_db_disabled()); @@ -696,6 +789,32 @@ TEST_F(QuotaManagerImplTest, GetOrCreateBucket) { EXPECT_EQ(bucket.value().id, created_bucket_id); } +TEST_F(QuotaManagerImplTest, GetOrCreateBucketSync) { + base::RunLoop loop; + // Post the function call on a different thread to ensure that the + // production DCHECK in GetOrCreateBucketSync passes. + base::ThreadPool::PostTask( + FROM_HERE, {base::WithBaseSyncPrimitives()}, + base::BindLambdaForTesting([&]() { + StorageKey storage_key = ToStorageKey("http://b.com"); + std::string bucket_name = "bucket_b"; + // Ensure that the synchronous function returns a bucket. + auto bucket = quota_manager_impl_->proxy()->GetOrCreateBucketSync( + storage_key, bucket_name); + ASSERT_TRUE(bucket.ok()); + BucketId created_bucket_id = bucket.value().id; + + // Ensure that the synchronous function does not create a new bucket + // each time. + bucket = quota_manager_impl_->proxy()->GetOrCreateBucketSync( + storage_key, bucket_name); + EXPECT_TRUE(bucket.ok()); + EXPECT_EQ(bucket.value().id, created_bucket_id); + loop.Quit(); + })); + loop.Run(); +} + TEST_F(QuotaManagerImplTest, GetBucket) { StorageKey storage_key = ToStorageKey("http://a.com/"); std::string bucket_name = "bucket_a"; @@ -745,7 +864,7 @@ TEST_F(QuotaManagerImplTest, GetStorageKeysForTypeWithDatabaseError) { OpenDatabase(); // Disable quota database for database error behavior. - disable_quota_database(true); + DisableQuotaDatabase(); // Return empty set when error is encountered. std::set<StorageKey> storage_keys = GetStorageKeysForType(kTemp); @@ -1695,6 +1814,55 @@ TEST_F(QuotaManagerImplTest, GetUsage_WithModification) { EXPECT_EQ(usage(), 4000 + 50000 + 900000000); } +TEST_F(QuotaManagerImplTest, GetUsage_WithBucketModification) { + static const ClientBucketData kData[] = { + {"http://foo.com/", kDefaultBucketName, kTemp, 1}, + {"http://foo.com/", kDefaultBucketName, kPerm, 50}, + {"http://bar.com/", "logs", kTemp, 100}, + }; + + MockQuotaClient* client = + CreateAndRegisterClient(QuotaClientType::kFileSystem, {kTemp, kPerm}); + RegisterClientBucketData(client, kData); + + auto global_usage_result = GetGlobalUsage(kPerm); + EXPECT_EQ(global_usage_result.usage, 50); + EXPECT_EQ(global_usage_result.unlimited_usage, 0); + + auto foo_temp_bucket = + GetBucket(ToStorageKey("http://foo.com/"), kDefaultBucketName, kTemp); + ASSERT_TRUE(foo_temp_bucket.ok()); + client->ModifyBucketAndNotify(foo_temp_bucket->id, 80000000); + + global_usage_result = GetGlobalUsage(kTemp); + EXPECT_EQ(global_usage_result.usage, 1 + 100 + 80000000); + EXPECT_EQ(global_usage_result.unlimited_usage, 0); + + global_usage_result = GetGlobalUsage(kPerm); + EXPECT_EQ(global_usage_result.usage, 50); + EXPECT_EQ(global_usage_result.unlimited_usage, 0); + + auto foo_perm_bucket = + GetBucket(ToStorageKey("http://foo.com/"), kDefaultBucketName, kPerm); + ASSERT_TRUE(foo_perm_bucket.ok()); + client->ModifyBucketAndNotify(foo_perm_bucket->id, 200); + + global_usage_result = GetGlobalUsage(kPerm); + EXPECT_EQ(global_usage_result.usage, 50 + 200); + EXPECT_EQ(global_usage_result.unlimited_usage, 0); + + GetHostUsageWithBreakdown("bar.com", kTemp); + EXPECT_EQ(usage(), 100); + + auto bar_temp_bucket = + GetBucket(ToStorageKey("http://bar.com/"), "logs", kTemp); + ASSERT_TRUE(bar_temp_bucket.ok()); + client->ModifyBucketAndNotify(bar_temp_bucket->id, 900000000); + + GetHostUsageWithBreakdown("bar.com", kTemp); + EXPECT_EQ(usage(), 100 + 900000000); +} + TEST_F(QuotaManagerImplTest, GetUsage_WithDeleteBucket) { static const ClientBucketData kData[] = { {"http://foo.com/", kDefaultBucketName, kTemp, 1}, @@ -2409,22 +2577,23 @@ TEST_F(QuotaManagerImplTest, FindAndDeleteBucketDataWithDBError) { MockQuotaClient* fs_client = CreateAndRegisterClient(QuotaClientType::kFileSystem, {kTemp, kPerm}); - auto quota_db = std::make_unique<MockQuotaDatabase>( - data_dir_.GetPath().AppendASCII("QuotaManager")); - MockQuotaDatabase* mock_database = quota_db.get(); - SetQuotaDatabase(std::move(quota_db)); - RegisterClientBucketData(fs_client, kData); // Check usage data before deletion. GetHostUsageWithBreakdown("foo.com", kTemp); ASSERT_EQ(123, usage()); - EXPECT_CALL(*mock_database, DeleteBucketInfo) - .Times(1) - .WillOnce(testing::Return(QuotaError::kDatabaseError)); + // Bucket lookup uses the `buckets_by_storage_key` index. So, we can corrupt + // any other index, and SQLite will only detect the corruption when trying to + // delete a bucket. + QuotaError corruption_error = CorruptDatabaseForTesting( + base::BindOnce([](const base::FilePath& db_path) { + ASSERT_TRUE(sql::test::CorruptIndexRootPage( + db_path, "buckets_by_last_accessed")); + })); + ASSERT_EQ(QuotaError::kNone, corruption_error); - // Trying to delete bucket for "http://foo.com/" should return error. + // Deleting the bucket will result in an error. EXPECT_EQ(FindAndDeleteBucketData(ToStorageKey("http://foo.com"), kDefaultBucketName), QuotaStatusCode::kErrorInvalidModification); @@ -2436,6 +2605,32 @@ TEST_F(QuotaManagerImplTest, FindAndDeleteBucketDataWithDBError) { EXPECT_EQ(0, usage()); } +TEST_F(QuotaManagerImplTest, GetHostUsageForInternals) { + static const ClientBucketData kData[] = { + {"http://example.com/", kDefaultBucketName, kTemp, 400}, + {"http://example.com/", kDefaultBucketName, kPerm, 2}, + }; + MockQuotaClient* client = + CreateAndRegisterClient(QuotaClientType::kFileSystem, {kTemp, kPerm}); + RegisterClientBucketData(client, kData); + + base::test::TestFuture<int64_t> temp_future; + quota_manager_impl()->GetHostUsageForInternals( + "example.com", storage::mojom::StorageType::kTemporary, + temp_future.GetCallback()); + int64_t temp_result = temp_future.Take(); + + EXPECT_EQ(400, temp_result); + + base::test::TestFuture<int64_t> perm_future; + quota_manager_impl()->GetHostUsageForInternals( + "example.com", storage::mojom::StorageType::kPersistent, + perm_future.GetCallback()); + int64_t perm_result = perm_future.Take(); + + EXPECT_EQ(2, perm_result); +} + TEST_F(QuotaManagerImplTest, NotifyAndLRUBucket) { static const ClientBucketData kData[] = { {"http://a.com/", kDefaultBucketName, kTemp, 0}, @@ -2576,7 +2771,7 @@ TEST_F(QuotaManagerImplTest, GetBucketsModifiedBetweenWithDatabaseError) { OpenDatabase(); // Disable quota database for database error behavior. - disable_quota_database(true); + DisableQuotaDatabase(); auto buckets = GetBucketsModifiedBetween(kTemp, base::Time(), base::Time::Max()); @@ -2585,23 +2780,11 @@ TEST_F(QuotaManagerImplTest, GetBucketsModifiedBetweenWithDatabaseError) { EXPECT_TRUE(buckets.empty()); } -TEST_F(QuotaManagerImplTest, DumpQuotaTable) { - SetPersistentHostQuota("example1.com", 1); - SetPersistentHostQuota("example2.com", 20); - SetPersistentHostQuota("example3.com", 300); - task_environment_.RunUntilIdle(); - - const QuotaTableEntries& entries = DumpQuotaTable(); - EXPECT_THAT( - entries, - testing::UnorderedElementsAre( - QuotaTableEntry{.host = "example1.com", .type = kPerm, .quota = 1}, - QuotaTableEntry{.host = "example2.com", .type = kPerm, .quota = 20}, - QuotaTableEntry{ - .host = "example3.com", .type = kPerm, .quota = 300})); -} - TEST_F(QuotaManagerImplTest, DumpBucketTable) { + // Dumping an unpopulated bucket table returns an empty vector. + const BucketTableEntries& initial_entries = DumpBucketTable(); + EXPECT_TRUE(initial_entries.empty()); + const StorageKey kStorageKey = ToStorageKey("http://example.com/"); CreateBucketForTesting(kStorageKey, kDefaultBucketName, kTemp); CreateBucketForTesting(kStorageKey, kDefaultBucketName, kPerm); @@ -2620,6 +2803,64 @@ TEST_F(QuotaManagerImplTest, DumpBucketTable) { MatchesBucketTableEntry(kStorageKey, kPerm, 2))); } +TEST_F(QuotaManagerImplTest, RetrieveBucketsTable) { + const StorageKey kStorageKey = ToStorageKey("http://example.com/"); + const std::string kSerializedStorageKey = kStorageKey.Serialize(); + const base::Time kAccessTime = base::Time::Now(); + + static const ClientBucketData kData[] = { + {"http://example.com/", kDefaultBucketName, kTemp, 0}, + {"http://example.com/", kDefaultBucketName, kPerm, 0}, + }; + + MockQuotaClient* client = + CreateAndRegisterClient(QuotaClientType::kFileSystem, {kTemp, kPerm}); + RegisterClientBucketData(client, kData); + + quota_manager_impl()->NotifyStorageAccessed(kStorageKey, kTemp, kAccessTime); + quota_manager_impl()->NotifyStorageAccessed(kStorageKey, kPerm, kAccessTime); + + base::Time time1 = client->IncrementMockTime(); + client->ModifyStorageKeyAndNotify(ToStorageKey("http://example.com/"), kTemp, + 10); + client->ModifyStorageKeyAndNotify(ToStorageKey("http://example.com/"), kPerm, + 10); + base::Time time2 = client->IncrementMockTime(); + client->ModifyStorageKeyAndNotify(ToStorageKey("http://example.com/"), kTemp, + 10); + base::Time time3 = client->IncrementMockTime(); + + auto temp_bucket = GetBucket(kStorageKey, kDefaultBucketName, kTemp); + auto perm_bucket = GetBucket(kStorageKey, kDefaultBucketName, kPerm); + + const std::vector<storage::mojom::BucketTableEntryPtr> bucket_table_entries = + RetrieveBucketsTable(); + + auto* temp_entry = + FindBucketTableEntry(bucket_table_entries, temp_bucket->id); + EXPECT_TRUE(temp_entry); + EXPECT_EQ(temp_entry->storage_key, kSerializedStorageKey); + EXPECT_EQ(temp_entry->host, "example.com"); + EXPECT_EQ(temp_entry->type, "temporary"); + EXPECT_EQ(temp_entry->name, kDefaultBucketName); + EXPECT_EQ(temp_entry->use_count, 1); + EXPECT_EQ(temp_entry->last_accessed, kAccessTime); + EXPECT_GE(temp_entry->last_modified, time2); + EXPECT_LE(temp_entry->last_modified, time3); + + auto* perm_entry = + FindBucketTableEntry(bucket_table_entries, perm_bucket->id); + EXPECT_TRUE(perm_entry); + EXPECT_EQ(perm_entry->storage_key, kSerializedStorageKey); + EXPECT_EQ(perm_entry->host, "example.com"); + EXPECT_EQ(perm_entry->type, "persistent"); + EXPECT_EQ(perm_entry->name, kDefaultBucketName); + EXPECT_EQ(perm_entry->use_count, 1); + EXPECT_EQ(perm_entry->last_accessed, kAccessTime); + EXPECT_GE(perm_entry->last_modified, time1); + EXPECT_LE(perm_entry->last_modified, time2); +} + TEST_F(QuotaManagerImplTest, QuotaForEmptyHost) { EXPECT_EQ(GetPersistentHostQuota(std::string()), 0); diff --git a/chromium/storage/browser/quota/quota_settings.cc b/chromium/storage/browser/quota/quota_settings.cc index 3a55be13bfe..c2a5a209635 100644 --- a/chromium/storage/browser/quota/quota_settings.cc +++ b/chromium/storage/browser/quota/quota_settings.cc @@ -13,7 +13,6 @@ #include "base/no_destructor.h" #include "base/rand_util.h" #include "base/system/sys_info.h" -#include "base/task/post_task.h" #include "base/task/thread_pool.h" #include "base/threading/scoped_blocking_call.h" #include "build/build_config.h" diff --git a/chromium/storage/browser/quota/quota_settings.h b/chromium/storage/browser/quota/quota_settings.h index d8fd5702fe0..a17bc3c3823 100644 --- a/chromium/storage/browser/quota/quota_settings.h +++ b/chromium/storage/browser/quota/quota_settings.h @@ -72,9 +72,9 @@ using GetQuotaSettingsFunc = // Posts a background task to calculate and report quota settings to the // |callback| function based on the size of the volume containing the storage // partition and a guestimate of the size required for the OS. The refresh -// interval is 60 seconds to accomodate changes to the size of the volume. -// Except, in the case of incognito, the poolize and quota values are based -// on the amount of physical memory and the rerfresh interval is max'd out. +// interval is 60 seconds to accommodate changes to the size of the volume. +// Except, in the case of incognito, the pool size and quota values are based +// on the amount of physical memory and the refresh interval is maxed out. COMPONENT_EXPORT(STORAGE_BROWSER) void GetNominalDynamicSettings(const base::FilePath& partition_path, bool is_incognito, diff --git a/chromium/storage/browser/quota/quota_temporary_storage_evictor.h b/chromium/storage/browser/quota/quota_temporary_storage_evictor.h index b130c16a1cb..c07ffd74d6b 100644 --- a/chromium/storage/browser/quota/quota_temporary_storage_evictor.h +++ b/chromium/storage/browser/quota/quota_temporary_storage_evictor.h @@ -15,6 +15,7 @@ #include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" #include "base/sequence_checker.h" +#include "base/time/time.h" #include "base/timer/timer.h" #include "components/services/storage/public/cpp/buckets/bucket_locator.h" #include "third_party/abseil-cpp/absl/types/optional.h" diff --git a/chromium/storage/browser/quota/special_storage_policy.cc b/chromium/storage/browser/quota/special_storage_policy.cc index bc0ae145a0c..f7774c8973d 100644 --- a/chromium/storage/browser/quota/special_storage_policy.cc +++ b/chromium/storage/browser/quota/special_storage_policy.cc @@ -4,6 +4,8 @@ #include "storage/browser/quota/special_storage_policy.h" +#include "base/observer_list.h" + namespace storage { SpecialStoragePolicy::Observer::~Observer() = default; diff --git a/chromium/storage/browser/quota/storage_directory.cc b/chromium/storage/browser/quota/storage_directory.cc new file mode 100644 index 00000000000..11b85fdb293 --- /dev/null +++ b/chromium/storage/browser/quota/storage_directory.cc @@ -0,0 +1,62 @@ +// Copyright 2022 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_directory.h" + +#include "base/files/file_enumerator.h" +#include "base/files/file_util.h" +#include "base/notreached.h" +#include "base/strings/strcat.h" +#include "components/services/storage/public/cpp/constants.h" + +namespace storage { +namespace { +const base::FilePath::CharType kDoomedPathName[] = + FILE_PATH_LITERAL("-doomed-"); +} + +StorageDirectory::StorageDirectory(const base::FilePath& profile_path) + : web_storage_path_(profile_path.Append(kWebStorageDirectory)) { + DCHECK(!profile_path.empty()) << "Should not be called in incognito mode."; +} + +StorageDirectory::~StorageDirectory() = default; + +bool StorageDirectory::Create() { + return base::CreateDirectory(web_storage_path_); +} + +bool StorageDirectory::Doom() { + if (!base::PathExists(web_storage_path_)) + return true; + + base::FilePath doomed_dir; + base::CreateTemporaryDirInDir( + web_storage_path_.DirName(), + base::StrCat({kWebStorageDirectory, kDoomedPathName}), &doomed_dir); + return base::Move(web_storage_path_, doomed_dir); +} + +void StorageDirectory::ClearDoomed() { + std::set<base::FilePath> paths = EnumerateDoomedDirectories(); + + for (const base::FilePath& path : paths) + base::DeletePathRecursively(path); +} + +std::set<base::FilePath> StorageDirectory::EnumerateDoomedDirectories() { + base::FileEnumerator enumerator( + web_storage_path_.DirName(), /*recursive=*/false, + base::FileEnumerator::DIRECTORIES, + base::StrCat( + {kWebStorageDirectory, kDoomedPathName, FILE_PATH_LITERAL("*")})); + + std::set<base::FilePath> paths; + base::FilePath path; + while (path = enumerator.Next(), !path.empty()) + paths.insert(path); + return paths; +} + +} // namespace storage diff --git a/chromium/storage/browser/quota/storage_directory.h b/chromium/storage/browser/quota/storage_directory.h new file mode 100644 index 00000000000..8e51798589b --- /dev/null +++ b/chromium/storage/browser/quota/storage_directory.h @@ -0,0 +1,51 @@ +// Copyright 2022 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_DIRECTORY_H_ +#define STORAGE_BROWSER_QUOTA_STORAGE_DIRECTORY_H_ + +#include <set> + +#include "base/component_export.h" +#include "base/files/file_path.h" + +namespace storage { + +// Interface for handling the WebStorage directory for a profile where +// Storage Buckets data is stored. +class COMPONENT_EXPORT(STORAGE_BROWSER) StorageDirectory { + public: + explicit StorageDirectory(const base::FilePath& profile_path); + StorageDirectory(const StorageDirectory&) = delete; + StorageDirectory& operator=(const StorageDirectory&) = delete; + ~StorageDirectory(); + + // Creates storage directory and returns true if creation succeeds or + // directory already exists. + bool Create(); + + // Marks the current storage directory for deletion and returns true on + // success. + bool Doom(); + + // Deletes doomed storage directories. + void ClearDoomed(); + + // Returns path where WebStorage data is persisted to disk. Returns empty path + // for incognito. + const base::FilePath& path() const { return web_storage_path_; } + + std::set<base::FilePath> EnumerateDoomedDirectoriesForTesting() { + return EnumerateDoomedDirectories(); + } + + private: + std::set<base::FilePath> EnumerateDoomedDirectories(); + + const base::FilePath web_storage_path_; +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_QUOTA_STORAGE_DIRECTORY_H_ diff --git a/chromium/storage/browser/quota/storage_directory_unittest.cc b/chromium/storage/browser/quota/storage_directory_unittest.cc new file mode 100644 index 00000000000..e1f50200994 --- /dev/null +++ b/chromium/storage/browser/quota/storage_directory_unittest.cc @@ -0,0 +1,82 @@ +// Copyright 2022 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 <memory> +#include <set> + +#include "base/files/file_enumerator.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "storage/browser/quota/storage_directory.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace storage { + +class StorageDirectoryTest : public testing::Test { + public: + void SetUp() override { + ASSERT_TRUE(temp_directory_.CreateUniqueTempDir()); + storage_directory_ = + std::make_unique<StorageDirectory>(temp_directory_.GetPath()); + } + void TearDown() override { ASSERT_TRUE(temp_directory_.Delete()); } + + protected: + base::ScopedTempDir temp_directory_; + std::unique_ptr<StorageDirectory> storage_directory_; +}; + +TEST_F(StorageDirectoryTest, CreateDirectory) { + base::FilePath storage_path = storage_directory_->path(); + + EXPECT_FALSE(base::PathExists(storage_path)); + + ASSERT_TRUE(storage_directory_->Create()); + EXPECT_TRUE(base::PathExists(storage_path)); + + // Should still return true if it already exists. + ASSERT_TRUE(storage_directory_->Create()); + EXPECT_TRUE(base::PathExists(storage_path)); +} + +TEST_F(StorageDirectoryTest, DoomAndClearStorage) { + base::FilePath storage_path = storage_directory_->path(); + ASSERT_TRUE(storage_directory_->Create()); + EXPECT_TRUE(base::PathExists(storage_path)); + + // Write data into directory. + base::WriteFile(storage_path.AppendASCII("FakeStorage"), "dummy_content"); + + ASSERT_TRUE(storage_directory_->Doom()); + EXPECT_FALSE(base::PathExists(storage_path)); + + std::set<base::FilePath> directories = + storage_directory_->EnumerateDoomedDirectoriesForTesting(); + EXPECT_EQ(directories.size(), 1u); + + storage_directory_->ClearDoomed(); + directories = storage_directory_->EnumerateDoomedDirectoriesForTesting(); + EXPECT_EQ(directories.size(), 0u); +} + +TEST_F(StorageDirectoryTest, ClearDoomedMultiple) { + base::FilePath storage_path = storage_directory_->path(); + + // Create and doom storage directory multiple times. + for (unsigned int i = 0; i < 5; i++) { + ASSERT_TRUE(storage_directory_->Create()); + ASSERT_TRUE(storage_directory_->Doom()); + } + + std::set<base::FilePath> directories = + storage_directory_->EnumerateDoomedDirectoriesForTesting(); + EXPECT_EQ(directories.size(), 5u); + + storage_directory_->ClearDoomed(); + directories = storage_directory_->EnumerateDoomedDirectoriesForTesting(); + EXPECT_EQ(directories.size(), 0u); +} + +} // namespace storage diff --git a/chromium/storage/browser/quota/storage_policy_observer.cc b/chromium/storage/browser/quota/storage_policy_observer.cc index 884ade794c5..73dde9d81b8 100644 --- a/chromium/storage/browser/quota/storage_policy_observer.cc +++ b/chromium/storage/browser/quota/storage_policy_observer.cc @@ -7,7 +7,6 @@ #include <utility> #include "base/feature_list.h" -#include "base/task/post_task.h" #include "storage/browser/quota/quota_features.h" #include "url/origin.h" diff --git a/chromium/storage/browser/quota/usage_tracker.cc b/chromium/storage/browser/quota/usage_tracker.cc index f6018b20d7d..1cceaf24cf5 100644 --- a/chromium/storage/browser/quota/usage_tracker.cc +++ b/chromium/storage/browser/quota/usage_tracker.cc @@ -271,6 +271,13 @@ void UsageTracker::AccumulateClientHostUsage(base::OnceClosure barrier_callback, case QuotaClientType::kNativeIO: info->usage_breakdown->fileSystem += total_usage; break; + case QuotaClientType::kMediaLicense: + // Media license data does not count against quota and should always + // report 0 usage. + // TODO(crbug.com/1305441): Consider counting media license data against + // quota. + DCHECK_EQ(total_usage, 0); + break; } std::move(barrier_callback).Run(); diff --git a/chromium/storage/browser/quota/usage_tracker_unittest.cc b/chromium/storage/browser/quota/usage_tracker_unittest.cc index 477d8fc4455..f361f86ed21 100644 --- a/chromium/storage/browser/quota/usage_tracker_unittest.cc +++ b/chromium/storage/browser/quota/usage_tracker_unittest.cc @@ -14,6 +14,7 @@ #include "base/memory/scoped_refptr.h" #include "base/run_loop.h" #include "base/task/single_thread_task_runner.h" +#include "base/test/bind.h" #include "base/test/task_environment.h" #include "base/test/test_future.h" #include "base/threading/thread_task_runner_handle.h" @@ -175,8 +176,16 @@ class UsageTrackerTest : public testing::Test { void OpenDatabase() { quota_manager_->EnsureDatabaseOpened(); } - void disable_quota_database(bool disable) { - quota_manager_->database_->SetDisabledForTesting(disable); + void DisableQuotaDatabase() { + base::RunLoop run_loop; + quota_manager_->PostTaskAndReplyWithResultForDBThread( + base::BindLambdaForTesting([&](QuotaDatabase* db) { + db->SetDisabledForTesting(true); + return QuotaError::kNone; + }), + base::BindLambdaForTesting([&](QuotaError error) { run_loop.Quit(); }), + FROM_HERE, /*is_bootstrap_task=*/false); + run_loop.Run(); } void disable_database_bootstrap(bool disable) { @@ -462,7 +471,7 @@ TEST_F(UsageTrackerTest, QuotaDatabaseDisabled) { disable_database_bootstrap(true); OpenDatabase(); - disable_quota_database(true); + DisableQuotaDatabase(); int64_t total_usage = 0; int64_t unlimited_usage = 0; diff --git a/chromium/storage/common/database/database_identifier.cc b/chromium/storage/common/database/database_identifier.cc index 9e369f69521..1b23ec9c2f0 100644 --- a/chromium/storage/common/database/database_identifier.cc +++ b/chromium/storage/common/database/database_identifier.cc @@ -6,7 +6,6 @@ #include <stddef.h> -#include "base/cxx17_backports.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "url/url_canon.h" @@ -116,7 +115,7 @@ DatabaseIdentifier DatabaseIdentifier::Parse(const std::string& identifier) { if (identifier.find("..") != std::string::npos) return DatabaseIdentifier(); static const char kForbidden[] = {'\\', '/', ':', '\0'}; - if (identifier.find_first_of(kForbidden, 0, base::size(kForbidden)) != + if (identifier.find_first_of(kForbidden, 0, std::size(kForbidden)) != std::string::npos) { return DatabaseIdentifier(); } diff --git a/chromium/storage/common/database/database_identifier_unittest.cc b/chromium/storage/common/database/database_identifier_unittest.cc index 01597087c04..a71b52afaa8 100644 --- a/chromium/storage/common/database/database_identifier_unittest.cc +++ b/chromium/storage/common/database/database_identifier_unittest.cc @@ -6,7 +6,6 @@ #include <stddef.h> -#include "base/cxx17_backports.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" #include "url/origin.h" @@ -165,7 +164,7 @@ TEST(DatabaseIdentifierTest, CreateIdentifierAllHostChars) { {"x\x80x", "__0", false}, }; - for (size_t i = 0; i < base::size(cases); ++i) { + for (size_t i = 0; i < std::size(cases); ++i) { GURL origin_url("http://" + cases[i].hostname); url::Origin origin = url::Origin::Create(origin_url); DatabaseIdentifier identifier_from_url = diff --git a/chromium/storage/common/file_system/OWNERS b/chromium/storage/common/file_system/OWNERS deleted file mode 100644 index d872f10c932..00000000000 --- a/chromium/storage/common/file_system/OWNERS +++ /dev/null @@ -1,5 +0,0 @@ -mek@chromium.org -nhiroki@chromium.org - -per-file *_type_converter*.*=set noparent -per-file *_type_converter*.*=file://ipc/SECURITY_OWNERS |