Tools: set multiple variable with fw_setenv utility

Add a sort of batch mode to fw_setenv, allowing to set
multiple variables in one shot, without updating the flash after
each set as now. It is added the possibility to pass
a config file with a list of pairs <variable, value> to be set,
separated by a TAB character.

Signed-off-by: Stefano Babic <sbabic@denx.de>
diff --git a/tools/env/fw_env.c b/tools/env/fw_env.c
index a46205d..04f3bf0 100644
--- a/tools/env/fw_env.c
+++ b/tools/env/fw_env.c
@@ -45,8 +45,7 @@
 
 #include "fw_env.h"
 
-#define	CMD_GETENV	"fw_printenv"
-#define	CMD_SETENV	"fw_setenv"
+#define WHITESPACE(c) ((c == '\t') || (c == ' '))
 
 #define min(x, y) ({				\
 	typeof(x) _min1 = (x);			\
@@ -210,7 +209,6 @@
 
 static int flash_io (int mode);
 static char *envmatch (char * s1, char * s2);
-static int env_init (void);
 static int parse_config (void);
 
 #if defined(CONFIG_FILE)
@@ -225,6 +223,22 @@
 	return rc;
 }
 
+static char *fw_string_blank(char *s, int noblank)
+{
+	int i;
+	int len = strlen(s);
+
+	for (i = 0; i < len; i++, s++) {
+		if ((noblank && !WHITESPACE(*s)) ||
+			(!noblank && WHITESPACE(*s)))
+			break;
+	}
+	if (i == len)
+		return NULL;
+
+	return s;
+}
+
 /*
  * Search the environment for a variable.
  * Return the value, if found, or NULL, if not found.
@@ -233,7 +247,7 @@
 {
 	char *env, *nxt;
 
-	if (env_init ())
+	if (fw_env_open())
 		return NULL;
 
 	for (env = environment.data; *env; env = nxt + 1) {
@@ -264,7 +278,7 @@
 	int i, n_flag;
 	int rc = 0;
 
-	if (env_init ())
+	if (fw_env_open())
 		return -1;
 
 	if (argc == 1) {		/* Print all env variables  */
@@ -327,30 +341,34 @@
 	return rc;
 }
 
