/*
     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 <http://www.gnu.org/licenses/>.

     SPDX-License-Identifier: AGPL3.0-or-later
 */
/**
 * @file src/util/configuration.c
 * @brief configuration management
 * @author Christian Grothoff
 */

#include "platform.h"
#include "gnunet_util_lib.h"
#include "gnunet_configuration_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;

  /**
   * Project data for this configuration object.
   */
  const struct GNUNET_OS_ProjectData *pd;

  /**
   * 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 (const struct GNUNET_OS_ProjectData *pd)
{
  struct GNUNET_CONFIGURATION_Handle *cfg;
  char *p;

  cfg = GNUNET_new (struct GNUNET_CONFIGURATION_Handle);
  cfg->pd = pd;
  /* make certain values from the project data available
     as PATHS */
  p = GNUNET_OS_installation_get_path (pd,
                                       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 (pd,
                                       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 (pd,
                                       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 (pd,
                                       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 (pd,
                                       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 (pd,
                                       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 (pd,
                                       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 (pd,
                                       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 struct GNUNET_OS_ProjectData *pd,
                                    const char *filename,
                                    GNUNET_CONFIGURATION_Callback cb,
                                    void *cb_cls)
{
  struct GNUNET_CONFIGURATION_Handle *cfg;
  enum GNUNET_GenericReturnValue ret;

  cfg = GNUNET_CONFIGURATION_create (pd);
  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 enum GNUNET_GenericReturnValue
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;

  if (NULL == cfg)
    return NULL;
  pos = cfg->sections;
  while ( (NULL != pos) &&
          (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
 */
static 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)
    {
      const char *pwname;
      const char *grname;
      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);
        pwname = (NULL == pw) ? "<unknown>" : pw->pw_name;
        grname = (NULL == gr) ? "<unknown>" : gr->gr_name;

        GNUNET_asprintf (&cs->hint_secret_stat,
                         "%s:%s %o",
                         pwname,
                         grname,
                         istat.st_mode);
      }
      else
      {
        cs->hint_secret_stat = GNUNET_strdup ("<can't stat file>");
      }
      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 (cfg->pd);
    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 configuration 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, '=')))
    {

      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 (int 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 (int i = strlen (value) - 1;
           (i >= 1) && (isspace ((unsigned char) value[i]));
           i--)
        value[i] = '\0';

      /* remove quotes */
      if ( ('"' == value[0]) &&
           ('"' == value[strlen (value) - 1]) )
      {
        value[strlen (value) - 1] = '\0';
        value++;
      }
      GNUNET_CONFIGURATION_set_value_string (cfg,
                                             section,
                                             tag,
                                             value);
      if (cfg->diagnostics)
      {
        set_entry_hint (cfg,
                        section,
                        tag,
                        source_filename
                        ? source_filename
                        : "<input>",
                        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 : "<input>");
    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;
}


/**
 * Dump an os installation path
 * to a buffer.
 *
 * @param cfg configuration
 * @param buf buffer to write to
 * @param ipk which path to print
 */
static void
dump_os_ipk (
  const struct GNUNET_CONFIGURATION_Handle *cfg,
  struct GNUNET_Buffer *buf,
  enum GNUNET_OS_InstallationPathKind ipk)
{
  char *v = GNUNET_OS_installation_get_path (cfg->pd,
                                             ipk);
  const char *ipkname;
  switch (ipk)
  {
  case GNUNET_OS_IPK_DATADIR:
    ipkname = "IPK_DATADIR";
    break;
  case GNUNET_OS_IPK_BINDIR:
    ipkname = "IPK_BINDIR";
    break;
  case GNUNET_OS_IPK_LIBDIR:
    ipkname = "IPK_LIBDIR";
    break;
  case GNUNET_OS_IPK_PREFIX:
    ipkname = "IPK_PREFIX";
  case GNUNET_OS_IPK_LOCALEDIR:
    ipkname = "IPK_LOCALEDIR";
    break;
  case GNUNET_OS_IPK_ICONDIR:
    ipkname = "IPK_ICONDIR";
  case GNUNET_OS_IPK_DOCDIR:
    ipkname = "IPK_DOCDIR";
    break;
  case GNUNET_OS_IPK_LIBEXECDIR:
    ipkname = "IPK_LIBEXECDIR";
    break;
  case GNUNET_OS_IPK_SELF_PREFIX:
    ipkname = "IPK_SELF_PREFIX";
    break;
  default:
    ipkname = "??";
    break;
  }
  GNUNET_buffer_write_fstr (buf,
                            "# %s = %s\n",
                            ipkname,
                            v);
  GNUNET_free (v);
}


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 :
                            "<none>");
  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# Installation paths:\n");

  dump_os_ipk (cfg, &buf, GNUNET_OS_IPK_DATADIR);
  dump_os_ipk (cfg, &buf, GNUNET_OS_IPK_LIBDIR);
  dump_os_ipk (cfg, &buf, GNUNET_OS_IPK_BINDIR);
  dump_os_ipk (cfg, &buf, GNUNET_OS_IPK_PREFIX);
  dump_os_ipk (cfg, &buf, GNUNET_OS_IPK_LOCALEDIR);
  dump_os_ipk (cfg, &buf, GNUNET_OS_IPK_ICONDIR);
  dump_os_ipk (cfg, &buf, GNUNET_OS_IPK_DOCDIR);
  dump_os_ipk (cfg, &buf, GNUNET_OS_IPK_LIBEXECDIR);
  dump_os_ipk (cfg, &buf, GNUNET_OS_IPK_SELF_PREFIX);


  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,
                                "# <section contents inaccessible>\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;

  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 (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_sections (
  const struct GNUNET_CONFIGURATION_Handle *cfg,
  GNUNET_CONFIGURATION_SectionIterator iter,
  void *iter_cls)
{
  struct ConfigSection *spos;
  struct ConfigSection *next;

  next = cfg->sections;
  while (next != NULL)
  {
    spos = next;
    next = spos->next;
    if (! spos->inaccessible)
      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 (cfg->pd);
  GNUNET_CONFIGURATION_iterate (cfg,
                                &copy_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;

  GNUNET_break (cfg_default->pd == cfg_new->pd);
  diffHandle.cfgDiff = GNUNET_CONFIGURATION_create (cfg_new->pd);
  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_NO;
  if (NULL == e->val)
    return GNUNET_NO;
  if (1 != sscanf (e->val,
                   "%llu%1s",
                   number,
                   dummy))
    return GNUNET_SYSERR;
  return GNUNET_OK;
}


void
GNUNET_CONFIGURATION_set_value_float (struct GNUNET_CONFIGURATION_Handle *cfg,
                                      const char *section,
                                      const char *option,
                                      float number)
{
  char s[64];

  /* See: #9369 */
  const locale_t cl = newlocale (LC_NUMERIC_MASK,
                                 "C",
                                 (locale_t) 0);
  locale_t old_locale = uselocale (cl);
  GNUNET_snprintf (s,
                   64,
                   "%f",
                   (double) number);
  uselocale (old_locale);
  GNUNET_CONFIGURATION_set_value_string (cfg,
                                         section,
                                         option,
                                         s);
}


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_NO;
  if (NULL == e->val)
    return GNUNET_NO;
  /* See #9369 */
  {
    const locale_t cl = newlocale (LC_NUMERIC_MASK,
                                   "C",
                                   (locale_t) 0);
    locale_t old_locale = uselocale (cl);

    if (1 != sscanf (e->val,
                     "%f%1s",
                     number,
                     dummy))
      return GNUNET_SYSERR;
    uselocale (old_locale);
  }
  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_NO;
  if (NULL == e->val)
    return GNUNET_NO;
  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_NO;
  if (NULL == e->val)
    return GNUNET_NO;
  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_NO;
  }
  *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_NO;
  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 `}' 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;
  enum GNUNET_GenericReturnValue ret;

  ret = GNUNET_CONFIGURATION_get_value_string (cfg,
                                               section,
                                               option,
                                               &tmp);
  if (GNUNET_OK != ret)
  {
    LOG (GNUNET_ERROR_TYPE_DEBUG,
         "Failed to retrieve filename\n");
    *value = NULL;
    return ret;
  }
  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;
  enum GNUNET_GenericReturnValue ret;

  ret =
    GNUNET_CONFIGURATION_get_value_choice (cfg,
                                           section,
                                           option,
                                           yesno,
                                           &val);
  if (GNUNET_OK != ret)
    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 ( (NULL != cb) &&
           (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;
      continue;
    }
    fun_ret = GNUNET_CONFIGURATION_parse (cfg,
                                          filename);
    if (fun_ret != GNUNET_OK)
      break;
  }
  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 (
  const struct GNUNET_OS_ProjectData *pd)
{
  char *cfg_fn;
  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;
}


enum GNUNET_GenericReturnValue
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 = cfg->pd->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 (cfg->pd,
                                             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;
}


/**
 * Print each option in a given section as a filename.
 *
 * @param cls closure
 * @param section name of the section
 * @param option name of the option
 * @param value value of the option
 */
static void
print_filename_option (void *cls,
                       const char *section,
                       const char *option,
                       const char *value)
{
  const struct GNUNET_CONFIGURATION_Handle *cfg = cls;

  char *value_fn;
  char *fn;

  GNUNET_assert (GNUNET_OK ==
                 GNUNET_CONFIGURATION_get_value_filename (cfg,
                                                          section,
                                                          option,
                                                          &value_fn));
  fn = GNUNET_STRINGS_filename_expand (value_fn);
  if (NULL == fn)
    fn = value_fn;
  else
    GNUNET_free (value_fn);
  fprintf (stdout,
           "%s = %s\n",
           option,
           fn);
  GNUNET_free (fn);
}


/**
 * Print each option in a given section.
 *
 * @param cls closure
 * @param section name of the section
 * @param option name of the option
 * @param value value of the option
 */
static void
print_option (void *cls,
              const char *section,
              const char *option,
              const char *value)
{
  (void) cls;
  (void) section;

  fprintf (stdout,
           "%s = %s\n",
           option,
           value);
}


/**
 * Print out given section name.
 *
 * @param cls unused
 * @param section a section in the configuration file
 */
static void
print_section_name (void *cls,
                    const char *section)
{
  (void) cls;
  fprintf (stdout,
           "%s\n",
           section);
}


void
GNUNET_CONFIGURATION_config_tool_run (
  void *cls,
  char *const *args,
  const char *cfgfile,
  const struct GNUNET_CONFIGURATION_Handle *cfg)
{
  struct GNUNET_CONFIGURATION_ConfigSettings *cs = cls;
  struct GNUNET_CONFIGURATION_Handle *out = NULL;
  struct GNUNET_CONFIGURATION_Handle *ncfg = NULL;

  (void) args;
  if (cs->diagnostics)
  {
    /* Re-parse the configuration with diagnostics enabled. */
    ncfg = GNUNET_CONFIGURATION_create (cfg->pd);
    GNUNET_CONFIGURATION_enable_diagnostics (ncfg);
    if (GNUNET_OK !=
        GNUNET_CONFIGURATION_load (ncfg,
                                   cfgfile))
    {
      fprintf (stderr,
               _ ("Failed to load config file `%s'"),
               cfgfile);
      return;
    }
    cfg = ncfg;
  }

  if (cs->full)
    cs->rewrite = GNUNET_YES;
  if (cs->list_sections)
  {
    fprintf (stderr,
             _ ("The following sections are available:\n"));
    GNUNET_CONFIGURATION_iterate_sections (cfg,
                                           &print_section_name,
                                           NULL);
    return;
  }
  if ( (! cs->rewrite) &&
       (NULL == cs->section) )
  {
    char *serialization;

    if (! cs->diagnostics)
    {
      fprintf (stderr,
               _ ("%s, %s or %s argument is required\n"),
               "--section",
               "--list-sections",
               "--diagnostics");
      cs->global_ret = EXIT_INVALIDARGUMENT;
      return;
    }
    serialization = GNUNET_CONFIGURATION_serialize_diagnostics (cfg);
    fprintf (stdout,
             "%s",
             serialization);
    GNUNET_free (serialization);
  }
  else if ( (NULL != cs->section) &&
            (NULL == cs->value) )
  {
    if (NULL == cs->option)
    {
      GNUNET_CONFIGURATION_iterate_section_values (
        cfg,
        cs->section,
        cs->is_filename
        ? &print_filename_option
        : &print_option,
        (void *) cfg);
    }
    else
    {
      char *value;

      if (cs->is_filename)
      {
        if (GNUNET_OK !=
            GNUNET_CONFIGURATION_get_value_filename (cfg,
                                                     cs->section,
                                                     cs->option,
                                                     &value))
        {
          GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                                     cs->section,
                                     cs->option);
          cs->global_ret = EXIT_NOTCONFIGURED;
          return;
        }
      }
      else
      {
        if (GNUNET_OK !=
            GNUNET_CONFIGURATION_get_value_string (cfg,
                                                   cs->section,
                                                   cs->option,
                                                   &value))
        {
          GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                                     cs->section,
                                     cs->option);
          cs->global_ret = EXIT_NOTCONFIGURED;
          return;
        }
      }
      fprintf (stdout,
               "%s\n",
               value);
      GNUNET_free (value);
    }
  }
  else if (NULL != cs->section)
  {
    if (NULL == cs->option)
    {
      fprintf (stderr,
               _ ("--option argument required to set value\n"));
      cs->global_ret = EXIT_INVALIDARGUMENT;
      return;
    }
    out = GNUNET_CONFIGURATION_dup (cfg);
    GNUNET_CONFIGURATION_set_value_string (out,
                                           cs->section,
                                           cs->option,
                                           cs->value);
    cs->rewrite = GNUNET_YES;
  }
  if (cs->rewrite)
  {
    char *cfg_fn = NULL;

    if (NULL == out)
      out = GNUNET_CONFIGURATION_dup (cfg);

    if (NULL == cfgfile)
    {
      const char *xdg = getenv ("XDG_CONFIG_HOME");

      if (NULL != xdg)
        GNUNET_asprintf (&cfg_fn,
                         "%s%s%s",
                         xdg,
                         DIR_SEPARATOR_STR,
                         cfg->pd->config_file);
      else
        cfg_fn = GNUNET_strdup (cfg->pd->user_config_file);
      cfgfile = cfg_fn;
    }

    if (! cs->full)
    {
      struct GNUNET_CONFIGURATION_Handle *def;

      def = GNUNET_CONFIGURATION_create (cfg->pd);
      if (GNUNET_OK !=
          GNUNET_CONFIGURATION_load (def,
                                     NULL))
      {
        fprintf (stderr,
                 _ ("failed to load configuration defaults"));
        cs->global_ret = 1;
        GNUNET_CONFIGURATION_destroy (def);
        GNUNET_CONFIGURATION_destroy (out);
        GNUNET_free (cfg_fn);
        return;
      }
      if (GNUNET_OK !=
          GNUNET_CONFIGURATION_write_diffs (def,
                                            out,
                                            cfgfile))
        cs->global_ret = 2;
      GNUNET_CONFIGURATION_destroy (def);
    }
    else
    {
      if (GNUNET_OK !=
          GNUNET_CONFIGURATION_write (out,
                                      cfgfile))
        cs->global_ret = 2;
    }
    GNUNET_free (cfg_fn);
  }
  if (NULL != out)
    GNUNET_CONFIGURATION_destroy (out);
  if (NULL != ncfg)
    GNUNET_CONFIGURATION_destroy (ncfg);
}


void
GNUNET_CONFIGURATION_config_settings_free (
  struct GNUNET_CONFIGURATION_ConfigSettings *cs)
{
  GNUNET_free (cs->option);
  GNUNET_free (cs->section);
  GNUNET_free (cs->value);
}


const struct GNUNET_OS_ProjectData *
GNUNET_CONFIGURATION_get_project_data (
  const struct GNUNET_CONFIGURATION_Handle *cfg)
{
  return cfg->pd;
}


/* end of configuration.c */
