/*
 * zipl - zSeries Initial Program Loader tool
 *
 * Scanner for zipl.conf configuration files
 *
 * Copyright IBM Corp. 2001, 2017
 *
 * s390-tools is free software; you can redistribute it and/or modify
 * it under the terms of the MIT license. See LICENSE for details.
 *
 */

static const char *VERSION_KEYWORD = "version";

/* Need ISOC99 function isblank() in ctype.h */
#ifndef __USE_ISOC99
#define __USE_ISOC99
#endif

/* Need GNU function strverscmp() in dirent.h */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <assert.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

#include <sys/stat.h>

#include "lib/util_base.h"

#include "boot.h"
#include "error.h"
#include "misc.h"
#include "scan.h"

/* Determines which keyword may be present in which section */
enum scan_key_state scan_key_table[SCAN_SECTION_NUM][SCAN_KEYWORD_NUM] = {
/*	 defa dump dump imag para parm ramd segm targ prom time defa tape mv
 *	 ult  to   tofs e    mete file isk  ent  et   pt   out  ultm      dump
 *			     rs                                 enu
 *
 *       targ targ targ targ targ defa kdum secu opti
 *       etba etty etge etbl etof ulta p    re   onal
 *       se   pe   omet ocks fset uto
 *                 ry   ize
 */
/* default auto */
	{opt, inv, inv, inv, inv, inv, inv, inv, req, opt, opt, inv, inv, inv,
	 opt, opt, opt, opt, opt, opt, inv, opt, inv},
/* default menu */
	{inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, req, inv, inv,
	 inv, inv, inv, inv, inv, inv, inv, opt, inv},
/* default section */
	{req, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv,
	 inv, inv, inv, inv, inv, inv, inv, opt, inv},
/* ipl		*/
	{inv, inv, inv, req, opt, opt, opt, inv, req, inv, inv, inv, inv, inv,
	 opt, opt, opt, opt, opt, inv, opt, opt, opt},
/* segment load */
	{inv, inv, inv, inv, inv, inv, inv, req, req, inv, inv, inv, inv, inv,
	 inv, inv, inv, inv, inv, inv, inv, inv, inv},
/* part dump	*/
	{inv, req, inv, inv, inv, inv, inv, inv, opt, inv, inv, inv, inv, inv,
	 inv, inv, inv, inv, inv, inv, inv, inv, inv},
/* fs dump	*/
	{inv, inv, req, inv, opt, opt, inv, inv, req, inv, inv, inv, inv, inv,
	 inv, inv, inv, inv, inv, inv, inv, inv, inv},
/* ipl tape	*/
	{inv, inv, inv, req, opt, opt, opt, inv, inv, inv, inv, inv, req, inv,
	 inv, inv, inv, inv, inv, inv, inv, inv, inv},
/* multi volume dump	*/
	{inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, inv, req,
	 inv, inv, inv, inv, inv, inv, inv, inv, inv}
};

/* Determines which keyword may be present in a menu section */
static enum scan_key_state scan_menu_key_table[SCAN_KEYWORD_NUM] = {
/* menu section */
	 opt, inv, inv, inv, inv, inv, inv, inv, req, opt, opt, inv, inv, inv,
	 opt, opt, opt, opt, opt, inv, inv, opt, inv
};

/* Mapping of keyword IDs to strings */
static const struct {
	char* keyword;
	enum scan_keyword_id id;
} keyword_list[] = {
	{ "defaultmenu", scan_keyword_defaultmenu},
	{ "default", scan_keyword_default },
	{ "dumptofs", scan_keyword_dumptofs },
	{ "dumpto", scan_keyword_dumpto },
	{ "image", scan_keyword_image },
	{ "mvdump", scan_keyword_mvdump },
	{ "parameters", scan_keyword_parameters },
	{ "parmfile", scan_keyword_parmfile },
	{ "ramdisk", scan_keyword_ramdisk },
	{ "segment", scan_keyword_segment },
	{ "targetbase", scan_keyword_targetbase},
	{ "targettype", scan_keyword_targettype},
	{ "targetgeometry", scan_keyword_targetgeometry},
	{ "targetblocksize", scan_keyword_targetblocksize},
	{ "targetoffset", scan_keyword_targetoffset},
	{ "target", scan_keyword_target},
	{ "prompt", scan_keyword_prompt},
	{ "timeout", scan_keyword_timeout},
	{ "tape", scan_keyword_tape},
	{ "kdump", scan_keyword_kdump},
	{ "secure", scan_keyword_secure},
	{ "optional", scan_keyword_optional},
};

/* List of keywords that are used without an assignment */
static const struct {
	char* keyword;
	enum scan_keyword_id id;
} keyword_only_list[] = {
	{ "defaultauto", scan_keyword_defaultauto}
};

/* Retrieve name of keyword identified by ID. */
char *
scan_keyword_name(enum scan_keyword_id id)
{
	unsigned int i;

	for (i=0; i < ARRAY_SIZE(keyword_list); i++) {
		if (id == keyword_list[i].id)
			return keyword_list[i].keyword;
	}
	for (i=0; i < ARRAY_SIZE(keyword_only_list); i++) {
		if (id == keyword_only_list[i].id)
			return keyword_only_list[i].keyword;
	}
	return "<unknown>";
}


/* Advance the current file pointer of file buffer FILE until the current
 * character is no longer a blank. Return 0 if at least one blank
 * character was encountered, non-zero otherwise. */
static int
skip_blanks(struct misc_file_buffer* file)
{
	int rc;

	rc = -1;
	for (; isblank(misc_get_char(file, 0)); file->pos++)
		rc = 0;
	return rc;
}


/* Advance the current file position to beginning of next line in file buffer
 * FILE or to end of file. */
static void
skip_line(struct misc_file_buffer* file)
{
	for (;; file->pos++) {
		switch (misc_get_char(file, 0)) {
		case '\n':
			file->pos++;
			return;
		case EOF:
			return;
		}
	}
}


/* Skip trailing blanks of line. On success, return zero and set the file
 * buffer position to beginning of next line or EOF. Return non-zero if
 * non-blank characters were found before end of line. */
static int
skip_trailing_blanks(struct misc_file_buffer* file)
{
	int current;

	for (;; file->pos++) {
		current = misc_get_char(file, 0);
		if (current == '\n') {
			file->pos++;
			return 0;
		} else if (current == EOF)
			return 0;
		else if (!isblank(current))
			return -1;
	}
}


static int
scan_section_heading(struct misc_file_buffer* file, struct scan_token* token,
		     int line)
{
	int start_pos;
	int end_pos;
	int current;
	char* name;

	for (start_pos=file->pos; misc_get_char(file, 0) != ']'; file->pos++) {
		current = misc_get_char(file, 0);
		switch (current) {
		case EOF:
		case '\n':
			error_reason("Line %d: unterminated section heading",
				     line);
			return -1;
		default:
			if (!(isalnum(current) || ispunct(current))) {
				error_reason("Line %d: invalid character in "
					     "section name", line);
				return -1;
			}
		}
	}
	end_pos = file->pos;
	if (end_pos == start_pos) {
		error_reason("Line %d: empty section name", line);
		return -1;
	}
	file->pos++;
	if (skip_trailing_blanks(file)) {
		error_reason("Line %d: unexpected characters after section "
			     "name", line);
		return -1;
	}
	name = (char *) misc_malloc(end_pos - start_pos + 1);
	if (name == NULL)
		return -1;
	memcpy(name, &file->buffer[start_pos], end_pos - start_pos);
	name[end_pos - start_pos] = 0;
	token->id = scan_id_section_heading;
	token->line = line;
	token->content.section.name = name;
	return 0;
}


static int
scan_menu_heading(struct misc_file_buffer* file, struct scan_token* token,
		  int line)
{
	int start_pos;
	int end_pos;
	int current;
	char* name;

	for (start_pos=file->pos; ; file->pos++) {
		current = misc_get_char(file, 0);
		if ((current == EOF) || (current == '\n'))
			break;
		else if (isblank(current))
			break;
		else if (!isalnum(current)) {
			error_reason("Line %d: invalid character in menu name ",
				     line);
			return -1;
		}
	}
	end_pos = file->pos;
	if (skip_trailing_blanks(file)) {
		error_reason("Line %d: blanks not allowed in menu name",
			     line);
		return -1;
	}
	if (end_pos == start_pos) {
		error_reason("Line %d: empty menu name", line);
		return -1;
	}
	name = (char *) misc_malloc(end_pos - start_pos + 1);
	if (name == NULL)
		return -1;
	memcpy(name, &file->buffer[start_pos], end_pos - start_pos);
	name[end_pos - start_pos] = 0;
	token->id = scan_id_menu_heading;
	token->line = line;
	token->content.menu.name = name;
	return 0;
}


