aboutsummaryrefslogtreecommitdiff
path: root/ntpd/ntp_scanner.c
diff options
context:
space:
mode:
Diffstat (limited to 'ntpd/ntp_scanner.c')
-rw-r--r--ntpd/ntp_scanner.c460
1 files changed, 320 insertions, 140 deletions
diff --git a/ntpd/ntp_scanner.c b/ntpd/ntp_scanner.c
index a7c19922bd53..49adf6bfb767 100644
--- a/ntpd/ntp_scanner.c
+++ b/ntpd/ntp_scanner.c
@@ -38,6 +38,7 @@
char yytext[MAX_LEXEME]; /* Buffer for storing the input text/lexeme */
u_int32 conf_file_sum; /* Simple sum of characters read */
+static struct FILE_INFO * lex_stack = NULL;
@@ -86,153 +87,342 @@ keyword(
}
-/* FILE INTERFACE
- * --------------
- * We define a couple of wrapper functions around the standard C fgetc
- * and ungetc functions in order to include positional bookkeeping
+/* FILE & STRING BUFFER INTERFACE
+ * ------------------------------
+ *
+ * This set out as a couple of wrapper functions around the standard C
+ * fgetc and ungetc functions in order to include positional
+ * bookkeeping. Alas, this is no longer a good solution with nested
+ * input files and the possibility to send configuration commands via
+ * 'ntpdc' and 'ntpq'.
+ *
+ * Now there are a few functions to maintain a stack of nested input
+ * sources (though nesting is only allowd for disk files) and from the
+ * scanner / parser point of view there's no difference between both
+ * types of sources.
+ *
+ * The 'fgetc()' / 'ungetc()' replacements now operate on a FILE_INFO
+ * structure. Instead of trying different 'ungetc()' strategies for file
+ * and buffer based parsing, we keep the backup char in our own
+ * FILE_INFO structure. This is sufficient, as the parser does *not*
+ * jump around via 'seek' or the like, and there's no need to
+ * check/clear the backup store in other places than 'lex_getch()'.
*/
-struct FILE_INFO *
-F_OPEN(
+/*
+ * Allocate an info structure and attach it to a file.
+ *
+ * Note: When 'mode' is NULL, then the INFO block will be set up to
+ * contain a NULL file pointer, as suited for remote config command
+ * parsing. Otherwise having a NULL file pointer is considered an error,
+ * and a NULL info block pointer is returned to indicate failure!
+ *
+ * Note: We use a variable-sized structure to hold a copy of the file
+ * name (or, more proper, the input source description). This is more
+ * secure than keeping a reference to some other storage that might go
+ * out of scope.
+ */
+static struct FILE_INFO *
+lex_open(
const char *path,
const char *mode
)
{
- struct FILE_INFO *my_info;
-
- my_info = emalloc(sizeof *my_info);
-
- my_info->line_no = 1;
- my_info->col_no = 0;
- my_info->prev_line_col_no = 0;
- my_info->prev_token_col_no = 0;
- my_info->fname = path;
-
- my_info->fd = fopen(path, mode);
- if (NULL == my_info->fd) {
- free(my_info);
- return NULL;
+ struct FILE_INFO *stream;
+ size_t nnambuf;
+
+ nnambuf = strlen(path);
+ stream = emalloc_zero(sizeof(*stream) + nnambuf);
+ stream->curpos.nline = 1;
+ stream->backch = EOF;
+ /* copy name with memcpy -- trailing NUL already there! */
+ memcpy(stream->fname, path, nnambuf);
+
+ if (NULL != mode) {
+ stream->fpi = fopen(path, mode);
+ if (NULL == stream->fpi) {
+ free(stream);
+ stream = NULL;
+ }
}
- return my_info;
+ return stream;
}
-int
-FGETC(
+/* get next character from buffer or file. This will return any putback
+ * character first; it will also make sure the last line is at least
+ * virtually terminated with a '\n'.
+ */
+static int
+lex_getch(
struct FILE_INFO *stream
)
{
int ch;
-
- do
- ch = fgetc(stream->fd);
- while (EOF != ch && (CHAR_MIN > ch || ch > CHAR_MAX));
-
- if (EOF != ch) {
- if (input_from_file)
- conf_file_sum += (u_char)ch;
- ++stream->col_no;
- if (ch == '\n') {
- stream->prev_line_col_no = stream->col_no;
- ++stream->line_no;
- stream->col_no = 1;
+
+ if (NULL == stream || stream->force_eof)
+ return EOF;
+
+ if (EOF != stream->backch) {
+ ch = stream->backch;
+ stream->backch = EOF;
+ if (stream->fpi)
+ conf_file_sum += ch;
+ } else if (stream->fpi) {
+ /* fetch next 7-bit ASCII char (or EOF) from file */
+ while ((ch = fgetc(stream->fpi)) != EOF && ch > SCHAR_MAX)
+ stream->curpos.ncol++;
+ if (EOF != ch) {
+ conf_file_sum += ch;
+ stream->curpos.ncol++;
}
+ } else {
+ /* fetch next 7-bit ASCII char from buffer */
+ const char * scan;
+ scan = &remote_config.buffer[remote_config.pos];
+ while ((ch = (u_char)*scan) > SCHAR_MAX) {
+ scan++;
+ stream->curpos.ncol++;
+ }
+ if ('\0' != ch) {
+ scan++;
+ stream->curpos.ncol++;
+ } else {
+ ch = EOF;
+ }
+ remote_config.pos = (int)(scan - remote_config.buffer);
+ }
+
+ /* If the last line ends without '\n', generate one. This
+ * happens most likely on Windows, where editors often have a
+ * sloppy concept of a line.
+ */
+ if (EOF == ch && stream->curpos.ncol != 0)
+ ch = '\n';
+
+ /* update scan position tallies */
+ if (ch == '\n') {
+ stream->bakpos = stream->curpos;
+ stream->curpos.nline++;
+ stream->curpos.ncol = 0;
}
return ch;
}
-/* BUGS: 1. Function will fail on more than one line of pushback
- * 2. No error checking is done to see if ungetc fails
- * SK: I don't think its worth fixing these bugs for our purposes ;-)
+/* Note: lex_ungetch will fail to track more than one line of push
+ * back. But since it guarantees only one char of back storage anyway,
+ * this should not be a problem.
*/
-int
-UNGETC(
+static int
+lex_ungetch(
int ch,
struct FILE_INFO *stream
)
{
- if (input_from_file)
- conf_file_sum -= (u_char)ch;
- if (ch == '\n') {
- stream->col_no = stream->prev_line_col_no;
- stream->prev_line_col_no = -1;
- --stream->line_no;
+ /* check preconditions */
+ if (NULL == stream || stream->force_eof)
+ return EOF;
+ if (EOF != stream->backch || EOF == ch)
+ return EOF;
+
+ /* keep for later reference and update checksum */
+ stream->backch = (u_char)ch;
+ if (stream->fpi)
+ conf_file_sum -= stream->backch;
+
+ /* update position */
+ if (stream->backch == '\n') {
+ stream->curpos = stream->bakpos;
+ stream->bakpos.ncol = -1;
}
- --stream->col_no;
- return ungetc(ch, stream->fd);
+ stream->curpos.ncol--;
+ return stream->backch;
}
-int
-FCLOSE(
+/* dispose of an input structure. If the file pointer is not NULL, close
+ * the file. This function does not check the result of 'fclose()'.
+ */
+static void
+lex_close(
struct FILE_INFO *stream
)
{
- int ret_val = fclose(stream->fd);
-
- if (!ret_val)
+ if (NULL != stream) {
+ if (NULL != stream->fpi)
+ fclose(stream->fpi);
free(stream);
- return ret_val;
+ }
}
-/* STREAM INTERFACE
- * ----------------
- * Provide a wrapper for the stream functions so that the
- * stream can either read from a file or from a character
- * array.
- * NOTE: This is not very efficient for reading from character
- * arrays, but needed to allow remote configuration where the
- * configuration command is provided through ntpq.
- *
- * The behavior of there two functions is determined by the
- * input_from_file flag.
+/* INPUT STACK
+ * -----------
+ *
+ * Nested input sources are a bit tricky at first glance. We deal with
+ * this problem using a stack of input sources, that is, a forward
+ * linked list of FILE_INFO structs.
+ *
+ * This stack is never empty during parsing; while an encounter with EOF
+ * can and will remove nested input sources, removing the last element
+ * in the stack will not work during parsing, and the EOF condition of
+ * the outermost input file remains until the parser folds up.
*/
-static int
-get_next_char(
- struct FILE_INFO *ip_file
+static struct FILE_INFO *
+_drop_stack_do(
+ struct FILE_INFO * head
)
{
- char ch;
-
- if (input_from_file)
- return FGETC(ip_file);
- else {
- if (remote_config.buffer[remote_config.pos] == '\0')
- return EOF;
- else {
- ip_file->col_no++;
- ch = remote_config.buffer[remote_config.pos++];
- if (ch == '\n') {
- ip_file->prev_line_col_no = ip_file->col_no;
- ++ip_file->line_no;
- ip_file->col_no = 1;
- }
- return ch;
- }
+ struct FILE_INFO * tail;
+ while (NULL != head) {
+ tail = head->st_next;
+ lex_close(head);
+ head = tail;
}
+ return head;
}
-static void
-push_back_char(
- struct FILE_INFO *ip_file,
- int ch
+
+
+/* Create a singleton input source on an empty lexer stack. This will
+ * fail if there is already an input source, or if the underlying disk
+ * file cannot be opened.
+ *
+ * Returns TRUE if a new input object was successfully created.
+ */
+int/*BOOL*/
+lex_init_stack(
+ const char * path,
+ const char * mode
+ )
+{
+ if (NULL != lex_stack || NULL == path)
+ return FALSE;
+
+ lex_stack = lex_open(path, mode);
+ return (NULL != lex_stack);
+}
+
+/* This removes *all* input sources from the stack, leaving the head
+ * pointer as NULL. Any attempt to parse in that state is likely to bomb
+ * with segmentation faults or the like.
+ *
+ * In other words: Use this to clean up after parsing, and do not parse
+ * anything until the next 'lex_init_stack()' succeeded.
+ */
+void
+lex_drop_stack()
+{
+ lex_stack = _drop_stack_do(lex_stack);
+}
+
+/* Flush the lexer input stack: This will nip all input objects on the
+ * stack (but keeps the current top-of-stack) and marks the top-of-stack
+ * as inactive. Any further calls to lex_getch yield only EOF, and it's
+ * no longer possible to push something back.
+ *
+ * Returns TRUE if there is a head element (top-of-stack) that was not
+ * in the force-eof mode before this call.
+ */
+int/*BOOL*/
+lex_flush_stack()
+{
+ int retv = FALSE;
+
+ if (NULL != lex_stack) {
+ retv = !lex_stack->force_eof;
+ lex_stack->force_eof = TRUE;
+ lex_stack->st_next = _drop_stack_do(
+ lex_stack->st_next);
+ }
+ return retv;
+}
+
+/* Push another file on the parsing stack. If the mode is NULL, create a
+ * FILE_INFO suitable for in-memory parsing; otherwise, create a
+ * FILE_INFO that is bound to a local/disc file. Note that 'path' must
+ * not be NULL, or the function will fail.
+ *
+ * Returns TRUE if a new info record was pushed onto the stack.
+ */
+int/*BOOL*/ lex_push_file(
+ const char * path,
+ const char * mode
)
{
- if (input_from_file)
- UNGETC(ch, ip_file);
- else {
- if (ch == '\n') {
- ip_file->col_no = ip_file->prev_line_col_no;
- ip_file->prev_line_col_no = -1;
- --ip_file->line_no;
+ struct FILE_INFO * next = NULL;
+
+ if (NULL != path) {
+ next = lex_open(path, mode);
+ if (NULL != next) {
+ next->st_next = lex_stack;
+ lex_stack = next;
}
- --ip_file->col_no;
+ }
+ return (NULL != next);
+}
- remote_config.pos--;
+/* Pop, close & free the top of the include stack, unless the stack
+ * contains only a singleton input object. In that case the function
+ * fails, because the parser does not expect the input stack to be
+ * empty.
+ *
+ * Returns TRUE if an object was successfuly popped from the stack.
+ */
+int/*BOOL*/
+lex_pop_file(void)
+{
+ struct FILE_INFO * head = lex_stack;
+ struct FILE_INFO * tail = NULL;
+
+ if (NULL != head) {
+ tail = head->st_next;
+ if (NULL != tail) {
+ lex_stack = tail;
+ lex_close(head);
+ }
}
+ return (NULL != tail);
+}
+
+/* Get include nesting level. This currently loops over the stack and
+ * counts elements; but since this is of concern only with an include
+ * statement and the nesting depth has a small limit, there's no
+ * bottleneck expected here.
+ *
+ * Returns the nesting level of includes, that is, the current depth of
+ * the lexer input stack.
+ *
+ * Note:
+ */
+size_t
+lex_level(void)
+{
+ size_t cnt = 0;
+ struct FILE_INFO *ipf = lex_stack;
+
+ while (NULL != ipf) {
+ cnt++;
+ ipf = ipf->st_next;
+ }
+ return cnt;
+}
+
+/* check if the current input is from a file */
+int/*BOOL*/
+lex_from_file(void)
+{
+ return (NULL != lex_stack) && (NULL != lex_stack->fpi);
+}
+
+struct FILE_INFO *
+lex_current()
+{
+ /* this became so simple, it could be a macro. But then,
+ * lex_stack needed to be global...
+ */
+ return lex_stack;
}
-
/* STATE MACHINES
* --------------
@@ -297,7 +487,7 @@ is_integer(
/* Check that all the remaining characters are digits */
for (; lexeme[i] != '\0'; i++) {
- if (!isdigit((unsigned char)lexeme[i]))
+ if (!isdigit((u_char)lexeme[i]))
return FALSE;
}
@@ -322,7 +512,7 @@ is_u_int(
int is_hex;
i = 0;
- if ('0' == lexeme[i] && 'x' == tolower((unsigned char)lexeme[i + 1])) {
+ if ('0' == lexeme[i] && 'x' == tolower((u_char)lexeme[i + 1])) {
i += 2;
is_hex = TRUE;
} else {
@@ -331,9 +521,9 @@ is_u_int(
/* Check that all the remaining characters are digits */
for (; lexeme[i] != '\0'; i++) {
- if (is_hex && !isxdigit((unsigned char)lexeme[i]))
+ if (is_hex && !isxdigit((u_char)lexeme[i]))
return FALSE;
- if (!is_hex && !isdigit((unsigned char)lexeme[i]))
+ if (!is_hex && !isdigit((u_char)lexeme[i]))
return FALSE;
}
@@ -357,14 +547,14 @@ is_double(
i++;
/* Read the integer part */
- for (; lexeme[i] && isdigit((unsigned char)lexeme[i]); i++)
+ for (; lexeme[i] && isdigit((u_char)lexeme[i]); i++)
num_digits++;
/* Check for the optional decimal point */
if ('.' == lexeme[i]) {
i++;
/* Check for any digits after the decimal point */
- for (; lexeme[i] && isdigit((unsigned char)lexeme[i]); i++)
+ for (; lexeme[i] && isdigit((u_char)lexeme[i]); i++)
num_digits++;
}
@@ -380,7 +570,7 @@ is_double(
return 1;
/* There is still more input, read the exponent */
- if ('e' == tolower((unsigned char)lexeme[i]))
+ if ('e' == tolower((u_char)lexeme[i]))
i++;
else
return 0;
@@ -390,7 +580,7 @@ is_double(
i++;
/* Now read the exponent part */
- while (lexeme[i] && isdigit((unsigned char)lexeme[i]))
+ while (lexeme[i] && isdigit((u_char)lexeme[i]))
i++;
/* Check if we are done */
@@ -455,7 +645,7 @@ create_string_token(
* ignore end of line whitespace
*/
pch = lexeme;
- while (*pch && isspace((unsigned char)*pch))
+ while (*pch && isspace((u_char)*pch))
pch++;
if (!*pch) {
@@ -476,37 +666,31 @@ create_string_token(
* value representing the token or type.
*/
int
-yylex(
- struct FILE_INFO *ip_file
- )
+yylex(void)
{
static follby followedby = FOLLBY_TOKEN;
- size_t i;
+ int i;
int instring;
int yylval_was_set;
int converted;
int token; /* The return value */
int ch;
- if (input_from_file)
- ip_file = fp[curr_include_level];
instring = FALSE;
yylval_was_set = FALSE;
do {
/* Ignore whitespace at the beginning */
- while (EOF != (ch = get_next_char(ip_file)) &&
+ while (EOF != (ch = lex_getch(lex_stack)) &&
isspace(ch) &&
!is_EOC(ch))
+
; /* Null Statement */
if (EOF == ch) {
- if (!input_from_file || curr_include_level <= 0)
+ if ( ! lex_pop_file())
return 0;
-
- FCLOSE(fp[curr_include_level]);
- ip_file = fp[--curr_include_level];
token = T_EOC;
goto normal_return;
@@ -531,15 +715,14 @@ yylex(
yytext[1] = '\0';
goto normal_return;
} else
- push_back_char(ip_file, ch);
+ lex_ungetch(ch, lex_stack);
/* save the position of start of the token */
- ip_file->prev_token_line_no = ip_file->line_no;
- ip_file->prev_token_col_no = ip_file->col_no;
+ lex_stack->tokpos = lex_stack->curpos;
/* Read in the lexeme */
i = 0;
- while (EOF != (ch = get_next_char(ip_file))) {
+ while (EOF != (ch = lex_getch(lex_stack))) {
yytext[i] = (char)ch;
@@ -553,7 +736,7 @@ yylex(
/* Read the rest of the line on reading a start
of comment character */
if ('#' == ch) {
- while (EOF != (ch = get_next_char(ip_file))
+ while (EOF != (ch = lex_getch(lex_stack))
&& '\n' != ch)
; /* Null Statement */
break;
@@ -571,7 +754,7 @@ yylex(
*/
if ('"' == ch) {
instring = TRUE;
- while (EOF != (ch = get_next_char(ip_file)) &&
+ while (EOF != (ch = lex_getch(lex_stack)) &&
ch != '"' && ch != '\n') {
yytext[i++] = (char)ch;
if (i >= COUNTOF(yytext))
@@ -583,18 +766,15 @@ yylex(
* not be pushed back, so we read another char.
*/
if ('"' == ch)
- ch = get_next_char(ip_file);
+ ch = lex_getch(lex_stack);
}
/* Pushback the last character read that is not a part
- * of this lexeme.
- * If the last character read was an EOF, pushback a
- * newline character. This is to prevent a parse error
- * when there is no newline at the end of a file.
+ * of this lexeme. This fails silently if ch is EOF,
+ * but then the EOF condition persists and is handled on
+ * the next turn by the include stack mechanism.
*/
- if (EOF == ch)
- push_back_char(ip_file, '\n');
- else
- push_back_char(ip_file, ch);
+ lex_ungetch(ch, lex_stack);
+
yytext[i] = '\0';
} while (i == 0);
@@ -627,7 +807,7 @@ yylex(
msyslog(LOG_ERR,
"Integer cannot be represented: %s",
yytext);
- if (input_from_file) {
+ if (lex_from_file()) {
exit(1);
} else {
/* force end of parsing */
@@ -640,7 +820,7 @@ yylex(
} else if (is_u_int(yytext)) {
yylval_was_set = TRUE;
if ('0' == yytext[0] &&
- 'x' == tolower((unsigned char)yytext[1]))
+ 'x' == tolower((unsigned long)yytext[1]))
converted = sscanf(&yytext[2], "%x",
&yylval.U_int);
else
@@ -650,7 +830,7 @@ yylex(
msyslog(LOG_ERR,
"U_int cannot be represented: %s",
yytext);
- if (input_from_file) {
+ if (lex_from_file()) {
exit(1);
} else {
/* force end of parsing */
@@ -735,14 +915,14 @@ lex_too_long:
yytext[min(sizeof(yytext) - 1, 50)] = 0;
msyslog(LOG_ERR,
"configuration item on line %d longer than limit of %lu, began with '%s'",
- ip_file->line_no, (u_long)min(sizeof(yytext) - 1, 50),
+ lex_stack->curpos.nline, (u_long)min(sizeof(yytext) - 1, 50),
yytext);
/*
* If we hit the length limit reading the startup configuration
* file, abort.
*/
- if (input_from_file)
+ if (lex_from_file())
exit(sizeof(yytext) - 1);
/*