diff options
author | Aaron Merey <amerey@redhat.com> | 2022-10-31 23:44:23 -0400 |
---|---|---|
committer | Aaron Merey <amerey@redhat.com> | 2022-11-01 00:25:15 -0400 |
commit | fb833750c395ce268d16fd44e4decafbb41208a8 (patch) | |
tree | f756e96a54ea06ad831e11d14d2675d49c5e242e /debuginfod | |
parent | 5d8cd51a460fc29f35a016836c39fcea6bf59fb0 (diff) | |
download | elfutils-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/ChangeLog | 22 | ||||
-rw-r--r-- | debuginfod/Makefile.am | 2 | ||||
-rw-r--r-- | debuginfod/debuginfod-client.c | 363 | ||||
-rw-r--r-- | debuginfod/debuginfod-find.c | 15 | ||||
-rw-r--r-- | debuginfod/debuginfod.cxx | 383 | ||||
-rw-r--r-- | debuginfod/debuginfod.h.in | 6 | ||||
-rw-r--r-- | debuginfod/libdebuginfod.map | 1 |
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; |