summaryrefslogblamecommitdiff
path: root/3rdparty/libconfig/libconfig.c
blob: 72520e7c58d16aaac73b6e1eb96d58b1469ddff1 (plain) (tree)
1
2
3
4
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587

                                                                               

                                             





















                                                                               





                     


                  

                   








                      
 
                        
                     
                               
 
                                                      




                                                                               

                                                                 





                                                                            
                           






                                                                               


                                                                   






















                                                                               

                                                                                                                
                                                      





                                                                               




                                                                               













                                                               

                                                                                                                
                                                      













































                                                                               


                                                                       







































































































































                                                                             


                                                                        

























                                           


                                                                        


















                                        






                                                                              
 








                                           
                                                                


                   
                                                          




























































































































































                                                                               
                                                            



                         
                                                                            













                                                                               





                                                  
                                







                                    
                            








                                    
                                                           





                                                  
                                                 








































                                                                               


                                                                   
 





                                                                       
               

                                                      



                                 


                                                                 

   

                                                                          


               


                                                                       









                                                                               
                                                          







                                                                               

                  
                                       














                                                                  
   


                      














                                                                               

                                       


                                            
                                  


                         

                               






























                                                                               


                                                                  







                                                                               
                                                 
      
                                                  





                                                                               














                                                                               




















































                                                                               
                                                                         




































                                                                               
                                                                         

















































































































































































































                                                                               
                                                                         







                                         
                                                                         














































































                                                                               

                                                                  
































                                                                               
                                                    





































































































































































































































































































































































                                                                               

          



                                      











                                                                                            









































































                                                                               
/* ----------------------------------------------------------------------------
   libconfig - A library for processing structured configuration files
   Copyright (C) 2013-2016  Hercules Dev Team
   Copyright (C) 2005-2014  Mark A Lindner

   This file is part of libconfig.

   This library is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this library.  If not, see <http://www.gnu.org/licenses/>.
   ----------------------------------------------------------------------------
*/

#ifdef HAVE_CONFIG_H
#include "ac_config.h"
#endif

#include <locale.h>

#ifdef HAVE_XLOCALE_H
#include <xlocale.h>
#endif

#include <ctype.h>
#include <float.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>

#include "libconfig.h"
#include "parsectx.h"
#include "scanctx.h"
#include "wincompat.h"
#include "grammar.h"
#include "scanner.h"

#define PATH_TOKENS ":/"
#define CHUNK_SIZE 16
#define FLOAT_PRECISION DBL_DIG

#define _new(T) (T *)calloc(1, sizeof(T)) /* zeroed */
#define _delete(P) free((void *)(P))

/* ------------------------------------------------------------------------- */

#ifndef LIBCONFIG_STATIC
#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__)    \
     || defined(WIN64) || defined(_WIN64))

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
  return(TRUE);
}

#endif /* WIN32 || WIN64 */
#endif /* LIBCONFIG_STATIC */

/* ------------------------------------------------------------------------- */

static const char *__io_error = "file I/O error";

static void __config_list_destroy(config_list_t *list);
static void __config_write_setting(const config_t *config,
                                   const config_setting_t *setting,
                                   FILE *stream, int depth);

/* ------------------------------------------------------------------------- */

static void __config_locale_override(void)
{
#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__)) \
  && ! defined(__MINGW32__)

  _configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
  setlocale(LC_NUMERIC, "C");

#elif defined(__APPLE__)

  locale_t loc = newlocale(LC_NUMERIC_MASK, "C", NULL);
  uselocale(loc);

#elif ((defined HAVE_NEWLOCALE) && (defined HAVE_USELOCALE))

  locale_t loc = newlocale(LC_NUMERIC, "C", NULL);
  uselocale(loc);

#else

/* locale overriding is pretty pointless (Hercules doesn't make use of the area that uses locale functionality),
 * but I'm actually removing it because it floods the buildbot with warnings  */
//#warning "No way to modify calling thread's locale!"

#endif
}

/* ------------------------------------------------------------------------- */

#define __config_has_option(C, O)               \
  (((C)->options & (O)) != 0)

/* ------------------------------------------------------------------------- */

static void __config_locale_restore(void)
{
#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__)) \
  && ! defined(__MINGW32__)

    _configthreadlocale(_DISABLE_PER_THREAD_LOCALE);

#elif ((defined HAVE_USELOCALE) && (defined HAVE_FREELOCALE))

  locale_t loc = uselocale(LC_GLOBAL_LOCALE);
  freelocale(loc);

#else

/* locale overriding is pretty pointless (Hercules doesn't make use of the area that uses locale functionality),
 * but I'm actually removing it because it floods the buildbot with warnings  */
//#warning "No way to modify calling thread's locale!"

#endif
}

/* ------------------------------------------------------------------------- */

static int __config_name_compare(const char *a, const char *b)
{
  const char *p, *q;

  for(p = a, q = b; ; p++, q++)
  {
    int pd = ((! *p) || strchr(PATH_TOKENS, *p));
    int qd = ((! *q) || strchr(PATH_TOKENS, *q));

    if(pd && qd)
      break;
    else if(pd)
      return(-1);
    else if(qd)
      return(1);
    else if(*p < *q)
      return(-1);
    else if(*p > *q)
      return(1);
  }

  return(0);
}

/* ------------------------------------------------------------------------- */

