LCOV - code coverage report
Current view: top level - debuginfod - debuginfod-client.c (source / functions) Hit Total Coverage
Test: elfutils-0.182 Lines: 367 426 86.2 %
Date: 2020-10-31 23:45:54 Functions: 16 18 88.9 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* Retrieve ELF / DWARF / source files from the debuginfod.
       2             :    Copyright (C) 2019-2020 Red Hat, Inc.
       3             :    This file is part of elfutils.
       4             : 
       5             :    This file is free software; you can redistribute it and/or modify
       6             :    it under the terms of either
       7             : 
       8             :      * the GNU Lesser General Public License as published by the Free
       9             :        Software Foundation; either version 3 of the License, or (at
      10             :        your option) any later version
      11             : 
      12             :    or
      13             : 
      14             :      * the GNU General Public License as published by the Free
      15             :        Software Foundation; either version 2 of the License, or (at
      16             :        your option) any later version
      17             : 
      18             :    or both in parallel, as here.
      19             : 
      20             :    elfutils is distributed in the hope that it will be useful, but
      21             :    WITHOUT ANY WARRANTY; without even the implied warranty of
      22             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      23             :    General Public License for more details.
      24             : 
      25             :    You should have received copies of the GNU General Public License and
      26             :    the GNU Lesser General Public License along with this program.  If
      27             :    not, see <http://www.gnu.org/licenses/>.  */
      28             : 
      29             : 
      30             : /* cargo-cult from libdwfl linux-kernel-modules.c */
      31             : /* In case we have a bad fts we include this before config.h because it
      32             :    can't handle _FILE_OFFSET_BITS.
      33             :    Everything we need here is fine if its declarations just come first.
      34             :    Also, include sys/types.h before fts. On some systems fts.h is not self
      35             :    contained. */
      36             : #ifdef BAD_FTS
      37             :   #include <sys/types.h>
      38             :   #include <fts.h>
      39             : #endif
      40             : 
      41             : #include "config.h"
      42             : #include "debuginfod.h"
      43             : #include "system.h"
      44             : #include <errno.h>
      45             : #include <stdlib.h>
      46             : 
      47             : /* We might be building a bootstrap dummy library, which is really simple. */
      48             : #ifdef DUMMY_LIBDEBUGINFOD
      49             : 
      50             : debuginfod_client *debuginfod_begin (void) { errno = ENOSYS; return NULL; }
      51             : int debuginfod_find_debuginfo (debuginfod_client *c, const unsigned char *b,
      52             :                                int s, char **p) { return -ENOSYS; }
      53             : int debuginfod_find_executable (debuginfod_client *c, const unsigned char *b,
      54             :                                 int s, char **p) { return -ENOSYS; }
      55             : int debuginfod_find_source (debuginfod_client *c, const unsigned char *b,
      56             :                             int s, const char *f, char **p)  { return -ENOSYS; }
      57             : void debuginfod_set_progressfn(debuginfod_client *c,
      58             :                                debuginfod_progressfn_t fn) { }
      59             : void debuginfod_set_user_data (debuginfod_client *c, void *d) { }
      60             : void* debuginfod_get_user_data (debuginfod_client *c) { return NULL; }
      61             : const char* debuginfod_get_url (debuginfod_client *c) { return NULL; }
      62             : int debuginfod_add_http_header (debuginfod_client *c,
      63             :                                 const char *h) { return -ENOSYS; }
      64             : void debuginfod_end (debuginfod_client *c) { }
      65             : 
      66             : #else /* DUMMY_LIBDEBUGINFOD */
      67             : 
      68             : #include <assert.h>
      69             : #include <dirent.h>
      70             : #include <stdio.h>
      71             : #include <errno.h>
      72             : #include <unistd.h>
      73             : #include <fcntl.h>
      74             : #include <fts.h>
      75             : #include <regex.h>
      76             : #include <string.h>
      77             : #include <stdbool.h>
      78             : #include <linux/limits.h>
      79             : #include <time.h>
      80             : #include <utime.h>
      81             : #include <sys/syscall.h>
      82             : #include <sys/types.h>
      83             : #include <sys/stat.h>
      84             : #include <sys/utsname.h>
      85             : #include <curl/curl.h>
      86             : 
      87             : /* If fts.h is included before config.h, its indirect inclusions may not
      88             :    give us the right LFS aliases of these functions, so map them manually.  */
      89             : #ifdef BAD_FTS
      90             :   #ifdef _FILE_OFFSET_BITS
      91             :     #define open open64
      92             :     #define fopen fopen64
      93             :   #endif
      94             : #else
      95             :   #include <sys/types.h>
      96             :   #include <fts.h>
      97             : #endif
      98             : 
      99             : struct debuginfod_client
     100             : {
     101             :   /* Progress/interrupt callback function. */
     102             :   debuginfod_progressfn_t progressfn;
     103             : 
     104             :   /* Stores user data. */
     105             :   void* user_data;
     106             : 
     107             :   /* Stores current/last url, if any. */
     108             :   char* url;
     109             : 
     110             :   /* Accumulates outgoing http header names/values. */
     111             :   int user_agent_set_p; /* affects add_default_headers */
     112             :   struct curl_slist *headers;
     113             : 
     114             :   /* Flags the default_progressfn having printed something that
     115             :      debuginfod_end needs to terminate. */
     116             :   int default_progressfn_printed_p;
     117             : 
     118             :   /* Can contain all other context, like cache_path, server_urls,
     119             :      timeout or other info gotten from environment variables, the
     120             :      handle data, etc. So those don't have to be reparsed and
     121             :      recreated on each request.  */
     122             : };
     123             : 
     124             : /* The cache_clean_interval_s file within the debuginfod cache specifies
     125             :    how frequently the cache should be cleaned. The file's st_mtime represents
     126             :    the time of last cleaning.  */
     127             : static const char *cache_clean_interval_filename = "cache_clean_interval_s";
     128             : static const time_t cache_clean_default_interval_s = 86400; /* 1 day */
     129             : 
     130             : /* The cache_max_unused_age_s file within the debuginfod cache specifies the
     131             :    the maximum time since last access that a file will remain in the cache.  */
     132             : static const char *cache_max_unused_age_filename = "max_unused_age_s";
     133             : static const time_t cache_default_max_unused_age_s = 604800; /* 1 week */
     134             : 
     135             : /* Location of the cache of files downloaded from debuginfods.
     136             :    The default parent directory is $HOME, or '/' if $HOME doesn't exist.  */
     137             : static const char *cache_default_name = ".debuginfod_client_cache";
     138             : static const char *cache_xdg_name = "debuginfod_client";
     139             : static const char *cache_path_envvar = DEBUGINFOD_CACHE_PATH_ENV_VAR;
     140             : 
     141             : /* URLs of debuginfods, separated by url_delim. */
     142             : static const char *server_urls_envvar = DEBUGINFOD_URLS_ENV_VAR;
     143             : static const char *url_delim =  " ";
     144             : static const char url_delim_char = ' ';
     145             : 
     146             : /* Timeout for debuginfods, in seconds (to get at least 100K). */
     147             : static const char *server_timeout_envvar = DEBUGINFOD_TIMEOUT_ENV_VAR;
     148             : static const long default_timeout = 90;
     149             : 
     150             : 
     151             : /* Data associated with a particular CURL easy handle. Passed to
     152             :    the write callback.  */
     153             : struct handle_data
     154             : {
     155             :   /* Cache file to be written to in case query is successful.  */
     156             :   int fd;
     157             : 
     158             :   /* URL queried by this handle.  */
     159             :   char url[PATH_MAX];
     160             : 
     161             :   /* This handle.  */
     162             :   CURL *handle;
     163             : 
     164             :   /* The client object whom we're serving. */
     165             :   debuginfod_client *client;
     166             : 
     167             :   /* Pointer to handle that should write to fd. Initially points to NULL,
     168             :      then points to the first handle that begins writing the target file
     169             :      to the cache. Used to ensure that a file is not downloaded from
     170             :      multiple servers unnecessarily.  */
     171             :   CURL **target_handle;
     172             : };
     173             : 
     174             : static size_t
     175         733 : debuginfod_write_callback (char *ptr, size_t size, size_t nmemb, void *data)
     176             : {
     177         733 :   ssize_t count = size * nmemb;
     178             : 
     179         733 :   struct handle_data *d = (struct handle_data*)data;
     180             : 
     181             :   /* Indicate to other handles that they can abort their transfer.  */
     182         733 :   if (*d->target_handle == NULL)
     183             :     {
     184          44 :       *d->target_handle = d->handle;
     185             :       /* update the client object */
     186          44 :       const char *url = NULL;
     187          44 :       (void) curl_easy_getinfo (d->handle, CURLINFO_EFFECTIVE_URL, &url);
     188          44 :       if (url)
     189             :         {
     190          44 :           free (d->client->url);
     191          44 :           d->client->url = strdup(url); /* ok if fails */
     192             :         }
     193             :     }
     194             : 
     195             :   /* If this handle isn't the target handle, abort transfer.  */
     196         733 :   if (*d->target_handle != d->handle)
     197             :     return -1;
     198             : 
     199         733 :   return (size_t) write(d->fd, (void*)ptr, count);
     200             : }
     201             : 
     202             : /* Create the cache and interval file if they do not already exist.
     203             :    Return 0 if cache and config file are initialized, otherwise return
     204             :    the appropriate error code.  */
     205             : static int
     206          60 : debuginfod_init_cache (char *cache_path, char *interval_path, char *maxage_path)
     207             : {
     208          60 :   struct stat st;
     209             : 
     210             :   /* If the cache and config file already exist then we are done.  */
     211          60 :   if (stat(cache_path, &st) == 0 && stat(interval_path, &st) == 0)
     212             :     return 0;
     213             : 
     214             :   /* Create the cache and config files as necessary.  */
     215           8 :   if (stat(cache_path, &st) != 0 && mkdir(cache_path, 0777) < 0)
     216           0 :     return -errno;
     217             : 
     218           8 :   int fd = -1;
     219             : 
     220             :   /* init cleaning interval config file.  */
     221           8 :   fd = open(interval_path, O_CREAT | O_RDWR, 0666);
     222           8 :   if (fd < 0)
     223           0 :     return -errno;
     224             : 
     225           8 :   if (dprintf(fd, "%ld", cache_clean_default_interval_s) < 0)
     226           0 :     return -errno;
     227             : 
     228             :   /* init max age config file.  */
     229           8 :   if (stat(maxage_path, &st) != 0
     230           8 :       && (fd = open(maxage_path, O_CREAT | O_RDWR, 0666)) < 0)
     231           0 :     return -errno;
     232             : 
     233           8 :   if (dprintf(fd, "%ld", cache_default_max_unused_age_s) < 0)
     234           0 :     return -errno;
     235             : 
     236             :   return 0;
     237             : }
     238             : 
     239             : 
     240             : /* Delete any files that have been unmodied for a period
     241             :    longer than $DEBUGINFOD_CACHE_CLEAN_INTERVAL_S.  */
     242             : static int
     243          60 : debuginfod_clean_cache(debuginfod_client *c,
     244             :                        char *cache_path, char *interval_path,
     245             :                        char *max_unused_path)
     246             : {
     247          60 :   struct stat st;
     248          60 :   FILE *interval_file;
     249          60 :   FILE *max_unused_file;
     250             : 
     251          60 :   if (stat(interval_path, &st) == -1)
     252             :     {
     253             :       /* Create new interval file.  */
     254           0 :       interval_file = fopen(interval_path, "w");
     255             : 
     256           0 :       if (interval_file == NULL)
     257           0 :         return -errno;
     258             : 
     259           0 :       int rc = fprintf(interval_file, "%ld", cache_clean_default_interval_s);
     260           0 :       fclose(interval_file);
     261             : 
     262           0 :       if (rc < 0)
     263           0 :         return -errno;
     264             :     }
     265             : 
     266             :   /* Check timestamp of interval file to see whether cleaning is necessary.  */
     267          60 :   time_t clean_interval;
     268          60 :   interval_file = fopen(interval_path, "r");
     269          60 :   if (interval_file)
     270             :     {
     271          60 :       if (fscanf(interval_file, "%ld", &clean_interval) != 1)
     272           0 :         clean_interval = cache_clean_default_interval_s;
     273          60 :       fclose(interval_file);
     274             :     }
     275             :   else
     276           0 :     clean_interval = cache_clean_default_interval_s;
     277             : 
     278          60 :   if (time(NULL) - st.st_mtime < clean_interval)
     279             :     /* Interval has not passed, skip cleaning.  */
     280             :     return 0;
     281             : 
     282             :   /* Read max unused age value from config file.  */
     283           2 :   time_t max_unused_age;
     284           2 :   max_unused_file = fopen(max_unused_path, "r");
     285           2 :   if (max_unused_file)
     286             :     {
     287           2 :       if (fscanf(max_unused_file, "%ld", &max_unused_age) != 1)
     288           0 :         max_unused_age = cache_default_max_unused_age_s;
     289           2 :       fclose(max_unused_file);
     290             :     }
     291             :   else
     292           0 :     max_unused_age = cache_default_max_unused_age_s;
     293             : 
     294           2 :   char * const dirs[] = { cache_path, NULL, };
     295             : 
     296           2 :   FTS *fts = fts_open(dirs, 0, NULL);
     297           2 :   if (fts == NULL)
     298           0 :     return -errno;
     299             : 
     300           2 :   regex_t re;
     301           2 :   const char * pattern = ".*/[a-f0-9]+/(debuginfo|executable|source.*)$";
     302           2 :   if (regcomp (&re, pattern, REG_EXTENDED | REG_NOSUB) != 0)
     303             :     return -ENOMEM;
     304             : 
     305             :   FTSENT *f;
     306             :   long files = 0;
     307          18 :   while ((f = fts_read(fts)) != NULL)
     308             :     {
     309             :       /* ignore any files that do not match the pattern.  */
     310          16 :       if (regexec (&re, f->fts_path, 0, NULL, 0) != 0)
     311             :         continue;
     312             : 
     313           0 :       files++;
     314           0 :       if (c->progressfn) /* inform/check progress callback */
     315           0 :         if ((c->progressfn) (c, files, 0))
     316             :           break;
     317             : 
     318           0 :       switch (f->fts_info)
     319             :         {
     320           0 :         case FTS_F:
     321             :           /* delete file if max_unused_age has been met or exceeded.  */
     322             :           /* XXX consider extra effort to clean up old tmp files */
     323           0 :           if (time(NULL) - f->fts_statp->st_atime >= max_unused_age)
     324           0 :             unlink (f->fts_path);
     325             :           break;
     326             : 
     327           0 :         case FTS_DP:
     328             :           /* Remove if empty. */
     329           0 :           (void) rmdir (f->fts_path);
     330           0 :           break;
     331             : 
     332             :         default:
     333          18 :           ;
     334             :         }
     335             :     }
     336           2 :   fts_close (fts);
     337           2 :   regfree (&re);
     338             : 
     339             :   /* Update timestamp representing when the cache was last cleaned.  */
     340           2 :   utime (interval_path, NULL);
     341           2 :   return 0;
     342             : }
     343             : 
     344             : 
     345             : #define MAX_BUILD_ID_BYTES 64
     346             : 
     347             : 
     348             : static void
     349          66 : add_default_headers(debuginfod_client *client)
     350             : {
     351          66 :   if (client->user_agent_set_p)
     352           8 :     return;
     353             : 
     354             :   /* Compute a User-Agent: string to send.  The more accurately this
     355             :      describes this host, the likelier that the debuginfod servers
     356             :      might be able to locate debuginfo for us. */
     357             : 
     358          58 :   char* utspart = NULL;
     359          58 :   struct utsname uts;
     360          58 :   int rc = 0;
     361          58 :   rc = uname (&uts);
     362          58 :   if (rc == 0)
     363         116 :     rc = asprintf(& utspart, "%s/%s", uts.sysname, uts.machine);
     364          58 :   if (rc < 0)
     365           0 :     utspart = NULL;
     366             : 
     367          58 :   FILE *f = fopen ("/etc/os-release", "r");
     368          58 :   if (f == NULL)
     369           0 :     f = fopen ("/usr/lib/os-release", "r");
     370          58 :   char *id = NULL;
     371          58 :   char *version = NULL;
     372          58 :   if (f != NULL)
     373             :     {
     374         348 :       while (id == NULL || version == NULL)
     375             :         {
     376         290 :           char buf[128];
     377         290 :           char *s = &buf[0];
     378         290 :           if (fgets (s, sizeof(buf), f) == NULL)
     379             :             break;
     380             : 
     381         290 :           int len = strlen (s);
     382         290 :           if (len < 3)
     383          58 :             continue;
     384         232 :           if (s[len - 1] == '\n')
     385             :             {
     386         232 :               s[len - 1] = '\0';
     387         232 :               len--;
     388             :             }
     389             : 
     390         232 :           char *v = strchr (s, '=');
     391         232 :           if (v == NULL || strlen (v) < 2)
     392             :             continue;
     393             : 
     394             :           /* Split var and value. */
     395         232 :           *v = '\0';
     396         232 :           v++;
     397             : 
     398             :           /* Remove optional quotes around value string. */
     399         232 :           if (*v == '"' || *v == '\'')
     400             :             {
     401           0 :               v++;
     402           0 :               s[len - 1] = '\0';
     403             :             }
     404         232 :           if (strcmp (s, "ID") == 0)
     405          58 :             id = strdup (v);
     406         232 :           if (strcmp (s, "VERSION_ID") == 0)
     407          58 :             version = strdup (v);
     408             :         }
     409          58 :       fclose (f);
     410             :     }
     411             : 
     412          58 :   char *ua = NULL;
     413         174 :   rc = asprintf(& ua, "User-Agent: %s/%s,%s,%s/%s",
     414             :                 PACKAGE_NAME, PACKAGE_VERSION,
     415          58 :                 utspart ?: "",
     416             :                 id ?: "",
     417             :                 version ?: "");
     418          58 :   if (rc < 0)
     419           0 :     ua = NULL;
     420             : 
     421          58 :   if (ua)
     422          58 :     (void) debuginfod_add_http_header (client, ua);
     423             : 
     424          58 :   free (ua);
     425          58 :   free (id);
     426          58 :   free (version);
     427          58 :   free (utspart);
     428             : }
     429             : 
     430             : 
     431             : #define xalloc_str(p, fmt, args...)        \
     432             :   do                                       \
     433             :     {                                      \
     434             :       if (asprintf (&p, fmt, args) < 0)    \
     435             :         {                                  \
     436             :           p = NULL;                        \
     437             :           rc = -ENOMEM;                    \
     438             :           goto out;                        \
     439             :         }                                  \
     440             :     } while (0)
     441             : 
     442             : 
     443             : /* Offer a basic form of progress tracing */
     444             : static int
     445           4 : default_progressfn (debuginfod_client *c, long a, long b)
     446             : {
     447           8 :   const char* url = debuginfod_get_url (c);
     448           4 :   int len = 0;
     449             : 
     450             :   /* We prefer to print the host part of the URL to keep the
     451             :      message short. */
     452           4 :   if (url != NULL)
     453             :     {
     454           2 :       const char* buildid = strstr(url, "buildid/");
     455           2 :       if (buildid != NULL)
     456           2 :         len = (buildid - url);
     457             :       else
     458           0 :         len = strlen(url);
     459             :     }
     460             : 
     461           4 :   if (b == 0 || url==NULL) /* early stage */
     462           4 :     dprintf(STDERR_FILENO,
     463           2 :             "\rDownloading %c", "-/|\\"[a % 4]);
     464           2 :   else if (b < 0) /* download in progress but unknown total length */
     465           0 :     dprintf(STDERR_FILENO,
     466             :             "\rDownloading from %.*s %ld",
     467             :             len, url, a);
     468             :   else /* download in progress, and known total length */
     469           2 :     dprintf(STDERR_FILENO,
     470             :             "\rDownloading from %.*s %ld/%ld",
     471             :             len, url, a, b);
     472           4 :   c->default_progressfn_printed_p = 1;
     473             : 
     474           4 :   return 0;
     475             : }
     476             : 
     477             : 
     478             : /* Query each of the server URLs found in $DEBUGINFOD_URLS for the file
     479             :    with the specified build-id, type (debuginfo, executable or source)
     480             :    and filename. filename may be NULL. If found, return a file
     481             :    descriptor for the target, otherwise return an error code.
     482             : */
     483             : static int
     484          66 : debuginfod_query_server (debuginfod_client *c,
     485             :                          const unsigned char *build_id,
     486             :                          int build_id_len,
     487             :                          const char *type,
     488             :                          const char *filename,
     489             :                          char **path)
     490             : {
     491          66 :   char *server_urls;
     492          66 :   char *urls_envvar;
     493          66 :   char *cache_path = NULL;
     494          66 :   char *maxage_path = NULL;
     495          66 :   char *interval_path = NULL;
     496          66 :   char *target_cache_dir = NULL;
     497          66 :   char *target_cache_path = NULL;
     498          66 :   char *target_cache_tmppath = NULL;
     499          66 :   char suffix[PATH_MAX + 1]; /* +1 for zero terminator.  */
     500          66 :   char build_id_bytes[MAX_BUILD_ID_BYTES * 2 + 1];
     501          66 :   int rc;
     502             : 
     503             :   /* Clear the obsolete URL from a previous _find operation. */
     504          66 :   free (c->url);
     505          66 :   c->url = NULL;
     506             : 
     507          66 :   add_default_headers(c);
     508             : 
     509             :   /* Is there any server we can query?  If not, don't do any work,
     510             :      just return with ENOSYS.  Don't even access the cache.  */
     511          66 :   urls_envvar = getenv(server_urls_envvar);
     512          66 :   if (urls_envvar == NULL || urls_envvar[0] == '\0')
     513             :     {
     514             :       rc = -ENOSYS;
     515             :       goto out;
     516             :     }
     517             : 
     518             :   /* Copy lowercase hex representation of build_id into buf.  */
     519          60 :   if ((build_id_len >= MAX_BUILD_ID_BYTES) ||
     520          59 :       (build_id_len == 0 &&
     521          59 :        strlen ((const char *) build_id) > MAX_BUILD_ID_BYTES*2))
     522             :     return -EINVAL;
     523          60 :   if (build_id_len == 0) /* expect clean hexadecimal */
     524          59 :     strcpy (build_id_bytes, (const char *) build_id);
     525             :   else
     526          21 :     for (int i = 0; i < build_id_len; i++)
     527          40 :       sprintf(build_id_bytes + (i * 2), "%02x", build_id[i]);
     528             : 
     529          60 :   if (filename != NULL)
     530             :     {
     531          13 :       if (filename[0] != '/') // must start with /
     532             :         return -EINVAL;
     533             : 
     534             :       /* copy the filename to suffix, s,/,#,g */
     535             :       unsigned q = 0;
     536         510 :       for (unsigned fi=0; q < PATH_MAX-2; fi++) /* -2, escape is 2 chars.  */
     537         497 :         switch (filename[fi])
     538             :           {
     539          13 :           case '\0':
     540          13 :             suffix[q] = '\0';
     541          13 :             q = PATH_MAX-1; /* escape for loop too */
     542          13 :             break;
     543          70 :           case '/': /* escape / to prevent dir escape */
     544          70 :             suffix[q++]='#';
     545          70 :             suffix[q++]='#';
     546          70 :             break;
     547           0 :           case '#': /* escape # to prevent /# vs #/ collisions */
     548           0 :             suffix[q++]='#';
     549           0 :             suffix[q++]='_';
     550           0 :             break;
     551         414 :           default:
     552         414 :             suffix[q++]=filename[fi];
     553             :           }
     554          13 :       suffix[q] = '\0';
     555             :       /* If the DWARF filenames are super long, this could exceed
     556             :          PATH_MAX and truncate/collide.  Oh well, that'll teach
     557             :          them! */
     558             :     }
     559             :   else
     560          47 :     suffix[0] = '\0';
     561             : 
     562             :   /* set paths needed to perform the query
     563             : 
     564             :      example format
     565             :      cache_path:        $HOME/.cache
     566             :      target_cache_dir:  $HOME/.cache/0123abcd
     567             :      target_cache_path: $HOME/.cache/0123abcd/debuginfo
     568             :      target_cache_path: $HOME/.cache/0123abcd/source#PATH#TO#SOURCE ?
     569             : 
     570             :      $XDG_CACHE_HOME takes priority over $HOME/.cache.
     571             :      $DEBUGINFOD_CACHE_PATH takes priority over $HOME/.cache and $XDG_CACHE_HOME.
     572             :   */
     573             : 
     574             :   /* Determine location of the cache. The path specified by the debuginfod
     575             :      cache environment variable takes priority.  */
     576          60 :   char *cache_var = getenv(cache_path_envvar);
     577          60 :   if (cache_var != NULL && strlen (cache_var) > 0)
     578          56 :     xalloc_str (cache_path, "%s", cache_var);
     579             :   else
     580             :     {
     581             :       /* If a cache already exists in $HOME ('/' if $HOME isn't set), then use
     582             :          that. Otherwise use the XDG cache directory naming format.  */
     583           4 :       xalloc_str (cache_path, "%s/%s", getenv ("HOME") ?: "/", cache_default_name);
     584             : 
     585           4 :       struct stat st;
     586           4 :       if (stat (cache_path, &st) < 0)
     587             :         {
     588           3 :           char cachedir[PATH_MAX];
     589           3 :           char *xdg = getenv ("XDG_CACHE_HOME");
     590             : 
     591           3 :           if (xdg != NULL && strlen (xdg) > 0)
     592           1 :             snprintf (cachedir, PATH_MAX, "%s", xdg);
     593             :           else
     594           2 :             snprintf (cachedir, PATH_MAX, "%s/.cache", getenv ("HOME") ?: "/");
     595             : 
     596             :           /* Create XDG cache directory if it doesn't exist.  */
     597           3 :           if (stat (cachedir, &st) == 0)
     598             :             {
     599           1 :               if (! S_ISDIR (st.st_mode))
     600             :                 {
     601             :                   rc = -EEXIST;
     602           0 :                   goto out;
     603             :                 }
     604             :             }
     605             :           else
     606             :             {
     607           2 :               rc = mkdir (cachedir, 0700);
     608             : 
     609             :               /* Also check for EEXIST and S_ISDIR in case another client just
     610             :                  happened to create the cache.  */
     611           2 :               if (rc < 0
     612           0 :                   && (errno != EEXIST
     613           0 :                       || stat (cachedir, &st) != 0
     614           0 :                       || ! S_ISDIR (st.st_mode)))
     615             :                 {
     616           0 :                   rc = -errno;
     617           0 :                   goto out;
     618             :                 }
     619             :             }
     620             : 
     621           3 :           free (cache_path);
     622           3 :           xalloc_str (cache_path, "%s/%s", cachedir, cache_xdg_name);
     623             :         }
     624             :     }
     625             : 
     626          60 :   xalloc_str (target_cache_dir, "%s/%s", cache_path, build_id_bytes);
     627          60 :   xalloc_str (target_cache_path, "%s/%s%s", target_cache_dir, type, suffix);
     628          60 :   xalloc_str (target_cache_tmppath, "%s.XXXXXX", target_cache_path);
     629             : 
     630             :   /* XXX combine these */
     631          60 :   xalloc_str (interval_path, "%s/%s", cache_path, cache_clean_interval_filename);
     632          60 :   xalloc_str (maxage_path, "%s/%s", cache_path, cache_max_unused_age_filename);
     633          60 :   rc = debuginfod_init_cache(cache_path, interval_path, maxage_path);
     634          60 :   if (rc != 0)
     635             :     goto out;
     636          60 :   rc = debuginfod_clean_cache(c, cache_path, interval_path, maxage_path);
     637          60 :   if (rc != 0)
     638             :     goto out;
     639             : 
     640             :   /* If the target is already in the cache then we are done.  */
     641          60 :   int fd = open (target_cache_path, O_RDONLY);
     642          60 :   if (fd >= 0)
     643             :     {
     644             :       /* Success!!!! */
     645          10 :       if (path != NULL)
     646          10 :         *path = strdup(target_cache_path);
     647          10 :       rc = fd;
     648          10 :       goto out;
     649             :     }
     650             : 
     651          50 :   long timeout = default_timeout;
     652          50 :   const char* timeout_envvar = getenv(server_timeout_envvar);
     653          50 :   if (timeout_envvar != NULL)
     654          50 :     timeout = atoi (timeout_envvar);
     655             : 
     656             :   /* make a copy of the envvar so it can be safely modified.  */
     657          50 :   server_urls = strdup(urls_envvar);
     658          50 :   if (server_urls == NULL)
     659             :     {
     660             :       rc = -ENOMEM;
     661             :       goto out;
     662             :     }
     663             :   /* thereafter, goto out0 on error*/
     664             : 
     665             :   /* create target directory in cache if not found.  */
     666          50 :   struct stat st;
     667          50 :   if (stat(target_cache_dir, &st) == -1 && mkdir(target_cache_dir, 0700) < 0)
     668             :     {
     669           0 :       rc = -errno;
     670           0 :       goto out0;
     671             :     }
     672             : 
     673             :   /* NB: write to a temporary file first, to avoid race condition of
     674             :      multiple clients checking the cache, while a partially-written or empty
     675             :      file is in there, being written from libcurl. */
     676          50 :   fd = mkstemp (target_cache_tmppath);
     677          50 :   if (fd < 0)
     678             :     {
     679           0 :       rc = -errno;
     680           0 :       goto out0;
     681             :     }
     682             : 
     683             :   /* Count number of URLs.  */
     684             :   int num_urls = 0;
     685        1215 :   for (int i = 0; server_urls[i] != '\0'; i++)
     686        1165 :     if (server_urls[i] != url_delim_char
     687        1161 :         && (i == 0 || server_urls[i - 1] == url_delim_char))
     688          54 :       num_urls++;
     689             : 
     690          50 :   CURLM *curlm = curl_multi_init();
     691          50 :   if (curlm == NULL)
     692             :     {
     693             :       rc = -ENETUNREACH;
     694             :       goto out0;
     695             :     }
     696             : 
     697             :   /* Tracks which handle should write to fd. Set to the first
     698             :      handle that is ready to write the target file to the cache.  */
     699          50 :   CURL *target_handle = NULL;
     700          50 :   struct handle_data *data = malloc(sizeof(struct handle_data) * num_urls);
     701          50 :   if (data == NULL)
     702             :     {
     703             :       rc = -ENOMEM;
     704             :       goto out0;
     705             :     }
     706             : 
     707             :   /* thereafter, goto out1 on error.  */
     708             : 
     709             :   /* Initalize handle_data with default values. */
     710         104 :   for (int i = 0; i < num_urls; i++)
     711             :     {
     712          54 :       data[i].handle = NULL;
     713          54 :       data[i].fd = -1;
     714             :     }
     715             : 
     716          50 :   char *strtok_saveptr;
     717          50 :   char *server_url = strtok_r(server_urls, url_delim, &strtok_saveptr);
     718             : 
     719             :   /* Initialize each handle.  */
     720         104 :   for (int i = 0; i < num_urls && server_url != NULL; i++)
     721             :     {
     722          54 :       data[i].fd = fd;
     723          54 :       data[i].target_handle = &target_handle;
     724          54 :       data[i].handle = curl_easy_init();
     725          54 :       data[i].client = c;
     726             : 
     727          54 :       if (data[i].handle == NULL)
     728             :         {
     729             :           rc = -ENETUNREACH;
     730             :           goto out1;
     731             :         }
     732             : 
     733             :       /* Build handle url. Tolerate both  http://foo:999  and
     734             :          http://foo:999/  forms */
     735          54 :       char *slashbuildid;
     736          54 :       if (strlen(server_url) > 1 && server_url[strlen(server_url)-1] == '/')
     737             :         slashbuildid = "buildid";
     738             :       else
     739          10 :         slashbuildid = "/buildid";
     740             : 
     741          54 :       if (filename) /* must start with / */
     742          13 :         snprintf(data[i].url, PATH_MAX, "%s%s/%s/%s%s", server_url,
     743             :                  slashbuildid, build_id_bytes, type, filename);
     744             :       else
     745          41 :         snprintf(data[i].url, PATH_MAX, "%s%s/%s/%s", server_url,
     746             :                  slashbuildid, build_id_bytes, type);
     747             : 
     748          54 :       curl_easy_setopt(data[i].handle, CURLOPT_URL, data[i].url);
     749          54 :       curl_easy_setopt(data[i].handle,
     750             :                        CURLOPT_WRITEFUNCTION,
     751             :                        debuginfod_write_callback);
     752          54 :       curl_easy_setopt(data[i].handle, CURLOPT_WRITEDATA, (void*)&data[i]);
     753          54 :       if (timeout > 0)
     754             :         {
     755             :           /* Make sure there is at least some progress,
     756             :              try to get at least 100K per timeout seconds.  */
     757          54 :           curl_easy_setopt (data[i].handle, CURLOPT_LOW_SPEED_TIME,
     758             :                             timeout);
     759          54 :           curl_easy_setopt (data[i].handle, CURLOPT_LOW_SPEED_LIMIT,
     760             :                             100 * 1024L);
     761             :         }
     762          54 :       curl_easy_setopt(data[i].handle, CURLOPT_FILETIME, (long) 1);
     763          54 :       curl_easy_setopt(data[i].handle, CURLOPT_FOLLOWLOCATION, (long) 1);
     764          54 :       curl_easy_setopt(data[i].handle, CURLOPT_FAILONERROR, (long) 1);
     765          54 :       curl_easy_setopt(data[i].handle, CURLOPT_NOSIGNAL, (long) 1);
     766             : #if LIBCURL_VERSION_NUM >= 0x072a00 /* 7.42.0 */
     767          54 :       curl_easy_setopt(data[i].handle, CURLOPT_PATH_AS_IS, (long) 1);
     768             : #else
     769             :       /* On old curl; no big deal, canonicalization here is almost the
     770             :          same, except perhaps for ? # type decorations at the tail. */
     771             : #endif
     772          54 :       curl_easy_setopt(data[i].handle, CURLOPT_AUTOREFERER, (long) 1);
     773          54 :       curl_easy_setopt(data[i].handle, CURLOPT_ACCEPT_ENCODING, "");
     774          54 :       curl_easy_setopt(data[i].handle, CURLOPT_HTTPHEADER, c->headers);
     775             : 
     776          54 :       curl_multi_add_handle(curlm, data[i].handle);
     777          54 :       server_url = strtok_r(NULL, url_delim, &strtok_saveptr);
     778             :     }
     779             : 
     780             :   /* Query servers in parallel.  */
     781             :   int still_running;
     782             :   long loops = 0;
     783        6550 :   do
     784             :     {
     785             :       /* Wait 1 second, the minimum DEBUGINFOD_TIMEOUT.  */
     786        6550 :       curl_multi_wait(curlm, NULL, 0, 1000, NULL);
     787             : 
     788             :       /* If the target file has been found, abort the other queries.  */
     789        6550 :       if (target_handle != NULL)
     790        1378 :         for (int i = 0; i < num_urls; i++)
     791         689 :           if (data[i].handle != target_handle)
     792           0 :             curl_multi_remove_handle(curlm, data[i].handle);
     793             : 
     794        6550 :       CURLMcode curlm_res = curl_multi_perform(curlm, &still_running);
     795        6550 :       if (curlm_res != CURLM_OK)
     796             :         {
     797           0 :           switch (curlm_res)
     798             :             {
     799             :             case CURLM_CALL_MULTI_PERFORM: continue;
     800             :             case CURLM_OUT_OF_MEMORY: rc = -ENOMEM; break;
     801           0 :             default: rc = -ENETUNREACH; break;
     802             :             }
     803           0 :           goto out1;
     804             :         }
     805             : 
     806        6550 :       if (c->progressfn) /* inform/check progress callback */
     807             :         {
     808          13 :           loops ++;
     809          13 :           long pa = loops; /* default params for progress callback */
     810          13 :           long pb = 0; /* transfer_timeout tempting, but loops != elapsed-time */
     811          13 :           if (target_handle) /* we've committed to a server; report its download progress */
     812             :             {
     813           5 :               CURLcode curl_res;
     814             : #ifdef CURLINFO_SIZE_DOWNLOAD_T
     815             :               curl_off_t dl;
     816             :               curl_res = curl_easy_getinfo(target_handle,
     817             :                                            CURLINFO_SIZE_DOWNLOAD_T,
     818             :                                            &dl);
     819             :               if (curl_res == 0 && dl >= 0)
     820             :                 pa = (dl > LONG_MAX ? LONG_MAX : (long)dl);
     821             : #else
     822           5 :               double dl;
     823           5 :               curl_res = curl_easy_getinfo(target_handle,
     824             :                                            CURLINFO_SIZE_DOWNLOAD,
     825             :                                            &dl);
     826           5 :               if (curl_res == 0)
     827           5 :                 pa = (dl > LONG_MAX ? LONG_MAX : (long)dl);
     828             : #endif
     829             : 
     830             :               /* NB: If going through deflate-compressing proxies, this
     831             :                  number is likely to be unavailable, so -1 may show. */
     832             : #ifdef CURLINFO_CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
     833             :               curl_off_t cl;
     834             :               curl_res = curl_easy_getinfo(target_handle,
     835             :                                            CURLINFO_CONTENT_LENGTH_DOWNLOAD_T,
     836             :                                            &cl);
     837             :               if (curl_res == 0 && cl >= 0)
     838             :                 pb = (cl > LONG_MAX ? LONG_MAX : (long)cl);
     839             : #else
     840           5 :               double cl;
     841           5 :               curl_res = curl_easy_getinfo(target_handle,
     842             :                                            CURLINFO_CONTENT_LENGTH_DOWNLOAD,
     843             :                                            &cl);
     844           5 :               if (curl_res == 0)
     845           5 :                 pb = (cl > LONG_MAX ? LONG_MAX : (long)cl);
     846             : #endif
     847             :             }
     848             : 
     849          13 :           if ((*c->progressfn) (c, pa, pb))
     850             :             break;
     851             :         }
     852        6550 :     } while (still_running);
     853             : 
     854             :   /* Check whether a query was successful. If so, assign its handle
     855             :      to verified_handle.  */
     856             :   int num_msg;
     857             :   rc = -ENOENT;
     858             :   CURL *verified_handle = NULL;
     859          54 :   do
     860             :     {
     861          54 :       CURLMsg *msg;
     862             : 
     863          54 :       msg = curl_multi_info_read(curlm, &num_msg);
     864          54 :       if (msg != NULL && msg->msg == CURLMSG_DONE)
     865             :         {
     866          54 :           if (msg->data.result != CURLE_OK)
     867             :             {
     868             :               /* Unsucessful query, determine error code.  */
     869          10 :               switch (msg->data.result)
     870             :                 {
     871             :                 case CURLE_COULDNT_RESOLVE_HOST: rc = -EHOSTUNREACH; break; // no NXDOMAIN
     872             :                 case CURLE_URL_MALFORMAT: rc = -EINVAL; break;
     873             :                 case CURLE_COULDNT_CONNECT: rc = -ECONNREFUSED; break;
     874             :                 case CURLE_PEER_FAILED_VERIFICATION: rc = -ECONNREFUSED; break;
     875             :                 case CURLE_REMOTE_ACCESS_DENIED: rc = -EACCES; break;
     876             :                 case CURLE_WRITE_ERROR: rc = -EIO; break;
     877             :                 case CURLE_OUT_OF_MEMORY: rc = -ENOMEM; break;
     878             :                 case CURLE_TOO_MANY_REDIRECTS: rc = -EMLINK; break;
     879             :                 case CURLE_SEND_ERROR: rc = -ECONNRESET; break;
     880             :                 case CURLE_RECV_ERROR: rc = -ECONNRESET; break;
     881             :                 case CURLE_OPERATION_TIMEDOUT: rc = -ETIME; break;
     882             :                 default: rc = -ENOENT; break;
     883             :                 }
     884             :             }
     885             :           else
     886             :             {
     887             :               /* Query completed without an error. Confirm that the
     888             :                  response code is 200 when using HTTP/HTTPS and 0 when
     889             :                  using file:// and set verified_handle.  */
     890             : 
     891          44 :               if (msg->easy_handle != NULL)
     892             :                 {
     893          44 :                   char *effective_url = NULL;
     894          44 :                   long resp_code = 500;
     895          44 :                   CURLcode ok1 = curl_easy_getinfo (target_handle,
     896             :                                                     CURLINFO_EFFECTIVE_URL,
     897             :                                                     &effective_url);
     898          44 :                   CURLcode ok2 = curl_easy_getinfo (target_handle,
     899             :                                                     CURLINFO_RESPONSE_CODE,
     900             :                                                     &resp_code);
     901          44 :                   if(ok1 == CURLE_OK && ok2 == CURLE_OK && effective_url)
     902             :                     {
     903          44 :                       if (strncasecmp (effective_url, "HTTP", 4) == 0)
     904          42 :                         if (resp_code == 200)
     905             :                           {
     906          42 :                             verified_handle = msg->easy_handle;
     907          44 :                             break;
     908             :                           }
     909           2 :                       if (strncasecmp (effective_url, "FILE", 4) == 0)
     910           1 :                         if (resp_code == 0)
     911             :                           {
     912           1 :                             verified_handle = msg->easy_handle;
     913           1 :                             break;
     914             :                           }
     915             :                     }
     916             :                   /* - libcurl since 7.52.0 version start to support
     917             :                        CURLINFO_SCHEME;
     918             :                      - before 7.61.0, effective_url would give us a
     919             :                        url with upper case SCHEME added in the front;
     920             :                      - effective_url between 7.61 and 7.69 can be lack
     921             :                        of scheme if the original url doesn't include one;
     922             :                      - since version 7.69 effective_url will be provide
     923             :                        a scheme in lower case.  */
     924             :                   #if LIBCURL_VERSION_NUM >= 0x073d00 /* 7.61.0 */
     925             :                   #if LIBCURL_VERSION_NUM <= 0x074500 /* 7.69.0 */
     926           1 :                   char *scheme = NULL;
     927           1 :                   CURLcode ok3 = curl_easy_getinfo (target_handle,
     928             :                                                     CURLINFO_SCHEME,
     929             :                                                     &scheme);
     930           1 :                   if(ok3 == CURLE_OK && scheme)
     931             :                     {
     932           1 :                       if (strncmp (scheme, "HTTP", 4) == 0)
     933           1 :                         if (resp_code == 200)
     934             :                           {
     935           1 :                             verified_handle = msg->easy_handle;
     936           1 :                             break;
     937             :                           }
     938             :                     }
     939             :                   #endif
     940             :                   #endif
     941             :                 }
     942             :             }
     943             :         }
     944          10 :     } while (num_msg > 0);
     945             : 
     946          50 :   if (verified_handle == NULL)
     947             :     goto out1;
     948             : 
     949             :   /* we've got one!!!! */
     950          44 :   time_t mtime;
     951          44 :   CURLcode curl_res = curl_easy_getinfo(verified_handle, CURLINFO_FILETIME, (void*) &mtime);
     952          44 :   if (curl_res != CURLE_OK)
     953           0 :     mtime = time(NULL); /* fall back to current time */
     954             : 
     955          44 :   struct timeval tvs[2];
     956          44 :   tvs[0].tv_sec = tvs[1].tv_sec = mtime;
     957          44 :   tvs[0].tv_usec = tvs[1].tv_usec = 0;
     958          44 :   (void) futimes (fd, tvs);  /* best effort */
     959             : 
     960             :   /* rename tmp->real */
     961          44 :   rc = rename (target_cache_tmppath, target_cache_path);
     962          44 :   if (rc < 0)
     963             :     {
     964           0 :       rc = -errno;
     965           0 :       goto out1;
     966             :       /* Perhaps we need not give up right away; could retry or something ... */
     967             :     }
     968             : 
     969             :   /* Success!!!! */
     970          88 :   for (int i = 0; i < num_urls; i++)
     971          44 :     curl_easy_cleanup(data[i].handle);
     972             : 
     973          44 :   curl_multi_cleanup (curlm);
     974          44 :   free (data);
     975          44 :   free (server_urls);
     976             : 
     977             :   /* don't close fd - we're returning it */
     978             :   /* don't unlink the tmppath; it's already been renamed. */
     979          44 :   if (path != NULL)
     980          43 :    *path = strdup(target_cache_path);
     981             : 
     982          44 :   rc = fd;
     983          44 :   goto out;
     984             : 
     985             : /* error exits */
     986           6 :  out1:
     987          16 :   for (int i = 0; i < num_urls; i++)
     988          10 :     curl_easy_cleanup(data[i].handle);
     989             : 
     990           6 :   curl_multi_cleanup(curlm);
     991           6 :   unlink (target_cache_tmppath);
     992           6 :   close (fd); /* before the rmdir, otherwise it'll fail */
     993           6 :   (void) rmdir (target_cache_dir); /* nop if not empty */
     994           6 :   free(data);
     995             : 
     996           6 :  out0:
     997           6 :   free (server_urls);
     998             : 
     999             : /* general purpose exit */
    1000          66 :  out:
    1001             :   /* Conclude the last \r status line */
    1002             :   /* Another possibility is to use the ANSI CSI n K EL "Erase in Line"
    1003             :      code.  That way, the previously printed messages would be erased,
    1004             :      and without a newline. */
    1005          66 :   if (c->default_progressfn_printed_p)
    1006           1 :     dprintf(STDERR_FILENO, "\n");
    1007             : 
    1008          66 :   free (cache_path);
    1009          66 :   free (maxage_path);
    1010          66 :   free (interval_path);
    1011          66 :   free (target_cache_dir);
    1012          66 :   free (target_cache_path);
    1013          66 :   free (target_cache_tmppath);
    1014          66 :   return rc;
    1015             : }
    1016             : 
    1017             : 
    1018             : 
    1019             : /* See debuginfod.h  */
    1020             : debuginfod_client  *
    1021          66 : debuginfod_begin (void)
    1022             : {
    1023          66 :   debuginfod_client *client;
    1024          66 :   size_t size = sizeof (struct debuginfod_client);
    1025          66 :   client = (debuginfod_client *) calloc (1, size);
    1026          66 :   if (client != NULL)
    1027             :     {
    1028          66 :       if (getenv(DEBUGINFOD_PROGRESS_ENV_VAR))
    1029           1 :         client->progressfn = default_progressfn;
    1030             :     }
    1031          66 :   return client;
    1032             : }
    1033             : 
    1034             : void
    1035          58 : debuginfod_set_user_data(debuginfod_client *client,
    1036             :                          void *data)
    1037             : {
    1038          58 :   client->user_data = data;
    1039          58 : }
    1040             : 
    1041             : void *
    1042           0 : debuginfod_get_user_data(debuginfod_client *client)
    1043             : {
    1044           0 :   return client->user_data;
    1045             : }
    1046             : 
    1047             : const char *
    1048           1 : debuginfod_get_url(debuginfod_client *client)
    1049             : {
    1050           4 :   return client->url;
    1051             : }
    1052             : 
    1053             : void
    1054          66 : debuginfod_end (debuginfod_client *client)
    1055             : {
    1056          66 :   if (client == NULL)
    1057             :     return;
    1058             : 
    1059          66 :   curl_slist_free_all (client->headers);
    1060          66 :   free (client->url);
    1061          66 :   free (client);
    1062             : }
    1063             : 
    1064             : int
    1065          28 : debuginfod_find_debuginfo (debuginfod_client *client,
    1066             :                            const unsigned char *build_id, int build_id_len,
    1067             :                            char **path)
    1068             : {
    1069          28 :   return debuginfod_query_server(client, build_id, build_id_len,
    1070             :                                  "debuginfo", NULL, path);
    1071             : }
    1072             : 
    1073             : 
    1074             : /* See debuginfod.h  */
    1075             : int
    1076          24 : debuginfod_find_executable(debuginfod_client *client,
    1077             :                            const unsigned char *build_id, int build_id_len,
    1078             :                            char **path)
    1079             : {
    1080          24 :   return debuginfod_query_server(client, build_id, build_id_len,
    1081             :                                  "executable", NULL, path);
    1082             : }
    1083             : 
    1084             : /* See debuginfod.h  */
    1085          14 : int debuginfod_find_source(debuginfod_client *client,
    1086             :                            const unsigned char *build_id, int build_id_len,
    1087             :                            const char *filename, char **path)
    1088             : {
    1089          14 :   return debuginfod_query_server(client, build_id, build_id_len,
    1090             :                                  "source", filename, path);
    1091             : }
    1092             : 
    1093             : 
    1094             : /* Add an outgoing HTTP header.  */
    1095          74 : int debuginfod_add_http_header (debuginfod_client *client, const char* header)
    1096             : {
    1097             :   /* Sanity check header value is of the form Header: Value.
    1098             :      It should contain exactly one colon that isn't the first or
    1099             :      last character.  */
    1100          74 :   char *colon = strchr (header, ':');
    1101         148 :   if (colon == NULL
    1102          74 :       || colon == header
    1103          74 :       || *(colon + 1) == '\0'
    1104          74 :       || strchr (colon + 1, ':') != NULL)
    1105             :     return -EINVAL;
    1106             : 
    1107          74 :   struct curl_slist *temp = curl_slist_append (client->headers, header);
    1108          74 :   if (temp == NULL)
    1109             :     return -ENOMEM;
    1110             : 
    1111             :   /* Track if User-Agent: is being set.  If so, signal not to add the
    1112             :      default one. */
    1113          74 :   if (strncmp (header, "User-Agent:", 11) == 0)
    1114          66 :     client->user_agent_set_p = 1;
    1115             : 
    1116          74 :   client->headers = temp;
    1117          74 :   return 0;
    1118             : }
    1119             : 
    1120             : 
    1121             : void
    1122           9 : debuginfod_set_progressfn(debuginfod_client *client,
    1123             :                           debuginfod_progressfn_t fn)
    1124             : {
    1125           9 :   client->progressfn = fn;
    1126           9 : }
    1127             : 
    1128             : 
    1129             : /* NB: these are thread-unsafe. */
    1130          61 : __attribute__((constructor)) attribute_hidden void libdebuginfod_ctor(void)
    1131             : {
    1132          61 :   curl_global_init(CURL_GLOBAL_DEFAULT);
    1133          61 : }
    1134             : 
    1135             : /* NB: this is very thread-unsafe: it breaks other threads that are still in libcurl */
    1136           0 : __attribute__((destructor)) attribute_hidden void libdebuginfod_dtor(void)
    1137             : {
    1138             :   /* ... so don't do this: */
    1139             :   /* curl_global_cleanup(); */
    1140           0 : }
    1141             : 
    1142             : #endif /* DUMMY_LIBDEBUGINFOD */

Generated by: LCOV version 1.13