summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAaron Merey <amerey@redhat.com>2022-10-31 23:44:23 -0400
committerFrank Ch. Eigler <fche@redhat.com>2023-04-07 10:53:22 -0400
commit91eda624d5c33a848e9f285a8f57eb89120f928d (patch)
tree0cc2ae928b6cfcdaa33ff5fca0313da4b3ccf5d8
parent2f1216bd5e311c2ecfe1cbb1eaf2776d91ee14f8 (diff)
downloadelfutils-91eda624d5c33a848e9f285a8f57eb89120f928d.tar.gz
debuginfod: Support queries for ELF/DWARF sections
Add new function debuginfod_find_section which queries debuginfod servers for the raw binary contents of the specified ELF/DWARF section in a file matching the given build-id. Extend the server webapi to support section queries. Section query URLS have the following format: /buildid/BUILDID/section/SECTION The server will attempt to extract the section from a debuginfo file matching the given build-id. If the debuginfo file cannot be found or the section has type SHT_NOBITS, the server will attempt to extract the section from the executable file matching the build-id. If the server is built without section query support, the client will attempt to download the debuginfo matching the build-id and extract the section. If the debuginfo file cannot be found or the section has type SHT_NOBITS, the server will attempt to download the executable file matching the build-id and extract the section. Signed-off-by: Aaron Merey <amerey@redhat.com>
-rw-r--r--ChangeLog4
-rw-r--r--NEWS2
-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
-rw-r--r--doc/ChangeLog7
-rw-r--r--doc/Makefile.am1
-rw-r--r--doc/debuginfod-client-config.78
-rw-r--r--doc/debuginfod-find.14
-rw-r--r--doc/debuginfod.811
-rw-r--r--doc/debuginfod_find_debuginfo.333
-rw-r--r--doc/debuginfod_find_section.31
-rw-r--r--tests/ChangeLog5
-rw-r--r--tests/Makefile.am4
-rwxr-xr-xtests/debuginfod-subr.sh2
-rwxr-xr-xtests/run-debuginfod-find-metadata.sh30
-rwxr-xr-xtests/run-debuginfod-section.sh135
21 files changed, 1307 insertions, 299 deletions
diff --git a/ChangeLog b/ChangeLog
index efce0716..1f66ec6d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -3,6 +3,10 @@
* configure.ac (HAVE_JSON_C): Defined iff libjson-c
is found, and debuginfod metadata querying is thus enabled.
+2022-10-31 Aaron Merey <amerey@redhat.com>
+
+ * NEWS: Add debuginfod_find_section.
+
2022-10-20 Mark Wielaard <mark@klomp.org>
* Makefile.am (rpm): Remove --sign.
diff --git a/NEWS b/NEWS
index 3df652e3..9369f809 100644
--- a/NEWS
+++ b/NEWS
@@ -3,7 +3,7 @@ Version 0.188 some time after 0.187
readelf: Add -D, --use-dynamic option.
debuginfod-client: Add $DEBUGINFOD_HEADERS_FILE setting to supply outgoing
- HTTP headers.
+ HTTP headers. Add new function debuginfod_find_section.
debuginfod: Add --disable-source-scan option.
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;
diff --git a/doc/ChangeLog b/doc/ChangeLog
index 7f852824..dfefae9b 100644
--- a/doc/ChangeLog
+++ b/doc/ChangeLog
@@ -5,6 +5,13 @@
* debuginfod_find_metadata.3: New file.
* Makefile.am (notrans_dist_*_man3): Add it.
+2022-10-31 Aaron Merey <amerey@redhat.com>
+
+ * Makefile.am (notrans_dist_man3_MANS): Add debuginfod_find_section.3.
+ * debuginfod_find_section.3: New file.
+ * debuginfod_find_debuginfo.3: Document debuginfod_find_section.
+ * debuginfod.8: Document section webapi.
+
2022-10-28 Arsen Arsenović <arsen@aarsen.me>
* readelf.1: Document the --syms alias.
diff --git a/doc/Makefile.am b/doc/Makefile.am
index 64ffdaa2..b547a8db 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -38,6 +38,7 @@ notrans_dist_man3_MANS += debuginfod_end.3
notrans_dist_man3_MANS += debuginfod_find_debuginfo.3
notrans_dist_man3_MANS += debuginfod_find_executable.3
notrans_dist_man3_MANS += debuginfod_find_source.3
+notrans_dist_man3_MANS += debuginfod_find_section.3
notrans_dist_man3_MANS += debuginfod_find_metadata.3
notrans_dist_man3_MANS += debuginfod_get_user_data.3
notrans_dist_man3_MANS += debuginfod_get_url.3
diff --git a/doc/debuginfod-client-config.7 b/doc/debuginfod-client-config.7
index 53d82806..bc98d1e6 100644
--- a/doc/debuginfod-client-config.7
+++ b/doc/debuginfod-client-config.7
@@ -134,3 +134,11 @@ are short-circuited (returning an immediate failure instead of sending
a new query to servers). This accelerates queries that probably would
still fail. The default is 600, 10 minutes. 0 means "forget
immediately".
+
+.TP
+.B metadata_retention_s
+This control file sets how long to remember the results of a metadata
+query. New queries for the same artifacts within this time window are
+short-circuited (repeating the same results). This accelerates
+queries that probably would probably have the same results. The
+default is 86400, 1 day. 0 means "do not retain".
diff --git a/doc/debuginfod-find.1 b/doc/debuginfod-find.1
index c44fbc76..c3670374 100644
--- a/doc/debuginfod-find.1
+++ b/doc/debuginfod-find.1
@@ -118,8 +118,8 @@ servers.
l l l .
KEY VALUE DESCRIPTION
-\fBfile\fP path match exact \fIpath\fP, including in archives
-\fBglob\fP pattern sqlite glob match \fIpattern\fP, including in archives
+\fBfile\fP path exact match \fIpath\fP, including in archives
+\fBglob\fP pattern glob match \fIpattern\fP, including in archives
.TE
The results of the search are output to \fBstdout\fP as a JSON array
diff --git a/doc/debuginfod.8 b/doc/debuginfod.8
index 9f529d0a..0703e3b4 100644
--- a/doc/debuginfod.8
+++ b/doc/debuginfod.8
@@ -371,6 +371,17 @@ Note: the client should %-escape characters in /SOURCE/FILE that are
not shown as "unreserved" in section 2.3 of RFC3986. Some characters
that will be escaped include "+", "\\", "$", "!", the 'space' character,
and ";". RFC3986 includes a more comprehensive list of these characters.
+
+.SS /buildid/\fIBUILDID\fP/section\fI/SECTION\fP
+If the given buildid is known to the server, the server will attempt to
+extract the contents of an ELF/DWARF section named SECTION from the
+debuginfo file matching BUILDID. If the debuginfo file can't be found
+or the section has type SHT_NOBITS, then the server will attempt to extract
+the section from the executable matching BUILDID. If the section is
+succesfully extracted then this request results in a binary object
+of the section's contents. Note that this result is the raw binary
+contents of the section, not an ELF file.
+
.SS /metrics
This endpoint returns a Prometheus formatted text/plain dump of a
diff --git a/doc/debuginfod_find_debuginfo.3 b/doc/debuginfod_find_debuginfo.3
index f131813e..7d68f161 100644
--- a/doc/debuginfod_find_debuginfo.3
+++ b/doc/debuginfod_find_debuginfo.3
@@ -43,10 +43,15 @@ LOOKUP FUNCTIONS
.BI " int " build_id_len ","
.BI " const char *" filename ","
.BI " char ** " path ");"
+.BI "int debuginfod_find_section(debuginfod_client *" client ","
+.BI " const unsigned char *" build_id ","
+.BI " int " build_id_len ","
+.BI " const char * " section ","
+.BI " char ** " path ");"
.BI "int debuginfod_find_metadata(debuginfod_client *" client ","
.BI " const char *" key ","
.BI " const char *" value ","
-.BI " char** " metadata ");"
+.BI " char ** " path ");"
OPTIONAL FUNCTIONS
@@ -102,6 +107,16 @@ accepts both forms. Specifically, debuginfod canonicalizes path names
according to RFC3986 section 5.2.4 (Remove Dot Segments), plus reducing
any \fB//\fP to \fB/\fP in the path.
+.BR debuginfod_find_section ()
+queries the debuginfod server URLs contained in
+.BR $DEBUGINFOD_URLS
+for the binary contents of an ELF/DWARF section contained in a debuginfo
+or executable file with the given \fIbuild_id\fP. \fIsection\fP should
+be the name of the desired ELF/DWARF section. If a server does not support
+section queries, debuginfod_find_section may query the server for the
+debuginfo and/or executable with \fIbuild_id\fP in order to retrieve
+and extract the section.
+
If \fIpath\fP is not NULL and the query is successful, \fIpath\fP is set
to the path of the file in the cache. The caller must \fBfree\fP() this value.
@@ -114,7 +129,7 @@ A handle may be reused for a series of lookups, which can improve
performance due to retention of connections and caches.
.BR debuginfod_find_metadata (),
-likewise queries all debuginfod server URLs contained in
+queries queries all debuginfod server URLs contained in
.BR $DEBUGINFOD_URLS
but instead retrieves metadata. The query search mode is specified
in the \fIkey\fP parameter, and its parameter \fIvalue\fP. See
@@ -132,13 +147,6 @@ to the client cache and a file descriptor to that file is returned.
The caller needs to \fBclose\fP() this descriptor. Otherwise, a
negative error code is returned.
-The one exception is \fBdebuginfod_find_metadata\fP, which likewise
-returns negative error codes, but on success returns 0 and sets
-\fI*metadata\fP to a string-form JSON array of the found matching
-metadata. This should be freed by the caller. See
-\fIdebuginfod-find(1)\fP for more information on the metadata being
-returned.
-
.SH "OPTIONAL FUNCTIONS"
A small number of optional functions are available to tune or query
@@ -253,11 +261,8 @@ void *debuginfod_so = dlopen(DEBUGINFOD_SONAME, RTLD_LAZY);
.in
.SH "SECURITY"
-.BR debuginfod_find_debuginfo (),
-.BR debuginfod_find_executable (),
-and
-.BR debuginfod_find_source ()
-\fBdo not\fP include any particular security
+.BR debuginfod_find_* ()
+functions \fBdo not\fP include any particular security
features. They trust that the binaries returned by the debuginfod(s)
are accurate. Therefore, the list of servers should include only
trustworthy ones. If accessed across HTTP rather than HTTPS, the
diff --git a/doc/debuginfod_find_section.3 b/doc/debuginfod_find_section.3
new file mode 100644
index 00000000..16279936
--- /dev/null
+++ b/doc/debuginfod_find_section.3
@@ -0,0 +1 @@
+.so man3/debuginfod_find_debuginfo.3
diff --git a/tests/ChangeLog b/tests/ChangeLog
index 79aae920..6f4a161e 100644
--- a/tests/ChangeLog
+++ b/tests/ChangeLog
@@ -4,6 +4,11 @@
* Makefile.am (TESTS): Add run-debuginfod-find-metadata.sh.
(EXTRA_DIST): Likewise.
+2022-10-31 Aaron Merey <amerey@redhat.com>
+
+ * Makefile.am (TESTS): Add run-debuginfod-section.sh.
+ * run-debuginfod-section.sh: New test.
+
2022-09-20 Yonggang Luo <luoyonggang@gmail.com>
* Makefile.am (EXTRA_DIST): Remove debuginfod-rpms/hello2.spec.
diff --git a/tests/Makefile.am b/tests/Makefile.am
index aaa5d35a..100d791e 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -248,6 +248,7 @@ TESTS += run-debuginfod-dlopen.sh \
run-debuginfod-response-headers.sh \
run-debuginfod-extraction-passive.sh \
run-debuginfod-webapi-concurrency.sh \
+ run-debuginfod-section.sh \
run-debuginfod-find-metadata.sh
endif
if !OLD_LIBMICROHTTPD
@@ -555,7 +556,8 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh run-ar.sh \
run-debuginfod-response-headers.sh \
run-debuginfod-extraction-passive.sh \
run-debuginfod-webapi-concurrency.sh \
- run-debuginfod-find-metadata.sh \
+ run-debuginfod-section.sh \
+ run-debuginfod-find-metadata.sh \
debuginfod-rpms/fedora30/hello2-1.0-2.src.rpm \
debuginfod-rpms/fedora30/hello2-1.0-2.x86_64.rpm \
debuginfod-rpms/fedora30/hello2-debuginfo-1.0-2.x86_64.rpm \
diff --git a/tests/debuginfod-subr.sh b/tests/debuginfod-subr.sh
index 108dff74..29ef128a 100755
--- a/tests/debuginfod-subr.sh
+++ b/tests/debuginfod-subr.sh
@@ -151,7 +151,7 @@ get_ports() {
}
-VERBOSE=-vvv
+VERBOSE=-vvvv
# We gather the LD_LIBRARY_PATH with this cunning trick:
ldpath=`testrun sh -c 'echo $LD_LIBRARY_PATH'`
PORT1=0
diff --git a/tests/run-debuginfod-find-metadata.sh b/tests/run-debuginfod-find-metadata.sh
index 2e1999f5..f75d5d99 100755
--- a/tests/run-debuginfod-find-metadata.sh
+++ b/tests/run-debuginfod-find-metadata.sh
@@ -29,7 +29,7 @@ pkg-config json-c libcurl || { echo "one or more libraries are missing (libjson-
DB=${PWD}/.debuginfod_tmp.sqlite
export DEBUGINFOD_CACHE_PATH=${PWD}/.client_cache
-tempfiles $DB ${DB}_2
+tempfiles $DB ${DB}_2 $DB-wal ${DB}_2-wal $DB-shm ${DB}_2-shm
# This variable is essential and ensures no time-race for claiming ports occurs
# set base to a unique multiple of 100 not used in any other 'run-debuginfod-*' test
@@ -65,9 +65,16 @@ wait_ready $PORT2 'thread_busy{role="scan"}' 0
export DEBUGINFOD_URLS=http://127.0.0.1:$PORT2
tempfiles json.txt
-# Check that we find 11 files(which means that the local and upstream correctly reply to the query)
-N_FOUND=`env LD_LIBRARY_PATH=$ldpath ${abs_builddir}/../debuginfod/debuginfod-find metadata glob "/?sr*" | jq '. | length'`
-test $N_FOUND -eq 11
+# Check that we find correct number of files, both via local and federated links
+RESULTF=`env LD_LIBRARY_PATH=$ldpath ${abs_builddir}/../debuginfod/debuginfod-find metadata glob "/u?r/bin/*"`
+cat $RESULTF
+N_FOUND=`cat $RESULTF | jq '. | length'`
+test $N_FOUND -eq 1
+RESULTF=`env LD_LIBRARY_PATH=$ldpath ${abs_builddir}/../debuginfod/debuginfod-find metadata glob "/usr/lo?al/bin/*"`
+cat $RESULTF
+N_FOUND=`cat $RESULTF | jq '. | length'`
+test $N_FOUND -eq 2
+
# Query via the webapi as well
EXPECTED='[ { "type": "executable", "buildid": "f17a29b5a25bd4960531d82aa6b07c8abe84fa66", "file": "/usr/bin/hithere"} ]'
@@ -75,7 +82,9 @@ curl http://127.0.0.1:$PORT2'/metadata?key=glob&value=/usr/bin/*hi*'
test `curl http://127.0.0.1:$PORT2'/metadata?key=glob&value=/usr/bin/*hi*' | jq ". == $EXPECTED" ` = 'true'
# An empty array is returned on server error or if the file DNE
-test `env LD_LIBRARY_PATH=$ldpath ${abs_builddir}/../debuginfod/debuginfod-find metadata file "/this/isnt/there" | jq ". == [ ]" ` = 'true'
+RESULTF=`env LD_LIBRARY_PATH=$ldpath ${abs_builddir}/../debuginfod/debuginfod-find metadata file "/this/isnt/there"`
+cat $RESULTF
+test `cat $RESULTF | jq ". == [ ]" ` = 'true'
kill $PID1
kill $PID2
@@ -84,6 +93,15 @@ wait $PID2
PID1=0
PID2=0
-test `env LD_LIBRARY_PATH=$ldpath ${abs_builddir}/../debuginfod/debuginfod-find metadata file "/usr/bin/hithere" | jq ". == [ ]" ` = 'true'
+# check it's still in cache
+RESULTF=`env LD_LIBRARY_PATH=$ldpath ${abs_builddir}/../debuginfod/debuginfod-find metadata file "/usr/bin/hithere"`
+cat $RESULTF
+test `cat $RESULTF | jq ". == [ ]" ` = 'true'
+
+# invalidate cache, retry previously successful query to now-dead servers
+echo 0 > $DEBUGINFOD_CACHE_PATH/metadata_retention_s
+RESULTF=`env LD_LIBRARY_PATH=$ldpath ${abs_builddir}/../debuginfod/debuginfod-find metadata glob "/u?r/bin/*"`
+cat $RESULTF
+test `cat $RESULTF | jq ". == [ ]" ` = 'true'
exit 0
diff --git a/tests/run-debuginfod-section.sh b/tests/run-debuginfod-section.sh
new file mode 100755
index 00000000..a3cae349
--- /dev/null
+++ b/tests/run-debuginfod-section.sh
@@ -0,0 +1,135 @@
+#!/usr/bin/env bash
+#
+# Copyright (C) 2019-2021 Red Hat, Inc.
+# This file is part of elfutils.
+#
+# This file is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# elfutils is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+. $srcdir/debuginfod-subr.sh
+
+# for test case debugging, uncomment:
+set -x
+unset VALGRIND_CMD
+
+DB=${PWD}/.debuginfod_tmp.sqlite
+tempfiles $DB
+export DEBUGINFOD_CACHE_PATH=${PWD}/.client_cache
+
+# Set up directories for scanning
+mkdir F
+mkdir R
+cp -rvp ${abs_srcdir}/debuginfod-rpms R
+
+# This variable is essential and ensures no time-race for claiming ports occurs
+# set base to a unique multiple of 100 not used in any other 'run-debuginfod-*' test
+base=13000
+get_ports
+
+# We use -t0 and -g0 here to turn off time-based scanning & grooming.
+# For testing purposes, we just sic SIGUSR1 at the process.
+
+env LD_LIBRARY_PATH=$ldpath DEBUGINFOD_URLS= ${abs_builddir}/../debuginfod/debuginfod $VERBOSE \
+ -R R -F F -p $PORT1 -d $DB -t0 -g0 -v F > vlog$PORT1 2>&1 &
+PID1=$!
+tempfiles vlog$PORT1
+errfiles vlog$PORT1
+# Server must become ready
+wait_ready $PORT1 'ready' 1
+# And initial scan should be done
+wait_ready $PORT1 'thread_work_total{role="traverse"}' 1
+
+export DEBUGINFOD_URLS=http://127.0.0.1:$PORT1
+
+# Check thread comm names
+ps -q $PID1 -e -L -o '%p %c %a' | grep groom
+ps -q $PID1 -e -L -o '%p %c %a' | grep scan
+ps -q $PID1 -e -L -o '%p %c %a' | grep traverse
+
+########################################################################
+
+# Compile a simple program, strip its debuginfo and save the build-id.
+# Also move the debuginfo into another directory so that elfutils
+# cannot find it without debuginfod.
+tempfiles prog.c
+echo "int main() { return 0; }" > ${PWD}/prog.c
+
+gcc -Wl,--build-id -g -o F/prog ${PWD}/prog.c
+
+testrun ${abs_top_builddir}/src/strip -g -f F/prog.debug ${PWD}/F/prog
+BUILDID=`env LD_LIBRARY_PATH=$ldpath ${abs_builddir}/../src/readelf \
+ -a F/prog | grep 'Build ID' | cut -d ' ' -f 7`
+
+kill -USR1 $PID1
+# Wait till both files are in the index.
+wait_ready $PORT1 'thread_work_total{role="traverse"}' 2
+wait_ready $PORT1 'thread_work_pending{role="scan"}' 0
+wait_ready $PORT1 'thread_busy{role="scan"}' 0
+
+########################################################################
+
+# Build-id for a file in the one of the testsuite's F31 rpms
+RPM_BUILDID=420e9e3308971f4b817cc5bf83928b41a6909d88
+
+# Download sections from files indexed with -F
+testrun ${abs_top_builddir}/debuginfod/debuginfod-find -vvv section $BUILDID .debug_info
+testrun ${abs_top_builddir}/debuginfod/debuginfod-find -vvv section $BUILDID .text
+
+# Download sections from files indexed with -R
+testrun ${abs_top_builddir}/debuginfod/debuginfod-find -vvv section $RPM_BUILDID .debug_info
+testrun ${abs_top_builddir}/debuginfod/debuginfod-find -vvv section $RPM_BUILDID .text
+
+# Verify that the downloaded files match the contents of the original sections
+tempfiles ${BUILDID}.debug_info
+objcopy F/prog.debug -O binary --only-section=.debug_info --set-section-flags .debug_info=alloc $BUILDID.debug_info
+cmp ${BUILDID}.debug_info ${DEBUGINFOD_CACHE_PATH}/${BUILDID}/section-.debug_info
+
+tempfiles ${BUILDID}.text
+objcopy F/prog -O binary --only-section=.text ${BUILDID}.text
+cmp ${BUILDID}.text ${DEBUGINFOD_CACHE_PATH}/${BUILDID}/section-.text
+
+# Download the original debuginfo/executable files.
+DEBUGFILE=`env LD_LIBRARY_PATH=$ldpath ${abs_top_builddir}/debuginfod/debuginfod-find debuginfo $RPM_BUILDID`
+EXECFILE=`env LD_LIBRARY_PATH=$ldpath ${abs_top_builddir}/debuginfod/debuginfod-find executable $RPM_BUILDID`
+testrun ${abs_top_builddir}/debuginfod/debuginfod-find -vvv debuginfo $BUILDID
+testrun ${abs_top_builddir}/debuginfod/debuginfod-find -vvv executable $BUILDID
+
+if test "$(arch)" == "x86_64"; then
+ tempfiles DEBUGFILE.debug_info
+ objcopy $DEBUGFILE -O binary --only-section=.debug_info --set-section-flags .debug_info=alloc DEBUGFILE.debug_info
+ testrun diff -u DEBUGFILE.debug_info ${DEBUGINFOD_CACHE_PATH}/${RPM_BUILDID}/section-.debug_info
+
+ tempfiles EXECFILE.text
+ objcopy $EXECFILE -O binary --only-section=.text EXECFILE.text
+ testrun diff -u EXECFILE.text ${DEBUGINFOD_CACHE_PATH}/${RPM_BUILDID}/section-.text
+fi
+
+# Kill the server.
+kill $PID1
+wait $PID1
+PID1=0
+
+# Delete the section files from the cache.
+rm -f ${DEBUGINFOD_CACHE_PATH}/${RPM_BUILDID}/section-.text
+rm -f ${DEBUGINFOD_CACHE_PATH}/${RPM_BUILDID}/section-.debug_info
+rm -f ${DEBUGINFOD_CACHE_PATH}/${BUILDID}/section-.text
+rm -f ${DEBUGINFOD_CACHE_PATH}/${BUILDID}/section-.debug_info
+
+# Verify that the client can extract sections from the debuginfo or executable
+# if they're already in the cache.
+testrun ${abs_top_builddir}/debuginfod/debuginfod-find -vvv section $BUILDID .debug_info
+testrun ${abs_top_builddir}/debuginfod/debuginfod-find -vvv section $BUILDID .text
+testrun ${abs_top_builddir}/debuginfod/debuginfod-find -vvv section $RPM_BUILDID .debug_info
+testrun ${abs_top_builddir}/debuginfod/debuginfod-find -vvv section $RPM_BUILDID .text
+
+exit 0