LCOV - code coverage report
Current view: top level - debuginfod - debuginfod-client.c (source / functions) Hit Total Coverage
Test: elfutils-0.178 Lines: 229 269 85.1 %
Date: 2019-11-26 23:55:16 Functions: 11 12 91.7 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* Retrieve ELF / DWARF / source files from the debuginfod.
       2             :    Copyright (C) 2019 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 <assert.h>
      44             : #include <dirent.h>
      45             : #include <stdio.h>
      46             : #include <stdlib.h>
      47             : #include <errno.h>
      48             : #include <unistd.h>
      49             : #include <errno.h>
      50             : #include <fcntl.h>
      51             : #include <fts.h>
      52             : #include <string.h>
      53             : #include <stdbool.h>
      54             : #include <linux/limits.h>
      55             : #include <time.h>
      56             : #include <utime.h>
      57             : #include <sys/syscall.h>
      58             : #include <sys/types.h>
      59             : #include <sys/stat.h>
      60             : #include <curl/curl.h>
      61             : 
      62             : /* If fts.h is included before config.h, its indirect inclusions may not
      63             :    give us the right LFS aliases of these functions, so map them manually.  */
      64             : #ifdef BAD_FTS
      65             :   #ifdef _FILE_OFFSET_BITS
      66             :     #define open open64
      67             :     #define fopen fopen64
      68             :   #endif
      69             : #else
      70             :   #include <sys/types.h>
      71             :   #include <fts.h>
      72             : #endif
      73             : 
      74             : struct debuginfod_client
      75             : {
      76             :   /* Progress/interrupt callback function. */
      77             :   debuginfod_progressfn_t progressfn;
      78             : 
      79             :   /* Can contain all other context, like cache_path, server_urls,
      80             :      timeout or other info gotten from environment variables, the
      81             :      handle data, etc. So those don't have to be reparsed and
      82             :      recreated on each request.  */
      83             : };
      84             : 
      85             : /* The cache_clean_interval_s file within the debuginfod cache specifies
      86             :    how frequently the cache should be cleaned. The file's st_mtime represents
      87             :    the time of last cleaning.  */
      88             : static const char *cache_clean_interval_filename = "cache_clean_interval_s";
      89             : static const time_t cache_clean_default_interval_s = 86400; /* 1 day */
      90             : 
      91             : /* The cache_max_unused_age_s file within the debuginfod cache specifies the
      92             :    the maximum time since last access that a file will remain in the cache.  */
      93             : static const char *cache_max_unused_age_filename = "max_unused_age_s";
      94             : static const time_t cache_default_max_unused_age_s = 604800; /* 1 week */
      95             : 
      96             : /* Location of the cache of files downloaded from debuginfods.
      97             :    The default parent directory is $HOME, or '/' if $HOME doesn't exist.  */
      98             : static const char *cache_default_name = ".debuginfod_client_cache";
      99             : static const char *cache_path_envvar = DEBUGINFOD_CACHE_PATH_ENV_VAR;
     100             : 
     101             : /* URLs of debuginfods, separated by url_delim.
     102             :    This env var must be set for debuginfod-client to run.  */
     103             : static const char *server_urls_envvar = DEBUGINFOD_URLS_ENV_VAR;
     104             : static const char *url_delim =  " ";
     105             : static const char url_delim_char = ' ';
     106             : 
     107             : /* Timeout for debuginfods, in seconds.
     108             :    This env var must be set for debuginfod-client to run.  */
     109             : static const char *server_timeout_envvar = DEBUGINFOD_TIMEOUT_ENV_VAR;
     110             : static int server_timeout = 5;
     111             : 
     112             : /* Data associated with a particular CURL easy handle. Passed to
     113             :    the write callback.  */
     114             : struct handle_data
     115             : {
     116             :   /* Cache file to be written to in case query is successful.  */
     117             :   int fd;
     118             : 
     119             :   /* URL queried by this handle.  */
     120             :   char url[PATH_MAX];
     121             : 
     122             :   /* This handle.  */
     123             :   CURL *handle;
     124             : 
     125             :   /* Pointer to handle that should write to fd. Initially points to NULL,
     126             :      then points to the first handle that begins writing the target file
     127             :      to the cache. Used to ensure that a file is not downloaded from
     128             :      multiple servers unnecessarily.  */
     129             :   CURL **target_handle;
     130             : };
     131             : 
     132             : static size_t
     133         377 : debuginfod_write_callback (char *ptr, size_t size, size_t nmemb, void *data)
     134             : {
     135         377 :   ssize_t count = size * nmemb;
     136             : 
     137         377 :   struct handle_data *d = (struct handle_data*)data;
     138             : 
     139             :   /* Indicate to other handles that they can abort their transfer.  */
     140         377 :   if (*d->target_handle == NULL)
     141          28 :     *d->target_handle = d->handle;
     142             : 
     143             :   /* If this handle isn't the target handle, abort transfer.  */
     144         377 :   if (*d->target_handle != d->handle)
     145             :     return -1;
     146             : 
     147         377 :   return (size_t) write(d->fd, (void*)ptr, count);
     148             : }
     149             : 
     150             : /* Create the cache and interval file if they do not already exist.
     151             :    Return 0 if cache and config file are initialized, otherwise return
     152             :    the appropriate error code.  */
     153             : static int
     154          33 : debuginfod_init_cache (char *cache_path, char *interval_path, char *maxage_path)
     155             : {
     156          33 :   struct stat st;
     157             : 
     158             :   /* If the cache and config file already exist then we are done.  */
     159          33 :   if (stat(cache_path, &st) == 0 && stat(interval_path, &st) == 0)
     160             :     return 0;
     161             : 
     162             :   /* Create the cache and config files as necessary.  */
     163           4 :   if (stat(cache_path, &st) != 0 && mkdir(cache_path, 0777) < 0)
     164           0 :     return -errno;
     165             : 
     166           4 :   int fd = -1;
     167             : 
     168             :   /* init cleaning interval config file.  */
     169           4 :   fd = open(interval_path, O_CREAT | O_RDWR, 0666);
     170           4 :   if (fd < 0)
     171           0 :     return -errno;
     172             : 
     173           4 :   if (dprintf(fd, "%ld", cache_clean_default_interval_s) < 0)
     174           0 :     return -errno;
     175             : 
     176             :   /* init max age config file.  */
     177           4 :   if (stat(maxage_path, &st) != 0
     178           4 :       && (fd = open(maxage_path, O_CREAT | O_RDWR, 0666)) < 0)
     179           0 :     return -errno;
     180             : 
     181           4 :   if (dprintf(fd, "%ld", cache_default_max_unused_age_s) < 0)
     182           0 :     return -errno;
     183             : 
     184             :   return 0;
     185             : }
     186             : 
     187             : 
     188             : /* Delete any files that have been unmodied for a period
     189             :    longer than $DEBUGINFOD_CACHE_CLEAN_INTERVAL_S.  */
     190             : static int
     191          33 : debuginfod_clean_cache(debuginfod_client *c,
     192             :                        char *cache_path, char *interval_path,
     193             :                        char *max_unused_path)
     194             : {
     195          33 :   struct stat st;
     196          33 :   FILE *interval_file;
     197          33 :   FILE *max_unused_file;
     198             : 
     199          33 :   if (stat(interval_path, &st) == -1)
     200             :     {
     201             :       /* Create new interval file.  */
     202           0 :       interval_file = fopen(interval_path, "w");
     203             : 
     204           0 :       if (interval_file == NULL)
     205           0 :         return -errno;
     206             : 
     207           0 :       int rc = fprintf(interval_file, "%ld", cache_clean_default_interval_s);
     208           0 :       fclose(interval_file);
     209             : 
     210           0 :       if (rc < 0)
     211           0 :         return -errno;
     212             :     }
     213             : 
     214             :   /* Check timestamp of interval file to see whether cleaning is necessary.  */
     215          33 :   time_t clean_interval;
     216          33 :   interval_file = fopen(interval_path, "r");
     217          33 :   if (fscanf(interval_file, "%ld", &clean_interval) != 1)
     218           0 :     clean_interval = cache_clean_default_interval_s;
     219          33 :   fclose(interval_file);
     220             : 
     221          33 :   if (time(NULL) - st.st_mtime < clean_interval)
     222             :     /* Interval has not passed, skip cleaning.  */
     223             :     return 0;
     224             : 
     225             :   /* Read max unused age value from config file.  */
     226           1 :   time_t max_unused_age;
     227           1 :   max_unused_file = fopen(max_unused_path, "r");
     228           1 :   if (max_unused_file)
     229             :     {
     230           1 :       if (fscanf(max_unused_file, "%ld", &max_unused_age) != 1)
     231           0 :         max_unused_age = cache_default_max_unused_age_s;
     232           1 :       fclose(max_unused_file);
     233             :     }
     234             :   else
     235           0 :     max_unused_age = cache_default_max_unused_age_s;
     236             : 
     237           1 :   char * const dirs[] = { cache_path, NULL, };
     238             : 
     239           1 :   FTS *fts = fts_open(dirs, 0, NULL);
     240           1 :   if (fts == NULL)
     241           0 :     return -errno;
     242             : 
     243             :   FTSENT *f;
     244             :   long files = 0;
     245           5 :   while ((f = fts_read(fts)) != NULL)
     246             :     {
     247           4 :       files++;
     248           4 :       if (c->progressfn) /* inform/check progress callback */
     249           0 :         if ((c->progressfn) (c, files, 0))
     250             :           break;
     251             : 
     252           4 :       switch (f->fts_info)
     253             :         {
     254           2 :         case FTS_F:
     255             :           /* delete file if max_unused_age has been met or exceeded.  */
     256             :           /* XXX consider extra effort to clean up old tmp files */
     257           2 :           if (time(NULL) - f->fts_statp->st_atime >= max_unused_age)
     258           2 :             unlink (f->fts_path);
     259             :           break;
     260             : 
     261           1 :         case FTS_DP:
     262             :           /* Remove if empty. */
     263           1 :           (void) rmdir (f->fts_path);
     264           1 :           break;
     265             : 
     266             :         default:
     267           5 :           ;
     268             :         }
     269             :     }
     270           1 :   fts_close(fts);
     271             : 
     272             :   /* Update timestamp representing when the cache was last cleaned.  */
     273           1 :   utime (interval_path, NULL);
     274           1 :   return 0;
     275             : }
     276             : 
     277             : 
     278             : #define MAX_BUILD_ID_BYTES 64
     279             : 
     280             : 
     281             : /* Query each of the server URLs found in $DEBUGINFOD_URLS for the file
     282             :    with the specified build-id, type (debuginfo, executable or source)
     283             :    and filename. filename may be NULL. If found, return a file
     284             :    descriptor for the target, otherwise return an error code.
     285             : */
     286             : static int
     287          33 : debuginfod_query_server (debuginfod_client *c,
     288             :                          const unsigned char *build_id,
     289             :                          int build_id_len,
     290             :                          const char *type,
     291             :                          const char *filename,
     292             :                          char **path)
     293             : {
     294          33 :   char *urls_envvar;
     295          33 :   char *server_urls;
     296          33 :   char cache_path[PATH_MAX];
     297          33 :   char maxage_path[PATH_MAX*3]; /* These *3 multipliers are to shut up gcc -Wformat-truncation */
     298          33 :   char interval_path[PATH_MAX*4];
     299          33 :   char target_cache_dir[PATH_MAX*2];
     300          33 :   char target_cache_path[PATH_MAX*4];
     301          33 :   char target_cache_tmppath[PATH_MAX*5];
     302          33 :   char suffix[PATH_MAX*2];
     303          33 :   char build_id_bytes[MAX_BUILD_ID_BYTES * 2 + 1];
     304             : 
     305             :   /* Copy lowercase hex representation of build_id into buf.  */
     306          33 :   if ((build_id_len >= MAX_BUILD_ID_BYTES) ||
     307             :       (build_id_len == 0 &&
     308             :        sizeof(build_id_bytes) > MAX_BUILD_ID_BYTES*2 + 1))
     309             :     return -EINVAL;
     310          33 :   if (build_id_len == 0) /* expect clean hexadecimal */
     311          33 :     strcpy (build_id_bytes, (const char *) build_id);
     312             :   else
     313           0 :     for (int i = 0; i < build_id_len; i++)
     314           0 :       sprintf(build_id_bytes + (i * 2), "%02x", build_id[i]);
     315             : 
     316          33 :   if (filename != NULL)
     317             :     {
     318           8 :       if (filename[0] != '/') // must start with /
     319             :         return -EINVAL;
     320             : 
     321             :       /* copy the filename to suffix, s,/,#,g */
     322             :       unsigned q = 0;
     323         333 :       for (unsigned fi=0; q < PATH_MAX-1; fi++)
     324         325 :         switch (filename[fi])
     325             :           {
     326           8 :           case '\0':
     327           8 :             suffix[q] = '\0';
     328           8 :             q = PATH_MAX-1; /* escape for loop too */
     329           8 :             break;
     330          42 :           case '/': /* escape / to prevent dir escape */
     331          42 :             suffix[q++]='#';
     332          42 :             suffix[q++]='#';
     333          42 :             break;
     334           0 :           case '#': /* escape # to prevent /# vs #/ collisions */
     335           0 :             suffix[q++]='#';
     336           0 :             suffix[q++]='_';
     337           0 :             break;
     338         275 :           default:
     339         275 :             suffix[q++]=filename[fi];
     340             :           }
     341           8 :       suffix[q] = '\0';
     342             :       /* If the DWARF filenames are super long, this could exceed
     343             :          PATH_MAX and truncate/collide.  Oh well, that'll teach
     344             :          them! */
     345             :     }
     346             :   else
     347          25 :     suffix[0] = '\0';
     348             : 
     349             :   /* set paths needed to perform the query
     350             : 
     351             :      example format
     352             :      cache_path:        $HOME/.debuginfod_cache
     353             :      target_cache_dir:  $HOME/.debuginfod_cache/0123abcd
     354             :      target_cache_path: $HOME/.debuginfod_cache/0123abcd/debuginfo
     355             :      target_cache_path: $HOME/.debuginfod_cache/0123abcd/source#PATH#TO#SOURCE ?
     356             :   */
     357             : 
     358          33 :   if (getenv(cache_path_envvar))
     359          33 :     strcpy(cache_path, getenv(cache_path_envvar));
     360             :   else
     361             :     {
     362           0 :       if (getenv("HOME"))
     363           0 :         sprintf(cache_path, "%s/%s", getenv("HOME"), cache_default_name);
     364             :       else
     365           0 :         sprintf(cache_path, "/%s", cache_default_name);
     366             :     }
     367             : 
     368             :   /* avoid using snprintf here due to compiler warning.  */
     369          33 :   snprintf(target_cache_dir, sizeof(target_cache_dir), "%s/%s", cache_path, build_id_bytes);
     370          33 :   snprintf(target_cache_path, sizeof(target_cache_path), "%s/%s%s", target_cache_dir, type, suffix);
     371          33 :   snprintf(target_cache_tmppath, sizeof(target_cache_tmppath), "%s.XXXXXX", target_cache_path);
     372             : 
     373             :   /* XXX combine these */
     374          33 :   snprintf(interval_path, sizeof(interval_path), "%s/%s", cache_path, cache_clean_interval_filename);
     375          33 :   snprintf(maxage_path, sizeof(maxage_path), "%s/%s", cache_path, cache_max_unused_age_filename);
     376          33 :   int rc = debuginfod_init_cache(cache_path, interval_path, maxage_path);
     377          33 :   if (rc != 0)
     378             :     goto out;
     379          33 :   rc = debuginfod_clean_cache(c, cache_path, interval_path, maxage_path);
     380          33 :   if (rc != 0)
     381             :     goto out;
     382             : 
     383             :   /* If the target is already in the cache then we are done.  */
     384          33 :   int fd = open (target_cache_path, O_RDONLY);
     385          33 :   if (fd >= 0)
     386             :     {
     387             :       /* Success!!!! */
     388           0 :       if (path != NULL)
     389           0 :         *path = strdup(target_cache_path);
     390           0 :       return fd;
     391             :     }
     392             : 
     393             : 
     394          33 :   urls_envvar = getenv(server_urls_envvar);
     395          33 :   if (urls_envvar == NULL || urls_envvar[0] == '\0')
     396             :     {
     397             :       rc = -ENOSYS;
     398             :       goto out;
     399             :     }
     400             : 
     401          31 :   if (getenv(server_timeout_envvar))
     402          31 :     server_timeout = atoi (getenv(server_timeout_envvar));
     403             : 
     404             :   /* make a copy of the envvar so it can be safely modified.  */
     405          31 :   server_urls = strdup(urls_envvar);
     406          31 :   if (server_urls == NULL)
     407             :     {
     408             :       rc = -ENOMEM;
     409             :       goto out;
     410             :     }
     411             :   /* thereafter, goto out0 on error*/
     412             : 
     413             :   /* create target directory in cache if not found.  */
     414          31 :   struct stat st;
     415          31 :   if (stat(target_cache_dir, &st) == -1 && mkdir(target_cache_dir, 0700) < 0)
     416             :     {
     417           1 :       rc = -errno;
     418           1 :       goto out0;
     419             :     }
     420             : 
     421             :   /* NB: write to a temporary file first, to avoid race condition of
     422             :      multiple clients checking the cache, while a partially-written or empty
     423             :      file is in there, being written from libcurl. */
     424          30 :   fd = mkstemp (target_cache_tmppath);
     425          30 :   if (fd < 0)
     426             :     {
     427           0 :       rc = -errno;
     428           0 :       goto out0;
     429             :     }
     430             : 
     431             :   /* Count number of URLs.  */
     432             :   int num_urls = 0;
     433         687 :   for (int i = 0; server_urls[i] != '\0'; i++)
     434         657 :     if (server_urls[i] != url_delim_char
     435         657 :         && (i == 0 || server_urls[i - 1] == url_delim_char))
     436          30 :       num_urls++;
     437             : 
     438             :   /* Tracks which handle should write to fd. Set to the first
     439             :      handle that is ready to write the target file to the cache.  */
     440          30 :   CURL *target_handle = NULL;
     441          30 :   struct handle_data *data = malloc(sizeof(struct handle_data) * num_urls);
     442             : 
     443             :   /* Initalize handle_data with default values. */
     444          60 :   for (int i = 0; i < num_urls; i++)
     445             :     {
     446          30 :       data[i].handle = NULL;
     447          30 :       data[i].fd = -1;
     448             :     }
     449             : 
     450          30 :   CURLM *curlm = curl_multi_init();
     451          30 :   if (curlm == NULL)
     452             :     {
     453             :       rc = -ENETUNREACH;
     454             :       goto out0;
     455             :     }
     456             :   /* thereafter, goto out1 on error.  */
     457             : 
     458          30 :   char *strtok_saveptr;
     459          30 :   char *server_url = strtok_r(server_urls, url_delim, &strtok_saveptr);
     460             : 
     461             :   /* Initialize each handle.  */
     462          60 :   for (int i = 0; i < num_urls && server_url != NULL; i++)
     463             :     {
     464          30 :       data[i].fd = fd;
     465          30 :       data[i].target_handle = &target_handle;
     466          30 :       data[i].handle = curl_easy_init();
     467             : 
     468          30 :       if (data[i].handle == NULL)
     469             :         {
     470             :           rc = -ENETUNREACH;
     471             :           goto out1;
     472             :         }
     473             : 
     474             :       /* Build handle url. Tolerate both  http://foo:999  and
     475             :          http://foo:999/  forms */
     476          30 :       char *slashbuildid;
     477          30 :       if (strlen(server_url) > 1 && server_url[strlen(server_url)-1] == '/')
     478             :         slashbuildid = "buildid";
     479             :       else
     480           3 :         slashbuildid = "/buildid";
     481             : 
     482          30 :       if (filename) /* must start with / */
     483           8 :         snprintf(data[i].url, PATH_MAX, "%s%s/%s/%s%s", server_url,
     484             :                  slashbuildid, build_id_bytes, type, filename);
     485             :       else
     486          22 :         snprintf(data[i].url, PATH_MAX, "%s%s/%s/%s", server_url,
     487             :                  slashbuildid, build_id_bytes, type);
     488             : 
     489          30 :       curl_easy_setopt(data[i].handle, CURLOPT_URL, data[i].url);
     490          30 :       curl_easy_setopt(data[i].handle,
     491             :                        CURLOPT_WRITEFUNCTION,
     492             :                        debuginfod_write_callback);
     493          30 :       curl_easy_setopt(data[i].handle, CURLOPT_WRITEDATA, (void*)&data[i]);
     494          30 :       curl_easy_setopt(data[i].handle, CURLOPT_TIMEOUT, (long) server_timeout);
     495          30 :       curl_easy_setopt(data[i].handle, CURLOPT_FILETIME, (long) 1);
     496          30 :       curl_easy_setopt(data[i].handle, CURLOPT_FOLLOWLOCATION, (long) 1);
     497          30 :       curl_easy_setopt(data[i].handle, CURLOPT_FAILONERROR, (long) 1);
     498          30 :       curl_easy_setopt(data[i].handle, CURLOPT_NOSIGNAL, (long) 1);
     499          30 :       curl_easy_setopt(data[i].handle, CURLOPT_AUTOREFERER, (long) 1);
     500          30 :       curl_easy_setopt(data[i].handle, CURLOPT_ACCEPT_ENCODING, "");
     501          30 :       curl_easy_setopt(data[i].handle, CURLOPT_USERAGENT, (void*) PACKAGE_STRING);
     502             : 
     503          30 :       curl_multi_add_handle(curlm, data[i].handle);
     504          30 :       server_url = strtok_r(NULL, url_delim, &strtok_saveptr);
     505             :     }
     506             : 
     507             :   /* Query servers in parallel.  */
     508             :   int still_running;
     509             :   long loops = 0;
     510         416 :   do
     511             :     {
     512         416 :       CURLMcode curl_res;
     513             : 
     514         416 :       if (c->progressfn) /* inform/check progress callback */
     515             :         {
     516           5 :           loops ++;
     517           5 :           long pa = loops; /* default params for progress callback */
     518           5 :           long pb = 0;
     519           5 :           if (target_handle) /* we've committed to a server; report its download progress */
     520             :             {
     521             : #ifdef CURLINFO_SIZE_DOWNLOAD_T
     522             :               curl_off_t dl;
     523             :               curl_res = curl_easy_getinfo(target_handle,
     524             :                                            CURLINFO_SIZE_DOWNLOAD_T,
     525             :                                            &dl);
     526             :               if (curl_res == 0 && dl >= 0)
     527             :                 pa = (dl > LONG_MAX ? LONG_MAX : (long)dl);
     528             : #else
     529           1 :               double dl;
     530           1 :               curl_res = curl_easy_getinfo(target_handle,
     531             :                                            CURLINFO_SIZE_DOWNLOAD,
     532             :                                            &dl);
     533           1 :               if (curl_res == 0)
     534           1 :                 pa = (dl > LONG_MAX ? LONG_MAX : (long)dl);
     535             : #endif
     536             : 
     537             : #ifdef CURLINFO_CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
     538             :               curl_off_t cl;
     539             :               curl_res = curl_easy_getinfo(target_handle,
     540             :                                            CURLINFO_CONTENT_LENGTH_DOWNLOAD_T,
     541             :                                            &cl);
     542             :               if (curl_res == 0 && cl >= 0)
     543             :                 pb = (cl > LONG_MAX ? LONG_MAX : (long)cl);
     544             : #else
     545           1 :               double cl;
     546           1 :               curl_res = curl_easy_getinfo(target_handle,
     547             :                                            CURLINFO_CONTENT_LENGTH_DOWNLOAD,
     548             :                                            &cl);
     549           1 :               if (curl_res == 0)
     550           1 :                 pb = (cl > LONG_MAX ? LONG_MAX : (long)cl);
     551             : #endif
     552             :             }
     553             : 
     554           5 :           if ((*c->progressfn) (c, pa, pb))
     555             :             break;
     556             :         }
     557             : 
     558             :       /* Wait 1 second, the minimum DEBUGINFOD_TIMEOUT.  */
     559         416 :       curl_multi_wait(curlm, NULL, 0, 1000, NULL);
     560             : 
     561             :       /* If the target file has been found, abort the other queries.  */
     562         416 :       if (target_handle != NULL)
     563         698 :         for (int i = 0; i < num_urls; i++)
     564         349 :           if (data[i].handle != target_handle)
     565           0 :             curl_multi_remove_handle(curlm, data[i].handle);
     566             : 
     567         416 :       curl_res = curl_multi_perform(curlm, &still_running);
     568         416 :       if (curl_res != CURLM_OK)
     569             :         {
     570           0 :           switch (curl_res)
     571             :             {
     572             :             case CURLM_CALL_MULTI_PERFORM: continue;
     573             :             case CURLM_OUT_OF_MEMORY: rc = -ENOMEM; break;
     574           0 :             default: rc = -ENETUNREACH; break;
     575             :             }
     576           0 :           goto out1;
     577             :         }
     578         416 :     } while (still_running);
     579             : 
     580             :   /* Check whether a query was successful. If so, assign its handle
     581             :      to verified_handle.  */
     582             :   int num_msg;
     583             :   rc = -ENOENT;
     584             :   CURL *verified_handle = NULL;
     585          30 :   do
     586             :     {
     587          30 :       CURLMsg *msg;
     588             : 
     589          30 :       msg = curl_multi_info_read(curlm, &num_msg);
     590          30 :       if (msg != NULL && msg->msg == CURLMSG_DONE)
     591             :         {
     592          30 :           if (msg->data.result != CURLE_OK)
     593             :             {
     594             :               /* Unsucessful query, determine error code.  */
     595           2 :               switch (msg->data.result)
     596             :                 {
     597             :                 case CURLE_COULDNT_RESOLVE_HOST: rc = -EHOSTUNREACH; break; // no NXDOMAIN
     598             :                 case CURLE_URL_MALFORMAT: rc = -EINVAL; break;
     599             :                 case CURLE_COULDNT_CONNECT: rc = -ECONNREFUSED; break;
     600             :                 case CURLE_REMOTE_ACCESS_DENIED: rc = -EACCES; break;
     601             :                 case CURLE_WRITE_ERROR: rc = -EIO; break;
     602             :                 case CURLE_OUT_OF_MEMORY: rc = -ENOMEM; break;
     603             :                 case CURLE_TOO_MANY_REDIRECTS: rc = -EMLINK; break;
     604             :                 case CURLE_SEND_ERROR: rc = -ECONNRESET; break;
     605             :                 case CURLE_RECV_ERROR: rc = -ECONNRESET; break;
     606             :                 case CURLE_OPERATION_TIMEDOUT: rc = -ETIME; break;
     607             :                 default: rc = -ENOENT; break;
     608             :                 }
     609             :             }
     610             :           else
     611             :             {
     612             :               /* Query completed without an error. Confirm that the
     613             :                  response code is 200 and set verified_handle.  */
     614          28 :               long resp_code = 500;
     615          28 :               CURLcode curl_res;
     616             : 
     617          28 :               curl_res = curl_easy_getinfo(target_handle,
     618             :                                            CURLINFO_RESPONSE_CODE,
     619             :                                            &resp_code);
     620             : 
     621          28 :               if (curl_res == CURLE_OK
     622          28 :                   && resp_code == 200
     623          28 :                   && msg->easy_handle != NULL)
     624             :                 {
     625          28 :                   verified_handle = msg->easy_handle;
     626          28 :                   break;
     627             :                 }
     628             :             }
     629             :         }
     630           2 :     } while (num_msg > 0);
     631             : 
     632          30 :   if (verified_handle == NULL)
     633             :     goto out1;
     634             : 
     635             :   /* we've got one!!!! */
     636          28 :   time_t mtime;
     637          28 :   CURLcode curl_res = curl_easy_getinfo(verified_handle, CURLINFO_FILETIME, (void*) &mtime);
     638          28 :   if (curl_res != CURLE_OK)
     639           0 :     mtime = time(NULL); /* fall back to current time */
     640             : 
     641          28 :   struct timeval tvs[2];
     642          28 :   tvs[0].tv_sec = tvs[1].tv_sec = mtime;
     643          28 :   tvs[0].tv_usec = tvs[1].tv_usec = 0;
     644          28 :   (void) futimes (fd, tvs);  /* best effort */
     645             : 
     646             :   /* rename tmp->real */
     647          28 :   rc = rename (target_cache_tmppath, target_cache_path);
     648          28 :   if (rc < 0)
     649             :     {
     650           0 :       rc = -errno;
     651           0 :       goto out1;
     652             :       /* Perhaps we need not give up right away; could retry or something ... */
     653             :     }
     654             : 
     655             :   /* Success!!!! */
     656          56 :   for (int i = 0; i < num_urls; i++)
     657          28 :     curl_easy_cleanup(data[i].handle);
     658             : 
     659          28 :   curl_multi_cleanup (curlm);
     660          28 :   free (data);
     661          28 :   free (server_urls);
     662             :   /* don't close fd - we're returning it */
     663             :   /* don't unlink the tmppath; it's already been renamed. */
     664          28 :   if (path != NULL)
     665          27 :    *path = strdup(target_cache_path);
     666             : 
     667             :   return fd;
     668             : 
     669             : /* error exits */
     670           2 :  out1:
     671           4 :   for (int i = 0; i < num_urls; i++)
     672           2 :     curl_easy_cleanup(data[i].handle);
     673             : 
     674           2 :   curl_multi_cleanup(curlm);
     675           2 :   unlink (target_cache_tmppath);
     676           2 :   (void) rmdir (target_cache_dir); /* nop if not empty */
     677           2 :   free(data);
     678           2 :   close (fd);
     679             : 
     680           3 :  out0:
     681           3 :   free (server_urls);
     682             : 
     683             :  out:
     684             :   return rc;
     685             : }
     686             : 
     687             : /* See debuginfod.h  */
     688             : debuginfod_client  *
     689          33 : debuginfod_begin (void)
     690             : {
     691          33 :   debuginfod_client *client;
     692          33 :   size_t size = sizeof (struct debuginfod_client);
     693          33 :   client = (debuginfod_client *) malloc (size);
     694          33 :   if (client != NULL)
     695          33 :     client->progressfn = NULL;
     696          33 :   return client;
     697             : }
     698             : 
     699             : void
     700          30 : debuginfod_end (debuginfod_client *client)
     701             : {
     702          30 :   free (client);
     703          30 : }
     704             : 
     705             : int
     706          14 : debuginfod_find_debuginfo (debuginfod_client *client,
     707             :                            const unsigned char *build_id, int build_id_len,
     708             :                            char **path)
     709             : {
     710          14 :   return debuginfod_query_server(client, build_id, build_id_len,
     711             :                                  "debuginfo", NULL, path);
     712             : }
     713             : 
     714             : 
     715             : /* See debuginfod.h  */
     716             : int
     717          11 : debuginfod_find_executable(debuginfod_client *client,
     718             :                            const unsigned char *build_id, int build_id_len,
     719             :                            char **path)
     720             : {
     721          11 :   return debuginfod_query_server(client, build_id, build_id_len,
     722             :                                  "executable", NULL, path);
     723             : }
     724             : 
     725             : /* See debuginfod.h  */
     726           8 : int debuginfod_find_source(debuginfod_client *client,
     727             :                            const unsigned char *build_id, int build_id_len,
     728             :                            const char *filename, char **path)
     729             : {
     730           8 :   return debuginfod_query_server(client, build_id, build_id_len,
     731             :                                  "source", filename, path);
     732             : }
     733             : 
     734             : 
     735             : void
     736           4 : debuginfod_set_progressfn(debuginfod_client *client,
     737             :                           debuginfod_progressfn_t fn)
     738             : {
     739           4 :   client->progressfn = fn;
     740           4 : }
     741             : 
     742             : 
     743             : /* NB: these are thread-unsafe. */
     744          32 : __attribute__((constructor)) attribute_hidden void libdebuginfod_ctor(void)
     745             : {
     746          32 :   curl_global_init(CURL_GLOBAL_DEFAULT);
     747          32 : }
     748             : 
     749             : /* NB: this is very thread-unsafe: it breaks other threads that are still in libcurl */
     750           0 : __attribute__((destructor)) attribute_hidden void libdebuginfod_dtor(void)
     751             : {
     752             :   /* ... so don't do this: */
     753             :   /* curl_global_cleanup(); */
     754           0 : }

Generated by: LCOV version 1.13