| // Copyright 2018 The Chromium OS 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 "cryptohome/persistent_lookup_table.h" |
| |
| #include <base/files/file_util.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_split.h> |
| |
| namespace { |
| |
| // Helper function to create a file path, given a key directory |
| // |key_dir| and a version number of the file, |version|. |
| base::FilePath CreateFilePathForKey(const base::FilePath& key_dir, |
| uint32_t version) { |
| return key_dir.Append(std::to_string(version)).AddExtension("value"); |
| } |
| |
| } // namespace |
| |
| namespace cryptohome { |
| |
| PersistentLookupTable::PersistentLookupTable(Platform* platform, |
| base::FilePath basedir) |
| : platform_(platform), table_dir_(basedir) { |
| CHECK(platform_); |
| } |
| |
| bool PersistentLookupTable::GetValue(const uint64_t key, |
| std::vector<uint8_t>* value) { |
| uint32_t latest_version = FindLatestVersion(key); |
| |
| if (latest_version == 0) { |
| VLOG(1) << "No entry exists for this key: " << key; |
| return false; |
| } |
| |
| base::FilePath key_dir = table_dir_.Append(std::to_string(key)); |
| base::FilePath filepath = CreateFilePathForKey(key_dir, latest_version); |
| if (!platform_->ReadFile(filepath, value)) { |
| LOG(ERROR) << "Trouble reading file: " << filepath.value(); |
| return false; |
| } |
| |
| // If the key directory has been marked as deleted, we should |
| // return a |false|. |
| if (value->size() == 0) { |
| value->clear(); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool PersistentLookupTable::StoreValue(const uint64_t key, |
| const std::vector<uint8_t>& new_val) { |
| uint32_t latest_version = FindLatestVersion(key); |
| base::FilePath key_dir = table_dir_.Append(std::to_string(key)); |
| |
| // Key doesn't exist. |
| if (latest_version == 0) { |
| if (!platform_->CreateDirectory(key_dir)) { |
| PLOG(ERROR) << "Failed to create key dir: " << key_dir.value(); |
| return false; |
| } |
| } |
| |
| // Create new file version. |
| uint32_t new_version = latest_version + 1; |
| CHECK(new_version); |
| base::FilePath new_file = CreateFilePathForKey(key_dir, new_version); |
| |
| if (!platform_->WriteFileAtomic(new_file, new_val, 0644)) { |
| LOG(ERROR) << "Failed to create disk entry for file: " << new_file.value(); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool PersistentLookupTable::RemoveKey(const uint64_t key) { |
| uint32_t latest_version = FindLatestVersion(key); |
| |
| if (latest_version != 0) { |
| // Create new file version. |
| uint32_t new_version = latest_version + 1; |
| CHECK(new_version); |
| base::FilePath key_dir = table_dir_.Append(std::to_string(key)); |
| base::FilePath new_file = CreateFilePathForKey(key_dir, new_version); |
| |
| if (!platform_->TouchFileDurable(new_file)) { |
| LOG(ERROR) << "Failed to create disk entry for file: " |
| << new_file.value(); |
| // If we couldn't write the "bad" file, something is amiss and we |
| // should surface an error. |
| return false; |
| } |
| } |
| |
| // Delete the entire directory anyway. |
| DeleteOldKeyVersions(key, 0); |
| return true; |
| } |
| |
| bool PersistentLookupTable::KeyExists(const uint64_t key) { |
| return FindLatestVersion(key) != 0; |
| } |
| |
| bool PersistentLookupTable::InitOnBoot() { |
| if (!platform_->DirectoryExists(table_dir_)) { |
| VLOG(1) << "Lookup table dir not found, have to create it."; |
| if (!platform_->CreateDirectory(table_dir_)) { |
| PLOG(ERROR) << "Failed to create dir: " << table_dir_.value(); |
| return false; |
| } |
| } else { |
| // Remove all old key versions of all keys. |
| base::FileEnumerator file(table_dir_, false, |
| base::FileEnumerator::DIRECTORIES); |
| for (base::FilePath cur_dir = file.Next(); !cur_dir.empty(); |
| cur_dir = file.Next()) { |
| uint64_t key; |
| if (!base::StringToUint64(cur_dir.BaseName().value(), &key)) { |
| LOG(WARNING) << "Can't parse directory, skipping: " << cur_dir.value(); |
| continue; |
| } |
| uint32_t version = FindLatestVersion(key); |
| DeleteOldKeyVersions(key, version); |
| } |
| } |
| return true; |
| } |
| |
| uint32_t PersistentLookupTable::FindLatestVersion(const uint64_t key) { |
| base::FilePath key_dir = table_dir_.Append(std::to_string(key)); |
| if (!platform_->DirectoryExists(key_dir)) { |
| // No directory with this key, so return 0; |
| return 0; |
| } |
| |
| base::FileEnumerator file(key_dir, false, base::FileEnumerator::FILES, |
| FILE_PATH_LITERAL("*.value")); |
| uint32_t latest_version = 0; |
| for (base::FilePath cur_file = file.Next(); !cur_file.empty(); |
| cur_file = file.Next()) { |
| uint32_t cur_version; |
| // Get the version number. |
| if (!base::StringToUint(cur_file.BaseName().RemoveExtension().value(), |
| &cur_version)) { |
| // If the file name is corrupt, we should just skip it. |
| LOG(ERROR) << "File name is not of correct format." << cur_file.value(); |
| continue; |
| } |
| |
| if (cur_version > latest_version) { |
| // TODO(pmalani): Make sure the data in this file is verified. |
| latest_version = cur_version; |
| } |
| } |
| |
| return latest_version; |
| } |
| |
| void PersistentLookupTable::DeleteOldKeyVersions(const uint64_t key, |
| uint32_t version_to_save) { |
| base::FilePath key_dir = table_dir_.Append(std::to_string(key)); |
| if (!platform_->DirectoryExists(key_dir)) { |
| return; |
| } |
| |
| // Delete the entire directory. |
| if (version_to_save == 0) { |
| if (!platform_->DeleteFile(key_dir, true)) { |
| LOG(WARNING) << "Failed to delete dir: " << key_dir.value(); |
| } |
| return; |
| } |
| |
| base::FileEnumerator file(key_dir, false, base::FileEnumerator::FILES); |
| base::FilePath cur_file; |
| while (true) { |
| cur_file = file.Next(); |
| if (cur_file.empty()) { |
| break; |
| } |
| |
| // Ignore file for version |version_to_save|. |
| if (cur_file.BaseName().RemoveExtension().value() == |
| std::to_string(version_to_save)) { |
| continue; |
| } |
| |
| if (!platform_->DeleteFile(cur_file, false)) { |
| LOG(WARNING) << "Failed to delete file: " << cur_file.value(); |
| } |
| } |
| } |
| |
| } // namespace cryptohome |