LCOV - code coverage report
Current view: top level - debuginfod - debuginfod-client.c (source / functions) Hit Total Coverage
Test: elfutils-0.184 Lines: 469 557 84.2 %
Date: 2021-05-22 20:18:50 Functions: 18 20 90.0 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 258 408 63.2 %

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

Generated by: LCOV version 1.14