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