static void __config_indent(FILE *stream, int depth, unsigned short w)
{
  if(w)
    fprintf(stream, "%*s", (depth - 1) * w, " ");
  else
  {
    int i;
    for(i = 0; i < (depth - 1); ++i)
      fputc('\t', stream);
  }
}

/* ------------------------------------------------------------------------- */

static void __config_write_value(const config_t *config,
                                 const config_value_t *value, int type,
                                 int format, int depth, FILE *stream)
{
  char fbuf[64];

  switch(type)
  {
    /* boolean */
    case CONFIG_TYPE_BOOL:
      fputs(value->ival ? "true" : "false", stream);
      break;

    /* int */
    case CONFIG_TYPE_INT:
      switch(format)
      {
        case CONFIG_FORMAT_HEX:
          fprintf(stream, "0x%X", value->ival);
          break;

        case CONFIG_FORMAT_DEFAULT:
        default:
          fprintf(stream, "%d", value->ival);
          break;
      }
      break;

    /* 64-bit int */
    case CONFIG_TYPE_INT64:
      switch(format)
      {
        case CONFIG_FORMAT_HEX:
          fprintf(stream, "0x" INT64_HEX_FMT "L", value->llval);
          break;

        case CONFIG_FORMAT_DEFAULT:
        default:
          fprintf(stream, INT64_FMT "L", value->llval);
          break;
      }
      break;

    /* float */
    case CONFIG_TYPE_FLOAT:
    {
      char *q;

      snprintf(fbuf, sizeof(fbuf) - 3, "%.*g", FLOAT_PRECISION, value->fval);

      /* check for exponent */
      q = strchr(fbuf, 'e');
      if(! q)
      {
        /* no exponent */
        if(! strchr(fbuf, '.')) /* no decimal point */
          strcat(fbuf, ".0");
        else
        {
          /* has decimal point */
          char *p;

          for(p = fbuf + strlen(fbuf) - 1; p > fbuf; --p)
          {
            if(*p != '0')
            {
              *(++p) = '\0';
              break;
            }
          }
        }
      }

      fputs(fbuf, stream);
      break;
    }

    /* string */
    case CONFIG_TYPE_STRING:
    {
      char *p;

      fputc('\"', stream);

      if(value->sval)
      {
        for(p = value->sval; *p; p++)
        {
          int c = (int)*p & 0xFF;
          switch(c)
          {
            case '\"':
            case '\\':
              fputc('\\', stream);
              fputc(c, stream);
              break;

            case '\n':
              fputs("\\n", stream);
              break;

            case '\r':
              fputs("\\r", stream);
              break;

            case '\f':
              fputs("\\f", stream);
              break;

            case '\t':
              fputs("\\t", stream);
              break;

            default:
              if(c >= ' ')
                fputc(c, stream);
              else
                fprintf(stream, "\\x%02X", c);
          }
        }
      }
      fputc('\"', stream);
      break;
    }

    /* list */
    case CONFIG_TYPE_LIST:
    {
      config_list_t *list = value->list;

      fprintf(stream, "( ");

      if(list)
      {
        int len = list->length;
        config_setting_t **s;

        for(s = list->elements; len--; s++)
        {
          __config_write_value(config, &((*s)->value), (*s)->type,
                               config_setting_get_format(*s), depth + 1,
                               stream);

          if(len)
            fputc(',', stream);

          fputc(' ', stream);
        }
      }

      fputc(')', stream);
      break;
    }

    /* array */
    case CONFIG_TYPE_ARRAY:
    {
      config_list_t *list = value->list;

      fprintf(stream, "[ ");

      if(list)
      {
        int len = list->length;
        config_setting_t **s;

        for(s = list->elements; len--; s++)
        {
          __config_write_value(config, &((*s)->value), (*s)->type,
                               config_setting_get_format(*s), depth + 1,
                               stream);

          if(len)
            fputc(',', stream);

          fputc(' ', stream);
        }
      }

      fputc(']', stream);
      break;
    }

    /* group */
    case CONFIG_TYPE_GROUP:
    {
      config_list_t *list = value->list;

      if(depth > 0)
      {
        if((config->options & CONFIG_OPTION_OPEN_BRACE_ON_SEPARATE_LINE) != 0)
        {
          fputc('\n', stream);

          if(depth > 1)
            __config_indent(stream, depth, config->tab_width);
        }

        fprintf(stream, "{\n");
      }

      if(list)
      {
        int len = list->length;
        config_setting_t **s;

        for(s = list->elements; len--; s++)
          __config_write_setting(config, *s, stream, depth + 1);
      }

      if(depth > 1)
        __config_indent(stream, depth, config->tab_width);

      if(depth > 0)
        fputc('}', stream);

      break;
    }

    default:
      /* this shouldn't happen, but handle it gracefully... */
      fputs("???", stream);
      break;
  }
}

/* ------------------------------------------------------------------------- */

static void __config_list_add(config_list_t *list, config_setting_t *setting)
{
  if((list->length % CHUNK_SIZE) == 0)
  {
    list->elements = (config_setting_t **)realloc(
      list->elements,
      (list->length + CHUNK_SIZE) * sizeof(config_setting_t *));
  }

  list->elements[list->length] = setting;
  list->length++;
}

/* ------------------------------------------------------------------------- */

