Branch data Line data Source code
1 : : /* Command-line frontend for retrieving ELF / DWARF / source files
2 : : from the debuginfod.
3 : : Copyright (C) 2019-2023 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 : : #include <signal.h>
34 : : #ifndef DUMMY_LIBDEBUGINFOD
35 : : #include <json-c/json.h>
36 : : #endif
37 : :
38 : : /* Name and version of program. */
39 : : ARGP_PROGRAM_VERSION_HOOK_DEF = print_version;
40 : :
41 : : /* Bug report address. */
42 : : ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
43 : :
44 : : /* Short description of program. */
45 : : static const char doc[] = N_("Request debuginfo-related content "
46 : : "from debuginfods listed in $" DEBUGINFOD_URLS_ENV_VAR ".");
47 : :
48 : : /* Strings for arguments in help texts. */
49 : : static const char args_doc[] = N_("debuginfo BUILDID\n"
50 : : "debuginfo PATH\n"
51 : : "executable BUILDID\n"
52 : : "executable PATH\n"
53 : : "source BUILDID /FILENAME\n"
54 : : "source PATH /FILENAME\n"
55 : : "section BUILDID SECTION-NAME\n"
56 : : "section PATH SECTION-NAME\n"
57 : : "metadata (glob|file|KEY) (GLOB|FILENAME|VALUE)\n"
58 : : );
59 : :
60 : : /* Definitions of arguments for argp functions. */
61 : : static const struct argp_option options[] =
62 : : {
63 : : { "verbose", 'v', NULL, 0, "Increase verbosity.", 0 },
64 : : { NULL, 0, NULL, 0, NULL, 0 }
65 : : };
66 : :
67 : : /* debuginfod connection handle. */
68 : : static debuginfod_client *client;
69 : : static int verbose;
70 : : static volatile sig_atomic_t interrupted;
71 : : static bool saw_progress_envvar;
72 : :
73 : : static void
74 : 0 : handle_sigint(int signo __attribute__((__unused__)))
75 : : {
76 : 0 : interrupted = 1;
77 : 0 : }
78 : :
79 : : static int
80 : 1032 : progressfn(debuginfod_client *c, long a, long b)
81 : : {
82 [ + - ]: 1032 : if (interrupted)
83 : : return 1;
84 [ + + ]: 1032 : if (verbose < 1)
85 : : {
86 : : /* Fallback to the default progressfn if verbose isn't set and
87 : : DEBUGINFOD_PROGRESS is set. */
88 [ + + ]: 1006 : if (saw_progress_envvar)
89 : 2 : return debuginfod_default_progressfn (c, a, b);
90 : : return 0;
91 : : }
92 : :
93 : 26 : static bool first = true;
94 : 26 : static struct timespec last;
95 : 26 : struct timespec now;
96 : 26 : uint64_t delta;
97 [ - + ]: 26 : if (!first)
98 : : {
99 : 0 : clock_gettime (CLOCK_MONOTONIC, &now);
100 : 0 : delta = ((now.tv_sec - last.tv_sec) * 1000000
101 : 0 : + (now.tv_nsec - last.tv_nsec) / 1000);
102 : : }
103 : : else
104 : : {
105 : 26 : first = false;
106 : 26 : delta = 250000;
107 : : }
108 : :
109 : : /* Show progress the first time and then at most 5 times a second. */
110 [ - - ]: 26 : if (delta > 200000)
111 : : {
112 : 26 : fprintf (stderr, "Progress %ld / %ld\n", a, b);
113 : 26 : clock_gettime (CLOCK_MONOTONIC, &last);
114 : : }
115 : : return 0;
116 : : }
117 : :
118 : :
119 : 2538 : static error_t parse_opt (int key, char *arg, struct argp_state *state)
120 : : {
121 : 2538 : (void) arg;
122 : 2538 : (void) state;
123 [ + + ]: 2538 : switch (key)
124 : : {
125 : 118 : case 'v': verbose++;
126 [ + + ]: 118 : if (verbose > 1)
127 : 72 : debuginfod_set_verbose_fd (client, STDERR_FILENO);
128 : : break;
129 : : default: return ARGP_ERR_UNKNOWN;
130 : : }
131 : : return 0;
132 : : }
133 : :
134 : :
135 : : /* Data structure to communicate with argp functions. */
136 : : static struct argp argp =
137 : : {
138 : : options, parse_opt, args_doc, doc, NULL, NULL, NULL
139 : : };
140 : :
141 : :
142 : :
143 : : int
144 : 484 : main(int argc, char** argv)
145 : : {
146 : 484 : elf_version (EV_CURRENT);
147 : :
148 : 484 : client = debuginfod_begin ();
149 [ - + ]: 484 : if (client == NULL)
150 : : {
151 : 0 : fprintf(stderr, "Couldn't create debuginfod client context\n");
152 : 0 : return 1;
153 : : }
154 : :
155 : : /* Set SIGINT handler and progressfn so that temp files can be cleaned
156 : : up when a download is cancelled. */
157 : 484 : struct sigaction sa;
158 : 484 : sigemptyset (&sa.sa_mask);
159 : 484 : sa.sa_flags = 0;
160 : 484 : sa.sa_handler = handle_sigint;
161 : 484 : sigaction (SIGINT, &sa, NULL);
162 : :
163 [ + + ]: 484 : if (getenv(DEBUGINFOD_PROGRESS_ENV_VAR) != NULL)
164 : 4 : saw_progress_envvar = true;
165 : :
166 : 484 : debuginfod_set_progressfn (client, & progressfn);
167 : :
168 : : /* Exercise user data pointer, to support testing only. */
169 : 484 : debuginfod_set_user_data (client, (void *)"Progress");
170 : :
171 : 484 : int remaining;
172 : 484 : (void) argp_parse (&argp, argc, argv, ARGP_IN_ORDER|ARGP_NO_ARGS, &remaining, NULL);
173 : :
174 [ + - + + ]: 484 : if (argc < 2 || remaining+1 >= argc) /* no arguments or at least two non-option words */
175 : : {
176 : 2 : argp_help (&argp, stderr, ARGP_HELP_USAGE, argv[0]);
177 : 2 : return 1;
178 : : }
179 : :
180 : : /* If we were passed an ELF file name in the BUILDID slot, look in there. */
181 : 482 : unsigned char* build_id = (unsigned char*) argv[remaining+1];
182 : 482 : int build_id_len = 0; /* assume text */
183 : 482 : Elf* elf = NULL;
184 : :
185 : : /* Process optional buildid given via ELF file name, for some query types only. */
186 [ + + ]: 482 : if (strcmp(argv[remaining], "debuginfo") == 0
187 [ + + ]: 270 : || strcmp(argv[remaining], "executable") == 0
188 [ + + ]: 80 : || strcmp(argv[remaining], "source") == 0
189 [ + + ]: 26 : || strcmp(argv[remaining], "section") == 0)
190 : : {
191 : : int any_non_hex = 0;
192 : : int i;
193 [ + + ]: 18950 : for (i = 0; build_id[i] != '\0'; i++)
194 [ + + ]: 18478 : if ((build_id[i] >= '0' && build_id[i] <= '9') ||
195 : : (build_id[i] >= 'a' && build_id[i] <= 'f'))
196 : : ;
197 : : else
198 : 366 : any_non_hex = 1;
199 : :
200 : 472 : int fd = -1;
201 [ + + ]: 472 : if (any_non_hex) /* raw build-id */
202 : : {
203 : 12 : fd = open ((char*) build_id, O_RDONLY);
204 [ + - ]: 12 : if (fd < 0)
205 : 0 : fprintf (stderr, "Cannot open %s: %s\n", build_id, strerror(errno));
206 : : }
207 : 12 : if (fd >= 0)
208 : : {
209 : 12 : elf = dwelf_elf_begin (fd);
210 [ + - ]: 12 : if (elf == NULL)
211 : 0 : fprintf (stderr, "Cannot open as ELF file %s: %s\n", build_id,
212 : : elf_errmsg (-1));
213 : : }
214 : 0 : if (elf != NULL)
215 : : {
216 : 12 : const void *extracted_build_id;
217 : 12 : ssize_t s = dwelf_elf_gnu_build_id(elf, &extracted_build_id);
218 [ + - ]: 12 : if (s > 0)
219 : : {
220 : : /* Success: replace the build_id pointer/len with the binary blob
221 : : that elfutils is keeping for us. It'll remain valid until elf_end(). */
222 : 12 : build_id = (unsigned char*) extracted_build_id;
223 : 12 : build_id_len = s;
224 : : }
225 : : else
226 : 0 : fprintf (stderr, "Cannot extract build-id from %s: %s\n", build_id, elf_errmsg(-1));
227 : : }
228 : : }
229 : :
230 : 482 : char *cache_name;
231 : 482 : int rc = 0;
232 : :
233 : : /* By default the stdout output is the path of the cached file.
234 : : Some requests (ex. metadata query may instead choose to do a different output,
235 : : in that case a stringified json object) */
236 : 482 : bool print_cached_file = true;
237 : : /* Check whether FILETYPE is valid and call the appropriate
238 : : debuginfod_find_* function. If FILETYPE is "source"
239 : : then ensure a FILENAME was also supplied as an argument. */
240 [ + + ]: 482 : if (strcmp(argv[remaining], "debuginfo") == 0)
241 : 212 : rc = debuginfod_find_debuginfo(client,
242 : : build_id, build_id_len,
243 : : &cache_name);
244 [ + + ]: 270 : else if (strcmp(argv[remaining], "executable") == 0)
245 : 190 : rc = debuginfod_find_executable(client,
246 : : build_id, build_id_len,
247 : : &cache_name);
248 [ + + ]: 80 : else if (strcmp(argv[remaining], "source") == 0)
249 : : {
250 [ + - - + ]: 54 : if (remaining+2 == argc || argv[remaining+2][0] != '/')
251 : : {
252 : 0 : fprintf(stderr, "If FILETYPE is \"source\" then absolute /FILENAME must be given\n");
253 : 0 : return 1;
254 : : }
255 : 54 : rc = debuginfod_find_source(client,
256 : : build_id, build_id_len,
257 : : argv[remaining+2], &cache_name);
258 : : }
259 [ + + ]: 26 : else if (strcmp(argv[remaining], "section") == 0)
260 : : {
261 [ - + ]: 16 : if (remaining+2 >= argc)
262 : : {
263 : 0 : fprintf(stderr,
264 : : "If FILETYPE is \"section\" then a section name must be given\n");
265 : 0 : return 1;
266 : : }
267 : 16 : rc = debuginfod_find_section(client, build_id, build_id_len,
268 : 16 : argv[remaining+2], &cache_name);
269 : : }
270 [ + - ]: 10 : else if (strcmp(argv[remaining], "metadata") == 0) /* no buildid! */
271 : : {
272 [ - + ]: 10 : if (remaining+2 == argc)
273 : : {
274 : 0 : fprintf(stderr, "Require KEY and VALUE for \"metadata\"\n");
275 : 0 : return 1;
276 : : }
277 : :
278 : 10 : rc = debuginfod_find_metadata (client, argv[remaining+1], argv[remaining+2],
279 : : &cache_name);
280 : : #ifndef DUMMY_LIBDEBUGINFOD
281 [ + - ]: 10 : if (rc >= 0)
282 : : {
283 : : /* We output a pprinted JSON object, not the regular debuginfod-find cached file path */
284 : 10 : print_cached_file = false;
285 : 10 : json_object *metadata = json_object_from_file(cache_name);
286 [ + - ]: 10 : if(metadata)
287 : : {
288 : 10 : printf("%s\n", json_object_to_json_string_ext(metadata,
289 : : JSON_C_TO_STRING_PRETTY
290 : : #ifdef JSON_C_TO_STRING_NOSLASHESCAPE /* json-c 0.15 */
291 : : | JSON_C_TO_STRING_NOSLASHESCAPE
292 : : #endif
293 : : ));
294 : 10 : json_object_put(metadata);
295 : : }
296 : : else
297 : : {
298 : 0 : fprintf(stderr, "%s does not contain a valid JSON format object\n", cache_name);
299 : 0 : return 1;
300 : : }
301 : : }
302 : : #endif
303 : : }
304 : : else
305 : : {
306 : 0 : argp_help (&argp, stderr, ARGP_HELP_USAGE, argv[0]);
307 : 0 : return 1;
308 : : }
309 : :
310 [ + + ]: 482 : if (verbose)
311 : : {
312 : 44 : const char* headers = debuginfod_get_headers(client);
313 [ + + ]: 44 : if (headers)
314 : 28 : fprintf(stderr, "Headers:\n%s", headers);
315 : 44 : const char* url = debuginfod_get_url (client);
316 [ + + ]: 44 : if (url != NULL)
317 : 26 : fprintf(stderr, "Downloaded from %s\n", url);
318 : : }
319 : :
320 : 482 : debuginfod_end (client);
321 [ + + ]: 482 : if (elf)
322 : 12 : elf_end(elf);
323 : :
324 [ + + ]: 482 : if (rc < 0)
325 : : {
326 [ - + ]: 36 : if (interrupted != 0)
327 : 0 : fputs ("Server query cancelled\n", stderr);
328 : : else
329 : 36 : fprintf(stderr, "Server query failed: %s\n", strerror(-rc));
330 : :
331 : 36 : return 1;
332 : : }
333 : : else
334 : 446 : close (rc);
335 : :
336 [ + + ]: 446 : if(print_cached_file) printf("%s\n", cache_name);
337 : 446 : free (cache_name);
338 : :
339 : 446 : return 0;
340 : : }
|