| //===-- ModuleCache.cpp ---------------------------------------------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "lldb/Target/ModuleCache.h" | |
| #include "lldb/Core/Module.h" | |
| #include "lldb/Core/ModuleList.h" | |
| #include "lldb/Core/ModuleSpec.h" | |
| #include "lldb/Host/File.h" | |
| #include "lldb/Host/LockFile.h" | |
| #include "lldb/Utility/LLDBLog.h" | |
| #include "lldb/Utility/Log.h" | |
| #include "llvm/Support/FileSystem.h" | |
| #include "llvm/Support/FileUtilities.h" | |
| #include <cassert> | |
| #include <cstdio> | |
| using namespace lldb; | |
| using namespace lldb_private; | |
| namespace { | |
| const char *kModulesSubdir = ".cache"; | |
| const char *kLockDirName = ".lock"; | |
| const char *kTempFileName = ".temp"; | |
| const char *kTempSymFileName = ".symtemp"; | |
| const char *kSymFileExtension = ".sym"; | |
| const char *kFSIllegalChars = "\\/:*?\"<>|"; | |
| std::string GetEscapedHostname(const char *hostname) { | |
| if (hostname == nullptr) | |
| hostname = "unknown"; | |
| std::string result(hostname); | |
| size_t size = result.size(); | |
| for (size_t i = 0; i < size; ++i) { | |
| if ((result[i] >= 1 && result[i] <= 31) || | |
| strchr(kFSIllegalChars, result[i]) != nullptr) | |
| result[i] = '_'; | |
| } | |
| return result; | |
| } | |
| class ModuleLock { | |
| private: | |
| FileUP m_file_up; | |
| std::unique_ptr<lldb_private::LockFile> m_lock; | |
| FileSpec m_file_spec; | |
| public: | |
| ModuleLock(const FileSpec &root_dir_spec, const UUID &uuid, Status &error); | |
| void Delete(); | |
| }; | |
| static FileSpec JoinPath(const FileSpec &path1, llvm::StringRef path2) { | |
| FileSpec result_spec(path1); | |
| result_spec.AppendPathComponent(path2); | |
| return result_spec; | |
| } | |
| static Status MakeDirectory(const FileSpec &dir_path) { | |
| namespace fs = llvm::sys::fs; | |
| return fs::create_directories(dir_path.GetPath(), true, fs::perms::owner_all); | |
| } | |
| FileSpec GetModuleDirectory(const FileSpec &root_dir_spec, const UUID &uuid) { | |
| const auto modules_dir_spec = JoinPath(root_dir_spec, kModulesSubdir); | |
| return JoinPath(modules_dir_spec, uuid.GetAsString().c_str()); | |
| } | |
| FileSpec GetSymbolFileSpec(const FileSpec &module_file_spec) { | |
| return FileSpec(module_file_spec.GetPath() + kSymFileExtension); | |
| } | |
| void DeleteExistingModule(const FileSpec &root_dir_spec, | |
| const FileSpec &sysroot_module_path_spec) { | |
| Log *log = GetLog(LLDBLog::Modules); | |
| UUID module_uuid; | |
| { | |
| auto module_sp = | |
| std::make_shared<Module>(ModuleSpec(sysroot_module_path_spec)); | |
| module_uuid = module_sp->GetUUID(); | |
| } | |
| if (!module_uuid.IsValid()) | |
| return; | |
| Status error; | |
| ModuleLock lock(root_dir_spec, module_uuid, error); | |
| if (error.Fail()) { | |
| LLDB_LOGF(log, "Failed to lock module %s: %s", | |
| module_uuid.GetAsString().c_str(), error.AsCString()); | |
| } | |
| namespace fs = llvm::sys::fs; | |
| fs::file_status st; | |
| if (status(sysroot_module_path_spec.GetPath(), st)) | |
| return; | |
| if (st.getLinkCount() > 2) // module is referred by other hosts. | |
| return; | |
| const auto module_spec_dir = GetModuleDirectory(root_dir_spec, module_uuid); | |
| llvm::sys::fs::remove_directories(module_spec_dir.GetPath()); | |
| lock.Delete(); | |
| } | |
| void DecrementRefExistingModule(const FileSpec &root_dir_spec, | |
| const FileSpec &sysroot_module_path_spec) { | |
| // Remove $platform/.cache/$uuid folder if nobody else references it. | |
| DeleteExistingModule(root_dir_spec, sysroot_module_path_spec); | |
| // Remove sysroot link. | |
| llvm::sys::fs::remove(sysroot_module_path_spec.GetPath()); | |
| FileSpec symfile_spec = GetSymbolFileSpec(sysroot_module_path_spec); | |
| llvm::sys::fs::remove(symfile_spec.GetPath()); | |
| } | |
| Status CreateHostSysRootModuleLink(const FileSpec &root_dir_spec, | |
| const char *hostname, | |
| const FileSpec &platform_module_spec, | |
| const FileSpec &local_module_spec, | |
| bool delete_existing) { | |
| const auto sysroot_module_path_spec = | |
| JoinPath(JoinPath(root_dir_spec, hostname), | |
| platform_module_spec.GetPath().c_str()); | |
| if (FileSystem::Instance().Exists(sysroot_module_path_spec)) { | |
| if (!delete_existing) | |
| return Status(); | |
| DecrementRefExistingModule(root_dir_spec, sysroot_module_path_spec); | |
| } | |
| Status error = | |
| MakeDirectory(FileSpec(sysroot_module_path_spec.GetDirectory())); | |
| if (error.Fail()) | |
| return error; | |
| return llvm::sys::fs::create_hard_link(local_module_spec.GetPath(), | |
| sysroot_module_path_spec.GetPath()); | |
| } | |
| } // namespace | |
| ModuleLock::ModuleLock(const FileSpec &root_dir_spec, const UUID &uuid, | |
| Status &error) { | |
| const auto lock_dir_spec = JoinPath(root_dir_spec, kLockDirName); | |
| error = MakeDirectory(lock_dir_spec); | |
| if (error.Fail()) | |
| return; | |
| m_file_spec = JoinPath(lock_dir_spec, uuid.GetAsString().c_str()); | |
| auto file = FileSystem::Instance().Open( | |
| m_file_spec, File::eOpenOptionWriteOnly | File::eOpenOptionCanCreate | | |
| File::eOpenOptionCloseOnExec); | |
| if (file) | |
| m_file_up = std::move(file.get()); | |
| else { | |
| m_file_up.reset(); | |
| error = Status::FromError(file.takeError()); | |
| return; | |
| } | |
| m_lock = std::make_unique<lldb_private::LockFile>(m_file_up->GetDescriptor()); | |
| error = m_lock->WriteLock(0, 1); | |
| if (error.Fail()) | |
| error = | |
| Status::FromErrorStringWithFormatv("Failed to lock file: {0}", error); | |
| } | |
| void ModuleLock::Delete() { | |
| if (!m_file_up) | |
| return; | |
| m_file_up->Close(); | |
| m_file_up.reset(); | |
| llvm::sys::fs::remove(m_file_spec.GetPath()); | |
| } | |
| ///////////////////////////////////////////////////////////////////////// | |
| Status ModuleCache::Put(const FileSpec &root_dir_spec, const char *hostname, | |
| const ModuleSpec &module_spec, const FileSpec &tmp_file, | |
| const FileSpec &target_file) { | |
| const auto module_spec_dir = | |
| GetModuleDirectory(root_dir_spec, module_spec.GetUUID()); | |
| const auto module_file_path = | |
| JoinPath(module_spec_dir, target_file.GetFilename()); | |
| const auto tmp_file_path = tmp_file.GetPath(); | |
| const auto err_code = | |
| llvm::sys::fs::rename(tmp_file_path, module_file_path.GetPath()); | |
| if (err_code) | |
| return Status::FromErrorStringWithFormat( | |
| "Failed to rename file %s to %s: %s", tmp_file_path.c_str(), | |
| module_file_path.GetPath().c_str(), err_code.message().c_str()); | |
| const auto error = CreateHostSysRootModuleLink( | |
| root_dir_spec, hostname, target_file, module_file_path, true); | |
| if (error.Fail()) | |
| return Status::FromErrorStringWithFormat("Failed to create link to %s: %s", | |
| module_file_path.GetPath().c_str(), | |
| error.AsCString()); | |
| return Status(); | |
| } | |
| Status ModuleCache::Get(const FileSpec &root_dir_spec, const char *hostname, | |
| const ModuleSpec &module_spec, | |
| ModuleSP &cached_module_sp, bool *did_create_ptr) { | |
| const auto find_it = | |
| m_loaded_modules.find(module_spec.GetUUID().GetAsString()); | |
| if (find_it != m_loaded_modules.end()) { | |
| cached_module_sp = (*find_it).second.lock(); | |
| if (cached_module_sp) | |
| return Status(); | |
| m_loaded_modules.erase(find_it); | |
| } | |
| const auto module_spec_dir = | |
| GetModuleDirectory(root_dir_spec, module_spec.GetUUID()); | |
| const auto module_file_path = | |
| JoinPath(module_spec_dir, module_spec.GetFileSpec().GetFilename()); | |
| if (!FileSystem::Instance().Exists(module_file_path)) | |
| return Status::FromErrorStringWithFormat( | |
| "Module %s not found", module_file_path.GetPath().c_str()); | |
| if (FileSystem::Instance().GetByteSize(module_file_path) != | |
| module_spec.GetObjectSize()) | |
| return Status::FromErrorStringWithFormat( | |
| "Module %s has invalid file size", module_file_path.GetPath().c_str()); | |
| // We may have already cached module but downloaded from an another host - in | |
| // this case let's create a link to it. | |
| auto error = CreateHostSysRootModuleLink(root_dir_spec, hostname, | |
| module_spec.GetFileSpec(), | |
| module_file_path, false); | |
| if (error.Fail()) | |
| return Status::FromErrorStringWithFormat("Failed to create link to %s: %s", | |
| module_file_path.GetPath().c_str(), | |
| error.AsCString()); | |
| auto cached_module_spec(module_spec); | |
| cached_module_spec.GetUUID().Clear(); // Clear UUID since it may contain md5 | |
| // content hash instead of real UUID. | |
| cached_module_spec.GetFileSpec() = module_file_path; | |
| cached_module_spec.GetPlatformFileSpec() = module_spec.GetFileSpec(); | |
| error = ModuleList::GetSharedModule(cached_module_spec, cached_module_sp, | |
| nullptr, did_create_ptr, | |
| /*invoke_locate_callback=*/false); | |
| if (error.Fail()) | |
| return error; | |
| FileSpec symfile_spec = GetSymbolFileSpec(cached_module_sp->GetFileSpec()); | |
| if (FileSystem::Instance().Exists(symfile_spec)) | |
| cached_module_sp->SetSymbolFileFileSpec(symfile_spec); | |
| m_loaded_modules.insert( | |
| std::make_pair(module_spec.GetUUID().GetAsString(), cached_module_sp)); | |
| return Status(); | |
| } | |
| Status ModuleCache::GetAndPut(const FileSpec &root_dir_spec, | |
| const char *hostname, | |
| const ModuleSpec &module_spec, | |
| const ModuleDownloader &module_downloader, | |
| const SymfileDownloader &symfile_downloader, | |
| lldb::ModuleSP &cached_module_sp, | |
| bool *did_create_ptr) { | |
| const auto module_spec_dir = | |
| GetModuleDirectory(root_dir_spec, module_spec.GetUUID()); | |
| auto error = MakeDirectory(module_spec_dir); | |
| if (error.Fail()) | |
| return error; | |
| ModuleLock lock(root_dir_spec, module_spec.GetUUID(), error); | |
| if (error.Fail()) | |
| return Status::FromErrorStringWithFormat( | |
| "Failed to lock module %s: %s", | |
| module_spec.GetUUID().GetAsString().c_str(), error.AsCString()); | |
| const auto escaped_hostname(GetEscapedHostname(hostname)); | |
| // Check local cache for a module. | |
| error = Get(root_dir_spec, escaped_hostname.c_str(), module_spec, | |
| cached_module_sp, did_create_ptr); | |
| if (error.Success()) | |
| return error; | |
| const auto tmp_download_file_spec = JoinPath(module_spec_dir, kTempFileName); | |
| error = module_downloader(module_spec, tmp_download_file_spec); | |
| llvm::FileRemover tmp_file_remover(tmp_download_file_spec.GetPath()); | |
| if (error.Fail()) | |
| return Status::FromErrorStringWithFormat("Failed to download module: %s", | |
| error.AsCString()); | |
| // Put downloaded file into local module cache. | |
| error = Put(root_dir_spec, escaped_hostname.c_str(), module_spec, | |
| tmp_download_file_spec, module_spec.GetFileSpec()); | |
| if (error.Fail()) | |
| return Status::FromErrorStringWithFormat( | |
| "Failed to put module into cache: %s", error.AsCString()); | |
| tmp_file_remover.releaseFile(); | |
| error = Get(root_dir_spec, escaped_hostname.c_str(), module_spec, | |
| cached_module_sp, did_create_ptr); | |
| if (error.Fail()) | |
| return error; | |
| // Fetching a symbol file for the module | |
| const auto tmp_download_sym_file_spec = | |
| JoinPath(module_spec_dir, kTempSymFileName); | |
| error = symfile_downloader(cached_module_sp, tmp_download_sym_file_spec); | |
| llvm::FileRemover tmp_symfile_remover(tmp_download_sym_file_spec.GetPath()); | |
| if (error.Fail()) | |
| // Failed to download a symfile but fetching the module was successful. The | |
| // module might contain the necessary symbols and the debugging is also | |
| // possible without a symfile. | |
| return Status(); | |
| error = Put(root_dir_spec, escaped_hostname.c_str(), module_spec, | |
| tmp_download_sym_file_spec, | |
| GetSymbolFileSpec(module_spec.GetFileSpec())); | |
| if (error.Fail()) | |
| return Status::FromErrorStringWithFormat( | |
| "Failed to put symbol file into cache: %s", error.AsCString()); | |
| tmp_symfile_remover.releaseFile(); | |
| FileSpec symfile_spec = GetSymbolFileSpec(cached_module_sp->GetFileSpec()); | |
| cached_module_sp->SetSymbolFileFileSpec(symfile_spec); | |
| return Status(); | |
| } |