/*
This file is part of GNUnet.
Copyright (C) 2006, 2007, 2008, 2009, 2013, 2020, 2021 GNUnet e.V.
GNUnet is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License,
or (at your option) any later version.
GNUnet 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
Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
SPDX-License-Identifier: AGPL3.0-or-later
*/
/**
* @file src/util/configuration.c
* @brief configuration management
* @author Christian Grothoff
*/
#include "platform.h"
#include "gnunet_crypto_lib.h"
#include "gnunet_strings_lib.h"
#include "gnunet_os_lib.h"
#include "gnunet_configuration_lib.h"
#include "gnunet_disk_lib.h"
#include "gnunet_buffer_lib.h"
#include "gnunet_container_lib.h"
#define LOG(kind, ...) GNUNET_log_from (kind, "util", __VA_ARGS__)
#define LOG_STRERROR_FILE(kind, syscall, filename) \
GNUNET_log_from_strerror_file (kind, "util", syscall, filename)
/**
* @brief configuration entry
*/
struct ConfigEntry
{
/**
* This is a linked list.
*/
struct ConfigEntry *next;
/**
* key for this entry
*/
char *key;
/**
* current, committed value
*/
char *val;
/**
* Diagnostics information for the filename.
*/
char *hint_filename;
/**
* Diagnostics information for the line number.
*/
unsigned int hint_lineno;
};
/**
* @brief configuration section
*/
struct ConfigSection
{
/**
* This is a linked list.
*/
struct ConfigSection *next;
/**
* entries in the section
*/
struct ConfigEntry *entries;
/**
* name of the section
*/
char *name;
/**
* Is the configuration section marked as inaccessible?
*
* This can happen if the section name is used in an @inline-secret@
* directive, but the referenced file can't be found or accessed.
*/
bool inaccessible;
/**
* Diagnostics hint for the secret file.
*/
char *hint_secret_filename;
/**
* Extra information regarding permissions of the secret file.
*/
char *hint_secret_stat;
/**
* For secret sections: Where was this inlined from?
*/
char *hint_inlined_from_filename;
/**
* For secret sections: Where was this inlined from?
*/
unsigned int hint_inlined_from_line;
};
struct ConfigFile
{
/**
* Source filename.
*/
char *source_filename;
/**
* Level in the tree of loaded config files.
*/
unsigned int level;
struct ConfigFile *prev;
struct ConfigFile *next;
/**
* Was this configuration file parsed via
* @inline-secret@?
*/
char *hint_restrict_section;
/**
* Was this configuration file inaccessible?
*/
bool hint_inaccessible;
};
/**
* @brief configuration data
*/
struct GNUNET_CONFIGURATION_Handle
{
/**
* Configuration sections.
*/
struct ConfigSection *sections;
/**
* Linked list of loaded files.
*/
struct ConfigFile *loaded_files_head;
/**
* Linked list of loaded files.
*/
struct ConfigFile *loaded_files_tail;
/**
* Current nesting level of file loading.
*/
unsigned int current_nest_level;
/**
* Enable diagnostics.
*/
bool diagnostics;
/**
* Modification indication since last save
* #GNUNET_NO if clean, #GNUNET_YES if dirty,
* #GNUNET_SYSERR on error (i.e. last save failed)
*/
enum GNUNET_GenericReturnValue dirty;
/**
* Was the configuration ever loaded via GNUNET_CONFIGURATION_load?
*/
bool load_called;
/**
* Name of the entry point configuration file.
*/
char *main_filename;
/**
* When parsing into this configuration, and this value
* is non-NULL, only parse sections of the same name,
* and ban import statements.
*/
const char *restrict_section;
};
/**
* Used for diffing a configuration object against
* the default one
*/
struct DiffHandle
{
const struct GNUNET_CONFIGURATION_Handle *cfg_default;
struct GNUNET_CONFIGURATION_Handle *cfgDiff;
};
void
GNUNET_CONFIGURATION_enable_diagnostics (struct
GNUNET_CONFIGURATION_Handle *cfg)
{
cfg->diagnostics = true;
}
struct GNUNET_CONFIGURATION_Handle *
GNUNET_CONFIGURATION_create ()
{
struct GNUNET_CONFIGURATION_Handle *cfg;
char *p;
cfg = GNUNET_new (struct GNUNET_CONFIGURATION_Handle);
/* make certain values from the project data available
as PATHS */
p = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
if (NULL != p)
{
GNUNET_CONFIGURATION_set_value_string (cfg,
"PATHS",
"DATADIR",
p);
GNUNET_free (p);
}
p = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_LIBDIR);
if (NULL != p)
{
GNUNET_CONFIGURATION_set_value_string (cfg,
"PATHS",
"LIBDIR",
p);
GNUNET_free (p);
}
p = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_BINDIR);
if (NULL != p)
{
GNUNET_CONFIGURATION_set_value_string (cfg,
"PATHS",
"BINDIR",
p);
GNUNET_free (p);
}
p = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_PREFIX);
if (NULL != p)
{
GNUNET_CONFIGURATION_set_value_string (cfg,
"PATHS",
"PREFIX",
p);
GNUNET_free (p);
}
p = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_LOCALEDIR);
if (NULL != p)
{
GNUNET_CONFIGURATION_set_value_string (cfg,
"PATHS",
"LOCALEDIR",
p);
GNUNET_free (p);
}
p = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_ICONDIR);
if (NULL != p)
{
GNUNET_CONFIGURATION_set_value_string (cfg,
"PATHS",
"ICONDIR",
p);
GNUNET_free (p);
}
p = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DOCDIR);
if (NULL != p)
{
GNUNET_CONFIGURATION_set_value_string (cfg,
"PATHS",
"DOCDIR",
p);
GNUNET_free (p);
}
p = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_LIBEXECDIR);
if (NULL != p)
{
GNUNET_CONFIGURATION_set_value_string (cfg,
"PATHS",
"LIBEXECDIR",
p);
GNUNET_free (p);
}
return cfg;
}
void
GNUNET_CONFIGURATION_destroy (struct GNUNET_CONFIGURATION_Handle *cfg)
{
struct ConfigSection *sec;
struct ConfigFile *cf;
while (NULL != (sec = cfg->sections))
GNUNET_CONFIGURATION_remove_section (cfg, sec->name);
while (NULL != (cf = cfg->loaded_files_head))
{
GNUNET_free (cf->hint_restrict_section);
GNUNET_free (cf->source_filename);
GNUNET_CONTAINER_DLL_remove (cfg->loaded_files_head,
cfg->loaded_files_tail,
cf);
GNUNET_free (cf);
}
GNUNET_free (cfg->main_filename);
GNUNET_free (cfg);
}
enum GNUNET_GenericReturnValue
GNUNET_CONFIGURATION_parse_and_run (const char *filename,
GNUNET_CONFIGURATION_Callback cb,
void *cb_cls)
{
struct GNUNET_CONFIGURATION_Handle *cfg;
enum GNUNET_GenericReturnValue ret;
cfg = GNUNET_CONFIGURATION_create ();
if (GNUNET_OK !=
GNUNET_CONFIGURATION_load (cfg,
filename))
{
GNUNET_break (0);
GNUNET_CONFIGURATION_destroy (cfg);
return GNUNET_SYSERR;
}
ret = cb (cb_cls, cfg);
GNUNET_CONFIGURATION_destroy (cfg);
return ret;
}
/**
* Closure to collect_files_cb.
*/
struct CollectFilesContext
{
/**
* Collected files from globbing.
*/
char **files;
/**
* Size of the files array.
*/
unsigned int files_length;
};
/**
* Function called with a filename.
*
* @param cls closure
* @param filename complete filename (absolute path)
* @return #GNUNET_OK to continue to iterate,
* #GNUNET_NO to stop iteration with no error,
* #GNUNET_SYSERR to abort iteration with error!
*/
static int
collect_files_cb (void *cls,
const char *filename)
{
struct CollectFilesContext *igc = cls;
GNUNET_array_append (igc->files,
igc->files_length,
GNUNET_strdup (filename));
return GNUNET_OK;
}
/**
* Find a section entry from a configuration.
*
* @param cfg configuration to search in
* @param section name of the section to look for
* @return matching entry, NULL if not found
*/
static struct ConfigSection *
find_section (const struct GNUNET_CONFIGURATION_Handle *cfg,
const char *section)
{
struct ConfigSection *pos;
pos = cfg->sections;
while ((pos != NULL) && (0 != strcasecmp (section, pos->name)))
pos = pos->next;
return pos;
}
static int
pstrcmp (const void *a, const void *b)
{
return strcmp (*((const char **) a), *((const char **) b));
}
/**
* Handle an inline directive.
*
* @returns #GNUNET_SYSERR on error, #GNUNET_OK otherwise
*/
enum GNUNET_GenericReturnValue
handle_inline (struct GNUNET_CONFIGURATION_Handle *cfg,
const char *path_or_glob,
bool path_is_glob,
const char *restrict_section,
const char *source_filename,
unsigned int source_lineno)
{
char *inline_path = NULL;
struct GNUNET_CONFIGURATION_Handle *other_cfg = NULL;
struct CollectFilesContext igc = {
.files = NULL,
.files_length = 0,
};
enum GNUNET_GenericReturnValue fun_ret;
unsigned int old_nest_level = cfg->current_nest_level++;
/* We support the section restriction only for non-globs */
GNUNET_assert (! (path_is_glob && (NULL != restrict_section)));
if (NULL == source_filename)
{
LOG (GNUNET_ERROR_TYPE_DEBUG,
"Refusing to parse inline configurations, "
"not allowed without source filename!\n");
fun_ret = GNUNET_SYSERR;
goto cleanup;
}
if ('/' == *path_or_glob)
inline_path = GNUNET_strdup (path_or_glob);
else
{
/* We compute the canonical, absolute path first,
so that relative imports resolve properly with symlinked
config files. */
char *source_realpath;
char *endsep;
source_realpath = realpath (source_filename,
NULL);
if (NULL == source_realpath)
{
/* Couldn't even resolve path of base dir. */
GNUNET_break (0);
/* failed to parse included config */
fun_ret = GNUNET_SYSERR;
goto cleanup;
}
endsep = strrchr (source_realpath, '/');
GNUNET_assert (NULL != endsep);
*endsep = '\0';
GNUNET_asprintf (&inline_path,
"%s/%s",
source_realpath,
path_or_glob);
free (source_realpath);
}
if (path_is_glob)
{
int nret;
LOG (GNUNET_ERROR_TYPE_DEBUG,
"processing config glob '%s'\n",
inline_path);
nret = GNUNET_DISK_glob (inline_path, collect_files_cb, &igc);
if (-1 == nret)
{
fun_ret = GNUNET_SYSERR;
goto cleanup;
}
GNUNET_assert (nret == igc.files_length);
qsort (igc.files, igc.files_length, sizeof (char *), pstrcmp);
for (int i = 0; i < nret; i++)
{
if (GNUNET_OK !=
GNUNET_CONFIGURATION_parse (cfg,
igc.files[i]))
{
fun_ret = GNUNET_SYSERR;
goto cleanup;
}
}
fun_ret = GNUNET_OK;
}
else if (NULL != restrict_section)
{
enum GNUNET_GenericReturnValue inner_ret;
struct ConfigSection *cs;
struct ConfigFile *cf = GNUNET_new (struct ConfigFile);
inner_ret = GNUNET_DISK_file_test_read (inline_path);
cs = find_section (cfg, restrict_section);
if (NULL == cs)
{
cs = GNUNET_new (struct ConfigSection);
cs->name = GNUNET_strdup (restrict_section);
cs->next = cfg->sections;
cfg->sections = cs;
cs->entries = NULL;
}
if (cfg->diagnostics)
{
char *sfn = GNUNET_STRINGS_filename_expand (inline_path);
struct stat istat;
cs->hint_secret_filename = sfn;
if (0 == stat (sfn, &istat))
{
struct passwd *pw = getpwuid (istat.st_uid);
struct group *gr = getgrgid (istat.st_gid);
char *pwname = (NULL == pw) ? "" : pw->pw_name;
char *grname = (NULL == gr) ? "" : gr->gr_name;
GNUNET_asprintf (&cs->hint_secret_stat,
"%s:%s %o",
pwname,
grname,
istat.st_mode);
}
else
{
cs->hint_secret_stat = GNUNET_strdup ("");
}
if (source_filename)
{
/* Possible that this secret section has been inlined before */
GNUNET_free (cs->hint_inlined_from_filename);
cs->hint_inlined_from_filename = GNUNET_strdup (source_filename);
cs->hint_inlined_from_line = source_lineno;
}
}
/* Put file in the load list for diagnostics, even if we can't access it. */
{
cf->level = cfg->current_nest_level;
cf->source_filename = GNUNET_strdup (inline_path);
cf->hint_restrict_section = GNUNET_strdup (restrict_section);
GNUNET_CONTAINER_DLL_insert_tail (cfg->loaded_files_head,
cfg->loaded_files_tail,
cf);
}
if (GNUNET_OK != inner_ret)
{
cs->inaccessible = true;
cf->hint_inaccessible = true;
/* File can't be accessed, but that's okay. */
fun_ret = GNUNET_OK;
goto cleanup;
}
other_cfg = GNUNET_CONFIGURATION_create ();
other_cfg->restrict_section = restrict_section;
inner_ret = GNUNET_CONFIGURATION_parse (other_cfg,
inline_path);
if (GNUNET_OK != inner_ret)
{
cf->hint_inaccessible = true;
fun_ret = inner_ret;
goto cleanup;
}
cs = find_section (other_cfg, restrict_section);
if (NULL == cs)
{
LOG (GNUNET_ERROR_TYPE_INFO,
"Configuration file '%s' loaded with @inline-secret@ "
"does not contain section '%s'.\n",
inline_path,
restrict_section);
/* Inlined onfiguration is accessible but doesn't contain any values.
We treat this as if the inlined section was empty, and do not
consider it an error. */
fun_ret = GNUNET_OK;
goto cleanup;
}
for (struct ConfigEntry *ce = cs->entries;
NULL != ce;
ce = ce->next)
GNUNET_CONFIGURATION_set_value_string (cfg,
restrict_section,
ce->key,
ce->val);
fun_ret = GNUNET_OK;
}
else if (GNUNET_OK !=
GNUNET_CONFIGURATION_parse (cfg,
inline_path))
{
fun_ret = GNUNET_SYSERR;
goto cleanup;
}
else
{
fun_ret = GNUNET_OK;
}
cleanup:
cfg->current_nest_level = old_nest_level;
if (NULL != other_cfg)
GNUNET_CONFIGURATION_destroy (other_cfg);
GNUNET_free (inline_path);
if (igc.files_length > 0)
{
for (size_t i = 0; i < igc.files_length; i++)
GNUNET_free (igc.files[i]);
GNUNET_array_grow (igc.files, igc.files_length, 0);
}
return fun_ret;
}
/**
* Find an entry from a configuration.
*
* @param cfg handle to the configuration
* @param section section the option is in
* @param key the option
* @return matching entry, NULL if not found
*/
static struct ConfigEntry *
find_entry (const struct GNUNET_CONFIGURATION_Handle *cfg,
const char *section,
const char *key)
{
struct ConfigSection *sec;
struct ConfigEntry *pos;
if (NULL == (sec = find_section (cfg, section)))
return NULL;
if (sec->inaccessible)
{
LOG (GNUNET_ERROR_TYPE_WARNING,
"Section '%s' is marked as inaccessible, because the configuration "
" file that contains the section can't be read. Attempts to use "
"option '%s' will fail.\n",
section,
key);
return NULL;
}
pos = sec->entries;
while ((pos != NULL) && (0 != strcasecmp (key, pos->key)))
pos = pos->next;
return pos;
}
/**
* Set a configuration hint.
*
* @param cfg configuration handle
* @param section section
* @param option config option
* @param hint_filename
* @param hint_line
*/
static void
set_entry_hint (struct GNUNET_CONFIGURATION_Handle *cfg,
const char *section,
const char *option,
const char *hint_filename,
unsigned int hint_line)
{
struct ConfigEntry *e = find_entry (cfg, section, option);
if (! cfg->diagnostics)
return;
if (! e)
return;
e->hint_filename = GNUNET_strdup (hint_filename);
e->hint_lineno = hint_line;
}
enum GNUNET_GenericReturnValue
GNUNET_CONFIGURATION_deserialize (struct GNUNET_CONFIGURATION_Handle *cfg,
const char *mem,
size_t size,
const char *source_filename)
{
size_t line_size;
unsigned int nr;
size_t r_bytes;
size_t to_read;
enum GNUNET_GenericReturnValue ret;
char *section;
char *eq;
char *tag;
char *value;
char *line_orig = NULL;
ret = GNUNET_OK;
section = NULL;
nr = 0;
r_bytes = 0;
while (r_bytes < size)
{
char *pos;
char *line;
bool emptyline;
GNUNET_free (line_orig);
/* fgets-like behaviour on buffer */
to_read = size - r_bytes;
pos = memchr (&mem[r_bytes], '\n', to_read);
if (NULL == pos)
{
line_orig = GNUNET_strndup (&mem[r_bytes],
line_size = to_read);
r_bytes += line_size;
}
else
{
line_orig = GNUNET_strndup (&mem[r_bytes],
line_size = (pos - &mem[r_bytes]));
r_bytes += line_size + 1;
}
line = line_orig;
/* increment line number */
nr++;
/* tabs and '\r' are whitespace */
emptyline = GNUNET_YES;
for (size_t i = 0; i < line_size; i++)
{
if (line[i] == '\t')
line[i] = ' ';
if (line[i] == '\r')
line[i] = ' ';
if (' ' != line[i])
emptyline = GNUNET_NO;
}
/* ignore empty lines */
if (GNUNET_YES == emptyline)
continue;
/* remove tailing whitespace */
for (size_t i = line_size - 1;
(i >= 1) && (isspace ((unsigned char) line[i]));
i--)
line[i] = '\0';
/* remove leading whitespace */
for (; line[0] != '\0' && (isspace ((unsigned char) line[0])); line++)
;
/* ignore comments */
if ( ('#' == line[0]) ||
('%' == line[0]) )
continue;
/* Handle special directives. */
if ('@' == line[0])
{
char *end = strchr (line + 1, '@');
char *directive;
enum GNUNET_GenericReturnValue directive_ret;
if (NULL != cfg->restrict_section)
{
LOG (GNUNET_ERROR_TYPE_WARNING,
_ (
"Illegal directive in line %u (parsing restricted section %s)\n"),
nr,
cfg->restrict_section);
ret = GNUNET_SYSERR;
break;
}
if (NULL == end)
{
LOG (GNUNET_ERROR_TYPE_WARNING,
_ ("Bad directive in line %u\n"),
nr);
ret = GNUNET_SYSERR;
break;
}
*end = '\0';
directive = line + 1;
if (0 == strcasecmp (directive, "INLINE"))
{
const char *path = end + 1;
/* Skip space before path */
for (; isspace (*path); path++)
;
directive_ret = handle_inline (cfg,
path,
false,
NULL,
source_filename,
nr);
}
else if (0 == strcasecmp (directive, "INLINE-MATCHING"))
{
const char *path = end + 1;
/* Skip space before path */
for (; isspace (*path); path++)
;
directive_ret = handle_inline (cfg,
path,
true,
NULL,
source_filename,
nr);
}
else if (0 == strcasecmp (directive, "INLINE-SECRET"))
{
char *secname = end + 1;
char *secname_end;
const char *path;
/* Skip space before secname */
for (; isspace (*secname); secname++)
;
secname_end = strchr (secname, ' ');
if (NULL == secname_end)
{
LOG (GNUNET_ERROR_TYPE_WARNING,
_ ("Bad inline-secret directive in line %u\n"),
nr);
ret = GNUNET_SYSERR;
break;
}
*secname_end = '\0';
path = secname_end + 1;
/* Skip space before path */
for (; isspace (*path); path++)
;
directive_ret = handle_inline (cfg,
path,
false,
secname,
source_filename,
nr);
}
else
{
LOG (GNUNET_ERROR_TYPE_WARNING,
_ ("Unknown or malformed directive '%s' in line %u\n"),
directive,
nr);
ret = GNUNET_SYSERR;
break;
}
if (GNUNET_OK != directive_ret)
{
ret = directive_ret;
break;
}
continue;
}
if (('[' == line[0]) && (']' == line[line_size - 1]))
{
/* [value] */
line[line_size - 1] = '\0';
value = &line[1];
GNUNET_free (section);
section = GNUNET_strdup (value);
continue;
}
if (NULL != (eq = strchr (line, '=')))
{
size_t i;
if (NULL == section)
{
LOG (GNUNET_ERROR_TYPE_WARNING,
_ (
"Syntax error while deserializing in line %u (option without section)\n"),
nr);
ret = GNUNET_SYSERR;
break;
}
/* tag = value */
tag = GNUNET_strndup (line, eq - line);
/* remove tailing whitespace */
for (i = strlen (tag) - 1;
(i >= 1) && (isspace ((unsigned char) tag[i]));
i--)
tag[i] = '\0';
/* Strip whitespace */
value = eq + 1;
while (isspace ((unsigned char) value[0]))
value++;
for (i = strlen (value) - 1;
(i >= 1) && (isspace ((unsigned char) value[i]));
i--)
value[i] = '\0';
/* remove quotes */
i = 0;
if (('"' == value[0]) && ('"' == value[strlen (value) - 1]))
{
value[strlen (value) - 1] = '\0';
value++;
}
GNUNET_CONFIGURATION_set_value_string (cfg, section, tag, &value[i]);
if (cfg->diagnostics)
{
set_entry_hint (cfg,
section,
tag,
source_filename ? source_filename : "",
nr);
}
GNUNET_free (tag);
continue;
}
/* parse error */
LOG (GNUNET_ERROR_TYPE_WARNING,
_ ("Syntax error while deserializing in line %u\n"),
nr);
ret = GNUNET_SYSERR;
break;
}
GNUNET_free (line_orig);
GNUNET_free (section);
GNUNET_assert ( (GNUNET_OK != ret) ||
(r_bytes == size) );
return ret;
}
enum GNUNET_GenericReturnValue
GNUNET_CONFIGURATION_parse (struct GNUNET_CONFIGURATION_Handle *cfg,
const char *filename)
{
uint64_t fs64;
size_t fs;
char *fn;
char *mem;
int dirty;
enum GNUNET_GenericReturnValue ret;
ssize_t sret;
fn = GNUNET_STRINGS_filename_expand (filename);
LOG (GNUNET_ERROR_TYPE_DEBUG, "Asked to parse config file `%s'\n", fn);
if (NULL == fn)
return GNUNET_SYSERR;
/* Check for cycles */
{
unsigned int lvl = cfg->current_nest_level;
struct ConfigFile *cf = cfg->loaded_files_tail;
struct ConfigFile *parent = NULL;
for (; NULL != cf; parent = cf, cf = cf->prev)
{
/* Check parents based on level, skipping children of siblings. */
if (cf->level >= lvl)
continue;
lvl = cf->level;
if ( (NULL == cf->source_filename) || (NULL == filename))
continue;
if (0 == strcmp (cf->source_filename, filename))
{
if (NULL == parent)
{
LOG (GNUNET_ERROR_TYPE_ERROR,
"Forbidden direct cyclic configuration import (%s -> %s)\n",
cf->source_filename,
filename);
}
else
LOG (GNUNET_ERROR_TYPE_ERROR,
"Forbidden indirect cyclic configuration import (%s -> ... -> %s -> %s)\n",
cf->source_filename,
parent->source_filename,
filename);
GNUNET_free (fn);
return GNUNET_SYSERR;
}
}
}
/* Keep track of loaded files.*/
{
struct ConfigFile *cf = GNUNET_new (struct ConfigFile);
cf->level = cfg->current_nest_level;
cf->source_filename = GNUNET_strdup (filename ? filename : "");
GNUNET_CONTAINER_DLL_insert_tail (cfg->loaded_files_head,
cfg->loaded_files_tail,
cf);
}
dirty = cfg->dirty; /* back up value! */
if (GNUNET_SYSERR ==
GNUNET_DISK_file_size (fn, &fs64, GNUNET_YES, GNUNET_YES))
{
LOG (GNUNET_ERROR_TYPE_WARNING,
"Error while determining the file size of `%s'\n",
fn);
GNUNET_free (fn);
return GNUNET_SYSERR;
}
if (fs64 > SIZE_MAX)
{
GNUNET_break (0); /* File size is more than the heap size */
GNUNET_free (fn);
return GNUNET_SYSERR;
}
fs = fs64;
mem = GNUNET_malloc (fs);
sret = GNUNET_DISK_fn_read (fn, mem, fs);
if ((sret < 0) || (fs != (size_t) sret))
{
LOG (GNUNET_ERROR_TYPE_WARNING, _ ("Error while reading file `%s'\n"), fn);
GNUNET_free (fn);
GNUNET_free (mem);
return GNUNET_SYSERR;
}
LOG (GNUNET_ERROR_TYPE_DEBUG, "Deserializing contents of file `%s'\n", fn);
ret = GNUNET_CONFIGURATION_deserialize (cfg,
mem,
fs,
fn);
if (GNUNET_SYSERR == ret)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
_ ("Failed to parse configuration file `%s'\n"),
fn);
}
GNUNET_free (fn);
GNUNET_free (mem);
/* restore dirty flag - anything we set in the meantime
* came from disk */
cfg->dirty = dirty;
return ret;
}
enum GNUNET_GenericReturnValue
GNUNET_CONFIGURATION_is_dirty (const struct GNUNET_CONFIGURATION_Handle *cfg)
{
return cfg->dirty;
}
/**
* Should we skip this configuration entry when serializing?
*
* @param sec section name
* @param key key
* @return true if we should skip it
*/
static bool
do_skip (const char *sec,
const char *key)
{
if (0 != strcasecmp ("PATHS",
sec))
return false;
return ( (0 == strcasecmp ("DATADIR",
key)) ||
(0 == strcasecmp ("LIBDIR",
key)) ||
(0 == strcasecmp ("BINDIR",
key)) ||
(0 == strcasecmp ("PREFIX",
key)) ||
(0 == strcasecmp ("LOCALEDIR",
key)) ||
(0 == strcasecmp ("ICONDIR",
key)) ||
(0 == strcasecmp ("DOCDIR",
key)) ||
(0 == strcasecmp ("DEFAULTCONFIG",
key)) ||
(0 == strcasecmp ("LIBEXECDIR",
key)) );
}
char *
GNUNET_CONFIGURATION_serialize (const struct GNUNET_CONFIGURATION_Handle *cfg,
size_t *size)
{
char *mem;
char *cbuf;
char *val;
char *pos;
size_t m_size;
size_t c_size;
/* Pass1 : calculate the buffer size required */
m_size = 0;
for (struct ConfigSection *sec = cfg->sections;
NULL != sec;
sec = sec->next)
{
if (sec->inaccessible)
continue;
/* For each section we need to add 3 characters: {'[',']','\n'} */
m_size += strlen (sec->name) + 3;
for (struct ConfigEntry *ent = sec->entries;
NULL != ent;
ent = ent->next)
{
if (do_skip (sec->name,
ent->key))
continue;
if (NULL != ent->val)
{
/* if val has any '\n' then they occupy +1 character as '\n'->'\\','n' */
pos = ent->val;
while (NULL != (pos = strstr (pos, "\n")))
{
m_size++;
pos++;
}
/* For each key = value pair we need to add 4 characters (2
spaces and 1 equal-to character and 1 new line) */
m_size += strlen (ent->key) + strlen (ent->val) + 4;
}
}
/* A new line after section end */
m_size++;
}
/* Pass2: Allocate memory and write the configuration to it */
mem = GNUNET_malloc (m_size);
c_size = 0;
*size = c_size;
for (struct ConfigSection *sec = cfg->sections;
NULL != sec;
sec = sec->next)
{
int len;
len = GNUNET_asprintf (&cbuf,
"[%s]\n",
sec->name);
GNUNET_assert (0 < len);
GNUNET_memcpy (mem + c_size,
cbuf,
len);
c_size += len;
GNUNET_free (cbuf);
for (struct ConfigEntry *ent = sec->entries;
NULL != ent;
ent = ent->next)
{
if (do_skip (sec->name,
ent->key))
continue;
if (NULL != ent->val)
{
val = GNUNET_malloc (strlen (ent->val) * 2 + 1);
strcpy (val, ent->val);
while (NULL != (pos = strstr (val, "\n")))
{
memmove (&pos[2], &pos[1], strlen (&pos[1]));
pos[0] = '\\';
pos[1] = 'n';
}
len = GNUNET_asprintf (&cbuf, "%s = %s\n", ent->key, val);
GNUNET_free (val);
GNUNET_memcpy (mem + c_size, cbuf, len);
c_size += len;
GNUNET_free (cbuf);
}
}
GNUNET_memcpy (mem + c_size, "\n", 1);
c_size++;
}
GNUNET_assert (c_size == m_size);
*size = c_size;
return mem;
}
char *
GNUNET_CONFIGURATION_serialize_diagnostics (const struct
GNUNET_CONFIGURATION_Handle *cfg)
{
struct GNUNET_Buffer buf = { 0 };
GNUNET_buffer_write_fstr (&buf,
"#\n# Configuration file diagnostics\n#\n");
GNUNET_buffer_write_fstr (&buf,
"# Entry point: %s\n",
cfg->main_filename ? cfg->main_filename :
"");
GNUNET_buffer_write_fstr (&buf,
"#\n# Files Loaded:\n");
for (struct ConfigFile *cfil = cfg->loaded_files_head;
NULL != cfil;
cfil = cfil->next)
{
GNUNET_buffer_write_fstr (&buf,
"# ");
for (unsigned int i = 0; i < cfil->level; i++)
GNUNET_buffer_write_fstr (&buf,
"+");
if (0 != cfil->level)
GNUNET_buffer_write_fstr (&buf,
" ");
GNUNET_buffer_write_fstr (&buf,
"%s",
cfil->source_filename);
if (NULL != cfil->hint_restrict_section)
GNUNET_buffer_write_fstr (&buf,
" (%s secret section %s)",
cfil->hint_inaccessible
? "inaccessible"
: "loaded",
cfil->hint_restrict_section);
GNUNET_buffer_write_str (&buf,
"\n");
}
GNUNET_buffer_write_fstr (&buf,
"#\n\n");
for (struct ConfigSection *sec = cfg->sections;
NULL != sec;
sec = sec->next)
{
if (sec->hint_secret_filename)
GNUNET_buffer_write_fstr (&buf,
"# secret section from %s\n# secret file stat %s\n",
sec->hint_secret_filename,
sec->hint_secret_stat);
if (sec->hint_inlined_from_filename)
{
GNUNET_buffer_write_fstr (&buf,
"# inlined from %s:%u\n",
sec->hint_inlined_from_filename,
sec->hint_inlined_from_line);
}
GNUNET_buffer_write_fstr (&buf,
"[%s]\n\n",
sec->name);
if (sec->inaccessible)
{
GNUNET_buffer_write_fstr (&buf,
"# \n\n\n");
continue;
}
for (struct ConfigEntry *ent = sec->entries;
NULL != ent;
ent = ent->next)
{
if (do_skip (sec->name,
ent->key))
continue;
if (NULL != ent->val)
{
char *pos;
char *val = GNUNET_malloc (strlen (ent->val) * 2 + 1);
strcpy (val, ent->val);
while (NULL != (pos = strstr (val, "\n")))
{
memmove (&pos[2], &pos[1], strlen (&pos[1]));
pos[0] = '\\';
pos[1] = 'n';
}
if (NULL != ent->hint_filename)
{
GNUNET_buffer_write_fstr (&buf,
"# %s:%u\n",
ent->hint_filename,
ent->hint_lineno);
}
GNUNET_buffer_write_fstr (&buf,
"%s = %s\n",
ent->key,
val);
GNUNET_free (val);
}
GNUNET_buffer_write_str (&buf, "\n");
}
GNUNET_buffer_write_str (&buf, "\n");
}
return GNUNET_buffer_reap_str (&buf);
}
enum GNUNET_GenericReturnValue
GNUNET_CONFIGURATION_write (struct GNUNET_CONFIGURATION_Handle *cfg,
const char *filename)
{
char *fn;
char *cfg_buf;
size_t size;
fn = GNUNET_STRINGS_filename_expand (filename);
if (fn == NULL)
return GNUNET_SYSERR;
if (GNUNET_OK != GNUNET_DISK_directory_create_for_file (fn))
{
GNUNET_free (fn);
return GNUNET_SYSERR;
}
cfg_buf = GNUNET_CONFIGURATION_serialize (cfg,
&size);
{
struct GNUNET_DISK_FileHandle *h;
h = GNUNET_DISK_file_open (fn,
GNUNET_DISK_OPEN_WRITE
| GNUNET_DISK_OPEN_TRUNCATE
| GNUNET_DISK_OPEN_CREATE,
GNUNET_DISK_PERM_USER_READ
| GNUNET_DISK_PERM_USER_WRITE
| GNUNET_DISK_PERM_GROUP_READ
| GNUNET_DISK_PERM_GROUP_WRITE);
if (NULL == h)
{
GNUNET_free (fn);
GNUNET_free (cfg_buf);
return GNUNET_SYSERR;
}
if (((ssize_t) size) !=
GNUNET_DISK_file_write (h,
cfg_buf,
size))
{
GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
"write",
fn);
GNUNET_DISK_file_close (h);
(void) GNUNET_DISK_directory_remove (fn);
GNUNET_free (fn);
GNUNET_free (cfg_buf);
cfg->dirty = GNUNET_SYSERR; /* last write failed */
return GNUNET_SYSERR;
}
GNUNET_assert (GNUNET_OK ==
GNUNET_DISK_file_close (h));
}
GNUNET_free (fn);
GNUNET_free (cfg_buf);
cfg->dirty = GNUNET_NO; /* last write succeeded */
return GNUNET_OK;
}
void
GNUNET_CONFIGURATION_iterate (const struct GNUNET_CONFIGURATION_Handle *cfg,
GNUNET_CONFIGURATION_Iterator iter,
void *iter_cls)
{
for (struct ConfigSection *spos = cfg->sections;
NULL != spos;
spos = spos->next)
for (struct ConfigEntry *epos = spos->entries;
NULL != epos;
epos = epos->next)
if (NULL != epos->val)
iter (iter_cls,
spos->name,
epos->key,
epos->val);
}
void
GNUNET_CONFIGURATION_iterate_section_values (
const struct GNUNET_CONFIGURATION_Handle *cfg,
const char *section,
GNUNET_CONFIGURATION_Iterator iter,
void *iter_cls)
{
struct ConfigSection *spos;
struct ConfigEntry *epos;
spos = cfg->sections;
while ((spos != NULL) && (0 != strcasecmp (spos->name, section)))
spos = spos->next;
if (NULL == spos)
return;
if (spos->inaccessible)
{
LOG (GNUNET_ERROR_TYPE_WARNING,
"Section '%s' is marked as inaccessible, because the configuration "
" file that contains the section can't be read.\n",
section);
return;
}
for (epos = spos->entries; NULL != epos; epos = epos->next)
if (NULL != epos->val)
iter (iter_cls, spos->name, epos->key, epos->val);
}
void
GNUNET_CONFIGURATION_iterate_sections (
const struct GNUNET_CONFIGURATION_Handle *cfg,
GNUNET_CONFIGURATION_Section_Iterator iter,
void *iter_cls)
{
struct ConfigSection *spos;
struct ConfigSection *next;
next = cfg->sections;
while (next != NULL)
{
spos = next;
next = spos->next;
iter (iter_cls, spos->name);
}
}
void
GNUNET_CONFIGURATION_remove_section (struct GNUNET_CONFIGURATION_Handle *cfg,
const char *section)
{
struct ConfigSection *spos;
struct ConfigSection *prev;
struct ConfigEntry *ent;
prev = NULL;
spos = cfg->sections;
while (NULL != spos)
{
if (0 == strcasecmp (section, spos->name))
{
if (NULL == prev)
cfg->sections = spos->next;
else
prev->next = spos->next;
while (NULL != (ent = spos->entries))
{
spos->entries = ent->next;
GNUNET_free (ent->key);
GNUNET_free (ent->val);
GNUNET_free (ent->hint_filename);
GNUNET_free (ent);
cfg->dirty = GNUNET_YES;
}
GNUNET_free (spos->name);
GNUNET_free (spos->hint_secret_filename);
GNUNET_free (spos->hint_secret_stat);
GNUNET_free (spos->hint_inlined_from_filename);
GNUNET_free (spos);
return;
}
prev = spos;
spos = spos->next;
}
}
/**
* Copy a configuration value to the given target configuration.
* Overwrites existing entries.
*
* @param cls the destination configuration (`struct GNUNET_CONFIGURATION_Handle *`)
* @param section section for the value
* @param option option name of the value
* @param value value to copy
*/
static void
copy_entry (void *cls,
const char *section,
const char *option,
const char *value)
{
struct GNUNET_CONFIGURATION_Handle *dst = cls;
GNUNET_CONFIGURATION_set_value_string (dst, section, option, value);
}
struct GNUNET_CONFIGURATION_Handle *
GNUNET_CONFIGURATION_dup (const struct GNUNET_CONFIGURATION_Handle *cfg)
{
struct GNUNET_CONFIGURATION_Handle *ret;
ret = GNUNET_CONFIGURATION_create ();
GNUNET_CONFIGURATION_iterate (cfg, ©_entry, ret);
return ret;
}
/**
* A callback function, compares entries from two configurations
* (default against a new configuration) and write the diffs in a
* diff-configuration object (the callback object).
*
* @param cls the diff configuration (`struct DiffHandle *`)
* @param section section for the value (of the default conf.)
* @param option option name of the value (of the default conf.)
* @param value value to copy (of the default conf.)
*/
static void
compare_entries (void *cls,
const char *section,
const char *option,
const char *value)
{
struct DiffHandle *dh = cls;
struct ConfigEntry *entNew;
entNew = find_entry (dh->cfg_default, section, option);
if ((NULL != entNew) && (NULL != entNew->val) &&
(0 == strcmp (entNew->val, value)))
return;
GNUNET_CONFIGURATION_set_value_string (dh->cfgDiff, section, option, value);
}
struct GNUNET_CONFIGURATION_Handle *
GNUNET_CONFIGURATION_get_diff (
const struct GNUNET_CONFIGURATION_Handle *cfg_default,
const struct GNUNET_CONFIGURATION_Handle *cfg_new)
{
struct DiffHandle diffHandle;
diffHandle.cfgDiff = GNUNET_CONFIGURATION_create ();
diffHandle.cfg_default = cfg_default;
GNUNET_CONFIGURATION_iterate (cfg_new, &compare_entries, &diffHandle);
return diffHandle.cfgDiff;
}
enum GNUNET_GenericReturnValue
GNUNET_CONFIGURATION_write_diffs (
const struct GNUNET_CONFIGURATION_Handle *cfg_default,
const struct GNUNET_CONFIGURATION_Handle *cfg_new,
const char *filename)
{
int ret;
struct GNUNET_CONFIGURATION_Handle *diff;
diff = GNUNET_CONFIGURATION_get_diff (cfg_default, cfg_new);
ret = GNUNET_CONFIGURATION_write (diff, filename);
GNUNET_CONFIGURATION_destroy (diff);
return ret;
}
void
GNUNET_CONFIGURATION_set_value_string (struct GNUNET_CONFIGURATION_Handle *cfg,
const char *section,
const char *option,
const char *value)
{
struct ConfigSection *sec;
struct ConfigEntry *e;
char *nv;
e = find_entry (cfg, section, option);
if (NULL != e)
{
if (NULL == value)
{
GNUNET_free (e->val);
e->val = NULL;
}
else
{
nv = GNUNET_strdup (value);
GNUNET_free (e->val);
e->val = nv;
}
return;
}
sec = find_section (cfg, section);
if (sec == NULL)
{
sec = GNUNET_new (struct ConfigSection);
sec->name = GNUNET_strdup (section);
sec->next = cfg->sections;
cfg->sections = sec;
}
e = GNUNET_new (struct ConfigEntry);
e->key = GNUNET_strdup (option);
e->val = GNUNET_strdup (value);
e->next = sec->entries;
sec->entries = e;
}
void
GNUNET_CONFIGURATION_set_value_number (struct GNUNET_CONFIGURATION_Handle *cfg,
const char *section,
const char *option,
unsigned long long number)
{
char s[64];
GNUNET_snprintf (s, 64, "%llu", number);
GNUNET_CONFIGURATION_set_value_string (cfg, section, option, s);
}
enum GNUNET_GenericReturnValue
GNUNET_CONFIGURATION_get_value_number (
const struct GNUNET_CONFIGURATION_Handle *cfg,
const char *section,
const char *option,
unsigned long long *number)
{
struct ConfigEntry *e;
char dummy[2];
if (NULL == (e = find_entry (cfg, section, option)))
return GNUNET_SYSERR;
if (NULL == e->val)
return GNUNET_SYSERR;
if (1 != sscanf (e->val, "%llu%1s", number, dummy))
return GNUNET_SYSERR;
return GNUNET_OK;
}
enum GNUNET_GenericReturnValue
GNUNET_CONFIGURATION_get_value_float (
const struct GNUNET_CONFIGURATION_Handle *cfg,
const char *section,
const char *option,
float *number)
{
struct ConfigEntry *e;
char dummy[2];
if (NULL == (e = find_entry (cfg, section, option)))
return GNUNET_SYSERR;
if (NULL == e->val)
return GNUNET_SYSERR;
if (1 != sscanf (e->val, "%f%1s", number, dummy))
return GNUNET_SYSERR;
return GNUNET_OK;
}
enum GNUNET_GenericReturnValue
GNUNET_CONFIGURATION_get_value_time (
const struct GNUNET_CONFIGURATION_Handle *cfg,
const char *section,
const char *option,
struct GNUNET_TIME_Relative *time)
{
struct ConfigEntry *e;
int ret;
if (NULL == (e = find_entry (cfg, section, option)))
return GNUNET_SYSERR;
if (NULL == e->val)
return GNUNET_SYSERR;
ret = GNUNET_STRINGS_fancy_time_to_relative (e->val, time);
if (GNUNET_OK != ret)
GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
section,
option,
_ ("Not a valid relative time specification"));
return ret;
}
enum GNUNET_GenericReturnValue
GNUNET_CONFIGURATION_get_value_size (
const struct GNUNET_CONFIGURATION_Handle *cfg,
const char *section,
const char *option,
unsigned long long *size)
{
struct ConfigEntry *e;
if (NULL == (e = find_entry (cfg, section, option)))
return GNUNET_SYSERR;
if (NULL == e->val)
return GNUNET_SYSERR;
return GNUNET_STRINGS_fancy_size_to_bytes (e->val, size);
}
/**
* Get a configuration value that should be a string.
*
* @param cfg configuration to inspect
* @param section section of interest
* @param option option of interest
* @param value will be set to a freshly allocated configuration
* value, or NULL if option is not specified
* @return #GNUNET_OK on success, #GNUNET_SYSERR on error
*/
enum GNUNET_GenericReturnValue
GNUNET_CONFIGURATION_get_value_string (
const struct GNUNET_CONFIGURATION_Handle *cfg,
const char *section,
const char *option,
char **value)
{
struct ConfigEntry *e;
if ((NULL == (e = find_entry (cfg, section, option))) || (NULL == e->val))
{
*value = NULL;
return GNUNET_SYSERR;
}
*value = GNUNET_strdup (e->val);
return GNUNET_OK;
}
enum GNUNET_GenericReturnValue
GNUNET_CONFIGURATION_get_value_choice (
const struct GNUNET_CONFIGURATION_Handle *cfg,
const char *section,
const char *option,
const char *const *choices,
const char **value)
{
struct ConfigEntry *e;
unsigned int i;
if (NULL == (e = find_entry (cfg, section, option)))
return GNUNET_SYSERR;
for (i = 0; NULL != choices[i]; i++)
if (0 == strcasecmp (choices[i], e->val))
break;
if (NULL == choices[i])
{
LOG (GNUNET_ERROR_TYPE_ERROR,
_ ("Configuration value '%s' for '%s'"
" in section '%s' is not in set of legal choices\n"),
e->val,
option,
section);
return GNUNET_SYSERR;
}
*value = choices[i];
return GNUNET_OK;
}
enum GNUNET_GenericReturnValue
GNUNET_CONFIGURATION_get_data (const struct GNUNET_CONFIGURATION_Handle *cfg,
const char *section,
const char *option,
void *buf,
size_t buf_size)
{
char *enc;
int res;
size_t data_size;
if (GNUNET_OK !=
(res =
GNUNET_CONFIGURATION_get_value_string (cfg, section, option, &enc)))
return res;
data_size = (strlen (enc) * 5) / 8;
if (data_size != buf_size)
{
GNUNET_free (enc);
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
GNUNET_STRINGS_string_to_data (enc, strlen (enc), buf, buf_size))
{
GNUNET_free (enc);
return GNUNET_SYSERR;
}
GNUNET_free (enc);
return GNUNET_OK;
}
enum GNUNET_GenericReturnValue
GNUNET_CONFIGURATION_have_value (const struct GNUNET_CONFIGURATION_Handle *cfg,
const char *section,
const char *option)
{
struct ConfigEntry *e;
if ((NULL == (e = find_entry (cfg, section, option))) || (NULL == e->val))
return GNUNET_NO;
return GNUNET_YES;
}
/**
* Expand an expression of the form "$FOO/BAR" to "DIRECTORY/BAR"
* where either in the "PATHS" section or the environment "FOO" is
* set to "DIRECTORY". We also support default expansion,
* i.e. ${VARIABLE:-default} will expand to $VARIABLE if VARIABLE is
* set in PATHS or the environment, and otherwise to "default". Note
* that "default" itself can also be a $-expression, thus
* "${VAR1:-{$VAR2}}" will expand to VAR1 and if that is not defined
* to VAR2.
*
* @param cfg configuration to use for path expansion
* @param orig string to $-expand (will be freed!)
* @param depth recursion depth, used to detect recursive expansions
* @return $-expanded string, never NULL unless @a orig was NULL
*/
static char *
expand_dollar (const struct GNUNET_CONFIGURATION_Handle *cfg,
char *orig,
unsigned int depth)
{
char *prefix;
char *result;
char *start;
const char *post;
const char *env;
char *def;
char *end;
unsigned int lopen;
char erased_char;
char *erased_pos;
size_t len;
if (NULL == orig)
return NULL;
if (depth > 128)
{
LOG (GNUNET_ERROR_TYPE_WARNING,
_ (
"Recursive expansion suspected, aborting $-expansion for term `%s'\n"),
orig);
return orig;
}
LOG (GNUNET_ERROR_TYPE_DEBUG, "Asked to $-expand %s\n", orig);
if ('$' != orig[0])
{
LOG (GNUNET_ERROR_TYPE_DEBUG, "Doesn't start with $ - not expanding\n");
return orig;
}
erased_char = 0;
erased_pos = NULL;
if ('{' == orig[1])
{
start = &orig[2];
lopen = 1;
end = &orig[1];
while (lopen > 0)
{
end++;
switch (*end)
{
case '}':
lopen--;
break;
case '{':
lopen++;
break;
case '\0':
LOG (GNUNET_ERROR_TYPE_WARNING,
_ ("Missing closing `%s' in option `%s'\n"),
"}",
orig);
return orig;
default:
break;
}
}
erased_char = *end;
erased_pos = end;
*end = '\0';
post = end + 1;
def = strchr (orig, ':');
if (NULL != def)
{
*def = '\0';
def++;
if (('-' == *def) || ('=' == *def))
def++;
def = GNUNET_strdup (def);
}
}
else
{
int i;
start = &orig[1];
def = NULL;
i = 0;
while ((orig[i] != '/') && (orig[i] != '\\') && (orig[i] != '\0') &&
(orig[i] != ' '))
i++;
if (orig[i] == '\0')
{
post = "";
}
else
{
erased_char = orig[i];
erased_pos = &orig[i];
orig[i] = '\0';
post = &orig[i + 1];
}
}
LOG (GNUNET_ERROR_TYPE_DEBUG,
"Split into `%s' and `%s' with default %s\n",
start,
post,
def);
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (cfg, "PATHS", start, &prefix))
{
if (NULL == (env = getenv (start)))
{
/* try default */
def = expand_dollar (cfg, def, depth + 1);
env = def;
}
if (NULL == env)
{
start = GNUNET_strdup (start);
if (erased_pos)
*erased_pos = erased_char;
LOG (GNUNET_ERROR_TYPE_WARNING,
_ (
"Failed to expand `%s' in `%s' as it is neither found in [PATHS] nor defined as an environmental variable\n"),
start,
orig);
GNUNET_free (start);
return orig;
}
prefix = GNUNET_strdup (env);
}
prefix = GNUNET_CONFIGURATION_expand_dollar (cfg, prefix);
if ((erased_pos) && ('}' != erased_char))
{
len = strlen (prefix) + 1;
prefix = GNUNET_realloc (prefix, len + 1);
prefix[len - 1] = erased_char;
prefix[len] = '\0';
}
result = GNUNET_malloc (strlen (prefix) + strlen (post) + 1);
strcpy (result, prefix);
strcat (result, post);
GNUNET_free (def);
GNUNET_free (prefix);
GNUNET_free (orig);
return result;
}
char *
GNUNET_CONFIGURATION_expand_dollar (
const struct GNUNET_CONFIGURATION_Handle *cfg,
char *orig)
{
char *dup;
size_t i;
size_t len;
for (i = 0; '\0' != orig[i]; i++)
{
if ('$' != orig[i])
continue;
dup = GNUNET_strdup (orig + i);
dup = expand_dollar (cfg, dup, 0);
GNUNET_assert (NULL != dup); /* make compiler happy */
len = strlen (dup) + 1;
orig = GNUNET_realloc (orig, i + len);
GNUNET_memcpy (orig + i, dup, len);
GNUNET_free (dup);
}
return orig;
}
enum GNUNET_GenericReturnValue
GNUNET_CONFIGURATION_get_value_filename (
const struct GNUNET_CONFIGURATION_Handle *cfg,
const char *section,
const char *option,
char **value)
{
char *tmp;
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (cfg, section, option, &tmp))
{
LOG (GNUNET_ERROR_TYPE_DEBUG, "Failed to retrieve filename\n");
*value = NULL;
return GNUNET_SYSERR;
}
tmp = GNUNET_CONFIGURATION_expand_dollar (cfg, tmp);
*value = GNUNET_STRINGS_filename_expand (tmp);
GNUNET_free (tmp);
if (*value == NULL)
return GNUNET_SYSERR;
return GNUNET_OK;
}
enum GNUNET_GenericReturnValue
GNUNET_CONFIGURATION_get_value_yesno (
const struct GNUNET_CONFIGURATION_Handle *cfg,
const char *section,
const char *option)
{
static const char *yesno[] = { "YES", "NO", NULL };
const char *val;
int ret;
ret =
GNUNET_CONFIGURATION_get_value_choice (cfg, section, option, yesno, &val);
if (ret == GNUNET_SYSERR)
return ret;
if (val == yesno[0])
return GNUNET_YES;
return GNUNET_NO;
}
int
GNUNET_CONFIGURATION_iterate_value_filenames (
const struct GNUNET_CONFIGURATION_Handle *cfg,
const char *section,
const char *option,
GNUNET_FileNameCallback cb,
void *cb_cls)
{
char *list;
char *pos;
char *end;
char old;
int ret;
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (cfg, section, option, &list))
return 0;
GNUNET_assert (list != NULL);
ret = 0;
pos = list;
while (1)
{
while (pos[0] == ' ')
pos++;
if (strlen (pos) == 0)
break;
end = pos + 1;
while ((end[0] != ' ') && (end[0] != '\0'))
{
if (end[0] == '\\')
{
switch (end[1])
{
case '\\':
case ' ':
memmove (end, &end[1], strlen (&end[1]) + 1);
case '\0':
/* illegal, but just keep it */
break;
default:
/* illegal, but just ignore that there was a '/' */
break;
}
}
end++;
}
old = end[0];
end[0] = '\0';
if (strlen (pos) > 0)
{
ret++;
if ((cb != NULL) && (GNUNET_OK != cb (cb_cls, pos)))
{
ret = GNUNET_SYSERR;
break;
}
}
if (old == '\0')
break;
pos = end + 1;
}
GNUNET_free (list);
return ret;
}
/**
* FIXME.
*
* @param value FIXME
* @return FIXME
*/
static char *
escape_name (const char *value)
{
char *escaped;
const char *rpos;
char *wpos;
escaped = GNUNET_malloc (strlen (value) * 2 + 1);
memset (escaped, 0, strlen (value) * 2 + 1);
rpos = value;
wpos = escaped;
while (rpos[0] != '\0')
{
switch (rpos[0])
{
case '\\':
case ' ':
wpos[0] = '\\';
wpos[1] = rpos[0];
wpos += 2;
break;
default:
wpos[0] = rpos[0];
wpos++;
}
rpos++;
}
return escaped;
}
/**
* FIXME.
*
* @param cls string we compare with (const char*)
* @param fn filename we are currently looking at
* @return #GNUNET_OK if the names do not match, #GNUNET_SYSERR if they do
*/
static enum GNUNET_GenericReturnValue
test_match (void *cls, const char *fn)
{
const char *of = cls;
return (0 == strcmp (of, fn)) ? GNUNET_SYSERR : GNUNET_OK;
}
enum GNUNET_GenericReturnValue
GNUNET_CONFIGURATION_append_value_filename (
struct GNUNET_CONFIGURATION_Handle *cfg,
const char *section,
const char *option,
const char *value)
{
char *escaped;
char *old;
char *nw;
if (GNUNET_SYSERR ==
GNUNET_CONFIGURATION_iterate_value_filenames (cfg,
section,
option,
&test_match,
(void *) value))
return GNUNET_NO; /* already exists */
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (cfg, section, option, &old))
old = GNUNET_strdup ("");
escaped = escape_name (value);
nw = GNUNET_malloc (strlen (old) + strlen (escaped) + 2);
strcpy (nw, old);
if (strlen (old) > 0)
strcat (nw, " ");
strcat (nw, escaped);
GNUNET_CONFIGURATION_set_value_string (cfg, section, option, nw);
GNUNET_free (old);
GNUNET_free (nw);
GNUNET_free (escaped);
return GNUNET_OK;
}
enum GNUNET_GenericReturnValue
GNUNET_CONFIGURATION_remove_value_filename (
struct GNUNET_CONFIGURATION_Handle *cfg,
const char *section,
const char *option,
const char *value)
{
char *list;
char *pos;
char *end;
char *match;
char old;
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (cfg, section, option, &list))
return GNUNET_NO;
match = escape_name (value);
pos = list;
while (1)
{
while (pos[0] == ' ')
pos++;
if (strlen (pos) == 0)
break;
end = pos + 1;
while ((end[0] != ' ') && (end[0] != '\0'))
{
if (end[0] == '\\')
{
switch (end[1])
{
case '\\':
case ' ':
end++;
break;
case '\0':
/* illegal, but just keep it */
break;
default:
/* illegal, but just ignore that there was a '/' */
break;
}
}
end++;
}
old = end[0];
end[0] = '\0';
if (0 == strcmp (pos, match))
{
if (old != '\0')
memmove (pos, &end[1], strlen (&end[1]) + 1);
else
{
if (pos != list)
pos[-1] = '\0';
else
pos[0] = '\0';
}
GNUNET_CONFIGURATION_set_value_string (cfg, section, option, list);
GNUNET_free (list);
GNUNET_free (match);
return GNUNET_OK;
}
if (old == '\0')
break;
end[0] = old;
pos = end + 1;
}
GNUNET_free (list);
GNUNET_free (match);
return GNUNET_NO;
}
enum GNUNET_GenericReturnValue
GNUNET_CONFIGURATION_load_from (struct GNUNET_CONFIGURATION_Handle *cfg,
const char *defaults_d)
{
struct CollectFilesContext files_context = {
.files = NULL,
.files_length = 0,
};
enum GNUNET_GenericReturnValue fun_ret;
if (GNUNET_SYSERR ==
GNUNET_DISK_directory_scan (defaults_d, &collect_files_cb,
&files_context))
return GNUNET_SYSERR; /* no configuration at all found */
qsort (files_context.files,
files_context.files_length,
sizeof (char *),
pstrcmp);
for (unsigned int i = 0; i < files_context.files_length; i++)
{
char *ext;
const char *filename = files_context.files[i];
/* Examine file extension */
ext = strrchr (filename, '.');
if ((NULL == ext) || (0 != strcmp (ext, ".conf")))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Skipping file `%s'\n", filename);
fun_ret = GNUNET_OK;
goto cleanup;
}
fun_ret = GNUNET_CONFIGURATION_parse (cfg, filename);
if (fun_ret != GNUNET_OK)
break;
}
cleanup:
if (files_context.files_length > 0)
{
for (size_t i = 0; i < files_context.files_length; i++)
GNUNET_free (files_context.files[i]);
GNUNET_array_grow (files_context.files,
files_context.files_length,
0);
}
return fun_ret;
}
char *
GNUNET_CONFIGURATION_default_filename (void)
{
char *cfg_fn;
const struct GNUNET_OS_ProjectData *pd = GNUNET_OS_project_data_get ();
const char *xdg = getenv ("XDG_CONFIG_HOME");
if (NULL != xdg)
GNUNET_asprintf (&cfg_fn,
"%s%s%s",
xdg,
DIR_SEPARATOR_STR,
pd->config_file);
else
cfg_fn = GNUNET_strdup (pd->user_config_file);
if (GNUNET_OK == GNUNET_DISK_file_test_read (cfg_fn))
return cfg_fn;
GNUNET_free (cfg_fn);
/* Fall back to /etc/ for the default configuration.
Should be okay to use forward slashes here. */
GNUNET_asprintf (&cfg_fn,
"/etc/%s",
pd->config_file);
if (GNUNET_OK == GNUNET_DISK_file_test_read (cfg_fn))
return cfg_fn;
GNUNET_free (cfg_fn);
GNUNET_asprintf (&cfg_fn,
"/etc/%s/%s",
pd->project_dirname,
pd->config_file);
if (GNUNET_OK == GNUNET_DISK_file_test_read (cfg_fn))
return cfg_fn;
GNUNET_free (cfg_fn);
return NULL;
}
struct GNUNET_CONFIGURATION_Handle *
GNUNET_CONFIGURATION_default (void)
{
const struct GNUNET_OS_ProjectData *pd = GNUNET_OS_project_data_get ();
const struct GNUNET_OS_ProjectData *dpd = GNUNET_OS_project_data_default ();
const char *xdg = getenv ("XDG_CONFIG_HOME");
char *cfgname = NULL;
struct GNUNET_CONFIGURATION_Handle *cfg;
/* Makes sure function implicitly looking at the installation directory (for
example GNUNET_CONFIGURATION_load further down) use GNUnet's environment
instead of the caller's. It's done at the start to make sure as many
functions as possible are directed to the proper paths. */
GNUNET_OS_init (dpd);
cfg = GNUNET_CONFIGURATION_create ();
/* First, try user configuration. */
if (NULL != xdg)
GNUNET_asprintf (&cfgname, "%s/%s", xdg, dpd->config_file);
else
cfgname = GNUNET_strdup (dpd->user_config_file);
/* If user config doesn't exist, try in
/etc// and /etc/ */
if (GNUNET_OK != GNUNET_DISK_file_test (cfgname))
{
GNUNET_free (cfgname);
GNUNET_asprintf (&cfgname, "/etc/%s", dpd->config_file);
}
if (GNUNET_OK != GNUNET_DISK_file_test (cfgname))
{
GNUNET_free (cfgname);
GNUNET_asprintf (&cfgname,
"/etc/%s/%s",
dpd->project_dirname,
dpd->config_file);
}
if (GNUNET_OK != GNUNET_DISK_file_test (cfgname))
{
LOG (GNUNET_ERROR_TYPE_ERROR,
"Unable to top-level configuration file.\n");
GNUNET_OS_init (pd);
GNUNET_CONFIGURATION_destroy (cfg);
GNUNET_free (cfgname);
return NULL;
}
/* We found a configuration file that looks good, try to load it. */
LOG (GNUNET_ERROR_TYPE_DEBUG,
"Loading top-level configuration from '%s'\n",
cfgname);
if (GNUNET_OK !=
GNUNET_CONFIGURATION_load (cfg, cfgname))
{
GNUNET_OS_init (pd);
GNUNET_CONFIGURATION_destroy (cfg);
GNUNET_free (cfgname);
return NULL;
}
GNUNET_free (cfgname);
GNUNET_OS_init (pd);
return cfg;
}
/**
* Load configuration (starts with defaults, then loads
* system-specific configuration).
*
* @param cfg configuration to update
* @param filename name of the configuration file, NULL to load defaults
* @return #GNUNET_OK on success, #GNUNET_SYSERR on error
*/
int
GNUNET_CONFIGURATION_load (struct GNUNET_CONFIGURATION_Handle *cfg,
const char *filename)
{
char *baseconfig;
const char *base_config_varname;
if (cfg->load_called)
{
/* FIXME: Make this a GNUNET_assert later */
GNUNET_break (0);
GNUNET_free (cfg->main_filename);
}
cfg->load_called = true;
if (NULL != filename)
{
GNUNET_free (cfg->main_filename);
cfg->main_filename = GNUNET_strdup (filename);
}
base_config_varname = GNUNET_OS_project_data_get ()->base_config_varname;
if ((NULL != base_config_varname)
&& (NULL != (baseconfig = getenv (base_config_varname))))
{
baseconfig = GNUNET_strdup (baseconfig);
}
else
{
char *ipath;
ipath = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
if (NULL == ipath)
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
GNUNET_asprintf (&baseconfig, "%s%s", ipath, "config.d");
GNUNET_free (ipath);
}
char *dname = GNUNET_STRINGS_filename_expand (baseconfig);
GNUNET_free (baseconfig);
if ((GNUNET_YES == GNUNET_DISK_directory_test (dname, GNUNET_YES)) &&
(GNUNET_SYSERR == GNUNET_CONFIGURATION_load_from (cfg, dname)))
{
LOG (GNUNET_ERROR_TYPE_WARNING,
"Failed to load base configuration from '%s'\n",
filename);
GNUNET_free (dname);
return GNUNET_SYSERR; /* no configuration at all found */
}
GNUNET_free (dname);
if ((NULL != filename) &&
(GNUNET_OK != GNUNET_CONFIGURATION_parse (cfg, filename)))
{
/* specified configuration not found */
LOG (GNUNET_ERROR_TYPE_WARNING,
"Failed to load configuration from file '%s'\n",
filename);
return GNUNET_SYSERR;
}
if (((GNUNET_YES !=
GNUNET_CONFIGURATION_have_value (cfg, "PATHS", "DEFAULTCONFIG"))) &&
(filename != NULL))
GNUNET_CONFIGURATION_set_value_string (cfg, "PATHS", "DEFAULTCONFIG",
filename);
return GNUNET_OK;
}
/* end of configuration.c */