1/* 2 * GIT - The information manager from hell 3 * 4 * Copyright (C) Linus Torvalds, 2005 5 * Copyright (C) Johannes Schindelin, 2005 6 * 7 */ 8#include "util.h" 9#include "cache.h" 10#include "exec_cmd.h" 11 12#define MAXNAME (256) 13 14#define DEBUG_CACHE_DIR ".debug" 15 16 17char buildid_dir[MAXPATHLEN]; /* root dir for buildid, binary cache */ 18 19static FILE *config_file; 20static const char *config_file_name; 21static int config_linenr; 22static int config_file_eof; 23 24static const char *config_exclusive_filename; 25 26static int get_next_char(void) 27{ 28 int c; 29 FILE *f; 30 31 c = '\n'; 32 if ((f = config_file) != NULL) { 33 c = fgetc(f); 34 if (c == '\r') { 35 /* DOS like systems */ 36 c = fgetc(f); 37 if (c != '\n') { 38 ungetc(c, f); 39 c = '\r'; 40 } 41 } 42 if (c == '\n') 43 config_linenr++; 44 if (c == EOF) { 45 config_file_eof = 1; 46 c = '\n'; 47 } 48 } 49 return c; 50} 51 52static char *parse_value(void) 53{ 54 static char value[1024]; 55 int quote = 0, comment = 0, space = 0; 56 size_t len = 0; 57 58 for (;;) { 59 int c = get_next_char(); 60 61 if (len >= sizeof(value) - 1) 62 return NULL; 63 if (c == '\n') { 64 if (quote) 65 return NULL; 66 value[len] = 0; 67 return value; 68 } 69 if (comment) 70 continue; 71 if (isspace(c) && !quote) { 72 space = 1; 73 continue; 74 } 75 if (!quote) { 76 if (c == ';' || c == '#') { 77 comment = 1; 78 continue; 79 } 80 } 81 if (space) { 82 if (len) 83 value[len++] = ' '; 84 space = 0; 85 } 86 if (c == '\\') { 87 c = get_next_char(); 88 switch (c) { 89 case '\n': 90 continue; 91 case 't': 92 c = '\t'; 93 break; 94 case 'b': 95 c = '\b'; 96 break; 97 case 'n': 98 c = '\n'; 99 break; 100 /* Some characters escape as themselves */ 101 case '\\': case '"': 102 break; 103 /* Reject unknown escape sequences */ 104 default: 105 return NULL; 106 } 107 value[len++] = c; 108 continue; 109 } 110 if (c == '"') { 111 quote = 1-quote; 112 continue; 113 } 114 value[len++] = c; 115 } 116} 117 118static inline int iskeychar(int c) 119{ 120 return isalnum(c) || c == '-'; 121} 122 123static int get_value(config_fn_t fn, void *data, char *name, unsigned int len) 124{ 125 int c; 126 char *value; 127 128 /* Get the full name */ 129 for (;;) { 130 c = get_next_char(); 131 if (config_file_eof) 132 break; 133 if (!iskeychar(c)) 134 break; 135 name[len++] = c; 136 if (len >= MAXNAME) 137 return -1; 138 } 139 name[len] = 0; 140 while (c == ' ' || c == '\t') 141 c = get_next_char(); 142 143 value = NULL; 144 if (c != '\n') { 145 if (c != '=') 146 return -1; 147 value = parse_value(); 148 if (!value) 149 return -1; 150 } 151 return fn(name, value, data); 152} 153 154static int get_extended_base_var(char *name, int baselen, int c) 155{ 156 do { 157 if (c == '\n') 158 return -1; 159 c = get_next_char(); 160 } while (isspace(c)); 161 162 /* We require the format to be '[base "extension"]' */ 163 if (c != '"') 164 return -1; 165 name[baselen++] = '.'; 166 167 for (;;) { 168 int ch = get_next_char(); 169 170 if (ch == '\n') 171 return -1; 172 if (ch == '"') 173 break; 174 if (ch == '\\') { 175 ch = get_next_char(); 176 if (ch == '\n') 177 return -1; 178 } 179 name[baselen++] = ch; 180 if (baselen > MAXNAME / 2) 181 return -1; 182 } 183 184 /* Final ']' */ 185 if (get_next_char() != ']') 186 return -1; 187 return baselen; 188} 189 190static int get_base_var(char *name) 191{ 192 int baselen = 0; 193 194 for (;;) { 195 int c = get_next_char(); 196 if (config_file_eof) 197 return -1; 198 if (c == ']') 199 return baselen; 200 if (isspace(c)) 201 return get_extended_base_var(name, baselen, c); 202 if (!iskeychar(c) && c != '.') 203 return -1; 204 if (baselen > MAXNAME / 2) 205 return -1; 206 name[baselen++] = tolower(c); 207 } 208} 209 210static int perf_parse_file(config_fn_t fn, void *data) 211{ 212 int comment = 0; 213 int baselen = 0; 214 static char var[MAXNAME]; 215 216 /* U+FEFF Byte Order Mark in UTF8 */ 217 static const unsigned char *utf8_bom = (unsigned char *) "\xef\xbb\xbf"; 218 const unsigned char *bomptr = utf8_bom; 219 220 for (;;) { 221 int c = get_next_char(); 222 if (bomptr && *bomptr) { 223 /* We are at the file beginning; skip UTF8-encoded BOM 224 * if present. Sane editors won't put this in on their 225 * own, but e.g. Windows Notepad will do it happily. */ 226 if ((unsigned char) c == *bomptr) { 227 bomptr++; 228 continue; 229 } else { 230 /* Do not tolerate partial BOM. */ 231 if (bomptr != utf8_bom) 232 break; 233 /* No BOM at file beginning. Cool. */ 234 bomptr = NULL; 235 } 236 } 237 if (c == '\n') { 238 if (config_file_eof) 239 return 0; 240 comment = 0; 241 continue; 242 } 243 if (comment || isspace(c)) 244 continue; 245 if (c == '#' || c == ';') { 246 comment = 1; 247 continue; 248 } 249 if (c == '[') { 250 baselen = get_base_var(var); 251 if (baselen <= 0) 252 break; 253 var[baselen++] = '.'; 254 var[baselen] = 0; 255 continue; 256 } 257 if (!isalpha(c)) 258 break; 259 var[baselen] = tolower(c); 260 if (get_value(fn, data, var, baselen+1) < 0) 261 break; 262 } 263 die("bad config file line %d in %s", config_linenr, config_file_name); 264} 265 266static int parse_unit_factor(const char *end, unsigned long *val) 267{ 268 if (!*end) 269 return 1; 270 else if (!strcasecmp(end, "k")) { 271 *val *= 1024; 272 return 1; 273 } 274 else if (!strcasecmp(end, "m")) { 275 *val *= 1024 * 1024; 276 return 1; 277 } 278 else if (!strcasecmp(end, "g")) { 279 *val *= 1024 * 1024 * 1024; 280 return 1; 281 } 282 return 0; 283} 284 285static int perf_parse_long(const char *value, long *ret) 286{ 287 if (value && *value) { 288 char *end; 289 long val = strtol(value, &end, 0); 290 unsigned long factor = 1; 291 if (!parse_unit_factor(end, &factor)) 292 return 0; 293 *ret = val * factor; 294 return 1; 295 } 296 return 0; 297} 298 299static void die_bad_config(const char *name) 300{ 301 if (config_file_name) 302 die("bad config value for '%s' in %s", name, config_file_name); 303 die("bad config value for '%s'", name); 304} 305 306int perf_config_int(const char *name, const char *value) 307{ 308 long ret = 0; 309 if (!perf_parse_long(value, &ret)) 310 die_bad_config(name); 311 return ret; 312} 313 314static int perf_config_bool_or_int(const char *name, const char *value, int *is_bool) 315{ 316 *is_bool = 1; 317 if (!value) 318 return 1; 319 if (!*value) 320 return 0; 321 if (!strcasecmp(value, "true") || !strcasecmp(value, "yes") || !strcasecmp(value, "on")) 322 return 1; 323 if (!strcasecmp(value, "false") || !strcasecmp(value, "no") || !strcasecmp(value, "off")) 324 return 0; 325 *is_bool = 0; 326 return perf_config_int(name, value); 327} 328 329int perf_config_bool(const char *name, const char *value) 330{ 331 int discard; 332 return !!perf_config_bool_or_int(name, value, &discard); 333} 334 335const char *perf_config_dirname(const char *name, const char *value) 336{ 337 if (!name) 338 return NULL; 339 return value; 340} 341 342static int perf_default_core_config(const char *var __used, const char *value __used) 343{ 344 /* Add other config variables here and to Documentation/config.txt. */ 345 return 0; 346} 347 348int perf_default_config(const char *var, const char *value, void *dummy __used) 349{ 350 if (!prefixcmp(var, "core.")) 351 return perf_default_core_config(var, value); 352 353 /* Add other config variables here and to Documentation/config.txt. */ 354 return 0; 355} 356 357static int perf_config_from_file(config_fn_t fn, const char *filename, void *data) 358{ 359 int ret; 360 FILE *f = fopen(filename, "r"); 361 362 ret = -1; 363 if (f) { 364 config_file = f; 365 config_file_name = filename; 366 config_linenr = 1; 367 config_file_eof = 0; 368 ret = perf_parse_file(fn, data); 369 fclose(f); 370 config_file_name = NULL; 371 } 372 return ret; 373} 374 375static const char *perf_etc_perfconfig(void) 376{ 377 static const char *system_wide; 378 if (!system_wide) 379 system_wide = system_path(ETC_PERFCONFIG); 380 return system_wide; 381} 382 383static int perf_env_bool(const char *k, int def) 384{ 385 const char *v = getenv(k); 386 return v ? perf_config_bool(k, v) : def; 387} 388 389static int perf_config_system(void) 390{ 391 return !perf_env_bool("PERF_CONFIG_NOSYSTEM", 0); 392} 393 394static int perf_config_global(void) 395{ 396 return !perf_env_bool("PERF_CONFIG_NOGLOBAL", 0); 397} 398 399int perf_config(config_fn_t fn, void *data) 400{ 401 int ret = 0, found = 0; 402 const char *home = NULL; 403 404 /* Setting $PERF_CONFIG makes perf read _only_ the given config file. */ 405 if (config_exclusive_filename) 406 return perf_config_from_file(fn, config_exclusive_filename, data); 407 if (perf_config_system() && !access(perf_etc_perfconfig(), R_OK)) { 408 ret += perf_config_from_file(fn, perf_etc_perfconfig(), 409 data); 410 found += 1; 411 } 412 413 home = getenv("HOME"); 414 if (perf_config_global() && home) { 415 char *user_config = strdup(mkpath("%s/.perfconfig", home)); 416 if (!access(user_config, R_OK)) { 417 ret += perf_config_from_file(fn, user_config, data); 418 found += 1; 419 } 420 free(user_config); 421 } 422 423 if (found == 0) 424 return -1; 425 return ret; 426} 427 428/* 429 * Call this to report error for your variable that should not 430 * get a boolean value (i.e. "[my] var" means "true"). 431 */ 432int config_error_nonbool(const char *var) 433{ 434 return error("Missing value for '%s'", var); 435} 436 437struct buildid_dir_config { 438 char *dir; 439}; 440 441static int buildid_dir_command_config(const char *var, const char *value, 442 void *data) 443{ 444 struct buildid_dir_config *c = data; 445 const char *v; 446 447 /* same dir for all commands */ 448 if (!prefixcmp(var, "buildid.") && !strcmp(var + 8, "dir")) { 449 v = perf_config_dirname(var, value); 450 if (!v) 451 return -1; 452 strncpy(c->dir, v, MAXPATHLEN-1); 453 c->dir[MAXPATHLEN-1] = '\0'; 454 } 455 return 0; 456} 457 458static void check_buildid_dir_config(void) 459{ 460 struct buildid_dir_config c; 461 c.dir = buildid_dir; 462 perf_config(buildid_dir_command_config, &c); 463} 464 465void set_buildid_dir(void) 466{ 467 buildid_dir[0] = '\0'; 468 469 /* try config file */ 470 check_buildid_dir_config(); 471 472 /* default to $HOME/.debug */ 473 if (buildid_dir[0] == '\0') { 474 char *v = getenv("HOME"); 475 if (v) { 476 snprintf(buildid_dir, MAXPATHLEN-1, "%s/%s", 477 v, DEBUG_CACHE_DIR); 478 } else { 479 strncpy(buildid_dir, DEBUG_CACHE_DIR, MAXPATHLEN-1); 480 } 481 buildid_dir[MAXPATHLEN-1] = '\0'; 482 } 483 /* for communicating with external commands */ 484 setenv("PERF_BUILDID_DIR", buildid_dir, 1); 485} 486