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