[flashrom] [PATCH 2/2] layout: Better file parsing.
Stefan Tauner
stefan.tauner at student.tuwien.ac.at
Thu Sep 19 01:29:38 CEST 2013
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 at 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!"
--
Kind regards, Stefan Tauner
More information about the flashrom
mailing list