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(kFSIllegalChars, 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(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(dir_path.GetPath(), true, fs::perms::owner_all); 71 } 72 73 FileSpec GetModuleDirectory(const FileSpec &root_dir_spec, const UUID &uuid) { 74 const auto modules_dir_spec = JoinPath(root_dir_spec, kModulesSubdir); 75 return JoinPath(modules_dir_spec, 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(LLDBLog::Modules); 85 UUID module_uuid; 86 { 87 auto module_sp = 88 std::make_shared<Module>(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(sysroot_module_path_spec.GetPath(), 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, module_uuid); 111 llvm::sys::fs::remove_directories(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(sysroot_module_path_spec.GetPath()); 122 123 FileSpec symfile_spec = GetSymbolFileSpec(sysroot_module_path_spec); 124 llvm::sys::fs::remove(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(JoinPath(root_dir_spec, hostname), 134 platform_module_spec.GetPath().c_str()); 135 if (FileSystem::Instance().Exists(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 FileSpec(sysroot_module_path_spec.GetDirectory().AsCString())); 144 if (error.Fail()) 145 return error; 146 147 return llvm::sys::fs::create_hard_link(local_module_spec.GetPath(), 148 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(root_dir_spec, kLockDirName); 156 error = MakeDirectory(lock_dir_spec); 157 if (error.Fail()) 158 return; 159 160 m_file_spec = JoinPath(lock_dir_spec, uuid.GetAsString().c_str()); 161 162 auto file = FileSystem::Instance().Open( 163 m_file_spec, 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(file.takeError()); 170 return; 171 } 172 173 m_lock = std::make_unique<lldb_private::LockFile>(m_file_up->GetDescriptor()); 174 error = m_lock->WriteLock(0, 1); 175 if (error.Fail()) 176 error = 177 Status::FromErrorStringWithFormatv("Failed to lock file: {0}", 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(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, module_spec.GetUUID()); 196 const auto module_file_path = 197 JoinPath(module_spec_dir, target_file.GetFilename().AsCString()); 198 199 const auto tmp_file_path = tmp_file.GetPath(); 200 const auto err_code = 201 llvm::sys::fs::rename(tmp_file_path, module_file_path.GetPath()); 202 if (err_code) 203 return Status::FromErrorStringWithFormat( 204 "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, target_file, module_file_path, true); 209 if (error.Fail()) 210 return Status::FromErrorStringWithFormat("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(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(find_it); 226 } 227 228 const auto module_spec_dir = 229 GetModuleDirectory(root_dir_spec, module_spec.GetUUID()); 230 const auto module_file_path = JoinPath( 231 module_spec_dir, module_spec.GetFileSpec().GetFilename().AsCString()); 232 233 if (!FileSystem::Instance().Exists(module_file_path)) 234 return Status::FromErrorStringWithFormat( 235 "Module %s not found", module_file_path.GetPath().c_str()); 236 if (FileSystem::Instance().GetByteSize(module_file_path) != 237 module_spec.GetObjectSize()) 238 return Status::FromErrorStringWithFormat( 239 "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 module_spec.GetFileSpec(), 245 module_file_path, false); 246 if (error.Fail()) 247 return Status::FromErrorStringWithFormat("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(cached_module_spec, cached_module_sp, 258 nullptr, nullptr, did_create_ptr, false); 259 if (error.Fail()) 260 return error; 261 262 FileSpec symfile_spec = GetSymbolFileSpec(cached_module_sp->GetFileSpec()); 263 if (FileSystem::Instance().Exists(symfile_spec)) 264 cached_module_sp->SetSymbolFileFileSpec(symfile_spec); 265 266 m_loaded_modules.insert( 267 std::make_pair(module_spec.GetUUID().GetAsString(), 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, module_spec.GetUUID()); 281 auto error = MakeDirectory(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 "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, 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(module_spec_dir, 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("Failed to download module: %s", 303 error.AsCString()); 304 305 // Put downloaded file into local module cache. 306 error = Put(root_dir_spec, escaped_hostname.c_str(), module_spec, 307 tmp_download_file_spec, module_spec.GetFileSpec()); 308 if (error.Fail()) 309 return Status::FromErrorStringWithFormat( 310 "Failed to put module into cache: %s", error.AsCString()); 311 312 tmp_file_remover.releaseFile(); 313 error = Get(root_dir_spec, 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(module_spec_dir, 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, escaped_hostname.c_str(), module_spec, 330 tmp_download_sym_file_spec, 331 GetSymbolFileSpec(module_spec.GetFileSpec())); 332 if (error.Fail()) 333 return Status::FromErrorStringWithFormat( 334 "Failed to put symbol file into cache: %s", error.AsCString()); 335 336 tmp_symfile_remover.releaseFile(); 337 338 FileSpec symfile_spec = GetSymbolFileSpec(cached_module_sp->GetFileSpec()); 339 cached_module_sp->SetSymbolFileFileSpec(symfile_spec); 340 return Status(); 341 } 342