static config_setting_t *__config_list_search(config_list_t *list,
                                              const char *name,
                                              unsigned int *idx)
{
  config_setting_t **found = NULL;
  unsigned int i;

  if(! list)
    return(NULL);

  for(i = 0, found = list->elements; i < list->length; i++, found++)
  {
    if(! (*found)->name)
      continue;

    if(! __config_name_compare(name, (*found)->name))
    {
      if(idx)
        *idx = i;

      return(*found);
    }
  }

  return(NULL);
}

/* ------------------------------------------------------------------------- */

static config_setting_t *__config_list_remove(config_list_t *list, int idx)
{
  config_setting_t *removed = *(list->elements + idx);
  int offset = (idx * sizeof(config_setting_t *));
  int len = list->length - 1 - idx;
  char *base = (char *)list->elements + offset;

  memmove(base, base + sizeof(config_setting_t *),
          len * sizeof(config_setting_t *));

  list->length--;

  /* possibly realloc smaller? */

  return(removed);
}

/* ------------------------------------------------------------------------- */

static void __config_setting_destroy(config_setting_t *setting)
{
  if(setting)
  {
    if(setting->name)
      _delete(setting->name);

    if(setting->type == CONFIG_TYPE_STRING)
      _delete(setting->value.sval);

    else if((setting->type == CONFIG_TYPE_GROUP)
            || (setting->type == CONFIG_TYPE_ARRAY)
            || (setting->type == CONFIG_TYPE_LIST))
    {
      if(setting->value.list)
        __config_list_destroy(setting->value.list);
    }

    if(setting->hook && setting->config->destructor)
      setting->config->destructor(setting->hook);

    _delete(setting);
  }
}

/* ------------------------------------------------------------------------- */

static void __config_list_destroy(config_list_t *list)
{
  config_setting_t **p;
  unsigned int i;

  if(! list)
    return;

  if(list->elements)
  {
    for(p = list->elements, i = 0; i < list->length; p++, i++)
      __config_setting_destroy(*p);

    _delete(list->elements);
  }

  _delete(list);
}

/* ------------------------------------------------------------------------- */

static int __config_vector_checktype(const config_setting_t *vector, int type)
{
  /* if the array is empty, then it has no type yet */

  if(! vector->value.list)
    return(CONFIG_TRUE);

  if(vector->value.list->length == 0)
    return(CONFIG_TRUE);

  /* if it's a list, any type is allowed */

  if(vector->type == CONFIG_TYPE_LIST)
    return(CONFIG_TRUE);

  /* otherwise the first element added determines the type of the array */

  return((vector->value.list->elements[0]->type == type)
         ? CONFIG_TRUE : CONFIG_FALSE);
}

/* ------------------------------------------------------------------------- */

static int __config_validate_name(const char *name)
{
  const char *p = name;

  if(*p == '\0')
    return(CONFIG_FALSE);

  if(! isalpha((int)*p) && !isdigit((int)*p) && (*p != '*'))
    return(CONFIG_FALSE);

  for(++p; *p; ++p)
  {
    if(! (isalpha((int)*p) || isdigit((int)*p) || strchr("*_-'.", (int)*p)))
      return(CONFIG_FALSE);
  }

  return(CONFIG_TRUE);
}

/* ------------------------------------------------------------------------- */

static int __config_read(config_t *config, FILE *stream, const char *filename,
                         const char *str)
{
  yyscan_t scanner;
  struct scan_context scan_ctx;
  struct parse_context parse_ctx;
  int r;

  /* Reinitialize the config */
  void (*destructor)(void *) = config->destructor;
  const char *include_dir = config->include_dir;
  unsigned short tab_width = config->tab_width;
  int options = config->options;

  config->include_dir = NULL;
  config_destroy(config);
  config_init(config);

  config->destructor = destructor;
  config->include_dir = include_dir;
  config->tab_width = tab_width;
  config->options = options;

  parsectx_init(&parse_ctx);
  parse_ctx.config = config;
  parse_ctx.parent = config->root;
  parse_ctx.setting = config->root;

  __config_locale_override();

  scanctx_init(&scan_ctx, filename);
  config->root->file = scanctx_current_filename(&scan_ctx);
  scan_ctx.config = config;
  libconfig_yylex_init_extra(&scan_ctx, &scanner);

  if(stream)
    libconfig_yyrestart(stream, scanner);
  else /* read from string */
    (void)libconfig_yy_scan_string(str, scanner);

  libconfig_yyset_lineno(1, scanner);
  r = libconfig_yyparse(scanner, &parse_ctx, &scan_ctx);

  if(r != 0)
  {
    YY_BUFFER_STATE buf;

    config->error_file = scanctx_current_filename(&scan_ctx);
    config->error_type = CONFIG_ERR_PARSE;

    /* Unwind the include stack, freeing the buffers and closing the files. */
    while((buf = (YY_BUFFER_STATE)scanctx_pop_include(&scan_ctx)) != NULL)
      libconfig_yy_delete_buffer(buf, scanner);
  }

  libconfig_yylex_destroy(scanner);
  config->filenames = scanctx_cleanup(&scan_ctx, &(config->num_filenames));
  parsectx_cleanup(&parse_ctx);

  __config_locale_restore();

  return(r == 0 ? CONFIG_TRUE : CONFIG_FALSE);
}

/* ------------------------------------------------------------------------- */

int config_read(config_t *config, FILE *stream)
{
  return(__config_read(config, stream, NULL, NULL));
}

/* ------------------------------------------------------------------------- */

