1 //===-- llvm/Debuginfod/Debuginfod.cpp - Debuginfod client library --------===// 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 /// \file 10 /// 11 /// This file defines the fetchInfo function, which retrieves 12 /// any of the three supported artifact types: (executable, debuginfo, source 13 /// file) associated with a build-id from debuginfod servers. If a source file 14 /// is to be fetched, its absolute path must be specified in the Description 15 /// argument to fetchInfo. 16 /// 17 //===----------------------------------------------------------------------===// 18 19 #include "llvm/Debuginfod/Debuginfod.h" 20 #include "llvm/ADT/StringRef.h" 21 #include "llvm/Debuginfod/HTTPClient.h" 22 #include "llvm/Support/CachePruning.h" 23 #include "llvm/Support/Caching.h" 24 #include "llvm/Support/Errc.h" 25 #include "llvm/Support/Error.h" 26 #include "llvm/Support/FileUtilities.h" 27 #include "llvm/Support/Path.h" 28 #include "llvm/Support/xxhash.h" 29 30 namespace llvm { 31 static std::string uniqueKey(llvm::StringRef S) { return utostr(xxHash64(S)); } 32 33 // Returns a binary BuildID as a normalized hex string. 34 // Uses lowercase for compatibility with common debuginfod servers. 35 static std::string buildIDToString(BuildIDRef ID) { 36 return llvm::toHex(ID, /*LowerCase=*/true); 37 } 38 39 Expected<SmallVector<StringRef>> getDefaultDebuginfodUrls() { 40 const char *DebuginfodUrlsEnv = std::getenv("DEBUGINFOD_URLS"); 41 if (DebuginfodUrlsEnv == nullptr) 42 return SmallVector<StringRef>(); 43 44 SmallVector<StringRef> DebuginfodUrls; 45 StringRef(DebuginfodUrlsEnv).split(DebuginfodUrls, " "); 46 return DebuginfodUrls; 47 } 48 49 Expected<std::string> getDefaultDebuginfodCacheDirectory() { 50 if (const char *CacheDirectoryEnv = std::getenv("DEBUGINFOD_CACHE_PATH")) 51 return CacheDirectoryEnv; 52 53 SmallString<64> CacheDirectory; 54 if (!sys::path::cache_directory(CacheDirectory)) 55 return createStringError( 56 errc::io_error, "Unable to determine appropriate cache directory."); 57 sys::path::append(CacheDirectory, "llvm-debuginfod", "client"); 58 return std::string(CacheDirectory); 59 } 60 61 std::chrono::milliseconds getDefaultDebuginfodTimeout() { 62 long Timeout; 63 const char *DebuginfodTimeoutEnv = std::getenv("DEBUGINFOD_TIMEOUT"); 64 if (DebuginfodTimeoutEnv && 65 to_integer(StringRef(DebuginfodTimeoutEnv).trim(), Timeout, 10)) 66 return std::chrono::milliseconds(Timeout * 1000); 67 68 return std::chrono::milliseconds(90 * 1000); 69 } 70 71 /// The following functions fetch a debuginfod artifact to a file in a local 72 /// cache and return the cached file path. They first search the local cache, 73 /// followed by the debuginfod servers. 74 75 Expected<std::string> getCachedOrDownloadSource(BuildIDRef ID, 76 StringRef SourceFilePath) { 77 SmallString<64> UrlPath; 78 sys::path::append(UrlPath, sys::path::Style::posix, "buildid", 79 buildIDToString(ID), "source", 80 sys::path::convert_to_slash(SourceFilePath)); 81 return getCachedOrDownloadArtifact(uniqueKey(UrlPath), UrlPath); 82 } 83 84 Expected<std::string> getCachedOrDownloadExecutable(BuildIDRef ID) { 85 SmallString<64> UrlPath; 86 sys::path::append(UrlPath, sys::path::Style::posix, "buildid", 87 buildIDToString(ID), "executable"); 88 return getCachedOrDownloadArtifact(uniqueKey(UrlPath), UrlPath); 89 } 90 91 Expected<std::string> getCachedOrDownloadDebuginfo(BuildIDRef ID) { 92 SmallString<64> UrlPath; 93 sys::path::append(UrlPath, sys::path::Style::posix, "buildid", 94 buildIDToString(ID), "debuginfo"); 95 return getCachedOrDownloadArtifact(uniqueKey(UrlPath), UrlPath); 96 } 97 98 // General fetching function. 99 Expected<std::string> getCachedOrDownloadArtifact(StringRef UniqueKey, 100 StringRef UrlPath) { 101 SmallString<10> CacheDir; 102 103 Expected<std::string> CacheDirOrErr = getDefaultDebuginfodCacheDirectory(); 104 if (!CacheDirOrErr) 105 return CacheDirOrErr.takeError(); 106 CacheDir = *CacheDirOrErr; 107 108 Expected<SmallVector<StringRef>> DebuginfodUrlsOrErr = 109 getDefaultDebuginfodUrls(); 110 if (!DebuginfodUrlsOrErr) 111 return DebuginfodUrlsOrErr.takeError(); 112 SmallVector<StringRef> &DebuginfodUrls = *DebuginfodUrlsOrErr; 113 return getCachedOrDownloadArtifact(UniqueKey, UrlPath, CacheDir, 114 DebuginfodUrls, 115 getDefaultDebuginfodTimeout()); 116 } 117 118 namespace { 119 120 /// A simple handler which streams the returned data to a cache file. The cache 121 /// file is only created if a 200 OK status is observed. 122 class StreamedHTTPResponseHandler : public HTTPResponseHandler { 123 using CreateStreamFn = 124 std::function<Expected<std::unique_ptr<CachedFileStream>>()>; 125 CreateStreamFn CreateStream; 126 HTTPClient &Client; 127 std::unique_ptr<CachedFileStream> FileStream; 128 129 public: 130 StreamedHTTPResponseHandler(CreateStreamFn CreateStream, HTTPClient &Client) 131 : CreateStream(CreateStream), Client(Client) {} 132 virtual ~StreamedHTTPResponseHandler() = default; 133 134 Error handleBodyChunk(StringRef BodyChunk) override; 135 }; 136 137 } // namespace 138 139 Error StreamedHTTPResponseHandler::handleBodyChunk(StringRef BodyChunk) { 140 if (!FileStream) { 141 if (Client.responseCode() != 200) 142 return Error::success(); 143 Expected<std::unique_ptr<CachedFileStream>> FileStreamOrError = 144 CreateStream(); 145 if (!FileStreamOrError) 146 return FileStreamOrError.takeError(); 147 FileStream = std::move(*FileStreamOrError); 148 } 149 *FileStream->OS << BodyChunk; 150 return Error::success(); 151 } 152 153 Expected<std::string> getCachedOrDownloadArtifact( 154 StringRef UniqueKey, StringRef UrlPath, StringRef CacheDirectoryPath, 155 ArrayRef<StringRef> DebuginfodUrls, std::chrono::milliseconds Timeout) { 156 SmallString<64> AbsCachedArtifactPath; 157 sys::path::append(AbsCachedArtifactPath, CacheDirectoryPath, 158 "llvmcache-" + UniqueKey); 159 160 Expected<FileCache> CacheOrErr = 161 localCache("Debuginfod-client", ".debuginfod-client", CacheDirectoryPath); 162 if (!CacheOrErr) 163 return CacheOrErr.takeError(); 164 165 FileCache Cache = *CacheOrErr; 166 // We choose an arbitrary Task parameter as we do not make use of it. 167 unsigned Task = 0; 168 Expected<AddStreamFn> CacheAddStreamOrErr = Cache(Task, UniqueKey); 169 if (!CacheAddStreamOrErr) 170 return CacheAddStreamOrErr.takeError(); 171 AddStreamFn &CacheAddStream = *CacheAddStreamOrErr; 172 if (!CacheAddStream) 173 return std::string(AbsCachedArtifactPath); 174 // The artifact was not found in the local cache, query the debuginfod 175 // servers. 176 if (!HTTPClient::isAvailable()) 177 return createStringError(errc::io_error, 178 "No working HTTP client is available."); 179 180 if (!HTTPClient::IsInitialized) 181 return createStringError( 182 errc::io_error, 183 "A working HTTP client is available, but it is not initialized. To " 184 "allow Debuginfod to make HTTP requests, call HTTPClient::initialize() " 185 "at the beginning of main."); 186 187 HTTPClient Client; 188 Client.setTimeout(Timeout); 189 for (StringRef ServerUrl : DebuginfodUrls) { 190 SmallString<64> ArtifactUrl; 191 sys::path::append(ArtifactUrl, sys::path::Style::posix, ServerUrl, UrlPath); 192 193 // Perform the HTTP request and if successful, write the response body to 194 // the cache. 195 StreamedHTTPResponseHandler Handler([&]() { return CacheAddStream(Task); }, 196 Client); 197 HTTPRequest Request(ArtifactUrl); 198 Error Err = Client.perform(Request, Handler); 199 if (Err) 200 return std::move(Err); 201 202 if (Client.responseCode() != 200) 203 continue; 204 205 // Return the path to the artifact on disk. 206 return std::string(AbsCachedArtifactPath); 207 } 208 209 return createStringError(errc::argument_out_of_domain, "build id not found"); 210 } 211 } // namespace llvm 212