blob: 02dbf9a8d93d1373982687bc3a05c4d21d08adf7 [file] [log] [blame]
// Copyright 2026 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/sqlite_vfs/vfs_utils.h"
#include "base/check.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/numerics/clamped_math.h"
#include "components/sqlite_vfs/constants.h"
#include "components/sqlite_vfs/file_type.h"
#include "components/sqlite_vfs/metrics_util.h"
#include "components/sqlite_vfs/pending_file_set.h"
#include "components/sqlite_vfs/sandboxed_file.h"
#include "components/sqlite_vfs/sqlite_database_vfs_file_set.h"
#if BUILDFLAG(IS_WIN)
#include <windows.h>
#endif
namespace sqlite_vfs {
namespace {
// Returns a duplicate of `source_file` (at `source_file_path`) which has either
// read-only (`source_is_read_write` = false) or read-write (otherwise) access
// that, itself, has either read-only (`target_read_write` = false) or
// read-write access.
base::File DuplicateFile(const base::File& source_file,
const base::FilePath& source_file_path,
bool source_is_read_write,
bool target_read_write) {
CHECK(source_file.IsValid());
// Can't upgrade from read-only to read-write.
CHECK(!target_read_write || source_is_read_write);
if (source_is_read_write == target_read_write) {
// Caller requests the same rights. Simple duplication as-is.
return source_file.Duplicate();
}
#if BUILDFLAG(IS_WIN)
// Duplicate the handle to the file with restricted rights.
HANDLE handle = nullptr;
if (!::DuplicateHandle(
/*hSourceProcessHandle=*/::GetCurrentProcess(),
/*hSourceHandle=*/source_file.GetPlatformFile(),
/*hTargetProcessHandle=*/::GetCurrentProcess(),
/*lpTargetHandle=*/&handle,
/*dwDesiredAccess=*/FILE_GENERIC_READ,
/*bInheritHandle=*/FALSE,
/*dwOptions=*/0)) {
// Duplication failed; return an invalid File.
DWORD error = ::GetLastError();
return base::File(base::File::OSErrorToFileError(error));
}
return base::File(handle);
#else
// It's not possible to get a new file descriptor with reduced permissions to
// the same file description, so open the file anew with read-only access.
return base::File(source_file_path,
base::File::FLAG_OPEN | base::File::FLAG_READ);
#endif
}
} // namespace
std::optional<PendingFileSet> MakePendingFileSet(
Client client,
const base::FilePath& directory,
const base::FilePath& base_name,
bool single_connection,
bool journal_mode_wal) {
// Write-ahead logging journaling is only supported for single connections.
CHECK(!journal_mode_wal || single_connection);
PendingFileSet pending_file_set;
uint32_t create_flags = base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_READ |
base::File::FLAG_WRITE |
base::File::FLAG_WIN_SHARE_DELETE |
base::File::FLAG_CAN_DELETE_ON_CLOSE;
if (single_connection) {
// If only a single connection is allowed, there is no need to allow others
// to open the files for reading or writing. Delete is always allowed so
// that the files can be deleted even while in-use.
create_flags |= base::File::FLAG_WIN_EXCLUSIVE_READ |
base::File::FLAG_WIN_EXCLUSIVE_WRITE;
}
// Make sure handles to these files are safe to pass to untrusted processes.
create_flags = base::File::AddFlagsForPassingToUntrustedProcess(create_flags);
auto db_file_path =
directory.Append(base_name).AddExtension(kDbFileExtension);
pending_file_set.db_file = base::File(db_file_path, create_flags);
base::UmaHistogramExactLinear(
GetHistogramName(client, "CreateResult", FileType::kMainDb),
-pending_file_set.db_file.error_details(), -base::File::FILE_ERROR_MAX);
if (!pending_file_set.db_file.IsValid()) {
return std::nullopt;
}
auto journal_file_path =
directory.Append(base_name).AddExtension(kJournalFileExtension);
pending_file_set.journal_file = base::File(journal_file_path, create_flags);
base::UmaHistogramExactLinear(
GetHistogramName(client, "CreateResult", FileType::kMainJournal),
-pending_file_set.journal_file.error_details(),
-base::File::FILE_ERROR_MAX);
if (!pending_file_set.journal_file.IsValid()) {
return std::nullopt;
}
if (journal_mode_wal) {
auto wal_file_path =
directory.Append(base_name).AddExtension(kWalJournalFileExtension);
pending_file_set.wal_file = base::File(wal_file_path, create_flags);
base::UmaHistogramExactLinear(
GetHistogramName(client, "CreateResult", FileType::kWal),
-pending_file_set.wal_file.error_details(),
-base::File::FILE_ERROR_MAX);
if (!pending_file_set.wal_file.IsValid()) {
return std::nullopt;
}
}
if (!single_connection) {
// The shared lock is only needed if multiple connections are permitted.
pending_file_set.shared_lock =
base::UnsafeSharedMemoryRegion::Create(sizeof(SharedAtomicLock));
if (!pending_file_set.shared_lock.IsValid()) {
return std::nullopt;
}
}
pending_file_set.read_write = true;
return pending_file_set;
}
std::optional<PendingFileSet> ShareConnection(const base::FilePath& directory,
const base::FilePath& base_name,
const SqliteVfsFileSet& file_set,
bool read_write) {
// Cannot share a single-connection backend. If it ever becomes interesting to
// connect to a backend in one process and then move it to another process,
// we shall introduce a way to `Unbind()` a backend to convert it back into a
// `PendingFileSet`.
CHECK(!file_set.is_single_connection());
// All connections using a write-ahead log are single-connection.
CHECK(!file_set.wal_journal_mode());
PendingFileSet pending_file_set;
pending_file_set.db_file =
DuplicateFile(file_set.GetDbFile(),
directory.Append(base_name).AddExtension(kDbFileExtension),
!file_set.read_only(), read_write);
if (!pending_file_set.db_file.IsValid()) {
return std::nullopt;
}
pending_file_set.journal_file = DuplicateFile(
file_set.GetJournalFile(),
directory.Append(base_name).AddExtension(kJournalFileExtension),
!file_set.read_only(), read_write);
if (!pending_file_set.journal_file.IsValid()) {
return std::nullopt;
}
pending_file_set.shared_lock = file_set.GetSharedLock().Duplicate();
if (!pending_file_set.shared_lock.IsValid()) {
return std::nullopt;
}
pending_file_set.read_write = read_write;
return pending_file_set;
}
base::FilePath GetBaseName(const base::FilePath& file) {
return file.MatchesFinalExtension(kDbFileExtension)
? file.BaseName().RemoveFinalExtension()
: base::FilePath();
}
int64_t DeleteFiles(Client client,
const base::FilePath& directory,
const base::FilePath& base_name) {
auto file_path = directory.Append(base_name).AddExtension(kDbFileExtension);
int64_t bytes_recovered = base::GetFileSize(file_path).value_or(0);
base::File::Error delete_result = base::DeleteFile(file_path)
? base::File::FILE_OK
: base::File::GetLastFileError();
base::UmaHistogramExactLinear(
GetHistogramName(client, "DeleteResult", FileType::kMainDb),
-delete_result, -base::File::FILE_ERROR_MAX);
if (delete_result != base::File::FILE_OK) {
return 0;
}
file_path = directory.Append(base_name).AddExtension(kJournalFileExtension);
auto file_size = base::GetFileSize(file_path).value_or(0);
delete_result = base::DeleteFile(file_path) ? base::File::FILE_OK
: base::File::GetLastFileError();
base::UmaHistogramExactLinear(
GetHistogramName(client, "DeleteResult", FileType::kMainJournal),
-delete_result, -base::File::FILE_ERROR_MAX);
if (delete_result == base::File::FILE_OK) {
bytes_recovered = base::ClampAdd(bytes_recovered, file_size);
}
file_path =
directory.Append(base_name).AddExtension(kWalJournalFileExtension);
file_size = base::GetFileSize(file_path).value_or(0);
delete_result = base::DeleteFile(file_path) ? base::File::FILE_OK
: base::File::GetLastFileError();
base::UmaHistogramExactLinear(
GetHistogramName(client, "DeleteResult", FileType::kWal), -delete_result,
-base::File::FILE_ERROR_MAX);
if (delete_result == base::File::FILE_OK) {
bytes_recovered = base::ClampAdd(bytes_recovered, file_size);
}
// TODO (https://crbug.com/377475540): Cleanup when deletion of journal
// failed.
return bytes_recovered;
}
} // namespace sqlite_vfs