/* * Copyright (c) International Business Machines Corp., 2008 * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. * * Author: Oliver Lohmann */ #include <ctype.h> #include <stdlib.h> #include <string.h> #include <stdio.h> #include <errno.h> #include <netinet/in.h> #include <sys/stat.h> #include <bootenv.h> #include "hashmap.h" #include "error.h" #include <mtd/ubi-media.h> #include "crc32.h" #define ubi_unused __attribute__((unused)) #define BOOTENV_MAXLINE 512 /* max line size of a bootenv.txt file */ /* Structures */ struct bootenv { hashmap_t map; ///< Pointer to hashmap which holds data structure. }; struct bootenv_list { hashmap_t head; ///< Pointer to list which holds the data structure. }; /** * @brief Remove the '\n' from a given line. * @param line Input/Output line. * @param size Size of the line. * @param fp File Pointer. * @return 0 * @return or error */ static int remove_lf(char *line, size_t size, FILE* fp) { size_t i; for (i = 0; i < size; i++) { if (line[i] == '\n') { line[i] = '\0'; return 0; } } if (!feof(fp)) { return BOOTENV_EINVAL; } return 0; } /** * @brief Determine if a line contains only WS. * @param line The line to process. * @param size Size of input line. * @return 1 Yes, only WS. * @return 0 No, contains data. */ static int is_ws(const char *line, size_t size) { size_t i = 0; while (i < size) { switch (line[i]) { case '\n': return 1; case '#': return 1; case ' ': i++; continue; case '\t': i++; continue; default: /* any other char -> no cmnt */ return 0; } } return 0; } /* ------------------------------------------------------------------------- */ /** * @brief Build a list from a comma seperated value string. * @param list Pointer to hashmap structure which shall store * the list. * @param value Comma seperated value string. * @return 0 * @return or error. */ static int build_list_definition(hashmap_t list, const char *value) { int rc = 0; char *str = NULL; char *ptr = NULL; size_t len, i, j; /* str: val1,val2 , val4,...,valN */ len = strlen(value); str = (char*) malloc((len+1) * sizeof(char)); /* 1. reformat string: remove spaces */ for (i = 0, j = 0; i < len; i++) { if (value[i] == ' ') continue; str[j] = value[i]; j++; } str[j] = '\0'; /* str: val1,val2,val4,...,valN\0*/ /* 2. replace ',' seperator with '\0' */ len = strlen(str); for (i = 0; i < len; i++) { if (str[i] == ',') { str[i] = '\0'; } } /* str: val1\0val2\0val4\0...\0valN\0*/ /* 3. insert definitions into a hash map, using it like a list */ i = j = 0; ptr = str; while (((i = strlen(ptr)) > 0) && (j < len)) { rc = hashmap_add(list, ptr, ""); if (rc != 0) { free(str); return rc; } j += i+1; if (j < len) ptr += i+1; } free(str); return rc; } /** * @brief Extract a key value pair and add it to a hashmap * @param str Input string which contains a key value pair. * @param env The updated handle which contains the new pair. * @return 0 * @return or error * @note The input string format is: "key=value" */ static int extract_pair(const char *str, bootenv_t env) { int rc = 0; char *key = NULL; char *val = NULL; key = strdup(str); if (key == NULL) return -ENOMEM; val = strstr(key, "="); if (val == NULL) { rc = BOOTENV_EBADENTRY; goto err; } *val = '\0'; /* split strings */ val++; rc = bootenv_set(env, key, val); err: free(key); return rc; } int bootenv_destroy(bootenv_t* env) { int rc = 0; if (env == NULL || *env == NULL) return -EINVAL; bootenv_t tmp = *env; rc = hashmap_free(tmp->map); if (rc != 0) return rc; free(tmp); return rc; } int bootenv_create(bootenv_t* env) { bootenv_t res; res = (bootenv_t) calloc(1, sizeof(struct bootenv)); if (res == NULL) return -ENOMEM; res->map = hashmap_new(); if (res->map == NULL) { free(res); return -ENOMEM; } *env = res; return 0; } /** * @brief Read a formatted buffer and scan it for valid bootenv * key/value pairs. Add those pairs into a hashmap. * @param env Hashmap which shall be used to hold the data. * @param buf Formatted buffer. * @param size Size of the buffer. * @return 0 * @return or error */ static int rd_buffer(bootenv_t env, const char *buf, size_t size) { const char *curr = buf; /* ptr to current key/value pair */ uint32_t i, j; /* current length, chars processed */ if (buf[size - 1] != '\0') /* must end in '\0' */ return BOOTENV_EFMT; for (j = 0; j < size; j += i, curr += i) { /* strlen returns the size of the string upto but not including the null terminator; adding 1 to account for '\0' */ i = strlen(curr) + 1; if (i == 1) return 0; /* no string found */ if (extract_pair(curr, env) != 0) return BOOTENV_EINVAL; } return 0; } int bootenv_read_crc(FILE* fp, bootenv_t env, size_t size, uint32_t* ret_crc) { int rc; char *buf = NULL; size_t i = 0; uint32_t crc32_table[256]; if ((fp == NULL) || (env == NULL)) return -EINVAL; /* allocate temp buffer */ buf = (char*) calloc(1, size * sizeof(char)); if (buf == NULL) return -ENOMEM; /* FIXME Andreas, please review this I removed size-1 and * replaced it by just size, I saw the kernel image starting * with a 0x0060.... and not with the 0x60.... what it should * be. Is this a tools problem or is it a problem here where * fp is moved not to the right place due to the former size-1 * here. */ while((i < size) && (!feof(fp))) { int c = fgetc(fp); if (c == EOF) { /* FIXME isn't this dangerous, to update the boot envs with incomplete data? */ buf[i++] = '\0'; break; /* we have enough */ } if (ferror(fp)) { rc = -EIO; goto err; } buf[i++] = (char)c; } /* calculate crc to return */ if (ret_crc != NULL) { init_crc32_table(crc32_table); *ret_crc = clc_crc32(crc32_table, UBI_CRC32_INIT, buf, size); } /* transfer to hashmap */ rc = rd_buffer(env, buf, size); err: free(buf); return rc; } /** * If we have a single file containing the boot-parameter size should * be specified either as the size of the file or as BOOTENV_MAXSIZE. * If the bootparameter are in the middle of a file we need the exact * length of the data. */ int bootenv_read(FILE* fp, bootenv_t env, size_t size) { return bootenv_read_crc(fp, env, size, NULL); } int bootenv_read_txt(FILE* fp, bootenv_t env) { int rc = 0; char *buf = NULL; char *line = NULL; char *lstart = NULL; char *curr = NULL; size_t len; size_t size; if ((fp == NULL) || (env == NULL)) return -EINVAL; size = BOOTENV_MAXSIZE; /* allocate temp buffers */ buf = (char*) calloc(1, size * sizeof(char)); lstart = line = (char*) calloc(1, size * sizeof(char)); if ((buf == NULL) || (line == NULL)) { rc = -ENOMEM; goto err; } curr = buf; while ((line = fgets(line, size, fp)) != NULL) { if (is_ws(line, size)) { continue; } rc = remove_lf(line, BOOTENV_MAXSIZE, fp); if (rc != 0) { goto err; } /* copy new line to binary buffer */ len = strlen(line); if (len > size) { rc = -EFBIG; goto err; } size -= len; /* track remaining space */ memcpy(curr, line, len); curr += len + 1; /* for \0 seperator */ } rc = rd_buffer(env, buf, BOOTENV_MAXSIZE); err: if (buf != NULL) free(buf); if (lstart != NULL) free(lstart); return rc; } static int fill_output_buffer(bootenv_t env, char *buf, size_t buf_size_max ubi_unused, size_t *written) { int rc = 0; size_t keys_size, i; size_t wr = 0; const char **keys = NULL; const char *val = NULL; rc = bootenv_get_key_vector(env, &keys_size, 1, &keys); if (rc != 0) goto err; for (i = 0; i < keys_size; i++) { if (wr > BOOTENV_MAXSIZE) { rc = -ENOSPC; goto err; } rc = bootenv_get(env, keys[i], &val); if (rc != 0) goto err; wr += snprintf(buf + wr, BOOTENV_MAXSIZE - wr, "%s=%s", keys[i], val); wr++; /* for \0 */ } *written = wr; err: if (keys != NULL) free(keys); return rc; } int bootenv_write_crc(FILE* fp, bootenv_t env, uint32_t* ret_crc) { int rc = 0; size_t size = 0; char *buf = NULL; uint32_t crc32_table[256]; if ((fp == NULL) || (env == NULL)) return -EINVAL; buf = (char*) calloc(1, BOOTENV_MAXSIZE * sizeof(char)); if (buf == NULL) return -ENOMEM; rc = fill_output_buffer(env, buf, BOOTENV_MAXSIZE, &size); if (rc != 0) goto err; /* calculate crc to return */ if (ret_crc != NULL) { init_crc32_table(crc32_table); *ret_crc = clc_crc32(crc32_table, UBI_CRC32_INIT, buf, size); } if (fwrite(buf, size, 1, fp) != 1) { rc = -EIO; goto err; } err: if (buf != NULL) free(buf); return rc; } int bootenv_write(FILE* fp, bootenv_t env) { return bootenv_write_crc(fp, env, NULL); } int bootenv_compare(bootenv_t first, bootenv_t second) { int rc; size_t written_first, written_second; char *buf_first, *buf_second; if (first == NULL || second == NULL) return -EINVAL; buf_first = malloc(BOOTENV_MAXSIZE); if (!buf_first) return -ENOMEM; buf_second = malloc(BOOTENV_MAXSIZE); if (!buf_second) { rc = -ENOMEM; goto err; } rc = fill_output_buffer(first, buf_first, BOOTENV_MAXSIZE, &written_first); if (rc < 0) goto err; rc = fill_output_buffer(second, buf_second, BOOTENV_MAXSIZE, &written_second); if (rc < 0) goto err; if (written_first != written_second) { rc = 1; goto err; } rc = memcmp(buf_first, buf_second, written_first); if (rc != 0) { rc = 2; goto err; } err: if (buf_first) free(buf_first); if (buf_second) free(buf_second); return rc; } int bootenv_size(bootenv_t env, size_t *size) { int rc = 0; char *buf = NULL; if (env == NULL) return -EINVAL; buf = (char*) calloc(1, BOOTENV_MAXSIZE * sizeof(char)); if (buf == NULL) return -ENOMEM; rc = fill_output_buffer(env, buf, BOOTENV_MAXSIZE, size); if (rc != 0) goto err; err: if (buf != NULL) free(buf); return rc; } int bootenv_write_txt(FILE* fp, bootenv_t env) { int rc = 0; size_t size, wr, i; const char **keys = NULL; const char *key = NULL; const char *val = NULL; if ((fp == NULL) || (env == NULL)) return -EINVAL; rc = bootenv_get_key_vector(env, &size, 1, &keys); if (rc != 0) goto err; for (i = 0; i < size; i++) { key = keys[i]; rc = bootenv_get(env, key, &val); if (rc != 0) goto err; wr = fprintf(fp, "%s=%s\n", key, val); if (wr != strlen(key) + strlen(val) + 2) { rc = -EIO; goto err; } } err: if (keys != NULL) free(keys); return rc; } int bootenv_valid(bootenv_t env ubi_unused) { /* @FIXME No sanity check implemented. */ return 0; } int bootenv_copy_bootenv(bootenv_t in, bootenv_t *out) { int rc = 0; const char *tmp = NULL; const char **keys = NULL; size_t vec_size, i; if ((in == NULL) || (out == NULL)) return -EINVAL; /* purge output var for sure... */ rc = bootenv_destroy(out); if (rc != 0) return rc; /* create the new map */ rc = bootenv_create(out); if (rc != 0) goto err; /* get the key list from the input map */ rc = bootenv_get_key_vector(in, &vec_size, 0, &keys); if (rc != 0) goto err; if (vec_size != hashmap_size(in->map)) { rc = BOOTENV_ECOPY; goto err; } /* make a deep copy of the hashmap */ for (i = 0; i < vec_size; i++) { rc = bootenv_get(in, keys[i], &tmp); if (rc != 0) goto err; rc = bootenv_set(*out, keys[i], tmp); if (rc != 0) goto err; } err: if (keys != NULL) free(keys); return rc; } /* ------------------------------------------------------------------------- */ int bootenv_pdd_keep(bootenv_t env_old, bootenv_t env_new, bootenv_t *env_res, int *warnings, char *err_buf ubi_unused, size_t err_buf_size ubi_unused) { bootenv_list_t l_old = NULL; bootenv_list_t l_new = NULL; const char *pdd_old = NULL; const char *pdd_new = NULL; const char *tmp = NULL; const char **vec_old = NULL; const char **vec_new = NULL; const char **pdd_up_vec = NULL; size_t vec_old_size, vec_new_size, pdd_up_vec_size, i; int rc = 0; if ((env_old == NULL) || (env_new == NULL) || (env_res == NULL)) return -EINVAL; /* get the pdd strings, e.g.: * pdd_old=a,b,c * pdd_new=a,c,d,e */ rc = bootenv_get(env_old, "pdd", &pdd_old); if (rc != 0) goto err; rc = bootenv_get(env_new, "pdd", &pdd_new); if (rc != 0) goto err; /* put it into a list and then convert it to an vector */ rc = bootenv_list_create(&l_old); if (rc != 0) goto err; rc = bootenv_list_create(&l_new); if (rc != 0) goto err; rc = bootenv_list_import(l_old, pdd_old); if (rc != 0) goto err; rc = bootenv_list_import(l_new, pdd_new); if (rc != 0) goto err; rc = bootenv_list_to_vector(l_old, &vec_old_size, &vec_old); if (rc != 0) goto err; rc = bootenv_list_to_vector(l_new, &vec_new_size, &vec_new); if (rc != 0) goto err; rc = bootenv_copy_bootenv(env_new, env_res); if (rc != 0) goto err; /* calculate the update vector between the old and new pdd */ pdd_up_vec = hashmap_get_update_key_vector(vec_old, vec_old_size, vec_new, vec_new_size, &pdd_up_vec_size); if (pdd_up_vec == NULL) { rc = -ENOMEM; goto err; } if (pdd_up_vec_size != 0) { /* need to warn the user about the unset of * some pdd/bootenv values */ *warnings = BOOTENV_WPDD_STRING_DIFFERS; /* remove all entries in the new bootenv load */ for (i = 0; i < pdd_up_vec_size; i++) { bootenv_unset(*env_res, pdd_up_vec[i]); } } /* generate the keep array and copy old pdd values to new bootenv */ for (i = 0; i < vec_old_size; i++) { rc = bootenv_get(env_old, vec_old[i], &tmp); if (rc != 0) { rc = BOOTENV_EPDDINVAL; goto err; } rc = bootenv_set(*env_res, vec_old[i], tmp); if (rc != 0) { goto err; } } /* put the old pdd string into the result map */ rc = bootenv_set(*env_res, "pdd", pdd_old); if (rc != 0) { goto err; } err: if (vec_old != NULL) free(vec_old); if (vec_new != NULL) free(vec_new); if (pdd_up_vec != NULL) free(pdd_up_vec); bootenv_list_destroy(&l_old); bootenv_list_destroy(&l_new); return rc; } int bootenv_pdd_overwrite(bootenv_t env_old, bootenv_t env_new, bootenv_t *env_res, int *warnings ubi_unused, char *err_buf ubi_unused, size_t err_buf_size ubi_unused) { if ((env_old == NULL) || (env_new == NULL) || (env_res == NULL)) return -EINVAL; return bootenv_copy_bootenv(env_new, env_res); } int bootenv_pdd_merge(bootenv_t env_old, bootenv_t env_new, bootenv_t *env_res, int *warnings ubi_unused, char *err_buf, size_t err_buf_size) { if ((env_old == NULL) || (env_new == NULL) || (env_res == NULL)) return -EINVAL; snprintf(err_buf, err_buf_size, "The PDD merge operation is not " "implemented. Contact: <oliloh@de.ibm.com>"); return BOOTENV_ENOTIMPL; } /* ------------------------------------------------------------------------- */ int bootenv_get(bootenv_t env, const char *key, const char **value) { if (env == NULL) return -EINVAL; *value = hashmap_lookup(env->map, key); if (*value == NULL) return BOOTENV_ENOTFOUND; return 0; } int bootenv_get_num(bootenv_t env, const char *key, uint32_t *value) { char *endptr = NULL; const char *str; if (env == NULL) return 0; str = hashmap_lookup(env->map, key); if (!str) return -EINVAL; *value = strtoul(str, &endptr, 0); if (*endptr == '\0') { return 0; } return -EINVAL; } int bootenv_set(bootenv_t env, const char *key, const char *value) { if (env == NULL) return -EINVAL; return hashmap_add(env->map, key, value); } int bootenv_unset(bootenv_t env, const char *key) { if (env == NULL) return -EINVAL; return hashmap_remove(env->map, key); } int bootenv_get_key_vector(bootenv_t env, size_t* size, int sort, const char ***vector) { if ((env == NULL) || (size == NULL)) return -EINVAL; *vector = hashmap_get_key_vector(env->map, size, sort); if (*vector == NULL) return -EINVAL; return 0; } int bootenv_dump(bootenv_t env) { if (env == NULL) return -EINVAL; return hashmap_dump(env->map); } int bootenv_list_create(bootenv_list_t *list) { bootenv_list_t res; res = (bootenv_list_t) calloc(1, sizeof(struct bootenv_list)); if (res == NULL) return -ENOMEM; res->head = hashmap_new(); if (res->head == NULL) { free(res); return -ENOMEM; } *list = res; return 0; } int bootenv_list_destroy(bootenv_list_t *list) { int rc = 0; if (list == NULL) return -EINVAL; bootenv_list_t tmp = *list; if (tmp == 0) return 0; rc = hashmap_free(tmp->head); if (rc != 0) return rc; free(tmp); *list = NULL; return 0; } int bootenv_list_import(bootenv_list_t list, const char *str) { if (list == NULL) return -EINVAL; return build_list_definition(list->head, str); } int bootenv_list_export(bootenv_list_t list, char **string) { size_t size, i, j, bufsize, tmp, rc = 0; const char **items; if (list == NULL) return -EINVAL; bufsize = BOOTENV_MAXLINE; char *res = (char*) malloc(bufsize * sizeof(char)); if (res == NULL) return -ENOMEM; rc = bootenv_list_to_vector(list, &size, &items); if (rc != 0) { goto err; } j = 0; for (i = 0; i < size; i++) { tmp = strlen(items[i]); if (j >= bufsize) { bufsize += BOOTENV_MAXLINE; res = (char*) realloc(res, bufsize * sizeof(char)); if (res == NULL) { rc = -ENOMEM; goto err; } } memcpy(res + j, items[i], tmp); j += tmp; if (i < (size - 1)) { res[j] = ','; j++; } } j++; res[j] = '\0'; free(items); *string = res; return 0; err: free(items); return rc; } int bootenv_list_add(bootenv_list_t list, const char *item) { if ((list == NULL) || (item == NULL)) return -EINVAL; return hashmap_add(list->head, item, ""); } int bootenv_list_remove(bootenv_list_t list, const char *item) { if ((list == NULL) || (item == NULL)) return -EINVAL; return hashmap_remove(list->head, item); } int bootenv_list_is_in(bootenv_list_t list, const char *item) { if ((list == NULL) || (item == NULL)) return -EINVAL; return hashmap_lookup(list->head, item) != NULL ? 1 : 0; } int bootenv_list_to_vector(bootenv_list_t list, size_t *size, const char ***vector) { if ((list == NULL) || (size == NULL)) return -EINVAL; *vector = hashmap_get_key_vector(list->head, size, 1); if (*vector == NULL) return -ENOMEM; return 0; } int bootenv_list_to_num_vector(bootenv_list_t list, size_t *size, uint32_t **vector) { int rc = 0; size_t i; uint32_t* res = NULL; char *endptr = NULL; const char **a = NULL; rc = bootenv_list_to_vector(list, size, &a); if (rc != 0) goto err; res = (uint32_t*) malloc (*size * sizeof(uint32_t)); if (!res) goto err; for (i = 0; i < *size; i++) { res[i] = strtoul(a[i], &endptr, 0); if (*endptr != '\0') goto err; } if (a) free(a); *vector = res; return 0; err: if (a) free(a); if (res) free(res); return rc; }