Remove trace level log message.

Remove trace level log message.

/*****************************************************************************
 * Licensed to Qualys, Inc. (QUALYS) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * QUALYS licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ****************************************************************************/

/**
 * @file
 * @brief IronBee --- Configuration File Parser
 *
 * @author Brian Rectanus <brectanus@qualys.com>
 */

#include "ironbee_config_auto.h"

#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <libgen.h>
#include <limits.h>
#include <math.h>
#include <errno.h>

#include <ironbee/engine.h>
#include <ironbee/util.h>
#include <ironbee/config.h>
#include <ironbee/mpool.h>
#include <ironbee/path.h>

#include "config-parser.h"
#include "config_private.h"

/* Caused by Ragel */
#ifdef __clang__
#pragma clang diagnostic ignored "-Wunreachable-code"
#endif

/**
 * Variables used by the finite state machine per-call.
 * Values here do not have to persist across calls to
 * ib_cfgparser_ragel_parse_chunk().
 */
typedef struct {
    const char *p;     /**< Pointer to the chunk being parsed. */
    const char *pe;    /**< Pointer past the end of p (p+length(p)). */
    const char *eof;   /**< eof==p==pe on last chunk. NULL otherwise. */
} fsm_vars_t;

/**
 * Append @a c to the internal buffer of @a cp.
 * @param[in] cp Configuration parser.
 * @param[in] c Char to append.
 * @returns
 * - IB_OK
 * - IB_EALLOC if there is no space left in the buffer.
 */
static ib_status_t cpbuf_append(ib_cfgparser_t *cp, char c)
{
    assert(cp != NULL);
    assert(cp->buffer != NULL);

    /* Protect against run-away token aggregation. 1k should be enough? */
    if (cp->buffer->len >= 1024) {
        ib_cfg_log_error(
            cp,
            "Token size limit exceeded. Will not grow past %zu characters.",
            cp->buffer->len);
        ib_cfg_log_trace(
            cp,
            "Current buffer is [[[[%.*s]]]]",
            (int) cp->buffer->len,
            (char *)cp->buffer->data);
        return IB_EALLOC;
    }

    return ib_vector_append(cp->buffer, &c, 1);
}

/**
 * Clear the buffer in @a cp.
 * @param[in] cp Configuration parser.
 */
static void cpbuf_clear(ib_cfgparser_t *cp) {
    assert(cp != NULL);
    assert(cp->buffer != NULL);
    ib_status_t rc;

    rc = ib_vector_truncate(cp->buffer, 0);
    if (rc != IB_OK) {
        ib_cfg_log_error(
            cp,
            "Failed to truncate token buffer: %s",
            ib_status_to_string(rc));
    }
}

/**
 * Using the given mp, strdup the buffer in @a cp consiering quotes.
 *
 * If the buffer starts and ends with double quotes, remove them.
 *
 * @param[in] cp The configuration parser
 * @param[in,out] mp Pool to copy out of.
 *
 * @return a buffer allocated from the temp_mp memory pool
 *         available in ib_cfgparser_ragel_parse_chunk. This buffer may be
 *         larger than the string stored in it if the length of the string is
 *         reduced by Javascript unescaping.
 */
static char *qstrdup(ib_cfgparser_t *cp, ib_mpool_t* mp)
{
    const char *start = (const char *)cp->buffer->data;
    const char *end = (const char *)(cp->buffer->data + cp->buffer->len - 1);
    size_t len = cp->buffer->len;

    /* Adjust for quoted value. */
    if ((*start == '"') && (*end == '"') && (start < end)) {
        start++;
        len -= 2;
    }

    return ib_mpool_memdup_to_str(mp, start, len);
}

/**
 * Callback function to handel parsing of parser directives.
 */
typedef ib_status_t(*parse_directive_fn_t)(
    ib_cfgparser_t *cp,
    ib_mpool_t* tmp_mp,
    ib_cfgparser_node_t *node);

/**
 * A table entry mapping a parse directive string to a handler function.
 */
struct parse_directive_entry_t {
    const char *directive;   /**< The directive. Case insensitive. */
    parse_directive_fn_t fn; /**< The handler function. */
};
typedef struct parse_directive_entry_t parse_directive_entry_t;

/**
 * Ensure that the node's file:line has not been encountered before.
 *
 * @param[in] cp Configuration parser.
 * @param[in] node The current parse node.
 * @returns
 * - IB_OK if the directive represented by @a node is new (not a dup).
 * - IB_EINVAL if we detect that @a node's file and line have been seen before.
 * - IB_EALLOC on allocation errors.
 * - Other if there is an internal IronBee error.
 */
static ib_status_t detect_file_loop(
    ib_cfgparser_t *cp,
    ib_cfgparser_node_t *node
) {
    assert(cp != NULL);
    assert(cp->mp != NULL);
    assert(node != NULL);
    assert(node->file != NULL);

    for (ib_cfgparser_node_t *node2 = node->parent;
         node2 != NULL;
         node2 = node2->parent)
    {
        /* If a node is at the same file and line, it is clearly a duplciate. */
        if (node2->type == IB_CFGPARSER_NODE_PARSE_DIRECTIVE
        &&  node->line == node2->line
        &&  strcmp(node->file, node2->file) == 0)
        {
            ib_cfg_log_error(
                cp,
                "File include cycle found at %s:%zu.",
                node->file,
                node->line);

            for (ib_cfgparser_node_t *node3 = node->parent;
                 node3 != NULL;
                 node3 = node3->parent)
            {
                /* Skip nodes that are not parse directives,
                 * such as the root node and file nodes. */
                if (node3->type == IB_CFGPARSER_NODE_PARSE_DIRECTIVE) {
                    ib_cfg_log_error(
                        cp,
                        "\t... %s included from %s:%zu.",
                        node3->directive,
                        node3->file,
                        node3->line);
                }
            }

            return IB_EINVAL;
        }
    }

    return IB_OK;
}

/**
 * Implementation of "Include" and "IncludeIfExists" parse directives.
 * param[in] cp Configuration parser.
 * param[in] mp Memory pool to use.
 * param[in] node The parse node containing the directive.
 *
 * @returns
 * - IB_OK on success.
 * - Any other code causes a general failure to be repoted but the
 *   parse continues.
 */
static ib_status_t include_parse_directive_impl(
    ib_cfgparser_t *cp,
    ib_mpool_t *tmp_mp,
    ib_cfgparser_node_t *node,
    bool if_exists
) {
    assert(cp != NULL);
    assert(cp->mp != NULL);
    assert(node != NULL);
    assert(node->directive != NULL);
    assert(node->params != NULL);
    assert(node->file != NULL);

    struct stat statbuf;
    ib_status_t rc;
    ib_mpool_t *mp = cp->mp;
    int statval;
    char *incfile;
    char *real;
    const char* pval;
    const ib_list_node_t *list_node;

    /* A temporary local value to store the parser state in.
     * We allocate this from local_mp to avoid putting the very large
     * buffer variable in fsm on the stack. */
    ib_cfgparser_fsm_t *fsm;

    ib_cfgparser_node_t *current_node;
    ib_mpool_t *local_mp = NULL;

    rc = ib_mpool_create(&local_mp, "local_mp", tmp_mp);
    if (rc != IB_OK) {
        ib_cfg_log_error(cp, "Failed to create local memory pool.");
        goto cleanup;
    }

    if (ib_list_elements(node->params) != 1) {
        ib_cfg_log_error(
            cp,
            "%s: %zu - Directive %s only takes 1 parameter not %zu.",
            node->file,
            node->line,
            node->directive,
            ib_list_elements(node->params));
        rc = IB_EINVAL;
        goto cleanup;
    }

    /* Grab the first parameter node. */
    list_node = ib_list_first_const(node->params);
    assert(list_node != NULL);

    /* Grab the parameter value. */
    pval = (const char*) ib_list_node_data_const(list_node);
    assert(pval != NULL);

    if_exists = strcasecmp("IncludeIfExists", node->directive) == 0;

    incfile = ib_util_relative_file(mp, node->file, pval);
    if (incfile == NULL) {
        ib_cfg_log_error(cp, "Failed to resolve included file \"%s\": %s",
                         node->file, strerror(errno));
        ib_mpool_release(local_mp);
        rc = IB_ENOENT;
        goto cleanup;
    }

    real = ib_mpool_alloc(local_mp, PATH_MAX);
    if (real == NULL) {
        ib_cfg_log_error(
            cp,
            "Failed to allocate path buffer of size %d",
            PATH_MAX);
        rc = IB_EALLOC;
        goto cleanup;
    }
    real = realpath(incfile, real);
    if (real == NULL) {
        if (!if_exists) {
            ib_cfg_log_error(cp,
                             "Failed to find real path of included file "
                             "(using original \"%s\"): %s",
                             incfile, strerror(errno));
        }
        else {
            ib_cfg_log_warning(
                cp,
                "Failed to normalize path. Using raw include path: %s",
                incfile);
        }

        real = incfile;
    }
    else if (strcmp(real, incfile) != 0) {
        ib_cfg_log_info(
            cp,
            "Real path of included file \"%s\" is \"%s\"",
            incfile,
            real);
    }
    else {
        ib_cfg_log_info(
            cp,
            "Including file \"%s\"",
            incfile);
    }

    rc = detect_file_loop(cp, node);
    if (rc != IB_OK) {
        goto cleanup;
    }

    if (access(incfile, R_OK) != 0) {
        if (if_exists) {
            ib_cfg_log(
                cp, (errno == ENOENT) ? IB_LOG_DEBUG : IB_LOG_NOTICE,
                "Ignoring include file \"%s\": %s",
                incfile,
                strerror(errno));
            rc = IB_OK;
            goto cleanup;
        }

        ib_cfg_log_error(
            cp,
            "Cannot access included file \"%s\": %s",
            incfile,
            strerror(errno));
        rc = IB_ENOENT;
        goto cleanup;
    }

    statval = stat(incfile, &statbuf);
    if (statval != 0) {
        if (if_exists) {
            ib_cfg_log(
                cp, (errno == ENOENT) ? IB_LOG_DEBUG : IB_LOG_NOTICE,
                "Ignoring include file \"%s\": %s",
                incfile,
                strerror(errno));
            rc = IB_OK;
            goto cleanup;
        }

        ib_cfg_log_error(
            cp,
            "Failed to stat include file \"%s\": %s",
            incfile,
            strerror(errno));
        rc = IB_ENOENT;
        goto cleanup;
    }

    if (S_ISREG(statbuf.st_mode) == 0) {
        if (if_exists) {
            ib_cfg_log_info(
                cp,
                "Ignoring include file \"%s\": Not a regular file",
                incfile);
            rc = IB_OK;
            goto cleanup;
        }

        ib_cfg_log_error(
            cp,
	    "Included file \"%s\" is not a regular file",
            incfile);
        rc = IB_ENOENT;
        goto cleanup;
    }

    ib_cfg_log_debug(cp, "Including '%s'", incfile);

    /* Make the given node the current node for the file inclusion. */
    current_node = cp->curr;
    cp->curr = node;

    /* Allocate fsm in the heap as it contains a very large buffer. */
    fsm = ib_mpool_alloc(local_mp, sizeof(*fsm));
    if (fsm == NULL) {
        rc = IB_EALLOC;
        goto cleanup;
    }

    /* Store current fsm. */
    *fsm = cp->fsm;

    /* Initialize new fsm in cp. */
    rc = ib_cfgparser_ragel_init(cp);
    if (rc != IB_OK) {
        ib_cfg_log_error(cp, "Could not initialize new parser.");
        return rc;
    }

    rc = ib_cfgparser_parse_private(cp, incfile, false);
    /* Restore fsm. */
    cp->fsm = *fsm;
    cp->curr = current_node;
    if (rc != IB_OK) {
        ib_cfg_log_error(
            cp,
            "Error parsing included file \"%s\": %s",
	    incfile,
            ib_status_to_string(rc));
        rc = IB_OK;
        goto cleanup;
    }

    ib_cfg_log_debug(cp, "Done processing include file \"%s\"", incfile);

cleanup:
    if (local_mp != NULL) {
        ib_mpool_release(local_mp);
    }
    return rc;
}

