Blame debuginfod/debuginfod-client.c.debuginfod-timeout-progress

Packit 86a02d
/* Retrieve ELF / DWARF / source files from the debuginfod.
Packit 86a02d
   Copyright (C) 2019 Red Hat, Inc.
Packit 86a02d
   This file is part of elfutils.
Packit 86a02d
Packit 86a02d
   This file is free software; you can redistribute it and/or modify
Packit 86a02d
   it under the terms of either
Packit 86a02d
Packit 86a02d
     * the GNU Lesser General Public License as published by the Free
Packit 86a02d
       Software Foundation; either version 3 of the License, or (at
Packit 86a02d
       your option) any later version
Packit 86a02d
Packit 86a02d
   or
Packit 86a02d
Packit 86a02d
     * the GNU General Public License as published by the Free
Packit 86a02d
       Software Foundation; either version 2 of the License, or (at
Packit 86a02d
       your option) any later version
Packit 86a02d
Packit 86a02d
   or both in parallel, as here.
Packit 86a02d
Packit 86a02d
   elfutils is distributed in the hope that it will be useful, but
Packit 86a02d
   WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 86a02d
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 86a02d
   General Public License for more details.
Packit 86a02d
Packit 86a02d
   You should have received copies of the GNU General Public License and
Packit 86a02d
   the GNU Lesser General Public License along with this program.  If
Packit 86a02d
   not, see <http://www.gnu.org/licenses/>.  */
Packit 86a02d
Packit 86a02d
Packit 86a02d
/* cargo-cult from libdwfl linux-kernel-modules.c */
Packit 86a02d
/* In case we have a bad fts we include this before config.h because it
Packit 86a02d
   can't handle _FILE_OFFSET_BITS.
Packit 86a02d
   Everything we need here is fine if its declarations just come first.
Packit 86a02d
   Also, include sys/types.h before fts. On some systems fts.h is not self
Packit 86a02d
   contained. */
Packit 86a02d
#ifdef BAD_FTS
Packit 86a02d
  #include <sys/types.h>
Packit 86a02d
  #include <fts.h>
Packit 86a02d
#endif
Packit 86a02d
Packit 86a02d
#include "config.h"
Packit 86a02d
#include "debuginfod.h"
Packit 86a02d
#include <assert.h>
Packit 86a02d
#include <dirent.h>
Packit 86a02d
#include <stdio.h>
Packit 86a02d
#include <stdlib.h>
Packit 86a02d
#include <errno.h>
Packit 86a02d
#include <unistd.h>
Packit 86a02d
#include <errno.h>
Packit 86a02d
#include <fcntl.h>
Packit 86a02d
#include <fts.h>
Packit 86a02d
#include <string.h>
Packit 86a02d
#include <stdbool.h>
Packit 86a02d
#include <linux/limits.h>
Packit 86a02d
#include <time.h>
Packit 86a02d
#include <utime.h>
Packit 86a02d
#include <sys/syscall.h>
Packit 86a02d
#include <sys/types.h>
Packit 86a02d
#include <sys/stat.h>
Packit 86a02d
#include <curl/curl.h>
Packit 86a02d
Packit 86a02d
/* If fts.h is included before config.h, its indirect inclusions may not
Packit 86a02d
   give us the right LFS aliases of these functions, so map them manually.  */
Packit 86a02d
#ifdef BAD_FTS
Packit 86a02d
  #ifdef _FILE_OFFSET_BITS
Packit 86a02d
    #define open open64
Packit 86a02d
    #define fopen fopen64
Packit 86a02d
  #endif
Packit 86a02d
#else
Packit 86a02d
  #include <sys/types.h>
Packit 86a02d
  #include <fts.h>
Packit 86a02d
#endif
Packit 86a02d
Packit 86a02d
struct debuginfod_client
Packit 86a02d
{
Packit 86a02d
  /* Progress/interrupt callback function. */
Packit 86a02d
  debuginfod_progressfn_t progressfn;
Packit 86a02d
Packit 86a02d
  /* Can contain all other context, like cache_path, server_urls,
Packit 86a02d
     timeout or other info gotten from environment variables, the
Packit 86a02d
     handle data, etc. So those don't have to be reparsed and
Packit 86a02d
     recreated on each request.  */
Packit 86a02d
};
Packit 86a02d
Packit 86a02d
/* The cache_clean_interval_s file within the debuginfod cache specifies
Packit 86a02d
   how frequently the cache should be cleaned. The file's st_mtime represents
Packit 86a02d
   the time of last cleaning.  */
Packit 86a02d
static const char *cache_clean_interval_filename = "cache_clean_interval_s";
Packit 86a02d
static const time_t cache_clean_default_interval_s = 86400; /* 1 day */
Packit 86a02d
Packit 86a02d
/* The cache_max_unused_age_s file within the debuginfod cache specifies the
Packit 86a02d
   the maximum time since last access that a file will remain in the cache.  */
Packit 86a02d
static const char *cache_max_unused_age_filename = "max_unused_age_s";
Packit 86a02d
static const time_t cache_default_max_unused_age_s = 604800; /* 1 week */
Packit 86a02d
Packit 86a02d
/* Location of the cache of files downloaded from debuginfods.
Packit 86a02d
   The default parent directory is $HOME, or '/' if $HOME doesn't exist.  */
