0

storage: Add task to clean up MediaLicense.db from buckets

The MediaLicense database is no longer being used, and exists and takes
up client space in a number of buckets. This CL introduces a cleanup
that would go through every bucket, and if the path containing
MediaLicense database exists, we delete everything in that path.

Bug: 373898308
Change-Id: I3482eab291b081bea9c8c2001d6d23d6bd77ac76
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6088694
Reviewed-by: Evan Stade <estade@chromium.org>
Commit-Queue: Vikram Pasupathy <vpasupathy@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1410666}
This commit is contained in:
Vikram Pasupathy
2025-01-23 17:25:51 -08:00
committed by Chromium LUCI CQ
parent b6390ae27e
commit 423fe125f3
5 changed files with 204 additions and 0 deletions

@ -67,6 +67,10 @@ const char kBucketTable[] = "buckets";
// registered into the buckets table. Introduced 2022-05 (crrev.com/c/3594211).
const char kBucketsTableBootstrapped[] = "IsBucketsBootstrapped";
// Flag to not repeat MediaLicenseDatabase cleanup in all the bucket
// directories. Introduced 2025-01 (crrev.com/c/6088694).
const char kMediaLicenseDatabaseRemoved[] = "IsMediaLicenseDatabaseRemoved";
const int kCommitIntervalMs = 30000;
const base::Clock* g_clock_for_testing = nullptr;
@ -925,6 +929,28 @@ QuotaError QuotaDatabase::SetIsBootstrapped(bool bootstrap_flag) {
: QuotaError::kDatabaseError;
}
bool QuotaDatabase::IsMediaLicenseDatabaseRemoved() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (EnsureOpened() != QuotaError::kNone) {
return false;
}
int flag = 0;
return meta_table_->GetValue(kMediaLicenseDatabaseRemoved, &flag) && flag;
}
QuotaError QuotaDatabase::SetIsMediaLicenseDatabaseRemoved(bool removed_flag) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
QuotaError open_error = EnsureOpened();
if (open_error != QuotaError::kNone) {
return open_error;
}
return meta_table_->SetValue(kMediaLicenseDatabaseRemoved, removed_flag)
? QuotaError::kNone
: QuotaError::kDatabaseError;
}
bool QuotaDatabase::RecoverOrRaze(int error_code) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

