| 1 | //===-- ModuleCache.cpp ---------------------------------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "lldb/Target/ModuleCache.h" |
| 10 | |
| 11 | #include "lldb/Core/Module.h" |
| 12 | #include "lldb/Core/ModuleList.h" |
| 13 | #include "lldb/Core/ModuleSpec.h" |
| 14 | #include "lldb/Host/File.h" |
| 15 | #include "lldb/Host/LockFile.h" |
| 16 | #include "lldb/Utility/LLDBLog.h" |
| 17 | #include "lldb/Utility/Log.h" |
| 18 | #include "llvm/Support/FileSystem.h" |
| 19 | #include "llvm/Support/FileUtilities.h" |
| 20 | |
| 21 | #include <cassert> |
| 22 | |
| 23 | #include <cstdio> |
| 24 | |
| 25 | using namespace lldb; |
| 26 | using namespace lldb_private; |
| 27 | |
| 28 | namespace { |
| 29 | |
| 30 | const char *kModulesSubdir = ".cache" ; |
| 31 | const char *kLockDirName = ".lock" ; |
| 32 | const char *kTempFileName = ".temp" ; |
| 33 | const char *kTempSymFileName = ".symtemp" ; |
| 34 | const char *kSymFileExtension = ".sym" ; |
| 35 | const char *kFSIllegalChars = "\\/:*?\"<>|" ; |
| 36 | |
| 37 | std::string GetEscapedHostname(const char *hostname) { |
| 38 | if (hostname == nullptr) |
| 39 | hostname = "unknown" ; |
| 40 | std::string result(hostname); |
| 41 | size_t size = result.size(); |
| 42 | for (size_t i = 0; i < size; ++i) { |
| 43 | if ((result[i] >= 1 && result[i] <= 31) || |
| 44 | strchr(s: kFSIllegalChars, c: result[i]) != nullptr) |
| 45 | result[i] = '_'; |
| 46 | } |
| 47 | return result; |
| 48 | } |
| 49 | |
| 50 | class ModuleLock { |
| 51 | private: |
| 52 | FileUP m_file_up; |
| 53 | std::unique_ptr<lldb_private::LockFile> m_lock; |
| 54 | FileSpec m_file_spec; |
| 55 | |
| 56 | public: |
| 57 | ModuleLock(const FileSpec &root_dir_spec, const UUID &uuid, Status &error); |
| 58 | void Delete(); |
| 59 | }; |
| 60 | |
| 61 | static FileSpec JoinPath(const FileSpec &path1, const char *path2) { |
| 62 | FileSpec result_spec(path1); |
| 63 | result_spec.AppendPathComponent(component: path2); |
| 64 | return result_spec; |
| 65 | } |
| 66 | |
| 67 | static Status MakeDirectory(const FileSpec &dir_path) { |
| 68 | namespace fs = llvm::sys::fs; |
| 69 | |
| 70 | return fs::create_directories(path: dir_path.GetPath(), IgnoreExisting: true, Perms: fs::perms::owner_all); |
| 71 | } |
| 72 | |
| 73 | FileSpec GetModuleDirectory(const FileSpec &root_dir_spec, const UUID &uuid) { |
| 74 | const auto modules_dir_spec = JoinPath(path1: root_dir_spec, path2: kModulesSubdir); |
| 75 | return JoinPath(path1: modules_dir_spec, path2: uuid.GetAsString().c_str()); |
| 76 | } |
| 77 | |
| 78 | FileSpec GetSymbolFileSpec(const FileSpec &module_file_spec) { |
| 79 | return FileSpec(module_file_spec.GetPath() + kSymFileExtension); |
| 80 | } |
| 81 | |
| 82 | void DeleteExistingModule(const FileSpec &root_dir_spec, |
| 83 | const FileSpec &sysroot_module_path_spec) { |
| 84 | Log *log = GetLog(mask: LLDBLog::Modules); |
| 85 | UUID module_uuid; |
| 86 | { |
| 87 | auto module_sp = |
| 88 | std::make_shared<Module>(args: ModuleSpec(sysroot_module_path_spec)); |
| 89 | module_uuid = module_sp->GetUUID(); |
| 90 | } |
| 91 | |
| 92 | if (!module_uuid.IsValid()) |
| 93 | return; |
| 94 | |
| 95 | Status error; |
| 96 | ModuleLock lock(root_dir_spec, module_uuid, error); |
| 97 | if (error.Fail()) { |
| 98 | LLDB_LOGF(log, "Failed to lock module %s: %s" , |
| 99 | module_uuid.GetAsString().c_str(), error.AsCString()); |
| 100 | } |
| 101 | |
| 102 | namespace fs = llvm::sys::fs; |
| 103 | fs::file_status st; |
| 104 | if (status(path: sysroot_module_path_spec.GetPath(), result&: st)) |
| 105 | return; |
| 106 | |
| 107 | if (st.getLinkCount() > 2) // module is referred by other hosts. |
| 108 | return; |
| 109 | |
| 110 | const auto module_spec_dir = GetModuleDirectory(root_dir_spec, uuid: module_uuid); |
| 111 | llvm::sys::fs::remove_directories(path: module_spec_dir.GetPath()); |
| 112 | lock.Delete(); |
| 113 | } |
| 114 | |
| 115 | void DecrementRefExistingModule(const FileSpec &root_dir_spec, |
| 116 | const FileSpec &sysroot_module_path_spec) { |
| 117 | // Remove $platform/.cache/$uuid folder if nobody else references it. |
| 118 | DeleteExistingModule(root_dir_spec, sysroot_module_path_spec); |
| 119 | |
| 120 | // Remove sysroot link. |
| 121 | llvm::sys::fs::remove(path: sysroot_module_path_spec.GetPath()); |
| 122 | |
| 123 | FileSpec symfile_spec = GetSymbolFileSpec(module_file_spec: sysroot_module_path_spec); |
| 124 | llvm::sys::fs::remove(path: symfile_spec.GetPath()); |
| 125 | } |
| 126 | |
| 127 | Status CreateHostSysRootModuleLink(const FileSpec &root_dir_spec, |
| 128 | const char *hostname, |
| 129 | const FileSpec &platform_module_spec, |
| 130 | const FileSpec &local_module_spec, |
| 131 | bool delete_existing) { |
| 132 | const auto sysroot_module_path_spec = |
| 133 | JoinPath(path1: JoinPath(path1: root_dir_spec, path2: hostname), |
| 134 | path2: platform_module_spec.GetPath().c_str()); |
| 135 | if (FileSystem::Instance().Exists(file_spec: sysroot_module_path_spec)) { |
| 136 | if (!delete_existing) |
| 137 | return Status(); |
| 138 | |
| 139 | DecrementRefExistingModule(root_dir_spec, sysroot_module_path_spec); |
| 140 | } |
| 141 | |
| 142 | Status error = MakeDirectory( |
| 143 | dir_path: FileSpec(sysroot_module_path_spec.GetDirectory().AsCString())); |
| 144 | if (error.Fail()) |
| 145 | return error; |
| 146 | |
| 147 | return llvm::sys::fs::create_hard_link(to: local_module_spec.GetPath(), |
| 148 | from: sysroot_module_path_spec.GetPath()); |
| 149 | } |
| 150 | |
| 151 | } // namespace |
| 152 | |
| 153 | ModuleLock::ModuleLock(const FileSpec &root_dir_spec, const UUID &uuid, |
| 154 | Status &error) { |
| 155 | const auto lock_dir_spec = JoinPath(path1: root_dir_spec, path2: kLockDirName); |
| 156 | error = MakeDirectory(dir_path: lock_dir_spec); |
| 157 | if (error.Fail()) |
| 158 | return; |
| 159 | |
| 160 | m_file_spec = JoinPath(path1: lock_dir_spec, path2: uuid.GetAsString().c_str()); |
| 161 | |
| 162 | auto file = FileSystem::Instance().Open( |
| 163 | file_spec: m_file_spec, options: File::eOpenOptionWriteOnly | File::eOpenOptionCanCreate | |
| 164 | File::eOpenOptionCloseOnExec); |
| 165 | if (file) |
| 166 | m_file_up = std::move(file.get()); |
| 167 | else { |
| 168 | m_file_up.reset(); |
| 169 | error = Status::FromError(error: file.takeError()); |
| 170 | return; |
| 171 | } |
| 172 | |
| 173 | m_lock = std::make_unique<lldb_private::LockFile>(args: m_file_up->GetDescriptor()); |
| 174 | error = m_lock->WriteLock(start: 0, len: 1); |
| 175 | if (error.Fail()) |
| 176 | error = |
| 177 | Status::FromErrorStringWithFormatv(format: "Failed to lock file: {0}" , args&: error); |
| 178 | } |
| 179 | |
| 180 | void ModuleLock::Delete() { |
| 181 | if (!m_file_up) |
| 182 | return; |
| 183 | |
| 184 | m_file_up->Close(); |
| 185 | m_file_up.reset(); |
| 186 | llvm::sys::fs::remove(path: m_file_spec.GetPath()); |
| 187 | } |
| 188 | |
| 189 | ///////////////////////////////////////////////////////////////////////// |
| 190 | |
| 191 | Status ModuleCache::Put(const FileSpec &root_dir_spec, const char *hostname, |
| 192 | const ModuleSpec &module_spec, const FileSpec &tmp_file, |
| 193 | const FileSpec &target_file) { |
| 194 | const auto module_spec_dir = |
| 195 | GetModuleDirectory(root_dir_spec, uuid: module_spec.GetUUID()); |
| 196 | const auto module_file_path = |
| 197 | JoinPath(path1: module_spec_dir, path2: target_file.GetFilename().AsCString()); |
| 198 | |
| 199 | const auto tmp_file_path = tmp_file.GetPath(); |
| 200 | const auto err_code = |
| 201 | llvm::sys::fs::rename(from: tmp_file_path, to: module_file_path.GetPath()); |
| 202 | if (err_code) |
| 203 | return Status::FromErrorStringWithFormat( |
| 204 | format: "Failed to rename file %s to %s: %s" , tmp_file_path.c_str(), |
| 205 | module_file_path.GetPath().c_str(), err_code.message().c_str()); |
| 206 | |
| 207 | const auto error = CreateHostSysRootModuleLink( |
| 208 | root_dir_spec, hostname, platform_module_spec: target_file, local_module_spec: module_file_path, delete_existing: true); |
| 209 | if (error.Fail()) |
| 210 | return Status::FromErrorStringWithFormat(format: "Failed to create link to %s: %s" , |
| 211 | module_file_path.GetPath().c_str(), |
| 212 | error.AsCString()); |
| 213 | return Status(); |
| 214 | } |
| 215 | |
| 216 | Status ModuleCache::Get(const FileSpec &root_dir_spec, const char *hostname, |
| 217 | const ModuleSpec &module_spec, |
| 218 | ModuleSP &cached_module_sp, bool *did_create_ptr) { |
| 219 | const auto find_it = |
| 220 | m_loaded_modules.find(x: module_spec.GetUUID().GetAsString()); |
| 221 | if (find_it != m_loaded_modules.end()) { |
| 222 | cached_module_sp = (*find_it).second.lock(); |
| 223 | if (cached_module_sp) |
| 224 | return Status(); |
| 225 | m_loaded_modules.erase(position: find_it); |
| 226 | } |
| 227 | |
| 228 | const auto module_spec_dir = |
| 229 | GetModuleDirectory(root_dir_spec, uuid: module_spec.GetUUID()); |
| 230 | const auto module_file_path = JoinPath( |
| 231 | path1: module_spec_dir, path2: module_spec.GetFileSpec().GetFilename().AsCString()); |
| 232 | |
| 233 | if (!FileSystem::Instance().Exists(file_spec: module_file_path)) |
| 234 | return Status::FromErrorStringWithFormat( |
| 235 | format: "Module %s not found" , module_file_path.GetPath().c_str()); |
| 236 | if (FileSystem::Instance().GetByteSize(file_spec: module_file_path) != |
| 237 | module_spec.GetObjectSize()) |
| 238 | return Status::FromErrorStringWithFormat( |
| 239 | format: "Module %s has invalid file size" , module_file_path.GetPath().c_str()); |
| 240 | |
| 241 | // We may have already cached module but downloaded from an another host - in |
| 242 | // this case let's create a link to it. |
| 243 | auto error = CreateHostSysRootModuleLink(root_dir_spec, hostname, |
| 244 | platform_module_spec: module_spec.GetFileSpec(), |
| 245 | local_module_spec: module_file_path, delete_existing: false); |
| 246 | if (error.Fail()) |
| 247 | return Status::FromErrorStringWithFormat(format: "Failed to create link to %s: %s" , |
| 248 | module_file_path.GetPath().c_str(), |
| 249 | error.AsCString()); |
| 250 | |
| 251 | auto cached_module_spec(module_spec); |
| 252 | cached_module_spec.GetUUID().Clear(); // Clear UUID since it may contain md5 |
| 253 | // content hash instead of real UUID. |
| 254 | cached_module_spec.GetFileSpec() = module_file_path; |
| 255 | cached_module_spec.GetPlatformFileSpec() = module_spec.GetFileSpec(); |
| 256 | |
| 257 | error = ModuleList::GetSharedModule(module_spec: cached_module_spec, module_sp&: cached_module_sp, |
| 258 | module_search_paths_ptr: nullptr, old_modules: nullptr, did_create_ptr, always_create: false); |
| 259 | if (error.Fail()) |
| 260 | return error; |
| 261 | |
| 262 | FileSpec symfile_spec = GetSymbolFileSpec(module_file_spec: cached_module_sp->GetFileSpec()); |
| 263 | if (FileSystem::Instance().Exists(file_spec: symfile_spec)) |
| 264 | cached_module_sp->SetSymbolFileFileSpec(symfile_spec); |
| 265 | |
| 266 | m_loaded_modules.insert( |
| 267 | x: std::make_pair(x: module_spec.GetUUID().GetAsString(), y&: cached_module_sp)); |
| 268 | |
| 269 | return Status(); |
| 270 | } |
| 271 | |
| 272 | Status ModuleCache::GetAndPut(const FileSpec &root_dir_spec, |
| 273 | const char *hostname, |
| 274 | const ModuleSpec &module_spec, |
| 275 | const ModuleDownloader &module_downloader, |
| 276 | const SymfileDownloader &symfile_downloader, |
| 277 | lldb::ModuleSP &cached_module_sp, |
| 278 | bool *did_create_ptr) { |
| 279 | const auto module_spec_dir = |
| 280 | GetModuleDirectory(root_dir_spec, uuid: module_spec.GetUUID()); |
| 281 | auto error = MakeDirectory(dir_path: module_spec_dir); |
| 282 | if (error.Fail()) |
| 283 | return error; |
| 284 | |
| 285 | ModuleLock lock(root_dir_spec, module_spec.GetUUID(), error); |
| 286 | if (error.Fail()) |
| 287 | return Status::FromErrorStringWithFormat( |
| 288 | format: "Failed to lock module %s: %s" , |
| 289 | module_spec.GetUUID().GetAsString().c_str(), error.AsCString()); |
| 290 | |
| 291 | const auto escaped_hostname(GetEscapedHostname(hostname)); |
| 292 | // Check local cache for a module. |
| 293 | error = Get(root_dir_spec, hostname: escaped_hostname.c_str(), module_spec, |
| 294 | cached_module_sp, did_create_ptr); |
| 295 | if (error.Success()) |
| 296 | return error; |
| 297 | |
| 298 | const auto tmp_download_file_spec = JoinPath(path1: module_spec_dir, path2: kTempFileName); |
| 299 | error = module_downloader(module_spec, tmp_download_file_spec); |
| 300 | llvm::FileRemover tmp_file_remover(tmp_download_file_spec.GetPath()); |
| 301 | if (error.Fail()) |
| 302 | return Status::FromErrorStringWithFormat(format: "Failed to download module: %s" , |
| 303 | error.AsCString()); |
| 304 | |
| 305 | // Put downloaded file into local module cache. |
| 306 | error = Put(root_dir_spec, hostname: escaped_hostname.c_str(), module_spec, |
| 307 | tmp_file: tmp_download_file_spec, target_file: module_spec.GetFileSpec()); |
| 308 | if (error.Fail()) |
| 309 | return Status::FromErrorStringWithFormat( |
| 310 | format: "Failed to put module into cache: %s" , error.AsCString()); |
| 311 | |
| 312 | tmp_file_remover.releaseFile(); |
| 313 | error = Get(root_dir_spec, hostname: escaped_hostname.c_str(), module_spec, |
| 314 | cached_module_sp, did_create_ptr); |
| 315 | if (error.Fail()) |
| 316 | return error; |
| 317 | |
| 318 | // Fetching a symbol file for the module |
| 319 | const auto tmp_download_sym_file_spec = |
| 320 | JoinPath(path1: module_spec_dir, path2: kTempSymFileName); |
| 321 | error = symfile_downloader(cached_module_sp, tmp_download_sym_file_spec); |
| 322 | llvm::FileRemover tmp_symfile_remover(tmp_download_sym_file_spec.GetPath()); |
| 323 | if (error.Fail()) |
| 324 | // Failed to download a symfile but fetching the module was successful. The |
| 325 | // module might contain the necessary symbols and the debugging is also |
| 326 | // possible without a symfile. |
| 327 | return Status(); |
| 328 | |
| 329 | error = Put(root_dir_spec, hostname: escaped_hostname.c_str(), module_spec, |
| 330 | tmp_file: tmp_download_sym_file_spec, |
| 331 | target_file: GetSymbolFileSpec(module_file_spec: module_spec.GetFileSpec())); |
| 332 | if (error.Fail()) |
| 333 | return Status::FromErrorStringWithFormat( |
| 334 | format: "Failed to put symbol file into cache: %s" , error.AsCString()); |
| 335 | |
| 336 | tmp_symfile_remover.releaseFile(); |
| 337 | |
| 338 | FileSpec symfile_spec = GetSymbolFileSpec(module_file_spec: cached_module_sp->GetFileSpec()); |
| 339 | cached_module_sp->SetSymbolFileFileSpec(symfile_spec); |
| 340 | return Status(); |
| 341 | } |
| 342 | |