int config_read_string(config_t *config, const char *str)
{
  return(__config_read(config, NULL, NULL, str));
}

/* ------------------------------------------------------------------------- */

static void __config_write_setting(const config_t *config,
                                   const config_setting_t *setting,
                                   FILE *stream, int depth)
{
  char group_assign_char = __config_has_option(
    config, CONFIG_OPTION_COLON_ASSIGNMENT_FOR_GROUPS) ? ':' : '=';

  char nongroup_assign_char = __config_has_option(
    config, CONFIG_OPTION_COLON_ASSIGNMENT_FOR_NON_GROUPS) ? ':' : '=';

  if(depth > 1)
    __config_indent(stream, depth, config->tab_width);


  if(setting->name)
  {
    fputs(setting->name, stream);
    fprintf(stream, " %c ", ((setting->type == CONFIG_TYPE_GROUP)
                             ? group_assign_char
                             : nongroup_assign_char));
  }

  __config_write_value(config, &(setting->value), setting->type,
                       config_setting_get_format(setting), depth, stream);

  if(depth > 0)
  {
    if(__config_has_option(config, CONFIG_OPTION_SEMICOLON_SEPARATORS))
      fputc(';', stream);

    fputc('\n', stream);
  }
}

/* ------------------------------------------------------------------------- */

void config_write(const config_t *config, FILE *stream)
{
  __config_locale_override();

  __config_write_setting(config, config->root, stream, 0);

  __config_locale_restore();
}

/* ------------------------------------------------------------------------- */

int config_read_file(config_t *config, const char *filename)
{
  int ret, ok = 0;

  FILE *stream = fopen(filename, "rt");
  if(stream != NULL)
  {
    // On some operating systems, fopen() succeeds on a directory.
    int fd = fileno(stream);
    struct stat statbuf;

    if(fstat(fd, &statbuf) == 0)
    {
      // Only proceed if this is not a directory.
      if(!S_ISDIR(statbuf.st_mode))
        ok = 1;
    }
  }

  if(!ok)
  {
    if(stream != NULL)
      fclose(stream);

    config->error_text = __io_error;
    config->error_type = CONFIG_ERR_FILE_IO;
    return(CONFIG_FALSE);
  }

  ret = __config_read(config, stream, filename, NULL);
  fclose(stream);

  return(ret);
}

/* ------------------------------------------------------------------------- */

int config_write_file(config_t *config, const char *filename)
{
  FILE *stream = fopen(filename, "wt");
  if(stream == NULL)
  {
    config->error_text = __io_error;
    config->error_type = CONFIG_ERR_FILE_IO;
    config->error_file = filename;
    return(CONFIG_FALSE);
  }

  config_write(config, stream);
  fclose(stream);
  config->error_type = CONFIG_ERR_NONE;
  return(CONFIG_TRUE);
}

/* ------------------------------------------------------------------------- */

void config_destroy(config_t *config)
{
  unsigned int count = config->num_filenames;
  const char **f;

  __config_setting_destroy(config->root);

  for(f = config->filenames; count > 0; ++f, --count)
    _delete(*f);

  _delete(config->filenames);
  _delete(config->include_dir);

  memset((void *)config, 0, sizeof(config_t));
}

/* ------------------------------------------------------------------------- */

void config_init(config_t *config)
{
  memset((void *)config, 0, sizeof(config_t));

  config->root = _new(config_setting_t);
  config->root->type = CONFIG_TYPE_GROUP;
  config->root->config = config;
  config->options = (CONFIG_OPTION_SEMICOLON_SEPARATORS
                     | CONFIG_OPTION_COLON_ASSIGNMENT_FOR_GROUPS
                     | CONFIG_OPTION_OPEN_BRACE_ON_SEPARATE_LINE);
  config->tab_width = 2;
}

/* ------------------------------------------------------------------------- */

void config_set_auto_convert(config_t *config, int flag)
{
  if(flag)
    config->options |= CONFIG_OPTION_AUTOCONVERT;
  else
    config->options &= ~CONFIG_OPTION_AUTOCONVERT;
}

/* ------------------------------------------------------------------------- */

int config_get_auto_convert(const config_t *config)
{
  return(__config_has_option(config, CONFIG_OPTION_AUTOCONVERT));
}

/* ------------------------------------------------------------------------- */

void config_set_options(config_t *config, int options)
{
  config->options = options;
}

/* ------------------------------------------------------------------------- */

int config_get_options(const config_t *config)
{
  return(config->options);
}

/* ------------------------------------------------------------------------- */

static config_setting_t *config_setting_create(config_setting_t *parent,
                                               const char *name, int type)
{
  config_setting_t *setting;
  config_list_t *list;

  if((parent->type != CONFIG_TYPE_GROUP)
     && (parent->type != CONFIG_TYPE_ARRAY)
     && (parent->type != CONFIG_TYPE_LIST))
    return(NULL);

  setting = _new(config_setting_t);
  setting->parent = parent;
  setting->name = (name == NULL) ? NULL : strdup(name);
  setting->type = type;
  setting->config = parent->config;
  setting->hook = NULL;
  setting->line = 0;

  list = parent->value.list;

  if(! list)
    list = parent->value.list = _new(config_list_t);

  __config_list_add(list, setting);

  return(setting);
}

/* ------------------------------------------------------------------------- */

