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