Packit 86a02d
static const char *cache_default_name = ".debuginfod_client_cache";
Packit 86a02d
static const char *cache_path_envvar = DEBUGINFOD_CACHE_PATH_ENV_VAR;
Packit 86a02d
Packit 86a02d
/* URLs of debuginfods, separated by url_delim.
Packit 86a02d
   This env var must be set for debuginfod-client to run.  */
Packit 86a02d
static const char *server_urls_envvar = DEBUGINFOD_URLS_ENV_VAR;
Packit 86a02d
static const char *url_delim =  " ";
Packit 86a02d
static const char url_delim_char = ' ';
Packit 86a02d
Packit 86a02d
/* Timeout for debuginfods, in seconds.
Packit 86a02d
   This env var must be set for debuginfod-client to run.  */
Packit 86a02d
static const char *server_timeout_envvar = DEBUGINFOD_TIMEOUT_ENV_VAR;
Packit 86a02d
static int server_timeout = 5;
Packit 86a02d
Packit 86a02d
/* Data associated with a particular CURL easy handle. Passed to
Packit 86a02d
   the write callback.  */
Packit 86a02d
struct handle_data
Packit 86a02d
{
Packit 86a02d
  /* Cache file to be written to in case query is successful.  */
Packit 86a02d
  int fd;
Packit 86a02d
Packit 86a02d
  /* URL queried by this handle.  */
Packit 86a02d
  char url[PATH_MAX];
Packit 86a02d
Packit 86a02d
  /* This handle.  */
Packit 86a02d
  CURL *handle;
Packit 86a02d
Packit 86a02d
  /* Pointer to handle that should write to fd. Initially points to NULL,
Packit 86a02d
     then points to the first handle that begins writing the target file
Packit 86a02d
     to the cache. Used to ensure that a file is not downloaded from
Packit 86a02d
     multiple servers unnecessarily.  */
Packit 86a02d
  CURL **target_handle;
Packit 86a02d
};
Packit 86a02d
Packit 86a02d
static size_t
Packit 86a02d
debuginfod_write_callback (char *ptr, size_t size, size_t nmemb, void *data)
Packit 86a02d
{
Packit 86a02d
  ssize_t count = size * nmemb;
Packit 86a02d
Packit 86a02d
  struct handle_data *d = (struct handle_data*)data;
Packit 86a02d
Packit 86a02d
  /* Indicate to other handles that they can abort their transfer.  */
Packit 86a02d
  if (*d->target_handle == NULL)
Packit 86a02d
    *d->target_handle = d->handle;
Packit 86a02d
Packit 86a02d
  /* If this handle isn't the target handle, abort transfer.  */
Packit 86a02d
  if (*d->target_handle != d->handle)
Packit 86a02d
    return -1;
Packit 86a02d
Packit 86a02d
  return (size_t) write(d->fd, (void*)ptr, count);
Packit 86a02d
}
Packit 86a02d
Packit 86a02d
/* Create the cache and interval file if they do not already exist.
Packit 86a02d
   Return 0 if cache and config file are initialized, otherwise return
Packit 86a02d
   the appropriate error code.  */
Packit 86a02d
static int
Packit 86a02d
debuginfod_init_cache (char *cache_path, char *interval_path, char *maxage_path)
Packit 86a02d
{
Packit 86a02d
  struct stat st;
Packit 86a02d
Packit 86a02d
  /* If the cache and config file already exist then we are done.  */
Packit 86a02d
  if (stat(cache_path, &st) == 0 && stat(interval_path, &st) == 0)
Packit 86a02d
    return 0;
Packit 86a02d
Packit 86a02d
  /* Create the cache and config files as necessary.  */
Packit 86a02d
  if (stat(cache_path, &st) != 0 && mkdir(cache_path, 0777) < 0)
Packit 86a02d
    return -errno;
Packit 86a02d
Packit 86a02d
  int fd = -1;
Packit 86a02d
Packit 86a02d
  /* init cleaning interval config file.  */
Packit 86a02d
  fd = open(interval_path, O_CREAT | O_RDWR, 0666);
Packit 86a02d
  if (fd < 0)
Packit 86a02d
    return -errno;
Packit 86a02d
Packit 86a02d
  if (dprintf(fd, "%ld", cache_clean_default_interval_s) < 0)
Packit 86a02d
    return -errno;
Packit 86a02d
Packit 86a02d
  /* init max age config file.  */
Packit 86a02d
  if (stat(maxage_path, &st) != 0
Packit 86a02d
      && (fd = open(maxage_path, O_CREAT | O_RDWR, 0666)) < 0)
Packit 86a02d
    return -errno;
Packit 86a02d
Packit 86a02d
  if (dprintf(fd, "%ld", cache_default_max_unused_age_s) < 0)
Packit 86a02d
    return -errno;
Packit 86a02d
Packit 86a02d
  return 0;
Packit 86a02d
}
Packit 86a02d
Packit 86a02d
Packit 86a02d
/* Delete any files that have been unmodied for a period
Packit 86a02d
   longer than $DEBUGINFOD_CACHE_CLEAN_INTERVAL_S.  */
Packit 86a02d
static int
Packit 86a02d
debuginfod_clean_cache(debuginfod_client *c,
Packit 86a02d
		       char *cache_path, char *interval_path,
Packit 86a02d
		       char *max_unused_path)