-/*
- * Deletes or sets environment variables. Returns -1 and sets errno error codes:
- * 0	  - OK
- * EINVAL - need at least 1 argument
- * EROFS  - certain variables ("ethaddr", "serial#") cannot be
- *	    modified or deleted
- *
- */
-int fw_setenv (int argc, char *argv[])
+int fw_env_close(void)
 {
-	int i, len;
-	char *env, *nxt;
-	char *oldval = NULL;
-	char *name;
+	/*
+	 * Update CRC
+	 */
+	*environment.crc = crc32(0, (uint8_t *) environment.data, ENV_SIZE);
 
-	if (argc < 2) {
-		errno = EINVAL;
-		return -1;
+	/* write environment back to flash */
+	if (flash_io(O_RDWR)) {
+		fprintf(stderr,
+			"Error: can't write fw_env to flash\n");
+			return -1;
 	}
 
-	if (env_init ())
-		return -1;
+	return 0;
+}
 
-	name = argv[1];
+
+/*
+ * Set/Clear a single variable in the environment.
+ * This is called in sequence to update the environment
+ * in RAM without updating the copy in flash after each set
+ */
+int fw_env_write(char *name, char *value)
+{
+	int len;
+	char *env, *nxt;
+	char *oldval = NULL;
 
 	/*
 	 * search if variable with this name already exists
@@ -358,7 +376,7 @@
 	for (nxt = env = environment.data; *env; env = nxt + 1) {
 		for (nxt = env; *nxt; ++nxt) {
 			if (nxt >= &environment.data[ENV_SIZE]) {
-				fprintf (stderr, "## Error: "
+				fprintf(stderr, "## Error: "
 					"environment not terminated\n");
 				errno = EINVAL;
 				return -1;
@@ -396,8 +414,8 @@
 	}
 
 	/* Delete only ? */
-	if (argc < 3)
-		goto WRITE_FLASH;
+	if (!value || !strlen(value))
+		return 0;
 
 	/*
 	 * Append new definition at the end
@@ -411,41 +429,202 @@
 	 */
 	len = strlen (name) + 2;
 	/* add '=' for first arg, ' ' for all others */
-	for (i = 2; i < argc; ++i) {
-		len += strlen (argv[i]) + 1;
-	}
+	len += strlen(value) + 1;
+
 	if (len > (&environment.data[ENV_SIZE] - env)) {
 		fprintf (stderr,
 			"Error: environment overflow, \"%s\" deleted\n",
 			name);
 		return -1;
 	}
+
 	while ((*env = *name++) != '\0')
 		env++;
-	for (i = 2; i < argc; ++i) {
-		char *val = argv[i];
-
-		*env = (i == 2) ? '=' : ' ';
-		while ((*++env = *val++) != '\0');
-	}
+	*env = '=';
+	while ((*++env = *value++) != '\0')
+		;
 
 	/* end is marked with double '\0' */
 	*++env = '\0';
 
-  WRITE_FLASH:
+	return 0;
+}
 
-	/*
-	 * Update CRC
-	 */
-	*environment.crc = crc32 (0, (uint8_t *) environment.data, ENV_SIZE);
+/*
+ * Deletes or sets environment variables. Returns -1 and sets errno error codes:
+ * 0	  - OK
+ * EINVAL - need at least 1 argument
+ * EROFS  - certain variables ("ethaddr", "serial#") cannot be
+ *	    modified or deleted
+ *
+ */
+int fw_setenv(int argc, char *argv[])
+{
+	int i, len;
+	char *name;
+	char *value = NULL;
+	char *tmpval = NULL;
 
-	/* write environment back to flash */
-	if (flash_io (O_RDWR)) {
-		fprintf (stderr, "Error: can't write fw_env to flash\n");
+	if (argc < 2) {
+		errno = EINVAL;
 		return -1;
 	}
 
-	return 0;
+	if (fw_env_open()) {
+		fprintf(stderr, "Error: environment not initialized\n");
+		return -1;
+	}
+
+	name = argv[1];
+
+	len = strlen(name) + 2;
+	for (i = 2; i < argc; ++i)
+		len += strlen(argv[i]) + 1;
+
+	/* Allocate enough place to the data string */
+	for (i = 2; i < argc; ++i) {
+		char *val = argv[i];
+		if (!value) {
+			value = (char *)malloc(len - strlen(name));
+			if (!value) {
+				fprintf(stderr,
+				"Cannot malloc %u bytes: %s\n",
+				len - strlen(name), strerror(errno));
+				return -1;
+			}
+			memset(value, 0, len - strlen(name));
+			tmpval = value;
+		}
+		if (i != 2)
+			*tmpval++ = ' ';
+		while (*val != '\0')
+			*tmpval++ = *val++;
+	}
+
+	fw_env_write(name, value);
+
+	if (value)
+		free(value);
+
+	return fw_env_close();
+}
+
+/*
+ * Parse  a file  and configure the u-boot variables.
+ * The script file has a very simple format, as follows:
+ *
+ * Each line has a couple with name, value:
+ * <white spaces>variable_name<white spaces>variable_value
+ *
+ * Both variable_name and variable_value are interpreted as strings.
+ * Any character after <white spaces> and before ending \r\n is interpreted
+ * as variable's value (no comment allowed on these lines !)
+ *
+ * Comments are allowed if the first character in the line is #
+ *
+ * Returns -1 and sets errno error codes:
+ * 0	  - OK
+ * -1     - Error
+ */
+int fw_parse_script(char *fname)
+{
+	FILE *fp;
+	char dump[1024];	/* Maximum line length in the file */
+	char *name;
+	char *val;
+	int lineno = 0;
+	int len;
+	int ret = 0;
+
+	if (fw_env_open()) {
+		fprintf(stderr, "Error: environment not initialized\n");
+		return -1;
+	}
+
+	if (strcmp(fname, "-") == 0)
+		fp = stdin;
+	else {
+		fp = fopen(fname, "r");
+		if (fp == NULL) {
+			fprintf(stderr, "I cannot open %s for reading\n",
+				 fname);
+			return -1;
+		}
+	}
+
+	while (fgets(dump, sizeof(dump), fp)) {
+		lineno++;
+		len = strlen(dump);
+
+		/*
+		 * Read a whole line from the file. If the line is too long
+		 * or is not terminated, reports an error and exit.
+		 */
+		if (dump[len - 1] != '\n') {
+			fprintf(stderr,
+			"Line %d not corrected terminated or too long\n",
+				lineno);
+			ret = -1;
+			break;
+		}
+
+		/* Drop ending line feed / carriage return */
+		while (len > 0 && (dump[len - 1] == '\n' ||
+				dump[len - 1] == '\r')) {
+			dump[len - 1] = '\0';
+			len--;
+		}
+
+		/* Skip comment or empty lines */
+		if ((len == 0) || dump[0] == '#')
+			continue;
+
+		/*
+		 * Search for variable's name,
+		 * remove leading whitespaces
+		 */
+		name = fw_string_blank(dump, 1);
+		if (!name)
+			continue;
+
+		/* The first white space is the end of variable name */
+		val = fw_string_blank(name, 0);
+		len = strlen(name);
+		if (val) {
+			*val++ = '\0';
+			if ((val - name) < len)
+				val = fw_string_blank(val, 1);
+			else
+				val = NULL;
+		}
+
+#ifdef DEBUG
+		fprintf(stderr, "Setting %s : %s\n",
+			name, val ? val : " removed");
+#endif
+
+		/*
+		 * If there is an error setting a variable,
+		 * try to save the environment and returns an error
+		 */
+		if (fw_env_write(name, val)) {
+			fprintf(stderr,
+			"fw_env_write returns with error : %s\n",
+				strerror(errno));
+			ret = -1;
+			break;
+		}
+
+	}
+
+	/* Close file if not stdin */
+	if (strcmp(fname, "-") != 0)
+		fclose(fp);
+
+	ret |= fw_env_close();
+
+	return ret;
+
 }
 
 /*
@@ -880,7 +1059,7 @@
 /*
  * Prevent confusion if running from erased flash memory
  */
-static int env_init (void)
+int fw_env_open(void)
 {
 	int crc0, crc0_ok;
 	char flag0;