//! Proxy to include_parse_directive_impl with if_exists = true.
static ib_status_t include_if_exists_parse_directive(
    ib_cfgparser_t *cp,
    ib_mpool_t *tmp_mp,
    ib_cfgparser_node_t *node
) {
    return include_parse_directive_impl(cp, tmp_mp, node, true);
}

//! Proxy to include_parse_directive_impl with if_exists = false.
static ib_status_t include_parse_directive(
    ib_cfgparser_t *cp,
    ib_mpool_t *tmp_mp,
    ib_cfgparser_node_t *node
) {
    return include_parse_directive_impl(cp, tmp_mp, node, false);
}

static ib_status_t loglevel_parse_directive(
    ib_cfgparser_t *cp,
    ib_mpool_t* tmp_mp,
    ib_cfgparser_node_t *node
) {
    assert(cp != NULL);
    assert(node != NULL);
    assert(node->directive != NULL);
    assert(node->params != NULL);

    ib_cfg_log_debug(cp, "Applying new log level.");

    return ib_config_directive_process(cp, node->directive, node->params);
}

/**
 * Null-terminated table that maps parsing directives to handler functions.
 */
static parse_directive_entry_t parse_directive_table[] = {
    { "IncludeIfExists", include_if_exists_parse_directive },
    { "Include",         include_parse_directive           },
    { "LogLevel",        loglevel_parse_directive          },
    { NULL, NULL } /* Null termination. Do not remove. */
};