static int
scan_number(struct misc_file_buffer* file, int* number, int line)
{
	int start_pos;
	int old_number;
	int new_number;

	old_number = 0;
	new_number = 0;
	start_pos = file->pos;
	for (; isdigit(misc_get_char(file, 0)); file->pos++) {
		new_number = old_number*10 + misc_get_char(file, 0) - '0';
		if (new_number < old_number) {
			error_reason("Line %d: number too large", line);
			return -1;
		}
		old_number = new_number;
	}
	if (file->pos == start_pos) {
		error_reason("Line %d: number expected", line);
		return -1;
	}
	*number = new_number;
	return 0;
}


static int
scan_value_string(struct misc_file_buffer* file, char** value, int line)
{
	int quote;
	int start_pos;
	int end_pos;
	int last_nonspace;
	int current;
	char* string;

	current = misc_get_char(file, 0);
	if (current == '\"') {
		quote = '\"';
		file->pos++;
	} else if (current == '\'') {
		quote = '\'';
		file->pos++;
	} else quote = 0;
	last_nonspace = -1;
	for (start_pos=file->pos;; file->pos++) {
		current = misc_get_char(file, 0);
		if ((current == EOF) || (current == '\n')) {
			break;
		} else if (quote) {
			if (current == quote)
				break;
		} else if (!isblank(current))
			last_nonspace = file->pos;
	}
	end_pos = file->pos;
	if (quote) {
		if (current != quote) {
			error_reason("Line %d: unterminated quotes", line);
			return -1;
		}
	} else if (last_nonspace >= 0)
		end_pos = last_nonspace + 1;
	string = (char *) misc_malloc(end_pos - start_pos + 1);
	if (string == NULL)
		return -1;
	if (end_pos > start_pos)
		memcpy(string, &file->buffer[start_pos], end_pos - start_pos);
	string[end_pos - start_pos] = 0;
	*value = string;
	if (quote)
		file->pos++;
	return 0;
}


static int
scan_number_assignment(struct misc_file_buffer* file, struct scan_token* token,
		      int line)
{
	int rc;

	rc = scan_number(file, &token->content.number.number, line);
	if (rc)
		return rc;
	skip_blanks(file);
	if (misc_get_char(file, 0) != '=') {
		error_reason("Line %d: number expected as keyword", line);
		return -1;
	}
	file->pos++;
	skip_blanks(file);
	rc = scan_value_string(file, &token->content.number.value, line);
	if (rc)
		return rc;
	if (skip_trailing_blanks(file)) {
		error_reason("Line %d: unexpected characters at end of line",
			     line);
		return -1;
	}
	token->id = scan_id_number_assignment;
	token->line = line;
	return 0;
}

static int
match_keyword(struct misc_file_buffer* file, const char* keyword)
{
	unsigned int i;

	for (i=0; i<strlen(keyword); i++)
		if (misc_get_char(file, i) != keyword[i])
			return -1;
	return 0;
}


static int
scan_keyword(struct misc_file_buffer* file, enum scan_keyword_id* id, int line)
{
	unsigned int i;

	for (i=0; i < ARRAY_SIZE(keyword_list); i++)
		if (match_keyword(file, keyword_list[i].keyword) == 0) {
			file->pos += strlen(keyword_list[i].keyword);
			*id = keyword_list[i].id;
			return 0;
		}
	error_reason("Line %d: unknown keyword", line);
	return -1;
}


static int
scan_keyword_assignment(struct misc_file_buffer* file, struct scan_token* token,
		       int line)
{
	int rc;

	rc = scan_keyword(file, &token->content.keyword.keyword, line);
	if (rc)
		return rc;
	skip_blanks(file);
	if (misc_get_char(file, 0) != '=') {
		error_reason("Line %d: unexpected characters after keyword",
			     line);
		return -1;
	}
	file->pos++;
	skip_blanks(file);
	rc = scan_value_string(file, &token->content.keyword.value, line);
	if (rc)
		return rc;
	if (skip_trailing_blanks(file)) {
		error_reason("Line %d: unexpected characters at end of line",
			     line);
		return -1;
	}
	token->id = scan_id_keyword_assignment;
	token->line = line;
	return 0;
}


static int
scan_keyword_only(struct misc_file_buffer* file, enum scan_keyword_id* id,
		  int line)
{
	unsigned int i;

	for (i=0; i < ARRAY_SIZE(keyword_only_list); i++)
		if (match_keyword(file, keyword_only_list[i].keyword) == 0) {
			file->pos += strlen(keyword_only_list[i].keyword);
			*id = keyword_only_list[i].id;
			return 0;
		}
	error_reason("Line %d: unknown keyword", line);
	return -1;
}


static int
scan_keyword_only_statement(struct misc_file_buffer* file,
			    struct scan_token* token, int line)
{
	int rc;

	rc = scan_keyword_only(file, &token->content.keyword.keyword, line);
	if (rc)
		return rc;
	if (skip_trailing_blanks(file)) {
		error_reason("Line %d: unexpected characters at end of line",
			     line);
		return -1;
	}
	token->id = scan_id_keyword_only;
	token->line = line;
	return 0;
}


static int
search_line_for(struct misc_file_buffer* file, int search)
{
	int i;
	int current;

	for (i=0; ; i++) {
		current = misc_get_char(file, i);
		switch (current) {
		case EOF:
		case '\n':
			return 0;
		default:
			if (current == search)
				return 1;
		}
	}
}


void
scan_free(struct scan_token* array)
{
	int i;

	for (i=0; array[i].id != 0; i++) {
		switch (array[i].id) {
		case scan_id_section_heading:
			if (array[i].content.section.name != NULL) {
				free(array[i].content.section.name);
				array[i].content.section.name = NULL;
			}
			break;
		case scan_id_menu_heading:
			if (array[i].content.menu.name != NULL) {
				free(array[i].content.menu.name);
				array[i].content.menu.name = NULL;
			}
			break;
		case scan_id_keyword_assignment:
			if (array[i].content.keyword.value != NULL) {
				free(array[i].content.keyword.value);
				array[i].content.keyword.value = NULL;
			}
			break;
		case scan_id_number_assignment:
			if (array[i].content.number.value != NULL) {
				free(array[i].content.number.value);
				array[i].content.number.value = NULL;
			}
			break;
		default:
			break;
		}
	}
	free(array);
}


#define INITIAL_ARRAY_LENGTH 40

/* Scan file FILENAME for tokens. Upon success, return the number allocated
 * tokens and set TOKEN to point to a NULL-terminated array of scan_tokens,
 * i.e. the token id of the last token is 0. Return non-zero otherwise. */
int
scan_file(const char* filename, struct scan_token** token)
{
	struct misc_file_buffer file;
	struct scan_token* array;
	struct scan_token* buffer;
	int pos;
	int size;
	int current;
	int rc;
	int line;

	rc = misc_get_file_buffer(filename, &file);
	if (rc)
		return rc;
	size = INITIAL_ARRAY_LENGTH;
	pos = 0;
	array = (struct scan_token*) misc_malloc(size *
						 sizeof(struct scan_token));
	if (array == NULL) {
		misc_free_file_buffer(&file);
		return -1;
	}
	memset(array, 0, size * sizeof(struct scan_token));
	line = 1;
	while ((size_t) file.pos < file.length) {
		skip_blanks(&file);
		current = misc_get_char(&file, 0);
		switch (current) {
		case '[':
			file.pos++;
			rc = scan_section_heading(&file, &array[pos++], line);
			break;
		case ':':
			file.pos++;
			rc = scan_menu_heading(&file, &array[pos++], line);
			break;
		case '#':
			file.pos++;
			skip_line(&file);
			rc = 0;
			break;
		case '\n':
			file.pos++;
			rc = 0;
			break;
		case EOF:
			rc = 0;
			break;
		default:
			if (search_line_for(&file, '=')) {
				if (isdigit(current))
					rc = scan_number_assignment(
						&file, &array[pos++], line);
				else
					rc = scan_keyword_assignment(
						&file, &array[pos++], line);
			} else {
				rc = scan_keyword_only_statement(&file,
							&array[pos++], line);
			}
		}
		if (rc)
			break;
		line++;
		/* Enlarge array if there is only one position left */
		if (pos + 1 >= size) {
			size *= 2;
			buffer = (struct scan_token *)
					misc_malloc(size *
						    sizeof(struct scan_token));
			if (buffer == NULL) {
				rc = -1;
				break;
			}
			memset(buffer, 0, size * sizeof(struct scan_token));
			memcpy(buffer, array, pos*sizeof(struct scan_token));
			free(array);
			array = buffer;
		}
	}
	misc_free_file_buffer(&file);
	if (rc) {
		scan_free(array);
		return rc;
	}

	*token = array;
	return size;
}


