summaryrefslogtreecommitdiff
path: root/debuginfod
diff options
context:
space:
mode:
authorAaron Merey <amerey@redhat.com>2022-10-31 23:44:23 -0400
committerAaron Merey <amerey@redhat.com>2022-11-01 00:25:15 -0400
commitfb833750c395ce268d16fd44e4decafbb41208a8 (patch)
treef756e96a54ea06ad831e11d14d2675d49c5e242e /debuginfod
parent5d8cd51a460fc29f35a016836c39fcea6bf59fb0 (diff)
downloadelfutils-fb833750c395ce268d16fd44e4decafbb41208a8.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>
Diffstat (limited to 'debuginfod')
-rw-r--r--debuginfod/ChangeLog22
-rw-r--r--debuginfod/Makefile.am2
-rw-r--r--debuginfod/debuginfod-client.c363
-rw-r--r--debuginfod/debuginfod-find.c15
-rw-r--r--debuginfod/debuginfod.cxx383
-rw-r--r--debuginfod/debuginfod.h.in6
-rw-r--r--debuginfod/libdebuginfod.map1
7 files changed, 696 insertions, 96 deletions
diff --git a/debuginfod/ChangeLog b/debuginfod/ChangeLog
index 1df903fe..92a880f8 100644
--- a/debuginfod/ChangeLog
+++ b/debuginfod/ChangeLog
@@ -1,3 +1,25 @@
+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 435cb8a6..f27d6e2e 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)
+libdebuginfod_so_LDLIBS = -lpthread $(libcurl_LIBS) $(fts_LIBS) $(libelf)
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 716cb769..d097ca49 100644
--- a/debuginfod/debuginfod-client.c
+++ b/debuginfod/debuginfod-client.c
@@ -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,6 +57,9 @@ 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; }
void debuginfod_set_progressfn(debuginfod_client *c,
debuginfod_progressfn_t fn) { }
void debuginfod_set_verbose_fd(debuginfod_client *c, int fd) { }
@@ -130,6 +134,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;
@@ -576,21 +583,257 @@ header_callback (char * buffer, size_t size, size_t numitems, void * userdata)
return numitems;
}
+/* 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;
+}
+
/* 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 (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;
@@ -603,6 +846,17 @@ debuginfod_query_server (debuginfod_client *c,
int vfd = c->verbose_fd;
int rc;
+ 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);
@@ -701,31 +955,13 @@ debuginfod_query_server (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';
@@ -797,7 +1033,10 @@ debuginfod_query_server (debuginfod_client *c,
}
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);
+ 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 */
@@ -881,6 +1120,18 @@ debuginfod_query_server (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)
@@ -1053,6 +1304,9 @@ debuginfod_query_server (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);
if (vfd >= 0)
@@ -1257,7 +1511,10 @@ debuginfod_query_server (debuginfod_client *c,
}
if ((*c->progressfn) (c, pa, dl_size))
- break;
+ {
+ c->progressfn_cancel = true;
+ break;
+ }
}
/* Check to see if we are downloading something which exceeds maxsize, if set.*/
@@ -1324,6 +1581,8 @@ debuginfod_query_server (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;
@@ -1649,6 +1908,56 @@ 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(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;
+}
/* 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 778fb09b..30731098 100644
--- a/debuginfod/debuginfod-find.c
+++ b/debuginfod/debuginfod-find.c
@@ -48,7 +48,9 @@ static const char args_doc[] = N_("debuginfo BUILDID\n"
"executable BUILDID\n"
"executable PATH\n"
"source BUILDID /FILENAME\n"
- "source PATH /FILENAME\n");
+ "source PATH /FILENAME\n"
+ "section BUILDID SECTION-NAME\n"
+ "section PATH SECTION-NAME\n");
/* Definitions of arguments for argp functions. */
@@ -208,6 +210,17 @@ 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);
+ }
else
{
argp_help (&argp, stderr, ARGP_HELP_USAGE, argv[0]);
diff --git a/debuginfod/debuginfod.cxx b/debuginfod/debuginfod.cxx
index 9dc4836b..f46da6ef 100644
--- a/debuginfod/debuginfod.cxx
+++ b/debuginfod/debuginfod.cxx
@@ -1136,66 +1136,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)
@@ -1589,6 +1529,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
@@ -1614,6 +1753,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;
@@ -1642,6 +1782,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)
{
@@ -1660,7 +1827,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;
@@ -1791,8 +1960,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)
@@ -1812,7 +2009,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;
@@ -1831,14 +2030,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)
{
@@ -1913,6 +2115,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");
@@ -1920,7 +2123,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");
@@ -1972,8 +2185,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)
{
@@ -1994,12 +2220,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.
@@ -2074,6 +2318,11 @@ and will not query the upstream servers");
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. */
diff --git a/debuginfod/debuginfod.h.in b/debuginfod/debuginfod.h.in
index 7d8e4972..69c9efd2 100644
--- a/debuginfod/debuginfod.h.in
+++ b/debuginfod/debuginfod.h.in
@@ -79,6 +79,12 @@ int debuginfod_find_source (debuginfod_client *client,
const char *filename,
char **path);
+int debuginfod_find_section (debuginfod_client *client,
+ const unsigned char *build_id,
+ int build_id_len,
+ const char *section,
+ char **path);
+
typedef int (*debuginfod_progressfn_t)(debuginfod_client *c, long a, long b);
void debuginfod_set_progressfn(debuginfod_client *c,
debuginfod_progressfn_t fn);
diff --git a/debuginfod/libdebuginfod.map b/debuginfod/libdebuginfod.map
index 93964167..6334373f 100644
--- a/debuginfod/libdebuginfod.map
+++ b/debuginfod/libdebuginfod.map
@@ -20,4 +20,5 @@ ELFUTILS_0.183 {
} ELFUTILS_0.179;
ELFUTILS_0.188 {
debuginfod_get_headers;
+ debuginfod_find_section;
} ELFUTILS_0.183;