@ -216,6 +216,12 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaDatabase {
bool IsBootstrapped();
QuotaError SetIsBootstrapped(bool bootstrap_flag);
// Returns false if SetIsMediaLicensedDatabaseRemoved() has never been called
// before, which means that Media License Databases still potentially exist
// within the bucket directories.
bool IsMediaLicenseDatabaseRemoved();
QuotaError SetIsMediaLicenseDatabaseRemoved(bool removed_flag);
// If the database has failed to open, this will attempt to reopen it.
// Otherwise, it will attempt to recover the database. If recovery is
// attempted but fails, the database will be razed. In all cases, this will

@ -66,6 +66,7 @@
#include "storage/browser/quota/quota_manager_proxy.h"
#include "storage/browser/quota/quota_override_handle.h"
#include "storage/browser/quota/quota_temporary_storage_evictor.h"
#include "storage/browser/quota/storage_directory_util.h"
#include "storage/browser/quota/usage_tracker.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/quota/quota_types.mojom-shared.h"
@ -1906,6 +1907,39 @@ void QuotaManagerImpl::EnsureDatabaseOpened() {
}
MaybeBootstrapDatabase();
MaybeRemoveMediaLicenseDatabases();
}
void QuotaManagerImpl::MaybeRemoveMediaLicenseDatabases() {
db_runner_->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&QuotaDatabase::IsMediaLicenseDatabaseRemoved,
base::Unretained(database_.get())),
base::BindOnce(&QuotaManagerImpl::DidGetMediaLicenseDatabaseRemovalFlag,
weak_factory_.GetWeakPtr()));
}
void QuotaManagerImpl::DidGetMediaLicenseDatabaseRemovalFlag(
bool is_media_license_database_removed) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!is_media_license_database_removed) {
RemoveMediaLicenseDatabases();
}
}
void QuotaManagerImpl::RemoveMediaLicenseDatabases() {
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&QuotaManagerImpl::DeleteMediaLicenseDatabase,
weak_factory_.GetWeakPtr()),
kMinutesAfterStartupToBeginMediaLicenseDatabaseDeletion);
PostTaskAndReplyWithResultForDBThread(
base::BindOnce([](QuotaDatabase* database) {
DCHECK(database);
return database->SetIsMediaLicenseDatabaseRemoved(true);
}),
base::DoNothing(), FROM_HERE);
}
void QuotaManagerImpl::MaybeBootstrapDatabase() {
@ -2004,6 +2038,13 @@ void QuotaManagerImpl::DidSetDatabaseBootstrapped(QuotaError error) {
base::BindOnce(&QuotaManagerImpl::StartEviction,
weak_factory_.GetWeakPtr()),
kMinutesAfterStartupToBeginEviction);
// Schedule the MediaLicenseDatabase deletion task.
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&QuotaManagerImpl::DeleteMediaLicenseDatabase,
weak_factory_.GetWeakPtr()),
kMinutesAfterStartupToBeginMediaLicenseDatabaseDeletion);
}
void QuotaManagerImpl::RunDatabaseCallbacks() {
@ -2289,6 +2330,39 @@ void QuotaManagerImpl::StartEviction() {
temporary_storage_evictor_->Start();
}
void QuotaManagerImpl::DeleteMediaLicenseDatabase() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
GetBucketsModifiedBetween(
StorageType::kTemporary, base::Time::Min(), base::Time::Max(),
base::BindOnce(&QuotaManagerImpl::DidGetBucketsForMediaLicenseDeletion,
weak_factory_.GetWeakPtr()));
}
void QuotaManagerImpl::DidGetBucketsForMediaLicenseDeletion(
const std::set<BucketLocator>& buckets) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::vector<base::FilePath> media_license_dir_paths;
for (const BucketLocator& bucket : buckets) {
if (bucket.storage_key.IsFirstPartyContext()) {
base::FilePath media_license_bucket_path = CreateClientBucketPath(
profile_path_, bucket, QuotaClientType::kMediaLicense);
media_license_dir_paths.push_back(media_license_bucket_path);
}
}
db_runner_->PostTask(FROM_HERE,
base::BindOnce(
[](std::vector<base::FilePath> file_paths) {
for (base::FilePath& path : file_paths) {
base::DeletePathRecursively(path);
}
},
std::move(media_license_dir_paths)));
}
void QuotaManagerImpl::DeleteBucketFromDatabase(
const BucketLocator& bucket,
bool commit_immediately,

@ -477,6 +477,12 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaManagerImpl
static constexpr base::TimeDelta kMinutesAfterStartupToBeginEviction =
base::Minutes(5);
// After 15 minutes from startup, go through the buckets to delete the
// MediaLicense database from all of the bucket directories.
static constexpr base::TimeDelta
kMinutesAfterStartupToBeginMediaLicenseDatabaseDeletion =
base::Minutes(15);
static constexpr int kThresholdOfErrorsToBeDenylisted = 3;
static constexpr char kDatabaseName[] = "QuotaManager";
@ -524,6 +530,8 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaManagerImpl
bool is_db_disabled_for_testing() { return db_disabled_; }
void DeleteMediaLicenseDatabaseForTesting() { DeleteMediaLicenseDatabase(); }
void AddObserver(
mojo::PendingRemote<storage::mojom::QuotaManagerObserver> observer);
@ -584,6 +592,16 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaManagerImpl
// manager by RegisterClient().
void EnsureDatabaseOpened();
// Removes Media License Databases only if it hasn't already happened.
void MaybeRemoveMediaLicenseDatabases();
// Methods to help with the removal of the Media License Databases, including
// retrieving the flag from the metadata and disabling the database if there
// is an error with the retrieval.
void RemoveMediaLicenseDatabases();
void DidGetMediaLicenseDatabaseRemovalFlag(
bool is_media_license_database_removed);
// Bootstraps only if it hasn't already happened.
void MaybeBootstrapDatabase();
// Bootstraps database with storage keys that may not have been registered.
@ -676,6 +694,11 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaManagerImpl
// user-facing dialog in Chrome.
void NotifyWriteFailed(const blink::StorageKey& storage_key);
// Methods for MediaLicenseDB logic.
void DeleteMediaLicenseDatabase();
void DidGetBucketsForMediaLicenseDeletion(
const std::set<BucketLocator>& buckets);
// Methods for eviction logic.
void StartEviction();
void DeleteBucketFromDatabase(

@ -48,6 +48,7 @@
#include "storage/browser/quota/quota_manager_impl.h"
#include "storage/browser/quota/quota_manager_proxy.h"
#include "storage/browser/quota/quota_override_handle.h"
#include "storage/browser/quota/storage_directory_util.h"
#include "storage/browser/test/mock_quota_client.h"
#include "storage/browser/test/mock_special_storage_policy.h"
#include "testing/gmock/include/gmock/gmock.h"
@ -3801,4 +3802,78 @@ TEST_F(QuotaManagerImplTest, StaticReportedQuota_Bucket) {
EXPECT_EQ(result.quota, storage_capacity.available_space);
}
}
TEST_F(QuotaManagerImplTest, DeleteMediaLicenseDb) {
auto clock = std::make_unique<base::SimpleTestClock>();
QuotaDatabase::SetClockForTesting(clock.get());
clock->SetNow(base::Time::Now());
// Create some buckets with MediaLicense.db files.
static const ClientBucketData kData[] = {
{"http://foo.com/", kDefaultBucketName, kTemp, 1},
{"http://bar.com/", kDefaultBucketName, kTemp, 1},
};
MockQuotaClient* client =
CreateAndRegisterClient(QuotaClientType::kFileSystem, {kTemp});
RegisterClientBucketData(client, kData);
// Create MediaLicense.db files in the bucket paths.
for (const auto& data : kData) {
ASSERT_OK_AND_ASSIGN(auto bucket, GetBucket(ToStorageKey(data.origin),
data.name, data.type));
auto media_license_path =
CreateClientBucketPath(data_dir_.GetPath(), bucket.ToBucketLocator(),
QuotaClientType::kMediaLicense);
base::CreateDirectory(media_license_path);
auto db_path =
media_license_path.Append(FILE_PATH_LITERAL("MediaLicense.db"));
std::string dummy_content = "dummy db content";
ASSERT_TRUE(base::WriteFile(db_path, dummy_content));
ASSERT_TRUE(base::PathExists(db_path));
}
// Run deletion on startup.
quota_manager_impl_->DeleteMediaLicenseDatabaseForTesting();
task_environment_.RunUntilIdle();
// Verify MediaLicense.db files are deleted.
for (const auto& data : kData) {
ASSERT_OK_AND_ASSIGN(auto bucket, GetBucket(ToStorageKey(data.origin),
data.name, data.type));
auto media_license_path =
CreateClientBucketPath(data_dir_.GetPath(), bucket.ToBucketLocator(),
QuotaClientType::kMediaLicense);
auto db_path =
media_license_path.Append(FILE_PATH_LITERAL("MediaLicense.db"));
EXPECT_FALSE(base::PathExists(db_path));
}
QuotaDatabase::SetClockForTesting(nullptr);
}
TEST_F(QuotaManagerImplTest, DeleteMediaLicenseDb_NonexistentFiles) {
// Test deletion works when no MediaLicense.db files exist.
static const ClientBucketData kData[] = {
{"http://foo.com/", kDefaultBucketName, kTemp, 1},
};
MockQuotaClient* client =
CreateAndRegisterClient(QuotaClientType::kFileSystem, {kTemp});
RegisterClientBucketData(client, kData);
ASSERT_OK_AND_ASSIGN(auto bucket, GetBucket(ToStorageKey(kData[0].origin),
kData[0].name, kData[0].type));
auto media_license_path =
CreateClientBucketPath(data_dir_.GetPath(), bucket.ToBucketLocator(),
QuotaClientType::kMediaLicense);
auto db_path =
media_license_path.Append(FILE_PATH_LITERAL("MediaLicense.db"));
ASSERT_FALSE(base::PathExists(db_path));
quota_manager_impl_->DeleteMediaLicenseDatabaseForTesting();
task_environment_.RunUntilIdle();
// Should complete without errors.
EXPECT_FALSE(base::PathExists(db_path));
}
} // namespace storage