static int __config_setting_get_int(const config_setting_t *setting,
                                    int *value)
{
  switch(setting->type)
  {
    case CONFIG_TYPE_INT:
      *value = setting->value.ival;
      return(CONFIG_TRUE);

    case CONFIG_TYPE_INT64:
      if((setting->value.llval > INT32_MAX)
         || (setting->value.llval < INT32_MIN))
        *value = 0;
      else
        *value = (int)(setting->value.llval);
      return(CONFIG_TRUE);

    case CONFIG_TYPE_FLOAT:
      if(__config_has_option(setting->config, CONFIG_OPTION_AUTOCONVERT))
      {
        *value = (int)(setting->value.fval);
        return(CONFIG_TRUE);
      }
      else
      { /* fall through */ }

    default:
      return(CONFIG_FALSE);
  }
}

/* ------------------------------------------------------------------------- */

int config_setting_get_int(const config_setting_t *setting)
{
  int value = 0;
  __config_setting_get_int(setting, &value);
  return(value);
}

/* ------------------------------------------------------------------------- */

static int __config_setting_get_int64(const config_setting_t *setting,
                                      long long *value)
{
  switch(setting->type)
  {
    case CONFIG_TYPE_INT64:
      *value = setting->value.llval;
      return(CONFIG_TRUE);

    case CONFIG_TYPE_INT:
      *value = (long long)(setting->value.ival);
      return(CONFIG_TRUE);

    case CONFIG_TYPE_FLOAT:
      if(__config_has_option(setting->config, CONFIG_OPTION_AUTOCONVERT))
      {
        *value = (long long)(setting->value.fval);
        return(CONFIG_TRUE);
      }
      else
      { /* fall through */ }

    default:
      return(CONFIG_FALSE);
  }
}

/* ------------------------------------------------------------------------- */

long long config_setting_get_int64(const config_setting_t *setting)
{
  long long value = 0;
  __config_setting_get_int64(setting, &value);
  return(value);
}

/* ------------------------------------------------------------------------- */

int config_setting_lookup_int(const config_setting_t *setting,
                              const char *name, int *value)
{
  config_setting_t *member = config_setting_get_member(setting, name);
  if(! member)
    return(CONFIG_FALSE);

  return(__config_setting_get_int(member, value));
}

/* ------------------------------------------------------------------------- */

int config_setting_lookup_int64(const config_setting_t *setting,
                                const char *name, long long *value)
{
  config_setting_t *member = config_setting_get_member(setting, name);
  if(! member)
    return(CONFIG_FALSE);

  return(__config_setting_get_int64(member, value));
}

/* ------------------------------------------------------------------------- */

static int __config_setting_get_float(const config_setting_t *setting,
                                      double *value)
{
  switch(setting->type)
  {
    case CONFIG_TYPE_FLOAT:
      *value = setting->value.fval;
      return(CONFIG_TRUE);

    case CONFIG_TYPE_INT:
      if(config_get_auto_convert(setting->config))
      {
        *value = (double)(setting->value.ival);
        return(CONFIG_TRUE);
      }
      else
        return(CONFIG_FALSE);

    case CONFIG_TYPE_INT64:
      if(config_get_auto_convert(setting->config))
      {
        *value = (double)(setting->value.llval);
        return(CONFIG_TRUE);
      }
      else
      { /* fall through */ }

    default:
      return(CONFIG_FALSE);
  }
}

/* ------------------------------------------------------------------------- */

double config_setting_get_float(const config_setting_t *setting)
{
  double value = 0.0;
  __config_setting_get_float(setting, &value);
  return(value);
}

/* ------------------------------------------------------------------------- */

int config_setting_lookup_float(const config_setting_t *setting,
                                const char *name, double *value)
{
  config_setting_t *member = config_setting_get_member(setting, name);
  if(! member)
    return(CONFIG_FALSE);

  return(__config_setting_get_float(member, value));
}

/* ------------------------------------------------------------------------- */

int config_setting_lookup_string(const config_setting_t *setting,
                                 const char *name, const char **value)
{
  config_setting_t *member = config_setting_get_member(setting, name);
  if(! member)
    return(CONFIG_FALSE);

  if(config_setting_type(member) != CONFIG_TYPE_STRING)
    return(CONFIG_FALSE);

  *value = config_setting_get_string(member);
  return(CONFIG_TRUE);
}

/* ------------------------------------------------------------------------- */

int config_setting_lookup_bool(const config_setting_t *setting,
                               const char *name, int *value)
{
  config_setting_t *member = config_setting_get_member(setting, name);
  if(! member)
    return(CONFIG_FALSE);

  if(config_setting_type(member) != CONFIG_TYPE_BOOL)
    return(CONFIG_FALSE);

  *value = config_setting_get_bool(member);
  return(CONFIG_TRUE);
}

/* ------------------------------------------------------------------------- */

int config_setting_set_int(config_setting_t *setting, int value)
{
  switch(setting->type)
  {
    case CONFIG_TYPE_NONE:
      setting->type = CONFIG_TYPE_INT;
      /* fall through */

    case CONFIG_TYPE_INT:
      setting->value.ival = value;
      return(CONFIG_TRUE);

    case CONFIG_TYPE_FLOAT:
      if(config_get_auto_convert(setting->config))
      {
        setting->value.fval = (float)value;
        return(CONFIG_TRUE);
      }
      else
        return(CONFIG_FALSE);

    default:
      return(CONFIG_FALSE);
  }
}

/* ------------------------------------------------------------------------- */