Packit 86a02d
{
Packit 86a02d
  struct stat st;
Packit 86a02d
  FILE *interval_file;
Packit 86a02d
  FILE *max_unused_file;
Packit 86a02d
Packit 86a02d
  if (stat(interval_path, &st) == -1)
Packit 86a02d
    {
Packit 86a02d
      /* Create new interval file.  */
Packit 86a02d
      interval_file = fopen(interval_path, "w");
Packit 86a02d
Packit 86a02d
      if (interval_file == NULL)
Packit 86a02d
        return -errno;
Packit 86a02d
Packit 86a02d
      int rc = fprintf(interval_file, "%ld", cache_clean_default_interval_s);
Packit 86a02d
      fclose(interval_file);
Packit 86a02d
Packit 86a02d
      if (rc < 0)
Packit 86a02d
        return -errno;
Packit 86a02d
    }
Packit 86a02d
Packit 86a02d
  /* Check timestamp of interval file to see whether cleaning is necessary.  */
Packit 86a02d
  time_t clean_interval;
Packit 86a02d
  interval_file = fopen(interval_path, "r");
Packit 86a02d
  if (fscanf(interval_file, "%ld", &clean_interval) != 1)
Packit 86a02d
    clean_interval = cache_clean_default_interval_s;
Packit 86a02d
  fclose(interval_file);
Packit 86a02d
Packit 86a02d
  if (time(NULL) - st.st_mtime < clean_interval)
Packit 86a02d
    /* Interval has not passed, skip cleaning.  */
Packit 86a02d
    return 0;
Packit 86a02d
Packit 86a02d
  /* Read max unused age value from config file.  */
Packit 86a02d
  time_t max_unused_age;
Packit 86a02d
  max_unused_file = fopen(max_unused_path, "r");
Packit 86a02d
  if (max_unused_file)
Packit 86a02d
    {
Packit 86a02d
      if (fscanf(max_unused_file, "%ld", &max_unused_age) != 1)
Packit 86a02d
        max_unused_age = cache_default_max_unused_age_s;
Packit 86a02d
      fclose(max_unused_file);
Packit 86a02d
    }
Packit 86a02d
  else
Packit 86a02d
    max_unused_age = cache_default_max_unused_age_s;
Packit 86a02d
Packit 86a02d
  char * const dirs[] = { cache_path, NULL, };
Packit 86a02d
Packit 86a02d
  FTS *fts = fts_open(dirs, 0, NULL);
Packit 86a02d
  if (fts == NULL)
Packit 86a02d
    return -errno;
Packit 86a02d
Packit 86a02d
  FTSENT *f;
Packit 86a02d
  long files = 0;
Packit 86a02d
  while ((f = fts_read(fts)) != NULL)
Packit 86a02d
    {
Packit 86a02d
      files++;
Packit 86a02d
      if (c->progressfn) /* inform/check progress callback */
Packit 86a02d
        if ((c->progressfn) (c, files, 0))
Packit 86a02d
          break;
Packit 86a02d
Packit 86a02d
      switch (f->fts_info)
Packit 86a02d
        {
Packit 86a02d
        case FTS_F:
Packit 86a02d
          /* delete file if max_unused_age has been met or exceeded.  */
Packit 86a02d
          /* XXX consider extra effort to clean up old tmp files */
Packit 86a02d
          if (time(NULL) - f->fts_statp->st_atime >= max_unused_age)
Packit 86a02d
            unlink (f->fts_path);
Packit 86a02d
          break;
Packit 86a02d
Packit 86a02d
        case FTS_DP:
Packit 86a02d
          /* Remove if empty. */
Packit 86a02d
          (void) rmdir (f->fts_path);
Packit 86a02d
          break;
Packit 86a02d
Packit 86a02d
        default:
Packit 86a02d
          ;
Packit 86a02d
        }
Packit 86a02d
    }
Packit 86a02d
  fts_close(fts);
Packit 86a02d
Packit 86a02d
  /* Update timestamp representing when the cache was last cleaned.  */
Packit 86a02d
  utime (interval_path, NULL);
Packit 86a02d
  return 0;
Packit 86a02d
}
Packit 86a02d
Packit 86a02d
Packit 86a02d
#define MAX_BUILD_ID_BYTES 64
Packit 86a02d
Packit 86a02d
Packit 86a02d
/* Query each of the server URLs found in $DEBUGINFOD_URLS for the file
Packit 86a02d
   with the specified build-id, type (debuginfo, executable or source)
Packit 86a02d
   and filename. filename may be NULL. If found, return a file
Packit 86a02d
   descriptor for the target, otherwise return an error code.
Packit 86a02d
*/
Packit 86a02d
static int
Packit 86a02d
debuginfod_query_server (debuginfod_client *c,
Packit 86a02d
			 const unsigned char *build_id,
Packit 86a02d
                         int build_id_len,
Packit 86a02d
                         const char *type,
Packit 86a02d
                         const char *filename,
Packit 86a02d
                         char **path)
