Commit 25f35912 authored by Daniel Salzman's avatar Daniel Salzman

zscanner: upgrade the API and add a state parsing support

parent b58c98b8
......@@ -13,8 +13,7 @@ ragel -T0 -s -o $OUT_T0 $IN
# Remove redundant comments and unused constants (clang warnings).
sed -i '/#line/d' $OUT_T0
sed -i '/static\ const\ int\ zone_scanner_en_/d' $OUT_T0
sed -i '/zone_scanner_first_final/d' $OUT_T0
sed -i '/static\ const\ int\ zone_scanner_/d' $OUT_T0
# Remove trailing white spaces.
sed -i 's/\s*$//g' $OUT_T0
......@@ -24,8 +23,7 @@ ragel -G2 -s -o $OUT_G2 $IN
# Remove redundant comments and unused constants (clang warnings).
sed -i '/#line/d' $OUT_G2
sed -i '/static\ const\ int\ zone_scanner_en_/d' $OUT_G2
sed -i '/zone_scanner_first_final/d' $OUT_G2
sed -i '/static\ const\ int\ zone_scanner_/d' $OUT_G2
# Remove trailing white spaces.
sed -i 's/\s*$//g' $OUT_G2
......
......@@ -27,20 +27,20 @@ typedef struct {
#define ERR_ITEM(code, text) { code, text, #code }
static const err_table_t err_msgs[] = {
ERR_ITEM( ZS_OK, "ok" ),
ERR_ITEM( ZS_SYSTEM,
"system error" ),
ERR_ITEM( ZS_OK,
"ok" ),
ERR_ITEM( ZS_EINVAL,
"invalid parameter" ),
ERR_ITEM( ZS_ENOMEM,
"not enough memory" ),
ERR_ITEM( ZS_FILE_OPEN,
"file open error" ),
ERR_ITEM( ZS_FILE_INVALID,
"invalid file" ),
ERR_ITEM( ZS_FILE_PATH,
"file path error" ),
ERR_ITEM( ZS_FILE_FSTAT,
"fstat error" ),
ERR_ITEM( ZS_FILE_DIR,
"not zone file but directory" ),
ERR_ITEM( ZS_FILE_MMAP,
"file mmap error" ),
"file memory mapping error" ),
ERR_ITEM( ZS_DOS_NEWLINE,
"unsupported CRLF newline, remove CR bytes" ),
ERR_ITEM( ZS_UNCOVERED_STATE,
......
......@@ -26,12 +26,11 @@
enum err_codes {
ZS_OK = 0,
ZS_SYSTEM = -1000,
ZS_EINVAL = -1000,
ZS_ENOMEM,
ZS_FILE_OPEN,
ZS_FILE_INVALID,
ZS_FILE_PATH,
ZS_FILE_FSTAT,
ZS_FILE_DIR,
ZS_FILE_MMAP,
ZS_DOS_NEWLINE,
ZS_UNCOVERED_STATE,
......
This diff is collapsed.
This diff is collapsed.
......@@ -27,6 +27,7 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "zscanner/error.h"
......@@ -83,6 +84,15 @@ typedef struct {
int8_t lat_sign, long_sign, alt_sign;
} loc_t;
/*! \brief Scanner states describing the result. */
typedef enum {
ZS_STATE_DATA, /*!< A record parsed. */
ZS_STATE_ERROR, /*!< An error occured. */
ZS_STATE_INCLUDE, /*!< An include directive parsed. */
ZS_STATE_EOF, /*!< The end of the current input reached. */
ZS_STATE_STOP /*!< Finished parsing. */
} zs_state_t;
/*!
* \brief Context structure for zone scanner.
*
......@@ -128,7 +138,9 @@ struct scanner {
/*! Auxiliary buffer for data storing. */
uint8_t buffer[MAX_RDATA_LENGTH];
/*! Auxiliary buffer for current included file name. */
char include_filename[MAX_RDATA_LENGTH + 1];
char include_filename[MAX_RDATA_LENGTH];
/*! Absolute path for relative includes. */
char *path;
/*! Auxiliary array of bitmap window blocks. */
window_t windows[BITMAP_WINDOWS];
......@@ -166,27 +178,34 @@ struct scanner {
/*! Value of the current default ttl (TTL directive sets this). */
uint32_t default_ttl;
/*! Callback function for correct zone record. */
void (*process_record)(zs_scanner_t *);
/*! Callback function for wrong situations. */
void (*process_error)(zs_scanner_t *);
/*! Arbitrary data useful inside callback functions. */
void *data;
/*! The current processing state. */
zs_state_t state;
/*! Absolute path for relative includes. */
char *path;
/*! Zone data line counter. */
uint64_t line_counter;
/*! Last occured error/warning code. */
int error_code;
/*! Errors/warnings counter. */
uint64_t error_counter;
/*!
* Indicates serious warning which is considered as an error and
* forces zone processing to stop.
*/
bool stop;
/*! Processing callbacks and auxiliary data. */
struct {
/*! Automatic zone processing using record/error callbacks. */
bool automatic;
/*! Callback function for correct zone record. */
void (*record)(zs_scanner_t *);
/*! Callback function for wrong situations. */
void (*error)(zs_scanner_t *);
/*! Arbitrary data useful inside callback functions. */
void *data;
} process;
/*! Input parameters. */
struct {
/*! Start of the block. */
const char *start;
/*! Current parser position. */
const char *current;
/*! End of the block. */
const char *end;
/*! Indication for the final block parsing. */
bool eof;
} input;
/*! File input parameters. */
struct {
/*! Zone file name. */
char *name;
......@@ -194,6 +213,18 @@ struct scanner {
int descriptor;
} file;
struct {
/*! Last occured error/warning code. */
int code;
/*! Error/warning counter. */
uint64_t counter;
/*! Indicates serious error - parsing cannot continue. */
bool fatal;
} error;
/*! Zone data line counter. */
uint64_t line_counter;
/*! Length of the current record owner. */
uint32_t r_owner_length;
/*!
......@@ -227,70 +258,119 @@ struct scanner {
};
/*!
* \brief Creates zone scanner structure.
* \brief Initializes the scanner context.
*
* \note Error code is stored in the scanner context.
*
* \param scanner Scanner context.
* \param origin Initial zone origin.
* \param rclass Zone class value.
* \param ttl Initial ttl value.
*
* \retval 0 if success.
* \retval -1 if error.
*/
int zs_init(
zs_scanner_t *scanner,
const char *origin,
const uint16_t rclass,
const uint32_t ttl
);
/*!
* \brief Deinitializes the scanner context.
*
* \param scanner Scanner context.
*/
void zs_deinit(
zs_scanner_t *scanner
);
/*!
* \brief Sets the scanner to parse a zone data string.
*
* \param origin Initial zone origin.
* \param rclass Zone class value.
* \param ttl Initial ttl value.
* \param process_record Processing callback function.
* \param process_error Error callback function.
* \param data Arbitrary data useful in callback functions.
* \note Error code is stored in the scanner context.
*
* \retval scanner if success.
* \retval NULL if error.
* \param scanner Scanner context.
* \param input Input zone data string to parse.
* \param size Size of the input string.
*
* \retval 0 if success.
* \retval -1 if error.
*/
zs_scanner_t* zs_scanner_create(const char *origin,
const uint16_t rclass,
const uint32_t ttl,
void (*process_record)(zs_scanner_t *),
void (*process_error)(zs_scanner_t *),
void *data);
int zs_set_input_string(
zs_scanner_t *scanner,
const char *input,
size_t size
);
/*!
* \brief Destroys zone scanner structure.
* \brief Sets the scanner to parse a zone file..
*
* \note Error code is stored in the scanner context.
*
* \param scanner Zone scanner structure.
* \param scanner Scanner context.
* \param file_name Name of the file to parse.
*
* \retval 0 if success.
* \retval -1 if error.
*/
void zs_scanner_free(zs_scanner_t *scanner);
int zs_set_input_file(
zs_scanner_t *scanner,
const char *file_name
);
/*!
* \brief Parser memory block with zone data.
* \brief Sets the scanner processing callbacks for automatic processing.
*
* \note Error code is stored in the scanner context.
*
* For each correctly recognized record data process_record callback function
* is called. If any syntax error occures, then process_error callback
* function is called.
* \param scanner Scanner context.
* \param process_record Processing callback function.
* \param process_error Error callback function.
* \param data Arbitrary data useful in callback functions.
*
* \note Zone scanner error code and other information are stored in
* the scanner structure.
* \retval 0 if success.
* \retval -1 if error.
*/
int zs_set_processing(
zs_scanner_t *s,
void (*process_record)(zs_scanner_t *),
void (*process_error)(zs_scanner_t *),
void *data
);
/*!
* \brief Parses one record from the input.
*
* \param scanner Zone scanner.
* \param start First byte of the zone data to parse.
* \param end Last byte of the zone data to parse.
* \param final_block Indicates if the current block is final i.e. no
* other blocks will be processed.
* The following processing should be based on the scanner->state.
*
* \retval 0 if success.
* \retval -1 if error.
* \note Error code and other information are stored in the scanner context.
*
* \param scanner Scanner context.
*
* \retval 0 if success.
* \retval -1 if error.
*/
int zs_scanner_parse(zs_scanner_t *scanner,
const char *start,
const char *end,
const bool final_block);
int zs_parse_record(
zs_scanner_t *scanner
);
/*!
* \brief Parses specified zone file.
* \brief Launches automatic parsing of the whole input.
*
* \note Zone scanner error code and other information are stored in
* the scanner structure. If error and error_count == 0, there is
* a more general problem with loading and this error is not processed
* with process_error!
* For each correctly recognized record, the record callback is executed.
* If any syntax error occures, the error callback is executed.
*
* \param scanner Zone scanner.
* \param file_name Name of file to process.
* \note Error code and other information are stored in the scanner context.
*
* \retval 0 if success.
* \retval -1 if error.
* \param scanner Scanner context.
*
* \retval 0 if success.
* \retval -1 if error.
*/
int zs_scanner_parse_file(zs_scanner_t *scanner,
const char *file_name);
int zs_parse_all(
zs_scanner_t *scanner
);
/*! @} */
This diff is collapsed.
......@@ -77,31 +77,53 @@
}
}
action _err_line_exit {
// Ending string in buffer.
// Terminate the error context string.
s->buffer[s->buffer_length++] = 0;
// Error counter incrementation.
s->error_counter++;
s->error.counter++;
// Initialization of fcall stack.
// Initialize the fcall stack.
top = 0;
// Process error message.
s->process_error(s);
// Reset.
s->error_code = ZS_OK;
// Reset the multiline context.
s->multiline = false;
// In case of serious error, stop scanner.
if (s->stop == true) {
return;
s->state = ZS_STATE_ERROR;
// Execute the error callback.
if (s->process.automatic) {
if (s->process.error != NULL) {
s->process.error(s);
// Stop the scanner if required.
if (s->state == ZS_STATE_STOP) {
fbreak;
}
}
// Stop the scanner if fatal.
if (s->error.fatal) {
s->state = ZS_STATE_STOP;
fbreak;
}
} else {
// Return if external processing.
escape = true;
}
}
action _err_line_exit_final {
if (escape) {
escape = false;
fnext main; fbreak;
} else {
fgoto main;
}
}
# Fill rest of the line to buffer and skip to main loop.
err_line := (^newline $_err_line)* >_err_line_init
%_err_line_exit . newline @{ fgoto main; };
%_err_line_exit . newline @_err_line_exit_final;
# END
# BEGIN - Domain name labels processing
......@@ -656,17 +678,16 @@
rdata_tail = s->r_data;
}
action _incl_filename_exit {
*rdata_tail = 0; // Ending filename string.
strncpy((char*)(s->include_filename), (char*)(s->r_data),
sizeof(s->include_filename));
// Check for correct string copy.
if (strlen(s->include_filename) !=
(size_t)(rdata_tail - s->r_data)) {
size_t len = rdata_tail - s->r_data;
if (len >= sizeof(s->include_filename)) {
ERR(ZS_BAD_INCLUDE_FILENAME);
fhold; fgoto err_line;
}
// Store zero terminated include filename.
memcpy(s->include_filename, s->r_data, len);
s->include_filename[len] = '\0';
// For detection whether origin is not present.
s->dname = NULL;
}
......@@ -687,54 +708,60 @@
}
action _include_exit {
char text_origin[4 * MAX_DNAME_LENGTH]; // Each char as \DDD.
// Extend relative file path.
if (s->include_filename[0] != '/') {
ret = snprintf((char *)(s->buffer), sizeof(s->buffer),
"%s/%s", s->path, s->include_filename);
if (ret <= 0 || ret > sizeof(s->buffer)) {
ERR(ZS_BAD_INCLUDE_FILENAME);
fhold; fgoto err_line;
}
memcpy(s->include_filename, s->buffer, ret);
}
// Origin conversion from wire to text form.
// Origin conversion from wire to text form in \DDD notation.
if (s->dname == NULL) { // Use current origin.
wire_dname_to_str(s->zone_origin,
s->zone_origin_length,
text_origin);
(char *)s->buffer);
} else { // Use specified origin.
wire_dname_to_str(s->r_data,
s->r_data_length,
text_origin);
(char *)s->buffer);
}
// Relative file path.
if (s->include_filename[0] != '/') {
snprintf((char*)(s->buffer), sizeof(s->buffer),
"%s/%s", s->path, s->include_filename);
// Let the caller to solve the include.
if (!s->process.automatic) {
s->state = ZS_STATE_INCLUDE;
escape = true;
} else {
strncpy((char*)(s->buffer), (char*)(s->include_filename),
sizeof(s->buffer));
}
// Create new scanner for included zone file.
zs_scanner_t *ss = zs_scanner_create(text_origin,
s->default_class,
s->default_ttl,
s->process_record,
s->process_error,
s->data);
if (ss != NULL) {
// Parse included zone file.
ret = zs_scanner_parse_file(ss, (char*)(s->buffer));
if (ret != 0) {
// File internal errors are handled by error callback.
if (ss->error_counter > 0) {
ERR(ZS_UNPROCESSED_INCLUDE);
// General include file error.
} else {
ERR(ss->error_code);
}
zs_scanner_free(ss);
fhold; fgoto err_line;
}
zs_scanner_free(ss);
} else {
zs_scanner_t *ss = malloc(sizeof(zs_scanner_t));
if (ss == NULL) {
ERR(ZS_UNPROCESSED_INCLUDE);
fhold; fgoto err_line;
}
// Parse included zone file.
if (zs_init(ss, (char *)s->buffer, s->default_class, s->default_ttl) != 0 ||
zs_set_input_file(ss, (char *)(s->include_filename)) != 0 ||
zs_set_processing(ss, s->process.record, s->process.error, s->process.data) != 0 ||
zs_parse_all(ss) != 0) {
// File internal errors are handled by error callback.
if (ss->error.counter > 0) {
ERR(ZS_UNPROCESSED_INCLUDE);
// General include file error.
} else {
ERR(ss->error.code);
}
zs_deinit(ss);
free(ss);
fhold; fgoto err_line;
}
zs_deinit(ss);
free(ss);
}
}
include_file_ :=
......@@ -751,11 +778,15 @@
# Each error/warning in directive should stop processing.
# Some internal errors cause warning only. This causes stop processing.
action _directive_init {
s->stop = true;
ERR(ZS_OK);
}
# Remove stop processing flag.
action _directive_exit {
s->stop = false;
NOERR;
if (escape) {
escape = false;
fnext main; fbreak;
}
}
action _directive_error {
ERR(ZS_BAD_DIRECTIVE);
......@@ -1966,11 +1997,21 @@
}
s->r_data_length = rdata_tail - s->r_data;
s->process_record(s);
s->state = ZS_STATE_DATA;
// Execute the record callback.
if (s->process.automatic) {
if (s->process.record != NULL) {
s->process.record(s);
// Stop scanner if required.
if (s->stop == true) {
return;
// Stop the scanner if required.
if (s->state == ZS_STATE_STOP) {
fbreak;
}
}
} else {
// Return if external processing.
fhold; fbreak;
}
}
......@@ -1988,7 +2029,15 @@
# Blank spaces with comments.
blank = rest . newline;
action _main_init {
if (escape) {
escape = false;
fbreak;
}
}
# Main processing loop.
# main := (record %_main_init | directive %_main_init | blank %_main_init)*;
main := (record | directive | blank)*;
# END
}%%
00-0_general
00-1_general
00-2_general
00-3_general
00-4_general
01_owner
02_class
03_rrttl
......
$ORIGIN .
$TTL 1
. NS @ ; No newline
\ No newline at end of file
OWNER=00
CLASS=0001
RRTTL=00000001
RTYPE=0002
RDATA=00
------
ERROR=ZS_FILE_DIR
ERROR=ZS_FILE_INVALID
------
......@@ -40,16 +40,16 @@ static void print_wire_dname(const uint8_t *dname, uint32_t dname_length)
void debug_process_error(zs_scanner_t *s)
{
if (s->stop == true) {
if (s->error.fatal) {
printf("LINE(%03"PRIu64") ERROR(%s) FILE(%s) NEAR(%s)\n",
s->line_counter,
zs_strerror(s->error_code),
zs_strerror(s->error.code),
s->file.name,
s->buffer);
} else {
printf("LINE(%03"PRIu64") WARNING(%s) FILE(%s) NEAR(%s)\n",
s->line_counter,
zs_strerror(s->error_code),
zs_strerror(s->error.code),
s->file.name,
s->buffer);
}
......@@ -85,10 +85,10 @@ void debug_process_record(zs_scanner_t *s)
void test_process_error(zs_scanner_t *s)
{
if (s->stop == true) {
printf("ERROR=%s\n%s", zs_errorname(s->error_code), separator);
if (s->error.fatal) {
printf("ERROR=%s\n%s", zs_errorname(s->error.code), separator);
} else {
printf("WARNG=%s\n%s", zs_errorname(s->error_code), separator);
printf("WARNG=%s\n%s", zs_errorname(s->error.code), separator);
}
fflush(stdout);
}
......
......@@ -11,7 +11,7 @@ TMPDIR=$(test_tmpdir)
TESTS_DIR="$SOURCE"/data
ZSCANNER_TOOL="$BUILD"/zscanner-tool
plan 71
plan 73
mkdir -p "$TMPDIR"/includes/
for a in 1 2 3 4 5 6; do
......
......@@ -45,6 +45,7 @@ static void help(void)
" 0 Empty output.\n"
" 1 Debug output (DEFAULT).\n"
" 2 Test output.\n"
" -s State parsing mode.\n"
" -t Launch unit tests.\n"
" -h Print this help.\n");
}
......@@ -69,26 +70,89 @@ static int time_test()
return EXIT_SUCCESS;
}
int state_parsing(
zs_scanner_t *s)
{
zs_scanner_t *ss;
while (zs_parse_record(s) == 0) {
switch (s->state) {
case ZS_STATE_DATA:
if (s->process.record != NULL) {
s->process.record(s);
}
break;
case ZS_STATE_ERROR:
if (s->process.error != NULL) {
s->process.error(s);
}
if (s->error.fatal) {
return -1;
}
break;
case ZS_STATE_INCLUDE:
ss = malloc(sizeof(zs_scanner_t));
if (ss == NULL) {
return -1;
}
if (zs_init(ss, (char *)s->buffer, s->default_class, s->default_ttl) != 0 ||
zs_set_input_file(ss, (char *)(s->include_filename)) != 0 ||
zs_set_processing(ss, s->process.record, s->process.error, s->process.data) != 0 ||
state_parsing(ss) != 0) {
if (ss->error.counter > 0) {
s->error.code = ZS_UNPROCESSED_INCLUDE;
} else {
s->error.code = ss->error.code;
}
s->error.counter++;
s->error.fatal = true;
if (s->process.error != NULL) {
s->process.error(s);
}
zs_deinit(ss);
free(ss);
return -1;
}
zs_deinit(ss);
free(ss);
break;
case ZS_STATE_EOF:
// Set the next input string block.
break;
case ZS_STATE_STOP:
return 0;
}
}
return 0;
}
int main(int argc, char *argv[])
{
// Parsed command line arguments.
int c = 0, li = 0;
int mode = DEFAULT_MODE, test = 0;
int c = 0, li = 0, ret;
int mode = DEFAULT_MODE, state = 0, test = 0;
// Command line long options.
struct option opts[] = {
{ "mode", required_argument, 0, 'm' },
{ "state", no_argument, 0, 's' },
{ "test", no_argument, 0, 't' },
{ "help", no_argument, 0, 'h' },
{ 0, 0, 0, 0 }
};
// Command line options processing.
while ((c = getopt_long(argc, argv, "m:th", opts, &li)) != -1) {
while ((c = getopt_long(argc, argv, "m:sth", opts, &li)) != -1) {
switch (c) {
case 'm':
mode = atoi(optarg);
break;
case 's':
state = 1;
break;
case 't':
test = 1;
break;
......@@ -111,65 +175,69 @@ int main(int argc, char *argv[])
return EXIT_FAILURE;