1/* Command-line frontend for retrieving ELF / DWARF / source files 2 from the debuginfod. 3 Copyright (C) 2019-2020 Red Hat, Inc. 4 This file is part of elfutils. 5 6 This file is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 3 of the License, or 9 (at your option) any later version. 10 11 elfutils is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. */ 18 19 20#include "config.h" 21#include "printversion.h" 22#include "debuginfod.h" 23#include <errno.h> 24#include <stdio.h> 25#include <stdlib.h> 26#include <string.h> 27#include <time.h> 28#include <argp.h> 29#include <unistd.h> 30#include <fcntl.h> 31#include <gelf.h> 32#include <libdwelf.h> 33 34 35/* Name and version of program. */ 36ARGP_PROGRAM_VERSION_HOOK_DEF = print_version; 37 38/* Bug report address. */ 39ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT; 40 41/* Short description of program. */ 42static const char doc[] = N_("Request debuginfo-related content " 43 "from debuginfods listed in $" DEBUGINFOD_URLS_ENV_VAR "."); 44 45/* Strings for arguments in help texts. */ 46static const char args_doc[] = N_("debuginfo BUILDID\n" 47 "debuginfo PATH\n" 48 "executable BUILDID\n" 49 "executable PATH\n" 50 "source BUILDID /FILENAME\n" 51 "source PATH /FILENAME\n" 52 "section BUILDID SECTION-NAME\n" 53 "section PATH SECTION-NAME\n"); 54 55 56/* Definitions of arguments for argp functions. */ 57static const struct argp_option options[] = 58 { 59 { "verbose", 'v', NULL, 0, "Increase verbosity.", 0 }, 60 { NULL, 0, NULL, 0, NULL, 0 } 61 }; 62 63/* debuginfod connection handle. */ 64static debuginfod_client *client; 65static int verbose; 66 67int progressfn(debuginfod_client *c __attribute__((__unused__)), 68 long a, long b) 69{ 70 static bool first = true; 71 static struct timespec last; 72 struct timespec now; 73 uint64_t delta; 74 if (!first) 75 { 76 clock_gettime (CLOCK_MONOTONIC, &now); 77 delta = ((now.tv_sec - last.tv_sec) * 1000000 78 + (now.tv_nsec - last.tv_nsec) / 1000); 79 } 80 else 81 { 82 first = false; 83 delta = 250000; 84 } 85 86 /* Show progress the first time and then at most 5 times a second. */ 87 if (delta > 200000) 88 { 89 fprintf (stderr, "Progress %ld / %ld\n", a, b); 90 clock_gettime (CLOCK_MONOTONIC, &last); 91 } 92 return 0; 93} 94 95 96static error_t parse_opt (int key, char *arg, struct argp_state *state) 97{ 98 (void) arg; 99 (void) state; 100 switch (key) 101 { 102 case 'v': verbose++; 103 debuginfod_set_progressfn (client, & progressfn); 104 if (verbose > 1) 105 debuginfod_set_verbose_fd (client, STDERR_FILENO); 106 break; 107 default: return ARGP_ERR_UNKNOWN; 108 } 109 return 0; 110} 111 112 113/* Data structure to communicate with argp functions. */ 114static struct argp argp = 115 { 116 options, parse_opt, args_doc, doc, NULL, NULL, NULL 117 }; 118 119 120 121int 122main(int argc, char** argv) 123{ 124 elf_version (EV_CURRENT); 125 126 client = debuginfod_begin (); 127 if (client == NULL) 128 { 129 fprintf(stderr, "Couldn't create debuginfod client context\n"); 130 return 1; 131 } 132 133 /* Exercise user data pointer, to support testing only. */ 134 debuginfod_set_user_data (client, (void *)"Progress"); 135 136 int remaining; 137 (void) argp_parse (&argp, argc, argv, ARGP_IN_ORDER|ARGP_NO_ARGS, &remaining, NULL); 138 139 if (argc < 2 || remaining+1 == argc) /* no arguments or at least two non-option words */ 140 { 141 argp_help (&argp, stderr, ARGP_HELP_USAGE, argv[0]); 142 return 1; 143 } 144 145 /* If we were passed an ELF file name in the BUILDID slot, look in there. */ 146 unsigned char* build_id = (unsigned char*) argv[remaining+1]; 147 int build_id_len = 0; /* assume text */ 148 149 int any_non_hex = 0; 150 int i; 151 for (i = 0; build_id[i] != '\0'; i++) 152 if ((build_id[i] >= '0' && build_id[i] <= '9') || 153 (build_id[i] >= 'a' && build_id[i] <= 'f')) 154 ; 155 else 156 any_non_hex = 1; 157 158 int fd = -1; 159 Elf* elf = NULL; 160 if (any_non_hex) /* raw build-id */ 161 { 162 fd = open ((char*) build_id, O_RDONLY); 163 if (fd < 0) 164 fprintf (stderr, "Cannot open %s: %s\n", build_id, strerror(errno)); 165 } 166 if (fd >= 0) 167 { 168 elf = dwelf_elf_begin (fd); 169 if (elf == NULL) 170 fprintf (stderr, "Cannot open as ELF file %s: %s\n", build_id, 171 elf_errmsg (-1)); 172 } 173 if (elf != NULL) 174 { 175 const void *extracted_build_id; 176 ssize_t s = dwelf_elf_gnu_build_id(elf, &extracted_build_id); 177 if (s > 0) 178 { 179 /* Success: replace the build_id pointer/len with the binary blob 180 that elfutils is keeping for us. It'll remain valid until elf_end(). */ 181 build_id = (unsigned char*) extracted_build_id; 182 build_id_len = s; 183 } 184 else 185 fprintf (stderr, "Cannot extract build-id from %s: %s\n", build_id, elf_errmsg(-1)); 186 } 187 188 char *cache_name; 189 int rc = 0; 190 191 /* Check whether FILETYPE is valid and call the appropriate 192 debuginfod_find_* function. If FILETYPE is "source" 193 then ensure a FILENAME was also supplied as an argument. */ 194 if (strcmp(argv[remaining], "debuginfo") == 0) 195 rc = debuginfod_find_debuginfo(client, 196 build_id, build_id_len, 197 &cache_name); 198 else if (strcmp(argv[remaining], "executable") == 0) 199 rc = debuginfod_find_executable(client, 200 build_id, build_id_len, 201 &cache_name); 202 else if (strcmp(argv[remaining], "source") == 0) 203 { 204 if (remaining+2 == argc || argv[remaining+2][0] != '/') 205 { 206 fprintf(stderr, "If FILETYPE is \"source\" then absolute /FILENAME must be given\n"); 207 return 1; 208 } 209 rc = debuginfod_find_source(client, 210 build_id, build_id_len, 211 argv[remaining+2], &cache_name); 212 } 213 else if (strcmp(argv[remaining], "section") == 0) 214 { 215 if (remaining+2 >= argc) 216 { 217 fprintf(stderr, 218 "If FILETYPE is \"section\" then a section name must be given\n"); 219 return 1; 220 } 221 rc = debuginfod_find_section(client, build_id, build_id_len, 222 argv[remaining+2], &cache_name); 223 } 224 else 225 { 226 argp_help (&argp, stderr, ARGP_HELP_USAGE, argv[0]); 227 return 1; 228 } 229 230 if (verbose) 231 { 232 const char* headers = debuginfod_get_headers(client); 233 if (headers) 234 fprintf(stderr, "Headers:\n%s", headers); 235 const char* url = debuginfod_get_url (client); 236 if (url != NULL) 237 fprintf(stderr, "Downloaded from %s\n", url); 238 } 239 240 debuginfod_end (client); 241 if (elf) 242 elf_end(elf); 243 if (fd >= 0) 244 close (fd); 245 246 if (rc < 0) 247 { 248 fprintf(stderr, "Server query failed: %s\n", strerror(-rc)); 249 return 1; 250 } 251 else 252 close (rc); 253 254 printf("%s\n", cache_name); 255 free (cache_name); 256 257 return 0; 258} 259