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