Packit 86a02d
{
Packit 86a02d
  char *urls_envvar;
Packit 86a02d
  char *server_urls;
Packit 86a02d
  char cache_path[PATH_MAX];
Packit 86a02d
  char maxage_path[PATH_MAX*3]; /* These *3 multipliers are to shut up gcc -Wformat-truncation */
Packit 86a02d
  char interval_path[PATH_MAX*4];
Packit 86a02d
  char target_cache_dir[PATH_MAX*2];
Packit 86a02d
  char target_cache_path[PATH_MAX*4];
Packit 86a02d
  char target_cache_tmppath[PATH_MAX*5];
Packit 86a02d
  char suffix[PATH_MAX*2];
Packit 86a02d
  char build_id_bytes[MAX_BUILD_ID_BYTES * 2 + 1];
Packit 86a02d
  int rc;
Packit 86a02d
Packit 86a02d
  /* Is there any server we can query?  If not, don't do any work,
Packit 86a02d
     just return with ENOSYS.  Don't even access the cache.  */
Packit 86a02d
  urls_envvar = getenv(server_urls_envvar);
Packit 86a02d
  if (urls_envvar == NULL || urls_envvar[0] == '\0')
Packit 86a02d
    {
Packit 86a02d
      rc = -ENOSYS;
Packit 86a02d
      goto out;
Packit 86a02d
    }
Packit 86a02d
Packit 86a02d
  /* Copy lowercase hex representation of build_id into buf.  */
Packit 86a02d
  if ((build_id_len >= MAX_BUILD_ID_BYTES) ||
Packit 86a02d
      (build_id_len == 0 &&
Packit 86a02d
       sizeof(build_id_bytes) > MAX_BUILD_ID_BYTES*2 + 1))
Packit 86a02d
    return -EINVAL;
Packit 86a02d
  if (build_id_len == 0) /* expect clean hexadecimal */
Packit 86a02d
    strcpy (build_id_bytes, (const char *) build_id);
Packit 86a02d
  else
Packit 86a02d
    for (int i = 0; i < build_id_len; i++)
Packit 86a02d
      sprintf(build_id_bytes + (i * 2), "%02x", build_id[i]);
Packit 86a02d
Packit 86a02d
  if (filename != NULL)
Packit 86a02d
    {
Packit 86a02d
      if (filename[0] != '/') // must start with /
Packit 86a02d
        return -EINVAL;
Packit 86a02d
Packit 86a02d
      /* copy the filename to suffix, s,/,#,g */
Packit 86a02d
      unsigned q = 0;
Packit 86a02d
      for (unsigned fi=0; q < PATH_MAX-1; fi++)
Packit 86a02d
        switch (filename[fi])
Packit 86a02d
          {
Packit 86a02d
          case '\0':
Packit 86a02d
            suffix[q] = '\0';
Packit 86a02d
            q = PATH_MAX-1; /* escape for loop too */
Packit 86a02d
            break;
Packit 86a02d
          case '/': /* escape / to prevent dir escape */
Packit 86a02d
            suffix[q++]='#';
Packit 86a02d
            suffix[q++]='#';
Packit 86a02d
            break;
Packit 86a02d
          case '#': /* escape # to prevent /# vs #/ collisions */
Packit 86a02d
            suffix[q++]='#';
Packit 86a02d
            suffix[q++]='_';
Packit 86a02d
            break;
Packit 86a02d
          default:
Packit 86a02d
            suffix[q++]=filename[fi];
Packit 86a02d
          }
Packit 86a02d
      suffix[q] = '\0';
Packit 86a02d
      /* If the DWARF filenames are super long, this could exceed
Packit 86a02d
         PATH_MAX and truncate/collide.  Oh well, that'll teach
Packit 86a02d
         them! */
Packit 86a02d
    }
Packit 86a02d
  else
Packit 86a02d
    suffix[0] = '\0';
Packit 86a02d
Packit 86a02d
  /* set paths needed to perform the query
Packit 86a02d
Packit 86a02d
     example format
Packit 86a02d
     cache_path:        $HOME/.debuginfod_cache
Packit 86a02d
     target_cache_dir:  $HOME/.debuginfod_cache/0123abcd
Packit 86a02d
     target_cache_path: $HOME/.debuginfod_cache/0123abcd/debuginfo
Packit 86a02d
     target_cache_path: $HOME/.debuginfod_cache/0123abcd/source#PATH#TO#SOURCE ?
Packit 86a02d
  */
Packit 86a02d
Packit 86a02d
  if (getenv(cache_path_envvar))
Packit 86a02d
    strcpy(cache_path, getenv(cache_path_envvar));
Packit 86a02d
  else
Packit 86a02d
    {
Packit 86a02d
      if (getenv("HOME"))
Packit 86a02d
        sprintf(cache_path, "%s/%s", getenv("HOME"), cache_default_name);
Packit 86a02d
      else
Packit 86a02d
        sprintf(cache_path, "/%s", cache_default_name);
Packit 86a02d
    }
Packit 86a02d
Packit 86a02d
  /* avoid using snprintf here due to compiler warning.  */
Packit 86a02d
  snprintf(target_cache_dir, sizeof(target_cache_dir), "%s/%s", cache_path, build_id_bytes);
Packit 86a02d
  snprintf(target_cache_path, sizeof(target_cache_path), "%s/%s%s", target_cache_dir, type, suffix);
Packit 86a02d
  snprintf(target_cache_tmppath, sizeof(target_cache_tmppath), "%s.XXXXXX", target_cache_path);
Packit 86a02d
Packit 86a02d
  /* XXX combine these */
Packit 86a02d
  snprintf(interval_path, sizeof(interval_path), "%s/%s", cache_path, cache_clean_interval_filename);
Packit 86a02d
  snprintf(maxage_path, sizeof(maxage_path), "%s/%s", cache_path, cache_max_unused_age_filename);
Packit 86a02d
  rc = debuginfod_init_cache(cache_path, interval_path, maxage_path);
Packit 86a02d
  if (rc != 0)
Packit 86a02d
    goto out;
Packit 86a02d
  rc = debuginfod_clean_cache(c, cache_path, interval_path, maxage_path);
Packit 86a02d
  if (rc != 0)
Packit 86a02d
    goto out;
Packit 86a02d
Packit 86a02d
  /* If the target is already in the cache then we are done.  */
Packit 86a02d
  int fd = open (target_cache_path, O_RDONLY);
Packit 86a02d
  if (fd >= 0)
Packit 86a02d
    {
Packit 86a02d
      /* Success!!!! */
Packit 86a02d
      if (path != NULL)
Packit 86a02d
        *path = strdup(target_cache_path);
Packit 86a02d
      return fd;
Packit 86a02d
    }
Packit 86a02d
Packit 86a02d
  if (getenv(server_timeout_envvar))
Packit 86a02d
    server_timeout = atoi (getenv(server_timeout_envvar));
Packit 86a02d
Packit 86a02d
  /* make a copy of the envvar so it can be safely modified.  */
Packit 86a02d
  server_urls = strdup(urls_envvar);
Packit 86a02d
  if (server_urls == NULL)
Packit 86a02d
    {
Packit 86a02d
      rc = -ENOMEM;
Packit 86a02d
      goto out;
Packit 86a02d
    }
Packit 86a02d
  /* thereafter, goto out0 on error*/
Packit 86a02d
Packit 86a02d
  /* create target directory in cache if not found.  */
Packit 86a02d
  struct stat st;
Packit 86a02d
  if (stat(target_cache_dir, &st) == -1 && mkdir(target_cache_dir, 0700) < 0)
Packit 86a02d
    {
Packit 86a02d
      rc = -errno;
Packit 86a02d
      goto out0;
Packit 86a02d
    }
Packit 86a02d
Packit 86a02d
  /* NB: write to a temporary file first, to avoid race condition of
Packit 86a02d
     multiple clients checking the cache, while a partially-written or empty
Packit 86a02d
     file is in there, being written from libcurl. */
Packit 86a02d
  fd = mkstemp (target_cache_tmppath);
Packit 86a02d
  if (fd < 0)
Packit 86a02d
    {
Packit 86a02d
      rc = -errno;
Packit 86a02d
      goto out0;
Packit 86a02d
    }
Packit 86a02d
Packit 86a02d
  /* Count number of URLs.  */
Packit 86a02d
  int num_urls = 0;
Packit 86a02d
  for (int i = 0; server_urls[i] != '\0'; i++)
Packit 86a02d
    if (server_urls[i] != url_delim_char
Packit 86a02d
        && (i == 0 || server_urls[i - 1] == url_delim_char))
Packit 86a02d
      num_urls++;
Packit 86a02d
Packit 86a02d
  /* Tracks which handle should write to fd. Set to the first
Packit 86a02d
     handle that is ready to write the target file to the cache.  */
Packit 86a02d
  CURL *target_handle = NULL;
Packit 86a02d
  struct handle_data *data = malloc(sizeof(struct handle_data) * num_urls);
Packit 86a02d
Packit 86a02d
  /* Initalize handle_data with default values. */
Packit 86a02d
  for (int i = 0; i < num_urls; i++)
Packit 86a02d
    {
Packit 86a02d
      data[i].handle = NULL;
Packit 86a02d
      data[i].fd = -1;
Packit 86a02d
    }
Packit 86a02d
Packit 86a02d
  CURLM *curlm = curl_multi_init();
Packit 86a02d
  if (curlm == NULL)
Packit 86a02d
    {
Packit 86a02d
      rc = -ENETUNREACH;
Packit 86a02d
      goto out0;
Packit 86a02d
    }
Packit 86a02d
  /* thereafter, goto out1 on error.  */
Packit 86a02d
Packit 86a02d
  char *strtok_saveptr;
Packit 86a02d
  char *server_url = strtok_r(server_urls, url_delim, &strtok_saveptr);
Packit 86a02d
Packit 86a02d
  /* Initialize each handle.  */
Packit 86a02d
  for (int i = 0; i < num_urls && server_url != NULL; i++)
Packit 86a02d
    {
Packit 86a02d
      data[i].fd = fd;
Packit 86a02d
      data[i].target_handle = &target_handle;
Packit 86a02d
      data[i].handle = curl_easy_init();
Packit 86a02d
Packit 86a02d
      if (data[i].handle == NULL)
Packit 86a02d
        {
Packit 86a02d
          rc = -ENETUNREACH;
Packit 86a02d
          goto out1;
Packit 86a02d
        }
Packit 86a02d
Packit 86a02d
      /* Build handle url. Tolerate both  http://foo:999  and
Packit 86a02d
         http://foo:999/  forms */
Packit 86a02d
      char *slashbuildid;
Packit 86a02d
      if (strlen(server_url) > 1 && server_url[strlen(server_url)-1] == '/')
Packit 86a02d
        slashbuildid = "buildid";
Packit 86a02d
      else
Packit 86a02d
        slashbuildid = "/buildid";
Packit 86a02d
Packit 86a02d
      if (filename) /* must start with / */
Packit 86a02d
        snprintf(data[i].url, PATH_MAX, "%s%s/%s/%s%s", server_url,
Packit 86a02d
                 slashbuildid, build_id_bytes, type, filename);
Packit 86a02d
      else
Packit 86a02d
        snprintf(data[i].url, PATH_MAX, "%s%s/%s/%s", server_url,
Packit 86a02d
                 slashbuildid, build_id_bytes, type);
Packit 86a02d
Packit 86a02d
      curl_easy_setopt(data[i].handle, CURLOPT_URL, data[i].url);
Packit 86a02d
      curl_easy_setopt(data[i].handle,
Packit 86a02d
                       CURLOPT_WRITEFUNCTION,
Packit 86a02d
                       debuginfod_write_callback);
Packit 86a02d
      curl_easy_setopt(data[i].handle, CURLOPT_WRITEDATA, (void*)&data[i]);
Packit 86a02d
      curl_easy_setopt(data[i].handle, CURLOPT_TIMEOUT, (long) server_timeout);
Packit 86a02d
      curl_easy_setopt(data[i].handle, CURLOPT_FILETIME, (long) 1);
Packit 86a02d
      curl_easy_setopt(data[i].handle, CURLOPT_FOLLOWLOCATION, (long) 1);
Packit 86a02d
      curl_easy_setopt(data[i].handle, CURLOPT_FAILONERROR, (long) 1);
Packit 86a02d
      curl_easy_setopt(data[i].handle, CURLOPT_NOSIGNAL, (long) 1);
Packit 86a02d
      curl_easy_setopt(data[i].handle, CURLOPT_AUTOREFERER, (long) 1);
Packit 86a02d
      curl_easy_setopt(data[i].handle, CURLOPT_ACCEPT_ENCODING, "");
Packit 86a02d
      curl_easy_setopt(data[i].handle, CURLOPT_USERAGENT, (void*) PACKAGE_STRING);
Packit 86a02d
Packit 86a02d
      curl_multi_add_handle(curlm, data[i].handle);
Packit 86a02d
      server_url = strtok_r(NULL, url_delim, &strtok_saveptr);
Packit 86a02d
    }
Packit 86a02d
Packit 86a02d
  /* Query servers in parallel.  */
Packit 86a02d
  int still_running;
Packit 86a02d
  long loops = 0;
Packit 86a02d
  do
Packit 86a02d
    {
Packit 86a02d
      if (c->progressfn) /* inform/check progress callback */
Packit 86a02d
        {
Packit 86a02d
          loops ++;
Packit 86a02d
          long pa = loops; /* default params for progress callback */
Packit 86a02d
          long pb = 0;
Packit 86a02d
          if (target_handle) /* we've committed to a server; report its download progress */
Packit 86a02d
            {
Packit 86a02d
              CURLcode curl_res;
Packit 86a02d
#ifdef CURLINFO_SIZE_DOWNLOAD_T
Packit 86a02d
              curl_off_t dl;
Packit 86a02d
              curl_res = curl_easy_getinfo(target_handle,
Packit 86a02d
                                           CURLINFO_SIZE_DOWNLOAD_T,
Packit 86a02d
                                           &dl);
Packit 86a02d
              if (curl_res == 0 && dl >= 0)
Packit 86a02d
                pa = (dl > LONG_MAX ? LONG_MAX : (long)dl);
Packit 86a02d
#else
Packit 86a02d
              double dl;
Packit 86a02d
              curl_res = curl_easy_getinfo(target_handle,
Packit 86a02d
                                           CURLINFO_SIZE_DOWNLOAD,
Packit 86a02d
                                           &dl);
Packit 86a02d
              if (curl_res == 0)
Packit 86a02d
                pa = (dl > LONG_MAX ? LONG_MAX : (long)dl);
Packit 86a02d
#endif
Packit 86a02d
Packit 86a02d
#ifdef CURLINFO_CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
Packit 86a02d
              curl_off_t cl;
Packit 86a02d
              curl_res = curl_easy_getinfo(target_handle,
Packit 86a02d
                                           CURLINFO_CONTENT_LENGTH_DOWNLOAD_T,
Packit 86a02d
                                           &cl);
Packit 86a02d
              if (curl_res == 0 && cl >= 0)
Packit 86a02d
                pb = (cl > LONG_MAX ? LONG_MAX : (long)cl);
Packit 86a02d
#else
Packit 86a02d
              double cl;
Packit 86a02d
              curl_res = curl_easy_getinfo(target_handle,
Packit 86a02d
                                           CURLINFO_CONTENT_LENGTH_DOWNLOAD,
Packit 86a02d
                                           &cl);
Packit 86a02d
              if (curl_res == 0)
Packit 86a02d
                pb = (cl > LONG_MAX ? LONG_MAX : (long)cl);
Packit 86a02d
#endif
Packit 86a02d
            }
Packit 86a02d
Packit 86a02d
          if ((*c->progressfn) (c, pa, pb))
Packit 86a02d
            break;
Packit 86a02d
        }
Packit 86a02d
Packit 86a02d
      /* Wait 1 second, the minimum DEBUGINFOD_TIMEOUT.  */
Packit 86a02d
      curl_multi_wait(curlm, NULL, 0, 1000, NULL);
Packit 86a02d
Packit 86a02d
      /* If the target file has been found, abort the other queries.  */
Packit 86a02d
      if (target_handle != NULL)
Packit 86a02d
        for (int i = 0; i < num_urls; i++)
Packit 86a02d
          if (data[i].handle != target_handle)
Packit 86a02d
            curl_multi_remove_handle(curlm, data[i].handle);
Packit 86a02d
Packit 86a02d
      CURLMcode curlm_res = curl_multi_perform(curlm, &still_running);
Packit 86a02d
      if (curlm_res != CURLM_OK)
Packit 86a02d
        {
Packit 86a02d
          switch (curlm_res)
Packit 86a02d
            {
Packit 86a02d
            case CURLM_CALL_MULTI_PERFORM: continue;
Packit 86a02d
            case CURLM_OUT_OF_MEMORY: rc = -ENOMEM; break;
Packit 86a02d
            default: rc = -ENETUNREACH; break;
Packit 86a02d
            }
Packit 86a02d
          goto out1;
Packit 86a02d
        }
Packit 86a02d
    } while (still_running);
Packit 86a02d
Packit 86a02d
  /* Check whether a query was successful. If so, assign its handle
Packit 86a02d
     to verified_handle.  */
Packit 86a02d
  int num_msg;
Packit 86a02d
  rc = -ENOENT;
Packit 86a02d
  CURL *verified_handle = NULL;
Packit 86a02d
  do
Packit 86a02d
    {
Packit 86a02d
      CURLMsg *msg;
Packit 86a02d
Packit 86a02d
      msg = curl_multi_info_read(curlm, &num_msg);
Packit 86a02d
      if (msg != NULL && msg->msg == CURLMSG_DONE)
Packit 86a02d
        {
Packit 86a02d
          if (msg->data.result != CURLE_OK)
Packit 86a02d
            {
Packit 86a02d
              /* Unsucessful query, determine error code.  */
Packit 86a02d
              switch (msg->data.result)
Packit 86a02d
                {
Packit 86a02d
                case CURLE_COULDNT_RESOLVE_HOST: rc = -EHOSTUNREACH; break; // no NXDOMAIN
Packit 86a02d
                case CURLE_URL_MALFORMAT: rc = -EINVAL; break;
Packit 86a02d
                case CURLE_COULDNT_CONNECT: rc = -ECONNREFUSED; break;
Packit 86a02d
                case CURLE_REMOTE_ACCESS_DENIED: rc = -EACCES; break;
Packit 86a02d
                case CURLE_WRITE_ERROR: rc = -EIO; break;
Packit 86a02d
                case CURLE_OUT_OF_MEMORY: rc = -ENOMEM; break;
Packit 86a02d
                case CURLE_TOO_MANY_REDIRECTS: rc = -EMLINK; break;
Packit 86a02d
                case CURLE_SEND_ERROR: rc = -ECONNRESET; break;
Packit 86a02d
                case CURLE_RECV_ERROR: rc = -ECONNRESET; break;
Packit 86a02d
                case CURLE_OPERATION_TIMEDOUT: rc = -ETIME; break;
Packit 86a02d
                default: rc = -ENOENT; break;
Packit 86a02d
                }
Packit 86a02d
            }
Packit 86a02d
          else
Packit 86a02d
            {
Packit 86a02d
              /* Query completed without an error. Confirm that the
Packit 86a02d
                 response code is 200 and set verified_handle.  */
Packit 86a02d
              long resp_code = 500;
Packit 86a02d
              CURLcode curl_res;
Packit 86a02d
Packit 86a02d
              curl_res = curl_easy_getinfo(target_handle,
Packit 86a02d
                                           CURLINFO_RESPONSE_CODE,
Packit 86a02d
                                           &resp_code);
Packit 86a02d
Packit 86a02d
              if (curl_res == CURLE_OK
Packit 86a02d
                  && resp_code == 200
Packit 86a02d
                  && msg->easy_handle != NULL)
Packit 86a02d
                {
Packit 86a02d
                  verified_handle = msg->easy_handle;
Packit 86a02d
                  break;
Packit 86a02d
                }
Packit 86a02d
            }
Packit 86a02d
        }
Packit 86a02d
    } while (num_msg > 0);
Packit 86a02d
Packit 86a02d
  if (verified_handle == NULL)
Packit 86a02d
    goto out1;
Packit 86a02d
Packit 86a02d
  /* we've got one!!!! */
Packit 86a02d
  time_t mtime;
Packit 86a02d
  CURLcode curl_res = curl_easy_getinfo(verified_handle, CURLINFO_FILETIME, (void*) &mtime);
Packit 86a02d
  if (curl_res != CURLE_OK)
Packit 86a02d
    mtime = time(NULL); /* fall back to current time */
Packit 86a02d
Packit 86a02d
  struct timeval tvs[2];
Packit 86a02d
  tvs[0].tv_sec = tvs[1].tv_sec = mtime;
Packit 86a02d
  tvs[0].tv_usec = tvs[1].tv_usec = 0;
Packit 86a02d
  (void) futimes (fd, tvs);  /* best effort */
Packit 86a02d
Packit 86a02d
  /* rename tmp->real */
Packit 86a02d
  rc = rename (target_cache_tmppath, target_cache_path);
Packit 86a02d
  if (rc < 0)
Packit 86a02d
    {
Packit 86a02d
      rc = -errno;
Packit 86a02d
      goto out1;
Packit 86a02d
      /* Perhaps we need not give up right away; could retry or something ... */
Packit 86a02d
    }
Packit 86a02d
Packit 86a02d
  /* Success!!!! */
Packit 86a02d
  for (int i = 0; i < num_urls; i++)
Packit 86a02d
    curl_easy_cleanup(data[i].handle);
Packit 86a02d
Packit 86a02d
  curl_multi_cleanup (curlm);
Packit 86a02d
  free (data);
Packit 86a02d
  free (server_urls);
Packit 86a02d
  /* don't close fd - we're returning it */
Packit 86a02d
  /* don't unlink the tmppath; it's already been renamed. */
Packit 86a02d
  if (path != NULL)
Packit 86a02d
   *path = strdup(target_cache_path);
Packit 86a02d
Packit 86a02d
  return fd;
Packit 86a02d
Packit 86a02d
/* error exits */
Packit 86a02d
 out1:
Packit 86a02d
  for (int i = 0; i < num_urls; i++)
Packit 86a02d
    curl_easy_cleanup(data[i].handle);
Packit 86a02d
Packit 86a02d
  curl_multi_cleanup(curlm);
Packit 86a02d
  unlink (target_cache_tmppath);
Packit 86a02d
  (void) rmdir (target_cache_dir); /* nop if not empty */
Packit 86a02d
  free(data);
Packit 86a02d
  close (fd);
Packit 86a02d
Packit 86a02d
 out0:
Packit 86a02d
  free (server_urls);
Packit 86a02d
Packit 86a02d
 out:
Packit 86a02d
  return rc;
Packit 86a02d
}
Packit 86a02d
Packit 86a02d
/* See debuginfod.h  */
Packit 86a02d
debuginfod_client  *
Packit 86a02d
debuginfod_begin (void)
Packit 86a02d
{
Packit 86a02d
  debuginfod_client *client;
Packit 86a02d
  size_t size = sizeof (struct debuginfod_client);
Packit 86a02d
  client = (debuginfod_client *) malloc (size);
Packit 86a02d
  if (client != NULL)
Packit 86a02d
    client->progressfn = NULL;
Packit 86a02d
  return client;
Packit 86a02d
}
Packit 86a02d
Packit 86a02d
void
Packit 86a02d
debuginfod_end (debuginfod_client *client)
Packit 86a02d
{
Packit 86a02d
  free (client);
Packit 86a02d
}
Packit 86a02d
Packit 86a02d
int
Packit 86a02d
debuginfod_find_debuginfo (debuginfod_client *client,
Packit 86a02d
			   const unsigned char *build_id, int build_id_len,
Packit 86a02d
                           char **path)
