| /* Command-line frontend for retrieving ELF / DWARF / source files |
| from the debuginfod. |
| Copyright (C) 2019-2020 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/>. */ |
| |
| |
| #include "config.h" |
| #include "printversion.h" |
| #include "debuginfod.h" |
| #include <errno.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <time.h> |
| #include <argp.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <gelf.h> |
| #include <libdwelf.h> |
| |
| |
| /* Name and version of program. */ |
| ARGP_PROGRAM_VERSION_HOOK_DEF = print_version; |
| |
| /* Bug report address. */ |
| ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT; |
| |
| /* Short description of program. */ |
| static const char doc[] = N_("Request debuginfo-related content " |
| "from debuginfods listed in $" DEBUGINFOD_URLS_ENV_VAR "."); |
| |
| /* Strings for arguments in help texts. */ |
| static const char args_doc[] = N_("debuginfo BUILDID\n" |
| "debuginfo PATH\n" |
| "executable BUILDID\n" |
| "executable PATH\n" |
| "source BUILDID /FILENAME\n" |
| "source PATH /FILENAME\n"); |
| |
| |
| /* Definitions of arguments for argp functions. */ |
| static const struct argp_option options[] = |
| { |
| { "verbose", 'v', NULL, 0, "Increase verbosity.", 0 }, |
| { NULL, 0, NULL, 0, NULL, 0 } |
| }; |
| |
| /* debuginfod connection handle. */ |
| static debuginfod_client *client; |
| static int verbose; |
| |
| int progressfn(debuginfod_client *c __attribute__((__unused__)), |
| long a, long b) |
| { |
| static bool first = true; |
| static struct timespec last; |
| struct timespec now; |
| uint64_t delta; |
| if (!first) |
| { |
| clock_gettime (CLOCK_MONOTONIC, &now); |
| delta = ((now.tv_sec - last.tv_sec) * 1000000 |
| + (now.tv_nsec - last.tv_nsec) / 1000); |
| } |
| else |
| { |
| first = false; |
| delta = 250000; |
| } |
| |
| /* Show progress the first time and then at most 5 times a second. */ |
| if (delta > 200000) |
| { |
| fprintf (stderr, "Progress %ld / %ld\n", a, b); |
| clock_gettime (CLOCK_MONOTONIC, &last); |
| } |
| return 0; |
| } |
| |
| |
| static error_t parse_opt (int key, char *arg, struct argp_state *state) |
| { |
| (void) arg; |
| (void) state; |
| switch (key) |
| { |
| case 'v': verbose++; |
| debuginfod_set_progressfn (client, & progressfn); |
| debuginfod_set_verbose_fd (client, STDERR_FILENO); |
| break; |
| default: return ARGP_ERR_UNKNOWN; |
| } |
| return 0; |
| } |
| |
| |
| /* Data structure to communicate with argp functions. */ |
| static struct argp argp = |
| { |
| options, parse_opt, args_doc, doc, NULL, NULL, NULL |
| }; |
| |
| |
| |
| int |
| main(int argc, char** argv) |
| { |
| elf_version (EV_CURRENT); |
| |
| client = debuginfod_begin (); |
| if (client == NULL) |
| { |
| fprintf(stderr, "Couldn't create debuginfod client context\n"); |
| return 1; |
| } |
| |
| /* Exercise user data pointer, to support testing only. */ |
| debuginfod_set_user_data (client, (void *)"Progress"); |
| |
| int remaining; |
| (void) argp_parse (&argp, argc, argv, ARGP_IN_ORDER|ARGP_NO_ARGS, &remaining, NULL); |
| |
| if (argc < 2 || remaining+1 == argc) /* no arguments or at least two non-option words */ |
| { |
| argp_help (&argp, stderr, ARGP_HELP_USAGE, argv[0]); |
| return 1; |
| } |
| |
| /* 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) |
| { |
| 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)); |
| } |
| |
| char *cache_name; |
| int rc = 0; |
| |
| /* Check whether FILETYPE is valid and call the appropriate |
| debuginfod_find_* function. If FILETYPE is "source" |
| then ensure a FILENAME was also supplied as an argument. */ |
| if (strcmp(argv[remaining], "debuginfo") == 0) |
| rc = debuginfod_find_debuginfo(client, |
| build_id, build_id_len, |
| &cache_name); |
| else if (strcmp(argv[remaining], "executable") == 0) |
| rc = debuginfod_find_executable(client, |
| build_id, build_id_len, |
| &cache_name); |
| else if (strcmp(argv[remaining], "source") == 0) |
| { |
| if (remaining+2 == argc || argv[remaining+2][0] != '/') |
| { |
| fprintf(stderr, "If FILETYPE is \"source\" then absolute /FILENAME must be given\n"); |
| return 1; |
| } |
| rc = debuginfod_find_source(client, |
| build_id, build_id_len, |
| argv[remaining+2], &cache_name); |
| } |
| else |
| { |
| argp_help (&argp, stderr, ARGP_HELP_USAGE, argv[0]); |
| return 1; |
| } |
| |
| if (verbose) |
| { |
| const char* url = debuginfod_get_url (client); |
| if (url != NULL) |
| fprintf(stderr, "Downloaded from %s\n", url); |
| } |
| |
| debuginfod_end (client); |
| if (elf) |
| elf_end(elf); |
| if (fd >= 0) |
| close (fd); |
| |
| if (rc < 0) |
| { |
| fprintf(stderr, "Server query failed: %s\n", strerror(-rc)); |
| return 1; |
| } |
| |
| printf("%s\n", cache_name); |
| free (cache_name); |
| |
| return 0; |
| } |