static int
bls_filter_by_names(const struct dirent *ent)
{
	int offset = strlen(ent->d_name) - strlen(".conf");

	if (offset < 0)
		return 0;

	return strncmp(ent->d_name + offset, ".conf", strlen(".conf")) == 0;
}

struct version {
	char *line; /* pointer to a line with version keyword */
	int offset; /* offset of version value in the line */
};

/*
 * Locate version in bls file represented by ENT
 */
static void get_version(const struct dirent *ent, struct version *v)
{
	char *line = NULL;
	size_t len = 0;
	char *d_name;
	FILE *stream;
	ssize_t read;

	memset(v, 0, sizeof(*v));
	d_name = misc_make_path((char *)blsdir, (char *)ent->d_name);
	if (!d_name)
		return;

	stream = fopen(d_name, "r");
	free(d_name);
	if (!stream)
		return;

	while ((read = getline(&line, &len, stream)) != -1) {
		if (line[read - 1] == '\n') {
			line[read - 1] = '\0';
			read--;
		}
		if ((size_t)read <= strlen(VERSION_KEYWORD) + 1)
			continue;
		if (strcmp(VERSION_KEYWORD, line) > 0)
			continue;
		if (!isblank(line[strlen(VERSION_KEYWORD)]))
			continue;
		/* skip blanks */
		v->offset = strlen(VERSION_KEYWORD) + 1;
		while (v->offset < read - 1 && isblank(line[v->offset]))
			v->offset++;
		if (isblank(line[v->offset]))
			/*
			 * all characters after the keyword
			 * are blanks. Invalid version
			 */
			continue;
		v->line = line;
		fclose(stream);
		return;
	}
	free(line);
	fclose(stream);
}

static void put_version(struct version *v)
{
	free(v->line);
}

/**
 * Check version in bls file represented by ENT.
 * Return 1 if version is valid. Otherwise return 0
 */
static int bls_filter_by_versions(const struct dirent *ent)
{
	struct version v;

	if (bls_filter_by_names(ent) == 0)
		return 0;

	get_version(ent, &v);
	if (v.line) {
		put_version(&v);
		return 1;
	}
	return 0;
}


static int
bls_sort_by_names(const struct dirent **ent_a, const struct dirent **ent_b)
{
	return strverscmp((*ent_a)->d_name, (*ent_b)->d_name);
}

static int
bls_sort_by_versions(const struct dirent **ent_a, const struct dirent **ent_b)
{
	struct version v1, v2;
	int ret;

	get_version(*ent_a, &v1);
	get_version(*ent_b, &v2);
	/*
	 * Both versions are valid.
	 * It is guaranteed by bls_filter_by_versions()
	 */
	ret = strverscmp(v1.line + v1.offset, v2.line + v2.offset);

	put_version(&v1);
	put_version(&v2);

	return ret;
}

static int
scan_append_section_heading(struct scan_token* scan, int* index, char* name);
static int
scan_append_keyword_assignment(struct scan_token* scan, int* index,
			       enum scan_keyword_id id, char* value);


static int
scan_bls_field(struct misc_file_buffer *file, struct scan_token* scan,
	       int* index)
{
	int current;
	int key_start, key_end;
	int val_start, val_end;
	char *val;

	for (key_start = file->pos; ; file->pos++) {
		current = misc_get_char(file, 0);

		if (isblank(current)) {
			key_end = file->pos;
			skip_blanks(file);
			val_start = file->pos;
			break;
		}

		if (!isalnum(current) && current != '_' && current != '-')
			return -1;
	}

	for (; ; file->pos++) {
		current = misc_get_char(file, 0);

		if (current == '\n' || current == EOF)
			break;
	}

	val_end = file->pos;

	while (val_end > val_start && isblank(file->buffer[val_end - 1]))
		val_end--;

	file->buffer[key_end] = '\0';
	file->buffer[val_end] = '\0';

	if (strncmp("title", &file->buffer[key_start], key_end - key_start) == 0) {
		scan_append_section_heading(scan, index, &file->buffer[val_start]);
	}

	if (strncmp("linux", &file->buffer[key_start], key_end - key_start) == 0) {
		misc_asprintf(&val, "%s", &file->buffer[val_start]);
		scan_append_keyword_assignment(scan, index, scan_keyword_image, val);
		free(val);
	}

	if (strncmp("options", &file->buffer[key_start], key_end - key_start) == 0) {
		scan_append_keyword_assignment(scan, index, scan_keyword_parameters,
					       &file->buffer[val_start]);
	}

	if (strncmp("initrd", &file->buffer[key_start], key_end - key_start) == 0) {
		misc_asprintf(&val, "%s", &file->buffer[val_start]);
		scan_append_keyword_assignment(scan, index, scan_keyword_ramdisk, val);
		free(val);
	}

	return 0;
}

/**
 * find a line with keyword "title" and move it to the top
 */
static int sort_bls_fields(struct misc_file_buffer *file, char *filename)
{
	bool is_title = false;
	size_t title_len = 0;
	size_t title_off = 0;
	int nr_titles = 0;
	char *title;
	int current;
	size_t len;

	while (file->length - file->pos > 4 /* for "title" */) {
		if (strncmp("title", &file->buffer[file->pos], 5) == 0) {
			is_title = true;
			nr_titles++;
			title_off = file->pos;
		}
		for (len = 0;; file->pos++, len++) {
			current = misc_get_char(file, 0);
			if (current == '\n' || current == EOF)
				break;
		}
		if (is_title == true)
			title_len = len;
		if (current == EOF)
			break;
		file->pos++;
	}
	file->pos = 0;

	if (nr_titles == 0) {
		error_reason("no title in %s", filename);
		return -1;
	}
	if (nr_titles > 1) {
		error_reason("more than one title in %s", filename);
		return -1;
	}
	if (title_off == 0)
		return 0;

	title = misc_malloc(title_len);
	if (!title)
		return -1;
	/*
	 * copy the title field w/o trailing '\n' to the temporary buffer
	 */
	memcpy(title, &file->buffer[title_off], title_len);
	/*
	 * shift preceded memory region w/o trailing '\n' to the right
	 */
	assert(file->buffer[title_off - 1] == '\n');
	memmove(&file->buffer[title_len + 1], &file->buffer[0], title_off - 1);
	file->buffer[title_len] = '\n';
	memcpy(&file->buffer[0], title, title_len);

	free(title);
	return 0;
}


/* Search scanned tokens SCAN for a section/menu heading (according to
 * TYPE) of the given NAME, beginning at token OFFSET. Return the index of
 * the section/menu heading on success, a negative value on error. */
int
scan_find_section(struct scan_token* scan, char* name, enum scan_id type,
		  int offset)
{
	int i;

	for (i=offset; (int) scan[i].id != 0; i++) {
		if ((scan[i].id == scan_id_section_heading) &&
		    (type == scan_id_section_heading))
			if (strcmp(scan[i].content.section.name, name) == 0)
				return i;
		if ((scan[i].id == scan_id_menu_heading) &&
		    (type == scan_id_menu_heading))
			if (strcmp(scan[i].content.menu.name, name) == 0)
				return i;
	}
	return -1;
}


/* Check whether a string contains a load address as comma separated hex
 * value at end of string. */
static int
contains_address(char* string)
{
	unsigned long long result;

	/* Find trailing comma */
	string = strrchr(string, ',');
	if (string != NULL) {
		/* Try to scan a hexadecimal address */
		if (sscanf(string + 1, "%llx", &result) == 1) {
			return 1;
		}
	}
	return 0;
}


enum scan_section_type
scan_get_section_type(char* keyword[])
{
	if (keyword[(int) scan_keyword_tape] != NULL)
		return section_ipl_tape;
	else if (keyword[(int) scan_keyword_image] != NULL)
		return section_ipl;
	else if (keyword[(int) scan_keyword_segment] != NULL)
		return section_segment;
	else if (keyword[(int) scan_keyword_dumpto] != NULL)
		return section_dump;
	else if (keyword[(int) scan_keyword_dumptofs] != NULL)
		return section_dumpfs;
	else if (keyword[(int) scan_keyword_mvdump] != NULL)
		return section_mvdump;
	else
		return section_invalid;
}

