mirror of
https://github.com/reactos/ccache.git
synced 2024-11-27 05:30:38 +00:00
552 lines
12 KiB
C
552 lines
12 KiB
C
/*
|
|
* Copyright (C) 2011 Joel Rosdahl
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the Free
|
|
* Software Foundation; either version 3 of the License, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program; if not, write to the Free Software Foundation, Inc., 51
|
|
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "conf.h"
|
|
#include "ccache.h"
|
|
|
|
typedef bool (*conf_item_parser)(const char *str, void *result, char **errmsg);
|
|
typedef bool (*conf_item_verifier)(void *value, char **errmsg);
|
|
|
|
struct conf_item {
|
|
const char *name;
|
|
conf_item_parser parser;
|
|
size_t offset;
|
|
conf_item_verifier verifier;
|
|
};
|
|
|
|
struct env_to_conf_item {
|
|
const char *env_name;
|
|
const char *conf_name;
|
|
};
|
|
|
|
static bool
|
|
parse_bool(const char *str, void *result, char **errmsg)
|
|
{
|
|
bool *value = (bool *)result;
|
|
|
|
if (str_eq(str, "true")) {
|
|
*value = true;
|
|
return true;
|
|
} else if (str_eq(str, "false")) {
|
|
*value = false;
|
|
return true;
|
|
} else {
|
|
*errmsg = format("not a boolean value: \"%s\"", str);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool
|
|
parse_env_string(const char *str, void *result, char **errmsg)
|
|
{
|
|
char **value = (char **)result;
|
|
free(*value);
|
|
*value = subst_env_in_string(str, errmsg);
|
|
return *value;
|
|
}
|
|
|
|
static bool
|
|
parse_octal(const char *str, void *result, char **errmsg)
|
|
{
|
|
unsigned *value = (unsigned *)result;
|
|
char *endptr;
|
|
errno = 0;
|
|
*value = strtoul(str, &endptr, 8);
|
|
if (errno == 0 && *str != '\0' && *endptr == '\0') {
|
|
return true;
|
|
} else {
|
|
*errmsg = format("not an octal integer: \"%s\"", str);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool
|
|
parse_size(const char *str, void *result, char **errmsg)
|
|
{
|
|
unsigned *value = (unsigned *)result;
|
|
size_t size;
|
|
*errmsg = NULL;
|
|
if (parse_size_with_suffix(str, &size)) {
|
|
*value = size;
|
|
return true;
|
|
} else {
|
|
*errmsg = format("invalid size: \"%s\"", str);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool
|
|
parse_sloppiness(const char *str, void *result, char **errmsg)
|
|
{
|
|
unsigned *value = (unsigned *)result;
|
|
char *word, *p, *q, *saveptr = NULL;
|
|
|
|
if (!str) {
|
|
return *value;
|
|
}
|
|
p = x_strdup(str);
|
|
q = p;
|
|
while ((word = strtok_r(q, ", ", &saveptr))) {
|
|
if (str_eq(word, "file_macro")) {
|
|
*value |= SLOPPY_FILE_MACRO;
|
|
} else if (str_eq(word, "include_file_mtime")) {
|
|
*value |= SLOPPY_INCLUDE_FILE_MTIME;
|
|
} else if (str_eq(word, "time_macros")) {
|
|
*value |= SLOPPY_TIME_MACROS;
|
|
} else {
|
|
*errmsg = format("unknown sloppiness: \"%s\"", word);
|
|
free(p);
|
|
return false;
|
|
}
|
|
q = NULL;
|
|
}
|
|
free(p);
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
parse_string(const char *str, void *result, char **errmsg)
|
|
{
|
|
char **value = (char **)result;
|
|
(void)errmsg;
|
|
free(*value);
|
|
*value = x_strdup(str);
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
parse_unsigned(const char *str, void *result, char **errmsg)
|
|
{
|
|
unsigned *value = (unsigned *)result;
|
|
long x;
|
|
char *endptr;
|
|
errno = 0;
|
|
x = strtol(str, &endptr, 10);
|
|
if (errno == 0 && x >= 0 && x <= (unsigned)-1 && *str != '\0'
|
|
&& *endptr == '\0') {
|
|
*value = x;
|
|
return true;
|
|
} else {
|
|
*errmsg = format("invalid unsigned integer: \"%s\"", str);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool
|
|
verify_absolute_path(void *value, char **errmsg)
|
|
{
|
|
char **path = (char **)value;
|
|
assert(*path);
|
|
if (str_eq(*path, "")) {
|
|
/* The empty string means "disable" in this case. */
|
|
return true;
|
|
} else if (is_absolute_path(*path)) {
|
|
return true;
|
|
} else {
|
|
*errmsg = format("not an absolute path: \"%s\"", *path);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool
|
|
verify_dir_levels(void *value, char **errmsg)
|
|
{
|
|
unsigned *levels = (unsigned *)value;
|
|
assert(levels);
|
|
if (*levels >= 1 && *levels <= 8) {
|
|
return true;
|
|
} else {
|
|
*errmsg = format("cache directory levels must be between 1 and 8");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#define ITEM(name, type) \
|
|
{#name, parse_##type, offsetof(struct conf, name), NULL}
|
|
#define ITEM_V(name, type, verification) \
|
|
{#name, parse_##type, offsetof(struct conf, name), verify_##verification}
|
|
|
|
static const struct conf_item conf_items[] = {
|
|
ITEM_V(base_dir, env_string, absolute_path),
|
|
ITEM(cache_dir, env_string),
|
|
ITEM_V(cache_dir_levels, unsigned, dir_levels),
|
|
ITEM(compiler, string),
|
|
ITEM(compiler_check, string),
|
|
ITEM(compression, bool),
|
|
ITEM(cpp_extension, string),
|
|
ITEM(detect_shebang, bool),
|
|
ITEM(direct_mode, bool),
|
|
ITEM(disable, bool),
|
|
ITEM(extra_files_to_hash, env_string),
|
|
ITEM(hard_link, bool),
|
|
ITEM(hash_dir, bool),
|
|
ITEM(log_file, env_string),
|
|
ITEM(max_files, unsigned),
|
|
ITEM(max_size, size),
|
|
ITEM(path, env_string),
|
|
ITEM(prefix_command, env_string),
|
|
ITEM(read_only, bool),
|
|
ITEM(recache, bool),
|
|
ITEM(run_second_cpp, bool),
|
|
ITEM(sloppiness, sloppiness),
|
|
ITEM(stats, bool),
|
|
ITEM(temporary_dir, env_string),
|
|
ITEM(umask, octal),
|
|
ITEM(unify, bool)
|
|
};
|
|
|
|
#define ENV_TO_CONF(env_name, conf_name) \
|
|
{#env_name, #conf_name}
|
|
|
|
static const struct env_to_conf_item env_to_conf_items[] = {
|
|
ENV_TO_CONF(BASEDIR, base_dir),
|
|
ENV_TO_CONF(CC, compiler),
|
|
ENV_TO_CONF(COMPILERCHECK, compiler_check),
|
|
ENV_TO_CONF(COMPRESS, compression),
|
|
ENV_TO_CONF(CPP2, run_second_cpp),
|
|
ENV_TO_CONF(DETECT_SHEBANG, detect_shebang),
|
|
ENV_TO_CONF(DIR, cache_dir),
|
|
ENV_TO_CONF(DIRECT, direct_mode),
|
|
ENV_TO_CONF(DISABLE, disable),
|
|
ENV_TO_CONF(EXTENSION, cpp_extension),
|
|
ENV_TO_CONF(EXTRAFILES, extra_files_to_hash),
|
|
ENV_TO_CONF(HARDLINK, hard_link),
|
|
ENV_TO_CONF(HASHDIR, hash_dir),
|
|
ENV_TO_CONF(LOGFILE, log_file),
|
|
ENV_TO_CONF(MAXFILES, max_files),
|
|
ENV_TO_CONF(MAXSIZE, max_size),
|
|
ENV_TO_CONF(NLEVELS, cache_dir_levels),
|
|
ENV_TO_CONF(PATH, path),
|
|
ENV_TO_CONF(PREFIX, prefix_command),
|
|
ENV_TO_CONF(READONLY, read_only),
|
|
ENV_TO_CONF(RECACHE, recache),
|
|
ENV_TO_CONF(SLOPPINESS, sloppiness),
|
|
ENV_TO_CONF(STATS, stats),
|
|
ENV_TO_CONF(TEMPDIR, temporary_dir),
|
|
ENV_TO_CONF(UMASK, umask),
|
|
ENV_TO_CONF(UNIFY, unify)
|
|
};
|
|
|
|
static int
|
|
compare_conf_items(const void *key1, const void *key2)
|
|
{
|
|
const struct conf_item *conf1 = (const struct conf_item *)key1;
|
|
const struct conf_item *conf2 = (const struct conf_item *)key2;
|
|
return strcmp(conf1->name, conf2->name);
|
|
}
|
|
|
|
static const struct conf_item *
|
|
find_conf(const char *name)
|
|
{
|
|
struct conf_item key;
|
|
key.name = name;
|
|
return bsearch(
|
|
&key, conf_items, sizeof(conf_items) / sizeof(conf_items[0]),
|
|
sizeof(conf_items[0]), compare_conf_items);
|
|
}
|
|
|
|
static int
|
|
compare_env_to_conf_items(const void *key1, const void *key2)
|
|
{
|
|
const struct env_to_conf_item *conf1 = (const struct env_to_conf_item *)key1;
|
|
const struct env_to_conf_item *conf2 = (const struct env_to_conf_item *)key2;
|
|
return strcmp(conf1->env_name, conf2->env_name);
|
|
}
|
|
|
|
static const struct env_to_conf_item *
|
|
find_env_to_conf(const char *name)
|
|
{
|
|
struct env_to_conf_item key;
|
|
key.env_name = name;
|
|
return bsearch(
|
|
&key,
|
|
env_to_conf_items,
|
|
sizeof(env_to_conf_items) / sizeof(env_to_conf_items[0]),
|
|
sizeof(env_to_conf_items[0]),
|
|
compare_env_to_conf_items);
|
|
}
|
|
|
|
static bool
|
|
handle_conf_setting(struct conf *conf, const char *key, const char *value,
|
|
char **errmsg, bool from_env_variable, bool negate_boolean)
|
|
{
|
|
const struct conf_item *item;
|
|
|
|
item = find_conf(key);
|
|
if (!item) {
|
|
*errmsg = format("unknown configuration option \"%s\"", key);
|
|
return false;
|
|
}
|
|
|
|
if (from_env_variable && item->parser == parse_bool) {
|
|
/*
|
|
* Special rule for boolean settings from the environment: any value means
|
|
* true.
|
|
*/
|
|
bool *value = (bool *)((void *)conf + item->offset);
|
|
*value = !negate_boolean;
|
|
return true;
|
|
}
|
|
|
|
if (!item->parser(value, (void *)conf + item->offset, errmsg)) {
|
|
return false;
|
|
}
|
|
if (item->verifier && !item->verifier((void *)conf + item->offset, errmsg)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
parse_line(struct conf *conf, const char *line, char **errmsg)
|
|
{
|
|
const char *p, *q;
|
|
char *key, *value;
|
|
bool result;
|
|
|
|
#define SKIP_WS(x) while (isspace(*x)) { ++x; }
|
|
|
|
p = line;
|
|
SKIP_WS(p);
|
|
if (*p == '\0' || *p == '#') {
|
|
return true;
|
|
}
|
|
q = p;
|
|
while (isalpha(*q) || *q == '_') {
|
|
++q;
|
|
}
|
|
key = x_strndup(p, q - p);
|
|
p = q;
|
|
SKIP_WS(p);
|
|
if (*p != '=') {
|
|
*errmsg = x_strdup("missing equal sign");
|
|
free(key);
|
|
return false;
|
|
}
|
|
++p;
|
|
|
|
/* Skip leading whitespace. */
|
|
SKIP_WS(p);
|
|
q = p;
|
|
while (*q) {
|
|
++q;
|
|
}
|
|
/* Skip trailing whitespace. */
|
|
while (isspace(q[-1])) {
|
|
--q;
|
|
}
|
|
value = x_strndup(p, q - p);
|
|
|
|
#undef SKIP_WS
|
|
|
|
result = handle_conf_setting(conf, key, value, errmsg, false, false);
|
|
|
|
free(key);
|
|
free(value);
|
|
return result;
|
|
}
|
|
|
|
/* For test purposes. */
|
|
bool
|
|
conf_verify_sortedness(void)
|
|
{
|
|
size_t i;
|
|
for (i = 1; i < sizeof(conf_items)/sizeof(conf_items[0]); i++) {
|
|
if (strcmp(conf_items[i-1].name, conf_items[i].name) >= 0) {
|
|
fprintf(stderr,
|
|
"conf_verify_sortedness: %s >= %s\n",
|
|
conf_items[i-1].name,
|
|
conf_items[i].name);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* For test purposes. */
|
|
bool
|
|
conf_verify_env_table_correctness(void)
|
|
{
|
|
size_t i;
|
|
for (i = 0;
|
|
i < sizeof(env_to_conf_items) / sizeof(env_to_conf_items[0]);
|
|
i++) {
|
|
if (i > 0
|
|
&& strcmp(env_to_conf_items[i-1].env_name,
|
|
env_to_conf_items[i].env_name) >= 0) {
|
|
fprintf(stderr,
|
|
"conf_verify_env_table_correctness: %s >= %s\n",
|
|
env_to_conf_items[i-1].env_name,
|
|
env_to_conf_items[i].env_name);
|
|
return false;
|
|
}
|
|
if (!find_conf(env_to_conf_items[i].conf_name)) {
|
|
fprintf(stderr,
|
|
"conf_verify_env_table_correctness: %s -> %s,"
|
|
" which doesn't exist\n",
|
|
env_to_conf_items[i].env_name,
|
|
env_to_conf_items[i].conf_name);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* Create a conf struct with default values. */
|
|
struct conf *
|
|
conf_create(void)
|
|
{
|
|
struct conf *conf = x_malloc(sizeof(*conf));
|
|
conf->base_dir = x_strdup("");
|
|
conf->cache_dir = format("%s/.ccache", get_home_directory());
|
|
conf->cache_dir_levels = 2;
|
|
conf->compiler = x_strdup("");
|
|
conf->compiler_check = x_strdup("mtime");
|
|
conf->compression = false;
|
|
conf->cpp_extension = x_strdup("");
|
|
conf->detect_shebang = false;
|
|
conf->direct_mode = true;
|
|
conf->disable = false;
|
|
conf->extra_files_to_hash = x_strdup("");
|
|
conf->hard_link = false;
|
|
conf->hash_dir = false;
|
|
conf->log_file = x_strdup("");
|
|
conf->max_files = 0;
|
|
conf->max_size = 1024 * 1024; /* kilobyte */
|
|
conf->path = x_strdup("");
|
|
conf->prefix_command = x_strdup("");
|
|
conf->read_only = false;
|
|
conf->recache = false;
|
|
conf->run_second_cpp = false;
|
|
conf->sloppiness = 0;
|
|
conf->stats = true;
|
|
conf->temporary_dir = x_strdup("");
|
|
conf->umask = UINT_MAX; /* default: don't set umask */
|
|
conf->unify = false;
|
|
return conf;
|
|
}
|
|
|
|
void
|
|
conf_free(struct conf *conf)
|
|
{
|
|
if (!conf) {
|
|
return;
|
|
}
|
|
free(conf->base_dir);
|
|
free(conf->cache_dir);
|
|
free(conf->compiler);
|
|
free(conf->compiler_check);
|
|
free(conf->cpp_extension);
|
|
free(conf->extra_files_to_hash);
|
|
free(conf->log_file);
|
|
free(conf->path);
|
|
free(conf->prefix_command);
|
|
free(conf->temporary_dir);
|
|
free(conf);
|
|
}
|
|
|
|
bool
|
|
conf_read(struct conf *conf, const char *path, char **errmsg)
|
|
{
|
|
FILE *f;
|
|
char buf[10000];
|
|
bool result = true;
|
|
unsigned line_number;
|
|
|
|
assert(errmsg);
|
|
*errmsg = NULL;
|
|
|
|
f = fopen(path, "r");
|
|
if (!f) {
|
|
*errmsg = format("%s: %s", path, strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
line_number = 0;
|
|
while (fgets(buf, sizeof(buf), f)) {
|
|
char *errmsg2;
|
|
++line_number;
|
|
if (!parse_line(conf, buf, &errmsg2)) {
|
|
*errmsg = format("%s:%u: %s", path, line_number, errmsg2);
|
|
free(errmsg2);
|
|
result = false;
|
|
goto out;
|
|
}
|
|
}
|
|
if (ferror(f)) {
|
|
*errmsg = x_strdup(strerror(errno));
|
|
result = false;
|
|
}
|
|
|
|
out:
|
|
fclose(f);
|
|
return result;
|
|
}
|
|
|
|
bool
|
|
conf_update_from_environment(struct conf *conf, char **errmsg)
|
|
{
|
|
char **p;
|
|
char *q;
|
|
char *key;
|
|
char *errmsg2;
|
|
const struct env_to_conf_item *env_to_conf_item;
|
|
bool negate;
|
|
size_t key_start;
|
|
|
|
for (p = environ; *p; ++p) {
|
|
if (!str_startswith(*p, "CCACHE_")) {
|
|
continue;
|
|
}
|
|
q = strchr(*p, '=');
|
|
if (!q) {
|
|
continue;
|
|
}
|
|
|
|
if (str_startswith(*p + 7, "NO")) {
|
|
negate = true;
|
|
key_start = 9;
|
|
} else {
|
|
negate = false;
|
|
key_start = 7;
|
|
}
|
|
key = x_strndup(*p + key_start, q - *p - key_start);
|
|
|
|
++q; /* Now points to the value. */
|
|
|
|
env_to_conf_item = find_env_to_conf(key);
|
|
if (!env_to_conf_item) {
|
|
free(key);
|
|
continue;
|
|
}
|
|
|
|
if (!handle_conf_setting(
|
|
conf, env_to_conf_item->conf_name, q, &errmsg2, true, negate)) {
|
|
*errmsg = format("%s: %s", key, errmsg2);
|
|
free(errmsg2);
|
|
free(key);
|
|
return false;
|
|
}
|
|
|
|
free(key);
|
|
}
|
|
|
|
return true;
|
|
}
|