2009-04-20 21:00:56 +08:00
|
|
|
/*
|
|
|
|
* GIT - The information manager from hell
|
|
|
|
*
|
|
|
|
* Copyright (C) Linus Torvalds, 2005
|
|
|
|
* Copyright (C) Johannes Schindelin, 2005
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
#include "util.h"
|
|
|
|
#include "cache.h"
|
|
|
|
#include "exec_cmd.h"
|
|
|
|
|
|
|
|
#define MAXNAME (256)
|
|
|
|
|
|
|
|
static FILE *config_file;
|
|
|
|
static const char *config_file_name;
|
|
|
|
static int config_linenr;
|
|
|
|
static int config_file_eof;
|
|
|
|
|
2010-05-19 05:29:23 +08:00
|
|
|
static const char *config_exclusive_filename;
|
2009-04-20 21:00:56 +08:00
|
|
|
|
|
|
|
static int get_next_char(void)
|
|
|
|
{
|
|
|
|
int c;
|
|
|
|
FILE *f;
|
|
|
|
|
|
|
|
c = '\n';
|
|
|
|
if ((f = config_file) != NULL) {
|
|
|
|
c = fgetc(f);
|
|
|
|
if (c == '\r') {
|
|
|
|
/* DOS like systems */
|
|
|
|
c = fgetc(f);
|
|
|
|
if (c != '\n') {
|
|
|
|
ungetc(c, f);
|
|
|
|
c = '\r';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (c == '\n')
|
|
|
|
config_linenr++;
|
|
|
|
if (c == EOF) {
|
|
|
|
config_file_eof = 1;
|
|
|
|
c = '\n';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *parse_value(void)
|
|
|
|
{
|
|
|
|
static char value[1024];
|
2009-07-01 18:37:06 +08:00
|
|
|
int quote = 0, comment = 0, space = 0;
|
|
|
|
size_t len = 0;
|
2009-04-20 21:00:56 +08:00
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
int c = get_next_char();
|
2009-07-01 18:37:06 +08:00
|
|
|
|
2009-04-20 21:00:56 +08:00
|
|
|
if (len >= sizeof(value) - 1)
|
|
|
|
return NULL;
|
|
|
|
if (c == '\n') {
|
|
|
|
if (quote)
|
|
|
|
return NULL;
|
|
|
|
value[len] = 0;
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
if (comment)
|
|
|
|
continue;
|
|
|
|
if (isspace(c) && !quote) {
|
|
|
|
space = 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!quote) {
|
|
|
|
if (c == ';' || c == '#') {
|
|
|
|
comment = 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (space) {
|
|
|
|
if (len)
|
|
|
|
value[len++] = ' ';
|
|
|
|
space = 0;
|
|
|
|
}
|
|
|
|
if (c == '\\') {
|
|
|
|
c = get_next_char();
|
|
|
|
switch (c) {
|
|
|
|
case '\n':
|
|
|
|
continue;
|
|
|
|
case 't':
|
|
|
|
c = '\t';
|
|
|
|
break;
|
|
|
|
case 'b':
|
|
|
|
c = '\b';
|
|
|
|
break;
|
|
|
|
case 'n':
|
|
|
|
c = '\n';
|
|
|
|
break;
|
|
|
|
/* Some characters escape as themselves */
|
|
|
|
case '\\': case '"':
|
|
|
|
break;
|
|
|
|
/* Reject unknown escape sequences */
|
|
|
|
default:
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
value[len++] = c;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (c == '"') {
|
|
|
|
quote = 1-quote;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
value[len++] = c;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int iskeychar(int c)
|
|
|
|
{
|
|
|
|
return isalnum(c) || c == '-';
|
|
|
|
}
|
|
|
|
|
|
|
|
static int get_value(config_fn_t fn, void *data, char *name, unsigned int len)
|
|
|
|
{
|
|
|
|
int c;
|
|
|
|
char *value;
|
|
|
|
|
|
|
|
/* Get the full name */
|
|
|
|
for (;;) {
|
|
|
|
c = get_next_char();
|
|
|
|
if (config_file_eof)
|
|
|
|
break;
|
|
|
|
if (!iskeychar(c))
|
|
|
|
break;
|
|
|
|
name[len++] = tolower(c);
|
|
|
|
if (len >= MAXNAME)
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
name[len] = 0;
|
|
|
|
while (c == ' ' || c == '\t')
|
|
|
|
c = get_next_char();
|
|
|
|
|
|
|
|
value = NULL;
|
|
|
|
if (c != '\n') {
|
|
|
|
if (c != '=')
|
|
|
|
return -1;
|
|
|
|
value = parse_value();
|
|
|
|
if (!value)
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return fn(name, value, data);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int get_extended_base_var(char *name, int baselen, int c)
|
|
|
|
{
|
|
|
|
do {
|
|
|
|
if (c == '\n')
|
|
|
|
return -1;
|
|
|
|
c = get_next_char();
|
|
|
|
} while (isspace(c));
|
|
|
|
|
|
|
|
/* We require the format to be '[base "extension"]' */
|
|
|
|
if (c != '"')
|
|
|
|
return -1;
|
|
|
|
name[baselen++] = '.';
|
|
|
|
|
|
|
|
for (;;) {
|
2009-08-15 18:26:57 +08:00
|
|
|
int ch = get_next_char();
|
|
|
|
|
|
|
|
if (ch == '\n')
|
2009-04-20 21:00:56 +08:00
|
|
|
return -1;
|
2009-08-15 18:26:57 +08:00
|
|
|
if (ch == '"')
|
2009-04-20 21:00:56 +08:00
|
|
|
break;
|
2009-08-15 18:26:57 +08:00
|
|
|
if (ch == '\\') {
|
|
|
|
ch = get_next_char();
|
|
|
|
if (ch == '\n')
|
2009-04-20 21:00:56 +08:00
|
|
|
return -1;
|
|
|
|
}
|
2009-08-15 18:26:57 +08:00
|
|
|
name[baselen++] = ch;
|
2009-04-20 21:00:56 +08:00
|
|
|
if (baselen > MAXNAME / 2)
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Final ']' */
|
|
|
|
if (get_next_char() != ']')
|
|
|
|
return -1;
|
|
|
|
return baselen;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int get_base_var(char *name)
|
|
|
|
{
|
|
|
|
int baselen = 0;
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
int c = get_next_char();
|
|
|
|
if (config_file_eof)
|
|
|
|
return -1;
|
|
|
|
if (c == ']')
|
|
|
|
return baselen;
|
|
|
|
if (isspace(c))
|
|
|
|
return get_extended_base_var(name, baselen, c);
|
|
|
|
if (!iskeychar(c) && c != '.')
|
|
|
|
return -1;
|
|
|
|
if (baselen > MAXNAME / 2)
|
|
|
|
return -1;
|
|
|
|
name[baselen++] = tolower(c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int perf_parse_file(config_fn_t fn, void *data)
|
|
|
|
{
|
|
|
|
int comment = 0;
|
|
|
|
int baselen = 0;
|
|
|
|
static char var[MAXNAME];
|
|
|
|
|
|
|
|
/* U+FEFF Byte Order Mark in UTF8 */
|
|
|
|
static const unsigned char *utf8_bom = (unsigned char *) "\xef\xbb\xbf";
|
|
|
|
const unsigned char *bomptr = utf8_bom;
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
int c = get_next_char();
|
|
|
|
if (bomptr && *bomptr) {
|
|
|
|
/* We are at the file beginning; skip UTF8-encoded BOM
|
|
|
|
* if present. Sane editors won't put this in on their
|
|
|
|
* own, but e.g. Windows Notepad will do it happily. */
|
|
|
|
if ((unsigned char) c == *bomptr) {
|
|
|
|
bomptr++;
|
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
/* Do not tolerate partial BOM. */
|
|
|
|
if (bomptr != utf8_bom)
|
|
|
|
break;
|
|
|
|
/* No BOM at file beginning. Cool. */
|
|
|
|
bomptr = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (c == '\n') {
|
|
|
|
if (config_file_eof)
|
|
|
|
return 0;
|
|
|
|
comment = 0;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (comment || isspace(c))
|
|
|
|
continue;
|
|
|
|
if (c == '#' || c == ';') {
|
|
|
|
comment = 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (c == '[') {
|
|
|
|
baselen = get_base_var(var);
|
|
|
|
if (baselen <= 0)
|
|
|
|
break;
|
|
|
|
var[baselen++] = '.';
|
|
|
|
var[baselen] = 0;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!isalpha(c))
|
|
|
|
break;
|
|
|
|
var[baselen] = tolower(c);
|
|
|
|
if (get_value(fn, data, var, baselen+1) < 0)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
die("bad config file line %d in %s", config_linenr, config_file_name);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int parse_unit_factor(const char *end, unsigned long *val)
|
|
|
|
{
|
|
|
|
if (!*end)
|
|
|
|
return 1;
|
|
|
|
else if (!strcasecmp(end, "k")) {
|
|
|
|
*val *= 1024;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else if (!strcasecmp(end, "m")) {
|
|
|
|
*val *= 1024 * 1024;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else if (!strcasecmp(end, "g")) {
|
|
|
|
*val *= 1024 * 1024 * 1024;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int perf_parse_long(const char *value, long *ret)
|
|
|
|
{
|
|
|
|
if (value && *value) {
|
|
|
|
char *end;
|
|
|
|
long val = strtol(value, &end, 0);
|
|
|
|
unsigned long factor = 1;
|
|
|
|
if (!parse_unit_factor(end, &factor))
|
|
|
|
return 0;
|
|
|
|
*ret = val * factor;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void die_bad_config(const char *name)
|
|
|
|
{
|
|
|
|
if (config_file_name)
|
|
|
|
die("bad config value for '%s' in %s", name, config_file_name);
|
|
|
|
die("bad config value for '%s'", name);
|
|
|
|
}
|
|
|
|
|
|
|
|
int perf_config_int(const char *name, const char *value)
|
|
|
|
{
|
|
|
|
long ret = 0;
|
|
|
|
if (!perf_parse_long(value, &ret))
|
|
|
|
die_bad_config(name);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2010-05-19 05:29:23 +08:00
|
|
|
static int perf_config_bool_or_int(const char *name, const char *value, int *is_bool)
|
2009-04-20 21:00:56 +08:00
|
|
|
{
|
|
|
|
*is_bool = 1;
|
|
|
|
if (!value)
|
|
|
|
return 1;
|
|
|
|
if (!*value)
|
|
|
|
return 0;
|
|
|
|
if (!strcasecmp(value, "true") || !strcasecmp(value, "yes") || !strcasecmp(value, "on"))
|
|
|
|
return 1;
|
|
|
|
if (!strcasecmp(value, "false") || !strcasecmp(value, "no") || !strcasecmp(value, "off"))
|
|
|
|
return 0;
|
|
|
|
*is_bool = 0;
|
|
|
|
return perf_config_int(name, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
int perf_config_bool(const char *name, const char *value)
|
|
|
|
{
|
|
|
|
int discard;
|
|
|
|
return !!perf_config_bool_or_int(name, value, &discard);
|
|
|
|
}
|
|
|
|
|
2009-07-01 18:37:06 +08:00
|
|
|
static int perf_default_core_config(const char *var __used, const char *value __used)
|
2009-04-20 21:00:56 +08:00
|
|
|
{
|
|
|
|
/* Add other config variables here and to Documentation/config.txt. */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-07-01 18:37:06 +08:00
|
|
|
int perf_default_config(const char *var, const char *value, void *dummy __used)
|
2009-04-20 21:00:56 +08:00
|
|
|
{
|
|
|
|
if (!prefixcmp(var, "core."))
|
|
|
|
return perf_default_core_config(var, value);
|
|
|
|
|
|
|
|
/* Add other config variables here and to Documentation/config.txt. */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-05-19 05:29:23 +08:00
|
|
|
static int perf_config_from_file(config_fn_t fn, const char *filename, void *data)
|
2009-04-20 21:00:56 +08:00
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
FILE *f = fopen(filename, "r");
|
|
|
|
|
|
|
|
ret = -1;
|
|
|
|
if (f) {
|
|
|
|
config_file = f;
|
|
|
|
config_file_name = filename;
|
|
|
|
config_linenr = 1;
|
|
|
|
config_file_eof = 0;
|
|
|
|
ret = perf_parse_file(fn, data);
|
|
|
|
fclose(f);
|
|
|
|
config_file_name = NULL;
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2010-05-19 05:29:23 +08:00
|
|
|
static const char *perf_etc_perfconfig(void)
|
2009-04-20 21:00:56 +08:00
|
|
|
{
|
|
|
|
static const char *system_wide;
|
|
|
|
if (!system_wide)
|
|
|
|
system_wide = system_path(ETC_PERFCONFIG);
|
|
|
|
return system_wide;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int perf_env_bool(const char *k, int def)
|
|
|
|
{
|
|
|
|
const char *v = getenv(k);
|
|
|
|
return v ? perf_config_bool(k, v) : def;
|
|
|
|
}
|
|
|
|
|
2010-05-19 05:29:23 +08:00
|
|
|
static int perf_config_system(void)
|
2009-04-20 21:00:56 +08:00
|
|
|
{
|
|
|
|
return !perf_env_bool("PERF_CONFIG_NOSYSTEM", 0);
|
|
|
|
}
|
|
|
|
|
2010-05-19 05:29:23 +08:00
|
|
|
static int perf_config_global(void)
|
2009-04-20 21:00:56 +08:00
|
|
|
{
|
|
|
|
return !perf_env_bool("PERF_CONFIG_NOGLOBAL", 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
int perf_config(config_fn_t fn, void *data)
|
|
|
|
{
|
|
|
|
int ret = 0, found = 0;
|
|
|
|
char *repo_config = NULL;
|
|
|
|
const char *home = NULL;
|
|
|
|
|
|
|
|
/* Setting $PERF_CONFIG makes perf read _only_ the given config file. */
|
|
|
|
if (config_exclusive_filename)
|
|
|
|
return perf_config_from_file(fn, config_exclusive_filename, data);
|
|
|
|
if (perf_config_system() && !access(perf_etc_perfconfig(), R_OK)) {
|
|
|
|
ret += perf_config_from_file(fn, perf_etc_perfconfig(),
|
|
|
|
data);
|
|
|
|
found += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
home = getenv("HOME");
|
|
|
|
if (perf_config_global() && home) {
|
|
|
|
char *user_config = strdup(mkpath("%s/.perfconfig", home));
|
|
|
|
if (!access(user_config, R_OK)) {
|
|
|
|
ret += perf_config_from_file(fn, user_config, data);
|
|
|
|
found += 1;
|
|
|
|
}
|
|
|
|
free(user_config);
|
|
|
|
}
|
|
|
|
|
|
|
|
repo_config = perf_pathdup("config");
|
|
|
|
if (!access(repo_config, R_OK)) {
|
|
|
|
ret += perf_config_from_file(fn, repo_config, data);
|
|
|
|
found += 1;
|
|
|
|
}
|
|
|
|
free(repo_config);
|
|
|
|
if (found == 0)
|
|
|
|
return -1;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Call this to report error for your variable that should not
|
|
|
|
* get a boolean value (i.e. "[my] var" means "true").
|
|
|
|
*/
|
|
|
|
int config_error_nonbool(const char *var)
|
|
|
|
{
|
|
|
|
return error("Missing value for '%s'", var);
|
|
|
|
}
|