blob: 9ae51a6805e0889eb08a105ed46beba030f53c9f [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/viz/host/persistent_cache_sandboxed_file_factory.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "components/persistent_cache/sqlite/vfs/sandboxed_file.h"
namespace viz {
namespace {
PersistentCacheSandboxedFileFactory* g_instance = nullptr;
struct PersistentCacheFilePaths {
base::FilePath db_path;
base::FilePath journal_path;
};
std::string GetVersionSuffix(const std::string& product) {
// Use produce's version to differentiate the cache files.
// TODO(crbug.com/399642827): Use Dawn/ANGLE/Skia's versions which change less
// often. The product string may contain characters that are not valid in a
// filename, so it must be sanitized.
std::string version_suffix = product;
base::ReplaceChars(version_suffix, "/\\", "_", &version_suffix);
return version_suffix;
}
// Returns the paths to the cache database and journal files. The format is:
// <cache_dir>/<cache_id>/<version>/cache.db
// <cache_dir>/<cache_id>/<version>/cache.journal
PersistentCacheFilePaths GetPersistentCacheFilePaths(
const base::FilePath& cache_root_dir,
const base::FilePath::StringType& cache_id,
const std::string& product) {
base::FilePath version_dir =
cache_root_dir.Append(cache_id).AppendASCII(GetVersionSuffix(product));
return {version_dir.AppendASCII("cache.db"),
version_dir.AppendASCII("cache.journal")};
}
// Deletes all files in the cache directory that are associated with the given
// cache_id but are not the current database or journal file. This is to clean
// up stale cache files from previous runs or different product versions.
void DeleteStaleFiles(const base::FilePath& cache_root_dir,
const base::FilePath::StringType& cache_id,
const std::string& product) {
DCHECK(!cache_root_dir.empty());
const std::string version_suffix = GetVersionSuffix(product);
base::FilePath cache_dir = cache_root_dir.Append(cache_id);
if (!base::PathExists(cache_dir)) {
return;
}
base::FileEnumerator enumerator(cache_dir, false,
base::FileEnumerator::DIRECTORIES);
for (base::FilePath name = enumerator.Next(); !name.empty();
name = enumerator.Next()) {
if (name.BaseName().MaybeAsASCII() != version_suffix) {
base::DeletePathRecursively(name);
}
}
}
bool CreateCacheDirectory(const base::FilePath& cache_dir) {
if (!base::CreateDirectory(cache_dir)) {
LOG(ERROR) << "Failed to create cache directory: " << cache_dir;
return false;
}
return true;
}
} // namespace
/* static */
void PersistentCacheSandboxedFileFactory::CreateInstance(
const base::FilePath& cache_root_dir) {
DCHECK(!g_instance);
g_instance = new PersistentCacheSandboxedFileFactory(
cache_root_dir,
/*task_runner=*/base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN}));
g_instance->AddRef();
}
/* static */
PersistentCacheSandboxedFileFactory*
PersistentCacheSandboxedFileFactory::GetInstance() {
return g_instance;
}
PersistentCacheSandboxedFileFactory::PersistentCacheSandboxedFileFactory(
const base::FilePath& cache_root_dir,
scoped_refptr<base::SequencedTaskRunner> background_task_runner)
: cache_root_dir_(cache_root_dir),
background_task_runner_(std::move(background_task_runner)) {
CHECK(!cache_root_dir_.empty());
background_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
[](const base::FilePath& dir) { CreateCacheDirectory(dir); },
cache_root_dir_));
}
PersistentCacheSandboxedFileFactory::~PersistentCacheSandboxedFileFactory() =
default;
std::optional<PersistentCacheSandboxedFiles>
PersistentCacheSandboxedFileFactory::CreateFiles(const CacheIdString& cache_id,
const std::string& product) {
background_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&DeleteStaleFiles, cache_root_dir_, cache_id, product));
DCHECK(!cache_root_dir_.empty());
auto paths = GetPersistentCacheFilePaths(cache_root_dir_, cache_id, product);
DCHECK_EQ(paths.db_path.DirName(), paths.journal_path.DirName());
if (!CreateCacheDirectory(paths.db_path.DirName())) {
return std::nullopt;
}
auto open_and_check_file = [](const base::FilePath& path) {
const auto flags = base::File::AddFlagsForPassingToUntrustedProcess(
base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_READ |
base::File::FLAG_WRITE);
base::File file(path, flags);
if (!file.IsValid()) {
LOG(ERROR) << "Failed to open persistent cache file: " << path
<< " error: "
<< base::File::ErrorToString(file.error_details());
}
return file;
};
base::File db_file = open_and_check_file(paths.db_path);
if (!db_file.IsValid()) {
return std::nullopt;
}
base::File journal_file = open_and_check_file(paths.journal_path);
if (!journal_file.IsValid()) {
return std::nullopt;
}
base::UnsafeSharedMemoryRegion shared_lock =
base::UnsafeSharedMemoryRegion::Create(
sizeof(persistent_cache::LockState));
if (!shared_lock.IsValid()) {
LOG(ERROR) << "Failed to create shared lock";
return std::nullopt;
}
return PersistentCacheSandboxedFiles{
std::move(db_file), std::move(journal_file), std::move(shared_lock)};
}
void PersistentCacheSandboxedFileFactory::CreateFilesAsync(
const CacheIdString& cache_id,
const std::string& product,
CreateFilesCallback callback) {
// The reply will be posted to the current SequencedTaskRunner.
background_task_runner_->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&PersistentCacheSandboxedFileFactory::CreateFiles, this,
cache_id, product),
std::move(callback));
}
bool PersistentCacheSandboxedFileFactory::ClearFiles(
const CacheIdString& cache_id,
const std::string& product) {
DCHECK(!cache_root_dir_.empty());
auto paths = GetPersistentCacheFilePaths(cache_root_dir_, cache_id, product);
// Delete the whole version directory.
DCHECK_EQ(paths.db_path.DirName(), paths.journal_path.DirName());
return base::DeletePathRecursively(paths.db_path.DirName());
}
void PersistentCacheSandboxedFileFactory::ClearFilesAsync(
const CacheIdString& cache_id,
const std::string& product,
ClearFilesCallback callback) {
// The reply will be posted to the current SequencedTaskRunner.
background_task_runner_->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&PersistentCacheSandboxedFileFactory::ClearFiles, this,
cache_id, product),
std::move(callback));
}
} // namespace viz