diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-11-18 16:35:47 +0100 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-11-18 15:45:54 +0000 |
commit | 32f5a1c56531e4210bc4cf8d8c7825d66e081888 (patch) | |
tree | eeeec6822f4d738d8454525233fd0e2e3a659e6d /chromium/storage | |
parent | 99677208ff3b216fdfec551fbe548da5520cd6fb (diff) | |
download | qtwebengine-chromium-32f5a1c56531e4210bc4cf8d8c7825d66e081888.tar.gz |
BASELINE: Update Chromium to 87.0.4280.67
Change-Id: Ib157360be8c2ffb2c73125751a89f60e049c1d54
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/storage')
46 files changed, 1285 insertions, 851 deletions
diff --git a/chromium/storage/browser/BUILD.gn b/chromium/storage/browser/BUILD.gn index 48be0d9f2bd..3ac459e3459 100644 --- a/chromium/storage/browser/BUILD.gn +++ b/chromium/storage/browser/BUILD.gn @@ -111,6 +111,8 @@ component("browser") { "file_system/file_system_usage_cache.h", "file_system/file_writer_delegate.cc", "file_system/file_writer_delegate.h", + "file_system/filesystem_proxy_file_stream_reader.cc", + "file_system/filesystem_proxy_file_stream_reader.h", "file_system/isolated_context.cc", "file_system/isolated_context.h", "file_system/isolated_file_system_backend.cc", @@ -224,6 +226,7 @@ component("browser") { "//base", "//base:i18n", "//base/third_party/dynamic_annotations", + "//components/services/storage/public/cpp/filesystem", "//mojo/public/cpp/bindings", "//net", "//services/network:network_service", @@ -275,6 +278,8 @@ source_set("unittests") { "file_system/copy_or_move_operation_delegate_unittest.cc", "file_system/dragged_file_util_unittest.cc", "file_system/external_mount_points_unittest.cc", + "file_system/file_stream_reader_test.cc", + "file_system/file_stream_reader_test.h", "file_system/file_stream_test_utils.cc", "file_system/file_stream_test_utils.h", "file_system/file_system_context_unittest.cc", @@ -285,6 +290,7 @@ source_set("unittests") { "file_system/file_system_url_unittest.cc", "file_system/file_system_usage_cache_unittest.cc", "file_system/file_writer_delegate_unittest.cc", + "file_system/filesystem_proxy_file_stream_reader_unittest.cc", "file_system/isolated_context_unittest.cc", "file_system/local_file_stream_reader_unittest.cc", "file_system/local_file_stream_writer_unittest.cc", @@ -318,6 +324,7 @@ source_set("unittests") { ":test_support", "//base/test:test_support", "//components/services/filesystem/public/mojom", + "//components/services/storage/public/cpp/filesystem", "//mojo/public/cpp/system", "//net:test_support", "//services/network/public/cpp", 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 184f335c802..3c27c863187 100644 --- a/chromium/storage/browser/blob/blob_builder_from_stream_unittest.cc +++ b/chromium/storage/browser/blob/blob_builder_from_stream_unittest.cc @@ -416,8 +416,7 @@ TEST_P(BlobBuilderFromStreamTest, ProgressEvents) { mojo::AssociatedRemote<blink::mojom::ProgressClient> progress_client_remote; mojo::AssociatedReceiver<blink::mojom::ProgressClient> progress_receiver( &progress_client, - progress_client_remote - .BindNewEndpointAndPassDedicatedReceiverForTesting()); + progress_client_remote.BindNewEndpointAndPassDedicatedReceiver()); mojo::DataPipe pipe; base::RunLoop loop; diff --git a/chromium/storage/browser/blob/blob_data_builder.cc b/chromium/storage/browser/blob/blob_data_builder.cc index a429d7b8bc2..9414959e882 100644 --- a/chromium/storage/browser/blob/blob_data_builder.cc +++ b/chromium/storage/browser/blob/blob_data_builder.cc @@ -321,7 +321,7 @@ void BlobDataBuilder::AppendBlob(const std::string& uuid, } void BlobDataBuilder::AppendFileSystemFile( - const GURL& url, + const FileSystemURL& url, uint64_t offset, uint64_t length, const base::Time& expected_modification_time, diff --git a/chromium/storage/browser/blob/blob_data_builder.h b/chromium/storage/browser/blob/blob_data_builder.h index a40f4ef4854..a4ad251e5ca 100644 --- a/chromium/storage/browser/blob/blob_data_builder.h +++ b/chromium/storage/browser/blob/blob_data_builder.h @@ -28,6 +28,7 @@ namespace storage { class BlobSliceTest; class BlobStorageContext; class BlobStorageRegistry; +class FileSystemURL; // This class is used to build blobs. It also facilitates the operation of // 'pending' data, where the user knows the size and existence of a file or @@ -136,7 +137,7 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) BlobDataBuilder { const BlobStorageRegistry& blob_registry); void AppendFileSystemFile( - const GURL& url, + const FileSystemURL& url, uint64_t offset, uint64_t length, const base::Time& expected_modification_time, diff --git a/chromium/storage/browser/blob/blob_data_item.cc b/chromium/storage/browser/blob/blob_data_item.cc index f5e06824930..ef05067a1ad 100644 --- a/chromium/storage/browser/blob/blob_data_item.cc +++ b/chromium/storage/browser/blob/blob_data_item.cc @@ -116,7 +116,7 @@ scoped_refptr<BlobDataItem> BlobDataItem::CreateFutureFile(uint64_t offset, // static scoped_refptr<BlobDataItem> BlobDataItem::CreateFileFilesystem( - const GURL& url, + const FileSystemURL& url, uint64_t offset, uint64_t length, base::Time expected_modification_time, @@ -193,6 +193,7 @@ void BlobDataItem::ShrinkBytes(size_t new_length) { DCHECK_EQ(type_, Type::kBytes); length_ = new_length; bytes_.resize(length_); + bytes_.shrink_to_fit(); } void BlobDataItem::PopulateFile( @@ -251,7 +252,7 @@ void PrintTo(const BlobDataItem& x, ::std::ostream* os) { << ", expected_modification_time: " << x.expected_modification_time(); break; case BlobDataItem::Type::kFileFilesystem: - *os << "kFileFilesystem, url: " << x.filesystem_url(); + *os << "kFileFilesystem, url: " << x.filesystem_url().DebugString(); break; case BlobDataItem::Type::kReadableDataHandle: *os << "kReadableDataHandle" diff --git a/chromium/storage/browser/blob/blob_data_item.h b/chromium/storage/browser/blob/blob_data_item.h index d40d1edf6c1..aa681aceb61 100644 --- a/chromium/storage/browser/blob/blob_data_item.h +++ b/chromium/storage/browser/blob/blob_data_item.h @@ -18,6 +18,7 @@ #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" +#include "storage/browser/file_system/file_system_url.h" #include "url/gurl.h" namespace storage { @@ -92,7 +93,7 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) BlobDataItem uint64_t length, uint64_t file_id); static scoped_refptr<BlobDataItem> CreateFileFilesystem( - const GURL& url, + const FileSystemURL& url, uint64_t offset, uint64_t length, base::Time expected_modification_time, @@ -118,7 +119,7 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) BlobDataItem return path_; } - const GURL& filesystem_url() const { + const FileSystemURL& filesystem_url() const { DCHECK_EQ(type_, Type::kFileFilesystem); return filesystem_url_; } @@ -182,9 +183,9 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) BlobDataItem uint64_t offset_; uint64_t length_; - std::vector<uint8_t> bytes_; // For Type::kBytes. - base::FilePath path_; // For Type::kFile. - GURL filesystem_url_; // For Type::kFileFilesystem. + std::vector<uint8_t> bytes_; // For Type::kBytes. + base::FilePath path_; // For Type::kFile. + FileSystemURL filesystem_url_; // For Type::kFileFilesystem. base::Time expected_modification_time_; // For Type::kFile and kFileFilesystem. diff --git a/chromium/storage/browser/blob/blob_reader.cc b/chromium/storage/browser/blob/blob_reader.cc index 8bed5a695b5..40e5c49b919 100644 --- a/chromium/storage/browser/blob/blob_reader.cc +++ b/chromium/storage/browser/blob/blob_reader.cc @@ -726,14 +726,12 @@ std::unique_ptr<FileStreamReader> BlobReader::CreateFileStreamReader( : item.length() - additional_offset; if (file_stream_provider_for_testing_) { return file_stream_provider_for_testing_->CreateFileStreamReader( - item.filesystem_url(), item.offset() + additional_offset, + item.filesystem_url().ToGURL(), item.offset() + additional_offset, max_bytes_to_read, item.expected_modification_time()); } return item.file_system_context()->CreateFileStreamReader( - FileSystemURL( - item.file_system_context()->CrackURL(item.filesystem_url())), - item.offset() + additional_offset, max_bytes_to_read, - item.expected_modification_time()); + item.filesystem_url(), item.offset() + additional_offset, + max_bytes_to_read, item.expected_modification_time()); } case BlobDataItem::Type::kBytes: case BlobDataItem::Type::kBytesDescription: diff --git a/chromium/storage/browser/blob/blob_reader_unittest.cc b/chromium/storage/browser/blob/blob_reader_unittest.cc index 51b85c71632..bdba18feb7e 100644 --- a/chromium/storage/browser/blob/blob_reader_unittest.cc +++ b/chromium/storage/browser/blob/blob_reader_unittest.cc @@ -206,6 +206,11 @@ class BlobReaderTest : public ::testing::Test { BlobReaderTest() = default; ~BlobReaderTest() override = default; + void SetUp() override { + file_system_context_ = + CreateFileSystemContextForTesting(nullptr, base::FilePath()); + } + void TearDown() override { reader_.reset(); blob_handle_.reset(); @@ -280,6 +285,7 @@ class BlobReaderTest : public ::testing::Test { std::unique_ptr<BlobDataHandle> blob_handle_; MockFileStreamReaderProvider* provider_ = nullptr; std::unique_ptr<BlobReader> reader_; + scoped_refptr<FileSystemContext> file_system_context_; private: DISALLOW_COPY_AND_ASSIGN(BlobReaderTest); @@ -347,10 +353,11 @@ TEST_F(BlobReaderTest, BasicFile) { TEST_F(BlobReaderTest, BasicFileSystem) { auto b = std::make_unique<BlobDataBuilder>("uuid"); - const GURL kURL("file://test_file/here.txt"); + const GURL kURL("filesystem:http://example.com/temporary/test_file/here.txt"); const std::string kData = "FileData!!!"; const base::Time kTime = base::Time::Now(); - b->AppendFileSystemFile(kURL, 0, kData.size(), kTime, nullptr); + b->AppendFileSystemFile(file_system_context_->CrackURL(kURL), 0, kData.size(), + kTime, file_system_context_); this->InitializeReader(std::move(b)); // Non-async reader. ExpectFileSystemCall(kURL, 0, kData.size(), kTime, @@ -609,10 +616,11 @@ TEST_F(BlobReaderTest, FileAsync) { TEST_F(BlobReaderTest, FileSystemAsync) { auto b = std::make_unique<BlobDataBuilder>("uuid"); - const GURL kURL("file://test_file/here.txt"); + const GURL kURL("filesystem:http://example.com/temporary/test_file/here.txt"); const std::string kData = "FileData!!!"; const base::Time kTime = base::Time::Now(); - b->AppendFileSystemFile(kURL, 0, kData.size(), kTime, nullptr); + b->AppendFileSystemFile(file_system_context_->CrackURL(kURL), 0, kData.size(), + kTime, file_system_context_); this->InitializeReader(std::move(b)); std::unique_ptr<FakeFileStreamReader> reader(new FakeFileStreamReader(kData)); diff --git a/chromium/storage/browser/blob/blob_registry_impl.cc b/chromium/storage/browser/blob/blob_registry_impl.cc index 1902cca8345..34e736e80de 100644 --- a/chromium/storage/browser/blob/blob_registry_impl.cc +++ b/chromium/storage/browser/blob/blob_registry_impl.cc @@ -30,20 +30,43 @@ BlobRegistryImpl::URLStoreCreationHook* g_url_store_creation_hook = nullptr; class BlobRegistryImpl::BlobUnderConstruction { public: + // Holds onto a blink::mojom::DataElement struct and optionally a bound + // mojo::Remote<blink::mojom::BytesProvider> or + // mojo::Remote<blink::mojom::Blob>, if the element encapsulates a large byte + // array or a blob. + struct ElementEntry { + explicit ElementEntry(blink::mojom::DataElementPtr e) + : element(std::move(e)) { + if (element && element->is_bytes()) + bytes_provider.Bind(std::move(element->get_bytes()->data)); + else if (element && element->is_blob()) + blob.Bind(std::move(element->get_blob()->blob)); + } + + ElementEntry(ElementEntry&& other) = default; + ~ElementEntry() = default; + + ElementEntry& operator=(ElementEntry&& other) = default; + + blink::mojom::DataElementPtr element; + FileSystemURL filesystem_url; + mojo::Remote<blink::mojom::BytesProvider> bytes_provider; + mojo::Remote<blink::mojom::Blob> blob; + }; + BlobUnderConstruction(BlobRegistryImpl* blob_registry, const std::string& uuid, const std::string& content_type, const std::string& content_disposition, - std::vector<blink::mojom::DataElementPtr> elements, + std::vector<ElementEntry> elements, mojo::ReportBadMessageCallback bad_message_callback) : blob_registry_(blob_registry), uuid_(uuid), builder_(std::make_unique<BlobDataBuilder>(uuid)), + elements_(std::move(elements)), bad_message_callback_(std::move(bad_message_callback)) { builder_->set_content_type(content_type); builder_->set_content_disposition(content_disposition); - for (auto& element : elements) - elements_.emplace_back(std::move(element)); } // Call this after constructing to kick of fetching of UUIDs of blobs @@ -57,29 +80,6 @@ class BlobRegistryImpl::BlobUnderConstruction { const std::string& uuid() const { return uuid_; } private: - // Holds onto a blink::mojom::DataElement struct and optionally a bound - // mojo::Remote<blink::mojom::BytesProvider> or - // mojo::Remote<blink::mojom::Blob>, if the element encapsulates a large byte - // array or a blob. - struct ElementEntry { - explicit ElementEntry(blink::mojom::DataElementPtr e) - : element(std::move(e)) { - if (element && element->is_bytes()) - bytes_provider.Bind(std::move(element->get_bytes()->data)); - else if (element && element->is_blob()) - blob.Bind(std::move(element->get_blob()->blob)); - } - - ElementEntry(ElementEntry&& other) = default; - ~ElementEntry() = default; - - ElementEntry& operator=(ElementEntry&& other) = default; - - blink::mojom::DataElementPtr element; - mojo::Remote<blink::mojom::BytesProvider> bytes_provider; - mojo::Remote<blink::mojom::Blob> blob; - }; - BlobStorageContext* context() const { return blob_registry_->context_.get(); } // Marks this blob as broken. If an optional |bad_message_reason| is provided, @@ -382,9 +382,10 @@ void BlobRegistryImpl::BlobUnderConstruction::ResolvedAllBlobDependencies() { f->path, f->offset, f->length, f->expected_modification_time.value_or(base::Time())); } else if (element->is_file_filesystem()) { + DCHECK(entry.filesystem_url.is_valid()); const auto& f = element->get_file_filesystem(); builder_->AppendFileSystemFile( - f->url, f->offset, f->length, + entry.filesystem_url, f->offset, f->length, f->expected_modification_time.value_or(base::Time()), blob_registry_->file_system_context_); } else if (element->is_blob()) { @@ -533,9 +534,12 @@ void BlobRegistryImpl::Register( Delegate* delegate = receivers_.current_context().get(); DCHECK(delegate); - for (const auto& element : elements) { - if (element->is_file()) { - if (!delegate->CanReadFile(element->get_file()->path)) { + std::vector<BlobUnderConstruction::ElementEntry> element_entries; + element_entries.reserve(elements.size()); + for (auto& element : elements) { + BlobUnderConstruction::ElementEntry entry(std::move(element)); + if (entry.element->is_file()) { + if (!delegate->CanReadFile(entry.element->get_file()->path)) { std::unique_ptr<BlobDataHandle> handle = context_->AddBrokenBlob( uuid, content_type, content_disposition, BlobStatus::ERR_REFERENCED_FILE_UNAVAILABLE); @@ -543,12 +547,13 @@ void BlobRegistryImpl::Register( std::move(callback).Run(); return; } - } else if (element->is_file_filesystem()) { - FileSystemURL filesystem_url( - file_system_context_->CrackURL(element->get_file_filesystem()->url)); - if (!filesystem_url.is_valid() || - !file_system_context_->GetFileSystemBackend(filesystem_url.type()) || - !delegate->CanReadFileSystemFile(filesystem_url)) { + } else if (entry.element->is_file_filesystem()) { + entry.filesystem_url = file_system_context_->CrackURL( + entry.element->get_file_filesystem()->url); + if (!entry.filesystem_url.is_valid() || + !file_system_context_->GetFileSystemBackend( + entry.filesystem_url.type()) || + !delegate->CanReadFileSystemFile(entry.filesystem_url)) { std::unique_ptr<BlobDataHandle> handle = context_->AddBrokenBlob( uuid, content_type, content_disposition, BlobStatus::ERR_REFERENCED_FILE_UNAVAILABLE); @@ -557,10 +562,11 @@ void BlobRegistryImpl::Register( return; } } + element_entries.push_back(std::move(entry)); } blobs_under_construction_[uuid] = std::make_unique<BlobUnderConstruction>( - this, uuid, content_type, content_disposition, std::move(elements), + this, uuid, content_type, content_disposition, std::move(element_entries), receivers_.GetBadMessageCallback()); std::unique_ptr<BlobDataHandle> handle = context_->AddFutureBlob( diff --git a/chromium/storage/browser/blob/blob_registry_impl_unittest.cc b/chromium/storage/browser/blob/blob_registry_impl_unittest.cc index a3207ff6b61..8f076e17c24 100644 --- a/chromium/storage/browser/blob/blob_registry_impl_unittest.cc +++ b/chromium/storage/browser/blob/blob_registry_impl_unittest.cc @@ -645,7 +645,9 @@ TEST_F(BlobRegistryImplTest, Register_FileSystemFile_Valid) { ASSERT_EQ(BlobStatus::DONE, handle->GetBlobStatus()); BlobDataBuilder expected_blob_data(kId); - expected_blob_data.AppendFileSystemFile(url, 0, 16, base::Time(), nullptr); + expected_blob_data.AppendFileSystemFile(file_system_context_->CrackURL(url), + 0, 16, base::Time(), + file_system_context_); EXPECT_EQ(expected_blob_data, *handle->CreateSnapshot()); EXPECT_EQ(0u, BlobsUnderConstruction()); diff --git a/chromium/storage/browser/blob/blob_storage_context_unittest.cc b/chromium/storage/browser/blob/blob_storage_context_unittest.cc index d09810799fc..42e7c34c7bb 100644 --- a/chromium/storage/browser/blob/blob_storage_context_unittest.cc +++ b/chromium/storage/browser/blob/blob_storage_context_unittest.cc @@ -32,9 +32,9 @@ #include "storage/browser/blob/blob_data_snapshot.h" #include "storage/browser/blob/blob_impl.h" #include "storage/browser/test/fake_blob_data_handle.h" +#include "storage/browser/test/test_file_system_context.h" #include "testing/gtest/include/gtest/gtest.h" - namespace storage { namespace { @@ -80,6 +80,8 @@ class BlobStorageContextTest : public testing::Test { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); base::ThreadRestrictions::SetIOAllowed(false); context_ = std::make_unique<BlobStorageContext>(); + file_system_context_ = + CreateFileSystemContextForTesting(nullptr, base::FilePath()); } void TearDown() override { @@ -138,6 +140,7 @@ class BlobStorageContextTest : public testing::Test { std::vector<FileCreationInfo> files_; base::ScopedTempDir temp_dir_; scoped_refptr<TestSimpleTaskRunner> file_runner_ = new TestSimpleTaskRunner(); + scoped_refptr<FileSystemContext> file_system_context_; base::test::SingleThreadTaskEnvironment task_environment_; std::unique_ptr<BlobStorageContext> context_; @@ -453,8 +456,8 @@ TEST_F(BlobStorageContextTest, AddFinishedBlob_LargeOffset) { const std::string kId2("id2"); auto builder1 = std::make_unique<BlobDataBuilder>(kId1); - builder1->AppendFileSystemFile(GURL(), 0, kLargeSize, base::Time::Now(), - nullptr); + builder1->AppendFileSystemFile(FileSystemURL(), 0, kLargeSize, + base::Time::Now(), file_system_context_); std::unique_ptr<BlobDataHandle> blob_data_handle1 = context_->AddFinishedBlob(std::move(builder1)); diff --git a/chromium/storage/browser/blob/blob_url_registry.cc b/chromium/storage/browser/blob/blob_url_registry.cc index ddca7989597..c0cea83e1fc 100644 --- a/chromium/storage/browser/blob/blob_url_registry.cc +++ b/chromium/storage/browser/blob/blob_url_registry.cc @@ -10,7 +10,9 @@ namespace storage { -BlobUrlRegistry::BlobUrlRegistry() = default; +BlobUrlRegistry::BlobUrlRegistry(base::WeakPtr<BlobUrlRegistry> fallback) + : fallback_(std::move(fallback)) {} + BlobUrlRegistry::~BlobUrlRegistry() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); } @@ -38,7 +40,11 @@ bool BlobUrlRegistry::RemoveUrlMapping(const GURL& blob_url) { bool BlobUrlRegistry::IsUrlMapped(const GURL& blob_url) const { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - return url_to_blob_.find(blob_url) != url_to_blob_.end(); + if (url_to_blob_.find(blob_url) != url_to_blob_.end()) + return true; + if (fallback_) + return fallback_->IsUrlMapped(blob_url); + return false; } mojo::PendingRemote<blink::mojom::Blob> BlobUrlRegistry::GetBlobFromUrl( @@ -46,7 +52,7 @@ mojo::PendingRemote<blink::mojom::Blob> BlobUrlRegistry::GetBlobFromUrl( DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); auto it = url_to_blob_.find(BlobUrlUtils::ClearUrlFragment(url)); if (it == url_to_blob_.end()) - return mojo::NullRemote(); + return fallback_ ? fallback_->GetBlobFromUrl(url) : mojo::NullRemote(); mojo::Remote<blink::mojom::Blob> blob(std::move(it->second)); mojo::PendingRemote<blink::mojom::Blob> result; blob->Clone(result.InitWithNewPipeAndPassReceiver()); diff --git a/chromium/storage/browser/blob/blob_url_registry.h b/chromium/storage/browser/blob/blob_url_registry.h index a3e80fe514d..c74eb3f00c4 100644 --- a/chromium/storage/browser/blob/blob_url_registry.h +++ b/chromium/storage/browser/blob/blob_url_registry.h @@ -24,7 +24,7 @@ namespace storage { // This class stores the mapping of blob Urls to blobs. class COMPONENT_EXPORT(STORAGE_BROWSER) BlobUrlRegistry { public: - BlobUrlRegistry(); + explicit BlobUrlRegistry(base::WeakPtr<BlobUrlRegistry> fallback = nullptr); ~BlobUrlRegistry(); // Creates a url mapping from blob to the given url. Returns false if @@ -59,6 +59,12 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) BlobUrlRegistry { private: SEQUENCE_CHECKER(sequence_checker_); + // Optional fallback BlobUrlRegistry. If lookups for URLs in this registry + // fail, they are retried in the fallback registry. This is used to allow + // "child" storage partitions to resolve URLs created by their "parent", while + // not allowing the reverse. + base::WeakPtr<BlobUrlRegistry> fallback_; + std::map<GURL, mojo::PendingRemote<blink::mojom::Blob>> url_to_blob_; std::map<base::UnguessableToken, std::pair<GURL, mojo::PendingRemote<blink::mojom::Blob>>> diff --git a/chromium/storage/browser/blob/view_blob_internals_job.cc b/chromium/storage/browser/blob/view_blob_internals_job.cc index a73d15d6f08..10c7ba69778 100644 --- a/chromium/storage/browser/blob/view_blob_internals_job.cc +++ b/chromium/storage/browser/blob/view_blob_internals_job.cc @@ -201,7 +201,7 @@ void ViewBlobInternalsJob::GenerateHTMLForBlobData( break; case BlobDataItem::Type::kFileFilesystem: AddHTMLListItem(kType, "filesystem", out); - AddHTMLListItem(kURL, item.filesystem_url().spec(), out); + AddHTMLListItem(kURL, item.filesystem_url().DebugString(), out); if (!item.expected_modification_time().is_null()) { AddHTMLListItem(kModificationTime, base::UTF16ToUTF8( TimeFormatFriendlyDateAndTime(item.expected_modification_time())), diff --git a/chromium/storage/browser/blob/write_blob_to_file.cc b/chromium/storage/browser/blob/write_blob_to_file.cc index 4f270b0878f..b41269b38f8 100644 --- a/chromium/storage/browser/blob/write_blob_to_file.cc +++ b/chromium/storage/browser/blob/write_blob_to_file.cc @@ -97,7 +97,8 @@ bool CopyFileContentsWithOffsetAndSize(base::File* infile, for (;;) { size_t bytes_to_read = - std::min(buffer.size(), base::saturated_cast<size_t>(max_size)); + std::min(buffer.size(), + base::checked_cast<size_t>(checked_max_size.ValueOrDie())); int bytes_read = infile->ReadAtCurrentPos(buffer.data(), bytes_to_read); if (bytes_read < 0) return false; diff --git a/chromium/storage/browser/file_system/file_stream_reader.h b/chromium/storage/browser/file_system/file_stream_reader.h index d9ac296c94a..4f6e30af5f6 100644 --- a/chromium/storage/browser/file_system/file_stream_reader.h +++ b/chromium/storage/browser/file_system/file_stream_reader.h @@ -13,6 +13,7 @@ #include "base/component_export.h" #include "base/files/file.h" #include "base/memory/weak_ptr.h" +#include "components/services/storage/public/cpp/filesystem/filesystem_proxy.h" #include "net/base/completion_once_callback.h" namespace base { @@ -44,8 +45,29 @@ class FileStreamReader { // ERR_UPLOAD_FILE_CHANGED error. COMPONENT_EXPORT(STORAGE_BROWSER) static std::unique_ptr<FileStreamReader> CreateForLocalFile( - base::TaskRunner* task_runner, + scoped_refptr<base::TaskRunner> task_runner, + const base::FilePath& file_path, + int64_t initial_offset, + const base::Time& expected_modification_time); + + // Creates a new FileReader for a local file |file_path|, which is a + // relative path into |filesystem_proxy|. This function's behavior + // is otherwise identical to CreateForLocalFile other than all file operations + // going through |filesystem_proxy|. + COMPONENT_EXPORT(STORAGE_BROWSER) + static std::unique_ptr<FileStreamReader> CreateForFilesystemProxy( + scoped_refptr<base::TaskRunner> task_runner, + const base::FilePath& file_path, + std::unique_ptr<storage::FilesystemProxy> filesystem_proxy, + int64_t initial_offset, + const base::Time& expected_modification_time); + + // The same as CreateForFilesystemProxy, but will emit diagnostic metrics. + COMPONENT_EXPORT(STORAGE_BROWSER) + static std::unique_ptr<FileStreamReader> CreateForIndexedDBDataItemReader( + scoped_refptr<base::TaskRunner> task_runner, const base::FilePath& file_path, + std::unique_ptr<storage::FilesystemProxy> filesystem_proxy, int64_t initial_offset, const base::Time& expected_modification_time); diff --git a/chromium/storage/browser/file_system/file_stream_reader_test.cc b/chromium/storage/browser/file_system/file_stream_reader_test.cc new file mode 100644 index 00000000000..1ce59c61b7e --- /dev/null +++ b/chromium/storage/browser/file_system/file_stream_reader_test.cc @@ -0,0 +1,12 @@ +// Copyright 2020 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/file_stream_reader_test.h" + +namespace storage { + +const base::StringPiece FileStreamReaderTest::kTestFileName; +const base::StringPiece FileStreamReaderTest::kTestData; + +} // namespace storage diff --git a/chromium/storage/browser/file_system/file_stream_reader_test.h b/chromium/storage/browser/file_system/file_stream_reader_test.h new file mode 100644 index 00000000000..f82e7cb00dd --- /dev/null +++ b/chromium/storage/browser/file_system/file_stream_reader_test.h @@ -0,0 +1,287 @@ +// Copyright 2020 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_FILE_STREAM_READER_TEST_H_ +#define STORAGE_BROWSER_FILE_SYSTEM_FILE_STREAM_READER_TEST_H_ + +#include "base/bind_helpers.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/single_thread_task_runner.h" +#include "base/test/task_environment.h" +#include "base/threading/thread.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/base/test_completion_callback.h" +#include "storage/browser/file_system/file_stream_reader.h" +#include "storage/browser/file_system/file_stream_test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace storage { + +// An interface for derived FileStreamReader to implement. +// This allows multiple FileStreamReader implementations can share the +// same test framework. Tests should implement CreateFileReader, WriteFile, and +// TouchFile to manipulate files for their particular implementation. +class FileStreamReaderTest : public testing::Test { + public: + static constexpr base::StringPiece kTestFileName = "test.dat"; + static constexpr base::StringPiece kTestData = "0123456789"; + + virtual std::unique_ptr<FileStreamReader> CreateFileReader( + const std::string& file_name, + int64_t initial_offset, + const base::Time& expected_modification_time) = 0; + virtual void WriteFile(const std::string& file_name, + const char* buf, + size_t buf_size, + base::Time* modification_time) = 0; + // Adjust a file's last modified time by |delta|. + virtual void TouchFile(const std::string& file_name, + base::TimeDelta delta) = 0; + virtual void EnsureFileTaskFinished() {} + + base::Time test_file_modification_time() const { + return test_file_modification_time_; + } + + void WriteTestFile() { + WriteFile(std::string(kTestFileName), kTestData.data(), kTestData.size(), + &test_file_modification_time_); + } + + static void NeverCalled(int unused) { ADD_FAILURE(); } + + private: + base::test::SingleThreadTaskEnvironment task_environment_{ + base::test::SingleThreadTaskEnvironment::MainThreadType::IO}; + base::Time test_file_modification_time_; +}; + +template <class SubClass> +class FileStreamReaderTypedTest : public SubClass { + public: + void SetUp() override { + SubClass::SetUp(); + this->WriteTestFile(); + } +}; + +TYPED_TEST_SUITE_P(FileStreamReaderTypedTest); + +TYPED_TEST_P(FileStreamReaderTypedTest, NonExistent) { + const char kFileName[] = "nonexistent"; + std::unique_ptr<FileStreamReader> reader( + this->CreateFileReader(kFileName, 0, base::Time())); + int result = 0; + std::string data; + ReadFromReader(reader.get(), &data, 10, &result); + ASSERT_EQ(net::ERR_FILE_NOT_FOUND, result); + ASSERT_EQ(0U, data.size()); +} + +TYPED_TEST_P(FileStreamReaderTypedTest, Empty) { + const char kFileName[] = "empty"; + this->WriteFile(kFileName, nullptr, 0, nullptr); + + std::unique_ptr<FileStreamReader> reader( + this->CreateFileReader(kFileName, 0, base::Time())); + int result = 0; + std::string data; + ReadFromReader(reader.get(), &data, 10, &result); + ASSERT_EQ(net::OK, result); + ASSERT_EQ(0U, data.size()); + + net::TestInt64CompletionCallback callback; + int64_t length_result = reader->GetLength(callback.callback()); + if (length_result == net::ERR_IO_PENDING) + length_result = callback.WaitForResult(); + ASSERT_EQ(0, length_result); +} + +TYPED_TEST_P(FileStreamReaderTypedTest, GetLengthNormal) { + std::unique_ptr<FileStreamReader> reader( + this->CreateFileReader(std::string(this->kTestFileName), 0, + this->test_file_modification_time())); + net::TestInt64CompletionCallback callback; + int64_t result = reader->GetLength(callback.callback()); + if (result == net::ERR_IO_PENDING) + result = callback.WaitForResult(); + ASSERT_EQ(static_cast<int64_t>(this->kTestData.size()), result); +} + +TYPED_TEST_P(FileStreamReaderTypedTest, GetLengthAfterModified) { + this->TouchFile(std::string(this->kTestFileName), + base::TimeDelta::FromSeconds(10)); + + std::unique_ptr<FileStreamReader> reader( + this->CreateFileReader(std::string(this->kTestFileName), 0, + this->test_file_modification_time())); + net::TestInt64CompletionCallback callback1; + int64_t result = reader->GetLength(callback1.callback()); + if (result == net::ERR_IO_PENDING) + result = callback1.WaitForResult(); + ASSERT_EQ(net::ERR_UPLOAD_FILE_CHANGED, result); + + // With nullptr expected modification time this should work. + reader = + this->CreateFileReader(std::string(this->kTestFileName), 0, base::Time()); + net::TestInt64CompletionCallback callback2; + result = reader->GetLength(callback2.callback()); + if (result == net::ERR_IO_PENDING) + result = callback2.WaitForResult(); + ASSERT_EQ(static_cast<int64_t>(this->kTestData.size()), result); +} + +TYPED_TEST_P(FileStreamReaderTypedTest, GetLengthWithOffset) { + std::unique_ptr<FileStreamReader> reader(this->CreateFileReader( + std::string(this->kTestFileName), 3, base::Time())); + net::TestInt64CompletionCallback callback; + int64_t result = reader->GetLength(callback.callback()); + if (result == net::ERR_IO_PENDING) + result = callback.WaitForResult(); + // Initial offset does not affect the result of GetLength. + ASSERT_EQ(static_cast<int64_t>(this->kTestData.size()), result); +} + +TYPED_TEST_P(FileStreamReaderTypedTest, ReadNormal) { + std::unique_ptr<FileStreamReader> reader( + this->CreateFileReader(std::string(this->kTestFileName), 0, + this->test_file_modification_time())); + int result = 0; + std::string data; + ReadFromReader(reader.get(), &data, this->kTestData.size(), &result); + ASSERT_EQ(net::OK, result); + ASSERT_EQ(this->kTestData, data); +} + +TYPED_TEST_P(FileStreamReaderTypedTest, ReadAfterModified) { + // Touch file so that the file's modification time becomes different + // from what we expect. Note that the resolution on some filesystems + // is 1s so we can't test with deltas less than that. + this->TouchFile(std::string(this->kTestFileName), + base::TimeDelta::FromSeconds(-1)); + + std::unique_ptr<FileStreamReader> reader( + this->CreateFileReader(std::string(this->kTestFileName), 0, + this->test_file_modification_time())); + int result = 0; + std::string data; + ReadFromReader(reader.get(), &data, this->kTestData.size(), &result); + ASSERT_EQ(net::ERR_UPLOAD_FILE_CHANGED, result); + ASSERT_EQ(0U, data.size()); +} + +TYPED_TEST_P(FileStreamReaderTypedTest, ReadAfterModifiedLessThanThreshold) { + // Due to precision loss converting int64_t->double->int64_t (e.g. through + // Blink) the expected/actual time may vary by microseconds. With + // modification time delta < 10us this should work. + this->TouchFile(std::string(this->kTestFileName), + base::TimeDelta::FromMicroseconds(1)); + std::unique_ptr<FileStreamReader> reader( + this->CreateFileReader(std::string(this->kTestFileName), 0, + this->test_file_modification_time())); + int result = 0; + std::string data; + + ReadFromReader(reader.get(), &data, this->kTestData.size(), &result); + ASSERT_EQ(net::OK, result); + ASSERT_EQ(this->kTestData, data); +} + +TYPED_TEST_P(FileStreamReaderTypedTest, ReadAfterModifiedWithMatchingTimes) { + this->TouchFile(std::string(this->kTestFileName), base::TimeDelta()); + std::unique_ptr<FileStreamReader> reader( + this->CreateFileReader(std::string(this->kTestFileName), 0, + this->test_file_modification_time())); + int result = 0; + std::string data; + + ReadFromReader(reader.get(), &data, this->kTestData.size(), &result); + ASSERT_EQ(net::OK, result); + ASSERT_EQ(this->kTestData, data); +} + +TYPED_TEST_P(FileStreamReaderTypedTest, ReadAfterModifiedWithoutExpectedTime) { + this->TouchFile(std::string(this->kTestFileName), + base::TimeDelta::FromSeconds(-1)); + std::unique_ptr<FileStreamReader> reader(this->CreateFileReader( + std::string(this->kTestFileName), 0, base::Time())); + int result = 0; + std::string data; + + ReadFromReader(reader.get(), &data, this->kTestData.size(), &result); + ASSERT_EQ(net::OK, result); + ASSERT_EQ(this->kTestData, data); +} + +TYPED_TEST_P(FileStreamReaderTypedTest, ReadWithOffset) { + std::unique_ptr<FileStreamReader> reader(this->CreateFileReader( + std::string(this->kTestFileName), 3, base::Time())); + int result = 0; + std::string data; + ReadFromReader(reader.get(), &data, this->kTestData.size(), &result); + ASSERT_EQ(net::OK, result); + + ASSERT_EQ(this->kTestData.substr(3), data); +} + +TYPED_TEST_P(FileStreamReaderTypedTest, ReadWithNegativeOffset) { + std::unique_ptr<FileStreamReader> reader(this->CreateFileReader( + std::string(this->kTestFileName), -1, base::Time())); + int result = 0; + std::string data; + ReadFromReader(reader.get(), &data, 1, &result); + ASSERT_EQ(net::ERR_INVALID_ARGUMENT, result); + ASSERT_EQ(data.size(), 0u); +} + +TYPED_TEST_P(FileStreamReaderTypedTest, ReadWithOffsetLargerThanFile) { + std::unique_ptr<FileStreamReader> reader( + this->CreateFileReader(std::string(this->kTestFileName), + this->kTestData.size() + 1, base::Time())); + int result = 0; + std::string data; + ReadFromReader(reader.get(), &data, 1, &result); + ASSERT_EQ(data.size(), 0u); + ASSERT_EQ(net::OK, result); +} + +TYPED_TEST_P(FileStreamReaderTypedTest, DeleteWithUnfinishedRead) { + std::unique_ptr<FileStreamReader> reader(this->CreateFileReader( + std::string(this->kTestFileName), 0, base::Time())); + + net::TestCompletionCallback callback; + scoped_refptr<net::IOBufferWithSize> buf = + base::MakeRefCounted<net::IOBufferWithSize>(this->kTestData.size()); + int rv = reader->Read(buf.get(), buf->size(), + base::BindOnce(&FileStreamReaderTest::NeverCalled)); + if (rv < 0) + ASSERT_EQ(rv, net::ERR_IO_PENDING); + + // Delete immediately. + // Should not crash; nor should NeverCalled be callback. + reader = nullptr; + this->EnsureFileTaskFinished(); +} + +REGISTER_TYPED_TEST_SUITE_P(FileStreamReaderTypedTest, + NonExistent, + Empty, + GetLengthNormal, + GetLengthAfterModified, + GetLengthWithOffset, + ReadNormal, + ReadAfterModified, + ReadAfterModifiedLessThanThreshold, + ReadAfterModifiedWithMatchingTimes, + ReadAfterModifiedWithoutExpectedTime, + ReadWithOffset, + ReadWithNegativeOffset, + ReadWithOffsetLargerThanFile, + DeleteWithUnfinishedRead); + +} // namespace storage + +#endif // STORAGE_BROWSER_FILE_SYSTEM_FILE_STREAM_READER_TEST_H_ diff --git a/chromium/storage/browser/file_system/file_system_file_stream_reader_unittest.cc b/chromium/storage/browser/file_system/file_system_file_stream_reader_unittest.cc index dc5f5999969..9dbc6661796 100644 --- a/chromium/storage/browser/file_system/file_system_file_stream_reader_unittest.cc +++ b/chromium/storage/browser/file_system/file_system_file_stream_reader_unittest.cc @@ -21,6 +21,8 @@ #include "net/base/net_errors.h" #include "net/base/test_completion_callback.h" #include "storage/browser/file_system/external_mount_points.h" + +#include "storage/browser/file_system/file_stream_reader_test.h" #include "storage/browser/file_system/file_stream_test_utils.h" #include "storage/browser/file_system/file_system_context.h" #include "storage/browser/file_system/file_system_file_util.h" @@ -33,58 +35,44 @@ namespace storage { namespace { - const char kURLOrigin[] = "http://remote/"; -const char kTestFileName[] = "test.dat"; -const char kTestData[] = "0123456789"; -const int kTestDataSize = base::size(kTestData) - 1; - -void NeverCalled(int unused) { - ADD_FAILURE(); -} - } // namespace -class FileSystemFileStreamReaderTest : public testing::Test { +class FileSystemFileStreamReaderTest : public FileStreamReaderTest { public: FileSystemFileStreamReaderTest() = default; void SetUp() override { - ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + ASSERT_TRUE(dir_.CreateUniqueTempDir()); file_system_context_ = - CreateFileSystemContextForTesting(nullptr, temp_dir_.GetPath()); + CreateFileSystemContextForTesting(nullptr, dir_.GetPath()); - file_system_context_->OpenFileSystem(url::Origin::Create(GURL(kURLOrigin)), - kFileSystemTypeTemporary, - OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, - base::BindOnce(&OnOpenFileSystem)); + file_system_context_->OpenFileSystem( + url::Origin::Create(GURL(kURLOrigin)), kFileSystemTypeTemporary, + OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + base::BindOnce([](const GURL& root_url, const std::string& name, + base::File::Error result) { + ASSERT_EQ(base::File::FILE_OK, result); + })); base::RunLoop().RunUntilIdle(); - - WriteFile(kTestFileName, kTestData, kTestDataSize, - &test_file_modification_time_); } void TearDown() override { base::RunLoop().RunUntilIdle(); } - protected: - FileSystemFileStreamReader* CreateFileReader( + std::unique_ptr<FileStreamReader> CreateFileReader( const std::string& file_name, int64_t initial_offset, - const base::Time& expected_modification_time) { - return new FileSystemFileStreamReader( + const base::Time& expected_modification_time) override { + return FileStreamReader::CreateForFileSystemFile( file_system_context_.get(), GetFileSystemURL(file_name), initial_offset, expected_modification_time); } - base::Time test_file_modification_time() const { - return test_file_modification_time_; - } - void WriteFile(const std::string& file_name, const char* buf, - int buf_size, - base::Time* modification_time) { + size_t buf_size, + base::Time* modification_time) override { FileSystemURL url = GetFileSystemURL(file_name); ASSERT_EQ(base::File::FILE_OK, @@ -99,11 +87,17 @@ class FileSystemFileStreamReaderTest : public testing::Test { *modification_time = file_info.last_modified; } - private: - static void OnOpenFileSystem(const GURL& root_url, - const std::string& name, - base::File::Error result) { - ASSERT_EQ(base::File::FILE_OK, result); + void TouchFile(const std::string& file_name, base::TimeDelta delta) override { + FileSystemURL url = GetFileSystemURL(file_name); + + base::File::Info file_info; + ASSERT_EQ(base::File::FILE_OK, + AsyncFileTestHelper::GetMetadata(file_system_context_.get(), url, + &file_info)); + ASSERT_EQ(base::File::FILE_OK, + AsyncFileTestHelper::TouchFile(file_system_context_.get(), url, + file_info.last_accessed, + file_info.last_modified + delta)); } FileSystemURL GetFileSystemURL(const std::string& file_name) { @@ -112,140 +106,13 @@ class FileSystemFileStreamReaderTest : public testing::Test { base::FilePath().AppendASCII(file_name)); } - base::test::SingleThreadTaskEnvironment task_environment_{ - base::test::SingleThreadTaskEnvironment::MainThreadType::IO}; - base::ScopedTempDir temp_dir_; + private: + base::ScopedTempDir dir_; scoped_refptr<FileSystemContext> file_system_context_; - base::Time test_file_modification_time_; }; -TEST_F(FileSystemFileStreamReaderTest, NonExistent) { - const char kFileName[] = "nonexistent"; - std::unique_ptr<FileSystemFileStreamReader> reader( - CreateFileReader(kFileName, 0, base::Time())); - int result = 0; - std::string data; - ReadFromReader(reader.get(), &data, 10, &result); - ASSERT_EQ(net::ERR_FILE_NOT_FOUND, result); - ASSERT_EQ(0U, data.size()); -} - -TEST_F(FileSystemFileStreamReaderTest, Empty) { - const char kFileName[] = "empty"; - WriteFile(kFileName, nullptr, 0, nullptr); - - std::unique_ptr<FileSystemFileStreamReader> reader( - CreateFileReader(kFileName, 0, base::Time())); - int result = 0; - std::string data; - ReadFromReader(reader.get(), &data, 10, &result); - ASSERT_EQ(net::OK, result); - ASSERT_EQ(0U, data.size()); - - net::TestInt64CompletionCallback callback; - int64_t length_result = reader->GetLength(callback.callback()); - if (length_result == net::ERR_IO_PENDING) - length_result = callback.WaitForResult(); - ASSERT_EQ(0, length_result); -} - -TEST_F(FileSystemFileStreamReaderTest, GetLengthNormal) { - std::unique_ptr<FileSystemFileStreamReader> reader( - CreateFileReader(kTestFileName, 0, test_file_modification_time())); - net::TestInt64CompletionCallback callback; - int64_t result = reader->GetLength(callback.callback()); - if (result == net::ERR_IO_PENDING) - result = callback.WaitForResult(); - ASSERT_EQ(kTestDataSize, result); -} - -TEST_F(FileSystemFileStreamReaderTest, GetLengthAfterModified) { - // Pass a fake expected modifictaion time so that the expectation fails. - base::Time fake_expected_modification_time = - test_file_modification_time() - base::TimeDelta::FromSeconds(10); - - std::unique_ptr<FileSystemFileStreamReader> reader( - CreateFileReader(kTestFileName, 0, fake_expected_modification_time)); - net::TestInt64CompletionCallback callback1; - int64_t result = reader->GetLength(callback1.callback()); - if (result == net::ERR_IO_PENDING) - result = callback1.WaitForResult(); - ASSERT_EQ(net::ERR_UPLOAD_FILE_CHANGED, result); - - // With nullptr expected modification time this should work. - reader.reset(CreateFileReader(kTestFileName, 0, base::Time())); - net::TestInt64CompletionCallback callback2; - result = reader->GetLength(callback2.callback()); - if (result == net::ERR_IO_PENDING) - result = callback2.WaitForResult(); - ASSERT_EQ(kTestDataSize, result); -} - -TEST_F(FileSystemFileStreamReaderTest, GetLengthWithOffset) { - std::unique_ptr<FileSystemFileStreamReader> reader( - CreateFileReader(kTestFileName, 3, base::Time())); - net::TestInt64CompletionCallback callback; - int64_t result = reader->GetLength(callback.callback()); - if (result == net::ERR_IO_PENDING) - result = callback.WaitForResult(); - // Initial offset does not affect the result of GetLength. - ASSERT_EQ(kTestDataSize, result); -} - -TEST_F(FileSystemFileStreamReaderTest, ReadNormal) { - std::unique_ptr<FileSystemFileStreamReader> reader( - CreateFileReader(kTestFileName, 0, test_file_modification_time())); - int result = 0; - std::string data; - ReadFromReader(reader.get(), &data, kTestDataSize, &result); - ASSERT_EQ(net::OK, result); - ASSERT_EQ(kTestData, data); -} - -TEST_F(FileSystemFileStreamReaderTest, ReadAfterModified) { - // Pass a fake expected modifictaion time so that the expectation fails. - base::Time fake_expected_modification_time = - test_file_modification_time() - base::TimeDelta::FromSeconds(10); - - std::unique_ptr<FileSystemFileStreamReader> reader( - CreateFileReader(kTestFileName, 0, fake_expected_modification_time)); - int result = 0; - std::string data; - ReadFromReader(reader.get(), &data, kTestDataSize, &result); - ASSERT_EQ(net::ERR_UPLOAD_FILE_CHANGED, result); - ASSERT_EQ(0U, data.size()); - - // With nullptr expected modification time this should work. - data.clear(); - reader.reset(CreateFileReader(kTestFileName, 0, base::Time())); - ReadFromReader(reader.get(), &data, kTestDataSize, &result); - ASSERT_EQ(net::OK, result); - ASSERT_EQ(kTestData, data); -} - -TEST_F(FileSystemFileStreamReaderTest, ReadWithOffset) { - std::unique_ptr<FileSystemFileStreamReader> reader( - CreateFileReader(kTestFileName, 3, base::Time())); - int result = 0; - std::string data; - ReadFromReader(reader.get(), &data, kTestDataSize, &result); - ASSERT_EQ(net::OK, result); - ASSERT_EQ(&kTestData[3], data); -} - -TEST_F(FileSystemFileStreamReaderTest, DeleteWithUnfinishedRead) { - std::unique_ptr<FileSystemFileStreamReader> reader( - CreateFileReader(kTestFileName, 0, base::Time())); - - net::TestCompletionCallback callback; - scoped_refptr<net::IOBufferWithSize> buf = - base::MakeRefCounted<net::IOBufferWithSize>(kTestDataSize); - int rv = reader->Read(buf.get(), buf->size(), base::BindOnce(&NeverCalled)); - ASSERT_TRUE(rv == net::ERR_IO_PENDING || rv >= 0); - - // Delete immediately. - // Should not crash; nor should NeverCalled be callback. - reader.reset(); -} +INSTANTIATE_TYPED_TEST_SUITE_P(FileSystem, + FileStreamReaderTypedTest, + FileSystemFileStreamReaderTest); } // namespace storage 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 51ffd3e3e57..dcba590fb00 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 @@ -248,7 +248,7 @@ TEST_F(FileSystemQuotaClientTest, NoFileSystemTest) { TEST_F(FileSystemQuotaClientTest, NoFileTest) { auto quota_client = NewQuotaClient(); const TestFile kFiles[] = { - {true, nullptr, 0, kDummyURL1, kTemporary}, + {true, "", 0, kDummyURL1, kTemporary}, }; InitializeOriginFiles(quota_client.get(), kFiles, base::size(kFiles)); @@ -260,7 +260,7 @@ TEST_F(FileSystemQuotaClientTest, NoFileTest) { TEST_F(FileSystemQuotaClientTest, OneFileTest) { auto quota_client = NewQuotaClient(); const TestFile kFiles[] = { - {true, nullptr, 0, kDummyURL1, kTemporary}, + {true, "", 0, kDummyURL1, kTemporary}, {false, "foo", 4921, kDummyURL1, kTemporary}, }; InitializeOriginFiles(quota_client.get(), kFiles, base::size(kFiles)); @@ -276,7 +276,7 @@ TEST_F(FileSystemQuotaClientTest, OneFileTest) { TEST_F(FileSystemQuotaClientTest, TwoFilesTest) { auto quota_client = NewQuotaClient(); const TestFile kFiles[] = { - {true, nullptr, 0, kDummyURL1, kTemporary}, + {true, "", 0, kDummyURL1, kTemporary}, {false, "foo", 10310, kDummyURL1, kTemporary}, {false, "bar", 41, kDummyURL1, kTemporary}, }; @@ -293,7 +293,7 @@ TEST_F(FileSystemQuotaClientTest, TwoFilesTest) { TEST_F(FileSystemQuotaClientTest, EmptyFilesTest) { auto quota_client = NewQuotaClient(); const TestFile kFiles[] = { - {true, nullptr, 0, kDummyURL1, kTemporary}, + {true, "", 0, kDummyURL1, kTemporary}, {false, "foo", 0, kDummyURL1, kTemporary}, {false, "bar", 0, kDummyURL1, kTemporary}, {false, "baz", 0, kDummyURL1, kTemporary}, @@ -311,7 +311,7 @@ TEST_F(FileSystemQuotaClientTest, EmptyFilesTest) { TEST_F(FileSystemQuotaClientTest, SubDirectoryTest) { auto quota_client = NewQuotaClient(); const TestFile kFiles[] = { - {true, nullptr, 0, kDummyURL1, kTemporary}, + {true, "", 0, kDummyURL1, kTemporary}, {true, "dirtest", 0, kDummyURL1, kTemporary}, {false, "dirtest/foo", 11921, kDummyURL1, kTemporary}, {false, "bar", 4814, kDummyURL1, kTemporary}, @@ -329,11 +329,11 @@ TEST_F(FileSystemQuotaClientTest, SubDirectoryTest) { TEST_F(FileSystemQuotaClientTest, MultiTypeTest) { auto quota_client = NewQuotaClient(); const TestFile kFiles[] = { - {true, nullptr, 0, kDummyURL1, kTemporary}, + {true, "", 0, kDummyURL1, kTemporary}, {true, "dirtest", 0, kDummyURL1, kTemporary}, {false, "dirtest/foo", 133, kDummyURL1, kTemporary}, {false, "bar", 14, kDummyURL1, kTemporary}, - {true, nullptr, 0, kDummyURL1, kPersistent}, + {true, "", 0, kDummyURL1, kPersistent}, {true, "dirtest", 0, kDummyURL1, kPersistent}, {false, "dirtest/foo", 193, kDummyURL1, kPersistent}, {false, "bar", 9, kDummyURL1, kPersistent}, @@ -357,19 +357,19 @@ TEST_F(FileSystemQuotaClientTest, MultiTypeTest) { TEST_F(FileSystemQuotaClientTest, MultiDomainTest) { auto quota_client = NewQuotaClient(); const TestFile kFiles[] = { - {true, nullptr, 0, kDummyURL1, kTemporary}, + {true, "", 0, kDummyURL1, kTemporary}, {true, "dir1", 0, kDummyURL1, kTemporary}, {false, "dir1/foo", 1331, kDummyURL1, kTemporary}, {false, "bar", 134, kDummyURL1, kTemporary}, - {true, nullptr, 0, kDummyURL1, kPersistent}, + {true, "", 0, kDummyURL1, kPersistent}, {true, "dir2", 0, kDummyURL1, kPersistent}, {false, "dir2/foo", 1903, kDummyURL1, kPersistent}, {false, "bar", 19, kDummyURL1, kPersistent}, - {true, nullptr, 0, kDummyURL2, kTemporary}, + {true, "", 0, kDummyURL2, kTemporary}, {true, "dom", 0, kDummyURL2, kTemporary}, {false, "dom/fan", 1319, kDummyURL2, kTemporary}, {false, "bar", 113, kDummyURL2, kTemporary}, - {true, nullptr, 0, kDummyURL2, kPersistent}, + {true, "", 0, kDummyURL2, kPersistent}, {true, "dom", 0, kDummyURL2, kPersistent}, {false, "dom/fan", 2013, kDummyURL2, kPersistent}, {false, "baz", 18, kDummyURL2, kPersistent}, @@ -403,7 +403,7 @@ TEST_F(FileSystemQuotaClientTest, MultiDomainTest) { TEST_F(FileSystemQuotaClientTest, GetUsage_MultipleTasks) { auto quota_client = NewQuotaClient(); const TestFile kFiles[] = { - {true, nullptr, 0, kDummyURL1, kTemporary}, + {true, "", 0, kDummyURL1, kTemporary}, {false, "foo", 11, kDummyURL1, kTemporary}, {false, "bar", 22, kDummyURL1, kTemporary}, }; @@ -433,9 +433,9 @@ TEST_F(FileSystemQuotaClientTest, GetUsage_MultipleTasks) { TEST_F(FileSystemQuotaClientTest, GetOriginsForType) { auto quota_client = NewQuotaClient(); const TestFile kFiles[] = { - {true, nullptr, 0, kDummyURL1, kTemporary}, - {true, nullptr, 0, kDummyURL2, kTemporary}, - {true, nullptr, 0, kDummyURL3, kPersistent}, + {true, "", 0, kDummyURL1, kTemporary}, + {true, "", 0, kDummyURL2, kTemporary}, + {true, "", 0, kDummyURL3, kPersistent}, }; InitializeOriginFiles(quota_client.get(), kFiles, base::size(kFiles)); @@ -459,11 +459,9 @@ TEST_F(FileSystemQuotaClientTest, GetOriginsForHost) { const char* kURL4 = "http://foo2.com/"; const char* kURL5 = "http://foo.com:2/"; const TestFile kFiles[] = { - {true, nullptr, 0, kURL1, kTemporary}, - {true, nullptr, 0, kURL2, kTemporary}, - {true, nullptr, 0, kURL3, kTemporary}, - {true, nullptr, 0, kURL4, kTemporary}, - {true, nullptr, 0, kURL5, kPersistent}, + {true, "", 0, kURL1, kTemporary}, {true, "", 0, kURL2, kTemporary}, + {true, "", 0, kURL3, kTemporary}, {true, "", 0, kURL4, kTemporary}, + {true, "", 0, kURL5, kPersistent}, }; InitializeOriginFiles(quota_client.get(), kFiles, base::size(kFiles)); @@ -482,19 +480,19 @@ TEST_F(FileSystemQuotaClientTest, GetOriginsForHost) { TEST_F(FileSystemQuotaClientTest, DeleteOriginTest) { auto quota_client = NewQuotaClient(); const TestFile kFiles[] = { - {true, nullptr, 0, "http://foo.com/", kTemporary}, + {true, "", 0, "http://foo.com/", kTemporary}, {false, "a", 1, "http://foo.com/", kTemporary}, - {true, nullptr, 0, "https://foo.com/", kTemporary}, + {true, "", 0, "https://foo.com/", kTemporary}, {false, "b", 2, "https://foo.com/", kTemporary}, - {true, nullptr, 0, "http://foo.com/", kPersistent}, + {true, "", 0, "http://foo.com/", kPersistent}, {false, "c", 4, "http://foo.com/", kPersistent}, - {true, nullptr, 0, "http://bar.com/", kTemporary}, + {true, "", 0, "http://bar.com/", kTemporary}, {false, "d", 8, "http://bar.com/", kTemporary}, - {true, nullptr, 0, "http://bar.com/", kPersistent}, + {true, "", 0, "http://bar.com/", kPersistent}, {false, "e", 16, "http://bar.com/", kPersistent}, - {true, nullptr, 0, "https://bar.com/", kPersistent}, + {true, "", 0, "https://bar.com/", kPersistent}, {false, "f", 32, "https://bar.com/", kPersistent}, - {true, nullptr, 0, "https://bar.com/", kTemporary}, + {true, "", 0, "https://bar.com/", kTemporary}, {false, "g", 64, "https://bar.com/", kTemporary}, }; InitializeOriginFiles(quota_client.get(), kFiles, base::size(kFiles)); diff --git a/chromium/storage/browser/file_system/filesystem_proxy_file_stream_reader.cc b/chromium/storage/browser/file_system/filesystem_proxy_file_stream_reader.cc new file mode 100644 index 00000000000..e60c66db978 --- /dev/null +++ b/chromium/storage/browser/file_system/filesystem_proxy_file_stream_reader.cc @@ -0,0 +1,245 @@ +// Copyright 2020 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/filesystem_proxy_file_stream_reader.h" + +#include <stdint.h> + +#include <utility> + +#include "base/bind.h" +#include "base/check_op.h" +#include "base/files/file_util.h" +#include "base/location.h" +#include "base/memory/ptr_util.h" +#include "base/metrics/histogram_macros.h" +#include "base/task_runner.h" +#include "base/task_runner_util.h" +#include "net/base/file_stream.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" + +namespace storage { + +namespace { + +const int kOpenFlagsForRead = + base::File::FLAG_OPEN | base::File::FLAG_READ | base::File::FLAG_ASYNC; + +using GetFileInfoCallback = + base::OnceCallback<void(base::File::Error, const base::File::Info&)>; + +FileErrorOr<base::File::Info> DoGetFileInfo( + const base::FilePath& path, + scoped_refptr<FilesystemProxyFileStreamReader::SharedFilesystemProxy> + shared_filesystem_proxy) { + if (!shared_filesystem_proxy->data->PathExists(path)) { + return base::File::FILE_ERROR_NOT_FOUND; + } + + base::Optional<base::File::Info> info = + shared_filesystem_proxy->data->GetFileInfo(path); + if (!info.has_value()) { + return base::File::FILE_ERROR_FAILED; + } + + return std::move(*info); +} + +FileErrorOr<base::File> DoOpenFile( + const base::FilePath& path, + scoped_refptr<FilesystemProxyFileStreamReader::SharedFilesystemProxy> + shared_filesystem_proxy) { + return shared_filesystem_proxy->data->OpenFile(path, kOpenFlagsForRead); +} + +} // namespace + +std::unique_ptr<FileStreamReader> FileStreamReader::CreateForFilesystemProxy( + scoped_refptr<base::TaskRunner> task_runner, + const base::FilePath& file_path, + std::unique_ptr<storage::FilesystemProxy> filesystem_proxy, + int64_t initial_offset, + const base::Time& expected_modification_time) { + DCHECK(filesystem_proxy); + constexpr bool emit_metrics = false; + return base::WrapUnique(new FilesystemProxyFileStreamReader( + std::move(task_runner), file_path, std::move(filesystem_proxy), + initial_offset, expected_modification_time, emit_metrics)); +} + +std::unique_ptr<FileStreamReader> +FileStreamReader::CreateForIndexedDBDataItemReader( + scoped_refptr<base::TaskRunner> task_runner, + const base::FilePath& file_path, + std::unique_ptr<storage::FilesystemProxy> filesystem_proxy, + int64_t initial_offset, + const base::Time& expected_modification_time) { + DCHECK(filesystem_proxy); + constexpr bool emit_metrics = true; + return base::WrapUnique(new FilesystemProxyFileStreamReader( + std::move(task_runner), file_path, std::move(filesystem_proxy), + initial_offset, expected_modification_time, emit_metrics)); +} + +FilesystemProxyFileStreamReader::~FilesystemProxyFileStreamReader() = default; + +int FilesystemProxyFileStreamReader::Read( + net::IOBuffer* buf, + int buf_len, + net::CompletionOnceCallback callback) { + DCHECK(!has_pending_open_); + + if (stream_impl_) + return stream_impl_->Read(buf, buf_len, std::move(callback)); + + Open(base::BindOnce(&FilesystemProxyFileStreamReader::DidOpenForRead, + weak_factory_.GetWeakPtr(), base::RetainedRef(buf), + buf_len, std::move(callback))); + + return net::ERR_IO_PENDING; +} + +int64_t FilesystemProxyFileStreamReader::GetLength( + net::Int64CompletionOnceCallback callback) { + base::PostTaskAndReplyWithResult( + task_runner_.get(), FROM_HERE, + base::BindOnce(&DoGetFileInfo, file_path_, shared_filesystem_proxy_), + base::BindOnce( + &FilesystemProxyFileStreamReader::DidGetFileInfoForGetLength, + weak_factory_.GetWeakPtr(), std::move(callback))); + return net::ERR_IO_PENDING; +} + +FilesystemProxyFileStreamReader::FilesystemProxyFileStreamReader( + scoped_refptr<base::TaskRunner> task_runner, + const base::FilePath& file_path, + std::unique_ptr<storage::FilesystemProxy> filesystem_proxy, + int64_t initial_offset, + const base::Time& expected_modification_time, + bool emit_metrics) + : task_runner_(std::move(task_runner)), + shared_filesystem_proxy_(base::MakeRefCounted<SharedFilesystemProxy>( + std::move(filesystem_proxy))), + file_path_(file_path), + initial_offset_(initial_offset), + expected_modification_time_(expected_modification_time), + emit_metrics_(emit_metrics) {} + +void FilesystemProxyFileStreamReader::Open( + net::CompletionOnceCallback callback) { + DCHECK(!has_pending_open_); + DCHECK(!stream_impl_.get()); + has_pending_open_ = true; + + // Call GetLength first to make it perform last-modified-time verification, + // and then call DidVerifyForOpen to do the rest. + int64_t verify_result = GetLength( + base::BindOnce(&FilesystemProxyFileStreamReader::DidVerifyForOpen, + weak_factory_.GetWeakPtr(), std::move(callback))); + DCHECK_EQ(verify_result, net::ERR_IO_PENDING); +} + +void FilesystemProxyFileStreamReader::DidVerifyForOpen( + net::CompletionOnceCallback callback, + int64_t get_length_result) { + if (get_length_result < 0) { + std::move(callback).Run(static_cast<int>(get_length_result)); + return; + } + + callback_ = std::move(callback); + base::PostTaskAndReplyWithResult( + task_runner_.get(), FROM_HERE, + base::BindOnce(&DoOpenFile, file_path_, shared_filesystem_proxy_), + base::BindOnce(&FilesystemProxyFileStreamReader::DidOpenFile, + weak_factory_.GetWeakPtr())); +} + +void FilesystemProxyFileStreamReader::DidOpenFile( + FileErrorOr<base::File> open_result) { + if (open_result.is_error()) { + std::move(callback_).Run(open_result.error()); + return; + } + + stream_impl_ = std::make_unique<net::FileStream>( + std::move(open_result.value()), task_runner_); + int seek_result = stream_impl_->Seek( + initial_offset_, + base::BindOnce(&FilesystemProxyFileStreamReader::DidSeekFileStream, + weak_factory_.GetWeakPtr())); + if (seek_result != net::ERR_IO_PENDING) { + std::move(callback_).Run(seek_result); + } +} + +void FilesystemProxyFileStreamReader::DidSeekFileStream(int64_t seek_result) { + if (seek_result < 0) { + std::move(callback_).Run(static_cast<int>(seek_result)); + return; + } + if (seek_result != initial_offset_) { + std::move(callback_).Run(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE); + return; + } + std::move(callback_).Run(net::OK); +} + +void FilesystemProxyFileStreamReader::DidOpenForRead( + net::IOBuffer* buf, + int buf_len, + net::CompletionOnceCallback callback, + int open_result) { + DCHECK(has_pending_open_); + has_pending_open_ = false; + if (open_result != net::OK) { + stream_impl_.reset(); + std::move(callback).Run(open_result); + return; + } + DCHECK(stream_impl_.get()); + + callback_ = std::move(callback); + const int read_result = stream_impl_->Read( + buf, buf_len, + base::BindOnce(&FilesystemProxyFileStreamReader::OnRead, + weak_factory_.GetWeakPtr())); + if (read_result != net::ERR_IO_PENDING) + std::move(callback_).Run(read_result); +} + +void FilesystemProxyFileStreamReader::DidGetFileInfoForGetLength( + net::Int64CompletionOnceCallback callback, + FileErrorOr<base::File::Info> result) { + // TODO(enne): track rate of missing blobs for http://crbug.com/1131151 + if (emit_metrics_) { + bool file_was_found = !result.is_error() || + result.error() != base::File::FILE_ERROR_NOT_FOUND; + UMA_HISTOGRAM_BOOLEAN("WebCore.IndexedDB.FoundBlobFileForValue", + file_was_found); + } + + if (result.is_error()) { + std::move(callback).Run(net::FileErrorToNetError(result.error())); + return; + } + + const auto& file_info = result.value(); + if (file_info.is_directory) { + std::move(callback).Run(net::ERR_FILE_NOT_FOUND); + return; + } + if (!VerifySnapshotTime(expected_modification_time_, file_info)) { + std::move(callback).Run(net::ERR_UPLOAD_FILE_CHANGED); + return; + } + std::move(callback).Run(file_info.size); +} + +void FilesystemProxyFileStreamReader::OnRead(int read_result) { + std::move(callback_).Run(read_result); +} + +} // namespace storage diff --git a/chromium/storage/browser/file_system/filesystem_proxy_file_stream_reader.h b/chromium/storage/browser/file_system/filesystem_proxy_file_stream_reader.h new file mode 100644 index 00000000000..10222e0d032 --- /dev/null +++ b/chromium/storage/browser/file_system/filesystem_proxy_file_stream_reader.h @@ -0,0 +1,93 @@ +// Copyright 2020 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_FILESYSTEM_PROXY_FILE_STREAM_READER_H_ +#define STORAGE_BROWSER_FILE_SYSTEM_FILESYSTEM_PROXY_FILE_STREAM_READER_H_ + +#include <stdint.h> + +#include <memory> + +#include "base/compiler_specific.h" +#include "base/component_export.h" +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/memory/weak_ptr.h" +#include "base/time/time.h" +#include "components/services/storage/public/cpp/filesystem/filesystem_proxy.h" +#include "net/base/completion_once_callback.h" +#include "storage/browser/file_system/file_stream_reader.h" + +namespace base { +class TaskRunner; +} + +namespace net { +class FileStream; +} + +namespace storage { + +// A thin wrapper of net::FileStream with range support for sliced file +// handling. +class COMPONENT_EXPORT(STORAGE_BROWSER) FilesystemProxyFileStreamReader + : public FileStreamReader { + public: + ~FilesystemProxyFileStreamReader() override; + + // FileStreamReader overrides. + int Read(net::IOBuffer* buf, + int buf_len, + net::CompletionOnceCallback callback) override; + int64_t GetLength(net::Int64CompletionOnceCallback callback) override; + + using SharedFilesystemProxy = + base::RefCountedData<std::unique_ptr<storage::FilesystemProxy>>; + + private: + friend class FileStreamReader; + + FilesystemProxyFileStreamReader( + scoped_refptr<base::TaskRunner> task_runner, + const base::FilePath& file_path, + std::unique_ptr<storage::FilesystemProxy> filesystem_proxy, + int64_t initial_offset, + const base::Time& expected_modification_time, + bool emit_metrics); + void Open(net::CompletionOnceCallback callback); + + // Callbacks that are chained from Open for Read. + void DidVerifyForOpen(net::CompletionOnceCallback callback, + int64_t get_length_result); + void DidOpenFile(FileErrorOr<base::File> result); + + void DidSeekFileStream(int64_t seek_result); + void DidOpenForRead(net::IOBuffer* buf, + int buf_len, + net::CompletionOnceCallback callback, + int open_result); + void OnRead(int read_result); + + void DidGetFileInfoForGetLength(net::Int64CompletionOnceCallback callback, + FileErrorOr<base::File::Info> result); + + net::CompletionOnceCallback callback_; + scoped_refptr<base::TaskRunner> task_runner_; + std::unique_ptr<net::FileStream> stream_impl_; + // FilesystemProxy is threadsafe, however all file operations must be + // run on |task_runner_|. Therefore, this refcounted-data preserves + // the lifetime of the filesystem proxy even if the FileStreamReader is + // destroyed during a callback. + scoped_refptr<SharedFilesystemProxy> shared_filesystem_proxy_; + const base::FilePath file_path_; + const int64_t initial_offset_; + const base::Time expected_modification_time_; + bool has_pending_open_ = false; + bool emit_metrics_ = false; + base::WeakPtrFactory<FilesystemProxyFileStreamReader> weak_factory_{this}; +}; + +} // namespace storage + +#endif // STORAGE_BROWSER_FILE_SYSTEM_FILESYSTEM_PROXY_FILE_STREAM_READER_H_ 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 new file mode 100644 index 00000000000..0cfd7801075 --- /dev/null +++ b/chromium/storage/browser/file_system/filesystem_proxy_file_stream_reader_unittest.cc @@ -0,0 +1,109 @@ +// Copyright 2020 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/local_file_stream_reader.h" + +#include <stddef.h> +#include <stdint.h> + +#include <memory> +#include <string> + +#include "base/bind.h" +#include "base/bind_helpers.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 "base/location.h" +#include "base/macros.h" +#include "base/run_loop.h" +#include "base/single_thread_task_runner.h" +#include "base/stl_util.h" +#include "base/test/task_environment.h" +#include "base/threading/thread.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/base/test_completion_callback.h" +#include "storage/browser/file_system/file_stream_reader_test.h" +#include "storage/browser/file_system/file_stream_test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace storage { + +class FilesystemProxyFileStreamReaderTest : public FileStreamReaderTest { + public: + FilesystemProxyFileStreamReaderTest() : file_thread_("TestFileThread") {} + + void SetUp() override { + ASSERT_TRUE(dir_.CreateUniqueTempDir()); + ASSERT_TRUE(file_thread_.Start()); + } + + void TearDown() override { + // Give another chance for deleted streams to perform Close. + base::RunLoop().RunUntilIdle(); + file_thread_.Stop(); + base::RunLoop().RunUntilIdle(); + } + + std::unique_ptr<FileStreamReader> CreateFileReader( + const std::string& file_name, + int64_t initial_offset, + const base::Time& expected_modification_time) override { + return FileStreamReader::CreateForFilesystemProxy( + file_task_runner(), test_dir().AppendASCII(file_name), + CreateFilesystemProxy(), initial_offset, expected_modification_time); + } + + void WriteFile(const std::string& file_name, + const char* buf, + size_t buf_size, + base::Time* modification_time) override { + base::FilePath path = test_dir().AppendASCII(file_name); + base::WriteFile(path, buf, buf_size); + + base::File::Info file_info; + ASSERT_TRUE(base::GetFileInfo(path, &file_info)); + if (modification_time) + *modification_time = file_info.last_modified; + } + + void TouchFile(const std::string& file_name, base::TimeDelta delta) override { + base::FilePath path = test_dir().AppendASCII(file_name); + base::File::Info file_info; + ASSERT_TRUE(base::GetFileInfo(path, &file_info)); + ASSERT_TRUE(base::TouchFile(path, file_info.last_accessed, + file_info.last_modified + delta)); + } + + void EnsureFileTaskFinished() override { + base::RunLoop run_loop; + file_task_runner()->PostTaskAndReply(FROM_HERE, base::DoNothing(), + + run_loop.QuitClosure()); + run_loop.Run(); + } + + base::FilePath test_dir() const { return dir_.GetPath(); } + + base::SingleThreadTaskRunner* file_task_runner() const { + return file_thread_.task_runner().get(); + } + + std::unique_ptr<FilesystemProxy> CreateFilesystemProxy() { + return std::make_unique<FilesystemProxy>(FilesystemProxy::UNRESTRICTED, + dir_.GetPath()); + } + + private: + base::ScopedTempDir dir_; + base::Thread file_thread_; +}; + +INSTANTIATE_TYPED_TEST_SUITE_P(FilesystemProxy, + FileStreamReaderTypedTest, + FilesystemProxyFileStreamReaderTest); + +} // namespace storage diff --git a/chromium/storage/browser/file_system/local_file_stream_reader.cc b/chromium/storage/browser/file_system/local_file_stream_reader.cc index 716b5806cff..efd958857eb 100644 --- a/chromium/storage/browser/file_system/local_file_stream_reader.cc +++ b/chromium/storage/browser/file_system/local_file_stream_reader.cc @@ -26,40 +26,27 @@ namespace { const int kOpenFlagsForRead = base::File::FLAG_OPEN | base::File::FLAG_READ | base::File::FLAG_ASYNC; -struct GetFileInfoResults { - base::File::Error error; - base::File::Info info; -}; - -using GetFileInfoCallback = - base::OnceCallback<void(base::File::Error, const base::File::Info&)>; - -GetFileInfoResults DoGetFileInfo(const base::FilePath& path) { - GetFileInfoResults results; - if (!base::PathExists(path)) { - results.error = base::File::FILE_ERROR_NOT_FOUND; - return results; - } - results.error = base::GetFileInfo(path, &results.info) - ? base::File::FILE_OK - : base::File::FILE_ERROR_FAILED; - return results; -} +FileErrorOr<base::File::Info> DoGetFileInfo(const base::FilePath& path) { + if (!base::PathExists(path)) + return base::File::FILE_ERROR_NOT_FOUND; -void SendGetFileInfoResults(GetFileInfoCallback callback, - const GetFileInfoResults& results) { - std::move(callback).Run(results.error, results.info); + base::File::Info info; + bool success = base::GetFileInfo(path, &info); + if (!success) + return base::File::FILE_ERROR_FAILED; + return info; } } // namespace std::unique_ptr<FileStreamReader> FileStreamReader::CreateForLocalFile( - base::TaskRunner* task_runner, + scoped_refptr<base::TaskRunner> task_runner, const base::FilePath& file_path, int64_t initial_offset, const base::Time& expected_modification_time) { - return base::WrapUnique(new LocalFileStreamReader( - task_runner, file_path, initial_offset, expected_modification_time)); + return base::WrapUnique( + new LocalFileStreamReader(std::move(task_runner), file_path, + initial_offset, expected_modification_time)); } LocalFileStreamReader::~LocalFileStreamReader() = default; @@ -83,24 +70,21 @@ int64_t LocalFileStreamReader::GetLength( net::Int64CompletionOnceCallback callback) { bool posted = base::PostTaskAndReplyWithResult( task_runner_.get(), FROM_HERE, base::BindOnce(&DoGetFileInfo, file_path_), - base::BindOnce( - &SendGetFileInfoResults, - base::BindOnce(&LocalFileStreamReader::DidGetFileInfoForGetLength, - weak_factory_.GetWeakPtr(), std::move(callback)))); + base::BindOnce(&LocalFileStreamReader::DidGetFileInfoForGetLength, + weak_factory_.GetWeakPtr(), std::move(callback))); DCHECK(posted); return net::ERR_IO_PENDING; } LocalFileStreamReader::LocalFileStreamReader( - base::TaskRunner* task_runner, + scoped_refptr<base::TaskRunner> task_runner, const base::FilePath& file_path, int64_t initial_offset, const base::Time& expected_modification_time) - : task_runner_(task_runner), + : task_runner_(std::move(task_runner)), file_path_(file_path), initial_offset_(initial_offset), - expected_modification_time_(expected_modification_time), - has_pending_open_(false) {} + expected_modification_time_(expected_modification_time) {} void LocalFileStreamReader::Open(net::CompletionOnceCallback callback) { DCHECK(!has_pending_open_); @@ -182,14 +166,14 @@ void LocalFileStreamReader::DidOpenForRead(net::IOBuffer* buf, void LocalFileStreamReader::DidGetFileInfoForGetLength( net::Int64CompletionOnceCallback callback, - base::File::Error error, - const base::File::Info& file_info) { - if (file_info.is_directory) { - std::move(callback).Run(net::ERR_FILE_NOT_FOUND); + FileErrorOr<base::File::Info> result) { + if (result.is_error()) { + std::move(callback).Run(net::FileErrorToNetError(result.error())); return; } - if (error != base::File::FILE_OK) { - std::move(callback).Run(net::FileErrorToNetError(error)); + const auto& file_info = result.value(); + if (file_info.is_directory) { + std::move(callback).Run(net::ERR_FILE_NOT_FOUND); return; } if (!VerifySnapshotTime(expected_modification_time_, file_info)) { diff --git a/chromium/storage/browser/file_system/local_file_stream_reader.h b/chromium/storage/browser/file_system/local_file_stream_reader.h index f46c3956828..e9f5866f513 100644 --- a/chromium/storage/browser/file_system/local_file_stream_reader.h +++ b/chromium/storage/browser/file_system/local_file_stream_reader.h @@ -45,7 +45,7 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) LocalFileStreamReader friend class FileStreamReader; friend class LocalFileStreamReaderTest; - LocalFileStreamReader(base::TaskRunner* task_runner, + LocalFileStreamReader(scoped_refptr<base::TaskRunner> task_runner, const base::FilePath& file_path, int64_t initial_offset, const base::Time& expected_modification_time); @@ -63,8 +63,7 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) LocalFileStreamReader void OnRead(int read_result); void DidGetFileInfoForGetLength(net::Int64CompletionOnceCallback callback, - base::File::Error error, - const base::File::Info& file_info); + FileErrorOr<base::File::Info> result); net::CompletionOnceCallback callback_; scoped_refptr<base::TaskRunner> task_runner_; @@ -72,7 +71,7 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) LocalFileStreamReader const base::FilePath file_path_; const int64_t initial_offset_; const base::Time expected_modification_time_; - bool has_pending_open_; + bool has_pending_open_ = false; base::WeakPtrFactory<LocalFileStreamReader> weak_factory_{this}; }; 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 7b7887d5214..d1f6bf8fe82 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 @@ -26,38 +26,19 @@ #include "net/base/io_buffer.h" #include "net/base/net_errors.h" #include "net/base/test_completion_callback.h" +#include "storage/browser/file_system/file_stream_reader_test.h" #include "storage/browser/file_system/file_stream_test_utils.h" #include "testing/gtest/include/gtest/gtest.h" namespace storage { -namespace { - -const char kTestData[] = "0123456789"; -const int kTestDataSize = base::size(kTestData) - 1; - -void NeverCalled(int) { - ADD_FAILURE(); -} - -void QuitLoop() { - base::RunLoop::QuitCurrentWhenIdleDeprecated(); -} - -} // namespace - -class LocalFileStreamReaderTest : public testing::Test { +class LocalFileStreamReaderTest : public FileStreamReaderTest { public: LocalFileStreamReaderTest() : file_thread_("TestFileThread") {} void SetUp() override { - ASSERT_TRUE(file_thread_.Start()); ASSERT_TRUE(dir_.CreateUniqueTempDir()); - - base::WriteFile(test_path(), kTestData); - base::File::Info info; - ASSERT_TRUE(base::GetFileInfo(test_path(), &info)); - test_file_modification_time_ = info.last_modified; + ASSERT_TRUE(file_thread_.Start()); } void TearDown() override { @@ -67,195 +48,57 @@ class LocalFileStreamReaderTest : public testing::Test { base::RunLoop().RunUntilIdle(); } - protected: - LocalFileStreamReader* CreateFileReader( - const base::FilePath& path, + std::unique_ptr<FileStreamReader> CreateFileReader( + const std::string& file_name, int64_t initial_offset, - const base::Time& expected_modification_time) { - return new LocalFileStreamReader(file_task_runner(), path, initial_offset, - expected_modification_time); + const base::Time& expected_modification_time) override { + return FileStreamReader::CreateForLocalFile( + file_task_runner(), test_dir().AppendASCII(file_name), initial_offset, + expected_modification_time); } - void TouchTestFile(base::TimeDelta delta) { - base::Time new_modified_time = test_file_modification_time() + delta; - ASSERT_TRUE(base::TouchFile(test_path(), test_file_modification_time(), - new_modified_time)); + void WriteFile(const std::string& file_name, + const char* buf, + size_t buf_size, + base::Time* modification_time) override { + base::FilePath path = test_dir().AppendASCII(file_name); + base::WriteFile(path, buf, buf_size); + + base::File::Info file_info; + ASSERT_TRUE(base::GetFileInfo(path, &file_info)); + if (modification_time) + *modification_time = file_info.last_modified; } - base::SingleThreadTaskRunner* file_task_runner() const { - return file_thread_.task_runner().get(); + void TouchFile(const std::string& file_name, base::TimeDelta delta) override { + base::FilePath path = test_dir().AppendASCII(file_name); + base::File::Info file_info; + ASSERT_TRUE(base::GetFileInfo(path, &file_info)); + ASSERT_TRUE(base::TouchFile(path, file_info.last_accessed, + file_info.last_modified + delta)); } - base::FilePath test_dir() const { return dir_.GetPath(); } - base::FilePath test_path() const { - return dir_.GetPath().AppendASCII("test"); - } - base::Time test_file_modification_time() const { - return test_file_modification_time_; + void EnsureFileTaskFinished() override { + base::RunLoop run_loop; + file_task_runner()->PostTaskAndReply(FROM_HERE, base::DoNothing(), + + run_loop.QuitClosure()); + run_loop.Run(); } - void EnsureFileTaskFinished() { - file_task_runner()->PostTaskAndReply(FROM_HERE, base::DoNothing(), - base::BindOnce(&QuitLoop)); - base::RunLoop().Run(); + base::FilePath test_dir() const { return dir_.GetPath(); } + + base::SingleThreadTaskRunner* file_task_runner() const { + return file_thread_.task_runner().get(); } private: - base::test::SingleThreadTaskEnvironment task_environment_{ - base::test::SingleThreadTaskEnvironment::MainThreadType::IO}; - base::Thread file_thread_; base::ScopedTempDir dir_; - base::Time test_file_modification_time_; + base::Thread file_thread_; }; -TEST_F(LocalFileStreamReaderTest, NonExistent) { - base::FilePath nonexistent_path = test_dir().AppendASCII("nonexistent"); - std::unique_ptr<LocalFileStreamReader> reader( - CreateFileReader(nonexistent_path, 0, base::Time())); - int result = 0; - std::string data; - ReadFromReader(reader.get(), &data, 10, &result); - ASSERT_EQ(net::ERR_FILE_NOT_FOUND, result); - ASSERT_EQ(0U, data.size()); -} - -TEST_F(LocalFileStreamReaderTest, Empty) { - base::FilePath empty_path = test_dir().AppendASCII("empty"); - base::File file(empty_path, base::File::FLAG_CREATE | base::File::FLAG_READ); - ASSERT_TRUE(file.IsValid()); - file.Close(); - - std::unique_ptr<LocalFileStreamReader> reader( - CreateFileReader(empty_path, 0, base::Time())); - int result = 0; - std::string data; - ReadFromReader(reader.get(), &data, 10, &result); - ASSERT_EQ(net::OK, result); - ASSERT_EQ(0U, data.size()); - - net::TestInt64CompletionCallback callback; - int64_t length_result = reader->GetLength(callback.callback()); - if (length_result == net::ERR_IO_PENDING) - length_result = callback.WaitForResult(); - ASSERT_EQ(0, result); -} - -TEST_F(LocalFileStreamReaderTest, GetLengthNormal) { - std::unique_ptr<LocalFileStreamReader> reader( - CreateFileReader(test_path(), 0, test_file_modification_time())); - net::TestInt64CompletionCallback callback; - int64_t result = reader->GetLength(callback.callback()); - if (result == net::ERR_IO_PENDING) - result = callback.WaitForResult(); - ASSERT_EQ(kTestDataSize, result); -} - -TEST_F(LocalFileStreamReaderTest, GetLengthAfterModified) { - // Touch file so that the file's modification time becomes different - // from what we expect. - TouchTestFile(base::TimeDelta::FromSeconds(-1)); - - std::unique_ptr<LocalFileStreamReader> reader( - CreateFileReader(test_path(), 0, test_file_modification_time())); - net::TestInt64CompletionCallback callback1; - int64_t result = reader->GetLength(callback1.callback()); - if (result == net::ERR_IO_PENDING) - result = callback1.WaitForResult(); - ASSERT_EQ(net::ERR_UPLOAD_FILE_CHANGED, result); - - // With nullptr expected modification time this should work. - reader.reset(CreateFileReader(test_path(), 0, base::Time())); - net::TestInt64CompletionCallback callback2; - result = reader->GetLength(callback2.callback()); - if (result == net::ERR_IO_PENDING) - result = callback2.WaitForResult(); - ASSERT_EQ(kTestDataSize, result); -} - -TEST_F(LocalFileStreamReaderTest, GetLengthWithOffset) { - std::unique_ptr<LocalFileStreamReader> reader( - CreateFileReader(test_path(), 3, base::Time())); - net::TestInt64CompletionCallback callback; - int64_t result = reader->GetLength(callback.callback()); - if (result == net::ERR_IO_PENDING) - result = callback.WaitForResult(); - // Initial offset does not affect the result of GetLength. - ASSERT_EQ(kTestDataSize, result); -} - -TEST_F(LocalFileStreamReaderTest, ReadNormal) { - std::unique_ptr<LocalFileStreamReader> reader( - CreateFileReader(test_path(), 0, test_file_modification_time())); - int result = 0; - std::string data; - ReadFromReader(reader.get(), &data, kTestDataSize, &result); - ASSERT_EQ(net::OK, result); - ASSERT_EQ(kTestData, data); -} - -TEST_F(LocalFileStreamReaderTest, ReadAfterModified) { - // Touch file so that the file's modification time becomes different - // from what we expect. Note that the resolution on some filesystems - // is 1s so we can't test with deltas less than that. - TouchTestFile(base::TimeDelta::FromSeconds(-1)); - std::unique_ptr<LocalFileStreamReader> reader( - CreateFileReader(test_path(), 0, test_file_modification_time())); - int result = 0; - std::string data; - ReadFromReader(reader.get(), &data, kTestDataSize, &result); - EXPECT_EQ(net::ERR_UPLOAD_FILE_CHANGED, result); - EXPECT_EQ(0U, data.size()); - - // Due to precision loss converting int64_t->double->int64_t (e.g. through - // Blink) the expected/actual time may vary by microseconds. With - // modification time delta < 10us this should work. - TouchTestFile(base::TimeDelta::FromMicroseconds(1)); - data.clear(); - reader.reset(CreateFileReader(test_path(), 0, test_file_modification_time())); - ReadFromReader(reader.get(), &data, kTestDataSize, &result); - EXPECT_EQ(net::OK, result); - EXPECT_EQ(kTestData, data); - - // With matching modification times time this should work. - TouchTestFile(base::TimeDelta()); - data.clear(); - reader.reset(CreateFileReader(test_path(), 0, test_file_modification_time())); - ReadFromReader(reader.get(), &data, kTestDataSize, &result); - EXPECT_EQ(net::OK, result); - EXPECT_EQ(kTestData, data); - - // And with nullptr expected modification time this should work. - data.clear(); - reader.reset(CreateFileReader(test_path(), 0, base::Time())); - ReadFromReader(reader.get(), &data, kTestDataSize, &result); - EXPECT_EQ(net::OK, result); - EXPECT_EQ(kTestData, data); -} - -TEST_F(LocalFileStreamReaderTest, ReadWithOffset) { - std::unique_ptr<LocalFileStreamReader> reader( - CreateFileReader(test_path(), 3, base::Time())); - int result = 0; - std::string data; - ReadFromReader(reader.get(), &data, kTestDataSize, &result); - ASSERT_EQ(net::OK, result); - ASSERT_EQ(&kTestData[3], data); -} - -TEST_F(LocalFileStreamReaderTest, DeleteWithUnfinishedRead) { - std::unique_ptr<LocalFileStreamReader> reader( - CreateFileReader(test_path(), 0, base::Time())); - - net::TestCompletionCallback callback; - scoped_refptr<net::IOBufferWithSize> buf = - base::MakeRefCounted<net::IOBufferWithSize>(kTestDataSize); - int rv = reader->Read(buf.get(), buf->size(), base::BindOnce(&NeverCalled)); - ASSERT_TRUE(rv == net::ERR_IO_PENDING || rv >= 0); - - // Delete immediately. - // Should not crash; nor should NeverCalled be callback. - reader.reset(); - EnsureFileTaskFinished(); -} +INSTANTIATE_TYPED_TEST_SUITE_P(Local, + FileStreamReaderTypedTest, + LocalFileStreamReaderTest); } // namespace storage diff --git a/chromium/storage/browser/file_system/local_file_stream_writer.cc b/chromium/storage/browser/file_system/local_file_stream_writer.cc index 342f1e8ca67..5ef1c708372 100644 --- a/chromium/storage/browser/file_system/local_file_stream_writer.cc +++ b/chromium/storage/browser/file_system/local_file_stream_writer.cc @@ -139,19 +139,46 @@ void LocalFileStreamWriter::DidOpen(base::OnceClosure main_operation, return; } - InitiateSeek(std::move(main_operation)); + auto file_info = std::make_unique<base::File::Info>(); + base::File::Info* raw_file_info = file_info.get(); + result = stream_impl_->GetFileInfo( + raw_file_info, + base::BindOnce(&LocalFileStreamWriter::InitiateSeek, + weak_factory_.GetWeakPtr(), std::move(main_operation), + std::move(file_info))); + DCHECK_NE(result, net::OK); + if (result != net::ERR_IO_PENDING) { + has_pending_operation_ = false; + std::move(write_callback_).Run(result); + } } -void LocalFileStreamWriter::InitiateSeek(base::OnceClosure main_operation) { +void LocalFileStreamWriter::InitiateSeek( + base::OnceClosure main_operation, + std::unique_ptr<base::File::Info> file_info, + int file_info_result) { DCHECK(has_pending_operation_); DCHECK(stream_impl_.get()); + if (file_info_result != net::OK) { + has_pending_operation_ = false; + std::move(write_callback_).Run(file_info_result); + return; + } + if (initial_offset_ == 0) { // No need to seek. std::move(main_operation).Run(); return; } + if (initial_offset_ > file_info->size) { + // We should not be writing past the end of the file. + has_pending_operation_ = false; + std::move(write_callback_).Run(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE); + return; + } + int result = stream_impl_->Seek( initial_offset_, base::BindOnce(&LocalFileStreamWriter::DidSeek, diff --git a/chromium/storage/browser/file_system/local_file_stream_writer.h b/chromium/storage/browser/file_system/local_file_stream_writer.h index ed51d9c4fb7..df2b4d1dff2 100644 --- a/chromium/storage/browser/file_system/local_file_stream_writer.h +++ b/chromium/storage/browser/file_system/local_file_stream_writer.h @@ -13,6 +13,7 @@ #include "base/callback.h" #include "base/compiler_specific.h" #include "base/component_export.h" +#include "base/files/file.h" #include "base/files/file_path.h" #include "base/macros.h" #include "base/memory/weak_ptr.h" @@ -54,7 +55,9 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) LocalFileStreamWriter // Seeks to |initial_offset_| and proceeds to |main_operation| if it succeeds. // If failed, the error code is returned by calling |error_callback|. - void InitiateSeek(base::OnceClosure main_operation); + void InitiateSeek(base::OnceClosure main_operation, + std::unique_ptr<base::File::Info> file_info, + int file_info_result); void DidSeek(base::OnceClosure main_operation, int64_t result); // Passed as the |main_operation| of InitiateOpen() function. diff --git a/chromium/storage/browser/file_system/local_file_stream_writer_unittest.cc b/chromium/storage/browser/file_system/local_file_stream_writer_unittest.cc index e2b2c1cbeb2..704fe4f1d37 100644 --- a/chromium/storage/browser/file_system/local_file_stream_writer_unittest.cc +++ b/chromium/storage/browser/file_system/local_file_stream_writer_unittest.cc @@ -24,6 +24,12 @@ namespace storage { +namespace { +void NeverCalled(int unused) { + ADD_FAILURE(); +} +} // namespace + class LocalFileStreamWriterTest : public testing::Test { public: LocalFileStreamWriterTest() : file_thread_("TestFileThread") {} @@ -75,10 +81,6 @@ class LocalFileStreamWriterTest : public testing::Test { base::ScopedTempDir temp_dir_; }; -void NeverCalled(int unused) { - ADD_FAILURE(); -} - TEST_F(LocalFileStreamWriterTest, Write) { base::FilePath path = CreateFileWithContent("file_a", std::string()); std::unique_ptr<LocalFileStreamWriter> writer(CreateWriter(path, 0)); @@ -100,6 +102,16 @@ TEST_F(LocalFileStreamWriterTest, WriteMiddle) { EXPECT_EQ("foxxxr", GetFileContent(path)); } +TEST_F(LocalFileStreamWriterTest, WriteNearEnd) { + base::FilePath path = CreateFileWithContent("file_a", "foobar"); + std::unique_ptr<LocalFileStreamWriter> writer(CreateWriter(path, 5)); + EXPECT_EQ(net::OK, WriteStringToWriter(writer.get(), "xxx")); + writer.reset(); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(base::PathExists(path)); + EXPECT_EQ("foobaxxx", GetFileContent(path)); +} + TEST_F(LocalFileStreamWriterTest, WriteEnd) { base::FilePath path = CreateFileWithContent("file_a", "foobar"); std::unique_ptr<LocalFileStreamWriter> writer(CreateWriter(path, 6)); @@ -110,6 +122,17 @@ TEST_F(LocalFileStreamWriterTest, WriteEnd) { EXPECT_EQ("foobarxxx", GetFileContent(path)); } +TEST_F(LocalFileStreamWriterTest, WriteAfterEnd) { + base::FilePath path = CreateFileWithContent("file_a", "foobar"); + std::unique_ptr<LocalFileStreamWriter> writer(CreateWriter(path, 7)); + EXPECT_EQ(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE, + WriteStringToWriter(writer.get(), "xxx")); + writer.reset(); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(base::PathExists(path)); + EXPECT_EQ("foobar", GetFileContent(path)); +} + TEST_F(LocalFileStreamWriterTest, WriteFailForNonexistingFile) { base::FilePath path = Path("file_a"); ASSERT_FALSE(base::PathExists(path)); diff --git a/chromium/storage/browser/file_system/memory_file_stream_reader_unittest.cc b/chromium/storage/browser/file_system/memory_file_stream_reader_unittest.cc index 99bcfcbeb7e..685f49c22ef 100644 --- a/chromium/storage/browser/file_system/memory_file_stream_reader_unittest.cc +++ b/chromium/storage/browser/file_system/memory_file_stream_reader_unittest.cc @@ -21,230 +21,69 @@ #include "net/base/io_buffer.h" #include "net/base/net_errors.h" #include "storage/browser/file_system/file_stream_reader.h" +#include "storage/browser/file_system/file_stream_reader_test.h" #include "storage/browser/file_system/file_stream_test_utils.h" #include "storage/browser/file_system/obfuscated_file_util_memory_delegate.h" #include "testing/gtest/include/gtest/gtest.h" namespace storage { -namespace { - -const char kTestData[] = "0123456789"; -const int kTestDataSize = base::size(kTestData) - 1; - -} // namespace - -class MemoryFileStreamReaderTest : public testing::Test { +class MemoryFileStreamReaderTest : public FileStreamReaderTest { public: - MemoryFileStreamReaderTest() {} + MemoryFileStreamReaderTest() = default; void SetUp() override { - ASSERT_TRUE(file_system_directory_.CreateUniqueTempDir()); - file_util_ = std::make_unique<ObfuscatedFileUtilMemoryDelegate>( - file_system_directory_.GetPath()); - - file_util_->CreateFileForTesting( - test_path(), base::span<const char>(kTestData, kTestDataSize)); - base::File::Info info; - ASSERT_EQ(base::File::FILE_OK, file_util_->GetFileInfo(test_path(), &info)); - test_file_modification_time_ = info.last_modified; + ASSERT_TRUE(dir_.CreateUniqueTempDir()); + file_util_ = std::make_unique<ObfuscatedFileUtilMemoryDelegate>(test_dir()); } void TearDown() override { // In memory operations should not have any residue in file system // directory. - EXPECT_TRUE(base::IsDirectoryEmpty(file_system_directory_.GetPath())); + EXPECT_TRUE(base::IsDirectoryEmpty(test_dir())); } - ObfuscatedFileUtilMemoryDelegate* file_util() { return file_util_.get(); } - - protected: std::unique_ptr<FileStreamReader> CreateFileReader( - const base::FilePath& path, + const std::string& file_name, int64_t initial_offset, - const base::Time& expected_modification_time) { + const base::Time& expected_modification_time) override { return FileStreamReader::CreateForMemoryFile( - base::ThreadTaskRunnerHandle::Get(), file_util_->GetWeakPtr(), path, - initial_offset, expected_modification_time); + base::ThreadTaskRunnerHandle::Get(), file_util_->GetWeakPtr(), + test_dir().AppendASCII(file_name), initial_offset, + expected_modification_time); } - void TouchTestFile(base::TimeDelta delta) { - base::Time new_modified_time = test_file_modification_time() + delta; - ASSERT_EQ(base::File::FILE_OK, - file_util()->Touch(test_path(), test_file_modification_time(), - new_modified_time)); + void WriteFile(const std::string& file_name, + const char* buf, + size_t buf_size, + base::Time* modification_time) override { + base::FilePath path = test_dir().AppendASCII(file_name); + file_util_->CreateFileForTesting(path, + base::span<const char>(buf, buf_size)); + base::File::Info file_info; + ASSERT_EQ(base::File::FILE_OK, file_util_->GetFileInfo(path, &file_info)); + if (modification_time) + *modification_time = file_info.last_modified; } - base::FilePath test_dir() const { return file_system_directory_.GetPath(); } - base::FilePath test_path() const { - return file_system_directory_.GetPath().AppendASCII("test"); - } - base::Time test_file_modification_time() const { - return test_file_modification_time_; + void TouchFile(const std::string& file_name, base::TimeDelta delta) override { + base::FilePath path = test_dir().AppendASCII(file_name); + base::File::Info file_info; + ASSERT_EQ(base::File::FILE_OK, file_util_->GetFileInfo(path, &file_info)); + ASSERT_EQ(base::File::FILE_OK, + file_util_->Touch(path, file_info.last_accessed, + file_info.last_modified + delta)); } + base::FilePath test_dir() const { return dir_.GetPath(); } + private: - base::test::TaskEnvironment task_environment_; - base::ScopedTempDir file_system_directory_; + base::ScopedTempDir dir_; std::unique_ptr<ObfuscatedFileUtilMemoryDelegate> file_util_; - base::Time test_file_modification_time_; }; -TEST_F(MemoryFileStreamReaderTest, NonExistent) { - base::FilePath nonexistent_path = test_dir().AppendASCII("nonexistent"); - std::unique_ptr<FileStreamReader> reader( - CreateFileReader(nonexistent_path, 0, base::Time())); - int result = 0; - std::string data; - ReadFromReader(reader.get(), &data, 10, &result); - ASSERT_EQ(net::ERR_FILE_NOT_FOUND, result); - ASSERT_EQ(0U, data.size()); -} - -TEST_F(MemoryFileStreamReaderTest, Empty) { - base::FilePath empty_path = test_dir().AppendASCII("empty"); - bool created; - EXPECT_EQ(base::File::FILE_OK, - file_util()->EnsureFileExists(empty_path, &created)); - - std::unique_ptr<FileStreamReader> reader( - CreateFileReader(empty_path, 0, base::Time())); - int result = 0; - std::string data; - ReadFromReader(reader.get(), &data, 10, &result); - ASSERT_EQ(net::OK, result); - ASSERT_EQ(0U, data.size()); - - int64_t length_result = GetLengthFromReader(reader.get()); - ASSERT_EQ(0, length_result); -} - -TEST_F(MemoryFileStreamReaderTest, GetLengthNormal) { - std::unique_ptr<FileStreamReader> reader( - CreateFileReader(test_path(), 0, test_file_modification_time())); - int64_t result = GetLengthFromReader(reader.get()); - ASSERT_EQ(kTestDataSize, result); -} - -TEST_F(MemoryFileStreamReaderTest, GetLengthAfterModified) { - // Touch file so that the file's modification time becomes different - // from what we expect. - TouchTestFile(base::TimeDelta::FromSeconds(-1)); - - std::unique_ptr<FileStreamReader> reader( - CreateFileReader(test_path(), 0, test_file_modification_time())); - int64_t result = GetLengthFromReader(reader.get()); - ASSERT_EQ(net::ERR_UPLOAD_FILE_CHANGED, result); -} - -TEST_F(MemoryFileStreamReaderTest, GetLengthAfterModifiedWithNoExpectedTime) { - // Touch file so that the file's modification time becomes different - // from what we expect. - TouchTestFile(base::TimeDelta::FromSeconds(-1)); - - std::unique_ptr<FileStreamReader> reader( - CreateFileReader(test_path(), 0, base::Time())); - int64_t result = GetLengthFromReader(reader.get()); - ASSERT_EQ(kTestDataSize, result); -} - -TEST_F(MemoryFileStreamReaderTest, GetLengthWithOffset) { - std::unique_ptr<FileStreamReader> reader( - CreateFileReader(test_path(), 3, base::Time())); - int64_t result = GetLengthFromReader(reader.get()); - // Initial offset does not affect the result of GetLength. - ASSERT_EQ(kTestDataSize, result); -} - -TEST_F(MemoryFileStreamReaderTest, ReadNormal) { - std::unique_ptr<FileStreamReader> reader( - CreateFileReader(test_path(), 0, test_file_modification_time())); - int result = 0; - std::string data; - ReadFromReader(reader.get(), &data, kTestDataSize, &result); - ASSERT_EQ(net::OK, result); - ASSERT_EQ(kTestData, data); -} - -TEST_F(MemoryFileStreamReaderTest, ReadAfterModified) { - // Touch file so that the file's modification time becomes different - // from what we expect. Note that the resolution on some filesystems - // is 1s so we can't test with deltas less than that. - TouchTestFile(base::TimeDelta::FromSeconds(-1)); - std::unique_ptr<FileStreamReader> reader( - CreateFileReader(test_path(), 0, test_file_modification_time())); - int result = 0; - std::string data; - ReadFromReader(reader.get(), &data, kTestDataSize, &result); - EXPECT_EQ(net::ERR_UPLOAD_FILE_CHANGED, result); - EXPECT_EQ(0U, data.size()); -} - -TEST_F(MemoryFileStreamReaderTest, ReadAfterModifiedLessThanThreshold) { - // Due to precision loss converting int64_t->double->int64_t (e.g. through - // Blink) the expected/actual time may vary by microseconds. With - // modification time delta < 10us this should work. - TouchTestFile(base::TimeDelta::FromMicroseconds(1)); - std::unique_ptr<FileStreamReader> reader( - CreateFileReader(test_path(), 0, test_file_modification_time())); - int result = 0; - std::string data; - - ReadFromReader(reader.get(), &data, kTestDataSize, &result); - EXPECT_EQ(net::OK, result); - EXPECT_EQ(kTestData, data); -} - -TEST_F(MemoryFileStreamReaderTest, ReadAfterModifiedWithMatchingTimes) { - TouchTestFile(base::TimeDelta()); - std::unique_ptr<FileStreamReader> reader( - CreateFileReader(test_path(), 0, test_file_modification_time())); - int result = 0; - std::string data; - - ReadFromReader(reader.get(), &data, kTestDataSize, &result); - EXPECT_EQ(net::OK, result); - EXPECT_EQ(kTestData, data); -} - -TEST_F(MemoryFileStreamReaderTest, ReadAfterModifiedWithoutExpectedTime) { - TouchTestFile(base::TimeDelta()); - std::unique_ptr<FileStreamReader> reader( - CreateFileReader(test_path(), 0, base::Time())); - int result = 0; - std::string data; - - ReadFromReader(reader.get(), &data, kTestDataSize, &result); - EXPECT_EQ(net::OK, result); - EXPECT_EQ(kTestData, data); -} - -TEST_F(MemoryFileStreamReaderTest, ReadWithOffset) { - std::unique_ptr<FileStreamReader> reader( - CreateFileReader(test_path(), 3, base::Time())); - int result = 0; - std::string data; - ReadFromReader(reader.get(), &data, kTestDataSize, &result); - ASSERT_EQ(net::OK, result); - ASSERT_EQ(&kTestData[3], data); -} - -TEST_F(MemoryFileStreamReaderTest, ReadWithNegativeOffset) { - std::unique_ptr<FileStreamReader> reader( - CreateFileReader(test_path(), -1, base::Time())); - int result = 0; - std::string data; - ReadFromReader(reader.get(), &data, 1, &result); - ASSERT_EQ(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE, result); -} - -TEST_F(MemoryFileStreamReaderTest, ReadWithOffsetLargerThanFile) { - std::unique_ptr<FileStreamReader> reader( - CreateFileReader(test_path(), kTestDataSize + 1, base::Time())); - int result = 0; - std::string data; - ReadFromReader(reader.get(), &data, 1, &result); - ASSERT_EQ(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE, result); -} +INSTANTIATE_TYPED_TEST_SUITE_P(Memory, + FileStreamReaderTypedTest, + MemoryFileStreamReaderTest); } // namespace storage diff --git a/chromium/storage/browser/file_system/memory_file_stream_writer.cc b/chromium/storage/browser/file_system/memory_file_stream_writer.cc index b36c4b5e4bb..7f45e02a310 100644 --- a/chromium/storage/browser/file_system/memory_file_stream_writer.cc +++ b/chromium/storage/browser/file_system/memory_file_stream_writer.cc @@ -34,6 +34,7 @@ MemoryFileStreamWriter::MemoryFileStreamWriter( file_path_(file_path), offset_(initial_offset) { DCHECK(memory_file_util_.MaybeValid()); + has_pending_operation_ = false; } MemoryFileStreamWriter::~MemoryFileStreamWriter() = default; @@ -41,6 +42,11 @@ MemoryFileStreamWriter::~MemoryFileStreamWriter() = default; int MemoryFileStreamWriter::Write(net::IOBuffer* buf, int buf_len, net::CompletionOnceCallback callback) { + DCHECK(!has_pending_operation_); + DCHECK(cancel_callback_.is_null()); + + has_pending_operation_ = true; + task_runner_->PostTaskAndReplyWithResult( FROM_HERE, base::BindOnce( @@ -66,17 +72,43 @@ int MemoryFileStreamWriter::Write(net::IOBuffer* buf, void MemoryFileStreamWriter::OnWriteCompleted( net::CompletionOnceCallback callback, int result) { + DCHECK(has_pending_operation_); + + if (CancelIfRequested()) + return; + has_pending_operation_ = false; + if (result > 0) offset_ += result; std::move(callback).Run(result); } -int MemoryFileStreamWriter::Cancel(net::CompletionOnceCallback /*callback*/) { - return net::ERR_UNEXPECTED; +int MemoryFileStreamWriter::Cancel(net::CompletionOnceCallback callback) { + if (!has_pending_operation_) + return net::ERR_UNEXPECTED; + + DCHECK(!callback.is_null()); + cancel_callback_ = std::move(callback); + return net::ERR_IO_PENDING; } int MemoryFileStreamWriter::Flush(net::CompletionOnceCallback /*callback*/) { + DCHECK(!has_pending_operation_); + DCHECK(cancel_callback_.is_null()); + return net::OK; } + +bool MemoryFileStreamWriter::CancelIfRequested() { + DCHECK(has_pending_operation_); + + if (cancel_callback_.is_null()) + return false; + + has_pending_operation_ = false; + std::move(cancel_callback_).Run(net::OK); + return true; +} + } // namespace storage diff --git a/chromium/storage/browser/file_system/memory_file_stream_writer.h b/chromium/storage/browser/file_system/memory_file_stream_writer.h index 74f6213f0f8..625a18edef3 100644 --- a/chromium/storage/browser/file_system/memory_file_stream_writer.h +++ b/chromium/storage/browser/file_system/memory_file_stream_writer.h @@ -37,14 +37,20 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) MemoryFileStreamWriter void OnWriteCompleted(net::CompletionOnceCallback callback, int result); + // Stops the in-flight operation and calls |cancel_callback_| if it has been + // set by Cancel() for the current operation. + bool CancelIfRequested(); + base::WeakPtr<ObfuscatedFileUtilMemoryDelegate> memory_file_util_; const scoped_refptr<base::TaskRunner> task_runner_; const base::FilePath file_path_; int64_t offset_; - base::WeakPtrFactory<MemoryFileStreamWriter> weak_factory_{this}; + bool has_pending_operation_; + net::CompletionOnceCallback cancel_callback_; + base::WeakPtrFactory<MemoryFileStreamWriter> weak_factory_{this}; DISALLOW_COPY_AND_ASSIGN(MemoryFileStreamWriter); }; diff --git a/chromium/storage/browser/file_system/memory_file_stream_writer_unittest.cc b/chromium/storage/browser/file_system/memory_file_stream_writer_unittest.cc index 44342a33c66..f15892c57e0 100644 --- a/chromium/storage/browser/file_system/memory_file_stream_writer_unittest.cc +++ b/chromium/storage/browser/file_system/memory_file_stream_writer_unittest.cc @@ -16,6 +16,7 @@ #include "base/test/task_environment.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" +#include "net/base/test_completion_callback.h" #include "storage/browser/file_system/file_stream_test_utils.h" #include "storage/browser/file_system/file_stream_writer.h" #include "storage/browser/file_system/obfuscated_file_util_memory_delegate.h" @@ -23,6 +24,12 @@ namespace storage { +namespace { +void NeverCalled(int unused) { + ADD_FAILURE(); +} +} // namespace + class MemoryFileStreamWriterTest : public testing::Test { public: MemoryFileStreamWriterTest() {} @@ -159,6 +166,25 @@ TEST_F(MemoryFileStreamWriterTest, CancelAfterFinishedOperation) { EXPECT_EQ("foo", GetFileContent(path)); } +TEST_F(MemoryFileStreamWriterTest, CancelWrite) { + base::FilePath path = Path("file_a"); + bool created; + EXPECT_EQ(base::File::FILE_OK, file_util()->EnsureFileExists(path, &created)); + + std::unique_ptr<FileStreamWriter> writer(CreateWriter(path, 0)); + + scoped_refptr<net::StringIOBuffer> buffer( + base::MakeRefCounted<net::StringIOBuffer>("xxx")); + int result = + writer->Write(buffer.get(), buffer->size(), base::BindOnce(&NeverCalled)); + ASSERT_EQ(net::ERR_IO_PENDING, result); + + net::TestCompletionCallback callback; + writer->Cancel(callback.callback()); + int cancel_result = callback.WaitForResult(); + EXPECT_EQ(net::OK, cancel_result); +} + TEST_F(MemoryFileStreamWriterTest, FlushBeforeWriting) { base::FilePath path = Path("file_a"); bool created; diff --git a/chromium/storage/browser/file_system/obfuscated_file_util_memory_delegate.cc b/chromium/storage/browser/file_system/obfuscated_file_util_memory_delegate.cc index 3a3d8563e3c..ec25aacf576 100644 --- a/chromium/storage/browser/file_system/obfuscated_file_util_memory_delegate.cc +++ b/chromium/storage/browser/file_system/obfuscated_file_util_memory_delegate.cc @@ -456,8 +456,13 @@ int ObfuscatedFileUtilMemoryDelegate::ReadFile(const base::FilePath& path, return net::ERR_FILE_NOT_FOUND; int64_t remaining = dp->entry->file_content.size() - offset; - if (offset < 0 || remaining < 0) - return net::ERR_REQUEST_RANGE_NOT_SATISFIABLE; + if (offset < 0) + return net::ERR_INVALID_ARGUMENT; + + // Seeking past the end of the file is ok, but returns nothing. + // This matches FileStream::Context behavior. + if (remaining < 0) + return 0; if (buf_len > remaining) buf_len = static_cast<int>(remaining); 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 ea0c93a6330..3f29d3c707e 100644 --- a/chromium/storage/browser/file_system/sandbox_file_stream_writer.cc +++ b/chromium/storage/browser/file_system/sandbox_file_stream_writer.cc @@ -140,7 +140,7 @@ void SandboxFileStreamWriter::DidCreateSnapshotFile( } file_size_ = file_info.size; if (initial_offset_ > file_size_) { - // We should not be writing pass the end of the file. + // We should not be writing past the end of the file. std::move(callback).Run(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE); return; } diff --git a/chromium/storage/browser/quota/quota_database.cc b/chromium/storage/browser/quota/quota_database.cc index b65429ae2db..7322359b68c 100644 --- a/chromium/storage/browser/quota/quota_database.cc +++ b/chromium/storage/browser/quota/quota_database.cc @@ -470,20 +470,34 @@ bool QuotaDatabase::GetLRUOrigin(StorageType type, return statement.Succeeded(); } -bool QuotaDatabase::GetOriginsModifiedSince(StorageType type, - std::set<url::Origin>* origins, - base::Time modified_since) { +bool QuotaDatabase::GetOriginsModifiedBetween(StorageType type, + std::set<url::Origin>* origins, + base::Time begin, + base::Time end) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(origins); if (!LazyOpen(false)) return false; - const char* kSql = "SELECT origin FROM OriginInfoTable" - " WHERE type = ? AND last_modified_time >= ?"; + DCHECK(!begin.is_max() && end != base::Time()); + static constexpr char kSqlQuerySince[] = + "SELECT origin FROM OriginInfoTable" + " WHERE type = ? AND last_modified_time >= ?"; - sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + static constexpr char kSqlQueryBetween[] = + "SELECT origin FROM OriginInfoTable" + " WHERE type = ? AND last_modified_time >= ? AND last_modified_time < ?"; + + sql::Statement statement; + if (end.is_max()) { + statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, kSqlQuerySince)); + } else { + statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, kSqlQueryBetween)); + } statement.BindInt(0, static_cast<int>(type)); - statement.BindInt64(1, TimeToSqlValue(modified_since)); + statement.BindInt64(1, TimeToSqlValue(begin)); + if (!end.is_max()) + statement.BindInt64(2, TimeToSqlValue(end)); origins->clear(); while (statement.Step()) diff --git a/chromium/storage/browser/quota/quota_database.h b/chromium/storage/browser/quota/quota_database.h index 286754e7268..64cbe16ec6e 100644 --- a/chromium/storage/browser/quota/quota_database.h +++ b/chromium/storage/browser/quota/quota_database.h @@ -124,10 +124,12 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaDatabase { base::Optional<url::Origin>* origin); // Populates |origins| with the ones that have been modified since - // the |modified_since|. Returns whether the operation succeeded. - bool GetOriginsModifiedSince(blink::mojom::StorageType type, - std::set<url::Origin>* origins, - base::Time modified_since); + // the |begin| and until the |end|. Returns whether the + // operation succeeded. + bool GetOriginsModifiedBetween(blink::mojom::StorageType type, + std::set<url::Origin>* origins, + base::Time begin, + base::Time end); // Returns false if SetOriginDatabaseBootstrapped has never // been called before, which means existing origins may not have been diff --git a/chromium/storage/browser/quota/quota_database_unittest.cc b/chromium/storage/browser/quota/quota_database_unittest.cc index 81edd2ab2e9..0310fda6708 100644 --- a/chromium/storage/browser/quota/quota_database_unittest.cc +++ b/chromium/storage/browser/quota/quota_database_unittest.cc @@ -219,12 +219,13 @@ class QuotaDatabaseTest : public testing::Test { EXPECT_FALSE(origin.has_value()); } - void OriginLastModifiedSince(const base::FilePath& kDbFile) { + void OriginLastModifiedBetween(const base::FilePath& kDbFile) { QuotaDatabase db(kDbFile); ASSERT_TRUE(db.LazyOpen(true)); std::set<url::Origin> origins; - EXPECT_TRUE(db.GetOriginsModifiedSince(kTemporary, &origins, base::Time())); + EXPECT_TRUE(db.GetOriginsModifiedBetween(kTemporary, &origins, base::Time(), + base::Time::Max())); EXPECT_TRUE(origins.empty()); const url::Origin kOrigin1 = url::Origin::Create(GURL("http://a/")); @@ -239,37 +240,58 @@ class QuotaDatabaseTest : public testing::Test { EXPECT_TRUE(db.SetOriginLastModifiedTime( kOrigin3, kTemporary, QuotaDatabase::TimeFromSqlValue(20))); - EXPECT_TRUE(db.GetOriginsModifiedSince(kTemporary, &origins, base::Time())); + EXPECT_TRUE(db.GetOriginsModifiedBetween(kTemporary, &origins, base::Time(), + base::Time::Max())); EXPECT_EQ(3U, origins.size()); EXPECT_EQ(1U, origins.count(kOrigin1)); EXPECT_EQ(1U, origins.count(kOrigin2)); EXPECT_EQ(1U, origins.count(kOrigin3)); - EXPECT_TRUE(db.GetOriginsModifiedSince(kTemporary, &origins, - QuotaDatabase::TimeFromSqlValue(5))); + EXPECT_TRUE(db.GetOriginsModifiedBetween(kTemporary, &origins, + QuotaDatabase::TimeFromSqlValue(5), + base::Time::Max())); EXPECT_EQ(2U, origins.size()); EXPECT_EQ(0U, origins.count(kOrigin1)); EXPECT_EQ(1U, origins.count(kOrigin2)); EXPECT_EQ(1U, origins.count(kOrigin3)); - EXPECT_TRUE(db.GetOriginsModifiedSince( - kTemporary, &origins, QuotaDatabase::TimeFromSqlValue(15))); + EXPECT_TRUE(db.GetOriginsModifiedBetween( + kTemporary, &origins, QuotaDatabase::TimeFromSqlValue(15), + base::Time::Max())); EXPECT_EQ(1U, origins.size()); EXPECT_EQ(0U, origins.count(kOrigin1)); EXPECT_EQ(0U, origins.count(kOrigin2)); EXPECT_EQ(1U, origins.count(kOrigin3)); - EXPECT_TRUE(db.GetOriginsModifiedSince( - kTemporary, &origins, QuotaDatabase::TimeFromSqlValue(25))); + EXPECT_TRUE(db.GetOriginsModifiedBetween( + kTemporary, &origins, QuotaDatabase::TimeFromSqlValue(25), + base::Time::Max())); EXPECT_TRUE(origins.empty()); + EXPECT_TRUE(db.GetOriginsModifiedBetween( + kTemporary, &origins, QuotaDatabase::TimeFromSqlValue(5), + QuotaDatabase::TimeFromSqlValue(15))); + EXPECT_EQ(1U, origins.size()); + EXPECT_EQ(0U, origins.count(kOrigin1)); + EXPECT_EQ(1U, origins.count(kOrigin2)); + EXPECT_EQ(0U, origins.count(kOrigin3)); + + EXPECT_TRUE(db.GetOriginsModifiedBetween( + kTemporary, &origins, QuotaDatabase::TimeFromSqlValue(0), + QuotaDatabase::TimeFromSqlValue(20))); + EXPECT_EQ(2U, origins.size()); + EXPECT_EQ(1U, origins.count(kOrigin1)); + EXPECT_EQ(1U, origins.count(kOrigin2)); + EXPECT_EQ(0U, origins.count(kOrigin3)); + // Update origin1's mod time but for persistent storage. EXPECT_TRUE(db.SetOriginLastModifiedTime( kOrigin1, kPersistent, QuotaDatabase::TimeFromSqlValue(30))); // Must have no effects on temporary origins info. - EXPECT_TRUE(db.GetOriginsModifiedSince(kTemporary, &origins, - QuotaDatabase::TimeFromSqlValue(5))); + EXPECT_TRUE(db.GetOriginsModifiedBetween(kTemporary, &origins, + QuotaDatabase::TimeFromSqlValue(5), + base::Time::Max())); EXPECT_EQ(2U, origins.size()); EXPECT_EQ(0U, origins.count(kOrigin1)); EXPECT_EQ(1U, origins.count(kOrigin2)); @@ -279,15 +301,17 @@ class QuotaDatabaseTest : public testing::Test { EXPECT_TRUE(db.SetOriginLastModifiedTime( kOrigin2, kPersistent, QuotaDatabase::TimeFromSqlValue(40))); - EXPECT_TRUE(db.GetOriginsModifiedSince( - kPersistent, &origins, QuotaDatabase::TimeFromSqlValue(25))); + EXPECT_TRUE(db.GetOriginsModifiedBetween( + kPersistent, &origins, QuotaDatabase::TimeFromSqlValue(25), + base::Time::Max())); EXPECT_EQ(2U, origins.size()); EXPECT_EQ(1U, origins.count(kOrigin1)); EXPECT_EQ(1U, origins.count(kOrigin2)); EXPECT_EQ(0U, origins.count(kOrigin3)); - EXPECT_TRUE(db.GetOriginsModifiedSince( - kPersistent, &origins, QuotaDatabase::TimeFromSqlValue(35))); + EXPECT_TRUE(db.GetOriginsModifiedBetween( + kPersistent, &origins, QuotaDatabase::TimeFromSqlValue(35), + base::Time::Max())); EXPECT_EQ(1U, origins.size()); EXPECT_EQ(0U, origins.count(kOrigin1)); EXPECT_EQ(1U, origins.count(kOrigin2)); @@ -614,12 +638,12 @@ TEST_F(QuotaDatabaseTest, OriginLastAccessTimeLRU) { OriginLastAccessTimeLRU(base::FilePath()); } -TEST_F(QuotaDatabaseTest, OriginLastModifiedSince) { +TEST_F(QuotaDatabaseTest, OriginLastModifiedBetween) { base::ScopedTempDir data_dir; ASSERT_TRUE(data_dir.CreateUniqueTempDir()); const base::FilePath kDbFile = data_dir.GetPath().AppendASCII(kDBFileName); - OriginLastModifiedSince(kDbFile); - OriginLastModifiedSince(base::FilePath()); + OriginLastModifiedBetween(kDbFile); + OriginLastModifiedBetween(base::FilePath()); } TEST_F(QuotaDatabaseTest, OriginLastEvicted) { diff --git a/chromium/storage/browser/quota/quota_features.cc b/chromium/storage/browser/quota/quota_features.cc index 241852ad512..5fe119cfd43 100644 --- a/chromium/storage/browser/quota/quota_features.cc +++ b/chromium/storage/browser/quota/quota_features.cc @@ -8,32 +8,16 @@ namespace storage { namespace features { -const base::Feature kQuotaExpandPoolSize{"QuotaExpandPoolSize", - base::FEATURE_ENABLED_BY_DEFAULT}; - -constexpr base::FeatureParam<double> kExperimentalPoolSizeRatio{ - &kQuotaExpandPoolSize, "PoolSizeRatio", 0.8}; - -constexpr base::FeatureParam<double> kPerHostRatio{&kQuotaExpandPoolSize, - "PerHostRatio", 0.75}; - -// QuotaUnlimitedPoolSize removes limitations around disk space consumption with -// respect to client-side storage web platform APIs. When enabled, quota will -// set no limit on how much space a single origin can consume, as well as -// removing limits on how much disk space the temporary pool can consume. -const base::Feature kQuotaUnlimitedPoolSize{"QuotaUnlimitedPoolSize", - base::FEATURE_DISABLED_BY_DEFAULT}; - // IncognitoDynamicQuota enables dynamic assignment of quota to incognito mode // based on the physical memory size and removes the fixed upper cap for it. const base::Feature kIncognitoDynamicQuota{"IncognitoDynamicQuota", - base::FEATURE_DISABLED_BY_DEFAULT}; + base::FEATURE_ENABLED_BY_DEFAULT}; // Dynamic quota for incognito mode would be set by a random fraction of // physical memory, between |IncognitoQuotaRatioLowerBound| and // |IncognitoQuotaRatioUpperBound|. constexpr base::FeatureParam<double> kIncognitoQuotaRatioLowerBound{ - &kIncognitoDynamicQuota, "IncognitoQuotaRatioLowerBound", 0.1}; + &kIncognitoDynamicQuota, "IncognitoQuotaRatioLowerBound", 0.15}; constexpr base::FeatureParam<double> kIncognitoQuotaRatioUpperBound{ &kIncognitoDynamicQuota, "IncognitoQuotaRatioUpperBound", 0.2}; diff --git a/chromium/storage/browser/quota/quota_features.h b/chromium/storage/browser/quota/quota_features.h index 1ddc03059b7..6364087068d 100644 --- a/chromium/storage/browser/quota/quota_features.h +++ b/chromium/storage/browser/quota/quota_features.h @@ -14,14 +14,6 @@ namespace storage { namespace features { COMPONENT_EXPORT(STORAGE_BROWSER) -extern const base::Feature kQuotaExpandPoolSize; -extern const base::FeatureParam<double> kExperimentalPoolSizeRatio; -extern const base::FeatureParam<double> kPerHostRatio; - -COMPONENT_EXPORT(STORAGE_BROWSER) -extern const base::Feature kQuotaUnlimitedPoolSize; - -COMPONENT_EXPORT(STORAGE_BROWSER) extern const base::Feature kIncognitoDynamicQuota; extern const base::FeatureParam<double> kIncognitoQuotaRatioLowerBound; extern const base::FeatureParam<double> kIncognitoQuotaRatioUpperBound; diff --git a/chromium/storage/browser/quota/quota_manager.cc b/chromium/storage/browser/quota/quota_manager.cc index fcee14568fe..8bb8ee893da 100644 --- a/chromium/storage/browser/quota/quota_manager.cc +++ b/chromium/storage/browser/quota/quota_manager.cc @@ -53,7 +53,7 @@ constexpr int64_t kReportHistogramInterval = 60 * 60 * 1000; // 1 hour // Take action on write errors if there is <= 2% disk space // available. -constexpr double kStoragePressureThresholdPercent = 2; +constexpr double kStoragePressureThresholdRatio = 0.02; // Limit how frequently QuotaManager polls for free disk space when // only using that information to identify storage pressure. @@ -774,22 +774,23 @@ class QuotaManager::StorageCleanupHelper : public QuotaTask { // time frame. // // This class is granted ownership of itself when it is passed to -// DidGetModifiedSince() via base::Owned(). When the closure for said function -// goes out of scope, the object is deleted. -// This is a thread-safe class. +// DidGetModifiedBetween() via base::Owned(). When the closure for said +// function goes out of scope, the object is deleted. This is a thread-safe +// class. class QuotaManager::GetModifiedSinceHelper { public: - bool GetModifiedSinceOnDBThread(StorageType type, - base::Time modified_since, - QuotaDatabase* database) { + bool GetModifiedBetweenOnDBThread(StorageType type, + base::Time begin, + base::Time end, + QuotaDatabase* database) { DCHECK(database); - return database->GetOriginsModifiedSince(type, &origins_, modified_since); + return database->GetOriginsModifiedBetween(type, &origins_, begin, end); } - void DidGetModifiedSince(const base::WeakPtr<QuotaManager>& manager, - GetOriginsCallback callback, - StorageType type, - bool success) { + void DidGetModifiedBetween(const base::WeakPtr<QuotaManager>& manager, + GetOriginsCallback callback, + StorageType type, + bool success) { if (!manager) { // The operation was aborted. std::move(callback).Run(std::set<url::Origin>(), type); @@ -1189,17 +1190,18 @@ bool QuotaManager::IsStorageUnlimited(const url::Origin& origin, special_storage_policy_->IsStorageUnlimited(origin.GetURL()); } -void QuotaManager::GetOriginsModifiedSince(StorageType type, - base::Time modified_since, - GetOriginsCallback callback) { +void QuotaManager::GetOriginsModifiedBetween(StorageType type, + base::Time begin, + base::Time end, + GetOriginsCallback callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); LazyInitialize(); GetModifiedSinceHelper* helper = new GetModifiedSinceHelper; PostTaskAndReplyWithResultForDBThread( FROM_HERE, - base::BindOnce(&GetModifiedSinceHelper::GetModifiedSinceOnDBThread, - base::Unretained(helper), type, modified_since), - base::BindOnce(&GetModifiedSinceHelper::DidGetModifiedSince, + base::BindOnce(&GetModifiedSinceHelper::GetModifiedBetweenOnDBThread, + base::Unretained(helper), type, begin, end), + base::BindOnce(&GetModifiedSinceHelper::DidGetModifiedBetween, base::Owned(helper), weak_factory_.GetWeakPtr(), std::move(callback), type)); } @@ -1485,7 +1487,7 @@ void QuotaManager::MaybeRunStoragePressureCallback(const url::Origin& origin, return; } - if (100 * available_space < kStoragePressureThresholdPercent * total_space) { + if (available_space < kStoragePressureThresholdRatio * total_space) { storage_pressure_callback_.Run(std::move(origin)); } } diff --git a/chromium/storage/browser/quota/quota_manager.h b/chromium/storage/browser/quota/quota_manager.h index 70e855de073..2748c4f7724 100644 --- a/chromium/storage/browser/quota/quota_manager.h +++ b/chromium/storage/browser/quota/quota_manager.h @@ -241,9 +241,10 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaManager bool IsStorageUnlimited(const url::Origin& origin, blink::mojom::StorageType type) const; - virtual void GetOriginsModifiedSince(blink::mojom::StorageType type, - base::Time modified_since, - GetOriginsCallback callback); + virtual void GetOriginsModifiedBetween(blink::mojom::StorageType type, + base::Time begin, + base::Time end, + GetOriginsCallback callback); bool ResetUsageTracker(blink::mojom::StorageType type); diff --git a/chromium/storage/browser/quota/quota_manager_proxy.cc b/chromium/storage/browser/quota/quota_manager_proxy.cc index 1cb5cd62b7a..f331f574255 100644 --- a/chromium/storage/browser/quota/quota_manager_proxy.cc +++ b/chromium/storage/browser/quota/quota_manager_proxy.cc @@ -41,11 +41,11 @@ void QuotaManagerProxy::RegisterClient( scoped_refptr<QuotaClient> client, QuotaClientType client_type, const std::vector<blink::mojom::StorageType>& storage_types) { - if (!io_thread_->BelongsToCurrentThread() && - io_thread_->PostTask( - FROM_HERE, - base::BindOnce(&QuotaManagerProxy::RegisterClient, this, - std::move(client), client_type, storage_types))) { + if (!io_thread_->BelongsToCurrentThread()) { + io_thread_->PostTask( + FROM_HERE, + base::BindOnce(&QuotaManagerProxy::RegisterClient, this, + std::move(client), client_type, storage_types)); return; } diff --git a/chromium/storage/browser/quota/quota_manager_unittest.cc b/chromium/storage/browser/quota/quota_manager_unittest.cc index 0b093668d80..9837c45e989 100644 --- a/chromium/storage/browser/quota/quota_manager_unittest.cc +++ b/chromium/storage/browser/quota/quota_manager_unittest.cc @@ -306,11 +306,13 @@ class QuotaManagerTest : public testing::Test { quota_manager_->NotifyOriginNoLongerInUse(origin); } - void GetOriginsModifiedSince(StorageType type, base::Time modified_since) { + void GetOriginsModifiedBetween(StorageType type, + base::Time begin, + base::Time end) { modified_origins_.clear(); modified_origins_type_ = StorageType::kUnknown; - quota_manager_->GetOriginsModifiedSince( - type, modified_since, + quota_manager_->GetOriginsModifiedBetween( + type, begin, end, base::BindOnce(&QuotaManagerTest::DidGetModifiedOrigins, weak_factory_.GetWeakPtr())); } @@ -2302,7 +2304,7 @@ TEST_F(QuotaManagerTest, GetLRUOriginWithOriginInUse) { EXPECT_EQ(ToOrigin("http://a.com/"), *eviction_origin()); } -TEST_F(QuotaManagerTest, GetOriginsModifiedSince) { +TEST_F(QuotaManagerTest, GetOriginsModifiedBetween) { static const MockOriginData kData[] = { { "http://a.com/", kTemp, 0 }, { "http://a.com:1/", kTemp, 0 }, @@ -2315,7 +2317,7 @@ TEST_F(QuotaManagerTest, GetOriginsModifiedSince) { {blink::mojom::StorageType::kTemporary, blink::mojom::StorageType::kPersistent}); - GetOriginsModifiedSince(kTemp, base::Time()); + GetOriginsModifiedBetween(kTemp, base::Time(), base::Time::Max()); task_environment_.RunUntilIdle(); EXPECT_TRUE(modified_origins().empty()); EXPECT_EQ(modified_origins_type(), kTemp); @@ -2329,7 +2331,7 @@ TEST_F(QuotaManagerTest, GetOriginsModifiedSince) { client->ModifyOriginAndNotify(ToOrigin("http://c.com/"), kTemp, 10); base::Time time3 = client->IncrementMockTime(); - GetOriginsModifiedSince(kTemp, time1); + GetOriginsModifiedBetween(kTemp, time1, base::Time::Max()); task_environment_.RunUntilIdle(); EXPECT_EQ(4U, modified_origins().size()); EXPECT_EQ(modified_origins_type(), kTemp); @@ -2338,18 +2340,18 @@ TEST_F(QuotaManagerTest, GetOriginsModifiedSince) { EXPECT_EQ(1U, modified_origins().count(ToOrigin(data.origin))); } - GetOriginsModifiedSince(kTemp, time2); + GetOriginsModifiedBetween(kTemp, time2, base::Time::Max()); task_environment_.RunUntilIdle(); EXPECT_EQ(2U, modified_origins().size()); - GetOriginsModifiedSince(kTemp, time3); + GetOriginsModifiedBetween(kTemp, time3, base::Time::Max()); task_environment_.RunUntilIdle(); EXPECT_TRUE(modified_origins().empty()); EXPECT_EQ(modified_origins_type(), kTemp); client->ModifyOriginAndNotify(ToOrigin("http://a.com/"), kTemp, 10); - GetOriginsModifiedSince(kTemp, time3); + GetOriginsModifiedBetween(kTemp, time3, base::Time::Max()); task_environment_.RunUntilIdle(); EXPECT_EQ(1U, modified_origins().size()); EXPECT_EQ(1U, modified_origins().count(ToOrigin("http://a.com/"))); diff --git a/chromium/storage/browser/quota/quota_settings.cc b/chromium/storage/browser/quota/quota_settings.cc index d5f67095528..27c280b3638 100644 --- a/chromium/storage/browser/quota/quota_settings.cc +++ b/chromium/storage/browser/quota/quota_settings.cc @@ -26,6 +26,8 @@ namespace { const int64_t kMBytes = 1024 * 1024; const int kRandomizedPercentage = 10; +const double kDefaultPerHostRatio = 0.75; +const double kDefaultPoolSizeRatio = 0.8; // Skews |value| by +/- |percent|. int64_t RandomizeByPercent(int64_t value, int percent) { @@ -74,10 +76,7 @@ base::Optional<QuotaSettings> CalculateNominalDynamicSettings( // The fraction of the device's storage the browser is willing to use for // temporary storage. - const double kTemporaryPoolSizeRatio = - base::FeatureList::IsEnabled(features::kQuotaUnlimitedPoolSize) - ? 1.0 - : features::kExperimentalPoolSizeRatio.Get(); + const double kTemporaryPoolSizeRatio = kDefaultPoolSizeRatio; // The amount of the device's storage the browser attempts to // keep free. If there is less than this amount of storage free @@ -111,10 +110,7 @@ base::Optional<QuotaSettings> CalculateNominalDynamicSettings( const double kMustRemainAvailableRatio = 0.01; // 1% // The fraction of the temporary pool that can be utilized by a single host. - const double kPerHostTemporaryRatio = - base::FeatureList::IsEnabled(features::kQuotaUnlimitedPoolSize) - ? 1.0 - : features::kPerHostRatio.Get(); + const double kPerHostTemporaryRatio = kDefaultPerHostRatio; // SessionOnly (or ephemeral) origins are allotted a fraction of what // normal origins are provided, and the amount is capped to a hard limit. diff --git a/chromium/storage/browser/quota/quota_settings_unittest.cc b/chromium/storage/browser/quota/quota_settings_unittest.cc index 34fb66ea801..25ae21522c9 100644 --- a/chromium/storage/browser/quota/quota_settings_unittest.cc +++ b/chromium/storage/browser/quota/quota_settings_unittest.cc @@ -140,48 +140,6 @@ TEST_F(QuotaSettingsTest, Default) { EXPECT_TRUE(callback_executed); } -TEST_F(QuotaSettingsTest, ExpandedTempPool) { - MockQuotaDeviceInfoHelper device_info_helper; - ON_CALL(device_info_helper, AmountOfTotalDiskSpace(_)) - .WillByDefault(::testing::Return(2000)); - scoped_feature_list_.InitAndEnableFeatureWithParameters( - features::kQuotaExpandPoolSize, - {{"PoolSizeRatio", "0.75"}, {"PerHostRatio", "0.5"}}); - - bool callback_executed = false; - GetNominalDynamicSettings( - profile_path(), false, &device_info_helper, - base::BindLambdaForTesting([&](base::Optional<QuotaSettings> settings) { - callback_executed = true; - ASSERT_NE(settings, base::nullopt); - // 1500 = 2000 * PoolSizeRatio - EXPECT_EQ(settings->pool_size, 1500); - // 750 = 1500 * PerHostRatio - EXPECT_EQ(settings->per_host_quota, 750); - })); - task_environment_.RunUntilIdle(); - EXPECT_TRUE(callback_executed); -} - -TEST_F(QuotaSettingsTest, UnlimitedTempPool) { - MockQuotaDeviceInfoHelper device_info_helper; - ON_CALL(device_info_helper, AmountOfTotalDiskSpace(_)) - .WillByDefault(::testing::Return(2000)); - scoped_feature_list_.InitAndEnableFeature(features::kQuotaUnlimitedPoolSize); - - bool callback_executed = false; - GetNominalDynamicSettings( - profile_path(), false, &device_info_helper, - base::BindLambdaForTesting([&](base::Optional<QuotaSettings> settings) { - callback_executed = true; - ASSERT_NE(settings, base::nullopt); - EXPECT_EQ(settings->pool_size, 2000); - EXPECT_EQ(settings->per_host_quota, 2000); - })); - task_environment_.RunUntilIdle(); - EXPECT_TRUE(callback_executed); -} - TEST_F(QuotaSettingsTest, IncognitoQuotaCapped) { MockQuotaDeviceInfoHelper device_info_helper; EXPECT_CALL(device_info_helper, AmountOfPhysicalMemory()).Times(1); |