int config_setting_set_int64(config_setting_t *setting, long long value)
{
  switch(setting->type)
  {
    case CONFIG_TYPE_NONE:
      setting->type = CONFIG_TYPE_INT64;
      /* fall through */

    case CONFIG_TYPE_INT64:
      setting->value.llval = value;
      return(CONFIG_TRUE);

    case CONFIG_TYPE_INT:
      if((value > INT32_MAX) || (value < INT32_MIN))
        setting->value.ival = 0;
      else
        setting->value.ival = (int)value;
      return(CONFIG_TRUE);

    case CONFIG_TYPE_FLOAT:
      if(config_get_auto_convert(setting->config))
      {
        setting->value.fval = (float)value;
        return(CONFIG_TRUE);
      }
      else
        return(CONFIG_FALSE);

    default:
      return(CONFIG_FALSE);
  }
}

/* ------------------------------------------------------------------------- */

int config_setting_set_float(config_setting_t *setting, double value)
{
  switch(setting->type)
  {
    case CONFIG_TYPE_NONE:
      setting->type = CONFIG_TYPE_FLOAT;
      /* fall through */

    case CONFIG_TYPE_FLOAT:
      setting->value.fval = value;
      return(CONFIG_TRUE);

    case CONFIG_TYPE_INT:
      if(__config_has_option(setting->config, CONFIG_OPTION_AUTOCONVERT))
      {
        setting->value.ival = (int)value;
        return(CONFIG_TRUE);
      }
      else
        return(CONFIG_FALSE);

    case CONFIG_TYPE_INT64:
      if(__config_has_option(setting->config, CONFIG_OPTION_AUTOCONVERT))
      {
        setting->value.llval = (long long)value;
        return(CONFIG_TRUE);
      }
      else
        return(CONFIG_FALSE);

    default:
      return(CONFIG_FALSE);
  }
}

/* ------------------------------------------------------------------------- */

int config_setting_get_bool(const config_setting_t *setting)
{
  return((setting->type == CONFIG_TYPE_BOOL) ? setting->value.ival : 0);
}

/* ------------------------------------------------------------------------- */

int config_setting_set_bool(config_setting_t *setting, int value)
{
  if(setting->type == CONFIG_TYPE_NONE)
    setting->type = CONFIG_TYPE_BOOL;
  else if(setting->type != CONFIG_TYPE_BOOL)
    return(CONFIG_FALSE);

  setting->value.ival = value;
  return(CONFIG_TRUE);
}

/* ------------------------------------------------------------------------- */

const char *config_setting_get_string(const config_setting_t *setting)
{
  return((setting->type == CONFIG_TYPE_STRING) ? setting->value.sval : NULL);
}

/* ------------------------------------------------------------------------- */

int config_setting_set_string(config_setting_t *setting, const char *value)
{
  if(setting->type == CONFIG_TYPE_NONE)
    setting->type = CONFIG_TYPE_STRING;
  else if(setting->type != CONFIG_TYPE_STRING)
    return(CONFIG_FALSE);

  if(setting->value.sval)
    _delete(setting->value.sval);

  setting->value.sval = (value == NULL) ? NULL : strdup(value);
  return(CONFIG_TRUE);
}

/* ------------------------------------------------------------------------- */

int config_setting_set_format(config_setting_t *setting, short format)
{
  if(((setting->type != CONFIG_TYPE_INT)
      && (setting->type != CONFIG_TYPE_INT64))
     || ((format != CONFIG_FORMAT_DEFAULT) && (format != CONFIG_FORMAT_HEX)))
    return(CONFIG_FALSE);

  setting->format = format;

  return(CONFIG_TRUE);
}

/* ------------------------------------------------------------------------- */

short config_setting_get_format(const config_setting_t *setting)
{
  return(setting->format != 0 ? setting->format
         : setting->config->default_format);
}

/* ------------------------------------------------------------------------- */

config_setting_t *config_setting_lookup(config_setting_t *setting,
                                        const char *path)
{
  const char *p = path;
  config_setting_t *found;

  for(;;)
  {
    while(*p && strchr(PATH_TOKENS, *p))
      p++;

    if(! *p)
      break;

    if(*p == '[')
      found = config_setting_get_elem(setting, atoi(++p));
    else
      found = config_setting_get_member(setting, p);

    if(! found)
      break;

    setting = found;

    while(! strchr(PATH_TOKENS, *p))
      p++;
  }

  return(*p ? NULL : setting);
}

/* ------------------------------------------------------------------------- */

config_setting_t *config_lookup(const config_t *config, const char *path)
{
  return(config_setting_lookup(config->root, path));
}

/* ------------------------------------------------------------------------- */

int config_lookup_string(const config_t *config, const char *path,
                         const char **value)
{
  const config_setting_t *s = config_lookup(config, path);
  if(! s)
    return(CONFIG_FALSE);

  if(config_setting_type(s) != CONFIG_TYPE_STRING)
    return(CONFIG_FALSE);

  *value = config_setting_get_string(s);

  return(CONFIG_TRUE);
}

/* ------------------------------------------------------------------------- */

int config_lookup_int(const config_t *config, const char *path,
                      int *value)
{
  const config_setting_t *s = config_lookup(config, path);
  if(! s)
    return(CONFIG_FALSE);

  return(__config_setting_get_int(s, value));
}

/* ------------------------------------------------------------------------- */

