Files
s390-tools/zipl/src/scan.c
Eduard Shishkin a0dba6bfdb zipl/src: Implement sorting bls entries by versions
This patch implements an additional method of sorting bls entries
by version field and uses it as default one. If sorting by version
fails, then it falls back to the most robust method of sorting by
file names

Signed-off-by: Eduard Shishkin <edward6@linux.ibm.com>
Reviewed-by: Stefan Haberland <sth@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2022-05-10 13:33:24 +02:00

2351 lines
59 KiB
C

/*
* 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;
int nr_titles = 0;
size_t title_off;
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))
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;
}