A complete rewrite of the layout file parser: - safe (unlike before) - support for comments - supports including/sourcing of other layout files (relative to the including file as well as via absolute paths) up to predefined nesting level - checks for duplicate region names - can handle region names and layout file names with spaces etc correctly - way better diagnostics - no more character arrays in struct romentry_t but pointers only
To help with migrating legacy layout files add a script for converting them to format 2.
Signed-off-by: Stefan Tauner stefan.tauner@student.tuwien.ac.at --- layout.c | 305 +++++++++++++++++++++++++++++++++------- util/convert_layout_v1_to_v2.sh | 79 +++++++++++ 2 files changed, 331 insertions(+), 53 deletions(-) create mode 100755 util/convert_layout_v1_to_v2.sh
diff --git a/layout.c b/layout.c index 4d4f35c..eb4ec07 100644 --- a/layout.c +++ b/layout.c @@ -22,17 +22,23 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <ctype.h> +#include <errno.h> #include <limits.h> #include "flash.h" #include "programmer.h"
#define MAX_ROMLAYOUT 32 +#define MAX_ENTRY_LEN 1024 +#define WHITESPACE_CHARS " \t" +#define INCLUDE_INSTR "source" +#define MAX_NESTING_LVL 5
typedef struct { unsigned int start; unsigned int end; unsigned int included; - char name[256]; + char *name; char *file; } romentry_t;
@@ -45,58 +51,264 @@ static int num_rom_entries = 0; /* the number of valid rom_entries */ static char *include_args[MAX_ROMLAYOUT]; static int num_include_args = 0; /* the number of valid include_args. */
-#ifndef __LIBPAYLOAD__ -int read_romlayout(char *name) +/* returns the index of the entry (or a negative value if it is not found) */ +static int find_romentry(char *name) { - FILE *romlayout; - char tempstr[256]; int i; + msg_gspew("Looking for region "%s"... ", name); + for (i = 0; i < num_rom_entries; i++) { + if (strcmp(rom_entries[i].name, name) == 0) { + msg_gspew("found.\n"); + return i; + } + } + msg_gspew("not found.\n"); + return -1; +}
- romlayout = fopen(name, "r"); +/* FIXME: While libpayload has no file I/O itself, code using libflashrom could still provide layout information + * obtained by other means like user input or by fetching it from somewhere else. Therefore the parsing code + * should be separated from the file reading code eventually. */ +#ifndef __LIBPAYLOAD__ +/** Parse one line in a layout file. + * @param file_name The name of the file this line originates from. + * @param entry If not NULL fill it with the parsed data, else just detect errors and print diagnostics. + * @return -1 on error, + * 0 if the line could be parsed into a layout entry succesfully, + * 1 if a file was successfully sourced. + */ +static int parse_entry(char *file_name, char *buf, romentry_t *entry) +{ + msg_gdbg2("String to parse: "%s".\n", buf); + + /* Skip all white space in the beginning. */ + char *tmp_str = buf + strspn(buf, WHITESPACE_CHARS); + char *endptr; + + /* Check for include command. */ + if (strncmp(tmp_str, INCLUDE_INSTR, strlen(INCLUDE_INSTR)) == 0) { + tmp_str += strlen(INCLUDE_INSTR); + tmp_str += strspn(tmp_str, WHITESPACE_CHARS); + if (unquote_string(&tmp_str, NULL, WHITESPACE_CHARS) != 0) { + msg_gerr("Error parsing version 2 layout entry: Could not find file name in "%s".\n", + buf); + return -1; + } + msg_gspew("Source command found with filename "%s".\n", tmp_str); + + static unsigned int nesting_lvl = 0; + if (nesting_lvl >= MAX_NESTING_LVL) { + msg_gerr("Error: Nesting level exeeded limit of %u.\n", MAX_NESTING_LVL); + msg_gerr("Unable to import "%s" in layout file "%s".\n", tmp_str, file_name); + return -1; + }
- if (!romlayout) { - msg_gerr("ERROR: Could not open layout file (%s).\n", - name); + nesting_lvl++; + int ret; + /* If a relative path is given, append it to the dirname of the current file. */ + if (*tmp_str != '/') { + /* We need space for: dirname of file_name, '/' , the file name in tmp_strand and '\0'. + * Since the dirname of file_name is shorter than file_name this is more than enough: */ + char *path = malloc(strlen(file_name) + strlen(tmp_str) + 2); + if (path == NULL) { + msg_gerr("Out of memory!\n"); + return -1; + } + strcpy(path, file_name); + + /* A less insane but incomplete dirname implementation... */ + endptr = strrchr(path, '/'); + if (endptr != NULL) { + endptr[0] = '/'; + endptr[1] = '\0'; + } else { + /* This is safe because the original file name was at least one char. */ + path[0] = '.'; + path[1] = '/'; + path[2] = '\0'; + } + strcat(path, tmp_str); + ret = read_romlayout(path); + free(path); + } else + ret = read_romlayout(tmp_str); + nesting_lvl--; + return ret >= 0 ? 1 : -1; /* Only return values < 0 are errors. */ + } + + errno = 0; + long tmp_long = strtol(tmp_str, &endptr, 0); + if (errno != 0 || endptr == tmp_str || tmp_long < 0 || tmp_long > UINT32_MAX) { + msg_gerr("Error parsing version 2 layout entry: Could not convert start address in "%s".\n", + buf); return -1; } + uint32_t start = tmp_long;
- while (!feof(romlayout)) { - char *tstr1, *tstr2; + tmp_str = endptr + strspn(endptr, WHITESPACE_CHARS); + if (*tmp_str != ':') { + msg_gerr("Error parsing version 2 layout entry: Address separator does not follow start " + "address in "%s".\n", buf); + return -1; + } + tmp_str++;
- if (num_rom_entries >= MAX_ROMLAYOUT) { - msg_gerr("Maximum number of entries (%i) in layout file reached.\n", MAX_ROMLAYOUT); - return 1; + errno = 0; + tmp_long = strtol(tmp_str, &endptr, 0); + if (errno != 0 || endptr == tmp_str || tmp_long < 0 || tmp_long > UINT32_MAX) { + msg_gerr("Error parsing version 2 layout entry: Could not convert end address in "%s"\n", + buf); + return -1; + } + uint32_t end = tmp_long; + + size_t skip = strspn(endptr, WHITESPACE_CHARS); + if (skip == 0) { + msg_gerr("Error parsing version 2 layout entry: End address is not followed by white space in " + ""%s"\n", buf); + return -1; + } + + tmp_str = endptr + skip; + /* The region name is either enclosed by quotes or ends with the first whitespace. */ + if (unquote_string(&tmp_str, &endptr, WHITESPACE_CHARS) != 0) { + msg_gerr("Error parsing version 2 layout entry: Could not find region name in "%s".\n", buf); + return -1; + } + + msg_gdbg("Parsed entry: 0x%08x - 0x%08x named "%s"\n", start, end, tmp_str); + + if (start >= end) { + msg_gerr("Error parsing version 2 layout entry: Length of region "%s" is not positive.\n", + tmp_str); + return -1; + } + + if (find_romentry(tmp_str) >= 0) { + msg_gerr("Error parsing version 2 layout entry: Region name "%s" used multiple times.\n", + tmp_str); + return -1; + } + + endptr += strspn(endptr, WHITESPACE_CHARS); + if (strlen(endptr) != 0) + msg_gerr("Warning: Region name "%s" is not followed by white space only.\n", tmp_str); + + if (entry != NULL) { + entry->name = strdup(tmp_str); + if (entry->name == NULL) { + msg_gerr("Out of memory!\n"); + return -1; } - if (2 != fscanf(romlayout, "%s %s\n", tempstr, rom_entries[num_rom_entries].name)) - continue; -#if 0 - // fscanf does not like arbitrary comments like that :( later - if (tempstr[0] == '#') { - continue; + + entry->start = start; + entry->end = end; + entry->included = 0; + entry->file = NULL; + } + return 0; +} + +/* Scan the first line for the determinant version comment and parse it, or assume it is version 1. */ +static int detect_layout_version(FILE *romlayout) +{ + int c; + do { /* Skip white space */ + c = fgetc(romlayout); + if (c == EOF) + return -1; + } while (isblank(c)); + ungetc(c, romlayout); + + const char* vcomment = "# flashrom layout v"; + char buf[strlen(vcomment) + 1]; /* comment + \0 */ + if (fgets(buf, sizeof(buf), romlayout) == NULL) + return -1; + if (strcmp(vcomment, buf) != 0) + return 1; + int version; + if (fscanf(romlayout, "%d", &version) != 1) + return -1; + if (version < 2) { + msg_gwarn("Warning: Layout file declares itself to be version %d, but self delcaration has\n" + "only been possible since version 2. Continuing anyway.\n", version); + } + return version; +} + +int read_romlayout(char *name) +{ + FILE *romlayout = fopen(name, "r"); + if (romlayout == NULL) { + msg_gerr("ERROR: Could not open layout file "%s".\n", name); + return -1; + } + + const int version = detect_layout_version(romlayout); + if (version < 0) { + msg_gerr("Could not determine version of layout file "%s".\n", name); + fclose(romlayout); + return 1; + } + if (version != 2) { + msg_gerr("Layout file version %d is not supported in this version of flashrom.\n", version); + fclose(romlayout); + return 1; + } + rewind(romlayout); + + msg_gdbg("Parsing layout file "%s" according to version %d.\n", name, version); + int linecnt = 0; + while (!feof(romlayout)) { + char buf[MAX_ENTRY_LEN]; + char *curchar = buf; + linecnt++; + msg_gspew("Parsing line %d of "%s".\n", linecnt, name); + + while (true) { + /* Make sure that we ignore various newline sequences by checking for \r too. + * NB: This might introduce empty lines. */ + char c = fgetc(romlayout); + if (c == '#') { + do { /* Skip characters in comments */ + c = fgetc(romlayout); + } while (c != EOF && c != '\n' && c != '\r'); + continue; + } + if (c == EOF || c == '\n' || c == '\r') { + *curchar = '\0'; + break; + } + if (curchar == &buf[MAX_ENTRY_LEN - 1]) { + msg_gerr("Line %d of layout file "%s" is longer than the allowed %d chars.\n", + linecnt, name, MAX_ENTRY_LEN); + fclose(romlayout); + return 1; + } + *curchar = c; + curchar++; } -#endif - tstr1 = strtok(tempstr, ":"); - tstr2 = strtok(NULL, ":"); - if (!tstr1 || !tstr2) { - msg_gerr("Error parsing layout file. Offending string: "%s"\n", tempstr); + + /* Skip all whitespace or empty lines */ + if (strspn(buf, WHITESPACE_CHARS) == strlen(buf)) + continue; + + romentry_t *entry = (num_rom_entries >= MAX_ROMLAYOUT) ? NULL : &rom_entries[num_rom_entries]; + int ret = parse_entry(name, buf, entry); + if (ret > 0) { fclose(romlayout); return 1; } - rom_entries[num_rom_entries].start = strtol(tstr1, (char **)NULL, 16); - rom_entries[num_rom_entries].end = strtol(tstr2, (char **)NULL, 16); - rom_entries[num_rom_entries].included = 0; - rom_entries[num_rom_entries].file = NULL; - num_rom_entries++; + /* Only 0 indicates the successfully parsing of an entry, others are errors or imports. */ + if (ret == 0) + num_rom_entries++; } - - for (i = 0; i < num_rom_entries; i++) { - msg_gdbg("romlayout %08x - %08x named %s\n", - rom_entries[i].start, - rom_entries[i].end, rom_entries[i].name); - } - fclose(romlayout); - + if (num_rom_entries >= MAX_ROMLAYOUT) { + msg_gerr("Found %d entries in layout file which is more than the %i allowed.\n", + num_rom_entries + 1, MAX_ROMLAYOUT); + return 1; + } return 0; } #endif @@ -135,21 +347,6 @@ int register_include_arg(char *name) return 0; }
-/* returns the index of the entry (or a negative value if it is not found) */ -static int find_romentry(char *name) -{ - int i; - msg_gspew("Looking for region "%s"... ", name); - for (i = 0; i < num_rom_entries; i++) { - if (strcmp(rom_entries[i].name, name) == 0) { - msg_gspew("found.\n"); - return i; - } - } - msg_gspew("not found.\n"); - return -1; -} - /* process -i arguments * returns 0 to indicate success, >0 to indicate failure */ @@ -224,6 +421,8 @@ void layout_cleanup(void) num_include_args = 0;
for (i = 0; i < num_rom_entries; i++) { + free(rom_entries[i].name); + rom_entries[i].name = NULL; free(rom_entries[i].file); rom_entries[i].file = NULL; rom_entries[i].included = 0; diff --git a/util/convert_layout_v1_to_v2.sh b/util/convert_layout_v1_to_v2.sh new file mode 100755 index 0000000..712de87 --- /dev/null +++ b/util/convert_layout_v1_to_v2.sh @@ -0,0 +1,79 @@ +#!/bin/sh +# +# Copyright 2013 Stefan Tauner +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# +# +# This script converts legacy layout files as understood by flashrom up to version 0.9.7 to format version 2. +# It converts all files given as parameters in place and creates backups (with the suffix ".old") of the old +# contents unless --nobackup is given. +# +# It does... +# - check if the file exists and if its format is already in v2 format +# - prefix addresses with 0x if they have not been already +# - remove superfluous white space (i.e. more than one consecutive space) +# - remove white space from otherwise empty lines + +usage () +{ + echo "Usage: $0 [--nobackup] FILE..." + exit 1 +} + +if [ $# -eq 0 ]; then + usage +fi + +if [ $1 = "--nobackup" ]; then + sed_opt="-i" + shift + if [ $# -eq 0 ]; then + usage + fi +else + sed_opt="-i.old" +fi + +# Test if all files are really there before starting conversion +ret=0 +for f in "$@" ; do + if [ ! -e "$f" ]; then + echo "File not found: $f">&2 + ret=1 + fi +done +if [ "$ret" -ne 0 ]; then + echo "Aborting" + return 1 +fi + +for f in "$@" ; do + if grep -q 'flashrom layout v2\b' "$f" ; then + echo "File already in new format: $f" + continue + fi + + sed $sed_opt -e " + 1i # flashrom layout v2 + s/ *(0x|)([0-9a-fA-F][0-9a-fA-F]*) *: *(0x|)([0-9a-fA-F][0-9a-fA-F]*) */0x\2:0x\4 / + s/ */ / + s/^ *$// + " "$f" + echo "$f done" +done +echo "Done!"