int config_lookup_int64(const config_t *config, const char *path,
                        long long *value)
{
  const config_setting_t *s = config_lookup(config, path);
  if(! s)
    return(CONFIG_FALSE);

  return(__config_setting_get_int64(s, value));
}

/* ------------------------------------------------------------------------- */

int config_lookup_float(const config_t *config, const char *path,
                        double *value)
{
  const config_setting_t *s = config_lookup(config, path);
  if(! s)
    return(CONFIG_FALSE);

  return(__config_setting_get_float(s, value));
}

/* ------------------------------------------------------------------------- */

int config_lookup_bool(const config_t *config, const char *path, int *value)
{
  const config_setting_t *s = config_lookup(config, path);
  if(! s)
    return(CONFIG_FALSE);

  if(config_setting_type(s) != CONFIG_TYPE_BOOL)
    return(CONFIG_FALSE);

  *value = config_setting_get_bool(s);
  return(CONFIG_TRUE);
}

/* ------------------------------------------------------------------------- */

int config_setting_get_int_elem(const config_setting_t *vector, int idx)
{
  const config_setting_t *element = config_setting_get_elem(vector, idx);

  return(element ? config_setting_get_int(element) : 0);
}

/* ------------------------------------------------------------------------- */

config_setting_t *config_setting_set_int_elem(config_setting_t *vector,
                                              int idx, int value)
{
  config_setting_t *element = NULL;

  if((vector->type != CONFIG_TYPE_ARRAY) && (vector->type != CONFIG_TYPE_LIST))
    return(NULL);

  if(idx < 0)
  {
    if(! __config_vector_checktype(vector, CONFIG_TYPE_INT))
      return(NULL);

    element = config_setting_create(vector, NULL, CONFIG_TYPE_INT);
  }
  else
  {
    element = config_setting_get_elem(vector, idx);

    if(! element)
      return(NULL);
  }

  if(! config_setting_set_int(element, value))
    return(NULL);

  return(element);
}

/* ------------------------------------------------------------------------- */

long long config_setting_get_int64_elem(const config_setting_t *vector,
                                        int idx)
{
  const config_setting_t *element = config_setting_get_elem(vector, idx);

  return(element ? config_setting_get_int64(element) : 0);
}

/* ------------------------------------------------------------------------- */

config_setting_t *config_setting_set_int64_elem(config_setting_t *vector,
                                                int idx, long long value)
{
  config_setting_t *element = NULL;

  if((vector->type != CONFIG_TYPE_ARRAY) && (vector->type != CONFIG_TYPE_LIST))
    return(NULL);

  if(idx < 0)
  {
    if(! __config_vector_checktype(vector, CONFIG_TYPE_INT64))
      return(NULL);

    element = config_setting_create(vector, NULL, CONFIG_TYPE_INT64);
  }
  else
  {
    element = config_setting_get_elem(vector, idx);

    if(! element)
      return(NULL);
  }

  if(! config_setting_set_int64(element, value))
    return(NULL);

  return(element);
}

/* ------------------------------------------------------------------------- */

double config_setting_get_float_elem(const config_setting_t *vector, int idx)
{
  config_setting_t *element = config_setting_get_elem(vector, idx);

  return(element ? config_setting_get_float(element) : 0.0);
}

/* ------------------------------------------------------------------------- */

config_setting_t *config_setting_set_float_elem(config_setting_t *vector,
                                                int idx, double value)
{
  config_setting_t *element = NULL;

  if((vector->type != CONFIG_TYPE_ARRAY) && (vector->type != CONFIG_TYPE_LIST))
    return(NULL);

  if(idx < 0)
  {
    if(! __config_vector_checktype(vector, CONFIG_TYPE_FLOAT))
      return(NULL);

    element = config_setting_create(vector, NULL, CONFIG_TYPE_FLOAT);
  }
  else
    element = config_setting_get_elem(vector, idx);

  if(! element)
    return(NULL);

  if(! config_setting_set_float(element, value))
    return(NULL);

  return(element);
}

/* ------------------------------------------------------------------------- */

int config_setting_get_bool_elem(const config_setting_t *vector, int idx)
{
  config_setting_t *element = config_setting_get_elem(vector, idx);

  if(! element)
    return(CONFIG_FALSE);

  if(element->type != CONFIG_TYPE_BOOL)
    return(CONFIG_FALSE);

  return(element->value.ival);
}

/* ------------------------------------------------------------------------- */

config_setting_t *config_setting_set_bool_elem(config_setting_t *vector,
                                               int idx, int value)
{
  config_setting_t *element = NULL;

  if((vector->type != CONFIG_TYPE_ARRAY) && (vector->type != CONFIG_TYPE_LIST))
    return(NULL);

  if(idx < 0)
  {
    if(! __config_vector_checktype(vector, CONFIG_TYPE_BOOL))
      return(NULL);

    element = config_setting_create(vector, NULL, CONFIG_TYPE_BOOL);
  }
  else
    element = config_setting_get_elem(vector, idx);

  if(! element)
    return(NULL);

  if(! config_setting_set_bool(element, value))
    return(NULL);

  return(element);
}

/* ------------------------------------------------------------------------- */

const char *config_setting_get_string_elem(const config_setting_t *vector,
                                           int idx)
{
  config_setting_t *element = config_setting_get_elem(vector, idx);

  if(! element)
    return(NULL);

  if(element->type != CONFIG_TYPE_STRING)
    return(NULL);

  return(element->value.sval);
}

