summaryrefslogtreecommitdiff
path: root/debuginfod
diff options
context:
space:
mode:
Diffstat (limited to 'debuginfod')
-rw-r--r--debuginfod/ChangeLog22
-rw-r--r--debuginfod/Makefile.am2
-rw-r--r--debuginfod/debuginfod-client.c732
-rw-r--r--debuginfod/debuginfod-find.c131
-rw-r--r--debuginfod/debuginfod.cxx437
-rw-r--r--debuginfod/debuginfod.h.in30
-rw-r--r--debuginfod/libdebuginfod.map5
7 files changed, 1085 insertions, 274 deletions
diff --git a/debuginfod/ChangeLog b/debuginfod/ChangeLog
index 79f827d9..81ff4d4d 100644
--- a/debuginfod/ChangeLog
+++ b/debuginfod/ChangeLog
@@ -22,6 +22,28 @@
functions used in place of identical previously inline code.
(debuginfod_find_metadata): New function.
+2022-10-31 Aaron Merey <amerey@redhat.com>
+
+ * Makefile.am (libdebuginfod_so_LDLIBS): Add libelf.
+ * debuginfod-client.c (debuginfod_find_section): New function.
+ (path_escape): New function.
+ (extract_section): New function.
+ (cache_find_section): New function.
+ (debuginfod_query_server): Add support for section queries.
+ * debuginfod-find.c (main): Add support for section queries.
+ * debuginfod.cxx (extract_section): New function.
+ (handle_buildid_f_match): Add section parameter. When non-empty,
+ try to create response from section contents.
+ (handle_buildid_r_match): Add section parameter. When non-empty,
+ try to create response from section contents.
+ (handle_buildid_match): Add section parameter. Pass to
+ handle_buildid_{f,r}_match.
+ (handle_buildid): Handle section name when artifacttype is set to
+ "section". Query upstream servers via debuginfod_find_section
+ when necessary.
+ (debuginfod.h.in): Add declaration for debuginfod_find_section.
+ (libdebuginfod.map): Add debuginfod_find_section.
+
2022-10-18 Daniel Thornburgh <dthorn@google.com>
* debuginfod-client.c (debuginfod_query_server): Add DEBUGINFOD_HEADERS_FILE
diff --git a/debuginfod/Makefile.am b/debuginfod/Makefile.am
index 3d6bc26e..602feb6c 100644
--- a/debuginfod/Makefile.am
+++ b/debuginfod/Makefile.am
@@ -97,7 +97,7 @@ libdebuginfod_so_LIBS = libdebuginfod_pic.a
if DUMMY_LIBDEBUGINFOD
libdebuginfod_so_LDLIBS =
else
-libdebuginfod_so_LDLIBS = -lpthread $(libcurl_LIBS) $(fts_LIBS) $(jsonc_LIBS)
+libdebuginfod_so_LDLIBS = -lpthread $(libcurl_LIBS) $(fts_LIBS) $(libelf) $(jsonc_LIBS)
endif
$(LIBDEBUGINFOD_SONAME): $(srcdir)/libdebuginfod.map $(libdebuginfod_so_LIBS)
$(AM_V_CCLD)$(LINK) $(dso_LDFLAGS) -o $@ \
diff --git a/debuginfod/debuginfod-client.c b/debuginfod/debuginfod-client.c
index 4a75eef2..8167911f 100644
--- a/debuginfod/debuginfod-client.c
+++ b/debuginfod/debuginfod-client.c
@@ -1,5 +1,5 @@
/* Retrieve ELF / DWARF / source files from the debuginfod.
- Copyright (C) 2019-2021 Red Hat, Inc.
+ Copyright (C) 2019-2023 Red Hat, Inc.
Copyright (C) 2021, 2022 Mark J. Wielaard <mark@klomp.org>
This file is part of elfutils.
@@ -45,6 +45,7 @@
#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
+#include <gelf.h>
/* We might be building a bootstrap dummy library, which is really simple. */
#ifdef DUMMY_LIBDEBUGINFOD
@@ -56,8 +57,11 @@ int debuginfod_find_executable (debuginfod_client *c, const unsigned char *b,
int s, char **p) { return -ENOSYS; }
int debuginfod_find_source (debuginfod_client *c, const unsigned char *b,
int s, const char *f, char **p) { return -ENOSYS; }
+int debuginfod_find_section (debuginfod_client *c, const unsigned char *b,
+ int s, const char *scn, char **p)
+ { return -ENOSYS; }
int debuginfod_find_metadata (debuginfod_client *c,
- const char *k, const char* v, char** m) { return -ENOSYS; }
+ const char *k, const char *v, char **p) { return -ENOSYS; }
void debuginfod_set_progressfn(debuginfod_client *c,
debuginfod_progressfn_t fn) { }
void debuginfod_set_verbose_fd(debuginfod_client *c, int fd) { }
@@ -136,6 +140,9 @@ struct debuginfod_client
debuginfod_end needs to terminate. */
int default_progressfn_printed_p;
+ /* Indicates whether the last query was cancelled by progressfn. */
+ bool progressfn_cancel;
+
/* File descriptor to output any verbose messages if > 0. */
int verbose_fd;
@@ -166,6 +173,11 @@ static const char *cache_miss_filename = "cache_miss_s";
static const char *cache_max_unused_age_filename = "max_unused_age_s";
static const long cache_default_max_unused_age_s = 604800; /* 1 week */
+/* The metadata_retention_default_s file within the debuginfod cache
+ specifies how long metadata query results should be cached. */
+static const long metadata_retention_default_s = 86400; /* 1 day */
+static const char *metadata_retention_filename = "metadata_retention_s";
+
/* Location of the cache of files downloaded from debuginfods.
The default parent directory is $HOME, or '/' if $HOME doesn't exist. */
static const char *cache_default_name = ".debuginfod_client_cache";
@@ -241,6 +253,8 @@ debuginfod_write_callback (char *ptr, size_t size, size_t nmemb, void *data)
return (size_t) write(d->fd, (void*)ptr, count);
}
+
+
/* handle config file read and write */
static int
debuginfod_config_cache(char *config_path,
@@ -328,7 +342,8 @@ debuginfod_clean_cache(debuginfod_client *c,
return -errno;
regex_t re;
- const char * pattern = ".*/[a-f0-9]+(/debuginfo|/executable|/source.*|)$"; /* include dirs */
+ const char * pattern = ".*/(metadata.*|[a-f0-9]+(/debuginfo|/executable|/source.*|))$"; /* include dirs */
+ /* NB: also includes .../section/ subdirs */
if (regcomp (&re, pattern, REG_EXTENDED | REG_NOSUB) != 0)
return -ENOMEM;
@@ -509,6 +524,7 @@ add_headers_from_file(debuginfod_client *client, const char* filename)
} while (0)
+
/* Offer a basic form of progress tracing */
static int
default_progressfn (debuginfod_client *c, long a, long b)
@@ -576,6 +592,7 @@ header_callback (char * buffer, size_t size, size_t numitems, void * userdata)
return numitems;
}
+
#ifdef HAVE_JSON_C
static size_t
metadata_callback (char * buffer, size_t size, size_t numitems, void * userdata)
@@ -588,7 +605,7 @@ metadata_callback (char * buffer, size_t size, size_t numitems, void * userdata)
temp = realloc(data->metadata, data->metadata_size + numitems + 1);
if (temp == NULL)
return 0;
-
+
memcpy(temp + data->metadata_size, buffer, numitems);
data->metadata = temp;
data->metadata_size += numitems;
@@ -908,21 +925,336 @@ perform_queries(CURLM *curlm, CURL **target_handle, struct handle_data *data, de
}
+/* Copy SRC to DEST, s,/,#,g */
+
+static void
+path_escape (const char *src, char *dest)
+{
+ unsigned q = 0;
+
+ for (unsigned fi=0; q < PATH_MAX-2; fi++) /* -2, escape is 2 chars. */
+ switch (src[fi])
+ {
+ case '\0':
+ dest[q] = '\0';
+ q = PATH_MAX-1; /* escape for loop too */
+ break;
+ case '/': /* escape / to prevent dir escape */
+ dest[q++]='#';
+ dest[q++]='#';
+ break;
+ case '#': /* escape # to prevent /# vs #/ collisions */
+ dest[q++]='#';
+ dest[q++]='_';
+ break;
+ default:
+ dest[q++]=src[fi];
+ }
+
+ dest[q] = '\0';
+}
+
+/* Attempt to read an ELF/DWARF section with name SECTION from FD and write
+ it to a separate file in the debuginfod cache. If successful the absolute
+ path of the separate file containing SECTION will be stored in USR_PATH.
+ FD_PATH is the absolute path for FD.
+
+ If the section cannot be extracted, then return a negative error code.
+ -ENOENT indicates that the parent file was able to be read but the
+ section name was not found. -EEXIST indicates that the section was
+ found but had type SHT_NOBITS. */
+
+int
+extract_section (int fd, const char *section, char *fd_path, char **usr_path)
+{
+ elf_version (EV_CURRENT);
+
+ Elf *elf = elf_begin (fd, ELF_C_READ_MMAP_PRIVATE, NULL);
+ if (elf == NULL)
+ return -EIO;
+
+ size_t shstrndx;
+ int rc = elf_getshdrstrndx (elf, &shstrndx);
+ if (rc < 0)
+ {
+ rc = -EIO;
+ goto out;
+ }
+
+ int sec_fd = -1;
+ char *escaped_name = NULL;
+ char *sec_path_tmp = NULL;
+ Elf_Scn *scn = NULL;
+
+ /* Try to find the target section and copy the contents into a
+ separate file. */
+ while (true)
+ {
+ scn = elf_nextscn (elf, scn);
+ if (scn == NULL)
+ {
+ rc = -ENOENT;
+ goto out;
+ }
+ GElf_Shdr shdr_storage;
+ GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_storage);
+ if (shdr == NULL)
+ {
+ rc = -EIO;
+ goto out;
+ }
+
+ const char *scn_name = elf_strptr (elf, shstrndx, shdr->sh_name);
+ if (scn_name == NULL)
+ {
+ rc = -EIO;
+ goto out;
+ }
+ if (strcmp (scn_name, section) == 0)
+ {
+ /* We found the desired section. */
+ if (shdr->sh_type == SHT_NOBITS)
+ {
+ rc = -EEXIST;
+ goto out;
+ }
+
+ Elf_Data *data = NULL;
+ data = elf_rawdata (scn, NULL);
+ if (data == NULL)
+ {
+ rc = -EIO;
+ goto out;
+ }
+
+ if (data->d_buf == NULL)
+ {
+ rc = -EIO;
+ goto out;
+ }
+
+ /* Compute the absolute filename we'll write the section to.
+ Replace the last component of FD_PATH with the path-escaped
+ section filename. */
+ int i = strlen (fd_path);
+ while (i >= 0)
+ {
+ if (fd_path[i] == '/')
+ {
+ fd_path[i] = '\0';
+ break;
+ }
+ --i;
+ }
+
+ escaped_name = malloc (strlen (section) * 2 + 1);
+ if (escaped_name == NULL)
+ {
+ rc = -ENOMEM;
+ goto out;
+ }
+ path_escape (section, escaped_name);
+
+ rc = asprintf (&sec_path_tmp, "%s/section-%s.XXXXXX",
+ fd_path, escaped_name);
+ if (rc == -1)
+ {
+ rc = -ENOMEM;
+ goto out1;
+ }
+
+ sec_fd = mkstemp (sec_path_tmp);
+ if (sec_fd < 0)
+ {
+ rc = -EIO;
+ goto out2;
+ }
+
+ ssize_t res = write_retry (sec_fd, data->d_buf, data->d_size);
+ if (res < 0 || (size_t) res != data->d_size)
+ {
+ rc = -EIO;
+ goto out3;
+ }
+
+ /* Success. Rename tmp file and update USR_PATH. */
+ char *sec_path;
+ if (asprintf (&sec_path, "%s/section-%s", fd_path, section) == -1)
+ {
+ rc = -ENOMEM;
+ goto out3;
+ }
+
+ rc = rename (sec_path_tmp, sec_path);
+ if (rc < 0)
+ {
+ free (sec_path);
+ rc = -EIO;
+ goto out3;
+ }
+
+ if (usr_path != NULL)
+ *usr_path = sec_path;
+ else
+ free (sec_path);
+ rc = sec_fd;
+ goto out2;
+ }
+ }
+
+out3:
+ close (sec_fd);
+ unlink (sec_path_tmp);
+
+out2:
+ free (sec_path_tmp);
+
+out1:
+ free (escaped_name);
+
+out:
+ elf_end (elf);
+ return rc;
+}
+
+/* Search TARGET_CACHE_DIR for a debuginfo or executable file containing
+ an ELF/DWARF section with name SCN_NAME. If found, extract the section
+ to a separate file in TARGET_CACHE_DIR and return a file descriptor
+ for the section file. The path for this file will be stored in USR_PATH.
+ Return a negative errno if unsuccessful. */
+
+static int
+cache_find_section (const char *scn_name, const char *target_cache_dir,
+ char **usr_path)
+{
+ int fd;
+ int rc = -EEXIST;
+ char parent_path[PATH_MAX];
+
+ /* Check the debuginfo first. */
+ snprintf (parent_path, PATH_MAX, "%s/debuginfo", target_cache_dir);
+ fd = open (parent_path, O_RDONLY);
+ if (fd >= 0)
+ {
+ rc = extract_section (fd, scn_name, parent_path, usr_path);
+ close (fd);
+ }
+
+ /* If the debuginfo file couldn't be found or the section type was
+ SHT_NOBITS, check the executable. */
+ if (rc == -EEXIST)
+ {
+ snprintf (parent_path, PATH_MAX, "%s/executable", target_cache_dir);
+ fd = open (parent_path, O_RDONLY);
+
+ if (fd >= 0)
+ {
+ rc = extract_section (fd, scn_name, parent_path, usr_path);
+ close (fd);
+ }
+ }
+
+ return rc;
+}
+
+
+/* Helper function to create client cache directory.
+ $XDG_CACHE_HOME takes priority over $HOME/.cache.
+ $DEBUGINFOD_CACHE_PATH takes priority over $HOME/.cache and $XDG_CACHE_HOME.
+
+ Return resulting path name or NULL on error. Caller must free resulting string.
+ */
+static char *
+make_cache_path(void)
+{
+ char* cache_path = NULL;
+ int rc = 0;
+ /* Determine location of the cache. The path specified by the debuginfod
+ cache environment variable takes priority. */
+ char *cache_var = getenv(DEBUGINFOD_CACHE_PATH_ENV_VAR);
+ if (cache_var != NULL && strlen (cache_var) > 0)
+ xalloc_str (cache_path, "%s", cache_var);
+ else
+ {
+ /* If a cache already exists in $HOME ('/' if $HOME isn't set), then use
+ that. Otherwise use the XDG cache directory naming format. */
+ xalloc_str (cache_path, "%s/%s", getenv ("HOME") ?: "/", cache_default_name);
+
+ struct stat st;
+ if (stat (cache_path, &st) < 0)
+ {
+ char cachedir[PATH_MAX];
+ char *xdg = getenv ("XDG_CACHE_HOME");
+
+ if (xdg != NULL && strlen (xdg) > 0)
+ snprintf (cachedir, PATH_MAX, "%s", xdg);
+ else
+ snprintf (cachedir, PATH_MAX, "%s/.cache", getenv ("HOME") ?: "/");
+
+ /* Create XDG cache directory if it doesn't exist. */
+ if (stat (cachedir, &st) == 0)
+ {
+ if (! S_ISDIR (st.st_mode))
+ {
+ rc = -EEXIST;
+ goto out1;
+ }
+ }
+ else
+ {
+ rc = mkdir (cachedir, 0700);
+
+ /* Also check for EEXIST and S_ISDIR in case another client just
+ happened to create the cache. */
+ if (rc < 0
+ && (errno != EEXIST
+ || stat (cachedir, &st) != 0
+ || ! S_ISDIR (st.st_mode)))
+ {
+ rc = -errno;
+ goto out1;
+ }
+ }
+
+ free (cache_path);
+ xalloc_str (cache_path, "%s/%s", cachedir, cache_xdg_name);
+ }
+ }
+
+ goto out;
+
+ out1:
+ (void) rc;
+ free (cache_path);
+ cache_path = NULL;
+
+ out:
+ if (cache_path != NULL)
+ (void) mkdir (cache_path, 0700);
+ return cache_path;
+}
+
+
+
/* Query each of the server URLs found in $DEBUGINFOD_URLS for the file
- with the specified build-id, type (debuginfo, executable or source)
- and filename. filename may be NULL. If found, return a file
- descriptor for the target, otherwise return an error code.
+ with the specified build-id and type (debuginfo, executable, source or
+ section). If type is source, then type_arg should be a filename. If
+ type is section, then type_arg should be the name of an ELF/DWARF
+ section. Otherwise type_arg may be NULL. Return a file descriptor
+ for the target if successful, otherwise return an error code.
*/
static int
debuginfod_query_server_by_buildid (debuginfod_client *c,
const unsigned char *build_id,
int build_id_len,
const char *type,
- const char *filename,
+ const char *type_arg,
char **path)
{
char *server_urls;
char *urls_envvar;
+ const char *section = NULL;
+ const char *filename = NULL;
char *cache_path = NULL;
char *maxage_path = NULL;
char *interval_path = NULL;
@@ -935,6 +1267,17 @@ debuginfod_query_server_by_buildid (debuginfod_client *c,
int vfd = c->verbose_fd;
int rc, r;
+ c->progressfn_cancel = false;
+
+ if (strcmp (type, "source") == 0)
+ filename = type_arg;
+ else if (strcmp (type, "section") == 0)
+ {
+ section = type_arg;
+ if (section == NULL)
+ return -EINVAL;
+ }
+
if (vfd >= 0)
{
dprintf (vfd, "debuginfod_find_%s ", type);
@@ -1033,31 +1376,13 @@ debuginfod_query_server_by_buildid (debuginfod_client *c,
goto out;
}
- /* copy the filename to suffix, s,/,#,g */
- unsigned q = 0;
- for (unsigned fi=0; q < PATH_MAX-2; fi++) /* -2, escape is 2 chars. */
- switch (filename[fi])
- {
- case '\0':
- suffix[q] = '\0';
- q = PATH_MAX-1; /* escape for loop too */
- break;
- case '/': /* escape / to prevent dir escape */
- suffix[q++]='#';
- suffix[q++]='#';
- break;
- case '#': /* escape # to prevent /# vs #/ collisions */
- suffix[q++]='#';
- suffix[q++]='_';
- break;
- default:
- suffix[q++]=filename[fi];
- }
- suffix[q] = '\0';
+ path_escape (filename, suffix);
/* If the DWARF filenames are super long, this could exceed
PATH_MAX and truncate/collide. Oh well, that'll teach
them! */
}
+ else if (section != NULL)
+ path_escape (section, suffix);
else
suffix[0] = '\0';
@@ -1065,71 +1390,26 @@ debuginfod_query_server_by_buildid (debuginfod_client *c,
dprintf (vfd, "suffix %s\n", suffix);
/* set paths needed to perform the query
-
- example format
+ example format:
cache_path: $HOME/.cache
target_cache_dir: $HOME/.cache/0123abcd
target_cache_path: $HOME/.cache/0123abcd/debuginfo
target_cache_path: $HOME/.cache/0123abcd/source#PATH#TO#SOURCE ?
-
- $XDG_CACHE_HOME takes priority over $HOME/.cache.
- $DEBUGINFOD_CACHE_PATH takes priority over $HOME/.cache and $XDG_CACHE_HOME.
*/
- /* Determine location of the cache. The path specified by the debuginfod
- cache environment variable takes priority. */
- char *cache_var = getenv(DEBUGINFOD_CACHE_PATH_ENV_VAR);
- if (cache_var != NULL && strlen (cache_var) > 0)
- xalloc_str (cache_path, "%s", cache_var);
- else
+ cache_path = make_cache_path();
+ if (!cache_path)
{
- /* If a cache already exists in $HOME ('/' if $HOME isn't set), then use
- that. Otherwise use the XDG cache directory naming format. */
- xalloc_str (cache_path, "%s/%s", getenv ("HOME") ?: "/", cache_default_name);
-
- struct stat st;
- if (stat (cache_path, &st) < 0)
- {
- char cachedir[PATH_MAX];
- char *xdg = getenv ("XDG_CACHE_HOME");
-
- if (xdg != NULL && strlen (xdg) > 0)
- snprintf (cachedir, PATH_MAX, "%s", xdg);
- else
- snprintf (cachedir, PATH_MAX, "%s/.cache", getenv ("HOME") ?: "/");
-
- /* Create XDG cache directory if it doesn't exist. */
- if (stat (cachedir, &st) == 0)
- {
- if (! S_ISDIR (st.st_mode))
- {
- rc = -EEXIST;
- goto out;
- }
- }
- else
- {
- rc = mkdir (cachedir, 0700);
-
- /* Also check for EEXIST and S_ISDIR in case another client just
- happened to create the cache. */
- if (rc < 0
- && (errno != EEXIST
- || stat (cachedir, &st) != 0
- || ! S_ISDIR (st.st_mode)))
- {
- rc = -errno;
- goto out;
- }
- }
-
- free (cache_path);
- xalloc_str (cache_path, "%s/%s", cachedir, cache_xdg_name);
- }
+ rc = -ENOMEM;
+ goto out;
}
-
xalloc_str (target_cache_dir, "%s/%s", cache_path, build_id_bytes);
- xalloc_str (target_cache_path, "%s/%s%s", target_cache_dir, type, suffix);
+ (void) mkdir (target_cache_dir, 0700); // failures with this mkdir would be caught later too
+
+ if (section != NULL)
+ xalloc_str (target_cache_path, "%s/%s-%s", target_cache_dir, type, suffix);
+ else
+ xalloc_str (target_cache_path, "%s/%s%s", target_cache_dir, type, suffix);
xalloc_str (target_cache_tmppath, "%s.XXXXXX", target_cache_path);
/* XXX combine these */
@@ -1213,6 +1493,18 @@ debuginfod_query_server_by_buildid (debuginfod_client *c,
/* Ensure old 000-permission files are not lingering in the cache. */
unlink(target_cache_path);
+ if (section != NULL)
+ {
+ /* Try to extract the section from a cached file before querying
+ any servers. */
+ rc = cache_find_section (section, target_cache_dir, path);
+
+ /* If the section was found or confirmed to not exist, then we
+ are done. */
+ if (rc >= 0 || rc == -ENOENT)
+ goto out;
+ }
+
long timeout = default_timeout;
const char* timeout_envvar = getenv(DEBUGINFOD_TIMEOUT_ENV_VAR);
if (timeout_envvar != NULL)
@@ -1332,6 +1624,9 @@ debuginfod_query_server_by_buildid (debuginfod_client *c,
snprintf(data[i].url, PATH_MAX, "%s/%s/%s/%s", server_url,
build_id_bytes, type, escaped_string);
}
+ else if (section)
+ snprintf(data[i].url, PATH_MAX, "%s/%s/%s/%s", server_url,
+ build_id_bytes, type, section);
else
snprintf(data[i].url, PATH_MAX, "%s/%s/%s", server_url, build_id_bytes, type);
@@ -1412,6 +1707,8 @@ debuginfod_query_server_by_buildid (debuginfod_client *c,
/* 406 signals that the requested file was too large */
if ( ok0 == CURLE_OK && resp_code == 406)
rc = -EFBIG;
+ else if (section != NULL && resp_code == 503)
+ rc = -EINVAL;
else
rc = -ENOENT;
break;
@@ -1737,23 +2034,80 @@ int debuginfod_find_source(debuginfod_client *client,
"source", filename, path);
}
+int
+debuginfod_find_section (debuginfod_client *client,
+ const unsigned char *build_id, int build_id_len,
+ const char *section, char **path)
+{
+ int rc = debuginfod_query_server_by_buildid(client, build_id, build_id_len,
+ "section", section, path);
+ if (rc != -EINVAL)
+ return rc;
+
+ /* The servers may have lacked support for section queries. Attempt to
+ download the debuginfo or executable containing the section in order
+ to extract it. */
+ rc = -EEXIST;
+ int fd = -1;
+ char *tmp_path = NULL;
+
+ fd = debuginfod_find_debuginfo (client, build_id, build_id_len, &tmp_path);
+ if (client->progressfn_cancel)
+ {
+ if (fd >= 0)
+ {
+ /* This shouldn't happen, but we'll check this condition
+ just in case. */
+ close (fd);
+ free (tmp_path);
+ }
+ return -ENOENT;
+ }
+ if (fd > 0)
+ {
+ rc = extract_section (fd, section, tmp_path, path);
+ close (fd);
+ }
+
+ if (rc == -EEXIST)
+ {
+ /* The section should be found in the executable. */
+ fd = debuginfod_find_executable (client, build_id,
+ build_id_len, &tmp_path);
+ if (fd > 0)
+ {
+ rc = extract_section (fd, section, tmp_path, path);
+ close (fd);
+ }
+ }
+
+ free (tmp_path);
+ return rc;
+}
+
int debuginfod_find_metadata (debuginfod_client *client,
- const char* key, const char* value, char** metadata)
+ const char* key, const char* value, char **path)
{
(void) client;
(void) key;
(void) value;
+ (void) path;
- if (NULL == metadata) return -EINVAL;
#ifdef HAVE_JSON_C
- char *server_urls;
- char *urls_envvar;
- json_object *json_metadata = json_object_new_array();
+ char *server_urls = NULL;
+ char *urls_envvar = NULL;
+ char *cache_path = NULL;
+ char *target_cache_dir = NULL;
+ char *target_cache_path = NULL;
+ char *target_cache_tmppath = NULL;
+ char *key_and_value = NULL;
int rc = 0, r;
int vfd = client->verbose_fd;
-
- if(NULL == json_metadata){
+ struct handle_data *data = NULL;
+
+ json_object *json_metadata = json_object_new_array();
+ if(NULL == json_metadata) {
rc = -ENOMEM;
goto out;
}
@@ -1777,6 +2131,98 @@ int debuginfod_find_metadata (debuginfod_client *client,
goto out;
}
+ /* set paths needed to perform the query
+ example format:
+ cache_path: $HOME/.cache
+ target_cache_dir: $HOME/.cache/metadata
+ target_cache_path: $HOME/.cache/metadata/key=ENCODED&value=ENCODED
+ target_cache_path: $HOME/.cache/metadata/key=ENCODED&value=ENCODED.XXXXXX
+ */
+
+ // libcurl > 7.62ish has curl_url_set()/etc. to construct these things more properly.
+ // curl_easy_escape() is older
+ {
+ CURL *c = curl_easy_init();
+ if (!c) {
+ rc = -ENOMEM;
+ goto out;
+ }
+ char *key_escaped = curl_easy_escape(c, key, 0);
+ char *value_escaped = curl_easy_escape(c, value, 0);
+
+ // fallback to unescaped values in unlikely case of error
+ xalloc_str (key_and_value, "key=%s&value=%s", key_escaped ?: key, value_escaped ?: value);
+ curl_free(value_escaped);
+ curl_free(key_escaped);
+ curl_easy_cleanup(c);
+ }
+
+ /* Check if we have a recent result already in the cache. */
+ cache_path = make_cache_path();
+ if (! cache_path)
+ goto out;
+ xalloc_str (target_cache_dir, "%s/metadata", cache_path);
+ (void) mkdir (target_cache_dir, 0700);
+ xalloc_str (target_cache_path, "%s/%s", target_cache_dir, key_and_value);
+ xalloc_str (target_cache_tmppath, "%s/%s.XXXXXX", target_cache_dir, key_and_value);
+
+ int fd = open(target_cache_path, O_RDONLY);
+ if (fd >= 0)
+ {
+ struct stat st;
+ int metadata_retention = 0;
+ time_t now = time(NULL);
+ char *metadata_retention_path = NULL;
+
+ xalloc_str (metadata_retention_path, "%s/%s", cache_path, metadata_retention_filename);
+ if (metadata_retention_path)
+ {
+ rc = debuginfod_config_cache(metadata_retention_path,
+ metadata_retention_default_s, &st);
+ free (metadata_retention_path);
+ if (rc < 0)
+ rc = 0;
+ }
+ else
+ rc = 0;
+ metadata_retention = rc;
+
+ if (fstat(fd, &st) != 0)
+ {
+ rc = -errno;
+ close (fd);
+ goto out;
+ }
+
+ if (metadata_retention > 0 && (now - st.st_mtime <= metadata_retention))
+ {
+ if (client && client->verbose_fd >= 0)
+ dprintf (client->verbose_fd, "cached metadata %s", key_and_value);
+
+ if (path != NULL)
+ {
+ *path = target_cache_path; // pass over the pointer
+ target_cache_path = NULL; // prevent free() in our own cleanup
+ }
+
+ /* Success!!!! */
+ rc = fd;
+ goto out;
+ }
+
+ /* We don't have to clear the likely-expired cached object here
+ by unlinking. We will shortly make a new request and save
+ results right on top. Erasing here could trigger a TOCTOU
+ race with another thread just finishing a query and passing
+ its results back.
+ */
+ // (void) unlink (target_cache_path);
+
+ close (fd);
+ }
+
+ /* No valid cached metadata found: time to make the queries. */
+
/* Clear the client of previous urls*/
free (client->url);
client->url = NULL;
@@ -1805,11 +2251,12 @@ int debuginfod_find_metadata (debuginfod_client *client,
rc = -ENOMEM;
goto out;
}
+
/* thereafter, goto out1 on error*/
char **server_url_list = NULL;
char *server_url;
- int num_urls;
+ int num_urls = 0;
r = init_server_urls("metadata", server_urls, &server_url_list, &num_urls, vfd);
if(0 != r){
rc = r;
@@ -1820,7 +2267,7 @@ int debuginfod_find_metadata (debuginfod_client *client,
assert (curlm != NULL);
CURL *target_handle = NULL;
- struct handle_data *data = malloc(sizeof(struct handle_data) * num_urls);
+ data = malloc(sizeof(struct handle_data) * num_urls);
if (data == NULL)
{
rc = -ENOMEM;
@@ -1829,7 +2276,6 @@ int debuginfod_find_metadata (debuginfod_client *client,
/* thereafter, goto out2 on error. */
-
/* Initialize handle_data */
for (int i = 0; i < num_urls; i++)
{
@@ -1844,19 +2290,7 @@ int debuginfod_find_metadata (debuginfod_client *client,
data[i].metadata = NULL;
data[i].metadata_size = 0;
- // libcurl > 7.62ish has curl_url_set()/etc. to construct these things more properly.
- // curl_easy_escape() is older
- CURL *c = curl_easy_init();
- if (c) {
- char *key_escaped = curl_easy_escape(c, key, 0);
- char *value_escaped = curl_easy_escape(c, value, 0);
- snprintf(data[i].url, PATH_MAX, "%s?key=%s&value=%s", server_url,
- // fallback to unescaped values in unlikely case of error
- key_escaped ?: key, value_escaped ?: value);
- curl_free(value_escaped);
- curl_free(key_escaped);
- curl_easy_cleanup(c);
- }
+ snprintf(data[i].url, PATH_MAX, "%s?%s", server_url, key_and_value);
r = init_handle(client, metadata_callback, header_callback, &data[i], i, timeout, vfd);
if(0 != r){
@@ -1870,7 +2304,7 @@ int debuginfod_find_metadata (debuginfod_client *client,
if (vfd >= 0)
dprintf (vfd, "Starting %d queries\n",num_urls);
r = perform_queries(curlm, NULL, data, client, num_urls, maxtime, 0, false, vfd);
- if(0 != r){
+ if (0 != r) {
rc = r;
goto out2;
}
@@ -1880,19 +2314,23 @@ int debuginfod_find_metadata (debuginfod_client *client,
available from servers which can be connected with no issues.
If running with additional verbosity, the failure will be noted in stderr */
- /* Building the new json array from all the upstream data
- and cleanup while at it
- */
+ /* Building the new json array from all the upstream data and
+ cleanup while at it.
+ */
for (int i = 0; i < num_urls; i++)
{
curl_multi_remove_handle(curlm, data[i].handle); /* ok to repeat */
- if(NULL == data[i].metadata)
+ curl_easy_cleanup (data[i].handle);
+ free (data[i].response_data);
+
+ if (NULL == data[i].metadata)
{
if (vfd >= 0)
dprintf (vfd, "Query to %s failed with error message:\n\t\"%s\"\n",
data[i].url, data[i].errbuf);
continue;
}
+
json_object *upstream_metadata = json_tokener_parse(data[i].metadata);
if(NULL == upstream_metadata) continue;
// Combine the upstream metadata into the json array
@@ -1903,14 +2341,61 @@ int debuginfod_find_metadata (debuginfod_client *client,
}
json_object_put(upstream_metadata);
- curl_easy_cleanup (data[i].handle);
- free (data[i].response_data);
free (data[i].metadata);
}
- *metadata = strdup(json_object_to_json_string_ext(json_metadata, JSON_C_TO_STRING_PRETTY));
+ /* Because of race with cache cleanup / rmdir, try to mkdir/mkstemp up to twice. */
+ for (int i=0; i<2; i++) {
+ /* (re)create target directory in cache */
+ (void) mkdir(target_cache_dir, 0700); /* files will be 0400 later */
- free (data);
+ /* NB: write to a temporary file first, to avoid race condition of
+ multiple clients checking the cache, while a partially-written or empty
+ file is in there, being written from libcurl. */
+ fd = mkstemp (target_cache_tmppath);
+ if (fd >= 0) break;
+ }
+ if (fd < 0) /* Still failed after two iterations. */
+ {
+ rc = -errno;
+ goto out1;
+ }
+
+
+ /* Plop the complete json_metadata object into the cache. */
+ const char* json_string = json_object_to_json_string_ext(json_metadata, JSON_C_TO_STRING_PRETTY);
+ if (json_string == NULL)
+ {
+ rc = -ENOMEM;
+ goto out1;
+ }
+ ssize_t res = write_retry (fd, json_string, strlen(json_string));
+ (void) lseek(fd, 0, SEEK_SET); // rewind file so client can read it from the top
+
+ /* NB: json_string is auto deleted when json_metadata object is nuked */
+ if (res < 0 || (size_t) res != strlen(json_string))
+ {
+ rc = -EIO;
+ goto out1;
+ }
+ /* PR27571: make cache files casually unwriteable; dirs are already 0700 */
+ (void) fchmod(fd, 0400);
+
+ /* rename tmp->real */
+ rc = rename (target_cache_tmppath, target_cache_path);
+ if (rc < 0)
+ {
+ rc = -errno;
+ goto out1;
+ /* Perhaps we need not give up right away; could retry or something ... */
+ }
+
+ /* don't close fd - we're returning it */
+ /* don't unlink the tmppath; it's already been renamed. */
+ if (path != NULL)
+ *path = strdup(target_cache_path);
+
+ rc = fd;
goto out1;
/* error exits */
@@ -1926,22 +2411,28 @@ out2:
free (data[i].metadata);
}
}
- free(data);
out1:
+ free(data);
+
for (int i = 0; i < num_urls; ++i)
free(server_url_list[i]);
free(server_url_list);
- free (server_urls);
-/* general purpose exit */
out:
+ free (server_urls);
json_object_put(json_metadata);
/* Reset sent headers */
curl_slist_free_all (client->headers);
client->headers = NULL;
client->user_agent_set_p = 0;
+ free (target_cache_dir);
+ free (target_cache_path);
+ free (target_cache_tmppath);
+ free (key_and_value);
+ free (cache_path);
+
return rc;
#else /* ! HAVE_JSON_C */
@@ -1949,6 +2440,7 @@ out:
#endif
}
+
/* Add an outgoing HTTP header. */
int debuginfod_add_http_header (debuginfod_client *client, const char* header)
{
diff --git a/debuginfod/debuginfod-find.c b/debuginfod/debuginfod-find.c
index 4136c567..2df6436d 100644
--- a/debuginfod/debuginfod-find.c
+++ b/debuginfod/debuginfod-find.c
@@ -1,6 +1,6 @@
/* Command-line frontend for retrieving ELF / DWARF / source files
from the debuginfod.
- Copyright (C) 2019-2020 Red Hat, Inc.
+ Copyright (C) 2019-2023 Red Hat, Inc.
This file is part of elfutils.
This file is free software; you can redistribute it and/or modify
@@ -52,8 +52,10 @@ static const char args_doc[] = N_("debuginfo BUILDID\n"
"executable PATH\n"
"source BUILDID /FILENAME\n"
"source PATH /FILENAME\n"
+ "section BUILDID SECTION-NAME\n"
+ "section PATH SECTION-NAME\n"
#ifdef HAVE_JSON_C
- "metadata KEY VALUE"
+ "metadata KEY VALUE\n"
#endif
);
@@ -147,71 +149,54 @@ main(int argc, char** argv)
return 1;
}
-#ifdef HAVE_JSON_C
- if(strcmp(argv[remaining], "metadata") == 0){
- if (remaining+2 == argc)
- {
- fprintf(stderr, "Require KEY and VALUE for \"metadata\"\n");
- return 1;
- }
-
- char* metadata;
- int rc = debuginfod_find_metadata (client, argv[remaining+1], argv[remaining+2],
- &metadata);
-
- if (rc < 0)
- {
- fprintf(stderr, "Server query failed: %s\n", strerror(-rc));
- return 1;
- }
- // Output the metadata to stdout
- printf("%s\n", metadata);
- free(metadata);
- return 0;
- }
-#endif
-
/* If we were passed an ELF file name in the BUILDID slot, look in there. */
unsigned char* build_id = (unsigned char*) argv[remaining+1];
int build_id_len = 0; /* assume text */
-
- int any_non_hex = 0;
- int i;
- for (i = 0; build_id[i] != '\0'; i++)
- if ((build_id[i] >= '0' && build_id[i] <= '9') ||
- (build_id[i] >= 'a' && build_id[i] <= 'f'))
- ;
- else
- any_non_hex = 1;
-
- int fd = -1;
Elf* elf = NULL;
- if (any_non_hex) /* raw build-id */
- {
- fd = open ((char*) build_id, O_RDONLY);
- if (fd < 0)
- fprintf (stderr, "Cannot open %s: %s\n", build_id, strerror(errno));
- }
- if (fd >= 0)
- {
- elf = dwelf_elf_begin (fd);
- if (elf == NULL)
- fprintf (stderr, "Cannot open as ELF file %s: %s\n", build_id,
- elf_errmsg (-1));
- }
- if (elf != NULL)
+
+ /* Process optional buildid given via ELF file name, for some query types only. */
+ if (strcmp(argv[remaining], "debuginfo") == 0
+ || strcmp(argv[remaining], "executable") == 0
+ || strcmp(argv[remaining], "source") == 0
+ || strcmp(argv[remaining], "section") == 0)
{
- const void *extracted_build_id;
- ssize_t s = dwelf_elf_gnu_build_id(elf, &extracted_build_id);
- if (s > 0)
+ int any_non_hex = 0;
+ int i;
+ for (i = 0; build_id[i] != '\0'; i++)
+ if ((build_id[i] >= '0' && build_id[i] <= '9') ||
+ (build_id[i] >= 'a' && build_id[i] <= 'f'))
+ ;
+ else
+ any_non_hex = 1;
+
+ int fd = -1;
+ if (any_non_hex) /* raw build-id */
+ {
+ fd = open ((char*) build_id, O_RDONLY);
+ if (fd < 0)
+ fprintf (stderr, "Cannot open %s: %s\n", build_id, strerror(errno));
+ }
+ if (fd >= 0)
{
- /* Success: replace the build_id pointer/len with the binary blob
- that elfutils is keeping for us. It'll remain valid until elf_end(). */
- build_id = (unsigned char*) extracted_build_id;
- build_id_len = s;
+ elf = dwelf_elf_begin (fd);
+ if (elf == NULL)
+ fprintf (stderr, "Cannot open as ELF file %s: %s\n", build_id,
+ elf_errmsg (-1));
+ }
+ if (elf != NULL)
+ {
+ const void *extracted_build_id;
+ ssize_t s = dwelf_elf_gnu_build_id(elf, &extracted_build_id);
+ if (s > 0)
+ {
+ /* Success: replace the build_id pointer/len with the binary blob
+ that elfutils is keeping for us. It'll remain valid until elf_end(). */
+ build_id = (unsigned char*) extracted_build_id;
+ build_id_len = s;
+ }
+ else
+ fprintf (stderr, "Cannot extract build-id from %s: %s\n", build_id, elf_errmsg(-1));
}
- else
- fprintf (stderr, "Cannot extract build-id from %s: %s\n", build_id, elf_errmsg(-1));
}
char *cache_name;
@@ -239,6 +224,30 @@ main(int argc, char** argv)
build_id, build_id_len,
argv[remaining+2], &cache_name);
}
+ else if (strcmp(argv[remaining], "section") == 0)
+ {
+ if (remaining+2 >= argc)
+ {
+ fprintf(stderr,
+ "If FILETYPE is \"section\" then a section name must be given\n");
+ return 1;
+ }
+ rc = debuginfod_find_section(client, build_id, build_id_len,
+ argv[remaining+2], &cache_name);
+ }
+#ifdef HAVE_JSON_C
+ else if (strcmp(argv[remaining], "metadata") == 0) /* no buildid! */
+ {
+ if (remaining+2 == argc)
+ {
+ fprintf(stderr, "Require KEY and VALUE for \"metadata\"\n");
+ return 1;
+ }
+
+ rc = debuginfod_find_metadata (client, argv[remaining+1], argv[remaining+2],
+ &cache_name);
+ }
+#endif
else
{
argp_help (&argp, stderr, ARGP_HELP_USAGE, argv[0]);
@@ -258,8 +267,6 @@ main(int argc, char** argv)
debuginfod_end (client);
if (elf)
elf_end(elf);
- if (fd >= 0)
- close (fd);
if (rc < 0)
{
diff --git a/debuginfod/debuginfod.cxx b/debuginfod/debuginfod.cxx
index 105c3908..0be01fba 100644
--- a/debuginfod/debuginfod.cxx
+++ b/debuginfod/debuginfod.cxx
@@ -1,5 +1,5 @@
/* Debuginfo-over-http server.
- Copyright (C) 2019-2021 Red Hat, Inc.
+ Copyright (C) 2019-2023 Red Hat, Inc.
Copyright (C) 2021, 2022 Mark J. Wielaard <mark@klomp.org>
This file is part of elfutils.
@@ -56,6 +56,7 @@ extern "C" {
#include <unistd.h>
#include <fcntl.h>
#include <netdb.h>
+#include <fnmatch.h>
/* If fts.h is included before config.h, its indirect inclusions may not
@@ -1149,66 +1150,6 @@ add_mhd_last_modified (struct MHD_Response *resp, time_t mtime)
add_mhd_response_header (resp, "Cache-Control", "public");
}
-
-
-static struct MHD_Response*
-handle_buildid_f_match (bool internal_req_t,
- int64_t b_mtime,
- const string& b_source0,
- int *result_fd)
-{
- (void) internal_req_t; // ignored
- int fd = open(b_source0.c_str(), O_RDONLY);
- if (fd < 0)
- throw libc_exception (errno, string("open ") + b_source0);
-
- // NB: use manual close(2) in error case instead of defer_dtor, because
- // in the normal case, we want to hand the fd over to libmicrohttpd for
- // file transfer.
-
- struct stat s;
- int rc = fstat(fd, &s);
- if (rc < 0)
- {
- close(fd);
- throw libc_exception (errno, string("fstat ") + b_source0);
- }
-
- if ((int64_t) s.st_mtime != b_mtime)
- {
- if (verbose)
- obatched(clog) << "mtime mismatch for " << b_source0 << endl;
- close(fd);
- return 0;
- }
-
- inc_metric ("http_responses_total","result","file");
- struct MHD_Response* r = MHD_create_response_from_fd ((uint64_t) s.st_size, fd);
- if (r == 0)
- {
- if (verbose)
- obatched(clog) << "cannot create fd-response for " << b_source0 << endl;
- close(fd);
- }
- else
- {
- std::string file = b_source0.substr(b_source0.find_last_of("/")+1, b_source0.length());
- add_mhd_response_header (r, "Content-Type", "application/octet-stream");
- add_mhd_response_header (r, "X-DEBUGINFOD-SIZE",
- to_string(s.st_size).c_str());
- add_mhd_response_header (r, "X-DEBUGINFOD-FILE", file.c_str());
- add_mhd_last_modified (r, s.st_mtime);
- if (verbose > 1)
- obatched(clog) << "serving file " << b_source0 << endl;
- /* libmicrohttpd will close it. */
- if (result_fd)
- *result_fd = fd;
- }
-
- return r;
-}
-
-
// quote all questionable characters of str for safe passage through a sh -c expansion.
static string
shell_escape(const string& str)
@@ -1602,6 +1543,205 @@ public:
};
static libarchive_fdcache fdcache;
+/* Search ELF_FD for an ELF/DWARF section with name SECTION.
+ If found copy the section to a temporary file and return
+ its file descriptor, otherwise return -1.
+
+ The temporary file's mtime will be set to PARENT_MTIME.
+ B_SOURCE should be a description of the parent file suitable
+ for printing to the log. */
+
+static int
+extract_section (int elf_fd, int64_t parent_mtime,
+ const string& b_source, const string& section)
+{
+ /* Search the fdcache. */
+ struct stat fs;
+ int fd = fdcache.lookup (b_source, section);
+ if (fd >= 0)
+ {
+ if (fstat (fd, &fs) != 0)
+ {
+ if (verbose)
+ obatched (clog) << "cannot fstate fdcache "
+ << b_source << " " << section << endl;
+ close (fd);
+ return -1;
+ }
+ if ((int64_t) fs.st_mtime != parent_mtime)
+ {
+ if (verbose)
+ obatched(clog) << "mtime mismatch for "
+ << b_source << " " << section << endl;
+ close (fd);
+ return -1;
+ }
+ /* Success. */
+ return fd;
+ }
+
+ Elf *elf = elf_begin (elf_fd, ELF_C_READ_MMAP_PRIVATE, NULL);
+ if (elf == NULL)
+ return -1;
+
+ /* Try to find the section and copy the contents into a separate file. */
+ try
+ {
+ size_t shstrndx;
+ int rc = elf_getshdrstrndx (elf, &shstrndx);
+ if (rc < 0)
+ throw elfutils_exception (rc, "getshdrstrndx");
+
+ Elf_Scn *scn = NULL;
+ while (true)
+ {
+ scn = elf_nextscn (elf, scn);
+ if (scn == NULL)
+ break;
+ GElf_Shdr shdr_storage;
+ GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_storage);
+ if (shdr == NULL)
+ break;
+
+ const char *scn_name = elf_strptr (elf, shstrndx, shdr->sh_name);
+ if (scn_name == NULL)
+ break;
+ if (scn_name == section)
+ {
+ Elf_Data *data = NULL;
+
+ /* We found the desired section. */
+ data = elf_rawdata (scn, NULL);
+ if (data == NULL)
+ throw elfutils_exception (elf_errno (), "elfraw_data");
+ if (data->d_buf == NULL)
+ {
+ obatched(clog) << "section " << section
+ << " is empty" << endl;
+ break;
+ }
+
+ /* Create temporary file containing the section. */
+ char *tmppath = NULL;
+ rc = asprintf (&tmppath, "%s/debuginfod.XXXXXX", tmpdir.c_str());
+ if (rc < 0)
+ throw libc_exception (ENOMEM, "cannot allocate tmppath");
+ defer_dtor<void*,void> tmmpath_freer (tmppath, free);
+ fd = mkstemp (tmppath);
+ if (fd < 0)
+ throw libc_exception (errno, "cannot create temporary file");
+ ssize_t res = write_retry (fd, data->d_buf, data->d_size);
+ if (res < 0 || (size_t) res != data->d_size)
+ throw libc_exception (errno, "cannot write to temporary file");
+
+ /* Set mtime to be the same as the parent file's mtime. */
+ struct timeval tvs[2];
+ if (fstat (elf_fd, &fs) != 0)
+ throw libc_exception (errno, "cannot fstat file");
+
+ tvs[0].tv_sec = tvs[1].tv_sec = fs.st_mtime;
+ tvs[0].tv_usec = tvs[1].tv_usec = 0;
+ (void) futimes (fd, tvs);
+
+ /* Add to fdcache. */
+ fdcache.intern (b_source, section, tmppath, data->d_size, true);
+ break;
+ }
+ }
+ }
+ catch (const reportable_exception &e)
+ {
+ e.report (clog);
+ close (fd);
+ fd = -1;
+ }
+
+ elf_end (elf);
+ return fd;
+}
+
+static struct MHD_Response*
+handle_buildid_f_match (bool internal_req_t,
+ int64_t b_mtime,
+ const string& b_source0,
+ const string& section,
+ int *result_fd)
+{
+ (void) internal_req_t; // ignored
+ int fd = open(b_source0.c_str(), O_RDONLY);
+ if (fd < 0)
+ throw libc_exception (errno, string("open ") + b_source0);
+
+ // NB: use manual close(2) in error case instead of defer_dtor, because
+ // in the normal case, we want to hand the fd over to libmicrohttpd for
+ // file transfer.
+
+ struct stat s;
+ int rc = fstat(fd, &s);
+ if (rc < 0)
+ {
+ close(fd);
+ throw libc_exception (errno, string("fstat ") + b_source0);
+ }
+
+ if ((int64_t) s.st_mtime != b_mtime)
+ {
+ if (verbose)
+ obatched(clog) << "mtime mismatch for " << b_source0 << endl;
+ close(fd);
+ return 0;
+ }
+
+ if (!section.empty ())
+ {
+ int scn_fd = extract_section (fd, s.st_mtime, b_source0, section);
+ close (fd);
+
+ if (scn_fd >= 0)
+ fd = scn_fd;
+ else
+ {
+ if (verbose)
+ obatched (clog) << "cannot find section " << section
+ << " for " << b_source0 << endl;
+ return 0;
+ }
+
+ rc = fstat(fd, &s);
+ if (rc < 0)
+ {
+ close (fd);
+ throw libc_exception (errno, string ("fstat ") + b_source0
+ + string (" ") + section);
+ }
+ }
+
+ struct MHD_Response* r = MHD_create_response_from_fd ((uint64_t) s.st_size, fd);
+ inc_metric ("http_responses_total","result","file");
+ if (r == 0)
+ {
+ if (verbose)
+ obatched(clog) << "cannot create fd-response for " << b_source0
+ << " section=" << section << endl;
+ close(fd);
+ }
+ else
+ {
+ std::string file = b_source0.substr(b_source0.find_last_of("/")+1, b_source0.length());
+ add_mhd_response_header (r, "Content-Type", "application/octet-stream");
+ add_mhd_response_header (r, "X-DEBUGINFOD-SIZE",
+ to_string(s.st_size).c_str());
+ add_mhd_response_header (r, "X-DEBUGINFOD-FILE", file.c_str());
+ add_mhd_last_modified (r, s.st_mtime);
+ if (verbose > 1)
+ obatched(clog) << "serving file " << b_source0 << " section=" << section << endl;
+ /* libmicrohttpd will close it. */
+ if (result_fd)
+ *result_fd = fd;
+ }
+
+ return r;
+}
// For security/portability reasons, many distro-package archives have
// a "./" in front of path names; others have nothing, others have
@@ -1627,6 +1767,7 @@ handle_buildid_r_match (bool internal_req_p,
int64_t b_mtime,
const string& b_source0,
const string& b_source1,
+ const string& section,
int *result_fd)
{
struct stat fs;
@@ -1655,6 +1796,33 @@ handle_buildid_r_match (bool internal_req_p,
break; // branch out of if "loop", to try new libarchive fetch attempt
}
+ if (!section.empty ())
+ {
+ int scn_fd = extract_section (fd, fs.st_mtime,
+ b_source0 + ":" + b_source1,
+ section);
+ close (fd);
+ if (scn_fd >= 0)
+ fd = scn_fd;
+ else
+ {
+ if (verbose)
+ obatched (clog) << "cannot find section " << section
+ << " for archive " << b_source0
+ << " file " << b_source1 << endl;
+ return 0;
+ }
+
+ rc = fstat(fd, &fs);
+ if (rc < 0)
+ {
+ close (fd);
+ throw libc_exception (errno,
+ string ("fstat archive ") + b_source0 + string (" file ") + b_source1
+ + string (" section ") + section);
+ }
+ }
+
struct MHD_Response* r = MHD_create_response_from_fd (fs.st_size, fd);
if (r == 0)
{
@@ -1673,7 +1841,9 @@ handle_buildid_r_match (bool internal_req_p,
add_mhd_response_header (r, "X-DEBUGINFOD-FILE", b_source1.c_str());
add_mhd_last_modified (r, fs.st_mtime);
if (verbose > 1)
- obatched(clog) << "serving fdcache archive " << b_source0 << " file " << b_source1 << endl;
+ obatched(clog) << "serving fdcache archive " << b_source0
+ << " file " << b_source1
+ << " section=" << section << endl;
/* libmicrohttpd will close it. */
if (result_fd)
*result_fd = fd;
@@ -1804,8 +1974,36 @@ handle_buildid_r_match (bool internal_req_p,
tmppath, archive_entry_size(e),
true); // requested ones go to the front of lru
+ if (!section.empty ())
+ {
+ int scn_fd = extract_section (fd, b_mtime,
+ b_source0 + ":" + b_source1,
+ section);
+ close (fd);
+ if (scn_fd >= 0)
+ fd = scn_fd;
+ else
+ {
+ if (verbose)
+ obatched (clog) << "cannot find section " << section
+ << " for archive " << b_source0
+ << " file " << b_source1 << endl;
+ return 0;
+ }
+
+ rc = fstat(fd, &fs);
+ if (rc < 0)
+ {
+ close (fd);
+ throw libc_exception (errno,
+ string ("fstat ") + b_source0 + string (" ") + section);
+ }
+ r = MHD_create_response_from_fd (fs.st_size, fd);
+ }
+ else
+ r = MHD_create_response_from_fd (archive_entry_size(e), fd);
+
inc_metric ("http_responses_total","result",archive_extension + " archive");
- r = MHD_create_response_from_fd (archive_entry_size(e), fd);
if (r == 0)
{
if (verbose)
@@ -1825,7 +2023,9 @@ handle_buildid_r_match (bool internal_req_p,
add_mhd_response_header (r, "X-DEBUGINFOD-FILE", file.c_str());
add_mhd_last_modified (r, archive_entry_mtime(e));
if (verbose > 1)
- obatched(clog) << "serving archive " << b_source0 << " file " << b_source1 << endl;
+ obatched(clog) << "serving archive " << b_source0
+ << " file " << b_source1
+ << " section=" << section << endl;
/* libmicrohttpd will close it. */
if (result_fd)
*result_fd = fd;
@@ -1896,14 +2096,17 @@ handle_buildid_match (bool internal_req_p,
const string& b_stype,
const string& b_source0,
const string& b_source1,
+ const string& section,
int *result_fd)
{
try
{
if (b_stype == "F")
- return handle_buildid_f_match(internal_req_p, b_mtime, b_source0, result_fd);
+ return handle_buildid_f_match(internal_req_p, b_mtime, b_source0,
+ section, result_fd);
else if (b_stype == "R")
- return handle_buildid_r_match(internal_req_p, b_mtime, b_source0, b_source1, result_fd);
+ return handle_buildid_r_match(internal_req_p, b_mtime, b_source0,
+ b_source1, section, result_fd);
}
catch (const reportable_exception &e)
{
@@ -1978,6 +2181,7 @@ handle_buildid (MHD_Connection* conn,
if (artifacttype == "debuginfo") atype_code = "D";
else if (artifacttype == "executable") atype_code = "E";
else if (artifacttype == "source") atype_code = "S";
+ else if (artifacttype == "section") atype_code = "I";
else {
artifacttype = "invalid"; // PR28242 ensure http_resposes metrics don't propagate unclean user data
throw reportable_exception("invalid artifacttype");
@@ -1985,7 +2189,17 @@ handle_buildid (MHD_Connection* conn,
if (conn != 0)
inc_metric("http_requests_total", "type", artifacttype);
-
+
+ string section;
+ if (atype_code == "I")
+ {
+ if (suffix.size () < 2)
+ throw reportable_exception ("invalid section suffix");
+
+ // Remove leading '/'
+ section = suffix.substr(1);
+ }
+
if (atype_code == "S" && suffix == "")
throw reportable_exception("invalid source suffix");
@@ -2037,8 +2251,21 @@ handle_buildid (MHD_Connection* conn,
pp->bind(2, suffix);
pp->bind(3, canon_pathname(suffix));
}
+ else if (atype_code == "I")
+ {
+ pp = new sqlite_ps (thisdb, "mhd-query-i",
+ "select mtime, sourcetype, source0, source1, 1 as debug_p from " BUILDIDS "_query_d where buildid = ? "
+ "union all "
+ "select mtime, sourcetype, source0, source1, 0 as debug_p from " BUILDIDS "_query_e where buildid = ? "
+ "order by debug_p desc, mtime desc");
+ pp->reset();
+ pp->bind(1, buildid);
+ pp->bind(2, buildid);
+ }
unique_ptr<sqlite_ps> ps_closer(pp); // release pp if exception or return
+ bool do_upstream_section_query = true;
+
// consume all the rows
while (1)
{
@@ -2059,12 +2286,30 @@ handle_buildid (MHD_Connection* conn,
// Try accessing the located match.
// XXX: in case of multiple matches, attempt them in parallel?
auto r = handle_buildid_match (conn ? false : true,
- b_mtime, b_stype, b_source0, b_source1, result_fd);
+ b_mtime, b_stype, b_source0, b_source1,
+ section, result_fd);
if (r)
return r;
+
+ // If a debuginfo file matching BUILDID was found but didn't contain
+ // the desired section, then the section should not exist. Don't
+ // bother querying upstream servers.
+ if (!section.empty () && (sqlite3_column_int (*pp, 4) == 1))
+ {
+ struct stat st;
+
+ // For "F" sourcetype, check if the debuginfo exists. For "R"
+ // sourcetype, check if the debuginfo was interned into the fdcache.
+ if ((b_stype == "F" && (stat (b_source0.c_str (), &st) == 0))
+ || (b_stype == "R" && fdcache.probe (b_source0, b_source1)))
+ do_upstream_section_query = false;
+ }
}
pp->reset();
+ if (!do_upstream_section_query)
+ throw reportable_exception(MHD_HTTP_NOT_FOUND, "not found");
+
// We couldn't find it in the database. Last ditch effort
// is to defer to other debuginfo servers.
@@ -2089,6 +2334,11 @@ handle_buildid (MHD_Connection* conn,
fd = debuginfod_find_source (client,
(const unsigned char*) buildid.c_str(),
0, suffix.c_str(), NULL);
+ else if (artifacttype == "section")
+ fd = debuginfod_find_section (client,
+ (const unsigned char*) buildid.c_str(),
+ 0, section.c_str(), NULL);
+
}
else
fd = -errno; /* Set by debuginfod_begin. */
@@ -2296,8 +2546,7 @@ handle_metadata (MHD_Connection* conn,
MHD_Response* r;
sqlite3 *thisdb = dbq;
- // Query locally for matching e, d and s files
-
+ // Query locally for matching e, d files
string op;
if (key == "glob")
op = "glob";
@@ -2317,7 +2566,7 @@ handle_metadata (MHD_Connection* conn,
"from " BUILDIDS "_f_de d2, " BUILDIDS "_files f2, " BUILDIDS "_buildids b2 "
"where f2.id = d2.file and d2.buildid = b2.id and f2.name " + op + " ? "
"union all \n"
- // delegate to query_s for this one
+ // delegate to query_s for this one // XXX: tolerable sqlite effort?
"select 0, 0, 1, q.buildid, q.artifactsrc "
"from " BUILDIDS "_query_s as q "
"where q.artifactsrc " + op + " ? ");
@@ -2355,6 +2604,11 @@ handle_metadata (MHD_Connection* conn,
string m_buildid = (const char*) sqlite3_column_text (*pp, 3) ?: ""; // should always be non-null
string m_file = (const char*) sqlite3_column_text (*pp, 4) ?: "";
+ // Confirm that m_file matches in the fnmatch(FNM_PATHNAME)
+ // sense, since sqlite's GLOB operator is a looser filter.
+ if (key == "glob" && fnmatch(value.c_str(), m_file.c_str(), FNM_PATHNAME) != 0)
+ continue;
+
auto add_metadata = [metadata, m_buildid, m_file](const string& type) {
json_object* entry = json_object_new_object();
if (NULL == entry) throw libc_exception (ENOMEM, "cannot allocate json");
@@ -2372,39 +2626,62 @@ handle_metadata (MHD_Connection* conn,
add_entry_metadata("type", type.c_str());
add_entry_metadata("buildid", m_buildid);
add_entry_metadata("file", m_file);
- json_object_array_add(metadata, json_object_get(entry)); // Increase ref count to switch its ownership
+ if (verbose > 3)
+ obatched(clog) << "metadata found local "
+ << json_object_to_json_string_ext(entry,
+ JSON_C_TO_STRING_PRETTY)
+ << endl;
+
+ // Increase ref count to switch its ownership
+ json_object_array_add(metadata, json_object_get(entry));
};
if (m_executable_p) add_metadata("executable");
if (m_debuginfo_p) add_metadata("debuginfo");
if (m_source_p) add_metadata("source");
-
}
pp->reset();
+ unsigned num_local_results = json_object_array_length(metadata);
+
// Query upstream as well
debuginfod_client *client = debuginfod_pool_begin();
if (metadata && client != NULL)
{
add_client_federation_headers(client, conn);
- char * upstream_metadata;
- if (0 == debuginfod_find_metadata(client, key.c_str(), value.c_str(), &upstream_metadata)) {
- json_object *upstream_metadata_json = json_tokener_parse(upstream_metadata);
- if(NULL != upstream_metadata_json)
+ int upstream_metadata_fd;
+ upstream_metadata_fd = debuginfod_find_metadata(client, key.c_str(), value.c_str(), NULL);
+ if (upstream_metadata_fd >= 0) {
+ json_object *upstream_metadata_json = json_object_from_fd(upstream_metadata_fd);
+ if (NULL != upstream_metadata_json)
{
for (int i = 0, n = json_object_array_length(upstream_metadata_json); i < n; i++) {
json_object *entry = json_object_array_get_idx(upstream_metadata_json, i);
+ if (verbose > 3)
+ obatched(clog) << "metadata found remote "
+ << json_object_to_json_string_ext(entry,
+ JSON_C_TO_STRING_PRETTY)
+ << endl;
+
json_object_get(entry); // increment reference count
json_object_array_add(metadata, entry);
}
json_object_put(upstream_metadata_json);
}
- free(upstream_metadata);
+ close(upstream_metadata_fd);
}
debuginfod_pool_end (client);
}
+ unsigned num_total_results = json_object_array_length(metadata);
+
+ if (verbose > 2)
+ obatched(clog) << "metadata found local=" << num_local_results
+ << " remote=" << (num_total_results-num_local_results)
+ << " total=" << num_total_results
+ << endl;
+
const char* metadata_str = (metadata != NULL) ?
json_object_to_json_string(metadata) : "[ ]" ;
if (! metadata_str)
@@ -2487,6 +2764,7 @@ handler_cb (void * /*cls*/,
clock_gettime (CLOCK_MONOTONIC, &ts_start);
double afteryou = 0.0;
string artifacttype, suffix;
+ string urlargs; // for logging
try
{
@@ -2564,6 +2842,7 @@ handler_cb (void * /*cls*/,
if (NULL == value || NULL == key)
throw reportable_exception("/metadata webapi error, need key and value");
+ urlargs = string("?key=") + string(key) + string("&value=") + string(value); // apprx., for logging
artifacttype = "metadata";
inc_metric("http_requests_total", "type", artifacttype);
r = handle_metadata(connection, key, value, &http_size);
@@ -2605,7 +2884,7 @@ handler_cb (void * /*cls*/,
// afteryou: delay waiting for other client's identical query to complete
// deltas: total latency, including afteryou waiting
obatched(clog) << conninfo(connection)
- << ' ' << method << ' ' << url
+ << ' ' << method << ' ' << url << urlargs
<< ' ' << http_code << ' ' << http_size
<< ' ' << (int)(afteryou*1000) << '+' << (int)((deltas-afteryou)*1000) << "ms"
<< endl;
diff --git a/debuginfod/debuginfod.h.in b/debuginfod/debuginfod.h.in
index 4aa38abb..cb2ffce9 100644
--- a/debuginfod/debuginfod.h.in
+++ b/debuginfod/debuginfod.h.in
@@ -59,9 +59,9 @@ debuginfod_client *debuginfod_begin (void);
it is a binary blob of given length.
If successful, return a file descriptor to the target, otherwise
- return a posix error code. If successful, set *path to a
- strdup'd copy of the name of the same file in the cache.
- Caller must free() it later. */
+ return a negative POSIX error code. If successful, set *path to a
+ strdup'd copy of the name of the same file in the cache. Caller
+ must free() it later. */
int debuginfod_find_debuginfo (debuginfod_client *client,
const unsigned char *build_id,
@@ -79,15 +79,23 @@ int debuginfod_find_source (debuginfod_client *client,
const char *filename,
char **path);
-/* Query the urls contained in $DEBUGINFOD_URLS for metadata
- with given query key/value.
-
- If successful, return 0, otherwise return a posix error code.
- If successful, set *metadata to a malloc'd json array
- with each entry being a json object of metadata for 1 file.
- Caller must free() it later. metadata MUST be non-NULL. */
+int debuginfod_find_section (debuginfod_client *client,
+ const unsigned char *build_id,
+ int build_id_len,
+ const char *section,
+ char **path);
+
+/* Query the urls contained in $DEBUGINFOD_URLS for metadata
+ with given query key/value.
+
+ If successful, return a file descriptor to the JSON document
+ describing matches, otherwise return a negative POSIX error code. If
+ successful, set *path to a strdup'd copy of the name of the same
+ file in the cache. Caller must free() it later. */
int debuginfod_find_metadata (debuginfod_client *client,
- const char *key, const char* value, char** metadata);
+ const char *key,
+ const char* value,
+ char **path);
typedef int (*debuginfod_progressfn_t)(debuginfod_client *c, long a, long b);
void debuginfod_set_progressfn(debuginfod_client *c,
diff --git a/debuginfod/libdebuginfod.map b/debuginfod/libdebuginfod.map
index 6e4fe4b5..355a89fd 100644
--- a/debuginfod/libdebuginfod.map
+++ b/debuginfod/libdebuginfod.map
@@ -20,5 +20,8 @@ ELFUTILS_0.183 {
} ELFUTILS_0.179;
ELFUTILS_0.188 {
debuginfod_get_headers;
- debuginfod_find_metadata;
+ debuginfod_find_section;
} ELFUTILS_0.183;
+ELFUTILS_0.190 {
+ debuginfod_find_metadata;
+} ELFUTILS_0.188;