enum scan_target_type
scan_get_target_type(char *type)
{
	if (strcasecmp(type, "SCSI") == 0)
		return target_type_scsi;
	else if (strcasecmp(type, "FBA") == 0)
		return target_type_fba;
	else if (strcasecmp(type, "LDL") == 0)
		return target_type_ldl;
	else if (strcasecmp(type, "CDL") == 0)
		return target_type_cdl;
	return target_type_invalid;
}

/* Check section data for correctness. KEYWORD[keyword_id] defines whether
 * a keyword is present, LINE[keyword_id] specifies in which line a keyword
 * was found or NULL when specified on command line, NAME specifies the
 * section name or NULL when specified on command line. */
int
scan_check_section_data(char* keyword[], int* line, char* name,
			int section_line, enum scan_section_type* type)
{
	char* main_keyword;
	int i;

	main_keyword = "";
	/* Find out what type this section is */
	if (*type == section_invalid) {
		if (keyword[(int) scan_keyword_tape]) {
			*type = section_ipl_tape;
			main_keyword = scan_keyword_name(scan_keyword_tape);
		} else if (keyword[(int) scan_keyword_image]) {
			*type = section_ipl;
			main_keyword = scan_keyword_name(scan_keyword_image);
		} else if (keyword[(int) scan_keyword_segment]) {
			*type = section_segment;
			main_keyword = scan_keyword_name(scan_keyword_segment);
		} else if (keyword[(int) scan_keyword_dumpto]) {
			*type = section_dump;
			main_keyword = scan_keyword_name(scan_keyword_dumpto);
		} else if (keyword[(int) scan_keyword_dumptofs]) {
			error_reason("Option dumptofs is deprecated, "
				     "use dumpto instead");
			return -1;
		} else if (keyword[(int) scan_keyword_mvdump]) {
			*type = section_mvdump;
			main_keyword = scan_keyword_name(scan_keyword_mvdump);
		} else {
			error_reason("Line %d: section '%s' must contain "
				     "either one of keywords 'image', "
				     "'segment', 'dumpto', 'dumptofs', "
				     "'mvdump' or 'tape'", section_line, name);
			return -1;
		}
	}
	/* Check keywords */
	for (i=0; i < SCAN_KEYWORD_NUM; i++) {
		switch (scan_key_table[(int) *type][i]) {
		case req:
			/* Check for missing data */
			if ((keyword[i] == 0) && (line != NULL)) {
				/* Missing keyword in config file section */
				error_reason("Line %d: missing keyword '%s' "
					     "in section '%s'", section_line,
					     scan_keyword_name(
						     (enum scan_keyword_id) i),
					     name);
				return -1;
			} else if ((keyword[i] == 0) && (line == NULL)) {
				/* Missing keyword on command line */
				error_reason("Option '%s' required when "
					     "specifying '%s'",
					     scan_keyword_name(
						     (enum scan_keyword_id) i),
					     main_keyword);

				return -1;
			}
			break;
		case inv:
			/* Check for invalid data */
			if ((keyword[i] != 0) && (line != NULL)) {
				/* Invalid keyword in config file section */
				error_reason("Line %d: keyword '%s' not "
					     "allowed in section '%s'",
					     line[i],
					     scan_keyword_name(
						     (enum scan_keyword_id) i),
					     name);
				return -1;
			} else if ((keyword[i] != 0) && (line == NULL)) {
				/* Invalid keyword on command line */
				error_reason("Only one of options '%s' and "
					     "'%s' allowed", main_keyword,
					     scan_keyword_name(
						     (enum scan_keyword_id) i));
				return -1;
			}
			break;
		case opt:
			break;
		}
	}
	/* Additional check needed for segment */
	i = (int) scan_keyword_segment;
	if (keyword[i] != NULL) {
		if (!contains_address(keyword[i])) {
			if (line != NULL) {
				error_reason("Line %d: keyword 'segment' "
					     "requires "
					     "load address", line[i]);
			} else {
				error_reason("Option 'segment' requires "
					     "load address");
			}
			return -1;
		}
	}
	return 0;
}


static int
check_blocksize(int size)
{
	switch (size) {
	case 512:
	case 1024:
	case 2048:
	case 4096:
		return 0;
	}
	return -1;
}

static int
scan_count_target_keywords(char* keyword[])
{
	int num = 0;

	if (keyword[(int) scan_keyword_target])
		num++;
	if (keyword[(int) scan_keyword_targetbase])
		num++;
	if (keyword[(int) scan_keyword_targettype])
		num++;
	if (keyword[(int) scan_keyword_targetgeometry])
		num++;
	if (keyword[(int) scan_keyword_targetblocksize])
		num++;
	if (keyword[(int) scan_keyword_targetoffset])
		num++;
	return num;
}

static int bls_scandir(struct dirent ***bls_entries)
{
	struct dirent **entries1;
	struct dirent **entries2;
	int n1, n2;

	/* arrange by names */
	n1 = scandir(blsdir, &entries1,
		     bls_filter_by_names, bls_sort_by_names);
	if (n1 <= 0)
		return n1;
	/* arrange by versions */
	n2 = scandir(blsdir, &entries2,
		     bls_filter_by_versions, bls_sort_by_versions);

	if (n2 <= 0 || n2 < n1) {
		/*
		 * failed to sort by versions,
		 * fall back to sorting by filenames
		 */
		*bls_entries = entries1;
		while (n2--)
			free(entries2[n2]);
		free(entries2);
		return n1;
	}
	/* use arrangement by versions */
	*bls_entries = entries2;
	while (n1--)
		free(entries1[n1]);
	free(entries1);
	return n2;
}

int
scan_check_target_data(char* keyword[], int* line)
{
	int cylinders, heads, sectors;
	char dummy;
	int number;
	enum scan_keyword_id errid;

	if (keyword[(int) scan_keyword_targetbase] == 0) {
		if (keyword[(int) scan_keyword_targettype] != 0)
			errid = scan_keyword_targettype;
		else if ((keyword[(int) scan_keyword_targetgeometry] != 0))
			errid = scan_keyword_targetgeometry;
		else if ((keyword[(int) scan_keyword_targetblocksize] != 0))
			errid = scan_keyword_targetblocksize;
		else if ((keyword[(int) scan_keyword_targetoffset] != 0))
			errid = scan_keyword_targetoffset;
		else
			return 0;
		if (line != NULL)
			error_reason("Line %d: keyword 'targetbase' required "
				"when specifying '%s'",
				line[(int) errid], scan_keyword_name(errid));
		else
			error_reason("Option 'targetbase' required when "
				"specifying '%s'",
				scan_keyword_name(errid));
		return -1;
	}
	if (keyword[(int) scan_keyword_targettype] == 0) {
		if (line != NULL)
			error_reason("Line %d: keyword 'targettype' "
				"required when specifying 'targetbase'",
				line[(int) scan_keyword_targetbase]);
		else
			error_reason("Option 'targettype' required "
				     "when specifying 'targetbase'");
		return -1;
	}
	switch (scan_get_target_type(keyword[(int) scan_keyword_targettype])) {
	case target_type_cdl:
	case target_type_ldl:
		if ((keyword[(int) scan_keyword_targetgeometry] != 0))
			break;
		if (line != NULL)
			error_reason("Line %d: keyword 'targetgeometry' "
				"required when specifying 'targettype' %s",
				line[(int) scan_keyword_targettype],
				keyword[(int) scan_keyword_targettype]);
		else
			error_reason("Option 'targetgeometry' required when "
				"specifying 'targettype' %s",
				keyword[(int) scan_keyword_targettype]);
		return -1;
	case target_type_scsi:
	case target_type_fba:
		if ((keyword[(int) scan_keyword_targetgeometry] == 0))
			break;
		if (line != NULL)
			error_reason("Line %d: keyword "
				"'targetgeometry' not allowed for "
				"'targettype' %s",
				line[(int) scan_keyword_targetgeometry],
				keyword[(int) scan_keyword_targettype]);
		else
			error_reason("Keyword 'targetgeometry' not "
				"allowed for 'targettype' %s",
				keyword[(int) scan_keyword_targettype]);
		return -1;
	case target_type_invalid:
		if (line != NULL)
			error_reason("Line %d: Unrecognized 'targettype' value "
				"'%s'",
				line[(int) scan_keyword_targettype],
				keyword[(int) scan_keyword_targettype]);
		else
			error_reason("Unrecognized 'targettype' value '%s'",
				keyword[(int) scan_keyword_targettype]);
		return -1;
	}
	if (keyword[(int) scan_keyword_targetgeometry] != 0) {
		if ((sscanf(keyword[(int) scan_keyword_targetgeometry],
		    "%d,%d,%d %c", &cylinders, &heads, &sectors, &dummy)
		    != 3) || (cylinders <= 0) || (heads <= 0) ||
		    (sectors <= 0)) {
			if (line != NULL)
				error_reason("Line %d: Invalid target geometry "
					"'%s'", line[
					(int) scan_keyword_targetgeometry],
					keyword[
					(int) scan_keyword_targetgeometry]);
			else
				error_reason("Invalid target geometry '%s'",
					keyword[
					(int) scan_keyword_targetgeometry]);
			return -1;
		}
	}
	if (keyword[(int) scan_keyword_targetblocksize] == 0) {
		if (line != NULL)
			error_reason("Line %d: Keyword 'targetblocksize' "
				"required when specifying 'targetbase'",
				line[(int) scan_keyword_targetbase]);
		else
			error_reason("Option 'targetblocksize' required when "
				"specifying 'targetbase'");
		return -1;
	}
	if ((sscanf(keyword[(int) scan_keyword_targetblocksize], "%d %c",
	    &number, &dummy) != 1) || check_blocksize(number)) {
		if (line != NULL)
			error_reason("Line %d: Invalid target blocksize '%s'",
				line[(int) scan_keyword_targetblocksize],
				keyword[(int) scan_keyword_targetblocksize]);
		else
			error_reason("Invalid target blocksize '%s'",
				keyword[(int) scan_keyword_targetblocksize]);
		return -1;
	}
	if (keyword[(int) scan_keyword_targetoffset] == 0) {
		if (line != NULL)
			error_reason("Line %d: Keyword 'targetoffset' "
				"required when specifying 'targetbase'",
				line[(int) scan_keyword_targetbase]);
		else
			error_reason("Option 'targetoffset' required when "
				"specifying 'targetbase'");
		return -1;
	}
	if (sscanf(keyword[(int) scan_keyword_targetoffset], "%d %c",
	    &number, &dummy) != 1) {
		if (line != NULL)
			error_reason("Line %d: Invalid target offset '%s'",
				line[(int) scan_keyword_targetoffset],
				keyword[(int) scan_keyword_targetoffset]);
		else
			error_reason("Invalid target offset '%s'",
				keyword[(int) scan_keyword_targetoffset]);
		return -1;
	}
	return 0;
}


