1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * config.c 4 * 5 * Helper functions for parsing config items. 6 * Originally copied from GIT source. 7 * 8 * Copyright (C) Linus Torvalds, 2005 9 * Copyright (C) Johannes Schindelin, 2005 10 * 11 */ 12 #include <errno.h> 13 #include <sys/param.h> 14 #include "cache.h" 15 #include "callchain.h" 16 #include <subcmd/exec-cmd.h> 17 #include "util/event.h" /* proc_map_timeout */ 18 #include "util/hist.h" /* perf_hist_config */ 19 #include "util/stat.h" /* perf_stat__set_big_num */ 20 #include "util/evsel.h" /* evsel__hw_names, evsel__use_bpf_counters */ 21 #include "util/srcline.h" /* addr2line_timeout_ms */ 22 #include "build-id.h" 23 #include "debug.h" 24 #include "config.h" 25 #include <sys/types.h> 26 #include <sys/stat.h> 27 #include <stdlib.h> 28 #include <unistd.h> 29 #include <linux/string.h> 30 #include <linux/zalloc.h> 31 #include <linux/ctype.h> 32 33 #define MAXNAME (256) 34 35 #define DEBUG_CACHE_DIR ".debug" 36 37 38 char buildid_dir[MAXPATHLEN]; /* root dir for buildid, binary cache */ 39 40 static FILE *config_file; 41 static const char *config_file_name; 42 static int config_linenr; 43 static int config_file_eof; 44 static struct perf_config_set *config_set; 45 46 const char *config_exclusive_filename; 47 48 static int get_next_char(void) 49 { 50 int c; 51 FILE *f; 52 53 c = '\n'; 54 if ((f = config_file) != NULL) { 55 c = fgetc(f); 56 if (c == '\r') { 57 /* DOS like systems */ 58 c = fgetc(f); 59 if (c != '\n') { 60 ungetc(c, f); 61 c = '\r'; 62 } 63 } 64 if (c == '\n') 65 config_linenr++; 66 if (c == EOF) { 67 config_file_eof = 1; 68 c = '\n'; 69 } 70 } 71 return c; 72 } 73 74 static char *parse_value(void) 75 { 76 static char value[1024]; 77 int quote = 0, comment = 0, space = 0; 78 size_t len = 0; 79 80 for (;;) { 81 int c = get_next_char(); 82 83 if (len >= sizeof(value) - 1) 84 return NULL; 85 if (c == '\n') { 86 if (quote) 87 return NULL; 88 value[len] = 0; 89 return value; 90 } 91 if (comment) 92 continue; 93 if (isspace(c) && !quote) { 94 space = 1; 95 continue; 96 } 97 if (!quote) { 98 if (c == ';' || c == '#') { 99 comment = 1; 100 continue; 101 } 102 } 103 if (space) { 104 if (len) 105 value[len++] = ' '; 106 space = 0; 107 } 108 if (c == '\\') { 109 c = get_next_char(); 110 switch (c) { 111 case '\n': 112 continue; 113 case 't': 114 c = '\t'; 115 break; 116 case 'b': 117 c = '\b'; 118 break; 119 case 'n': 120 c = '\n'; 121 break; 122 /* Some characters escape as themselves */ 123 case '\\': case '"': 124 break; 125 /* Reject unknown escape sequences */ 126 default: 127 return NULL; 128 } 129 value[len++] = c; 130 continue; 131 } 132 if (c == '"') { 133 quote = 1-quote; 134 continue; 135 } 136 value[len++] = c; 137 } 138 } 139 140 static inline int iskeychar(int c) 141 { 142 return isalnum(c) || c == '-' || c == '_'; 143 } 144 145 static int get_value(config_fn_t fn, void *data, char *name, unsigned int len) 146 { 147 int c; 148 char *value; 149 150 /* Get the full name */ 151 for (;;) { 152 c = get_next_char(); 153 if (config_file_eof) 154 break; 155 if (!iskeychar(c)) 156 break; 157 name[len++] = c; 158 if (len >= MAXNAME) 159 return -1; 160 } 161 name[len] = 0; 162 while (c == ' ' || c == '\t') 163 c = get_next_char(); 164 165 value = NULL; 166 if (c != '\n') { 167 if (c != '=') 168 return -1; 169 value = parse_value(); 170 if (!value) 171 return -1; 172 } 173 return fn(name, value, data); 174 } 175 176 static int get_extended_base_var(char *name, int baselen, int c) 177 { 178 do { 179 if (c == '\n') 180 return -1; 181 c = get_next_char(); 182 } while (isspace(c)); 183 184 /* We require the format to be '[base "extension"]' */ 185 if (c != '"') 186 return -1; 187 name[baselen++] = '.'; 188 189 for (;;) { 190 int ch = get_next_char(); 191 192 if (ch == '\n') 193 return -1; 194 if (ch == '"') 195 break; 196 if (ch == '\\') { 197 ch = get_next_char(); 198 if (ch == '\n') 199 return -1; 200 } 201 name[baselen++] = ch; 202 if (baselen > MAXNAME / 2) 203 return -1; 204 } 205 206 /* Final ']' */ 207 if (get_next_char() != ']') 208 return -1; 209 return baselen; 210 } 211 212 static int get_base_var(char *name) 213 { 214 int baselen = 0; 215 216 for (;;) { 217 int c = get_next_char(); 218 if (config_file_eof) 219 return -1; 220 if (c == ']') 221 return baselen; 222 if (isspace(c)) 223 return get_extended_base_var(name, baselen, c); 224 if (!iskeychar(c) && c != '.') 225 return -1; 226 if (baselen > MAXNAME / 2) 227 return -1; 228 name[baselen++] = tolower(c); 229 } 230 } 231 232 static int perf_parse_file(config_fn_t fn, void *data) 233 { 234 int comment = 0; 235 int baselen = 0; 236 static char var[MAXNAME]; 237 238 /* U+FEFF Byte Order Mark in UTF8 */ 239 static const unsigned char *utf8_bom = (unsigned char *) "\xef\xbb\xbf"; 240 const unsigned char *bomptr = utf8_bom; 241 242 for (;;) { 243 int line, c = get_next_char(); 244 245 if (bomptr && *bomptr) { 246 /* We are at the file beginning; skip UTF8-encoded BOM 247 * if present. Sane editors won't put this in on their 248 * own, but e.g. Windows Notepad will do it happily. */ 249 if ((unsigned char) c == *bomptr) { 250 bomptr++; 251 continue; 252 } else { 253 /* Do not tolerate partial BOM. */ 254 if (bomptr != utf8_bom) 255 break; 256 /* No BOM at file beginning. Cool. */ 257 bomptr = NULL; 258 } 259 } 260 if (c == '\n') { 261 if (config_file_eof) 262 return 0; 263 comment = 0; 264 continue; 265 } 266 if (comment || isspace(c)) 267 continue; 268 if (c == '#' || c == ';') { 269 comment = 1; 270 continue; 271 } 272 if (c == '[') { 273 baselen = get_base_var(var); 274 if (baselen <= 0) 275 break; 276 var[baselen++] = '.'; 277 var[baselen] = 0; 278 continue; 279 } 280 if (!isalpha(c)) 281 break; 282 var[baselen] = tolower(c); 283 284 /* 285 * The get_value function might or might not reach the '\n', 286 * so saving the current line number for error reporting. 287 */ 288 line = config_linenr; 289 if (get_value(fn, data, var, baselen+1) < 0) { 290 config_linenr = line; 291 break; 292 } 293 } 294 pr_err("bad config file line %d in %s\n", config_linenr, config_file_name); 295 return -1; 296 } 297 298 static int parse_unit_factor(const char *end, unsigned long *val) 299 { 300 if (!*end) 301 return 1; 302 else if (!strcasecmp(end, "k")) { 303 *val *= 1024; 304 return 1; 305 } 306 else if (!strcasecmp(end, "m")) { 307 *val *= 1024 * 1024; 308 return 1; 309 } 310 else if (!strcasecmp(end, "g")) { 311 *val *= 1024 * 1024 * 1024; 312 return 1; 313 } 314 return 0; 315 } 316 317 static int perf_parse_llong(const char *value, long long *ret) 318 { 319 if (value && *value) { 320 char *end; 321 long long val = strtoll(value, &end, 0); 322 unsigned long factor = 1; 323 324 if (!parse_unit_factor(end, &factor)) 325 return 0; 326 *ret = val * factor; 327 return 1; 328 } 329 return 0; 330 } 331 332 static int perf_parse_long(const char *value, long *ret) 333 { 334 if (value && *value) { 335 char *end; 336 long val = strtol(value, &end, 0); 337 unsigned long factor = 1; 338 if (!parse_unit_factor(end, &factor)) 339 return 0; 340 *ret = val * factor; 341 return 1; 342 } 343 return 0; 344 } 345 346 static void bad_config(const char *name) 347 { 348 if (config_file_name) 349 pr_warning("bad config value for '%s' in %s, ignoring...\n", name, config_file_name); 350 else 351 pr_warning("bad config value for '%s', ignoring...\n", name); 352 } 353 354 int perf_config_u64(u64 *dest, const char *name, const char *value) 355 { 356 long long ret = 0; 357 358 if (!perf_parse_llong(value, &ret)) { 359 bad_config(name); 360 return -1; 361 } 362 363 *dest = ret; 364 return 0; 365 } 366 367 int perf_config_int(int *dest, const char *name, const char *value) 368 { 369 long ret = 0; 370 if (!perf_parse_long(value, &ret)) { 371 bad_config(name); 372 return -1; 373 } 374 *dest = ret; 375 return 0; 376 } 377 378 int perf_config_u8(u8 *dest, const char *name, const char *value) 379 { 380 long ret = 0; 381 382 if (!perf_parse_long(value, &ret)) { 383 bad_config(name); 384 return -1; 385 } 386 *dest = ret; 387 return 0; 388 } 389 390 static int perf_config_bool_or_int(const char *name, const char *value, int *is_bool) 391 { 392 int ret; 393 394 *is_bool = 1; 395 if (!value) 396 return 1; 397 if (!*value) 398 return 0; 399 if (!strcasecmp(value, "true") || !strcasecmp(value, "yes") || !strcasecmp(value, "on")) 400 return 1; 401 if (!strcasecmp(value, "false") || !strcasecmp(value, "no") || !strcasecmp(value, "off")) 402 return 0; 403 *is_bool = 0; 404 return perf_config_int(&ret, name, value) < 0 ? -1 : ret; 405 } 406 407 int perf_config_bool(const char *name, const char *value) 408 { 409 int discard; 410 return !!perf_config_bool_or_int(name, value, &discard); 411 } 412 413 static const char *perf_config_dirname(const char *name, const char *value) 414 { 415 if (!name) 416 return NULL; 417 return value; 418 } 419 420 static int perf_buildid_config(const char *var, const char *value) 421 { 422 /* same dir for all commands */ 423 if (!strcmp(var, "buildid.dir")) { 424 const char *dir = perf_config_dirname(var, value); 425 426 if (!dir) { 427 pr_err("Invalid buildid directory!\n"); 428 return -1; 429 } 430 strncpy(buildid_dir, dir, MAXPATHLEN-1); 431 buildid_dir[MAXPATHLEN-1] = '\0'; 432 } 433 434 return 0; 435 } 436 437 static int perf_default_core_config(const char *var, const char *value) 438 { 439 if (!strcmp(var, "core.proc-map-timeout")) 440 proc_map_timeout = strtoul(value, NULL, 10); 441 442 if (!strcmp(var, "core.addr2line-timeout")) 443 addr2line_timeout_ms = strtoul(value, NULL, 10); 444 445 /* Add other config variables here. */ 446 return 0; 447 } 448 449 static int perf_ui_config(const char *var, const char *value) 450 { 451 /* Add other config variables here. */ 452 if (!strcmp(var, "ui.show-headers")) 453 symbol_conf.show_hist_headers = perf_config_bool(var, value); 454 455 return 0; 456 } 457 458 static int perf_stat_config(const char *var, const char *value) 459 { 460 if (!strcmp(var, "stat.big-num")) 461 perf_stat__set_big_num(perf_config_bool(var, value)); 462 463 if (!strcmp(var, "stat.no-csv-summary")) 464 perf_stat__set_no_csv_summary(perf_config_bool(var, value)); 465 466 if (!strcmp(var, "stat.bpf-counter-events")) 467 evsel__bpf_counter_events = strdup(value); 468 469 /* Add other config variables here. */ 470 return 0; 471 } 472 473 int perf_default_config(const char *var, const char *value, 474 void *dummy __maybe_unused) 475 { 476 if (strstarts(var, "core.")) 477 return perf_default_core_config(var, value); 478 479 if (strstarts(var, "hist.")) 480 return perf_hist_config(var, value); 481 482 if (strstarts(var, "ui.")) 483 return perf_ui_config(var, value); 484 485 if (strstarts(var, "call-graph.")) 486 return perf_callchain_config(var, value); 487 488 if (strstarts(var, "buildid.")) 489 return perf_buildid_config(var, value); 490 491 if (strstarts(var, "stat.")) 492 return perf_stat_config(var, value); 493 494 /* Add other config variables here. */ 495 return 0; 496 } 497 498 static int perf_config_from_file(config_fn_t fn, const char *filename, void *data) 499 { 500 int ret; 501 FILE *f = fopen(filename, "r"); 502 503 ret = -1; 504 if (f) { 505 config_file = f; 506 config_file_name = filename; 507 config_linenr = 1; 508 config_file_eof = 0; 509 ret = perf_parse_file(fn, data); 510 fclose(f); 511 config_file_name = NULL; 512 } 513 return ret; 514 } 515 516 const char *perf_etc_perfconfig(void) 517 { 518 static const char *system_wide; 519 if (!system_wide) 520 system_wide = system_path(ETC_PERFCONFIG); 521 return system_wide; 522 } 523 524 static int perf_env_bool(const char *k, int def) 525 { 526 const char *v = getenv(k); 527 return v ? perf_config_bool(k, v) : def; 528 } 529 530 int perf_config_system(void) 531 { 532 return !perf_env_bool("PERF_CONFIG_NOSYSTEM", 0); 533 } 534 535 int perf_config_global(void) 536 { 537 return !perf_env_bool("PERF_CONFIG_NOGLOBAL", 0); 538 } 539 540 static char *home_perfconfig(void) 541 { 542 const char *home = NULL; 543 char *config; 544 struct stat st; 545 char path[PATH_MAX]; 546 547 home = getenv("HOME"); 548 549 /* 550 * Skip reading user config if: 551 * - there is no place to read it from (HOME) 552 * - we are asked not to (PERF_CONFIG_NOGLOBAL=1) 553 */ 554 if (!home || !*home || !perf_config_global()) 555 return NULL; 556 557 config = strdup(mkpath(path, sizeof(path), "%s/.perfconfig", home)); 558 if (config == NULL) { 559 pr_warning("Not enough memory to process %s/.perfconfig, ignoring it.\n", home); 560 return NULL; 561 } 562 563 if (stat(config, &st) < 0) 564 goto out_free; 565 566 if (st.st_uid && (st.st_uid != geteuid())) { 567 pr_warning("File %s not owned by current user or root, ignoring it.\n", config); 568 goto out_free; 569 } 570 571 if (st.st_size) 572 return config; 573 574 out_free: 575 free(config); 576 return NULL; 577 } 578 579 const char *perf_home_perfconfig(void) 580 { 581 static const char *config; 582 static bool failed; 583 584 if (failed || config) 585 return config; 586 587 config = home_perfconfig(); 588 if (!config) 589 failed = true; 590 591 return config; 592 } 593 594 static struct perf_config_section *find_section(struct list_head *sections, 595 const char *section_name) 596 { 597 struct perf_config_section *section; 598 599 list_for_each_entry(section, sections, node) 600 if (!strcmp(section->name, section_name)) 601 return section; 602 603 return NULL; 604 } 605 606 static struct perf_config_item *find_config_item(const char *name, 607 struct perf_config_section *section) 608 { 609 struct perf_config_item *item; 610 611 list_for_each_entry(item, §ion->items, node) 612 if (!strcmp(item->name, name)) 613 return item; 614 615 return NULL; 616 } 617 618 static struct perf_config_section *add_section(struct list_head *sections, 619 const char *section_name) 620 { 621 struct perf_config_section *section = zalloc(sizeof(*section)); 622 623 if (!section) 624 return NULL; 625 626 INIT_LIST_HEAD(§ion->items); 627 section->name = strdup(section_name); 628 if (!section->name) { 629 pr_debug("%s: strdup failed\n", __func__); 630 free(section); 631 return NULL; 632 } 633 634 list_add_tail(§ion->node, sections); 635 return section; 636 } 637 638 static struct perf_config_item *add_config_item(struct perf_config_section *section, 639 const char *name) 640 { 641 struct perf_config_item *item = zalloc(sizeof(*item)); 642 643 if (!item) 644 return NULL; 645 646 item->name = strdup(name); 647 if (!item->name) { 648 pr_debug("%s: strdup failed\n", __func__); 649 free(item); 650 return NULL; 651 } 652 653 list_add_tail(&item->node, §ion->items); 654 return item; 655 } 656 657 static int set_value(struct perf_config_item *item, const char *value) 658 { 659 char *val = strdup(value); 660 661 if (!val) 662 return -1; 663 664 zfree(&item->value); 665 item->value = val; 666 return 0; 667 } 668 669 static int collect_config(const char *var, const char *value, 670 void *perf_config_set) 671 { 672 int ret = -1; 673 char *ptr, *key; 674 char *section_name, *name; 675 struct perf_config_section *section = NULL; 676 struct perf_config_item *item = NULL; 677 struct perf_config_set *set = perf_config_set; 678 struct list_head *sections; 679 680 if (set == NULL) 681 return -1; 682 683 sections = &set->sections; 684 key = ptr = strdup(var); 685 if (!key) { 686 pr_debug("%s: strdup failed\n", __func__); 687 return -1; 688 } 689 690 section_name = strsep(&ptr, "."); 691 name = ptr; 692 if (name == NULL || value == NULL) 693 goto out_free; 694 695 section = find_section(sections, section_name); 696 if (!section) { 697 section = add_section(sections, section_name); 698 if (!section) 699 goto out_free; 700 } 701 702 item = find_config_item(name, section); 703 if (!item) { 704 item = add_config_item(section, name); 705 if (!item) 706 goto out_free; 707 } 708 709 /* perf_config_set can contain both user and system config items. 710 * So we should know where each value is from. 711 * The classification would be needed when a particular config file 712 * is overwritten by setting feature i.e. set_config(). 713 */ 714 if (strcmp(config_file_name, perf_etc_perfconfig()) == 0) { 715 section->from_system_config = true; 716 item->from_system_config = true; 717 } else { 718 section->from_system_config = false; 719 item->from_system_config = false; 720 } 721 722 ret = set_value(item, value); 723 724 out_free: 725 free(key); 726 return ret; 727 } 728 729 int perf_config_set__collect(struct perf_config_set *set, const char *file_name, 730 const char *var, const char *value) 731 { 732 config_file_name = file_name; 733 return collect_config(var, value, set); 734 } 735 736 static int perf_config_set__init(struct perf_config_set *set) 737 { 738 int ret = -1; 739 740 /* Setting $PERF_CONFIG makes perf read _only_ the given config file. */ 741 if (config_exclusive_filename) 742 return perf_config_from_file(collect_config, config_exclusive_filename, set); 743 if (perf_config_system() && !access(perf_etc_perfconfig(), R_OK)) { 744 if (perf_config_from_file(collect_config, perf_etc_perfconfig(), set) < 0) 745 goto out; 746 } 747 if (perf_config_global() && perf_home_perfconfig()) { 748 if (perf_config_from_file(collect_config, perf_home_perfconfig(), set) < 0) 749 goto out; 750 } 751 752 out: 753 return ret; 754 } 755 756 struct perf_config_set *perf_config_set__new(void) 757 { 758 struct perf_config_set *set = zalloc(sizeof(*set)); 759 760 if (set) { 761 INIT_LIST_HEAD(&set->sections); 762 perf_config_set__init(set); 763 } 764 765 return set; 766 } 767 768 struct perf_config_set *perf_config_set__load_file(const char *file) 769 { 770 struct perf_config_set *set = zalloc(sizeof(*set)); 771 772 if (set) { 773 INIT_LIST_HEAD(&set->sections); 774 perf_config_from_file(collect_config, file, set); 775 } 776 777 return set; 778 } 779 780 static int perf_config__init(void) 781 { 782 if (config_set == NULL) 783 config_set = perf_config_set__new(); 784 785 return config_set == NULL; 786 } 787 788 int perf_config_set(struct perf_config_set *set, 789 config_fn_t fn, void *data) 790 { 791 int ret = 0; 792 char key[BUFSIZ]; 793 struct perf_config_section *section; 794 struct perf_config_item *item; 795 796 perf_config_set__for_each_entry(set, section, item) { 797 char *value = item->value; 798 799 if (value) { 800 scnprintf(key, sizeof(key), "%s.%s", 801 section->name, item->name); 802 ret = fn(key, value, data); 803 if (ret < 0) { 804 pr_err("Error in the given config file: wrong config key-value pair %s=%s\n", 805 key, value); 806 /* 807 * Can't be just a 'break', as perf_config_set__for_each_entry() 808 * expands to two nested for() loops. 809 */ 810 goto out; 811 } 812 } 813 } 814 out: 815 return ret; 816 } 817 818 int perf_config(config_fn_t fn, void *data) 819 { 820 if (config_set == NULL && perf_config__init()) 821 return -1; 822 823 return perf_config_set(config_set, fn, data); 824 } 825 826 void perf_config__exit(void) 827 { 828 perf_config_set__delete(config_set); 829 config_set = NULL; 830 } 831 832 void perf_config__refresh(void) 833 { 834 perf_config__exit(); 835 perf_config__init(); 836 } 837 838 static void perf_config_item__delete(struct perf_config_item *item) 839 { 840 zfree(&item->name); 841 zfree(&item->value); 842 free(item); 843 } 844 845 static void perf_config_section__purge(struct perf_config_section *section) 846 { 847 struct perf_config_item *item, *tmp; 848 849 list_for_each_entry_safe(item, tmp, §ion->items, node) { 850 list_del_init(&item->node); 851 perf_config_item__delete(item); 852 } 853 } 854 855 static void perf_config_section__delete(struct perf_config_section *section) 856 { 857 perf_config_section__purge(section); 858 zfree(§ion->name); 859 free(section); 860 } 861 862 static void perf_config_set__purge(struct perf_config_set *set) 863 { 864 struct perf_config_section *section, *tmp; 865 866 list_for_each_entry_safe(section, tmp, &set->sections, node) { 867 list_del_init(§ion->node); 868 perf_config_section__delete(section); 869 } 870 } 871 872 void perf_config_set__delete(struct perf_config_set *set) 873 { 874 if (set == NULL) 875 return; 876 877 perf_config_set__purge(set); 878 free(set); 879 } 880 881 /* 882 * Call this to report error for your variable that should not 883 * get a boolean value (i.e. "[my] var" means "true"). 884 */ 885 int config_error_nonbool(const char *var) 886 { 887 pr_err("Missing value for '%s'", var); 888 return -1; 889 } 890 891 void set_buildid_dir(const char *dir) 892 { 893 if (dir) 894 scnprintf(buildid_dir, MAXPATHLEN, "%s", dir); 895 896 /* default to $HOME/.debug */ 897 if (buildid_dir[0] == '\0') { 898 char *home = getenv("HOME"); 899 900 if (home) { 901 snprintf(buildid_dir, MAXPATHLEN, "%s/%s", 902 home, DEBUG_CACHE_DIR); 903 } else { 904 strncpy(buildid_dir, DEBUG_CACHE_DIR, MAXPATHLEN-1); 905 } 906 buildid_dir[MAXPATHLEN-1] = '\0'; 907 } 908 /* for communicating with external commands */ 909 setenv("PERF_BUILDID_DIR", buildid_dir, 1); 910 } 911 912 struct perf_config_scan_data { 913 const char *name; 914 const char *fmt; 915 const char *value; 916 va_list args; 917 int ret; 918 }; 919 920 static int perf_config_scan_cb(const char *var, const char *value, void *data) 921 { 922 struct perf_config_scan_data *d = data; 923 924 if (!strcmp(var, d->name)) 925 d->ret = vsscanf(value, d->fmt, d->args); 926 927 return 0; 928 } 929 930 int perf_config_scan(const char *name, const char *fmt, ...) 931 { 932 struct perf_config_scan_data d = { 933 .name = name, 934 .fmt = fmt, 935 }; 936 937 va_start(d.args, fmt); 938 perf_config(perf_config_scan_cb, &d); 939 va_end(d.args); 940 941 return d.ret; 942 } 943 944 static int perf_config_get_cb(const char *var, const char *value, void *data) 945 { 946 struct perf_config_scan_data *d = data; 947 948 if (!strcmp(var, d->name)) 949 d->value = value; 950 951 return 0; 952 } 953 954 const char *perf_config_get(const char *name) 955 { 956 struct perf_config_scan_data d = { 957 .name = name, 958 .value = NULL, 959 }; 960 961 perf_config(perf_config_get_cb, &d); 962 return d.value; 963 } 964