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

Generated by: LCOV version 1.13