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 17 char buildid_dir[MAXPATHLEN]; /* root dir for buildid, binary cache */ 18 19 static FILE *config_file; 20 static const char *config_file_name; 21 static int config_linenr; 22 static int config_file_eof; 23 24 static const char *config_exclusive_filename; 25 26 static 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 52 static 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 118 static inline int iskeychar(int c) 119 { 120 return isalnum(c) || c == '-'; 121 } 122 123 static 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 154 static 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 190 static 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 210 static 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 266 static 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 285 static 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 299 static 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 306 int 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 314 static 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 329 int 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 335 const char *perf_config_dirname(const char *name, const char *value) 336 { 337 if (!name) 338 return NULL; 339 return value; 340 } 341 342 static 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 348 int 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 357 static 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 375 static 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 383 static 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 389 static int perf_config_system(void) 390 { 391 return !perf_env_bool("PERF_CONFIG_NOSYSTEM", 0); 392 } 393 394 static int perf_config_global(void) 395 { 396 return !perf_env_bool("PERF_CONFIG_NOGLOBAL", 0); 397 } 398 399 int 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 */ 432 int config_error_nonbool(const char *var) 433 { 434 return error("Missing value for '%s'", var); 435 } 436 437 struct buildid_dir_config { 438 char *dir; 439 }; 440 441 static 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 458 static 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 465 void 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