static int
scan_is_delimiter(enum scan_id id)
{
	return id == scan_id_empty || id == scan_id_section_heading ||
	       id == scan_id_menu_heading;
}


/*
 * Copy data from scan array to keyword/number assignment array. Return 0
 * on success, non-zero otherwise.
 *
 * If keyword X is specified in the section, keyword[X] contains the value of
 * that keyword and keyword_line[x] the line in which the keyword was defined.
 * Otherwise keyword[X] contains NULL.
 * If number assignment X is specified in the section, num[X - 1] contains
 * the value of that assignment and num_line the line in which the assignment
 * was found. Otherwise num[X - 1] contains NULL. If num is NULL, an error
 * is reported if a number assignment is found.
 */
static int
scan_get_section_keywords(struct scan_token* scan, int* index, char* name,
			  char* keyword[], int keyword_line[], char** num,
			  int* num_line)
{
	int i;
	int id;
	int key;
	int line;
	int number;
	char* value;

	/* Initialize array data. */
	for (i = 0; i < SCAN_KEYWORD_NUM; i++) {
		keyword[i] = NULL;
		keyword_line[i] = 0;
	}
	if (num) {
		for (i = 0; i < BOOT_MENU_ENTRIES; i++) {
			num[i] = NULL;
			num_line[i] = 0;
		}
	}
	/* Extract data for keyword and number assignments. */
	for (i = *index + 1; !scan_is_delimiter(scan[i].id); i++) {
		id = (int) scan[i].id;
		line = scan[i].line;
		/* Handle number assignments first. */
		if (id == scan_id_number_assignment) {
			number = scan[i].content.number.number;
			/* Check if number assignments are expected. */
			if (!num) {
				error_reason("Line %d: number assignment not "
					     "allowed in section '%s'",
					     line, name);
				return -1;
			}
			/* Check for valid numbers. */
			if (number <= 0) {
				error_reason("Line %d: position must be "
					     "greater than zero",
					     line);
                                return -1;

			}
			if (number > BOOT_MENU_ENTRIES) {
				error_reason("Line %d: position must not "
					     "exceed %d", line,
					     BOOT_MENU_ENTRIES);
                                return -1;
			}
			/* Rule 10 */
			if (num[number - 1]) {
				error_reason("Line %d: position %d already "
					     "specified", line, number);
				return -1;
			}
			num[number - 1] = scan[i].content.number.value;
			num_line[number - 1] = line;
			continue;
		}
		/* Handle keyword assignments. */
		if (id == scan_id_keyword_assignment) {
			key = scan[i].content.keyword.keyword;
			value = scan[i].content.keyword.value;
		} else if (id == scan_id_keyword_only) {
			key = scan[i].content.keyword_only.keyword;
			/* Define a dummy value. */
			value = "";
		} else {
			continue;
		}
		if (keyword[key]) {
			/* Rule 5 */
			error_reason("Line %d: keyword '%s' already specified",
				     line, scan_keyword_name(key));
			return -1;
		}
		keyword[key] = value;
		keyword_line[key] = line;
	}
	*index = i;

	return 0;
}


/*
 * Get the default target defined either in the defaultboot section or the
 * menu section for the defaultmenu if there is a default menu defined.
 */
static char *scan_get_default_target(struct scan_token *scan)
{
	int keyword_line[SCAN_KEYWORD_NUM];
	char *keyword[SCAN_KEYWORD_NUM];
	int num_line[BOOT_MENU_ENTRIES];
	char *num[BOOT_MENU_ENTRIES];
	int i, j, rc;

	/* Find the defaultboot section */
	i = scan_find_section(scan, DEFAULTBOOT_SECTION,
			      scan_id_section_heading, 0);
	if (i < 0) {
		error_reason("No '%s' section found and no section specified on command line",
			     DEFAULTBOOT_SECTION);
		return NULL;
	}

	/* Get keyword and number data */
	rc = scan_get_section_keywords(scan, &i, DEFAULTBOOT_SECTION, keyword,
				       keyword_line, NULL, NULL);
	if (rc) {
		error_reason("Could not get keywords for '%s' section",
			     DEFAULTBOOT_SECTION);
		return NULL;
	}

	/* Check if a defaultmenu is set */
	i = (int) scan_keyword_defaultmenu;
	if (keyword[i]) {
		j = scan_find_section(scan, keyword[i],
				      scan_id_menu_heading, 0);
		if (j < 0) {
			error_reason("No '%s' section found", keyword[i]);
			return NULL;
		}

		/* Get keyword and number data */
		rc = scan_get_section_keywords(scan, &j, keyword[i], keyword,
					       keyword_line, num, num_line);
		if (rc) {
			error_reason("Could not get keywords for '%s' section",
				     keyword[i]);
			return NULL;
		}
	}

	return keyword[(int) scan_keyword_target];
}

#define BLS_TOKEN_MAX 5 /* section heading, image, ramdisk, parameter, target */

