212 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			212 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			C++
		
	
	
	
| //===-- llvm/Debuginfod/Debuginfod.cpp - Debuginfod client library --------===//
 | |
| //
 | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 | |
| // See https://llvm.org/LICENSE.txt for license information.
 | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 | |
| //
 | |
| //===----------------------------------------------------------------------===//
 | |
| ///
 | |
| /// \file
 | |
| ///
 | |
| /// This file defines the fetchInfo function, which retrieves
 | |
| /// any of the three supported artifact types: (executable, debuginfo, source
 | |
| /// file) associated with a build-id from debuginfod servers. If a source file
 | |
| /// is to be fetched, its absolute path must be specified in the Description
 | |
| /// argument to fetchInfo.
 | |
| ///
 | |
| //===----------------------------------------------------------------------===//
 | |
| 
 | |
| #include "llvm/Debuginfod/Debuginfod.h"
 | |
| #include "llvm/ADT/StringRef.h"
 | |
| #include "llvm/Debuginfod/HTTPClient.h"
 | |
| #include "llvm/Support/CachePruning.h"
 | |
| #include "llvm/Support/Caching.h"
 | |
| #include "llvm/Support/Errc.h"
 | |
| #include "llvm/Support/Error.h"
 | |
| #include "llvm/Support/FileUtilities.h"
 | |
| #include "llvm/Support/Path.h"
 | |
| #include "llvm/Support/xxhash.h"
 | |
| 
 | |
| namespace llvm {
 | |
| static std::string uniqueKey(llvm::StringRef S) { return utostr(xxHash64(S)); }
 | |
| 
 | |
| // Returns a binary BuildID as a normalized hex string.
 | |
| // Uses lowercase for compatibility with common debuginfod servers.
 | |
| static std::string buildIDToString(BuildIDRef ID) {
 | |
|   return llvm::toHex(ID, /*LowerCase=*/true);
 | |
| }
 | |
| 
 | |
| Expected<SmallVector<StringRef>> getDefaultDebuginfodUrls() {
 | |
|   const char *DebuginfodUrlsEnv = std::getenv("DEBUGINFOD_URLS");
 | |
|   if (DebuginfodUrlsEnv == nullptr)
 | |
|     return SmallVector<StringRef>();
 | |
| 
 | |
|   SmallVector<StringRef> DebuginfodUrls;
 | |
|   StringRef(DebuginfodUrlsEnv).split(DebuginfodUrls, " ");
 | |
|   return DebuginfodUrls;
 | |
| }
 | |
| 
 | |
| Expected<std::string> getDefaultDebuginfodCacheDirectory() {
 | |
|   if (const char *CacheDirectoryEnv = std::getenv("DEBUGINFOD_CACHE_PATH"))
 | |
|     return CacheDirectoryEnv;
 | |
| 
 | |
|   SmallString<64> CacheDirectory;
 | |
|   if (!sys::path::cache_directory(CacheDirectory))
 | |
|     return createStringError(
 | |
|         errc::io_error, "Unable to determine appropriate cache directory.");
 | |
|   sys::path::append(CacheDirectory, "llvm-debuginfod", "client");
 | |
|   return std::string(CacheDirectory);
 | |
| }
 | |
| 
 | |
| std::chrono::milliseconds getDefaultDebuginfodTimeout() {
 | |
|   long Timeout;
 | |
|   const char *DebuginfodTimeoutEnv = std::getenv("DEBUGINFOD_TIMEOUT");
 | |
|   if (DebuginfodTimeoutEnv &&
 | |
|       to_integer(StringRef(DebuginfodTimeoutEnv).trim(), Timeout, 10))
 | |
|     return std::chrono::milliseconds(Timeout * 1000);
 | |
| 
 | |
|   return std::chrono::milliseconds(90 * 1000);
 | |
| }
 | |
| 
 | |
| /// The following functions fetch a debuginfod artifact to a file in a local
 | |
| /// cache and return the cached file path. They first search the local cache,
 | |
| /// followed by the debuginfod servers.
 | |
| 
 | |
| Expected<std::string> getCachedOrDownloadSource(BuildIDRef ID,
 | |
|                                                 StringRef SourceFilePath) {
 | |
|   SmallString<64> UrlPath;
 | |
|   sys::path::append(UrlPath, sys::path::Style::posix, "buildid",
 | |
|                     buildIDToString(ID), "source",
 | |
|                     sys::path::convert_to_slash(SourceFilePath));
 | |
|   return getCachedOrDownloadArtifact(uniqueKey(UrlPath), UrlPath);
 | |
| }
 | |
| 
 | |
| Expected<std::string> getCachedOrDownloadExecutable(BuildIDRef ID) {
 | |
|   SmallString<64> UrlPath;
 | |
|   sys::path::append(UrlPath, sys::path::Style::posix, "buildid",
 | |
|                     buildIDToString(ID), "executable");
 | |
|   return getCachedOrDownloadArtifact(uniqueKey(UrlPath), UrlPath);
 | |
| }
 | |
| 
 | |
| Expected<std::string> getCachedOrDownloadDebuginfo(BuildIDRef ID) {
 | |
|   SmallString<64> UrlPath;
 | |
|   sys::path::append(UrlPath, sys::path::Style::posix, "buildid",
 | |
|                     buildIDToString(ID), "debuginfo");
 | |
|   return getCachedOrDownloadArtifact(uniqueKey(UrlPath), UrlPath);
 | |
| }
 | |
| 
 | |
| // General fetching function.
 | |
| Expected<std::string> getCachedOrDownloadArtifact(StringRef UniqueKey,
 | |
|                                                   StringRef UrlPath) {
 | |
|   SmallString<10> CacheDir;
 | |
| 
 | |
|   Expected<std::string> CacheDirOrErr = getDefaultDebuginfodCacheDirectory();
 | |
|   if (!CacheDirOrErr)
 | |
|     return CacheDirOrErr.takeError();
 | |
|   CacheDir = *CacheDirOrErr;
 | |
| 
 | |
|   Expected<SmallVector<StringRef>> DebuginfodUrlsOrErr =
 | |
|       getDefaultDebuginfodUrls();
 | |
|   if (!DebuginfodUrlsOrErr)
 | |
|     return DebuginfodUrlsOrErr.takeError();
 | |
|   SmallVector<StringRef> &DebuginfodUrls = *DebuginfodUrlsOrErr;
 | |
|   return getCachedOrDownloadArtifact(UniqueKey, UrlPath, CacheDir,
 | |
|                                      DebuginfodUrls,
 | |
|                                      getDefaultDebuginfodTimeout());
 | |
| }
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| /// A simple handler which streams the returned data to a cache file. The cache
 | |
| /// file is only created if a 200 OK status is observed.
 | |
| class StreamedHTTPResponseHandler : public HTTPResponseHandler {
 | |
|   using CreateStreamFn =
 | |
|       std::function<Expected<std::unique_ptr<CachedFileStream>>()>;
 | |
|   CreateStreamFn CreateStream;
 | |
|   HTTPClient &Client;
 | |
|   std::unique_ptr<CachedFileStream> FileStream;
 | |
| 
 | |
| public:
 | |
|   StreamedHTTPResponseHandler(CreateStreamFn CreateStream, HTTPClient &Client)
 | |
|       : CreateStream(CreateStream), Client(Client) {}
 | |
|   virtual ~StreamedHTTPResponseHandler() = default;
 | |
| 
 | |
|   Error handleBodyChunk(StringRef BodyChunk) override;
 | |
| };
 | |
| 
 | |
| } // namespace
 | |
