|  | // Copyright 2020 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 "tools/symbol-index/symbol_index.h" | 
|  |  | 
|  | #include <lib/syslog/cpp/macros.h> | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <filesystem> | 
|  | #include <fstream> | 
|  | #include <iostream> | 
|  | #include <string> | 
|  |  | 
|  | #include "src/lib/fxl/strings/split_string.h" | 
|  | #include "src/lib/fxl/strings/string_printf.h" | 
|  | #include "tools/symbol-index/reader.h" | 
|  |  | 
|  | namespace symbol_index { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // Canonical path with the following rules | 
|  | // 1. Allow non-existent components (as opposite to std::filesystem::canonical). | 
|  | // 2. Remove tailing "/" (as opposite to std::filesystem::weakly_canonical). | 
|  | std::string CanonicalPath(std::string path) { | 
|  | auto canonical = std::filesystem::absolute(path).lexically_normal(); | 
|  | if (canonical.filename().empty()) | 
|  | canonical = canonical.parent_path(); | 
|  | return canonical; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | std::string SymbolIndex::Entry::ToString() const { | 
|  | std::string str = symbol_path; | 
|  | if (!build_dir.empty()) | 
|  | str += "\t" + build_dir; | 
|  | return str; | 
|  | } | 
|  |  | 
|  | SymbolIndex::SymbolIndex(const std::string& path) { | 
|  | if (path.empty()) { | 
|  | char* home = std::getenv("HOME"); | 
|  | FX_CHECK(home); | 
|  | file_path_ = std::string(home) + "/.fuchsia/debug/symbol-index"; | 
|  | } else { | 
|  | file_path_ = path; | 
|  | } | 
|  | } | 
|  |  | 
|  | // TODO: Split out the parsing into something that takes an input stream for better testing. | 
|  | Error SymbolIndex::Load() { | 
|  | // Clears the entries_ first, in case Load gets called twice. | 
|  | entries_.clear(); | 
|  | std::error_code err; | 
|  |  | 
|  | // Non-existing files are not considered an error. | 
|  | if (!std::filesystem::exists(file_path_, err)) { | 
|  | return ""; | 
|  | } | 
|  |  | 
|  | std::ifstream file(file_path_); | 
|  | std::vector<std::vector<std::string>> entries; | 
|  | Reader reader('\t'); | 
|  |  | 
|  | if (Error err = reader.Read(file, file_path_, &entries); !err.empty()) { | 
|  | return err; | 
|  | } | 
|  |  | 
|  | for (const auto& entry : entries) { | 
|  | FX_CHECK(entry.size() >= 1); | 
|  |  | 
|  | std::string symbol_path = entry[0]; | 
|  | std::string build_dir; | 
|  |  | 
|  | if (entry.size() >= 2) { | 
|  | build_dir = entry[1]; | 
|  | } | 
|  |  | 
|  | // Both paths must be absolute. | 
|  | if (symbol_path.empty() || symbol_path[0] != '/' || | 
|  | (!build_dir.empty() && build_dir[0] != '/')) { | 
|  | FX_LOGS(ERROR) << "Invalid line in " << file_path_ << ": " << symbol_path; | 
|  | continue; | 
|  | } | 
|  |  | 
|  | entries_.emplace_back(symbol_path, build_dir); | 
|  | } | 
|  | return ""; | 
|  | } | 
|  |  | 
|  | bool SymbolIndex::Add(std::string symbol_path, std::string build_dir) { | 
|  | symbol_path = CanonicalPath(symbol_path); | 
|  | if (!build_dir.empty()) { | 
|  | build_dir = CanonicalPath(build_dir); | 
|  | } | 
|  | if (std::find_if(entries_.begin(), entries_.end(), [&symbol_path](Entry e) { | 
|  | return e.symbol_path == symbol_path; | 
|  | }) != entries_.end()) { | 
|  | return false; | 
|  | } | 
|  | entries_.emplace_back(symbol_path, build_dir); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | Error SymbolIndex::AddAll(const std::string& input_file) { | 
|  | std::vector<std::vector<std::string>> entries; | 
|  | Reader reader(' '); | 
|  | Error err; | 
|  | std::string prefix; | 
|  |  | 
|  | if (input_file.empty()) { | 
|  | err = reader.Read(std::cin, "stdin", &entries); | 
|  | } else { | 
|  | std::ifstream file(input_file); | 
|  | err = reader.Read(file, input_file, &entries); | 
|  | prefix = input_file + "/../"; | 
|  | } | 
|  |  | 
|  | if (!err.empty()) | 
|  | return err; | 
|  |  | 
|  | for (const auto& entry : entries) { | 
|  | FX_CHECK(entry.size() >= 1); | 
|  |  | 
|  | std::string symbol_path = prefix + entry[0]; | 
|  | std::string build_dir; | 
|  |  | 
|  | if (entry.size() >= 2) { | 
|  | build_dir = prefix + entry[1]; | 
|  | } | 
|  |  | 
|  | Add(symbol_path, build_dir); | 
|  | } | 
|  |  | 
|  | return ""; | 
|  | } | 
|  |  | 
|  | bool SymbolIndex::Remove(std::string symbol_path) { | 
|  | symbol_path = CanonicalPath(symbol_path); | 
|  | auto loc = std::find_if(entries_.begin(), entries_.end(), | 
|  | [&symbol_path](Entry e) { return e.symbol_path == symbol_path; }); | 
|  | if (loc == entries_.end()) { | 
|  | return false; | 
|  | } | 
|  | entries_.erase(loc); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | std::vector<SymbolIndex::Entry> SymbolIndex::Purge() { | 
|  | auto purge_begin = std::remove_if(entries_.begin(), entries_.end(), [](Entry e) { | 
|  | std::error_code err; | 
|  | // If the build_dir exists but symbol_path doesn't, we would assume that the symbol_path is | 
|  | // not generated yet and keep it in the index. | 
|  | if (!e.build_dir.empty()) | 
|  | return !std::filesystem::exists(e.build_dir, err); | 
|  | else | 
|  | return !std::filesystem::exists(e.symbol_path, err); | 
|  | }); | 
|  | std::vector<Entry> result(purge_begin, entries_.end()); | 
|  | entries_.erase(purge_begin, entries_.end()); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | Error SymbolIndex::Save() { | 
|  | std::error_code err; | 
|  | std::filesystem::create_directories(std::filesystem::path(file_path_).parent_path(), err); | 
|  | std::ofstream file(file_path_); | 
|  | if (file.fail()) { | 
|  | return fxl::StringPrintf("Cannot open %s to write", file_path_.c_str()); | 
|  | } | 
|  |  | 
|  | for (const auto& entry : entries_) { | 
|  | file << entry.ToString() << std::endl; | 
|  | } | 
|  | return ""; | 
|  | } | 
|  |  | 
|  | }  // namespace symbol_index |