int scan_bls(struct scan_token **token, int scan_size)
{
	int size, remaining = 0, n, current, rc = -1, count = 0;
	struct scan_token *array = *token;
	struct misc_file_buffer file;
	struct dirent **bls_entries;
	struct scan_token *buffer;
	char filename[PATH_MAX];
	char *target = NULL;
	struct stat sb;

	if (!(stat(blsdir, &sb) == 0 && S_ISDIR(sb.st_mode)))
		return 0;

	n = bls_scandir(&bls_entries);
	if (n <= 0)
		return n;

	while (array[count].id != 0)
		count++;

	remaining = scan_size - count;

	/* The array of scanned tokens is allocated when the zipl config file is
	 * parsed. Its size is a multiple of INITIAL_ARRAY_LENGTH so it may have
	 * enough space to scan all the tokens that are defined in the BLS files.
	 * Calculate if is enough assuming that a BLS fragment can contain up to
	 * BLS_TOKEN_MAX.
	 */
	if (remaining < n * BLS_TOKEN_MAX) {
		size = scan_size - remaining + (n * BLS_TOKEN_MAX);
		buffer = (struct scan_token *)misc_malloc(size * sizeof(struct scan_token));
		if (!buffer)
			goto err;
		memset(buffer, 0, size * sizeof(struct scan_token));
		memcpy(buffer, array, count * sizeof(struct scan_token));
	} else {
		buffer = array;
	}

	target = scan_get_default_target(array);
	while (n--) {
		sprintf(filename, "%s/%s", blsdir, bls_entries[n]->d_name);
		printf("Using BLS config file '%s'\n", filename);

		rc = misc_get_file_buffer(filename, &file);
		if (rc)
			goto err;

		rc = sort_bls_fields(&file, filename);
		if (rc)
			goto err;

		while ((size_t)file.pos < file.length) {
			current = misc_get_char(&file, 0);
			switch (current) {
			case '#':
				file.pos++;
				skip_line(&file);
				break;
			case EOF:
				break;
			case '\t':
			case '\n':
			case '\0':
			case ' ':
				file.pos++;
				break;
			default:
				rc = scan_bls_field(&file, buffer, &count);
				if (rc) {
					error_reason("Incorrect BLS field in config file %s\n",
						     filename);
					goto err;
				}
				break;
			}
		}

		if (target != NULL)
			scan_append_keyword_assignment(buffer, &count,
						       scan_keyword_target,
						       target);

		misc_free_file_buffer(&file);
		free(bls_entries[n]);
	}

	*token = buffer;
	rc = 0;
err:
	if (n > 0) {
		do {
			free(bls_entries[n]);
		} while (n-- > 0);
	}

	free(bls_entries);
	return rc;
}


/* Check section at INDEX for compliance with config file rules. Upon success,
 * return zero and advance INDEX to point to the end of the section. Return
 * non-zero otherwise. */
static int
check_section(struct scan_token* scan, int* index)
{
	char* name;
	char* keyword[SCAN_KEYWORD_NUM];
	int keyword_line[SCAN_KEYWORD_NUM];
	enum scan_section_type type;
	int line;
	int rc;
	
	name = scan[*index].content.section.name;
	/* Ensure unique section names */
	line = scan_find_section(scan, name, scan_id_section_heading, *index+1);
	if (line >= 0) {
		error_reason("Line %d: section name '%s' already specified",
			     scan[line].line, name);
		return -1;
	}
	line = scan[*index].line;
	/* Get keyword data and advance index to end of section */
	rc = scan_get_section_keywords(scan, index, name, keyword, keyword_line,
				       NULL, NULL);
	if (rc)
		return rc;
	/* Check already done in scan_check_defaultboot */
	if (strcmp(name, DEFAULTBOOT_SECTION) == 0)
		return 0;
	else
		type = section_invalid;
	/* Check section data */
	rc = scan_check_section_data(keyword, keyword_line, name, line, &type);
	if (rc)
		return rc;
	/* Check target data */
	rc = scan_check_target_data(keyword, keyword_line);
	if (rc)
		return rc;
	return 0;
}


static int
check_menu_keyword_data(char* keyword[], int* line, char* name,
			int section_line)
{
	int i;

	for (i = 0; i < SCAN_KEYWORD_NUM; i++) {
		switch (scan_menu_key_table[i]) {
		case inv:
			if (!keyword[i])
				break;
			error_reason("Line %d: keyword '%s' not allowed in "
				     "menu section '%s'", line[i],
				     scan_keyword_name(
					(enum scan_keyword_id) i), name);
				return -1;
			break;
		case req:
			if (keyword[i])
				break;
			error_reason("Line %d: missing keyword '%s' "
				     "in menu section '%s'", section_line,
				     scan_keyword_name(
					(enum scan_keyword_id) i), name);
			return -1;
		default:
			break;
		}
	}
	return 0;
}

/* Check menu section at INDEX for compliance with config file rules. Upon
 * success, return zero and advance INDEX to point to the end of the section.
 * Return non-zero otherwise. */
static int
check_menu(struct scan_token* scan, int* index)
{
	char* keyword[SCAN_KEYWORD_NUM];
	int keyword_line[SCAN_KEYWORD_NUM];
	char* num[BOOT_MENU_ENTRIES];
	int num_line[BOOT_MENU_ENTRIES];
	char* menu_name;
	int menu_line;
	char dummy;
	int i;
	int rc;
	int number;
	int num_configs;

	menu_name = scan[*index].content.menu.name;
	menu_line = scan[*index].line;
	/* Rule 15 */
	i = scan_find_section(scan, menu_name, scan_id_menu_heading,
			      *index + 1);
	if (i >= 0) {
		error_reason("Line %d: menu name '%s' already specified",
			     scan[i].line, menu_name);
		return -1;
	}
	/* Get keyword and number assignment data */
	rc = scan_get_section_keywords(scan, index, menu_name, keyword,
				       keyword_line, num, num_line);
	if (rc)
		return rc;
	/* Check for required and invalid keywords */
	rc = check_menu_keyword_data(keyword, keyword_line, menu_name,
				     menu_line);
	if (rc)
		return rc;
	/* Check default value */
	i = (int) scan_keyword_default;
	if (keyword[i]) {
		if (sscanf(keyword[i], "%d %c", &number, &dummy) != 1) {
			error_reason("Line %d: default position must be a "
				     "number", keyword_line[i]);
			return -1;
		}
		if (number < 1) {
			error_reason("Line %d: default position must be "
				     "greater than zero", keyword_line[i]);
			return -1;
		}
		if (number > BOOT_MENU_ENTRIES) {
			error_reason("Line %d: default position too large",
				     keyword_line[i]);
			return -1;
		}
		if (!num[number - 1]) {
			error_reason("Line %d: menu position %d not defined",
				     keyword_line[i], number);
			return -1;
		}
	}
	/* Check prompt value */
	i = (int) scan_keyword_prompt;
	if (keyword[i]) {
		if (sscanf(keyword[i], "%d %c", &number, &dummy) != 1) {
			error_reason("Line %d: prompt value must be a number",
				     keyword_line[i]);
			return -1;
		}
		if (number != 0 && number != 1) {
			error_reason("Line %d: prompt value is out of range "
				     "(must be 0 or 1)", keyword_line[i]);
			return -1;
		}
	}
	/* Check timeout value */
	i = (int) scan_keyword_timeout;
	if (keyword[i]) {
		if (sscanf(keyword[i], "%d %c", &number, &dummy) != 1) {
			error_reason("Line %d: timeout value must be a number",
				     keyword_line[i]);
			return -1;
		}
		if (number < 0) {
			error_reason("Line %d: timeout value is out of range",
				     keyword_line[i]);
			return -1;
		}
	}
	/* Check number assignments */
	num_configs = 0;
	for (i = 0; i < BOOT_MENU_ENTRIES; i++) {
		if (!num[i])
			continue;
		if (scan_find_section(scan, num[i], scan_id_section_heading,
				      0) < 0) {
			error_reason("Line %d: section '%s' not found",
				     num_line[i], num[i]);
			return -1;
		}
		num_configs++;
	}
	/* Check for boot configurations */
	if (num_configs == 0) {
		error_reason("Line %d: no boot configuration specified in "
			     "menu '%s'", menu_line, menu_name);
		return -1;
	}
	/* Check target data */
	rc = scan_check_target_data(keyword, keyword_line);
	if (rc)
		return rc;

	return 0;
}

static const struct {
	char* name;
	enum scan_id id;
} id_list[] = {
	{ "none", scan_id_empty },
	{ "section heading", scan_id_section_heading },
	{ "menu heading", scan_id_menu_heading },
	{ "keyword assignment",  scan_id_keyword_assignment },
	{ "number assignment", scan_id_number_assignment},
	{ "keyword", scan_id_keyword_only},
};

static const char *
scan_id_name(enum scan_id id)
{
	unsigned int i;

	for (i = 0; i < ARRAY_SIZE(id_list); i++) {
		if (id == id_list[i].id)
			return id_list[i].name;
	}
	return "<unknown>";
}


/* Check scanned tokens for compliance with config file rules. Return zero
 * if config file complies, non-zero otherwise. Rules are:
 *
 * Global:
 *  1 - no keywords are allowed outside of a section
 *
 * Configuration sections:
 *  2 - may not contain number assignments
 *  3 - must contain certain keywords according to type (see keyword_table)
 *  4 - may not contain certain keywords according to type (see keyword_table)
 *  5 - keywords may be specified at most once
 *  6 - must contain at least one keyword assignment
 *  7 - section name must be unique in the configuration file
 *  8 - defaultboot:default must point to a valid section
 *
 * Menu sections:
 *  9  - must contain at least one number assignment
 *  10 - numbers in number assignment have to be unique in the section
 *  11 - referenced sections must be present
 *  12 - must contain certain keywords according to type (see keyword_table)
 *  13 - may not contain certain keywords according to type (see keyword_table)
 *  14 - keywords may be specified at most once
 *  15 - menu name must be unique in the configuration file
 *  16 - optional default position must be a valid position
 * */