Packit 86a02d
{
Packit 86a02d
  return debuginfod_query_server(client, build_id, build_id_len,
Packit 86a02d
                                 "debuginfo", NULL, path);
Packit 86a02d
}
Packit 86a02d
Packit 86a02d
Packit 86a02d
/* See debuginfod.h  */
Packit 86a02d
int
Packit 86a02d
debuginfod_find_executable(debuginfod_client *client,
Packit 86a02d
			   const unsigned char *build_id, int build_id_len,
Packit 86a02d
                           char **path)
Packit 86a02d
{
Packit 86a02d
  return debuginfod_query_server(client, build_id, build_id_len,
Packit 86a02d
                                 "executable", NULL, path);
Packit 86a02d
}
Packit 86a02d
Packit 86a02d
/* See debuginfod.h  */
Packit 86a02d
int debuginfod_find_source(debuginfod_client *client,
Packit 86a02d
			   const unsigned char *build_id, int build_id_len,
Packit 86a02d
                           const char *filename, char **path)
Packit 86a02d
{
Packit 86a02d
  return debuginfod_query_server(client, build_id, build_id_len,
Packit 86a02d
                                 "source", filename, path);
Packit 86a02d
}
Packit 86a02d
Packit 86a02d
Packit 86a02d
void
Packit 86a02d
debuginfod_set_progressfn(debuginfod_client *client,
Packit 86a02d
			  debuginfod_progressfn_t fn)
Packit 86a02d
{
Packit 86a02d
  client->progressfn = fn;
Packit 86a02d
}
Packit 86a02d
Packit 86a02d
Packit 86a02d
/* NB: these are thread-unsafe. */
Packit 86a02d
__attribute__((constructor)) attribute_hidden void libdebuginfod_ctor(void)
Packit 86a02d
{
Packit 86a02d
  curl_global_init(CURL_GLOBAL_DEFAULT);
Packit 86a02d
}
Packit 86a02d
Packit 86a02d
/* NB: this is very thread-unsafe: it breaks other threads that are still in libcurl */
Packit 86a02d
__attribute__((destructor)) attribute_hidden void libdebuginfod_dtor(void)
Packit 86a02d
{
Packit 86a02d
  /* ... so don't do this: */
Packit 86a02d
  /* curl_global_cleanup(); */
Packit 86a02d
}