1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * builtin-buildid-cache.c |
4 | * |
5 | * Builtin buildid-cache command: Manages build-id cache |
6 | * |
7 | * Copyright (C) 2010, Red Hat Inc. |
8 | * Copyright (C) 2010, Arnaldo Carvalho de Melo <acme@redhat.com> |
9 | */ |
10 | #include <sys/types.h> |
11 | #include <sys/time.h> |
12 | #include <time.h> |
13 | #include <dirent.h> |
14 | #include <errno.h> |
15 | #include <unistd.h> |
16 | #include "builtin.h" |
17 | #include "namespaces.h" |
18 | #include "util/debug.h" |
19 | #include "util/header.h" |
20 | #include <subcmd/pager.h> |
21 | #include <subcmd/parse-options.h> |
22 | #include "util/strlist.h" |
23 | #include "util/build-id.h" |
24 | #include "util/session.h" |
25 | #include "util/dso.h" |
26 | #include "util/symbol.h" |
27 | #include "util/time-utils.h" |
28 | #include "util/util.h" |
29 | #include "util/probe-file.h" |
30 | #include "util/config.h" |
31 | #include <linux/string.h> |
32 | #include <linux/err.h> |
33 | |
34 | static int build_id_cache__kcore_buildid(const char *proc_dir, char *sbuildid) |
35 | { |
36 | char root_dir[PATH_MAX]; |
37 | char *p; |
38 | |
39 | strlcpy(p: root_dir, q: proc_dir, size: sizeof(root_dir)); |
40 | |
41 | p = strrchr(root_dir, '/'); |
42 | if (!p) |
43 | return -1; |
44 | *p = '\0'; |
45 | return sysfs__sprintf_build_id(root_dir, sbuild_id: sbuildid); |
46 | } |
47 | |
48 | static int build_id_cache__kcore_dir(char *dir, size_t sz) |
49 | { |
50 | return fetch_current_timestamp(buf: dir, sz); |
51 | } |
52 | |
53 | static bool same_kallsyms_reloc(const char *from_dir, char *to_dir) |
54 | { |
55 | char from[PATH_MAX]; |
56 | char to[PATH_MAX]; |
57 | const char *name; |
58 | u64 addr1 = 0, addr2 = 0; |
59 | int i, err = -1; |
60 | |
61 | scnprintf(buf: from, size: sizeof(from), fmt: "%s/kallsyms" , from_dir); |
62 | scnprintf(buf: to, size: sizeof(to), fmt: "%s/kallsyms" , to_dir); |
63 | |
64 | for (i = 0; (name = ref_reloc_sym_names[i]) != NULL; i++) { |
65 | err = kallsyms__get_function_start(kallsyms_filename: from, symbol_name: name, addr: &addr1); |
66 | if (!err) |
67 | break; |
68 | } |
69 | |
70 | if (err) |
71 | return false; |
72 | |
73 | if (kallsyms__get_function_start(kallsyms_filename: to, symbol_name: name, addr: &addr2)) |
74 | return false; |
75 | |
76 | return addr1 == addr2; |
77 | } |
78 | |
79 | static int build_id_cache__kcore_existing(const char *from_dir, char *to_dir, |
80 | size_t to_dir_sz) |
81 | { |
82 | char from[PATH_MAX]; |
83 | char to[PATH_MAX]; |
84 | char to_subdir[PATH_MAX]; |
85 | struct dirent *dent; |
86 | int ret = -1; |
87 | DIR *d; |
88 | |
89 | d = opendir(to_dir); |
90 | if (!d) |
91 | return -1; |
92 | |
93 | scnprintf(buf: from, size: sizeof(from), fmt: "%s/modules" , from_dir); |
94 | |
95 | while (1) { |
96 | dent = readdir(d); |
97 | if (!dent) |
98 | break; |
99 | if (dent->d_type != DT_DIR) |
100 | continue; |
101 | scnprintf(buf: to, size: sizeof(to), fmt: "%s/%s/modules" , to_dir, |
102 | dent->d_name); |
103 | scnprintf(buf: to_subdir, size: sizeof(to_subdir), fmt: "%s/%s" , |
104 | to_dir, dent->d_name); |
105 | if (!compare_proc_modules(from, to) && |
106 | same_kallsyms_reloc(from_dir, to_dir: to_subdir)) { |
107 | strlcpy(p: to_dir, q: to_subdir, size: to_dir_sz); |
108 | ret = 0; |
109 | break; |
110 | } |
111 | } |
112 | |
113 | closedir(d); |
114 | |
115 | return ret; |
116 | } |
117 | |
118 | static int build_id_cache__add_kcore(const char *filename, bool force) |
119 | { |
120 | char dir[32], sbuildid[SBUILD_ID_SIZE]; |
121 | char from_dir[PATH_MAX], to_dir[PATH_MAX]; |
122 | char *p; |
123 | |
124 | strlcpy(p: from_dir, q: filename, size: sizeof(from_dir)); |
125 | |
126 | p = strrchr(from_dir, '/'); |
127 | if (!p || strcmp(p + 1, "kcore" )) |
128 | return -1; |
129 | *p = '\0'; |
130 | |
131 | if (build_id_cache__kcore_buildid(proc_dir: from_dir, sbuildid) < 0) |
132 | return -1; |
133 | |
134 | scnprintf(buf: to_dir, size: sizeof(to_dir), fmt: "%s/%s/%s" , |
135 | buildid_dir, DSO__NAME_KCORE, sbuildid); |
136 | |
137 | if (!force && |
138 | !build_id_cache__kcore_existing(from_dir, to_dir, to_dir_sz: sizeof(to_dir))) { |
139 | pr_debug("same kcore found in %s\n" , to_dir); |
140 | return 0; |
141 | } |
142 | |
143 | if (build_id_cache__kcore_dir(dir, sz: sizeof(dir))) |
144 | return -1; |
145 | |
146 | scnprintf(buf: to_dir, size: sizeof(to_dir), fmt: "%s/%s/%s/%s" , |
147 | buildid_dir, DSO__NAME_KCORE, sbuildid, dir); |
148 | |
149 | if (mkdir_p(path: to_dir, mode: 0755)) |
150 | return -1; |
151 | |
152 | if (kcore_copy(from_dir, to_dir)) { |
153 | /* Remove YYYYmmddHHMMSShh directory */ |
154 | if (!rmdir(to_dir)) { |
155 | p = strrchr(to_dir, '/'); |
156 | if (p) |
157 | *p = '\0'; |
158 | /* Try to remove buildid directory */ |
159 | if (!rmdir(to_dir)) { |
160 | p = strrchr(to_dir, '/'); |
161 | if (p) |
162 | *p = '\0'; |
163 | /* Try to remove [kernel.kcore] directory */ |
164 | rmdir(to_dir); |
165 | } |
166 | } |
167 | return -1; |
168 | } |
169 | |
170 | pr_debug("kcore added to build-id cache directory %s\n" , to_dir); |
171 | |
172 | return 0; |
173 | } |
174 | |
175 | static int build_id_cache__add_file(const char *filename, struct nsinfo *nsi) |
176 | { |
177 | char sbuild_id[SBUILD_ID_SIZE]; |
178 | struct build_id bid; |
179 | int err; |
180 | struct nscookie nsc; |
181 | |
182 | nsinfo__mountns_enter(nsi, &nsc); |
183 | err = filename__read_build_id(filename, id: &bid); |
184 | nsinfo__mountns_exit(&nsc); |
185 | if (err < 0) { |
186 | pr_debug("Couldn't read a build-id in %s\n" , filename); |
187 | return -1; |
188 | } |
189 | |
190 | build_id__sprintf(build_id: &bid, bf: sbuild_id); |
191 | err = build_id_cache__add_s(sbuild_id, name: filename, nsi, |
192 | is_kallsyms: false, is_vdso: false); |
193 | pr_debug("Adding %s %s: %s\n" , sbuild_id, filename, |
194 | err ? "FAIL" : "Ok" ); |
195 | return err; |
196 | } |
197 | |
198 | static int build_id_cache__remove_file(const char *filename, struct nsinfo *nsi) |
199 | { |
200 | char sbuild_id[SBUILD_ID_SIZE]; |
201 | struct build_id bid; |
202 | struct nscookie nsc; |
203 | |
204 | int err; |
205 | |
206 | nsinfo__mountns_enter(nsi, &nsc); |
207 | err = filename__read_build_id(filename, id: &bid); |
208 | nsinfo__mountns_exit(&nsc); |
209 | if (err < 0) { |
210 | pr_debug("Couldn't read a build-id in %s\n" , filename); |
211 | return -1; |
212 | } |
213 | |
214 | build_id__sprintf(build_id: &bid, bf: sbuild_id); |
215 | err = build_id_cache__remove_s(sbuild_id); |
216 | pr_debug("Removing %s %s: %s\n" , sbuild_id, filename, |
217 | err ? "FAIL" : "Ok" ); |
218 | |
219 | return err; |
220 | } |
221 | |
222 | static int build_id_cache__purge_path(const char *pathname, struct nsinfo *nsi) |
223 | { |
224 | struct strlist *list; |
225 | struct str_node *pos; |
226 | int err; |
227 | |
228 | err = build_id_cache__list_build_ids(pathname, nsi, result: &list); |
229 | if (err) |
230 | goto out; |
231 | |
232 | strlist__for_each_entry(pos, list) { |
233 | err = build_id_cache__remove_s(sbuild_id: pos->s); |
234 | pr_debug("Removing %s %s: %s\n" , pos->s, pathname, |
235 | err ? "FAIL" : "Ok" ); |
236 | if (err) |
237 | break; |
238 | } |
239 | strlist__delete(slist: list); |
240 | |
241 | out: |
242 | pr_debug("Purging %s: %s\n" , pathname, err ? "FAIL" : "Ok" ); |
243 | |
244 | return err; |
245 | } |
246 | |
247 | static int build_id_cache__purge_all(void) |
248 | { |
249 | struct strlist *list; |
250 | struct str_node *pos; |
251 | int err = 0; |
252 | char *buf; |
253 | |
254 | list = build_id_cache__list_all(validonly: false); |
255 | if (!list) { |
256 | pr_debug("Failed to get buildids: -%d\n" , errno); |
257 | return -EINVAL; |
258 | } |
259 | |
260 | strlist__for_each_entry(pos, list) { |
261 | buf = build_id_cache__origname(sbuild_id: pos->s); |
262 | err = build_id_cache__remove_s(sbuild_id: pos->s); |
263 | pr_debug("Removing %s (%s): %s\n" , buf, pos->s, |
264 | err ? "FAIL" : "Ok" ); |
265 | free(buf); |
266 | if (err) |
267 | break; |
268 | } |
269 | strlist__delete(slist: list); |
270 | |
271 | pr_debug("Purged all: %s\n" , err ? "FAIL" : "Ok" ); |
272 | return err; |
273 | } |
274 | |
275 | static bool dso__missing_buildid_cache(struct dso *dso, int parm __maybe_unused) |
276 | { |
277 | char filename[PATH_MAX]; |
278 | struct build_id bid; |
279 | |
280 | if (!dso__build_id_filename(dso, bf: filename, size: sizeof(filename), is_debug: false)) |
281 | return true; |
282 | |
283 | if (filename__read_build_id(filename, id: &bid) == -1) { |
284 | if (errno == ENOENT) |
285 | return false; |
286 | |
287 | pr_warning("Problems with %s file, consider removing it from the cache\n" , |
288 | filename); |
289 | } else if (memcmp(p: dso->bid.data, q: bid.data, size: bid.size)) { |
290 | pr_warning("Problems with %s file, consider removing it from the cache\n" , |
291 | filename); |
292 | } |
293 | |
294 | return true; |
295 | } |
296 | |
297 | static int build_id_cache__fprintf_missing(struct perf_session *session, FILE *fp) |
298 | { |
299 | perf_session__fprintf_dsos_buildid(session, fp, dso__missing_buildid_cache, 0); |
300 | return 0; |
301 | } |
302 | |
303 | static int build_id_cache__update_file(const char *filename, struct nsinfo *nsi) |
304 | { |
305 | char sbuild_id[SBUILD_ID_SIZE]; |
306 | struct build_id bid; |
307 | struct nscookie nsc; |
308 | |
309 | int err; |
310 | |
311 | nsinfo__mountns_enter(nsi, &nsc); |
312 | err = filename__read_build_id(filename, id: &bid); |
313 | nsinfo__mountns_exit(&nsc); |
314 | if (err < 0) { |
315 | pr_debug("Couldn't read a build-id in %s\n" , filename); |
316 | return -1; |
317 | } |
318 | err = 0; |
319 | |
320 | build_id__sprintf(build_id: &bid, bf: sbuild_id); |
321 | if (build_id_cache__cached(sbuild_id)) |
322 | err = build_id_cache__remove_s(sbuild_id); |
323 | |
324 | if (!err) |
325 | err = build_id_cache__add_s(sbuild_id, name: filename, nsi, is_kallsyms: false, |
326 | is_vdso: false); |
327 | |
328 | pr_debug("Updating %s %s: %s\n" , sbuild_id, filename, |
329 | err ? "FAIL" : "Ok" ); |
330 | |
331 | return err; |
332 | } |
333 | |
334 | static int build_id_cache__show_all(void) |
335 | { |
336 | struct strlist *bidlist; |
337 | struct str_node *nd; |
338 | char *buf; |
339 | |
340 | bidlist = build_id_cache__list_all(validonly: true); |
341 | if (!bidlist) { |
342 | pr_debug("Failed to get buildids: -%d\n" , errno); |
343 | return -1; |
344 | } |
345 | strlist__for_each_entry(nd, bidlist) { |
346 | buf = build_id_cache__origname(sbuild_id: nd->s); |
347 | fprintf(stdout, "%s %s\n" , nd->s, buf); |
348 | free(buf); |
349 | } |
350 | strlist__delete(slist: bidlist); |
351 | return 0; |
352 | } |
353 | |
354 | static int perf_buildid_cache_config(const char *var, const char *value, void *cb) |
355 | { |
356 | struct perf_debuginfod *di = cb; |
357 | |
358 | if (!strcmp(var, "buildid-cache.debuginfod" )) { |
359 | di->urls = strdup(value); |
360 | if (!di->urls) |
361 | return -ENOMEM; |
362 | di->set = true; |
363 | } |
364 | |
365 | return 0; |
366 | } |
367 | |
368 | int cmd_buildid_cache(int argc, const char **argv) |
369 | { |
370 | struct strlist *list; |
371 | struct str_node *pos; |
372 | int ret, ns_id = -1; |
373 | bool force = false; |
374 | bool list_files = false; |
375 | bool opts_flag = false; |
376 | bool purge_all = false; |
377 | char const *add_name_list_str = NULL, |
378 | *remove_name_list_str = NULL, |
379 | *purge_name_list_str = NULL, |
380 | *missing_filename = NULL, |
381 | *update_name_list_str = NULL, |
382 | *kcore_filename = NULL; |
383 | struct perf_debuginfod debuginfod = { }; |
384 | char sbuf[STRERR_BUFSIZE]; |
385 | |
386 | struct perf_data data = { |
387 | .mode = PERF_DATA_MODE_READ, |
388 | }; |
389 | struct perf_session *session = NULL; |
390 | struct nsinfo *nsi = NULL; |
391 | |
392 | const struct option buildid_cache_options[] = { |
393 | OPT_STRING('a', "add" , &add_name_list_str, |
394 | "file list" , "file(s) to add" ), |
395 | OPT_STRING('k', "kcore" , &kcore_filename, |
396 | "file" , "kcore file to add" ), |
397 | OPT_STRING('r', "remove" , &remove_name_list_str, "file list" , |
398 | "file(s) to remove" ), |
399 | OPT_STRING('p', "purge" , &purge_name_list_str, "file list" , |
400 | "file(s) to remove (remove old caches too)" ), |
401 | OPT_BOOLEAN('P', "purge-all" , &purge_all, "purge all cached files" ), |
402 | OPT_BOOLEAN('l', "list" , &list_files, "list all cached files" ), |
403 | OPT_STRING('M', "missing" , &missing_filename, "file" , |
404 | "to find missing build ids in the cache" ), |
405 | OPT_BOOLEAN('f', "force" , &force, "don't complain, do it" ), |
406 | OPT_STRING('u', "update" , &update_name_list_str, "file list" , |
407 | "file(s) to update" ), |
408 | OPT_STRING_OPTARG_SET(0, "debuginfod" , &debuginfod.urls, |
409 | &debuginfod.set, "debuginfod urls" , |
410 | "Enable debuginfod data retrieval from DEBUGINFOD_URLS or specified urls" , |
411 | "system" ), |
412 | OPT_INCR('v', "verbose" , &verbose, "be more verbose" ), |
413 | OPT_INTEGER(0, "target-ns" , &ns_id, "target pid for namespace context" ), |
414 | OPT_END() |
415 | }; |
416 | const char * const buildid_cache_usage[] = { |
417 | "perf buildid-cache [<options>]" , |
418 | NULL |
419 | }; |
420 | |
421 | ret = perf_config(fn: perf_buildid_cache_config, &debuginfod); |
422 | if (ret) |
423 | return ret; |
424 | |
425 | argc = parse_options(argc, argv, buildid_cache_options, |
426 | buildid_cache_usage, 0); |
427 | |
428 | opts_flag = add_name_list_str || kcore_filename || |
429 | remove_name_list_str || purge_name_list_str || |
430 | missing_filename || update_name_list_str || |
431 | purge_all; |
432 | |
433 | if (argc || !(list_files || opts_flag)) |
434 | usage_with_options(buildid_cache_usage, buildid_cache_options); |
435 | |
436 | perf_debuginfod_setup(di: &debuginfod); |
437 | |
438 | /* -l is exclusive. It can not be used with other options. */ |
439 | if (list_files && opts_flag) { |
440 | usage_with_options_msg(buildid_cache_usage, |
441 | buildid_cache_options, "-l is exclusive.\n" ); |
442 | } |
443 | |
444 | if (ns_id > 0) |
445 | nsi = nsinfo__new(ns_id); |
446 | |
447 | if (missing_filename) { |
448 | data.path = missing_filename; |
449 | data.force = force; |
450 | |
451 | session = perf_session__new(data: &data, NULL); |
452 | if (IS_ERR(ptr: session)) |
453 | return PTR_ERR(ptr: session); |
454 | } |
455 | |
456 | if (symbol__init(env: session ? &session->header.env : NULL) < 0) |
457 | goto out; |
458 | |
459 | setup_pager(); |
460 | |
461 | if (list_files) { |
462 | ret = build_id_cache__show_all(); |
463 | goto out; |
464 | } |
465 | |
466 | if (add_name_list_str) { |
467 | list = strlist__new(slist: add_name_list_str, NULL); |
468 | if (list) { |
469 | strlist__for_each_entry(pos, list) |
470 | if (build_id_cache__add_file(filename: pos->s, nsi)) { |
471 | if (errno == EEXIST) { |
472 | pr_debug("%s already in the cache\n" , |
473 | pos->s); |
474 | continue; |
475 | } |
476 | pr_warning("Couldn't add %s: %s\n" , |
477 | pos->s, str_error_r(errno, sbuf, sizeof(sbuf))); |
478 | } |
479 | |
480 | strlist__delete(slist: list); |
481 | } |
482 | } |
483 | |
484 | if (remove_name_list_str) { |
485 | list = strlist__new(slist: remove_name_list_str, NULL); |
486 | if (list) { |
487 | strlist__for_each_entry(pos, list) |
488 | if (build_id_cache__remove_file(filename: pos->s, nsi)) { |
489 | if (errno == ENOENT) { |
490 | pr_debug("%s wasn't in the cache\n" , |
491 | pos->s); |
492 | continue; |
493 | } |
494 | pr_warning("Couldn't remove %s: %s\n" , |
495 | pos->s, str_error_r(errno, sbuf, sizeof(sbuf))); |
496 | } |
497 | |
498 | strlist__delete(slist: list); |
499 | } |
500 | } |
501 | |
502 | if (purge_name_list_str) { |
503 | list = strlist__new(slist: purge_name_list_str, NULL); |
504 | if (list) { |
505 | strlist__for_each_entry(pos, list) |
506 | if (build_id_cache__purge_path(pathname: pos->s, nsi)) { |
507 | if (errno == ENOENT) { |
508 | pr_debug("%s wasn't in the cache\n" , |
509 | pos->s); |
510 | continue; |
511 | } |
512 | pr_warning("Couldn't remove %s: %s\n" , |
513 | pos->s, str_error_r(errno, sbuf, sizeof(sbuf))); |
514 | } |
515 | |
516 | strlist__delete(slist: list); |
517 | } |
518 | } |
519 | |
520 | if (purge_all) { |
521 | if (build_id_cache__purge_all()) { |
522 | pr_warning("Couldn't remove some caches. Error: %s.\n" , |
523 | str_error_r(errno, sbuf, sizeof(sbuf))); |
524 | } |
525 | } |
526 | |
527 | if (missing_filename) |
528 | ret = build_id_cache__fprintf_missing(session, stdout); |
529 | |
530 | if (update_name_list_str) { |
531 | list = strlist__new(slist: update_name_list_str, NULL); |
532 | if (list) { |
533 | strlist__for_each_entry(pos, list) |
534 | if (build_id_cache__update_file(filename: pos->s, nsi)) { |
535 | if (errno == ENOENT) { |
536 | pr_debug("%s wasn't in the cache\n" , |
537 | pos->s); |
538 | continue; |
539 | } |
540 | pr_warning("Couldn't update %s: %s\n" , |
541 | pos->s, str_error_r(errno, sbuf, sizeof(sbuf))); |
542 | } |
543 | |
544 | strlist__delete(slist: list); |
545 | } |
546 | } |
547 | |
548 | if (kcore_filename && build_id_cache__add_kcore(filename: kcore_filename, force)) |
549 | pr_warning("Couldn't add %s\n" , kcore_filename); |
550 | |
551 | out: |
552 | perf_session__delete(session); |
553 | nsinfo__zput(nsi); |
554 | |
555 | return ret; |
556 | } |
557 | |