/* ------------------------------------------------------------------------- */

config_setting_t *config_setting_set_string_elem(config_setting_t *vector,
                                                 int idx, const char *value)
{
  config_setting_t *element = NULL;

  if((vector->type != CONFIG_TYPE_ARRAY) && (vector->type != CONFIG_TYPE_LIST))
    return(NULL);

  if(idx < 0)
  {
    if(! __config_vector_checktype(vector, CONFIG_TYPE_STRING))
      return(NULL);

    element = config_setting_create(vector, NULL, CONFIG_TYPE_STRING);
  }
  else
    element = config_setting_get_elem(vector, idx);

  if(! element)
    return(NULL);

  if(! config_setting_set_string(element, value))
    return(NULL);

  return(element);
}

/* ------------------------------------------------------------------------- */

config_setting_t *config_setting_get_elem(const config_setting_t *vector,
                                          unsigned int idx)
{
  config_list_t *list = vector->value.list;

  if(((vector->type != CONFIG_TYPE_ARRAY)
      && (vector->type != CONFIG_TYPE_LIST)
      && (vector->type != CONFIG_TYPE_GROUP)) || ! list)
    return(NULL);

  if(idx >= list->length)
    return(NULL);

  return(list->elements[idx]);
}

/* ------------------------------------------------------------------------- */

config_setting_t *config_setting_get_member(const config_setting_t *setting,
                                            const char *name)
{
  if(setting->type != CONFIG_TYPE_GROUP)
    return(NULL);

  return(__config_list_search(setting->value.list, name, NULL));
}

/* ------------------------------------------------------------------------- */

void config_set_destructor(config_t *config, void (*destructor)(void *))
{
  config->destructor = destructor;
}

/* ------------------------------------------------------------------------- */

void config_set_include_dir(config_t *config, const char *include_dir)
{
  _delete(config->include_dir);
  config->include_dir = strdup(include_dir);
}

/* ------------------------------------------------------------------------- */

int config_setting_length(const config_setting_t *setting)
{
  if((setting->type != CONFIG_TYPE_GROUP)
     && (setting->type != CONFIG_TYPE_ARRAY)
     && (setting->type != CONFIG_TYPE_LIST))
    return(0);

  if(! setting->value.list)
    return(0);

  return(setting->value.list->length);
}

/* ------------------------------------------------------------------------- */

void config_setting_set_hook(config_setting_t *setting, void *hook)
{
  setting->hook = hook;
}

/* ------------------------------------------------------------------------- */

config_setting_t *config_setting_add(config_setting_t *parent,
                                     const char *name, int type)
{
  if((type < CONFIG_TYPE_NONE) || (type > CONFIG_TYPE_LIST))
    return(NULL);

  if(! parent)
    return(NULL);

  if((parent->type == CONFIG_TYPE_ARRAY) || (parent->type == CONFIG_TYPE_LIST))
    name = NULL;

  if(name)
  {
    if(! __config_validate_name(name))
      return(NULL);
  }

#if 0
  /* https://github.com/HerculesWS/Hercules/pull/136#discussion_r6363319
   * With this code, accidental duplicate keys would cause the file parsing to fail
   * (would cause several issues during runtime on file reloads), while libconfig's code
   * has no problems with duplicate members so it was ducked out -- TODO: looking now though
   * I'd think it could be useful to have it display a warning or error message when finding
   * duplicate keys instead of silently moving on. [Ind]
   */
  if(config_setting_get_member(parent, name) != NULL)
    return(NULL); /* already exists */
#endif

  return(config_setting_create(parent, name, type));
}

/* ------------------------------------------------------------------------- */

int config_setting_remove(config_setting_t *parent, const char *name)
{
  unsigned int idx;
  config_setting_t *setting;

  if(! parent)
    return(CONFIG_FALSE);

  if(parent->type != CONFIG_TYPE_GROUP)
    return(CONFIG_FALSE);

  if(! (setting = __config_list_search(parent->value.list, name, &idx)))
    return(CONFIG_FALSE);

  __config_list_remove(parent->value.list, idx);
  __config_setting_destroy(setting);

  return(CONFIG_TRUE);
}

/* ------------------------------------------------------------------------- */

int config_setting_remove_elem(config_setting_t *parent, unsigned int idx)
{
  config_list_t *list;
  config_setting_t *removed = NULL;

  if(! parent)
    return(CONFIG_FALSE);

  list = parent->value.list;

  if(((parent->type != CONFIG_TYPE_ARRAY)
      && (parent->type != CONFIG_TYPE_LIST)
      && (parent->type != CONFIG_TYPE_GROUP)) || ! list)
    return(CONFIG_FALSE);

  if(idx >= list->length)
    return(CONFIG_FALSE);

  removed = __config_list_remove(list, idx);
  __config_setting_destroy(removed);

  return(CONFIG_TRUE);
}

/* ------------------------------------------------------------------------- */

int config_setting_index(const config_setting_t *setting)
{
  config_setting_t **found = NULL;
  config_list_t *list;
  int i;

  if(! setting->parent)
    return(-1);

  list = setting->parent->value.list;

  for(i = 0, found = list->elements; i < (int)list->length; ++i, ++found)
  {
    if(*found == setting)
      return(i);
  }

  return(-1);
}

/* ------------------------------------------------------------------------- */