Blame debuginfod/debuginfod-client.c

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