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 | const auto 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(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.SetErrorStringWithFormat("Failed to lock file: %s" , |
177 | error.AsCString()); |
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("Failed to rename file %s to %s: %s" , tmp_file_path.c_str(), |
204 | module_file_path.GetPath().c_str(), |
205 | 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("Failed to create link to %s: %s" , |
211 | module_file_path.GetPath().c_str(), error.AsCString()); |
212 | return Status(); |
213 | } |
214 | |
215 | Status ModuleCache::Get(const FileSpec &root_dir_spec, const char *hostname, |
216 | const ModuleSpec &module_spec, |
217 | ModuleSP &cached_module_sp, bool *did_create_ptr) { |
218 | const auto find_it = |
219 | m_loaded_modules.find(x: module_spec.GetUUID().GetAsString()); |
220 | if (find_it != m_loaded_modules.end()) { |
221 | cached_module_sp = (*find_it).second.lock(); |
222 | if (cached_module_sp) |
223 | return Status(); |
224 | m_loaded_modules.erase(position: find_it); |
225 | } |
226 | |
227 | const auto module_spec_dir = |
228 | GetModuleDirectory(root_dir_spec, uuid: module_spec.GetUUID()); |
229 | const auto module_file_path = JoinPath( |
230 | path1: module_spec_dir, path2: module_spec.GetFileSpec().GetFilename().AsCString()); |
231 | |
232 | if (!FileSystem::Instance().Exists(file_spec: module_file_path)) |
233 | return Status("Module %s not found" , module_file_path.GetPath().c_str()); |
234 | if (FileSystem::Instance().GetByteSize(file_spec: module_file_path) != |
235 | module_spec.GetObjectSize()) |
236 | return Status("Module %s has invalid file size" , |
237 | module_file_path.GetPath().c_str()); |
238 | |
239 | // We may have already cached module but downloaded from an another host - in |
240 | // this case let's create a link to it. |
241 | auto error = CreateHostSysRootModuleLink(root_dir_spec, hostname, |
242 | platform_module_spec: module_spec.GetFileSpec(), |
243 | local_module_spec: module_file_path, delete_existing: false); |
244 | if (error.Fail()) |
245 | return Status("Failed to create link to %s: %s" , |
246 | module_file_path.GetPath().c_str(), error.AsCString()); |
247 | |
248 | auto cached_module_spec(module_spec); |
249 | cached_module_spec.GetUUID().Clear(); // Clear UUID since it may contain md5 |
250 | // content hash instead of real UUID. |
251 | cached_module_spec.GetFileSpec() = module_file_path; |
252 | cached_module_spec.GetPlatformFileSpec() = module_spec.GetFileSpec(); |
253 | |
254 | error = ModuleList::GetSharedModule(module_spec: cached_module_spec, module_sp&: cached_module_sp, |
255 | module_search_paths_ptr: nullptr, old_modules: nullptr, did_create_ptr, always_create: false); |
256 | if (error.Fail()) |
257 | return error; |
258 | |
259 | FileSpec symfile_spec = GetSymbolFileSpec(module_file_spec: cached_module_sp->GetFileSpec()); |
260 | if (FileSystem::Instance().Exists(file_spec: symfile_spec)) |
261 | cached_module_sp->SetSymbolFileFileSpec(symfile_spec); |
262 | |
263 | m_loaded_modules.insert( |
264 | x: std::make_pair(x: module_spec.GetUUID().GetAsString(), y&: cached_module_sp)); |
265 | |
266 | return Status(); |
267 | } |
268 | |
269 | Status ModuleCache::GetAndPut(const FileSpec &root_dir_spec, |
270 | const char *hostname, |
271 | const ModuleSpec &module_spec, |
272 | const ModuleDownloader &module_downloader, |
273 | const SymfileDownloader &symfile_downloader, |
274 | lldb::ModuleSP &cached_module_sp, |
275 | bool *did_create_ptr) { |
276 | const auto module_spec_dir = |
277 | GetModuleDirectory(root_dir_spec, uuid: module_spec.GetUUID()); |
278 | auto error = MakeDirectory(dir_path: module_spec_dir); |
279 | if (error.Fail()) |
280 | return error; |
281 | |
282 | ModuleLock lock(root_dir_spec, module_spec.GetUUID(), error); |
283 | if (error.Fail()) |
284 | return Status("Failed to lock module %s: %s" , |
285 | module_spec.GetUUID().GetAsString().c_str(), |
286 | error.AsCString()); |
287 | |
288 | const auto escaped_hostname(GetEscapedHostname(hostname)); |
289 | // Check local cache for a module. |
290 | error = Get(root_dir_spec, hostname: escaped_hostname.c_str(), module_spec, |
291 | cached_module_sp, did_create_ptr); |
292 | if (error.Success()) |
293 | return error; |
294 | |
295 | const auto tmp_download_file_spec = JoinPath(path1: module_spec_dir, path2: kTempFileName); |
296 | error = module_downloader(module_spec, tmp_download_file_spec); |
297 | llvm::FileRemover tmp_file_remover(tmp_download_file_spec.GetPath()); |
298 | if (error.Fail()) |
299 | return Status("Failed to download module: %s" , error.AsCString()); |
300 | |
301 | // Put downloaded file into local module cache. |
302 | error = Put(root_dir_spec, hostname: escaped_hostname.c_str(), module_spec, |
303 | tmp_file: tmp_download_file_spec, target_file: module_spec.GetFileSpec()); |
304 | if (error.Fail()) |
305 | return Status("Failed to put module into cache: %s" , error.AsCString()); |
306 | |
307 | tmp_file_remover.releaseFile(); |
308 | error = Get(root_dir_spec, hostname: escaped_hostname.c_str(), module_spec, |
309 | cached_module_sp, did_create_ptr); |
310 | if (error.Fail()) |
311 | return error; |
312 | |
313 | // Fetching a symbol file for the module |
314 | const auto tmp_download_sym_file_spec = |
315 | JoinPath(path1: module_spec_dir, path2: kTempSymFileName); |
316 | error = symfile_downloader(cached_module_sp, tmp_download_sym_file_spec); |
317 | llvm::FileRemover tmp_symfile_remover(tmp_download_sym_file_spec.GetPath()); |
318 | if (error.Fail()) |
319 | // Failed to download a symfile but fetching the module was successful. The |
320 | // module might contain the necessary symbols and the debugging is also |
321 | // possible without a symfile. |
322 | return Status(); |
323 | |
324 | error = Put(root_dir_spec, hostname: escaped_hostname.c_str(), module_spec, |
325 | tmp_file: tmp_download_sym_file_spec, |
326 | target_file: GetSymbolFileSpec(module_file_spec: module_spec.GetFileSpec())); |
327 | if (error.Fail()) |
328 | return Status("Failed to put symbol file into cache: %s" , |
329 | error.AsCString()); |
330 | |
331 | tmp_symfile_remover.releaseFile(); |
332 | |
333 | FileSpec symfile_spec = GetSymbolFileSpec(module_file_spec: cached_module_sp->GetFileSpec()); |
334 | cached_module_sp->SetSymbolFileFileSpec(symfile_spec); |
335 | return Status(); |
336 | } |
337 | |