// Copyright 2019 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 "sql/vfs_wrapper_fuchsia.h" #include "base/containers/fixed_flat_set.h" #include "base/containers/flat_map.h" #include "base/containers/flat_set.h" #include "base/logging.h" #include "base/no_destructor.h" #include "base/notreached.h" #include "base/synchronization/lock.h" #include "base/thread_annotations.h" #include "sql/vfs_wrapper.h" namespace sql { namespace { struct FileLock { int lock_level; // Used to track the pointers to different VfsFile instances that hold shared // locks on the same underlying file. The pointer is only used as a unique id // for the VfsFile instance. The contents are never accessed. base::flat_set readers = {}; // Used to track a VfsFile instance that holds a reserved/pending/exclusive // lock for writing. The pointer is only used as a unique id for the VfsFile // instance. The contents are never accessed. VfsFile* writer = nullptr; }; // Singleton that stores and mutates state as described in // https://www.sqlite.org/lockingv3.html class FuchsiaFileLockManager { public: FuchsiaFileLockManager() = default; // Returns lock manager for the current process. static FuchsiaFileLockManager* Instance() { static base::NoDestructor lock_manager; return lock_manager.get(); } int Lock(VfsFile* vfs_file, int requested_lock) { DCHECK_GT(requested_lock, SQLITE_LOCK_NONE) << "SQLITE_LOCK_NONE can only be set via Unlock"; base::AutoLock lock(lock_); const auto file_lock_state = GetFileLockStateLocked(vfs_file); // Allow any lock level since the lock isn't held. if (file_lock_state.readers.empty() && file_lock_state.writer == nullptr) { if (requested_lock == SQLITE_LOCK_SHARED) { locked_files_[vfs_file->file_name] = {.lock_level = requested_lock, .readers = {vfs_file}}; } else { locked_files_[vfs_file->file_name] = {.lock_level = requested_lock, .writer = vfs_file}; } return SQLITE_OK; } if (requested_lock == SQLITE_LOCK_SHARED) { if (file_lock_state.lock_level >= SQLITE_LOCK_PENDING) { DVLOG(1) << "lock for file " << vfs_file->file_name << " is held by a writer and cannot be shared."; return SQLITE_BUSY; } locked_files_[vfs_file->file_name].readers.insert(vfs_file); return SQLITE_OK; } if (file_lock_state.writer != nullptr && file_lock_state.writer != vfs_file) { DVLOG(1) << "lock for file " << vfs_file->file_name << " is already held by another writer."; return SQLITE_BUSY; } if (requested_lock == SQLITE_LOCK_EXCLUSIVE && (file_lock_state.readers.size() > 1 || (file_lock_state.readers.size() == 1 && !file_lock_state.readers.contains(vfs_file)))) { DVLOG(1) << "lock for file " << vfs_file->file_name << " is held by readers and can't yet be upgraded to exclusive."; return SQLITE_BUSY; } DCHECK(file_lock_state.writer == nullptr || file_lock_state.writer == vfs_file); locked_files_[vfs_file->file_name].lock_level = requested_lock; locked_files_[vfs_file->file_name].writer = vfs_file; locked_files_[vfs_file->file_name].readers.erase(vfs_file); DCHECK(locked_files_[vfs_file->file_name].lock_level < SQLITE_LOCK_EXCLUSIVE || locked_files_[vfs_file->file_name].readers.empty()); return SQLITE_OK; } int Unlock(VfsFile* vfs_file, int requested_lock) { base::AutoLock lock(lock_); const auto file_lock_state = GetFileLockStateLocked(vfs_file); DCHECK_LE(requested_lock, file_lock_state.lock_level) << "Attempted to unlock to a higher lock level, unlock can only " "decrement."; // Shortcut if the caller doesn't currently hold a lock. if (!file_lock_state.readers.contains(vfs_file) && file_lock_state.writer != vfs_file) { DVLOG(1) << "caller can't unlock because it doesn't currently " << "hold a lock for file " << vfs_file->file_name; return SQLITE_OK; } if (requested_lock == SQLITE_LOCK_NONE) { locked_files_[vfs_file->file_name].readers.erase(vfs_file); } else if (requested_lock == SQLITE_LOCK_SHARED) { locked_files_[vfs_file->file_name].readers.insert(vfs_file); } if (requested_lock < SQLITE_LOCK_RESERVED && file_lock_state.writer == vfs_file) { locked_files_[vfs_file->file_name].writer = nullptr; } // Check that `vfs_file` is correctly tracked given the `requested_lock`. DCHECK(requested_lock == SQLITE_LOCK_SHARED || !locked_files_[vfs_file->file_name].readers.contains(vfs_file)); DCHECK_EQ(requested_lock > SQLITE_LOCK_SHARED, locked_files_[vfs_file->file_name].writer == vfs_file); // Mark lock level as shared if there are only shared usages. if (!file_lock_state.readers.empty() && file_lock_state.writer == nullptr) { locked_files_[vfs_file->file_name].lock_level = SQLITE_LOCK_SHARED; return SQLITE_OK; } // Remove lock if there are no usages left. if (file_lock_state.readers.empty() && file_lock_state.writer == nullptr) { DCHECK_EQ(requested_lock, SQLITE_LOCK_NONE); locked_files_.erase(vfs_file->file_name); return SQLITE_OK; } if (file_lock_state.writer != vfs_file) { DCHECK_GE(file_lock_state.lock_level, SQLITE_LOCK_RESERVED); DCHECK_LE(requested_lock, SQLITE_LOCK_SHARED); return SQLITE_OK; } locked_files_[vfs_file->file_name].lock_level = requested_lock; return SQLITE_OK; } int CheckReservedLock(VfsFile* vfs_file, int* result) { base::AutoLock lock(lock_); const auto file_lock_state = GetFileLockStateLocked(vfs_file); switch (file_lock_state.lock_level) { case SQLITE_LOCK_NONE: case SQLITE_LOCK_SHARED: *result = 0; return SQLITE_OK; case SQLITE_LOCK_RESERVED: case SQLITE_LOCK_PENDING: case SQLITE_LOCK_EXCLUSIVE: *result = 1; return SQLITE_OK; default: return SQLITE_IOERR_CHECKRESERVEDLOCK; } } private: ~FuchsiaFileLockManager() = delete; const FileLock& GetFileLockStateLocked(VfsFile* vfs_file) EXCLUSIVE_LOCKS_REQUIRED(lock_) { static const FileLock kUnlockedFileLock = {.lock_level = SQLITE_LOCK_NONE}; const auto file_lock_state_iter = locked_files_.find(vfs_file->file_name); if (file_lock_state_iter == locked_files_.end()) { return kUnlockedFileLock; } return file_lock_state_iter->second; } base::Lock lock_; // Set of all currently locked files. base::flat_map locked_files_ GUARDED_BY(lock_); }; } // namespace int Lock(sqlite3_file* sqlite_file, int file_lock) { DCHECK(file_lock == SQLITE_LOCK_SHARED || file_lock == SQLITE_LOCK_RESERVED || file_lock == SQLITE_LOCK_PENDING || file_lock == SQLITE_LOCK_EXCLUSIVE); auto* vfs_file = reinterpret_cast(sqlite_file); return FuchsiaFileLockManager::Instance()->Lock(vfs_file, file_lock); } int Unlock(sqlite3_file* sqlite_file, int file_lock) { auto* vfs_file = reinterpret_cast(sqlite_file); return FuchsiaFileLockManager::Instance()->Unlock(vfs_file, file_lock); } int CheckReservedLock(sqlite3_file* sqlite_file, int* result) { auto* vfs_file = reinterpret_cast(sqlite_file); return FuchsiaFileLockManager::Instance()->CheckReservedLock(vfs_file, result); } } // namespace sql