| // Copyright 2016 The Fuchsia 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 "src/ledger/bin/storage/impl/ledger_storage_impl.h" |
| |
| #include <dirent.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/async/default.h> |
| #include <lib/fit/function.h> |
| |
| #include <iterator> |
| #include <set> |
| #include <string> |
| #include <vector> |
| |
| #include "src/ledger/bin/storage/impl/page_storage_impl.h" |
| #include "src/ledger/bin/storage/public/constants.h" |
| #include "src/ledger/lib/callback/scoped_callback.h" |
| #include "src/ledger/lib/callback/trace_callback.h" |
| #include "src/ledger/lib/files/scoped_tmp_dir.h" |
| #include "src/ledger/lib/logging/logging.h" |
| #include "third_party/abseil-cpp/absl/strings/escaping.h" |
| #include "third_party/abseil-cpp/absl/strings/string_view.h" |
| |
| namespace storage { |
| |
| namespace { |
| |
| constexpr absl::string_view kStagingDirName = "staging"; |
| |
| // Encodes opaque bytes in a way that is usable as a directory name. |
| std::string GetDirectoryName(absl::string_view bytes) { return absl::WebSafeBase64Escape(bytes); } |
| |
| // Decodes opaque bytes used as a directory names into an id. This is the |
| // opposite transformation of GetDirectoryName. |
| std::string GetId(absl::string_view bytes) { |
| std::string decoded; |
| bool result = absl::WebSafeBase64Unescape(bytes, &decoded); |
| LEDGER_DCHECK(result); |
| return decoded; |
| } |
| |
| } // namespace |
| |
| LedgerStorageImpl::LedgerStorageImpl(ledger::Environment* environment, |
| encryption::EncryptionService* encryption_service, |
| DbFactory* db_factory, ledger::DetachedPath content_dir, |
| CommitPruningPolicy policy, |
| clocks::DeviceIdManager* device_id_manager) |
| : environment_(environment), |
| encryption_service_(encryption_service), |
| db_factory_(db_factory), |
| storage_dir_(std::move(content_dir)), |
| staging_dir_(storage_dir_.SubPath(kStagingDirName)), |
| pruning_policy_(policy), |
| device_id_manager_(device_id_manager), |
| weak_factory_(this) {} |
| |
| LedgerStorageImpl::~LedgerStorageImpl() = default; |
| |
| Status LedgerStorageImpl::Init() { |
| if (!environment_->file_system()->CreateDirectory(storage_dir_)) { |
| LEDGER_LOG(ERROR) << "Failed to create the storage directory in " << storage_dir_.path(); |
| return Status::INTERNAL_ERROR; |
| } |
| return Status::OK; |
| } |
| |
| void LedgerStorageImpl::ListPages(fit::function<void(Status, std::set<PageId>)> callback) { |
| auto timed_callback = TRACE_CALLBACK(std::move(callback), "ledger", "ledger_storage_list_pages"); |
| std::set<PageId> page_ids; |
| std::vector<std::string> encoded_page_ids; |
| environment_->file_system()->GetDirectoryContents(storage_dir_, &encoded_page_ids); |
| for (const std::string& encoded_page_id : encoded_page_ids) { |
| if (encoded_page_id != kStagingDirName) { |
| page_ids.insert(GetId(encoded_page_id)); |
| } |
| } |
| timed_callback(Status::OK, std::move(page_ids)); |
| } |
| |
| void LedgerStorageImpl::CreatePageStorage( |
| PageId page_id, fit::function<void(Status, std::unique_ptr<PageStorage>)> callback) { |
| auto timed_callback = |
| TRACE_CALLBACK(std::move(callback), "ledger", "ledger_storage_create_page_storage"); |
| auto page_path = GetPathFor(page_id); |
| GetOrCreateDb(std::move(page_path), std::move(page_id), DbFactory::OnDbNotFound::CREATE, |
| std::move(timed_callback)); |
| } |
| |
| void LedgerStorageImpl::GetPageStorage( |
| PageId page_id, fit::function<void(Status, std::unique_ptr<PageStorage>)> callback) { |
| auto timed_callback = |
| TRACE_CALLBACK(std::move(callback), "ledger", "ledger_storage_get_page_storage"); |
| auto page_path = GetPathFor(page_id); |
| GetOrCreateDb(std::move(page_path), std::move(page_id), DbFactory::OnDbNotFound::RETURN, |
| std::move(timed_callback)); |
| } |
| |
| void LedgerStorageImpl::DeletePageStorage(PageIdView page_id, |
| fit::function<void(Status)> callback) { |
| auto timed_callback = |
| TRACE_CALLBACK(std::move(callback), "ledger", "ledger_storage_delete_page_storage"); |
| ledger::DetachedPath path = GetPathFor(page_id); |
| // |final_callback| will be called from the I/O loop and call the original |
| // |callback| in the main one. The main loop outlives the I/O one, so it's |
| // safe to capture environment_->dispatcher() here. |
| auto final_callback = [dispatcher = environment_->dispatcher(), |
| callback = std::move(timed_callback)](Status status) mutable { |
| // Call the callback in the main thread. |
| async::PostTask(dispatcher, [status, callback = std::move(callback)] { callback(status); }); |
| }; |
| |
| async::PostTask( |
| environment_->io_dispatcher(), |
| [file_system = environment_->file_system(), path = std::move(path), |
| staging_dir = staging_dir_, callback = std::move(final_callback)]() mutable { |
| if (!file_system->IsDirectory(path)) { |
| callback(Status::PAGE_NOT_FOUND); |
| return; |
| } |
| std::unique_ptr<ledger::ScopedTmpDir> tmp_directory = |
| file_system->CreateScopedTmpDir(staging_dir); |
| ledger::DetachedPath tmp_directory_path = tmp_directory->path(); |
| std::string destination = tmp_directory_path.path() + "/graveyard"; |
| |
| // <storage_dir_>/<base64(page)> becomes |
| // <storage_dir_>/staging/<random_temporary_name>/graveyard/<base64(page)> |
| if (file_system->Rename( |
| path, ledger::DetachedPath(tmp_directory_path.root_fd(), destination)) != 0) { |
| LEDGER_LOG(ERROR) << "Unable to move local page storage to " << destination << "."; |
| callback(Status::IO_ERROR); |
| return; |
| } |
| |
| if (!file_system->DeletePathRecursively( |
| ledger::DetachedPath(tmp_directory_path.root_fd(), destination))) { |
| LEDGER_LOG(ERROR) << "Unable to delete local staging storage at: " << destination; |
| callback(Status::IO_ERROR); |
| return; |
| } |
| callback(Status::OK); |
| }); |
| } |
| |
| void LedgerStorageImpl::InitializePageStorage( |
| PageId page_id, std::unique_ptr<Db> db, |
| fit::function<void(Status, std::unique_ptr<PageStorage>)> callback) { |
| auto storage = std::make_unique<PageStorageImpl>(environment_, encryption_service_, std::move(db), |
| std::move(page_id), pruning_policy_); |
| PageStorageImpl* storage_ptr = storage.get(); |
| storage_in_initialization_[storage_ptr] = std::move(storage); |
| storage_ptr->Init(device_id_manager_, [this, callback = std::move(callback), |
| storage_ptr](Status status) mutable { |
| std::unique_ptr<PageStorage> storage = std::move(storage_in_initialization_[storage_ptr]); |
| storage_in_initialization_.erase(storage_ptr); |
| |
| if (status != Status::OK) { |
| LEDGER_LOG(ERROR) << "Failed to initialize PageStorage. Status: " << status; |
| callback(status, nullptr); |
| return; |
| } |
| callback(Status::OK, std::move(storage)); |
| }); |
| } |
| |
| void LedgerStorageImpl::GetOrCreateDb( |
| ledger::DetachedPath path, PageId page_id, DbFactory::OnDbNotFound on_db_not_found, |
| fit::function<void(Status, std::unique_ptr<PageStorage>)> callback) { |
| db_factory_->GetOrCreateDb( |
| std::move(path), on_db_not_found, |
| ledger::MakeScoped(weak_factory_.GetWeakPtr(), |
| [this, page_id = std::move(page_id), callback = std::move(callback)]( |
| Status status, std::unique_ptr<Db> db) mutable { |
| if (status != Status::OK) { |
| callback(status, nullptr); |
| return; |
| } |
| InitializePageStorage(std::move(page_id), std::move(db), |
| std::move(callback)); |
| })); |
| } |
| |
| ledger::DetachedPath LedgerStorageImpl::GetPathFor(PageIdView page_id) { |
| LEDGER_DCHECK(!page_id.empty()); |
| return storage_dir_.SubPath(GetDirectoryName(page_id)); |
| } |
| |
| } // namespace storage |