Annotation of embedaddon/strongswan/src/starter/parser/conf_parser.c, revision 1.1

1.1     ! misho       1: /*
        !             2:  * Copyright (C) 2013-2014 Tobias Brunner
        !             3:  * HSR Hochschule fuer Technik Rapperswil
        !             4:  *
        !             5:  * This program is free software; you can redistribute it and/or modify it
        !             6:  * under the terms of the GNU General Public License as published by the
        !             7:  * Free Software Foundation; either version 2 of the License, or (at your
        !             8:  * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
        !             9:  *
        !            10:  * This program is distributed in the hope that it will be useful, but
        !            11:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
        !            12:  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
        !            13:  * for more details.
        !            14:  */
        !            15: 
        !            16: #include "conf_parser.h"
        !            17: 
        !            18: #include <collections/array.h>
        !            19: #include <collections/hashtable.h>
        !            20: 
        !            21: /**
        !            22:  * Provided by the generated parser
        !            23:  */
        !            24: bool conf_parser_parse_file(conf_parser_t *this, char *file);
        !            25: 
        !            26: typedef struct private_conf_parser_t private_conf_parser_t;
        !            27: typedef struct section_t section_t;
        !            28: 
        !            29: /**
        !            30:  * Private data
        !            31:  */
        !            32: struct private_conf_parser_t {
        !            33: 
        !            34:        /**
        !            35:         * Public interface
        !            36:         */
        !            37:        conf_parser_t public;
        !            38: 
        !            39:        /**
        !            40:         * Path to config file
        !            41:         */
        !            42:        char *file;
        !            43: 
        !            44:        /**
        !            45:         * TRUE while parsing the config file
        !            46:         */
        !            47:        bool parsing;
        !            48: 
        !            49:        /**
        !            50:         * Hashtable for ca sections, as section_t
        !            51:         */
        !            52:        hashtable_t *cas;
        !            53: 
        !            54:        /**
        !            55:         * Hashtable for conn sections, as section_t
        !            56:         */
        !            57:        hashtable_t *conns;
        !            58: 
        !            59:        /**
        !            60:         * Array to keep track of the order of conn sections, as section_t
        !            61:         */
        !            62:        array_t *conns_order;
        !            63: 
        !            64:        /**
        !            65:         * config setup section
        !            66:         */
        !            67:        section_t *config_setup;
        !            68: 
        !            69:        /**
        !            70:         * Pointer to the current section (the one added last) during parsing
        !            71:         */
        !            72:        section_t *current_section;
        !            73: 
        !            74:        /**
        !            75:         * Refcount for this parser instance (also used by dictionaries)
        !            76:         */
        !            77:        refcount_t ref;
        !            78: };
        !            79: 
        !            80: typedef struct {
        !            81:        /** Key of the setting */
        !            82:        char *key;
        !            83:        /** Value of the setting */
        !            84:        char *value;
        !            85: } setting_t;
        !            86: 
        !            87: int setting_find(const void *a, const void *b)
        !            88: {
        !            89:        const char *key = a;
        !            90:        const setting_t *setting = b;
        !            91:        return strcmp(key, setting->key);
        !            92: }
        !            93: 
        !            94: int setting_sort(const void *a, const void *b, void *user)
        !            95: {
        !            96:        const setting_t *sa = a, *sb = b;
        !            97:        return strcmp(sa->key, sb->key);
        !            98: }
        !            99: 
        !           100: static void setting_destroy(setting_t *this)
        !           101: {
        !           102:        free(this->key);
        !           103:        free(this->value);
        !           104:        free(this);
        !           105: }
        !           106: 
        !           107: struct section_t {
        !           108:        /** Name of the section */
        !           109:        char *name;
        !           110:        /** Sorted array of settings, as setting_t */
        !           111:        array_t *settings;
        !           112:        /** Array of also= settings (in reversed order, i.e. most important to least
        !           113:         * important), as setting_t */
        !           114:        array_t *also;
        !           115:        /** Array of linearized parent objects derived from also= settings, in their
        !           116:         * order of importance (most to least, i.e. %default) as section_t
        !           117:         * NULL if not yet determined */
        !           118:        array_t *parents;
        !           119: };
        !           120: 
        !           121: static section_t *section_create(char *name)
        !           122: {
        !           123:        section_t *this;
        !           124: 
        !           125:        INIT(this,
        !           126:                .name = name,
        !           127:        );
        !           128:        return this;
        !           129: }
        !           130: 
        !           131: static void section_destroy(section_t *this)
        !           132: {
        !           133:        array_destroy_function(this->settings, (void*)setting_destroy, NULL);
        !           134:        array_destroy_function(this->also, (void*)setting_destroy, NULL);
        !           135:        array_destroy(this->parents);
        !           136:        free(this->name);
        !           137:        free(this);
        !           138: }
        !           139: 
        !           140: typedef struct {
        !           141:        /** Public interface */
        !           142:        dictionary_t public;
        !           143:        /** Parser object */
        !           144:        private_conf_parser_t *parser;
        !           145:        /** Section object */
        !           146:        section_t *section;
        !           147: } section_dictionary_t;
        !           148: 
        !           149: typedef struct {
        !           150:        /** Public interface */
        !           151:        enumerator_t public;
        !           152:        /** Current settings enumerator */
        !           153:        enumerator_t *settings;
        !           154:        /** Enumerator into parent list */
        !           155:        enumerator_t *parents;
        !           156:        /** Hashtable to keep track of already enumerated settings */
        !           157:        hashtable_t *seen;
        !           158: } dictionary_enumerator_t;
        !           159: 
        !           160: METHOD(enumerator_t, dictionary_enumerate, bool,
        !           161:        dictionary_enumerator_t *this, va_list args)
        !           162: {
        !           163:        setting_t *setting;
        !           164:        section_t *parent;
        !           165:        char **key, **value;
        !           166: 
        !           167:        VA_ARGS_VGET(args, key, value);
        !           168: 
        !           169:        while (TRUE)
        !           170:        {
        !           171:                if (this->settings &&
        !           172:                        this->settings->enumerate(this->settings, &setting))
        !           173:                {
        !           174:                        if (this->seen->get(this->seen, setting->key))
        !           175:                        {
        !           176:                                continue;
        !           177:                        }
        !           178:                        this->seen->put(this->seen, setting->key, setting->key);
        !           179:                        if (!setting->value)
        !           180:                        {
        !           181:                                continue;
        !           182:                        }
        !           183:                        if (key)
        !           184:                        {
        !           185:                                *key = setting->key;
        !           186:                        }
        !           187:                        if (value)
        !           188:                        {
        !           189:                                *value = setting->value;
        !           190:                        }
        !           191:                        return TRUE;
        !           192:                }
        !           193:                DESTROY_IF(this->settings);
        !           194:                this->settings = NULL;
        !           195:                if (this->parents &&
        !           196:                        this->parents->enumerate(this->parents, &parent))
        !           197:                {
        !           198:                        if (parent->settings)
        !           199:                        {
        !           200:                                this->settings = array_create_enumerator(parent->settings);
        !           201:                        }
        !           202:                        continue;
        !           203:                }
        !           204:                DESTROY_IF(this->parents);
        !           205:                this->parents = NULL;
        !           206:                break;
        !           207:        }
        !           208:        return FALSE;
        !           209: }
        !           210: 
        !           211: METHOD(enumerator_t, dictionary_enumerator_destroy, void,
        !           212:        dictionary_enumerator_t *this)
        !           213: {
        !           214:        DESTROY_IF(this->settings);
        !           215:        DESTROY_IF(this->parents);
        !           216:        this->seen->destroy(this->seen);
        !           217:        free(this);
        !           218: }
        !           219: 
        !           220: METHOD(dictionary_t, dictionary_create_enumerator, enumerator_t*,
        !           221:        section_dictionary_t *this)
        !           222: {
        !           223:        dictionary_enumerator_t *enumerator;
        !           224: 
        !           225:        INIT(enumerator,
        !           226:                .public = {
        !           227:                        .enumerate = enumerator_enumerate_default,
        !           228:                        .venumerate = _dictionary_enumerate,
        !           229:                        .destroy = _dictionary_enumerator_destroy,
        !           230:                },
        !           231:                .seen = hashtable_create(hashtable_hash_str, hashtable_equals_str, 8),
        !           232:        );
        !           233:        if (this->section->settings)
        !           234:        {
        !           235:                enumerator->settings = array_create_enumerator(this->section->settings);
        !           236:        }
        !           237:        if (this->section->parents)
        !           238:        {
        !           239:                enumerator->parents = array_create_enumerator(this->section->parents);
        !           240:        }
        !           241:        return &enumerator->public;
        !           242: }
        !           243: 
        !           244: METHOD(dictionary_t, dictionary_get, void*,
        !           245:        section_dictionary_t *this, const void *key)
        !           246: {
        !           247:        enumerator_t *parents;
        !           248:        section_t *section;
        !           249:        setting_t *setting;
        !           250:        char *value = NULL;
        !           251: 
        !           252:        section = this->section;
        !           253:        if (array_bsearch(section->settings, key, setting_find, &setting) != -1)
        !           254:        {
        !           255:                return setting->value;
        !           256:        }
        !           257:        parents = array_create_enumerator(section->parents);
        !           258:        while (parents->enumerate(parents, &section))
        !           259:        {
        !           260:                if (array_bsearch(section->settings, key, setting_find, &setting) != -1)
        !           261:                {
        !           262:                        value = setting->value;
        !           263:                        break;
        !           264:                }
        !           265:        }
        !           266:        parents->destroy(parents);
        !           267:        return value;
        !           268: }
        !           269: 
        !           270: METHOD(dictionary_t, dictionary_destroy, void,
        !           271:        section_dictionary_t *this)
        !           272: {
        !           273:        this->parser->public.destroy(&this->parser->public);
        !           274:        free(this);
        !           275: }
        !           276: 
        !           277: static dictionary_t *section_dictionary_create(private_conf_parser_t *parser,
        !           278:                                                                                           section_t *section)
        !           279: {
        !           280:        section_dictionary_t *this;
        !           281: 
        !           282:        INIT(this,
        !           283:                .public = {
        !           284:                        .create_enumerator = _dictionary_create_enumerator,
        !           285:                        .get = _dictionary_get,
        !           286:                        .destroy = _dictionary_destroy,
        !           287:                },
        !           288:                .parser = parser,
        !           289:                .section = section,
        !           290:        );
        !           291: 
        !           292:        ref_get(&parser->ref);
        !           293: 
        !           294:        return &this->public;
        !           295: }
        !           296: 
        !           297: CALLBACK(conn_filter, bool,
        !           298:        void *unused, enumerator_t *orig, va_list args)
        !           299: {
        !           300:        section_t *section;
        !           301:        char **name;
        !           302: 
        !           303:        VA_ARGS_VGET(args, name);
        !           304: 
        !           305:        while (orig->enumerate(orig, &section))
        !           306:        {
        !           307:                if (!streq(section->name, "%default"))
        !           308:                {
        !           309:                        *name = section->name;
        !           310:                        return TRUE;
        !           311:                }
        !           312:        }
        !           313:        return FALSE;
        !           314: }
        !           315: 
        !           316: CALLBACK(ca_filter, bool,
        !           317:        void *unused, enumerator_t *orig, va_list args)
        !           318: {
        !           319:        void *key;
        !           320:        section_t *section;
        !           321:        char **name;
        !           322: 
        !           323:        VA_ARGS_VGET(args, name);
        !           324: 
        !           325:        while (orig->enumerate(orig, &key, &section))
        !           326:        {
        !           327:                if (!streq(section->name, "%default"))
        !           328:                {
        !           329:                        *name = section->name;
        !           330:                        return TRUE;
        !           331:                }
        !           332:        }
        !           333:        return FALSE;
        !           334: }
        !           335: 
        !           336: METHOD(conf_parser_t, get_sections, enumerator_t*,
        !           337:        private_conf_parser_t *this, conf_parser_section_t type)
        !           338: {
        !           339:        switch (type)
        !           340:        {
        !           341:                case CONF_PARSER_CONN:
        !           342:                        return enumerator_create_filter(
        !           343:                                                                        array_create_enumerator(this->conns_order),
        !           344:                                                                        conn_filter, NULL, NULL);
        !           345:                case CONF_PARSER_CA:
        !           346:                        return enumerator_create_filter(
        !           347:                                                                        this->cas->create_enumerator(this->cas),
        !           348:                                                                        ca_filter, NULL, NULL);
        !           349:                case CONF_PARSER_CONFIG_SETUP:
        !           350:                default:
        !           351:                        return enumerator_create_empty();
        !           352:        }
        !           353: }
        !           354: 
        !           355: METHOD(conf_parser_t, get_section, dictionary_t*,
        !           356:        private_conf_parser_t *this, conf_parser_section_t type, char *name)
        !           357: {
        !           358:        section_t *section = NULL;
        !           359: 
        !           360:        if (name && streq(name, "%default"))
        !           361:        {
        !           362:                return NULL;
        !           363:        }
        !           364:        switch (type)
        !           365:        {
        !           366:                case CONF_PARSER_CONFIG_SETUP:
        !           367:                        section = this->config_setup;
        !           368:                        break;
        !           369:                case CONF_PARSER_CONN:
        !           370:                        section = this->conns->get(this->conns, name);
        !           371:                        break;
        !           372:                case CONF_PARSER_CA:
        !           373:                        section = this->cas->get(this->cas, name);
        !           374:                        break;
        !           375:                default:
        !           376:                        break;
        !           377:        }
        !           378:        return section ? section_dictionary_create(this, section) : NULL;
        !           379: }
        !           380: 
        !           381: METHOD(conf_parser_t, add_section, bool,
        !           382:        private_conf_parser_t *this, conf_parser_section_t type, char *name)
        !           383: {
        !           384:        hashtable_t *sections = this->conns;
        !           385:        array_t *order = this->conns_order;
        !           386:        section_t *section = NULL;
        !           387:        bool exists = FALSE;
        !           388: 
        !           389:        if (!this->parsing)
        !           390:        {
        !           391:                free(name);
        !           392:                return exists;
        !           393:        }
        !           394:        switch (type)
        !           395:        {
        !           396:                case CONF_PARSER_CONFIG_SETUP:
        !           397:                        section = this->config_setup;
        !           398:                        /* we don't expect a name, but just in case */
        !           399:                        free(name);
        !           400:                        break;
        !           401:                case CONF_PARSER_CA:
        !           402:                        sections = this->cas;
        !           403:                        order = NULL;
        !           404:                        /* fall-through */
        !           405:                case CONF_PARSER_CONN:
        !           406:                        section = sections->get(sections, name);
        !           407:                        if (!section)
        !           408:                        {
        !           409:                                section = section_create(name);
        !           410:                                sections->put(sections, name, section);
        !           411:                                if (order)
        !           412:                                {
        !           413:                                        array_insert(order, ARRAY_TAIL, section);
        !           414:                                }
        !           415:                        }
        !           416:                        else
        !           417:                        {
        !           418:                                exists = TRUE;
        !           419:                                free(name);
        !           420:                        }
        !           421:                        break;
        !           422: 
        !           423:        }
        !           424:        this->current_section = section;
        !           425:        return exists;
        !           426: }
        !           427: 
        !           428: METHOD(conf_parser_t, add_setting, void,
        !           429:        private_conf_parser_t *this, char *key, char *value)
        !           430: {
        !           431:        section_t *section = this->current_section;
        !           432:        setting_t *setting;
        !           433: 
        !           434:        if (!this->parsing || !this->current_section)
        !           435:        {
        !           436:                free(key);
        !           437:                free(value);
        !           438:                return;
        !           439:        }
        !           440:        if (streq(key, "also"))
        !           441:        {
        !           442:                if (!value || !strlen(value) || streq(value, "%default"))
        !           443:                {       /* we require a name, but all sections inherit from %default */
        !           444:                        free(key);
        !           445:                        free(value);
        !           446:                        return;
        !           447:                }
        !           448:                INIT(setting,
        !           449:                        .key = key,
        !           450:                        .value = value,
        !           451:                );
        !           452:                array_insert_create(&section->also, ARRAY_HEAD, setting);
        !           453:                return;
        !           454:        }
        !           455:        if (array_bsearch(section->settings, key, setting_find, &setting) == -1)
        !           456:        {
        !           457:                INIT(setting,
        !           458:                        .key = key,
        !           459:                        .value = value,
        !           460:                );
        !           461:                array_insert_create(&section->settings, ARRAY_TAIL, setting);
        !           462:                array_sort(section->settings, setting_sort, NULL);
        !           463:        }
        !           464:        else
        !           465:        {
        !           466:                free(setting->value);
        !           467:                setting->value = value;
        !           468:                free(key);
        !           469:        }
        !           470: }
        !           471: 
        !           472: /**
        !           473:  * Check if the given section is contained in the given array.  The search
        !           474:  * starts at the given index.
        !           475:  */
        !           476: static bool is_contained_in(array_t *arr, section_t *section)
        !           477: {
        !           478:        section_t *current;
        !           479:        int i;
        !           480: 
        !           481:        for (i = 0; i < array_count(arr); i++)
        !           482:        {
        !           483:                array_get(arr, i, &current);
        !           484:                if (streq(section->name, current->name))
        !           485:                {
        !           486:                        return TRUE;
        !           487:                }
        !           488:        }
        !           489:        return FALSE;
        !           490: }
        !           491: 
        !           492: /**
        !           493:  * This algorithm to linearize sections uses a bottom-first depth-first
        !           494:  * semantic, with an additional elimination step that removes all but the
        !           495:  * last occurrence of each section.
        !           496:  *
        !           497:  * Consider this configuration:
        !           498:  *
        !           499:  *   conn A
        !           500:  *   conn B
        !           501:  *     also=A
        !           502:  *   conn C
        !           503:  *     also=A
        !           504:  *   conn D
        !           505:  *     also=C
        !           506:  *     also=B
        !           507:  *
        !           508:  * The linearization would yield D B A C A, which gets reduced to D B C A.
        !           509:  *
        !           510:  * Ambiguous configurations are handled pragmatically.
        !           511:  *
        !           512:  * Consider the following configuration:
        !           513:  *
        !           514:  *   conn A
        !           515:  *   conn B
        !           516:  *   conn C
        !           517:  *     also=A
        !           518:  *     also=B
        !           519:  *   conn D
        !           520:  *     also=B
        !           521:  *     also=A
        !           522:  *   conn E
        !           523:  *     also=C
        !           524:  *     also=D
        !           525:  *
        !           526:  * It is ambiguous because D and C include the same two sections but in
        !           527:  * a different order.
        !           528:  *
        !           529:  * The linearization would yield E D A B C B A which gets reduced to E D C B A.
        !           530:  */
        !           531: static bool resolve_also_single(hashtable_t *sections,
        !           532:                                                        section_t *section, section_t *def, array_t *stack)
        !           533: {
        !           534:        enumerator_t *enumerator;
        !           535:        array_t *parents;
        !           536:        section_t *parent, *grandparent;
        !           537:        setting_t *also;
        !           538:        bool success = TRUE;
        !           539:        int i;
        !           540: 
        !           541:        array_insert(stack, ARRAY_HEAD, section);
        !           542:        parents = array_create(0, 0);
        !           543:        enumerator = array_create_enumerator(section->also);
        !           544:        while (enumerator->enumerate(enumerator, &also))
        !           545:        {
        !           546:                parent = sections->get(sections, also->value);
        !           547:                if (!parent || is_contained_in(stack, parent))
        !           548:                {
        !           549:                        if (!parent)
        !           550:                        {
        !           551:                                DBG1(DBG_CFG, "section '%s' referenced in section '%s' not "
        !           552:                                         "found", also->value, section->name);
        !           553:                        }
        !           554:                        else
        !           555:                        {
        !           556:                                DBG1(DBG_CFG, "section '%s' referenced in section '%s' causes "
        !           557:                                         "a loop", parent->name, section->name);
        !           558:                        }
        !           559:                        array_remove_at(section->also, enumerator);
        !           560:                        setting_destroy(also);
        !           561:                        success = FALSE;
        !           562:                        continue;
        !           563:                }
        !           564:                if (!parent->parents)
        !           565:                {
        !           566:                        if (!resolve_also_single(sections, parent, def, stack))
        !           567:                        {
        !           568:                                success = FALSE;
        !           569:                                continue;
        !           570:                        }
        !           571:                }
        !           572:                /* add the grandparents and the parent to the list */
        !           573:                array_insert(parents, ARRAY_TAIL, parent);
        !           574:                for (i = 0; i < array_count(parent->parents); i++)
        !           575:                {
        !           576:                        array_get(parent->parents, i, &grandparent);
        !           577:                        array_insert(parents, ARRAY_TAIL, grandparent);
        !           578:                }
        !           579:        }
        !           580:        enumerator->destroy(enumerator);
        !           581:        array_remove(stack, ARRAY_HEAD, NULL);
        !           582: 
        !           583:        if (success && def && !array_count(parents))
        !           584:        {
        !           585:                array_insert(parents, ARRAY_TAIL, def);
        !           586:        }
        !           587: 
        !           588:        while (success && array_remove(parents, ARRAY_HEAD, &parent))
        !           589:        {
        !           590:                if (!is_contained_in(parents, parent))
        !           591:                {       /* last occurrence of this section */
        !           592:                        array_insert_create(&section->parents, ARRAY_TAIL, parent);
        !           593:                }
        !           594:        }
        !           595:        array_destroy(parents);
        !           596:        return success;
        !           597: }
        !           598: 
        !           599: /**
        !           600:  * Resolve also= statements. The functions returns TRUE if everything is fine,
        !           601:  * or FALSE if either a referenced section does not exist, or if the section
        !           602:  * inheritance can't be determined properly (e.g. if there are loops or if a
        !           603:  * section inherits from multiple sections - perhaps over several levels - in
        !           604:  * an ambiguous way).
        !           605:  */
        !           606: static bool resolve_also(hashtable_t *sections)
        !           607: {
        !           608:        enumerator_t *enumerator;
        !           609:        section_t *def, *section;
        !           610:        array_t *stack;
        !           611:        bool success = TRUE;
        !           612: 
        !           613:        stack = array_create(0, 0);
        !           614: 
        !           615:        def = sections->get(sections, "%default");
        !           616:        if (def)
        !           617:        {       /* the default section is the only one with an empty parents list */
        !           618:                def->parents = array_create(0, 0);
        !           619:        }
        !           620:        enumerator = sections->create_enumerator(sections);
        !           621:        while (enumerator->enumerate(enumerator, NULL, &section))
        !           622:        {
        !           623:                if (section->parents)
        !           624:                {       /* already determined */
        !           625:                        continue;
        !           626:                }
        !           627:                success = resolve_also_single(sections, section, def, stack) && success;
        !           628:        }
        !           629:        enumerator->destroy(enumerator);
        !           630:        array_destroy(stack);
        !           631:        return success;
        !           632: }
        !           633: 
        !           634: METHOD(conf_parser_t, parse, bool,
        !           635:        private_conf_parser_t *this)
        !           636: {
        !           637:        bool success;
        !           638: 
        !           639:        if (!this->file)
        !           640:        {       /* no file, lets assume this is OK */
        !           641:                return TRUE;
        !           642:        }
        !           643:        this->parsing = TRUE;
        !           644:        success = conf_parser_parse_file(&this->public, this->file);
        !           645:        this->parsing = FALSE;
        !           646:        return success && resolve_also(this->conns) && resolve_also(this->cas);
        !           647: }
        !           648: 
        !           649: METHOD(conf_parser_t, destroy, void,
        !           650:        private_conf_parser_t *this)
        !           651: {
        !           652:        if (ref_put(&this->ref))
        !           653:        {
        !           654:                this->cas->destroy_function(this->cas, (void*)section_destroy);
        !           655:                this->conns->destroy_function(this->conns, (void*)section_destroy);
        !           656:                section_destroy(this->config_setup);
        !           657:                array_destroy(this->conns_order);
        !           658:                free(this->file);
        !           659:                free(this);
        !           660:        }
        !           661: }
        !           662: 
        !           663: /*
        !           664:  * Described in header
        !           665:  */
        !           666: conf_parser_t *conf_parser_create(const char *file)
        !           667: {
        !           668:        private_conf_parser_t *this;
        !           669: 
        !           670:        INIT(this,
        !           671:                .public = {
        !           672:                        .parse = _parse,
        !           673:                        .get_sections = _get_sections,
        !           674:                        .get_section = _get_section,
        !           675:                        .add_section = _add_section,
        !           676:                        .add_setting = _add_setting,
        !           677:                        .destroy = _destroy,
        !           678:                },
        !           679:                .file = strdupnull(file),
        !           680:                .cas = hashtable_create(hashtable_hash_str,
        !           681:                                                                hashtable_equals_str, 8),
        !           682:                .conns = hashtable_create(hashtable_hash_str,
        !           683:                                                                  hashtable_equals_str, 8),
        !           684:                .conns_order = array_create(0, 0),
        !           685:                .config_setup = section_create(NULL),
        !           686:                .ref = 1,
        !           687:        );
        !           688: 
        !           689:        return &this->public;
        !           690: }

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>