| 
 | |
| Error StreamedHTTPResponseHandler::handleBodyChunk(StringRef BodyChunk) {
 | |
|   if (!FileStream) {
 | |
|     if (Client.responseCode() != 200)
 | |
|       return Error::success();
 | |
|     Expected<std::unique_ptr<CachedFileStream>> FileStreamOrError =
 | |
|         CreateStream();
 | |
|     if (!FileStreamOrError)
 | |
|       return FileStreamOrError.takeError();
 | |
|     FileStream = std::move(*FileStreamOrError);
 | |
|   }
 | |
|   *FileStream->OS << BodyChunk;
 | |
|   return Error::success();
 | |
| }
 | |
| 
 | |
| Expected<std::string> getCachedOrDownloadArtifact(
 | |
|     StringRef UniqueKey, StringRef UrlPath, StringRef CacheDirectoryPath,
 | |
|     ArrayRef<StringRef> DebuginfodUrls, std::chrono::milliseconds Timeout) {
 | |
|   SmallString<64> AbsCachedArtifactPath;
 | |
|   sys::path::append(AbsCachedArtifactPath, CacheDirectoryPath,
 | |
|                     "llvmcache-" + UniqueKey);
 | |
| 
 | |
|   Expected<FileCache> CacheOrErr =
 | |
|       localCache("Debuginfod-client", ".debuginfod-client", CacheDirectoryPath);
 | |
|   if (!CacheOrErr)
 | |
|     return CacheOrErr.takeError();
 | |
| 
 | |
|   FileCache Cache = *CacheOrErr;
 | |
|   // We choose an arbitrary Task parameter as we do not make use of it.
 | |
|   unsigned Task = 0;
 | |
|   Expected<AddStreamFn> CacheAddStreamOrErr = Cache(Task, UniqueKey);
 | |
|   if (!CacheAddStreamOrErr)
 | |
|     return CacheAddStreamOrErr.takeError();
 | |
|   AddStreamFn &CacheAddStream = *CacheAddStreamOrErr;
 | |
|   if (!CacheAddStream)
 | |
|     return std::string(AbsCachedArtifactPath);
 | |
|   // The artifact was not found in the local cache, query the debuginfod
 | |
|   // servers.
 | |
|   if (!HTTPClient::isAvailable())
 | |
|     return createStringError(errc::io_error,
 | |
|                              "No working HTTP client is available.");
 | |
| 
 | |
|   if (!HTTPClient::IsInitialized)
 | |
|     return createStringError(
 | |
|         errc::io_error,
 | |
|         "A working HTTP client is available, but it is not initialized. To "
 | |
|         "allow Debuginfod to make HTTP requests, call HTTPClient::initialize() "
 | |
|         "at the beginning of main.");
 | |
| 
 | |
|   HTTPClient Client;
 | |
|   Client.setTimeout(Timeout);
 | |
|   for (StringRef ServerUrl : DebuginfodUrls) {
 | |
|     SmallString<64> ArtifactUrl;
 | |
|     sys::path::append(ArtifactUrl, sys::path::Style::posix, ServerUrl, UrlPath);
 | |
| 
 | |
|     // Perform the HTTP request and if successful, write the response body to
 | |
|     // the cache.
 | |
|     StreamedHTTPResponseHandler Handler([&]() { return CacheAddStream(Task); },
 | |
|                                         Client);
 | |
|     HTTPRequest Request(ArtifactUrl);
 | |
|     Error Err = Client.perform(Request, Handler);
 | |
|     if (Err)
 | |
|       return std::move(Err);
 | |
| 
 | |
|     if (Client.responseCode() != 200)
 | |
|       continue;
 | |
| 
 | |
|     // Return the path to the artifact on disk.
 | |
|     return std::string(AbsCachedArtifactPath);
 | |
|   }
 | |
| 
 | |
|   return createStringError(errc::argument_out_of_domain, "build id not found");
 | |
| }
 | |
| } // namespace llvm
 |