int
scan_check(struct scan_token* scan)
{
	int i;
	int rc;

	i = 0;
	while (scan[i].id != scan_id_empty) {
		switch (scan[i].id) {
		case scan_id_section_heading:
			rc = check_section(scan, &i);
			if (rc)
				return rc;
			break;
		case scan_id_menu_heading:
			rc = check_menu(scan, &i);
			if (rc)
				return rc;
			break;
		default:
			/* Rule 1 */
			error_reason("Line %d: %s not allowed outside of "
				     "section", scan[i].line,
				     scan_id_name(scan[i].id));
			return -1;
		}
	}
	return 0;
}

/*
 * Check if kernel and initrd image paths provided by BLS files are readable.
 * If not, add value of 'scan_keyword_target' into search path and silently
 * update scan list if the file exists.
 * In case neither path works the scan_check code will correctly handle missing
 * files
 */
void scan_update_bls_path(struct scan_token *scan)
{
	char *target_value = NULL;
	char *img_value = NULL;
	char *file = NULL;
	char *tmp, *value;
	int i;
	/*
	 * In the BLS case, each BLS section heading inherits a keyword
	 * assignment target= from zipl.conf, and they are all the same.
	 *
	 */
	for (i = 0 ; scan[i].id != scan_id_empty; i++) {
		if (scan[i].id == scan_id_keyword_assignment &&
		    scan[i].content.keyword.keyword == scan_keyword_target) {
			target_value = scan[i].content.keyword.value;
			break;
		}
	}
	if (!target_value)
		return;
	for (i = 0 ; scan[i].id != scan_id_empty; i++) {
		if (scan[i].id != scan_id_keyword_assignment)
			continue;
		if (scan[i].content.keyword.keyword == scan_keyword_image ||
		    scan[i].content.keyword.keyword == scan_keyword_ramdisk) {

			value = scan[i].content.keyword.value;
			/*
			 * put the filename only into the file var before
			 * checking its presence
			 */
			if (contains_address(value)) {
				tmp = strrchr(value, ',');
				file = strndup(value, tmp - value);
			} else {
				file = value;
			}
			if (misc_check_readable_file(file)) {
				misc_asprintf(&img_value, "%s%s",
					      target_value, file);
				if (misc_check_readable_file(img_value)) {
					free(img_value);
					continue;
				}

				/*
				 * when file has stripped the load address part,
				 * do generate a prefixed value
				 */
				if (file != value) {
					free(file);
					free(img_value);
					misc_asprintf(&img_value, "%s%s",
						      target_value, value);
				}
				free(scan[i].content.keyword.value);
				scan[i].content.keyword.value = img_value;
			}
		}
	}
	return;
}

static int
scan_get_defaultboot_type(char* keyword[], int line[], int section_line,
			  enum scan_section_type* type)
{
	int key1;
	int key2;

	if (keyword[(int) scan_keyword_defaultauto]) {
		if (keyword[(int) scan_keyword_defaultmenu]) {
			key1 = (int) scan_keyword_defaultauto;
			key2 = (int) scan_keyword_defaultmenu;
			goto err_too_much;
		}
		if (!keyword[(int) scan_keyword_target]) {
			goto err_no_target;
		}
		*type = section_default_auto;
	} else if (keyword[(int) scan_keyword_target]) {
		if (keyword[(int) scan_keyword_defaultmenu]) {
			key1 = (int) scan_keyword_target;
			key2 = (int) scan_keyword_defaultmenu;
			goto err_too_much_target;
		}
		*type = section_default_auto;
	} else if (keyword[(int) scan_keyword_defaultmenu]) {
		if (keyword[(int) scan_keyword_default]) {
			key1 = (int) scan_keyword_defaultmenu;
			key2 = (int) scan_keyword_default;
			goto err_too_much;
		}
		*type = section_default_menu;
	} else if (keyword[(int) scan_keyword_default]) {
		*type = section_default_section;
	} else {
		error_reason("Line %d: Section '%s' requires one of keywords "
			     "'default', 'defaultmenu', 'defaultauto' or "
			     "'target'", section_line, DEFAULTBOOT_SECTION);
		return -1;
	}
	return 0;

err_no_target:
	error_reason("Line %d: Keyword 'target' required in section '%s' when "
		     "specifying 'defaultauto'", section_line,
		     DEFAULTBOOT_SECTION);
	return -1;

err_too_much:
	error_reason("Line %d: Only one of keywords 'default', 'defaultmenu' "
		     "and 'defaultauto' allowed in section '%s'",
		     MAX(line[key1], line[key2]), DEFAULTBOOT_SECTION);
	return -1;

err_too_much_target:
	error_reason("Line %d: Only one of keywords 'default', 'defaultmenu' "
		     "and 'target' allowed in section '%s'",
		     MAX(line[key1], line[key2]), DEFAULTBOOT_SECTION);
	return -1;
}


/* scan_check_defaultboot checks the defaultboot section of the configuration
 * file. It returns
 *    0   on successful check, NO automenu is to be built
 *    1   on successful check, the automenu must be built
 *   -1   on error
 */
int
scan_check_defaultboot(struct scan_token* scan)
{
	int i;
	int j;
	char* keyword[SCAN_KEYWORD_NUM];
	int keyword_line[SCAN_KEYWORD_NUM];
	enum scan_section_type type;
	int defaultboot_line;
	int rc;

	/* Check if defaultboot is defined */
	i = scan_find_section(scan, DEFAULTBOOT_SECTION,
			      scan_id_section_heading, 0);
	if (i < 0) {
		error_reason("No '%s' section found and no section specified "
			     "on command line", DEFAULTBOOT_SECTION);
		return -1;
	}
	/* Ensure unique section names */
	j = scan_find_section(scan, DEFAULTBOOT_SECTION,
			      scan_id_section_heading, i + 1);
	if (j >= 0) {
		error_reason("Line %d: section name '%s' already specified",
			     scan[j].line, DEFAULTBOOT_SECTION);
		return -1;
	}
	defaultboot_line = scan[i].line;
	/* Get keyword data */
	rc = scan_get_section_keywords(scan, &i, DEFAULTBOOT_SECTION, keyword,
				       keyword_line, NULL, NULL);
	if (rc)
		return rc;
	/* Check default keyword value */
	i = (int) scan_keyword_default;
	if (keyword[i] && scan_find_section(scan, keyword[i],
					    scan_id_section_heading, 0) < 0) {
		error_reason("Line %d: no such section '%s'", keyword_line[i],
			     keyword[i]);
		return -1;
	}
	/* Determine default boot type */
	rc = scan_get_defaultboot_type(keyword, keyword_line, defaultboot_line,
				       &type);
	if (rc)
		return rc;
	/* Check target keywords */
	if (type == section_default_auto &&
	    scan_count_target_keywords(keyword) > 0) {
		rc = scan_check_target_data(keyword, keyword_line);
		if (rc)
			return rc;
	}
	/* Check remaining section data */
	rc = scan_check_section_data(keyword, keyword_line, DEFAULTBOOT_SECTION,
				     defaultboot_line, &type);
	if (rc)
		return rc;
	if (type == section_default_auto)
		return 1;
	else
		return 0;
}


static void
scan_skip_section(struct scan_token* scan, int* index)
{
	do {
		(*index)++;
	} while (!scan_is_delimiter(scan[*index].id));
}


static int
scan_copy_section(struct scan_token* from, struct scan_token* to,
		  int* index_from, int* index_to)
{
	struct scan_token* token;

	do {
		token = &to[*index_to];
		*token = from[*index_from];
		switch (token->id) {
		case scan_id_section_heading:
			token->content.section.name =
				misc_strdup(token->content.section.name);
			if (!token->content.section.name)
				return -1;
			break;
		case scan_id_menu_heading:
			token->content.menu.name =
				misc_strdup(token->content.menu.name);
			if (!token->content.menu.name)
				return -1;
			break;
		case scan_id_keyword_assignment:
			token->content.keyword.value =
				misc_strdup(token->content.keyword.value);
			if (!token->content.keyword.value)
				return -1;
			break;
		case scan_id_number_assignment:
			token->content.number.value =
				misc_strdup(token->content.number.value);
			if (!token->content.number.value)
				return -1;
			break;
		default:
			break;
		}
		(*index_from)++;
		(*index_to)++;
	} while (!scan_is_delimiter(from[*index_from].id));

	return 0;
}


