1 //===--------------------- ModuleCache.cpp ----------------------*- C++ -*-===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 10 #include "lldb/Target/ModuleCache.h" 11 12 #include "lldb/Core/Module.h" 13 #include "lldb/Core/ModuleList.h" 14 #include "lldb/Core/ModuleSpec.h" 15 #include "lldb/Host/File.h" 16 #include "lldb/Host/LockFile.h" 17 #include "lldb/Utility/Log.h" 18 #include "llvm/Support/FileSystem.h" 19 #include "llvm/Support/FileUtilities.h" 20 21 #include <assert.h> 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 File m_file; 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(GetLogIfAllCategoriesSet(LIBLLDB_LOG_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 if (log) 99 log->Printf("Failed to lock module %s: %s", 100 module_uuid.GetAsString().c_str(), error.AsCString()); 101 } 102 103 namespace fs = llvm::sys::fs; 104 fs::file_status st; 105 if (status(sysroot_module_path_spec.GetPath(), st)) 106 return; 107 108 if (st.getLinkCount() > 2) // module is referred by other hosts. 109 return; 110 111 const auto module_spec_dir = GetModuleDirectory(root_dir_spec, module_uuid); 112 llvm::sys::fs::remove_directories(module_spec_dir.GetPath()); 113 lock.Delete(); 114 } 115 116 void DecrementRefExistingModule(const FileSpec &root_dir_spec, 117 const FileSpec &sysroot_module_path_spec) { 118 // Remove $platform/.cache/$uuid folder if nobody else references it. 119 DeleteExistingModule(root_dir_spec, sysroot_module_path_spec); 120 121 // Remove sysroot link. 122 llvm::sys::fs::remove(sysroot_module_path_spec.GetPath()); 123 124 FileSpec symfile_spec = GetSymbolFileSpec(sysroot_module_path_spec); 125 llvm::sys::fs::remove(symfile_spec.GetPath()); 126 } 127 128 Status CreateHostSysRootModuleLink(const FileSpec &root_dir_spec, 129 const char *hostname, 130 const FileSpec &platform_module_spec, 131 const FileSpec &local_module_spec, 132 bool delete_existing) { 133 const auto sysroot_module_path_spec = 134 JoinPath(JoinPath(root_dir_spec, hostname), 135 platform_module_spec.GetPath().c_str()); 136 if (FileSystem::Instance().Exists(sysroot_module_path_spec)) { 137 if (!delete_existing) 138 return Status(); 139 140 DecrementRefExistingModule(root_dir_spec, sysroot_module_path_spec); 141 } 142 143 const auto error = MakeDirectory( 144 FileSpec(sysroot_module_path_spec.GetDirectory().AsCString())); 145 if (error.Fail()) 146 return error; 147 148 return llvm::sys::fs::create_hard_link(local_module_spec.GetPath(), 149 sysroot_module_path_spec.GetPath()); 150 } 151 152 } // namespace 153 154 ModuleLock::ModuleLock(const FileSpec &root_dir_spec, const UUID &uuid, 155 Status &error) { 156 const auto lock_dir_spec = JoinPath(root_dir_spec, kLockDirName); 157 error = MakeDirectory(lock_dir_spec); 158 if (error.Fail()) 159 return; 160 161 m_file_spec = JoinPath(lock_dir_spec, uuid.GetAsString().c_str()); 162 FileSystem::Instance().Open(m_file, m_file_spec, 163 File::eOpenOptionWrite | 164 File::eOpenOptionCanCreate | 165 File::eOpenOptionCloseOnExec); 166 if (!m_file) { 167 error.SetErrorToErrno(); 168 return; 169 } 170 171 m_lock.reset(new lldb_private::LockFile(m_file.GetDescriptor())); 172 error = m_lock->WriteLock(0, 1); 173 if (error.Fail()) 174 error.SetErrorStringWithFormat("Failed to lock file: %s", 175 error.AsCString()); 176 } 177 178 void ModuleLock::Delete() { 179 if (!m_file) 180 return; 181 182 m_file.Close(); 183 llvm::sys::fs::remove(m_file_spec.GetPath()); 184 } 185 186 ///////////////////////////////////////////////////////////////////////// 187 188 Status ModuleCache::Put(const FileSpec &root_dir_spec, const char *hostname, 189 const ModuleSpec &module_spec, const FileSpec &tmp_file, 190 const FileSpec &target_file) { 191 const auto module_spec_dir = 192 GetModuleDirectory(root_dir_spec, module_spec.GetUUID()); 193 const auto module_file_path = 194 JoinPath(module_spec_dir, target_file.GetFilename().AsCString()); 195 196 const auto tmp_file_path = tmp_file.GetPath(); 197 const auto err_code = 198 llvm::sys::fs::rename(tmp_file_path, module_file_path.GetPath()); 199 if (err_code) 200 return Status("Failed to rename file %s to %s: %s", tmp_file_path.c_str(), 201 module_file_path.GetPath().c_str(), 202 err_code.message().c_str()); 203 204 const auto error = CreateHostSysRootModuleLink( 205 root_dir_spec, hostname, target_file, module_file_path, true); 206 if (error.Fail()) 207 return Status("Failed to create link to %s: %s", 208 module_file_path.GetPath().c_str(), error.AsCString()); 209 return Status(); 210 } 211 212 Status ModuleCache::Get(const FileSpec &root_dir_spec, const char *hostname, 213 const ModuleSpec &module_spec, 214 ModuleSP &cached_module_sp, bool *did_create_ptr) { 215 const auto find_it = 216 m_loaded_modules.find(module_spec.GetUUID().GetAsString()); 217 if (find_it != m_loaded_modules.end()) { 218 cached_module_sp = (*find_it).second.lock(); 219 if (cached_module_sp) 220 return Status(); 221 m_loaded_modules.erase(find_it); 222 } 223 224 const auto module_spec_dir = 225 GetModuleDirectory(root_dir_spec, module_spec.GetUUID()); 226 const auto module_file_path = JoinPath( 227 module_spec_dir, module_spec.GetFileSpec().GetFilename().AsCString()); 228 229 if (!FileSystem::Instance().Exists(module_file_path)) 230 return Status("Module %s not found", module_file_path.GetPath().c_str()); 231 if (FileSystem::Instance().GetByteSize(module_file_path) != 232 module_spec.GetObjectSize()) 233 return Status("Module %s has invalid file size", 234 module_file_path.GetPath().c_str()); 235 236 // We may have already cached module but downloaded from an another host - in 237 // this case let's create a link to it. 238 auto error = CreateHostSysRootModuleLink(root_dir_spec, hostname, 239 module_spec.GetFileSpec(), 240 module_file_path, false); 241 if (error.Fail()) 242 return Status("Failed to create link to %s: %s", 243 module_file_path.GetPath().c_str(), error.AsCString()); 244 245 auto cached_module_spec(module_spec); 246 cached_module_spec.GetUUID().Clear(); // Clear UUID since it may contain md5 247 // content hash instead of real UUID. 248 cached_module_spec.GetFileSpec() = module_file_path; 249 cached_module_spec.GetPlatformFileSpec() = module_spec.GetFileSpec(); 250 251 error = ModuleList::GetSharedModule(cached_module_spec, cached_module_sp, 252 nullptr, nullptr, did_create_ptr, false); 253 if (error.Fail()) 254 return error; 255 256 FileSpec symfile_spec = GetSymbolFileSpec(cached_module_sp->GetFileSpec()); 257 if (FileSystem::Instance().Exists(symfile_spec)) 258 cached_module_sp->SetSymbolFileFileSpec(symfile_spec); 259 260 m_loaded_modules.insert( 261 std::make_pair(module_spec.GetUUID().GetAsString(), cached_module_sp)); 262 263 return Status(); 264 } 265 266 Status ModuleCache::GetAndPut(const FileSpec &root_dir_spec, 267 const char *hostname, 268 const ModuleSpec &module_spec, 269 const ModuleDownloader &module_downloader, 270 const SymfileDownloader &symfile_downloader, 271 lldb::ModuleSP &cached_module_sp, 272 bool *did_create_ptr) { 273 const auto module_spec_dir = 274 GetModuleDirectory(root_dir_spec, module_spec.GetUUID()); 275 auto error = MakeDirectory(module_spec_dir); 276 if (error.Fail()) 277 return error; 278 279 ModuleLock lock(root_dir_spec, module_spec.GetUUID(), error); 280 if (error.Fail()) 281 return Status("Failed to lock module %s: %s", 282 module_spec.GetUUID().GetAsString().c_str(), 283 error.AsCString()); 284 285 const auto escaped_hostname(GetEscapedHostname(hostname)); 286 // Check local cache for a module. 287 error = Get(root_dir_spec, escaped_hostname.c_str(), module_spec, 288 cached_module_sp, did_create_ptr); 289 if (error.Success()) 290 return error; 291 292 const auto tmp_download_file_spec = JoinPath(module_spec_dir, kTempFileName); 293 error = module_downloader(module_spec, tmp_download_file_spec); 294 llvm::FileRemover tmp_file_remover(tmp_download_file_spec.GetPath()); 295 if (error.Fail()) 296 return Status("Failed to download module: %s", error.AsCString()); 297 298 // Put downloaded file into local module cache. 299 error = Put(root_dir_spec, escaped_hostname.c_str(), module_spec, 300 tmp_download_file_spec, module_spec.GetFileSpec()); 301 if (error.Fail()) 302 return Status("Failed to put module into cache: %s", error.AsCString()); 303 304 tmp_file_remover.releaseFile(); 305 error = Get(root_dir_spec, escaped_hostname.c_str(), module_spec, 306 cached_module_sp, did_create_ptr); 307 if (error.Fail()) 308 return error; 309 310 // Fetching a symbol file for the module 311 const auto tmp_download_sym_file_spec = 312 JoinPath(module_spec_dir, kTempSymFileName); 313 error = symfile_downloader(cached_module_sp, tmp_download_sym_file_spec); 314 llvm::FileRemover tmp_symfile_remover(tmp_download_sym_file_spec.GetPath()); 315 if (error.Fail()) 316 // Failed to download a symfile but fetching the module was successful. The 317 // module might contain the necessary symbols and the debugging is also 318 // possible without a symfile. 319 return Status(); 320 321 error = Put(root_dir_spec, escaped_hostname.c_str(), module_spec, 322 tmp_download_sym_file_spec, 323 GetSymbolFileSpec(module_spec.GetFileSpec())); 324 if (error.Fail()) 325 return Status("Failed to put symbol file into cache: %s", 326 error.AsCString()); 327 328 tmp_symfile_remover.releaseFile(); 329 330 FileSpec symfile_spec = GetSymbolFileSpec(cached_module_sp->GetFileSpec()); 331 cached_module_sp->SetSymbolFileFileSpec(symfile_spec); 332 return Status(); 333 } 334