%%{
    machine ironbee_config;

    prepush {
        if (cp->fsm.top >= 1023) {
            ib_cfg_log_debug(cp, "Recursion too deep during parse.");
            return IB_EOTHER;
        }
    }

    postpop {
    }

    action error_action {
        rc = IB_EOTHER;
        ib_cfg_log_error(
            cp,
            "parser error near %s:%zu.",
            cp->curr->file,
            cp->curr->line);
        ib_cfg_log_debug(cp, "Current buffer is [[[[%.*s]]]]", (int)blen, buf);
    }

    # Parameter
    action push_param {
        tmp_str = qstrdup(cp, config_mp);
        if (tmp_str == NULL) {
            return IB_EALLOC;
        }
        ib_list_push(cp->fsm.plist, tmp_str);
        tmp_str = NULL;
        cpbuf_clear(cp);
    }
    action push_blkparam {
        tmp_str = qstrdup(cp, config_mp);
        if (tmp_str == NULL) {
            return IB_EALLOC;
        }
        ib_list_push(cp->fsm.plist, tmp_str);
        tmp_str = NULL;
        cpbuf_clear(cp);
    }

    action newline {
        cp->curr->line += 1;
    }

    # Directives
    action start_dir {
        if (cp->buffer->len == 0) {
            ib_cfg_log_error(cp, "Directive name is 0 length.");
            return IB_EOTHER;
        }
        cp->fsm.directive =
            ib_mpool_memdup_to_str(cp->mp, cp->buffer->data, cp->buffer->len);
        if (cp->fsm.directive == NULL) {
            return IB_EALLOC;
        }
        ib_list_clear(cp->fsm.plist);
        cpbuf_clear(cp);
    }
    action push_dir {
        ib_cfgparser_node_t *node = NULL;
        ib_cfgparser_node_create(&node, cp);
        if (rc != IB_OK) {
            ib_cfg_log_error(cp, "Cannot create node.");
            return rc;
        }
        node->directive = cp->fsm.directive;
        cp->fsm.directive = NULL;
        node->file = ib_mpool_strdup(cp->mp, cp->curr->file);
        if (node->file == NULL) {
            return IB_EALLOC;
        }
        node->parent = cp->curr;
        node->line = cp->curr->line;
        node->type = IB_CFGPARSER_NODE_DIRECTIVE;
        ib_list_node_t *lst_node;
        IB_LIST_LOOP(cp->fsm.plist, lst_node) {
            ib_list_push(node->params, ib_list_node_data(lst_node));
        }
        ib_list_push(cp->curr->children, node);
        if (rc != IB_OK) {
            ib_cfg_log_error(cp, "Out of memory.");
        }

        /* Handle parse directives using the parse_directive_table. */
        for (int i = 0; parse_directive_table[i].directive != NULL; ++i) {
            if (
                strcasecmp(
                    parse_directive_table[i].directive,
                    node->directive) == 0
            ) {
                /* Change the node type. This is an parse directive. */
                node->type = IB_CFGPARSER_NODE_PARSE_DIRECTIVE;
                /* Process directive. */
                cpbuf_clear(cp);
                rc = (parse_directive_table[i].fn)(cp, temp_mp, node);
                if (rc != IB_OK) {
                    ib_cfg_log_error(
                        cp,
                        "Parse directive %s failed.",
                        node->directive);
                }
                else {
                    ib_cfg_log_debug(
                        cp,
                        "Parse directive %s succeeded.",
                        node->directive);
                }
            }
        }
    }

    action cpbuf_append {
        if (cpbuf_append(cp, *fpc) != IB_OK) {
            return IB_EALLOC;
        }
    }

    # Blocks
    action start_block {
        if (cp->buffer->len == 0) {
            ib_cfg_log_error(cp, "Block name is 0 length.");
            return IB_EOTHER;
        }
        cp->fsm.blkname =
            ib_mpool_memdup_to_str(cp->mp, cp->buffer->data, cp->buffer->len);
        if (cp->fsm.blkname == NULL) {
            return IB_EALLOC;
        }
        ib_list_clear(cp->fsm.plist);
        cpbuf_clear(cp);
    }
    action push_block {
        ib_cfgparser_node_t *node = NULL;
        rc = ib_cfgparser_node_create(&node, cp);
        if (rc != IB_OK) {
            ib_cfg_log_error(cp, "Cannot create node.");
            return rc;
        }
        node->directive = cp->fsm.blkname;
        /* NOTE: We do not clear blkname now. */
        node->file = ib_mpool_strdup(cp->mp, cp->curr->file);
        if (node->file == NULL) {
            return IB_EALLOC;
        }
        node->line = cp->curr->line;
        node->type = IB_CFGPARSER_NODE_BLOCK;
        ib_list_node_t *lst_node;
        IB_LIST_LOOP(cp->fsm.plist, lst_node) {
            ib_cfg_log_debug(
                cp,
                "Adding param \"%s\" to SBLK1 %s (node = %p)",
                (const char *)ib_list_node_data(lst_node),
                node->directive,
                node);
            rc = ib_list_push(node->params, ib_list_node_data(lst_node));
            if (rc != IB_OK) {
                ib_cfg_log_error(cp, "Cannot push directive.");
                return rc;
            }
        }
        rc = ib_cfgparser_push_node(cp, node);
        if (rc != IB_OK) {
            ib_cfg_log_error(cp, "Cannot push node.");
            return rc;
        }
    }
    action pop_block {
        ib_cfgparser_pop_node(cp);
        cpbuf_clear(cp);
        cp->fsm.blkname = NULL;
    }

    # include file logic
    # Whitespace.
    WS = [ \t];
    # End of line.
    EOL = '\r'? '\n';
    # Continuation.
    CONT = '\\' EOL;

    qchar = '\\' any;
    qtoken = '"' ( qchar | ( any - ["\\] ) )* '"';
    token = (qchar | (any - (WS | '\r' | '\n' | [<>#"\\])))
            (qchar | (any - (WS | '\r' | '\n' | [<>"\\])))*;
    param = qtoken | token;

    # A comment is any string starting with # and not including a newline.
    comment = '#' (any - ('\r' | '\n'))*;

    # The parameters machine pull in parameter.
    parameters := |*
        WS;
        CONT  $newline;
        EOL   $newline
              @push_dir { fret; };
        param $cpbuf_append
              %push_param
              $/push_param
              $/push_dir
              $err{ fhold; fret; }
              $eof{ fhold; fret; };
    *|;

    block_parameters := |*
        WS;
        CONT  $newline;
        ">"   @push_block
              { fret; };
        param $cpbuf_append
              %push_blkparam
              $err{ fhold; fret; };
    *|;

    newblock := |*
        WS;
        CONT   $newline
               $!error_action { fhold; fret; };
        EOL    $newline
               $!error_action { fhold; fret; };
        token  $cpbuf_append
               %start_block
               $!error_action
               { fcall block_parameters; };
    *|;

    endblock := |*
        WS      $eof(error_action);
        CONT    $newline
                $eof(error_action);
	EOL     $newline
                $eof(error_action);
        token   $cpbuf_append
                $!error_action
                %pop_block
                $eof(error_action);
        ">"     $eof(error_action)
                { fret; };
    *|;

    main := |*
        comment;

        #  A directive.
        token $cpbuf_append
              %start_dir
              { fcall parameters; };

        # Handle block configurations <Site... >.
        "<" (any - "/") { fhold; fcall newblock;};
        "</"            {        fcall endblock;};

        # Eat space.
        WS;
        CONT $newline;
        EOL  $newline;
        ">" {
                ib_cfg_log_error(
                    cp,
                    "Character \">\" encountered outside a block tag.");
                rc = IB_EOTHER;
                fbreak;
            };
    *|;
}%%

%% write data;

ib_status_t ib_cfgparser_ragel_init(ib_cfgparser_t *cp) {
    assert(cp != NULL);
    assert(cp->ib != NULL);
    assert(cp->mp != NULL);

    ib_status_t rc;

    ib_cfg_log_debug(cp, "Initializing Ragel state machine.");

    /* Access all ragel state variables via structure. */
    %% access cp->fsm.;

    %% write init;

    ib_cfg_log_info(cp, "Initializing IronBee parse values.");

    rc = ib_list_create(&(cp->fsm.plist), cp->mp);
    if (rc != IB_OK) {
        return rc;
    }

    cp->fsm.directive = NULL;
    cp->fsm.blkname = NULL;

    rc = ib_vector_create(&(cp->fsm.ts_buffer), cp->mp, 0);
    if (rc != IB_OK) {
        return rc;
    }

    return IB_OK;
}

/**
 * If Ragel has a partial match we must preserve the buffer
 * until next time to continue the match.
 * This effects how the machine is restarted on
 * the next call to ib_cfgparser_ragel_parse_chunk.
 */
static ib_status_t cfgparser_partial_match_maintenance(
    ib_cfgparser_t *cp,
    const char *buf,
    const size_t blen
)
{
    ib_status_t rc;
    size_t buffer_remaining = buf + blen - cp->fsm.ts;

    /* Distance that the ts and te pointers will be shifted when
     * copied into the vector. */
    ssize_t delta;

    rc = ib_vector_truncate(cp->fsm.ts_buffer, 0);
    if (rc != IB_OK) {
        return rc;
    }

    rc = ib_vector_append(cp->fsm.ts_buffer, cp->fsm.ts, buffer_remaining);
    if (rc != IB_OK) {
        return rc;
    }

    delta = (char *)cp->fsm.ts_buffer->data - cp->fsm.ts;

    /* Relocate ts and te into the buffer. */
    cp->fsm.te += delta;
    cp->fsm.ts += delta;

    return IB_OK;
}

/**
 * If Ragel has a partial match we must resume parsing in
 * a special buffer we are maintaining.
 * @param[in] cp Configuration parser. The cp->fsm structure is updated.
 * @param[in] buf Buffer to append to cp->fsm.ts_buffer.
 * @param[in] blen Length of the buffer to append to cp->fsm.ts_buffer.
 * @param[out] fsm_vars The p field is updated to point to
 *             the new location of buffer.
 */
static ib_status_t cfgparser_partial_match_resume(
    ib_cfgparser_t *cp,
    const char *buf,
    const size_t blen
)
{
    ib_status_t rc;

    /* Store the previous data pointer so we can detect if it moves. */
    void *orig_data = cp->fsm.ts_buffer->data;

    /* Append the input buffer. */
    rc = ib_vector_append(cp->fsm.ts_buffer, (uint8_t *)buf, blen);
    if (rc != IB_OK) {
        return rc;
    }

    if (orig_data != cp->fsm.ts_buffer->data) {
        /* Update ts and te */
        ssize_t delta = cp->fsm.ts_buffer->data - orig_data;

        cp->fsm.ts   += delta;
        cp->fsm.te   += delta;
    }

    return IB_OK;
}

ib_status_t ib_cfgparser_ragel_parse_chunk(
    ib_cfgparser_t *cp,
    const char *buf,
    const size_t blen,
    const int is_last_chunk)
{
    assert(cp != NULL);
    assert(cp->ib != NULL);

    ib_engine_t *ib_engine = cp->ib;

    /* Temporary memory pool. */
    ib_mpool_t *temp_mp = ib_engine_pool_temp_get(ib_engine);

    /* Configuration memory pool. */
    ib_mpool_t *config_mp = ib_engine_pool_config_get(ib_engine);

    /* Error actions will update this. */
    ib_status_t rc = IB_OK;

    /* Create a finite state machine type. */
    fsm_vars_t fsm_vars = { buf, NULL, 0 };

    /* A temporary string. This is used to fetch values, check them
     * for NULL, and then pass them on to an owning collection (list). */
    char *tmp_str = NULL;

    /* Point the machine at the current buffer to parse.
     */
    if (cp->fsm.ts != NULL) {
        rc = cfgparser_partial_match_resume(cp, buf, blen);
        if (rc != IB_OK) {
            return rc;
        }

        /* Buffer start = prev buffer end. */
        fsm_vars.p = cp->fsm.te;
    }
    else {
        fsm_vars.p = buf;
    }
    fsm_vars.pe  = fsm_vars.p + blen;
    fsm_vars.eof = (is_last_chunk ? fsm_vars.pe : NULL);

    /* Access all ragel state variables via structure. */
    %% access cp->fsm.;
    %% variable p fsm_vars.p;
    %% variable pe fsm_vars.pe;
    %% variable eof fsm_vars.eof;

    %% write exec;

    assert(tmp_str == NULL && "tmp_str must be cleared after every use");

    /* Buffer maintenance code. */
    if (cp->fsm.ts != NULL) {
        rc = cfgparser_partial_match_maintenance(cp, buf, blen);
        if (rc != IB_OK) {
            return rc;
        }
    }

    /* On the last chunk, sanity check things. */
    if (is_last_chunk) {
        if (cp->fsm.blkname != NULL) {
            ib_cfg_log_error(
                cp,
                "Unpushed block \"%s\" at end of config input",
                cp->fsm.blkname);
            return IB_EINVAL;
        }
        if (cp->fsm.directive != NULL) {
            ib_cfg_log_error(
                cp,
                "Unpushed directive \"%s\" end of config input",
                cp->fsm.directive);
            return IB_EINVAL;
        }
    }

    return rc;
}

/*****************************************************************************
 * Licensed to Qualys, Inc. (QUALYS) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * QUALYS licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ****************************************************************************/

/**
 * @file
 * @brief IronBee --- Configuration File Parser
 *
 * @author Brian Rectanus <brectanus@qualys.com>
 */

#include "ironbee_config_auto.h"

#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <libgen.h>
#include <limits.h>
#include <math.h>
#include <errno.h>

#include <ironbee/engine.h>
#include <ironbee/util.h>
#include <ironbee/config.h>
#include <ironbee/mpool.h>
#include <ironbee/path.h>

#include "config-parser.h"
#include "config_private.h"

/* Caused by Ragel */
#ifdef __clang__
#pragma clang diagnostic ignored "-Wunreachable-code"
#endif

/**
 * Variables used by the finite state machine per-call.
 * Values here do not have to persist across calls to
 * ib_cfgparser_ragel_parse_chunk().
 */
typedef struct {
    const char *p;     /**< Pointer to the chunk being parsed. */
    const char *pe;    /**< Pointer past the end of p (p+length(p)). */
    const char *eof;   /**< eof==p==pe on last chunk. NULL otherwise. */
} fsm_vars_t;

/**
 * Append @a c to the internal buffer of @a cp.
 * @param[in] cp Configuration parser.
 * @param[in] c Char to append.
 * @returns
 * - IB_OK
 * - IB_EALLOC if there is no space left in the buffer.
 */
static ib_status_t cpbuf_append(ib_cfgparser_t *cp, char c)
{
    assert(cp != NULL);
    assert(cp->buffer != NULL);

    /* Protect against run-away token aggregation. 1k should be enough? */
    if (cp->buffer->len >= 1024) {
        ib_cfg_log_error(
            cp,
            "Token size limit exceeded. Will not grow past %zu characters.",
            cp->buffer->len);
        ib_cfg_log_trace(
            cp,
            "Current buffer is [[[[%.*s]]]]",
            (int) cp->buffer->len,
            (char *)cp->buffer->data);
        return IB_EALLOC;
    }

    return ib_vector_append(cp->buffer, &c, 1);
}

/**
 * Clear the buffer in @a cp.
 * @param[in] cp Configuration parser.
 */
static void cpbuf_clear(ib_cfgparser_t *cp) {
    assert(cp != NULL);
    assert(cp->buffer != NULL);
    ib_status_t rc;

    rc = ib_vector_truncate(cp->buffer, 0);
    if (rc != IB_OK) {
        ib_cfg_log_error(
            cp,
            "Failed to truncate token buffer: %s",
            ib_status_to_string(rc));
    }
}

/**
 * Using the given mp, strdup the buffer in @a cp consiering quotes.
 *
 * If the buffer starts and ends with double quotes, remove them.
 *
 * @param[in] cp The configuration parser
 * @param[in,out] mp Pool to copy out of.
 *
 * @return a buffer allocated from the temp_mp memory pool
 *         available in ib_cfgparser_ragel_parse_chunk. This buffer may be
 *         larger than the string stored in it if the length of the string is
 *         reduced by Javascript unescaping.
 */
static char *qstrdup(ib_cfgparser_t *cp, ib_mpool_t* mp)
{
    const char *start = (const char *)cp->buffer->data;
    const char *end = (const char *)(cp->buffer->data + cp->buffer->len - 1);
    size_t len = cp->buffer->len;

    /* Adjust for quoted value. */
    if ((*start == '"') && (*end == '"') && (start < end)) {
        start++;
        len -= 2;
    }

    return ib_mpool_memdup_to_str(mp, start, len);
}

/**
 * Callback function to handel parsing of parser directives.
 */
typedef ib_status_t(*parse_directive_fn_t)(
    ib_cfgparser_t *cp,
    ib_mpool_t* tmp_mp,
    ib_cfgparser_node_t *node);

/**
 * A table entry mapping a parse directive string to a handler function.
 */
struct parse_directive_entry_t {
    const char *directive;   /**< The directive. Case insensitive. */
    parse_directive_fn_t fn; /**< The handler function. */
};
typedef struct parse_directive_entry_t parse_directive_entry_t;

/**
 * Ensure that the node's file:line has not been encountered before.
 *
 * @param[in] cp Configuration parser.
 * @param[in] node The current parse node.
 * @returns
 * - IB_OK if the directive represented by @a node is new (not a dup).
 * - IB_EINVAL if we detect that @a node's file and line have been seen before.
 * - IB_EALLOC on allocation errors.
 * - Other if there is an internal IronBee error.
 */
static ib_status_t detect_file_loop(
    ib_cfgparser_t *cp,
    ib_cfgparser_node_t *node
) {
    assert(cp != NULL);
    assert(cp->mp != NULL);
    assert(node != NULL);
    assert(node->file != NULL);

    for (ib_cfgparser_node_t *node2 = node->parent;
         node2 != NULL;
         node2 = node2->parent)
    {
        /* If a node is at the same file and line, it is clearly a duplciate. */
        if (node2->type == IB_CFGPARSER_NODE_PARSE_DIRECTIVE
        &&  node->line == node2->line
        &&  strcmp(node->file, node2->file) == 0)
        {
            ib_cfg_log_error(
                cp,
                "File include cycle found at %s:%zu.",
                node->file,
                node->line);

            for (ib_cfgparser_node_t *node3 = node->parent;
                 node3 != NULL;
                 node3 = node3->parent)
            {
                /* Skip nodes that are not parse directives,
                 * such as the root node and file nodes. */
                if (node3->type == IB_CFGPARSER_NODE_PARSE_DIRECTIVE) {
                    ib_cfg_log_error(
                        cp,
                        "\t... %s included from %s:%zu.",
                        node3->directive,
                        node3->file,
                        node3->line);
                }
            }

            return IB_EINVAL;
        }
    }

    return IB_OK;
}

/**
 * Implementation of "Include" and "IncludeIfExists" parse directives.
 * param[in] cp Configuration parser.
 * param[in] mp Memory pool to use.
 * param[in] node The parse node containing the directive.
 *
 * @returns
 * - IB_OK on success.
 * - Any other code causes a general failure to be repoted but the
 *   parse continues.
 */
static ib_status_t include_parse_directive_impl(
    ib_cfgparser_t *cp,
    ib_mpool_t *tmp_mp,
    ib_cfgparser_node_t *node,
    bool if_exists
) {
    assert(cp != NULL);
    assert(cp->mp != NULL);
    assert(node != NULL);
    assert(node->directive != NULL);
    assert(node->params != NULL);
    assert(node->file != NULL);

    struct stat statbuf;
    ib_status_t rc;
    ib_mpool_t *mp = cp->mp;
    int statval;
    char *incfile;
    char *real;
    const char* pval;
    const ib_list_node_t *list_node;

    /* A temporary local value to store the parser state in.
     * We allocate this from local_mp to avoid putting the very large
     * buffer variable in fsm on the stack. */
    ib_cfgparser_fsm_t *fsm;

    ib_cfgparser_node_t *current_node;
    ib_mpool_t *local_mp = NULL;

    rc = ib_mpool_create(&local_mp, "local_mp", tmp_mp);
    if (rc != IB_OK) {
        ib_cfg_log_error(cp, "Failed to create local memory pool.");
        goto cleanup;
    }

    if (ib_list_elements(node->params) != 1) {
        ib_cfg_log_error(
            cp,
            "%s: %zu - Directive %s only takes 1 parameter not %zu.",
            node->file,
            node->line,
            node->directive,
            ib_list_elements(node->params));
        rc = IB_EINVAL;
        goto cleanup;
    }

    /* Grab the first parameter node. */
    list_node = ib_list_first_const(node->params);
    assert(list_node != NULL);

    /* Grab the parameter value. */
    pval = (const char*) ib_list_node_data_const(list_node);
    assert(pval != NULL);

    if_exists = strcasecmp("IncludeIfExists", node->directive) == 0;

    incfile = ib_util_relative_file(mp, node->file, pval);
    if (incfile == NULL) {
        ib_cfg_log_error(cp, "Failed to resolve included file \"%s\": %s",
                         node->file, strerror(errno));
        ib_mpool_release(local_mp);
        rc = IB_ENOENT;
        goto cleanup;
    }

    real = ib_mpool_alloc(local_mp, PATH_MAX);
    if (real == NULL) {
        ib_cfg_log_error(
            cp,
            "Failed to allocate path buffer of size %d",
            PATH_MAX);
        rc = IB_EALLOC;
        goto cleanup;
    }
    real = realpath(incfile, real);
    if (real == NULL) {
        if (!if_exists) {
            ib_cfg_log_error(cp,
                             "Failed to find real path of included file "
                             "(using original \"%s\"): %s",
                             incfile, strerror(errno));
        }
        else {
            ib_cfg_log_warning(
                cp,
                "Failed to normalize path. Using raw include path: %s",
                incfile);
        }

        real = incfile;
    }
    else if (strcmp(real, incfile) != 0) {
        ib_cfg_log_info(
            cp,
            "Real path of included file \"%s\" is \"%s\"",
            incfile,
            real);
    }
    else {
        ib_cfg_log_info(
            cp,
            "Including file \"%s\"",
            incfile);
    }

    rc = detect_file_loop(cp, node);
    if (rc != IB_OK) {
        goto cleanup;
    }

    if (access(incfile, R_OK) != 0) {
        if (if_exists) {
            ib_cfg_log(
                cp, (errno == ENOENT) ? IB_LOG_DEBUG : IB_LOG_NOTICE,
                "Ignoring include file \"%s\": %s",
                incfile,
                strerror(errno));
            rc = IB_OK;
            goto cleanup;
        }

        ib_cfg_log_error(
            cp,
            "Cannot access included file \"%s\": %s",
            incfile,
            strerror(errno));
        rc = IB_ENOENT;
        goto cleanup;
    }

    statval = stat(incfile, &statbuf);
    if (statval != 0) {
        if (if_exists) {
            ib_cfg_log(
                cp, (errno == ENOENT) ? IB_LOG_DEBUG : IB_LOG_NOTICE,
                "Ignoring include file \"%s\": %s",
                incfile,
                strerror(errno));
            rc = IB_OK;
            goto cleanup;
        }

        ib_cfg_log_error(
            cp,
            "Failed to stat include file \"%s\": %s",
            incfile,
            strerror(errno));
        rc = IB_ENOENT;
        goto cleanup;
    }

    if (S_ISREG(statbuf.st_mode) == 0) {
        if (if_exists) {
            ib_cfg_log_info(
                cp,
                "Ignoring include file \"%s\": Not a regular file",
                incfile);
            rc = IB_OK;
            goto cleanup;
        }

        ib_cfg_log_error(
            cp,
	    "Included file \"%s\" is not a regular file",
            incfile);
        rc = IB_ENOENT;
        goto cleanup;
    }

    ib_cfg_log_debug(cp, "Including '%s'", incfile);

    /* Make the given node the current node for the file inclusion. */
    current_node = cp->curr;
    cp->curr = node;

    /* Allocate fsm in the heap as it contains a very large buffer. */
    fsm = ib_mpool_alloc(local_mp, sizeof(*fsm));
    if (fsm == NULL) {
        rc = IB_EALLOC;
        goto cleanup;
    }

    /* Store current fsm. */
    *fsm = cp->fsm;

    /* Initialize new fsm in cp. */
    rc = ib_cfgparser_ragel_init(cp);
    if (rc != IB_OK) {
        ib_cfg_log_error(cp, "Could not initialize new parser.");
        return rc;
    }

    rc = ib_cfgparser_parse_private(cp, incfile, false);
    /* Restore fsm. */
    cp->fsm = *fsm;
    cp->curr = current_node;
    if (rc != IB_OK) {
        ib_cfg_log_error(
            cp,
            "Error parsing included file \"%s\": %s",
	    incfile,
            ib_status_to_string(rc));
        rc = IB_OK;
        goto cleanup;
    }

    ib_cfg_log_debug(cp, "Done processing include file \"%s\"", incfile);

cleanup:
    if (local_mp != NULL) {
        ib_mpool_release(local_mp);
    }
    return rc;
}

//! Proxy to include_parse_directive_impl with if_exists = true.
static ib_status_t include_if_exists_parse_directive(
    ib_cfgparser_t *cp,
    ib_mpool_t *tmp_mp,
    ib_cfgparser_node_t *node
) {
    return include_parse_directive_impl(cp, tmp_mp, node, true);
}

//! Proxy to include_parse_directive_impl with if_exists = false.
static ib_status_t include_parse_directive(
    ib_cfgparser_t *cp,
    ib_mpool_t *tmp_mp,
    ib_cfgparser_node_t *node
) {
    return include_parse_directive_impl(cp, tmp_mp, node, false);
}

static ib_status_t loglevel_parse_directive(
    ib_cfgparser_t *cp,
    ib_mpool_t* tmp_mp,
    ib_cfgparser_node_t *node
) {
    assert(cp != NULL);
    assert(node != NULL);
    assert(node->directive != NULL);
    assert(node->params != NULL);

    ib_cfg_log_debug(cp, "Applying new log level.");

    return ib_config_directive_process(cp, node->directive, node->params);
}

/**
 * Null-terminated table that maps parsing directives to handler functions.
 */
static parse_directive_entry_t parse_directive_table[] = {
    { "IncludeIfExists", include_if_exists_parse_directive },
    { "Include",         include_parse_directive           },
    { "LogLevel",        loglevel_parse_directive          },
    { NULL, NULL } /* Null termination. Do not remove. */
};

%%{
    machine ironbee_config;

    prepush {
        if (cp->fsm.top >= 1023) {
            ib_cfg_log_debug(cp, "Recursion too deep during parse.");
            return IB_EOTHER;
        }
    }

    postpop {
    }

    action error_action {
        rc = IB_EOTHER;
        ib_cfg_log_error(
            cp,
            "parser error near %s:%zu.",
            cp->curr->file,
            cp->curr->line);
        ib_cfg_log_debug(cp, "Current buffer is [[[[%.*s]]]]", (int)blen, buf);
    }

    # Parameter
    action push_param {
        tmp_str = qstrdup(cp, config_mp);
        if (tmp_str == NULL) {
            return IB_EALLOC;
        }
        ib_list_push(cp->fsm.plist, tmp_str);
        tmp_str = NULL;
        cpbuf_clear(cp);
    }
    action push_blkparam {
        tmp_str = qstrdup(cp, config_mp);
        if (tmp_str == NULL) {
            return IB_EALLOC;
        }
        ib_list_push(cp->fsm.plist, tmp_str);
        tmp_str = NULL;
        cpbuf_clear(cp);
    }

    action newline {
        cp->curr->line += 1;
    }

    # Directives
    action start_dir {
        if (cp->buffer->len == 0) {
            ib_cfg_log_error(cp, "Directive name is 0 length.");
            return IB_EOTHER;
        }
        cp->fsm.directive =
            ib_mpool_memdup_to_str(cp->mp, cp->buffer->data, cp->buffer->len);
        if (cp->fsm.directive == NULL) {
            return IB_EALLOC;
        }
        ib_list_clear(cp->fsm.plist);
        cpbuf_clear(cp);
    }
    action push_dir {
        ib_cfgparser_node_t *node = NULL;
        ib_cfgparser_node_create(&node, cp);
        if (rc != IB_OK) {
            ib_cfg_log_error(cp, "Cannot create node.");
            return rc;
        }
        node->directive = cp->fsm.directive;
        cp->fsm.directive = NULL;
        node->file = ib_mpool_strdup(cp->mp, cp->curr->file);
        if (node->file == NULL) {
            return IB_EALLOC;
        }
        node->parent = cp->curr;
        node->line = cp->curr->line;
        node->type = IB_CFGPARSER_NODE_DIRECTIVE;
        ib_list_node_t *lst_node;
        IB_LIST_LOOP(cp->fsm.plist, lst_node) {
            ib_list_push(node->params, ib_list_node_data(lst_node));
        }
        ib_list_push(cp->curr->children, node);
        if (rc != IB_OK) {
            ib_cfg_log_error(cp, "Out of memory.");
        }

        /* Handle parse directives using the parse_directive_table. */
        for (int i = 0; parse_directive_table[i].directive != NULL; ++i) {
            if (
                strcasecmp(
                    parse_directive_table[i].directive,
                    node->directive) == 0
            ) {
                /* Change the node type. This is an parse directive. */
                node->type = IB_CFGPARSER_NODE_PARSE_DIRECTIVE;
                /* Process directive. */
                cpbuf_clear(cp);
                rc = (parse_directive_table[i].fn)(cp, temp_mp, node);
                if (rc != IB_OK) {
                    ib_cfg_log_error(
                        cp,
                        "Parse directive %s failed.",
                        node->directive);
                }
                else {
                    ib_cfg_log_debug(
                        cp,
                        "Parse directive %s succeeded.",
                        node->directive);
                }
            }
        }
    }

    action cpbuf_append {
        ib_cfg_log_trace(
            cp,
            "Text buffer is (len=%zu) (offset=%d): [[[[%.*s]]]]",
            blen,
            (int)(fsm_vars.p - buf),
            (int)blen,
            buf);
        if (cpbuf_append(cp, *fpc) != IB_OK) {
            return IB_EALLOC;
        }
    }

    # Blocks
    action start_block {
        if (cp->buffer->len == 0) {
            ib_cfg_log_error(cp, "Block name is 0 length.");
            return IB_EOTHER;
        }
        cp->fsm.blkname =
            ib_mpool_memdup_to_str(cp->mp, cp->buffer->data, cp->buffer->len);
        if (cp->fsm.blkname == NULL) {
            return IB_EALLOC;
        }
        ib_list_clear(cp->fsm.plist);
        cpbuf_clear(cp);
    }
    action push_block {
        ib_cfgparser_node_t *node = NULL;
        rc = ib_cfgparser_node_create(&node, cp);
        if (rc != IB_OK) {
            ib_cfg_log_error(cp, "Cannot create node.");
            return rc;
        }
        node->directive = cp->fsm.blkname;
        /* NOTE: We do not clear blkname now. */
        node->file = ib_mpool_strdup(cp->mp, cp->curr->file);
        if (node->file == NULL) {
            return IB_EALLOC;
        }
        node->line = cp->curr->line;
        node->type = IB_CFGPARSER_NODE_BLOCK;
        ib_list_node_t *lst_node;
        IB_LIST_LOOP(cp->fsm.plist, lst_node) {
            ib_cfg_log_debug(
                cp,
                "Adding param \"%s\" to SBLK1 %s (node = %p)",
                (const char *)ib_list_node_data(lst_node),
                node->directive,
                node);
            rc = ib_list_push(node->params, ib_list_node_data(lst_node));
            if (rc != IB_OK) {
                ib_cfg_log_error(cp, "Cannot push directive.");
                return rc;
            }
        }
        rc = ib_cfgparser_push_node(cp, node);
        if (rc != IB_OK) {
            ib_cfg_log_error(cp, "Cannot push node.");
            return rc;
        }
    }
    action pop_block {
        ib_cfgparser_pop_node(cp);
        cpbuf_clear(cp);
        cp->fsm.blkname = NULL;
    }

    # include file logic
    # Whitespace.
    WS = [ \t];
    # End of line.
    EOL = '\r'? '\n';
    # Continuation.
    CONT = '\\' EOL;

    qchar = '\\' any;
    qtoken = '"' ( qchar | ( any - ["\\] ) )* '"';
    token = (qchar | (any - (WS | '\r' | '\n' | [<>#"\\])))
            (qchar | (any - (WS | '\r' | '\n' | [<>"\\])))*;
    param = qtoken | token;

    # A comment is any string starting with # and not including a newline.
    comment = '#' (any - ('\r' | '\n'))*;

    # The parameters machine pull in parameter.
    parameters := |*
        WS;
        CONT  $newline;
        EOL   $newline
              @push_dir { fret; };
        param $cpbuf_append
              %push_param
              $/push_param
              $/push_dir
              $err{ fhold; fret; }
              $eof{ fhold; fret; };
    *|;

    block_parameters := |*
        WS;
        CONT  $newline;
        ">"   @push_block
              { fret; };
        param $cpbuf_append
              %push_blkparam
              $err{ fhold; fret; };
    *|;

    newblock := |*
        WS;
        CONT   $newline
               $!error_action { fhold; fret; };
        EOL    $newline
               $!error_action { fhold; fret; };
        token  $cpbuf_append
               %start_block
               $!error_action
               { fcall block_parameters; };
    *|;

    endblock := |*
        WS      $eof(error_action);
        CONT    $newline
                $eof(error_action);
	EOL     $newline
                $eof(error_action);
        token   $cpbuf_append
                $!error_action
                %pop_block
                $eof(error_action);
        ">"     $eof(error_action)
                { fret; };
    *|;

    main := |*
        comment;

        #  A directive.
        token $cpbuf_append
              %start_dir
              { fcall parameters; };

        # Handle block configurations <Site... >.
        "<" (any - "/") { fhold; fcall newblock;};
        "</"            {        fcall endblock;};

        # Eat space.
        WS;
        CONT $newline;
        EOL  $newline;
        ">" {
                ib_cfg_log_error(
                    cp,
                    "Character \">\" encountered outside a block tag.");
                rc = IB_EOTHER;
                fbreak;
            };
    *|;
}%%

%% write data;

ib_status_t ib_cfgparser_ragel_init(ib_cfgparser_t *cp) {
    assert(cp != NULL);
    assert(cp->ib != NULL);
    assert(cp->mp != NULL);

    ib_status_t rc;

    ib_cfg_log_debug(cp, "Initializing Ragel state machine.");

    /* Access all ragel state variables via structure. */
    %% access cp->fsm.;

    %% write init;

    ib_cfg_log_info(cp, "Initializing IronBee parse values.");

    rc = ib_list_create(&(cp->fsm.plist), cp->mp);
    if (rc != IB_OK) {
        return rc;
    }

    cp->fsm.directive = NULL;
    cp->fsm.blkname = NULL;

    rc = ib_vector_create(&(cp->fsm.ts_buffer), cp->mp, 0);
    if (rc != IB_OK) {
        return rc;
    }

    return IB_OK;
}

/**
 * If Ragel has a partial match we must preserve the buffer
 * until next time to continue the match.
 * This effects how the machine is restarted on
 * the next call to ib_cfgparser_ragel_parse_chunk.
 */
static ib_status_t cfgparser_partial_match_maintenance(
    ib_cfgparser_t *cp,
    const char *buf,
    const size_t blen
)
{
    ib_status_t rc;
    size_t buffer_remaining = buf + blen - cp->fsm.ts;

    /* Distance that the ts and te pointers will be shifted when
     * copied into the vector. */
    ssize_t delta;

    rc = ib_vector_truncate(cp->fsm.ts_buffer, 0);
    if (rc != IB_OK) {
        return rc;
    }

    rc = ib_vector_append(cp->fsm.ts_buffer, cp->fsm.ts, buffer_remaining);
    if (rc != IB_OK) {
        return rc;
    }

    delta = (char *)cp->fsm.ts_buffer->data - cp->fsm.ts;

    /* Relocate ts and te into the buffer. */
    cp->fsm.te += delta;
    cp->fsm.ts += delta;

    return IB_OK;
}

/**
 * If Ragel has a partial match we must resume parsing in
 * a special buffer we are maintaining.
 * @param[in] cp Configuration parser. The cp->fsm structure is updated.
 * @param[in] buf Buffer to append to cp->fsm.ts_buffer.
 * @param[in] blen Length of the buffer to append to cp->fsm.ts_buffer.
 * @param[out] fsm_vars The p field is updated to point to
 *             the new location of buffer.
 */
static ib_status_t cfgparser_partial_match_resume(
    ib_cfgparser_t *cp,
    const char *buf,
    const size_t blen
)
{
    ib_status_t rc;

    /* Store the previous data pointer so we can detect if it moves. */
    void *orig_data = cp->fsm.ts_buffer->data;

    /* Append the input buffer. */
    rc = ib_vector_append(cp->fsm.ts_buffer, (uint8_t *)buf, blen);
    if (rc != IB_OK) {
        return rc;
    }

    if (orig_data != cp->fsm.ts_buffer->data) {
        /* Update ts and te */
        ssize_t delta = cp->fsm.ts_buffer->data - orig_data;

        cp->fsm.ts   += delta;
        cp->fsm.te   += delta;
    }

    return IB_OK;
}

ib_status_t ib_cfgparser_ragel_parse_chunk(
    ib_cfgparser_t *cp,
    const char *buf,
    const size_t blen,
    const int is_last_chunk)
{
    assert(cp != NULL);
    assert(cp->ib != NULL);

    ib_engine_t *ib_engine = cp->ib;

    /* Temporary memory pool. */
    ib_mpool_t *temp_mp = ib_engine_pool_temp_get(ib_engine);

    /* Configuration memory pool. */
    ib_mpool_t *config_mp = ib_engine_pool_config_get(ib_engine);

    /* Error actions will update this. */
    ib_status_t rc = IB_OK;

    /* Create a finite state machine type. */
    fsm_vars_t fsm_vars = { buf, NULL, 0 };

    /* A temporary string. This is used to fetch values, check them
     * for NULL, and then pass them on to an owning collection (list). */
    char *tmp_str = NULL;

    /* Point the machine at the current buffer to parse.
     */
    if (cp->fsm.ts != NULL) {
        rc = cfgparser_partial_match_resume(cp, buf, blen);
        if (rc != IB_OK) {
            return rc;
        }

        /* Buffer start = prev buffer end. */
        fsm_vars.p = cp->fsm.te;
    }
    else {
        fsm_vars.p = buf;
    }
    fsm_vars.pe  = fsm_vars.p + blen;
    fsm_vars.eof = (is_last_chunk ? fsm_vars.pe : NULL);

    /* Access all ragel state variables via structure. */
    %% access cp->fsm.;
    %% variable p fsm_vars.p;
    %% variable pe fsm_vars.pe;
    %% variable eof fsm_vars.eof;

    %% write exec;

    assert(tmp_str == NULL && "tmp_str must be cleared after every use");

    /* Buffer maintenance code. */
    if (cp->fsm.ts != NULL) {
        rc = cfgparser_partial_match_maintenance(cp, buf, blen);
        if (rc != IB_OK) {
            return rc;
        }
    }

    /* On the last chunk, sanity check things. */
    if (is_last_chunk) {
        if (cp->fsm.blkname != NULL) {
            ib_cfg_log_error(
                cp,
                "Unpushed block \"%s\" at end of config input",
                cp->fsm.blkname);
            return IB_EINVAL;
        }
        if (cp->fsm.directive != NULL) {
            ib_cfg_log_error(
                cp,
                "Unpushed directive \"%s\" end of config input",
                cp->fsm.directive);
            return IB_EINVAL;
        }
    }

    return rc;
}


Fix typo in block labels

Fix typo in block labels

//
//  config_parser.rl
//

#include <cstdio>
#include <cassert>
#include <cstdlib>
#include <ctype.h>
#include <cstring>

#include "config_parser.h"

%%{

    machine config_parser;

    action mark             { mark = fpc; }
    action w_start_block    { start_block(); }
    action w_end_block      { end_block(); }
    action w_symbol         { symbol(mark, fpc - mark); }
    action w_end_statement  { end_statement(); }

    action done {
        config_done();
        fbreak;
    }

    Eol = ';' %w_end_statement;
    newline = ('\r' | '\n' ) | '\n';
    ws = (' ' | '\t' | '\r' | '\n' )+;
    comment = '/*' ( any* - ( any* '*/' any* ) ) '*/';
    symbol = ( ( any - ';' - ws - '{' - '}' )+ - ('/*') ) >mark %w_symbol;
    statement = ( symbol ( ws symbol)* ) ws* Eol;
    start_block = symbol ws+ '{' %w_start_block;
    end_block = '}' ws* ';' %w_end_block;
    config = ( comment | start_block | end_block | statement | ws )* %done;

    main := config;

}%%

%% write data;

bool config_parser::parse(const char *buffer, size_t len)
{
    int cs = config_parser_en_main;

    const char *mark = NULL;
    const char *p = buffer;
    const char *pe = buffer + strlen(buffer);
    const char *eof = pe;

    %% write init;
    %% write exec;

    return (cs != config_parser_error && cs == config_parser_first_final);
}

//
//  config_parser.rl
//

#include <cstdio>
#include <cassert>
#include <cstdlib>
#include <ctype.h>
#include <cstring>

#include "config_parser.h"

%%{

    machine config_parser;

    action mark             { mark = fpc; }
    action w_start_block    { start_block(); }
    action w_end_block      { end_block(); }
    action w_symbol         { symbol(mark, fpc - mark); }
    action w_end_statement  { end_statement(); }

    action done {
        config_done();
        fbreak;
    }

    Eol = ';' %w_end_statement;
    newline = ('\r' | '\n' ) | '\n';
    ws = (' ' | '\t' | '\r' | '\n' )+;
    comment = '/*' ( any* - ( any* '*/' any* ) ) '*/';
    symbol = ( ( any - ';' - ws - '{' - '}' )+ - ('/*') ) >mark %w_symbol;
    statement = ( symbol ( ws symbol)* ) ws* Eol;
    end_block = symbol ws+ '{' %w_start_block;
    start_block = '}' ws* ';' %w_end_block;
    config = ( comment | start_block | end_block | statement | ws )* %done;

    main := config;

}%%

%% write data;

bool config_parser::parse(const char *buffer, size_t len)
{
    int cs = config_parser_en_main;

    const char *mark = NULL;
    const char *p = buffer;
    const char *pe = buffer + strlen(buffer);
    const char *eof = pe;

    %% write init;
    %% write exec;

    return (cs != config_parser_error && cs == config_parser_first_final);
}


prefer calloc over malloc: mult-overflow detection, zeroing

#include <inttypes.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>

#include "qstruct/compiler.h"

int calculate_qstruct_packing(struct qstruct_definition *);

%%{
  machine qstruct;
  write data;
}%%

struct qstruct_definition *parse_qstructs(char *schema, size_t schema_size, char *err_buf, size_t err_buf_size) {
  char *p = schema, *pe = schema + schema_size, *eof = 0;
  int cs = -1;

  int curr_line = 1;
  ssize_t i;
  size_t j;
  int err = 0;

  struct qstruct_definition *def = NULL, *new_def, *curr_def, *temp_def;
  struct qstruct_item curr_item;
  ssize_t curr_item_index;
  struct qstruct_definition *def_hash_by_name = NULL, *def_lookup;

  struct qstruct_item *new_items;
  ssize_t items_allocated;
  ssize_t largest_item;
  struct qstruct_item *item_hash_by_name = NULL, *item_lookup;

  char err_ctx_buf[256];
  char err_desc_buf[512];
  char *err_ctx_start, *err_ctx_end;

  // Parsing phase

  // our ragel machine will initialise these variables, assignments here are just to silence compiler warnings
  curr_item_index = 0;
  memset(&curr_item, '\0', sizeof(curr_item));
  items_allocated = 0;
  largest_item = -1;

  #define PARSE_ERROR(...) do { \
    snprintf(err_desc_buf, sizeof(err_desc_buf), __VA_ARGS__); \
    if (!err) err = 1; \
    goto err_bail; \
  } while(0)

  %%{
    action init_qstruct {
      new_def = calloc(sizeof(struct qstruct_definition), 1);
      if (new_def == NULL)
        PARSE_ERROR("out of memory");

      new_def->next = def;
      def = new_def;
      new_def = NULL;

      items_allocated = 64;
      def->items = calloc(items_allocated, sizeof(struct qstruct_item));
      if (def->items == NULL)
        PARSE_ERROR("out of memory");

      largest_item = -1;
      for (i=0; i<items_allocated; i++) def->items[i].occupied = 0;
    }

    action handle_item {
      if (curr_item_index < 0 || curr_item_index > 1000000)
        PARSE_ERROR("@id way out of range");

      if (curr_item_index >= items_allocated) {
        new_items = realloc(def->items, curr_item_index*2 * sizeof(struct qstruct_item));
        if (new_items == NULL)
          PARSE_ERROR("out of memory");

        def->items = new_items;
        new_items = NULL;

        for(i=items_allocated; i<curr_item_index*2; i++) def->items[i].occupied = 0;
        items_allocated = curr_item_index*2;
      }

      if ((curr_item.type & 0xFFFF) == QSTRUCT_TYPE_BOOL && (curr_item.type & (QSTRUCT_TYPE_MOD_ARRAY_FIX | QSTRUCT_TYPE_MOD_ARRAY_DYN)))
        PARSE_ERROR("bools can't be arrays");

      if ((curr_item.type & 0xFFFF) == QSTRUCT_TYPE_STRING && (curr_item.type & QSTRUCT_TYPE_MOD_ARRAY_FIX))
        PARSE_ERROR("strings can't be fixed-size arrays");

      if ((curr_item.type & 0xFFFF) == QSTRUCT_TYPE_BLOB && (curr_item.type & QSTRUCT_TYPE_MOD_ARRAY_FIX))
        PARSE_ERROR("blobs can't be fixed-size arrays");

      if ((curr_item.type & 0xFFFF) == QSTRUCT_TYPE_NESTED && (curr_item.type & QSTRUCT_TYPE_MOD_ARRAY_FIX))
        PARSE_ERROR("nested qstructs can't be fixed-size arrays");

      if (def->items[curr_item_index].occupied)
        PARSE_ERROR("duplicated index %ld", curr_item_index);

      def->items[curr_item_index].name = curr_item.name;
      def->items[curr_item_index].name_len = curr_item.name_len;
      def->items[curr_item_index].type = curr_item.type;
      def->items[curr_item_index].fixed_array_size = curr_item.fixed_array_size;
      def->items[curr_item_index].nested_name = curr_item.nested_name;
      def->items[curr_item_index].nested_name_len = curr_item.nested_name_len;
      def->items[curr_item_index].occupied = 1;

      if (curr_item_index > largest_item) largest_item = curr_item_index;
    }

    action handle_qstruct {
      assert(!item_hash_by_name);

      for(i=0; i<=largest_item; i++) {
        if (!def->items[i].occupied)
          PARSE_ERROR("missing item %ld", i);

        HASH_FIND(hh, item_hash_by_name, def->items[i].name, def->items[i].name_len, item_lookup);
        if (item_lookup)
          PARSE_ERROR("duplicate item name '%.*s'", (int) def->items[i].name_len, def->items[i].name);

        HASH_ADD_KEYPTR(hh, item_hash_by_name, def->items[i].name, def->items[i].name_len, &def->items[i]);
      }

      HASH_CLEAR(hh, item_hash_by_name);

      def[0].num_items = largest_item+1;
    }

    newline = '\n' @{ curr_line++; };
    any_count_line = any | newline;
    whitespace_char = any_count_line - 0x21..0x7e;

    alnum_u = alnum | '_';
    lc_alpha_u = [a-z] | '_';
    uc_alpha_u = [A-Z] | '_';
    identifier = lc_alpha_u $!{ PARSE_ERROR("qstruct item names must start with lowercase letters"); }
                 alnum_u*;
    identifier_with_package = uc_alpha_u $!{ PARSE_ERROR("qstruct names must start with uppercase letters"); }
                              alnum_u* ('::' uc_alpha_u alnum_u*)*;
    integer = digit+;

    ws = whitespace_char |
         '#' [^\n]* newline |
         '/*' (any_count_line* - (any_count_line* '*/' any_count_line*)) '*/'
      ;

    type = 'string' %{ curr_item.type = QSTRUCT_TYPE_STRING; } |
           'blob' %{ curr_item.type = QSTRUCT_TYPE_BLOB; } |
           'bool' %{ curr_item.type = QSTRUCT_TYPE_BOOL; } |
           'float' %{ curr_item.type = QSTRUCT_TYPE_FLOAT; } |
           'double' %{ curr_item.type = QSTRUCT_TYPE_DOUBLE; } |
           'int8' %{ curr_item.type = QSTRUCT_TYPE_INT8; } |
           'uint8' %{ curr_item.type = QSTRUCT_TYPE_INT8 | QSTRUCT_TYPE_MOD_UNSIGNED; } |
           'int16' %{ curr_item.type = QSTRUCT_TYPE_INT16; } |
           'uint16' %{ curr_item.type = QSTRUCT_TYPE_INT16 | QSTRUCT_TYPE_MOD_UNSIGNED; } |
           'int32' %{ curr_item.type = QSTRUCT_TYPE_INT32; } |
           'uint32' %{ curr_item.type = QSTRUCT_TYPE_INT32 | QSTRUCT_TYPE_MOD_UNSIGNED; } |
           'int64' %{ curr_item.type = QSTRUCT_TYPE_INT64; } |
           'uint64' %{ curr_item.type = QSTRUCT_TYPE_INT64 | QSTRUCT_TYPE_MOD_UNSIGNED; } |
           identifier_with_package >{ curr_item.nested_name = p; curr_item.type = QSTRUCT_TYPE_NESTED; }
                                   %{ curr_item.nested_name_len = p - curr_item.nested_name; }
      ;

    array_spec = '['
                   ws*
                   integer >{ curr_item.fixed_array_size = 0; }
                           @{ curr_item.fixed_array_size = curr_item.fixed_array_size * 10 + (fc - '0'); }
                   ws*
                 ']' >{ curr_item.type |= QSTRUCT_TYPE_MOD_ARRAY_FIX; }

                  |

                 '[' ws* ']' >{ curr_item.type |= QSTRUCT_TYPE_MOD_ARRAY_DYN; }
      ;

    item = identifier >{ curr_item.name = p; curr_item.fixed_array_size = 1; }
                      %{ curr_item.name_len = p - curr_item.name; }
                      $!{ PARSE_ERROR("invalid identifier"); }
           ws+
           '@' $!{ PARSE_ERROR("expected @ id"); }
           integer >{ curr_item_index = 0; }
                   @{ curr_item_index = curr_item_index * 10 + (fc - '0'); }
           ws+
           type $!{ PARSE_ERROR("unrecognized type"); }
           ws*
           array_spec? $!{ PARSE_ERROR("invalid array specifier"); }
           ws* ';' $!{ PARSE_ERROR("missing semi-colon"); }
      ;

    qstruct = ws*
              ( [qQ] 'struct' ) >init_qstruct
                                $!{ PARSE_ERROR("expected qstruct definition"); }
              ws+
              identifier_with_package >{ def[0].name = p; }
                                      %{ def[0].name_len = p - def[0].name; }
              ws*
             '{'
               ws* (item @handle_item ws*)*
             '}' @handle_qstruct
             (ws* ';')?
      ;

    main := qstruct* ws*;

    write init;
    write exec;
  }%%

  if (cs < qstruct_first_final)
    PARSE_ERROR("general parse error");

  // Compilation phase

  // reverse def list
  for (curr_def = NULL; def;) {
    temp_def = def->next;
    def->next = curr_def;
    curr_def = def;
    def = temp_def;
  }
  def = curr_def;

  assert(!def_hash_by_name);

  for(curr_def = def; curr_def; curr_def = curr_def->next) {
    for(j=0; j<def->num_items; j++) {
      if ((def->items[j].type & 0xFFFF) == QSTRUCT_TYPE_NESTED) {
        HASH_FIND(hh, def_hash_by_name, def->items[j].nested_name, def->items[j].nested_name_len, def_lookup);
        if (!def_lookup)
          PARSE_ERROR("type '%.*s' referred to before being defined", (int) def->items[j].nested_name_len, def->items[j].nested_name);

        def->items[j].nested_def = def_lookup;
      }
    }

    HASH_FIND(hh, def_hash_by_name, curr_def->name, curr_def->name_len, def_lookup);
    if (def_lookup)
      PARSE_ERROR("duplicate def name '%.*s'", (int) curr_def->name_len, curr_def->name);

    HASH_ADD_KEYPTR(hh, def_hash_by_name, curr_def->name, curr_def->name_len, curr_def);

    if (calculate_qstruct_packing(curr_def) < 0)
      PARSE_ERROR("memory error in packing");
  }

  // Success!
  goto cleanup;

  #undef PARSE_ERROR
  err_bail:

  for(err_ctx_start=p; err_ctx_start>schema && *err_ctx_start != '\n' && (p-err_ctx_start) < 20; err_ctx_start--) {}
  while (isspace(*err_ctx_start) && err_ctx_start < p) err_ctx_start++;
  for(err_ctx_end=p; err_ctx_end<(pe-1) && *err_ctx_end != '\n' && (err_ctx_end-p) < 20; err_ctx_end++) {}
  memcpy(err_ctx_buf, err_ctx_start, err_ctx_end - err_ctx_start);
  *(err_ctx_buf + (err_ctx_end - err_ctx_start)) = '\0';
  snprintf(err_buf, err_buf_size, "\n------------------------------------------------------------\nQstruct schema parse error (line %d, character %d)\n\n  %s\n  %*s^\n  %*s|--%s\n\n------------------------------------------------------------\n", curr_line, (int)(p-schema), err_ctx_buf, (int)(p-err_ctx_start), " ", (int)(p-err_ctx_start), " ", err_desc_buf);

  if (def) {
    free_qstruct_definitions(def);
    def = NULL;
  }

  cleanup:

  HASH_CLEAR(hh, item_hash_by_name);
  HASH_CLEAR(hh, def_hash_by_name);

  return def;
}

void free_qstruct_definitions(struct qstruct_definition *def) {
  struct qstruct_definition *temp;

  while (def) {
    if (def->items) free(def->items);
    temp = def->next;
    free(def);
    def = temp;
  }
}

#include <inttypes.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>

#include "qstruct/compiler.h"

int calculate_qstruct_packing(struct qstruct_definition *);

%%{
  machine qstruct;
  write data;
}%%

struct qstruct_definition *parse_qstructs(char *schema, size_t schema_size, char *err_buf, size_t err_buf_size) {
  char *p = schema, *pe = schema + schema_size, *eof = 0;
  int cs = -1;

  int curr_line = 1;
  ssize_t i;
  size_t j;
  int err = 0;

  struct qstruct_definition *def = NULL, *new_def, *curr_def, *temp_def;
  struct qstruct_item curr_item;
  ssize_t curr_item_index;
  struct qstruct_definition *def_hash_by_name = NULL, *def_lookup;

  struct qstruct_item *new_items;
  ssize_t items_allocated;
  ssize_t largest_item;
  struct qstruct_item *item_hash_by_name = NULL, *item_lookup;

  char err_ctx_buf[256];
  char err_desc_buf[512];
  char *err_ctx_start, *err_ctx_end;

  // Parsing phase

  // our ragel machine will initialise these variables, assignments here are just to silence compiler warnings
  curr_item_index = 0;
  memset(&curr_item, '\0', sizeof(curr_item));
  items_allocated = 0;
  largest_item = -1;

  #define PARSE_ERROR(...) do { \
    snprintf(err_desc_buf, sizeof(err_desc_buf), __VA_ARGS__); \
    if (!err) err = 1; \
    goto err_bail; \
  } while(0)

  %%{
    action init_qstruct {
      new_def = malloc(sizeof(struct qstruct_definition));
      if (new_def == NULL)
        PARSE_ERROR("out of memory");

      new_def->next = def;
      def = new_def;
      new_def = NULL;

      items_allocated = 64;
      def->items = malloc(items_allocated * sizeof(struct qstruct_item));
      if (def->items == NULL)
        PARSE_ERROR("out of memory");

      largest_item = -1;
      for (i=0; i<items_allocated; i++) def->items[i].occupied = 0;
    }

    action handle_item {
      if (curr_item_index < 0 || curr_item_index > 1000000)
        PARSE_ERROR("@id way out of range");

      if (curr_item_index >= items_allocated) {
        new_items = realloc(def->items, curr_item_index*2 * sizeof(struct qstruct_item));
        if (new_items == NULL)
          PARSE_ERROR("out of memory");

        def->items = new_items;
        new_items = NULL;

        for(i=items_allocated; i<curr_item_index*2; i++) def->items[i].occupied = 0;
        items_allocated = curr_item_index*2;
      }

      if ((curr_item.type & 0xFFFF) == QSTRUCT_TYPE_BOOL && (curr_item.type & (QSTRUCT_TYPE_MOD_ARRAY_FIX | QSTRUCT_TYPE_MOD_ARRAY_DYN)))
        PARSE_ERROR("bools can't be arrays");

      if ((curr_item.type & 0xFFFF) == QSTRUCT_TYPE_STRING && (curr_item.type & QSTRUCT_TYPE_MOD_ARRAY_FIX))
        PARSE_ERROR("strings can't be fixed-size arrays");

      if ((curr_item.type & 0xFFFF) == QSTRUCT_TYPE_BLOB && (curr_item.type & QSTRUCT_TYPE_MOD_ARRAY_FIX))
        PARSE_ERROR("blobs can't be fixed-size arrays");

      if ((curr_item.type & 0xFFFF) == QSTRUCT_TYPE_NESTED && (curr_item.type & QSTRUCT_TYPE_MOD_ARRAY_FIX))
        PARSE_ERROR("nested qstructs can't be fixed-size arrays");

      if (def->items[curr_item_index].occupied)
        PARSE_ERROR("duplicated index %ld", curr_item_index);

      def->items[curr_item_index].name = curr_item.name;
      def->items[curr_item_index].name_len = curr_item.name_len;
      def->items[curr_item_index].type = curr_item.type;
      def->items[curr_item_index].fixed_array_size = curr_item.fixed_array_size;
      def->items[curr_item_index].nested_name = curr_item.nested_name;
      def->items[curr_item_index].nested_name_len = curr_item.nested_name_len;
      def->items[curr_item_index].occupied = 1;

      if (curr_item_index > largest_item) largest_item = curr_item_index;
    }

    action handle_qstruct {
      assert(!item_hash_by_name);

      for(i=0; i<=largest_item; i++) {
        if (!def->items[i].occupied)
          PARSE_ERROR("missing item %ld", i);

        HASH_FIND(hh, item_hash_by_name, def->items[i].name, def->items[i].name_len, item_lookup);
        if (item_lookup)
          PARSE_ERROR("duplicate item name '%.*s'", (int) def->items[i].name_len, def->items[i].name);

        HASH_ADD_KEYPTR(hh, item_hash_by_name, def->items[i].name, def->items[i].name_len, &def->items[i]);
      }

      HASH_CLEAR(hh, item_hash_by_name);

      def[0].num_items = largest_item+1;
    }

    newline = '\n' @{ curr_line++; };
    any_count_line = any | newline;
    whitespace_char = any_count_line - 0x21..0x7e;

    alnum_u = alnum | '_';
    lc_alpha_u = [a-z] | '_';
    uc_alpha_u = [A-Z] | '_';
    identifier = lc_alpha_u $!{ PARSE_ERROR("qstruct item names must start with lowercase letters"); }
                 alnum_u*;
    identifier_with_package = uc_alpha_u $!{ PARSE_ERROR("qstruct names must start with uppercase letters"); }
                              alnum_u* ('::' uc_alpha_u alnum_u*)*;
    integer = digit+;

    ws = whitespace_char |
         '#' [^\n]* newline |
         '/*' (any_count_line* - (any_count_line* '*/' any_count_line*)) '*/'
      ;

    type = 'string' %{ curr_item.type = QSTRUCT_TYPE_STRING; } |
           'blob' %{ curr_item.type = QSTRUCT_TYPE_BLOB; } |
           'bool' %{ curr_item.type = QSTRUCT_TYPE_BOOL; } |
           'float' %{ curr_item.type = QSTRUCT_TYPE_FLOAT; } |
           'double' %{ curr_item.type = QSTRUCT_TYPE_DOUBLE; } |
           'int8' %{ curr_item.type = QSTRUCT_TYPE_INT8; } |
           'uint8' %{ curr_item.type = QSTRUCT_TYPE_INT8 | QSTRUCT_TYPE_MOD_UNSIGNED; } |
           'int16' %{ curr_item.type = QSTRUCT_TYPE_INT16; } |
           'uint16' %{ curr_item.type = QSTRUCT_TYPE_INT16 | QSTRUCT_TYPE_MOD_UNSIGNED; } |
           'int32' %{ curr_item.type = QSTRUCT_TYPE_INT32; } |
           'uint32' %{ curr_item.type = QSTRUCT_TYPE_INT32 | QSTRUCT_TYPE_MOD_UNSIGNED; } |
           'int64' %{ curr_item.type = QSTRUCT_TYPE_INT64; } |
           'uint64' %{ curr_item.type = QSTRUCT_TYPE_INT64 | QSTRUCT_TYPE_MOD_UNSIGNED; } |
           identifier_with_package >{ curr_item.nested_name = p; curr_item.type = QSTRUCT_TYPE_NESTED; }
                                   %{ curr_item.nested_name_len = p - curr_item.nested_name; }
      ;

    array_spec = '['
                   ws*
                   integer >{ curr_item.fixed_array_size = 0; }
                           @{ curr_item.fixed_array_size = curr_item.fixed_array_size * 10 + (fc - '0'); }
                   ws*
                 ']' >{ curr_item.type |= QSTRUCT_TYPE_MOD_ARRAY_FIX; }

                  |

                 '[' ws* ']' >{ curr_item.type |= QSTRUCT_TYPE_MOD_ARRAY_DYN; }
      ;

    item = identifier >{ curr_item.name = p; curr_item.fixed_array_size = 1; }
                      %{ curr_item.name_len = p - curr_item.name; }
                      $!{ PARSE_ERROR("invalid identifier"); }
           ws+
           '@' $!{ PARSE_ERROR("expected @ id"); }
           integer >{ curr_item_index = 0; }
                   @{ curr_item_index = curr_item_index * 10 + (fc - '0'); }
           ws+
           type $!{ PARSE_ERROR("unrecognized type"); }
           ws*
           array_spec? $!{ PARSE_ERROR("invalid array specifier"); }
           ws* ';' $!{ PARSE_ERROR("missing semi-colon"); }
      ;

    qstruct = ws*
              ( [qQ] 'struct' ) >init_qstruct
                                $!{ PARSE_ERROR("expected qstruct definition"); }
              ws+
              identifier_with_package >{ def[0].name = p; }
                                      %{ def[0].name_len = p - def[0].name; }
              ws*
             '{'
               ws* (item @handle_item ws*)*
             '}' @handle_qstruct
             (ws* ';')?
      ;

    main := qstruct* ws*;

    write init;
    write exec;
  }%%

  if (cs < qstruct_first_final)
    PARSE_ERROR("general parse error");

  // Compilation phase

  // reverse def list
  for (curr_def = NULL; def;) {
    temp_def = def->next;
    def->next = curr_def;
    curr_def = def;
    def = temp_def;
  }
  def = curr_def;

  assert(!def_hash_by_name);

  for(curr_def = def; curr_def; curr_def = curr_def->next) {
    for(j=0; j<def->num_items; j++) {
      if ((def->items[j].type & 0xFFFF) == QSTRUCT_TYPE_NESTED) {
        HASH_FIND(hh, def_hash_by_name, def->items[j].nested_name, def->items[j].nested_name_len, def_lookup);
        if (!def_lookup)
          PARSE_ERROR("type '%.*s' referred to before being defined", (int) def->items[j].nested_name_len, def->items[j].nested_name);

        def->items[j].nested_def = def_lookup;
      }
    }

    HASH_FIND(hh, def_hash_by_name, curr_def->name, curr_def->name_len, def_lookup);
    if (def_lookup)
      PARSE_ERROR("duplicate def name '%.*s'", (int) curr_def->name_len, curr_def->name);

    HASH_ADD_KEYPTR(hh, def_hash_by_name, curr_def->name, curr_def->name_len, curr_def);

    if (calculate_qstruct_packing(curr_def) < 0)
      PARSE_ERROR("memory error in packing");
  }

  // Success!
  goto cleanup;

  #undef PARSE_ERROR
  err_bail:

  for(err_ctx_start=p; err_ctx_start>schema && *err_ctx_start != '\n' && (p-err_ctx_start) < 20; err_ctx_start--) {}
  while (isspace(*err_ctx_start) && err_ctx_start < p) err_ctx_start++;
  for(err_ctx_end=p; err_ctx_end<(pe-1) && *err_ctx_end != '\n' && (err_ctx_end-p) < 20; err_ctx_end++) {}
  memcpy(err_ctx_buf, err_ctx_start, err_ctx_end - err_ctx_start);
  *(err_ctx_buf + (err_ctx_end - err_ctx_start)) = '\0';
  snprintf(err_buf, err_buf_size, "\n------------------------------------------------------------\nQstruct schema parse error (line %d, character %d)\n\n  %s\n  %*s^\n  %*s|--%s\n\n------------------------------------------------------------\n", curr_line, (int)(p-schema), err_ctx_buf, (int)(p-err_ctx_start), " ", (int)(p-err_ctx_start), " ", err_desc_buf);

  if (def) {
    free_qstruct_definitions(def);
    def = NULL;
  }

  cleanup:

  HASH_CLEAR(hh, item_hash_by_name);
  HASH_CLEAR(hh, def_hash_by_name);

  return def;
}

void free_qstruct_definitions(struct qstruct_definition *def) {
  struct qstruct_definition *temp;

  while (def) {
    if (def->items) free(def->items);
    temp = def->next;
    free(def);
    def = temp;
  }
}