static int
scan_append_section_heading(struct scan_token* scan, int* index, char* name)
{
	scan[*index].id = scan_id_section_heading;
	scan[*index].line = 0;
	scan[*index].content.section.name = misc_strdup(name);
	if (!scan[*index].content.section.name)
		return -1;
	(*index)++;
	return 0;
}


static int
scan_append_menu_heading(struct scan_token* scan, int* index, char* name)
{
	scan[*index].id = scan_id_menu_heading;
	scan[*index].line = 0;
	scan[*index].content.menu.name = misc_strdup(name);
	if (!scan[*index].content.menu.name)
		return -1;
	(*index)++;
	return 0;
}


static int
scan_append_number_assignment(struct scan_token* scan, int* index, int num,
			      char* value)
{
	scan[*index].id = scan_id_number_assignment;
	scan[*index].line = 0;
	scan[*index].content.number.number = num;
	scan[*index].content.number.value = misc_strdup(value);
	if (!scan[*index].content.number.value)
		return -1;
	(*index)++;
	return 0;
}


static int
scan_append_keyword_assignment(struct scan_token* scan, int* index,
			       enum scan_keyword_id id, char* value)
{
	scan[*index].id = scan_id_keyword_assignment;
	scan[*index].line = 0;
	scan[*index].content.keyword.keyword = id;
	scan[*index].content.keyword.value = misc_strdup(value);
	if (!scan[*index].content.keyword.value)
		return -1;
	(*index)++;
	return 0;
}


static int
scan_append_target_keywords(struct scan_token* scan, int* index,
			    char* keyword[])
{
	enum scan_keyword_id id[] = {
		scan_keyword_target,
		scan_keyword_targetbase,
		scan_keyword_targettype,
		scan_keyword_targetgeometry,
		scan_keyword_targetblocksize,
		scan_keyword_targetoffset,
	};
	unsigned int i;
	int rc;

	for (i = 0; i < ARRAY_SIZE(id); i++) {
		if (!keyword[(int) id[i]])
			continue;
		rc = scan_append_keyword_assignment(scan, index, id[i],
						    keyword[(int) id[i]]);
		if (rc)
			return rc;
	}

	return 0;
}


struct scan_token *
scan_build_automenu(struct scan_token* scan)
{
	char* entry[BOOT_MENU_ENTRIES];
	int num_entries;
	int default_entry;
	char* db_keyword[SCAN_KEYWORD_NUM];
	int db_line[SCAN_KEYWORD_NUM];
	char* sec_keyword[SCAN_KEYWORD_NUM];
	int sec_line[SCAN_KEYWORD_NUM];
	int num_targets;
	int num_sections;
	size_t size;
	struct scan_token* new_scan;
	int i;
	int i_new;
	char* name;
	char* default_name;
	int pos;

	/* Find defaultboot */
	i = scan_find_section(scan, DEFAULTBOOT_SECTION,
			      scan_id_section_heading, 0);
	/* Get defaultboot data */
	if (scan_get_section_keywords(scan, &i, DEFAULTBOOT_SECTION, db_keyword,
				      db_line, NULL, NULL))
		return NULL;
	default_name = db_keyword[(int) scan_keyword_default];
	num_targets = scan_count_target_keywords(db_keyword);
	/* Get size of scan array and number of sections */
	num_sections = 0;
	for (i = 0; scan[i].id != scan_id_empty; i++) {
		if (scan[i].id == scan_id_section_heading)
			num_sections++;
	}
	size = /* old scan array + delimiter */     i + 1 +
	       /* defaultboot heading  */           1 +
	       /* defaultmenu */                    1 +
	       /* menu heading  */                  1 +
	       /* keyword default,prompt,timeout */ 3 +
	       /* keyword secure */                 1 +
	       /* target keywords*/                 num_targets +
	       /* missing target definitions */     num_sections * num_targets +
	       /* number assigment  */              num_sections;
	size *= sizeof(struct scan_token);
	new_scan = misc_malloc(size);
	if (!new_scan)
		return NULL;
	memset(new_scan, 0, size);
	/* Fill new array */
	i = 0;
	i_new = 0;
	num_entries = 0;
	default_entry = -1;
	memset(entry, 0, sizeof(entry));
	while (scan[i].id != scan_id_empty) {
		switch (scan[i].id) {
		case scan_id_menu_heading:
			name = scan[i].content.menu.name;
			/* Abort if automenu name is already in use */
			if (strcmp(name, SCAN_AUTOMENU_NAME) == 0) {
				error_reason("Cannot build automenu: menu name "
					     "'%s' already used",
					     SCAN_AUTOMENU_NAME);
				goto err;
			}
			/* Menu sections are copied without changes */
			if (scan_copy_section(scan, new_scan, &i, &i_new))
				goto err;
			break;
		case scan_id_section_heading:
			name = scan[i].content.section.name;
			/* Do not copy old defaultboot section */
			if (strcmp(name, DEFAULTBOOT_SECTION) == 0) {
				scan_skip_section(scan, &i);
				break;
			}
			/* Get section data but do not advance index */
			pos = i;
			if (scan_get_section_keywords(scan, &pos, name,
						      sec_keyword, sec_line,
						      NULL, NULL))
				goto err;
			/* Copy section contents and advance index */
			if (scan_copy_section(scan, new_scan, &i, &i_new))
				goto err;
			/* Stop here for non-IPL sections. */
			if (scan_get_section_type(sec_keyword) != section_ipl)
				break;
			/* Is there enough room for another section? */
			if (num_entries == BOOT_MENU_ENTRIES) {
				error_reason("Cannot build automenu: too many "
					     "IPL sections defined (max %d)",
					     BOOT_MENU_ENTRIES);
				goto err;
			}
			/* Determine if this is the default entry */
			if (default_name && strcmp(default_name, name) == 0)
				default_entry = num_entries;
			entry[num_entries++] = name;
			/* Add missing target parameters if necessary */
			if (scan_count_target_keywords(sec_keyword) > 0)
				break;
			if (scan_append_target_keywords(new_scan, &i_new,
							db_keyword))
				goto err;
			break;
		default:
			/* Rule 1 */
			error_reason("Line %d: %s not allowed outside of "
				     "section", scan[i].line,
				     scan_id_name(scan[i].id));
			goto err;
		}
	}
	if (num_entries == 0) {
		error_reason("Cannot build automenu: no IPL entries available");
		goto err;
	}

	/* Append new defaultboot and automenu sections */
	/* [defaultboot] */
	if (scan_append_section_heading(new_scan, &i_new, DEFAULTBOOT_SECTION))
		goto err;
	/* defaultmenu=zipl-automatic-menu */
	if (scan_append_keyword_assignment(new_scan, &i_new,
				scan_keyword_defaultmenu, SCAN_AUTOMENU_NAME))
		goto err;
	/* :zipl-automatic-menu */
	if (scan_append_menu_heading(new_scan, &i_new, SCAN_AUTOMENU_NAME))
		goto err;
	/* default= */
	if (default_entry >= 0) {
		char str[20];

		snprintf(str, sizeof(str), "%d", default_entry + 1);
		if (scan_append_keyword_assignment(new_scan, &i_new,
						   scan_keyword_default, str))
			goto err;
	}
	/* prompt= */
	i = (int) scan_keyword_prompt;
	if (db_keyword[i]) {
		if (scan_append_keyword_assignment(new_scan, &i_new,
						   scan_keyword_prompt,
						   db_keyword[i]))
			goto err;
	}
	/* timeout= */
	i = (int) scan_keyword_timeout;
	if (db_keyword[i]) {
		if (scan_append_keyword_assignment(new_scan, &i_new,
						   scan_keyword_timeout,
						   db_keyword[i]))
			goto err;
	}
	/* secure= */
	i = (int) scan_keyword_secure;
	if (db_keyword[i]) {
		if (scan_append_keyword_assignment(new_scan, &i_new,
						   scan_keyword_secure,
						   db_keyword[i]))
			goto err;
			}
	/* target= */
	/* targetbase= */
	/* targetgeometry= */
	/* targetblocksize= */
	/* targetoffset= */
	if (scan_append_target_keywords(new_scan, &i_new, db_keyword))
		goto err;
	/* <num>=<section name>*/
	for (i = 0; i < num_entries; i++) {
		if (scan_append_number_assignment(new_scan, &i_new, i + 1,
						  entry[i]))
			goto err;
	}

	return new_scan;

err:
	scan_free(new_scan);
	return NULL;
}
