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