| /* |
| * Copyright International Business Machines Corp., 2006, 2007 |
| * |
| * 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. |
| */ |
| |
| /* |
| * Authors: Oliver Lohmann <oliloh@de.ibm.com> |
| * Drake Dowsett <dowsett@de.ibm.com> |
| * Contact: Andreas Arnez <anrez@de.ibm.com> |
| */ |
| |
| /* TODO Compare data before writing it. This implies that the volume |
| * parameters are compared first: size, alignment, name, type, ..., |
| * this is the same, compare the data. Volume deletion is deffered |
| * until the difference has been found out. |
| */ |
| |
| #include <stdio.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #define __USE_GNU |
| #include <string.h> |
| #include <stdlib.h> |
| #include <limits.h> |
| #include <sys/ioctl.h> |
| |
| #include <libubi.h> |
| #include <pfiflash.h> |
| |
| #include <mtd/ubi-user.h> /* FIXME Is this ok here? */ |
| #include <mtd/mtd-user.h> |
| |
| #include "pfiflash_error.h" |
| #include "ubimirror.h" |
| #include "error.h" |
| #include "reader.h" |
| #include "example_ubi.h" |
| #include "bootenv.h" |
| |
| /* ubi-header.h and crc32.h needed for CRC checking */ |
| #include <mtd/ubi-media.h> /* FIXME Is this ok here? */ |
| #include "crc32.h" |
| |
| #define ubi_unused __attribute__((unused)) |
| |
| #define COMPARE_BUFFER_SIZE 2048 |
| |
| #define DEFAULT_DEV_PATTERN "/dev/ubi%d" |
| #define DEFAULT_VOL_PATTERN "/dev/ubi%d_%d" |
| |
| static const char copyright [] ubi_unused = |
| "Copyright International Business Machines Corp., 2006, 2007"; |
| |
| /* simply clear buffer, then write into front of it */ |
| #define EBUF(fmt...) \ |
| snprintf(err_buf, err_buf_size, fmt); |
| |
| /* make a history of buffer and then prepend something in front */ |
| #define EBUF_PREPEND(fmt) \ |
| do { \ |
| int EBUF_HISTORY_LENGTH = strlen(err_buf); \ |
| char EBUF_HISTORY[EBUF_HISTORY_LENGTH + 1]; \ |
| strncpy(EBUF_HISTORY, err_buf, EBUF_HISTORY_LENGTH + 1);\ |
| EBUF(fmt ": %s", EBUF_HISTORY); \ |
| } while (0) |
| |
| /* An array of PDD function pointers indexed by the algorithm. */ |
| static pdd_func_t pdd_funcs[PDD_HANDLING_NUM] = |
| { |
| &bootenv_pdd_keep, |
| &bootenv_pdd_merge, |
| &bootenv_pdd_overwrite |
| }; |
| |
| typedef enum ubi_update_process_t { |
| UBI_REMOVE = 0, |
| UBI_WRITE, |
| UBI_COMPARE, |
| } ubi_update_process_t; |
| |
| |
| /** |
| * skip_raw_volumes - reads data from pfi to advance fp past raw block |
| * @pfi: fp to pfi data |
| * @pfi_raws: header information |
| * |
| * Error handling): |
| * when early EOF in pfi data |
| * - returns -PFIFLASH_ERR_EOF, err_buf matches text to err |
| * when file I/O error |
| * - returns -PFIFLASH_ERR_FIO, err_buf matches text to err |
| **/ |
| static int |
| skip_raw_volumes(FILE* pfi, list_t pfi_raws, |
| char* err_buf, size_t err_buf_size) |
| { |
| int rc; |
| void *i; |
| list_t ptr; |
| |
| if (is_empty(pfi_raws)) |
| return 0; |
| |
| rc = 0; |
| foreach(i, ptr, pfi_raws) { |
| size_t j; |
| pfi_raw_t raw; |
| |
| raw = (pfi_raw_t)i; |
| for(j = 0; j < raw->data_size; j++) { |
| int c; |
| |
| c = fgetc(pfi); |
| if (c == EOF) |
| rc = -PFIFLASH_ERR_EOF; |
| else if (ferror(pfi)) |
| rc = -PFIFLASH_ERR_FIO; |
| |
| if (rc != 0) |
| goto err; |
| } |
| } |
| |
| err: |
| EBUF("%s", PFIFLASH_ERRSTR[-rc]); |
| return rc; |
| } |
| |
| |
| /** |
| * my_ubi_mkvol - wraps the ubi_mkvol functions and impl. bootenv update hook |
| * @devno: UBI device number. |
| * @s: Current seqnum. |
| * @u: Information about the UBI volume from the PFI. |
| * |
| * Error handling: |
| * when UBI system couldn't be opened |
| * - returns -PFIFLASH_ERR_UBI_OPEN, err_buf matches text to err |
| * when UBI system couldn't create a volume |
| * - returns -PFIFLASH_ERR_UBI_MKVOL, err_buf matches text to err |
| **/ |
| static int |
| my_ubi_mkvol(int devno, int s, pfi_ubi_t u, |
| char *err_buf, size_t err_buf_size) |
| { |
| int rc, type; |
| char path[PATH_MAX]; |
| libubi_t ulib; |
| struct ubi_mkvol_request req; |
| |
| rc = 0; |
| ulib = NULL; |
| |
| log_msg("[ ubimkvol id=%d, size=%d, data_size=%d, type=%d, " |
| "alig=%d, nlen=%d, name=%s", |
| u->ids[s], u->size, u->data_size, u->type, u->alignment, |
| strnlen(u->names[s], PFI_UBI_VOL_NAME_LEN), u->names[s]); |
| |
| ulib = libubi_open(); |
| if (ulib == NULL) { |
| rc = -PFIFLASH_ERR_UBI_OPEN; |
| EBUF("%s", PFIFLASH_ERRSTR[-rc]); |
| goto err; |
| } |
| |
| switch (u->type) { |
| case pfi_ubi_static: |
| type = UBI_STATIC_VOLUME; break; |
| case pfi_ubi_dynamic: |
| default: |
| type = UBI_DYNAMIC_VOLUME; |
| } |
| |
| snprintf(path, PATH_MAX, DEFAULT_DEV_PATTERN, devno); |
| |
| req.vol_id = u->ids[s]; |
| req.alignment = u->alignment; |
| req.bytes = u->size; |
| req.vol_type = type; |
| req.name = u->names[s]; |
| |
| rc = ubi_mkvol(ulib, path, &req); |
| if (rc != 0) { |
| rc = -PFIFLASH_ERR_UBI_MKVOL; |
| EBUF(PFIFLASH_ERRSTR[-rc], u->ids[s]); |
| goto err; |
| } |
| |
| err: |
| if (ulib != NULL) |
| libubi_close(ulib); |
| |
| return rc; |
| } |
| |
| |
| /** |
| * my_ubi_rmvol - a wrapper around the UBI library function ubi_rmvol |
| * @devno UBI device number |
| * @id UBI volume id to remove |
| * |
| * If the volume does not exist, the function will return success. |
| * |
| * Error handling: |
| * when UBI system couldn't be opened |
| * - returns -PFIFLASH_ERR_UBI_OPEN, err_buf matches text to err |
| * when UBI system couldn't update (truncate) a volume |
| * - returns -PFIFLASH_ERR_UBI_VOL_UPDATE, err_buf matches text to err |
| * when UBI system couldn't remove a volume |
| * - returns -PFIFLASH_ERR_UBI_RMVOL, err_buf matches text to err |
| **/ |
| static int |
| my_ubi_rmvol(int devno, uint32_t id, |
| char *err_buf, size_t err_buf_size) |
| { |
| int rc, fd; |
| char path[PATH_MAX]; |
| libubi_t ulib; |
| |
| rc = 0; |
| ulib = NULL; |
| |
| log_msg("[ ubirmvol id=%d", id); |
| |
| ulib = libubi_open(); |
| if (ulib == NULL) { |
| rc = -PFIFLASH_ERR_UBI_OPEN; |
| EBUF("%s", PFIFLASH_ERRSTR[-rc]); |
| goto err; |
| } |
| |
| snprintf(path, PATH_MAX, DEFAULT_VOL_PATTERN, devno, id); |
| |
| /* truncate whether it exist or not */ |
| fd = open(path, O_RDWR); |
| if (fd < 0) { |
| libubi_close(ulib); |
| return 0; /* not existent, return 0 */ |
| } |
| |
| rc = ubi_update_start(ulib, fd, 0); |
| close(fd); |
| if (rc < 0) { |
| rc = -PFIFLASH_ERR_UBI_VOL_UPDATE; |
| EBUF(PFIFLASH_ERRSTR[-rc], id); |
| goto err; /* if EBUSY than empty device, continue */ |
| } |
| |
| snprintf(path, PATH_MAX, DEFAULT_DEV_PATTERN, devno); |
| |
| rc = ubi_rmvol(ulib, path, id); |
| if (rc != 0) { |
| #ifdef DEBUG |
| int rc_old = rc; |
| dbg_msg("Remove UBI volume %d returned with error: %d " |
| "errno=%d", id, rc_old, errno); |
| #endif |
| |
| rc = -PFIFLASH_ERR_UBI_RMVOL; |
| EBUF(PFIFLASH_ERRSTR[-rc], id); |
| |
| /* TODO Define a ubi_rmvol return value which says |
| * sth like EUBI_NOSUCHDEV. In this case, a failed |
| * operation is acceptable. Everything else has to be |
| * classified as real error. But talk to Andreas Arnez |
| * before defining something odd... |
| */ |
| /* if ((errno == EINVAL) || (errno == ENODEV)) |
| return 0; */ /* currently it is EINVAL or ENODEV */ |
| |
| goto err; |
| } |
| |
| err: |
| if (ulib != NULL) |
| libubi_close(ulib); |
| |
| return rc; |
| } |
| |
| |
| /** |
| * read_bootenv_volume - reads the current bootenv data from id into be_old |
| * @devno UBI device number |
| * @id UBI volume id to remove |
| * @bootenv_old to hold old boot_env data |
| * |
| * Error handling: |
| * when UBI system couldn't be opened |
| * - returns -PFIFLASH_ERR_UBI_OPEN, err_buf matches text to err |
| * when UBI system couldn't open a volume to read |
| * - returns -PFIFLASH_ERR_UBI_VOL_FOPEN, err_buf matches text to err |
| * when couldn't read bootenv data |
| * - returns -PFIFLASH_ERR_BOOTENV_READ, err_buf matches text to err |
| **/ |
| static int |
| read_bootenv_volume(int devno, uint32_t id, bootenv_t bootenv_old, |
| char *err_buf, size_t err_buf_size) |
| { |
| int rc; |
| FILE* fp_in; |
| char path[PATH_MAX]; |
| libubi_t ulib; |
| |
| rc = 0; |
| fp_in = NULL; |
| ulib = NULL; |
| |
| ulib = libubi_open(); |
| if (ulib == NULL) { |
| rc = -PFIFLASH_ERR_UBI_OPEN; |
| EBUF("%s", PFIFLASH_ERRSTR[-rc]); |
| goto err; |
| } |
| |
| snprintf(path, PATH_MAX, DEFAULT_VOL_PATTERN, devno, id); |
| |
| fp_in = fopen(path, "r"); |
| if (!fp_in) { |
| rc = -PFIFLASH_ERR_UBI_VOL_FOPEN; |
| EBUF(PFIFLASH_ERRSTR[-rc], id); |
| goto err; |
| } |
| |
| log_msg("[ reading old bootenvs ..."); |
| |
| /* Save old bootenvs for reference */ |
| rc = bootenv_read(fp_in, bootenv_old, BOOTENV_MAXSIZE); |
| if (rc != 0) { |
| rc = -PFIFLASH_ERR_BOOTENV_READ; |
| EBUF("%s", PFIFLASH_ERRSTR[-rc]); |
| goto err; |
| } |
| |
| err: |
| if (fp_in) |
| fclose(fp_in); |
| if (ulib) |
| libubi_close(ulib); |
| |
| return rc; |
| } |
| |
| |
| /** |
| * write_bootenv_volume - writes data from PFI file int to bootenv UBI volume |
| * @devno UBI device number |
| * @id UBI volume id |
| * @bootend_old old PDD data from machine |
| * @pdd_f function to handle PDD with |
| * @fp_in new pdd data contained in PFI |
| * @fp_in_size data size of new pdd data in PFI |
| * @pfi_crc crc value from PFI header |
| * |
| * Error handling: |
| * when UBI system couldn't be opened |
| * - returns -PFIFLASH_ERR_UBI_OPEN, err_buf matches text to err |
| * when bootenv can't be created |
| * - returns -PFIFLASH_ERR_BOOTENV_CREATE, err_buf matches text to err |
| * when bootenv can't be read |
| * - returns -PFIFLASH_ERR_BOOTENV_READ, err_buf matches text to err |
| * when PDD handling function returns and error |
| * - passes rc and err_buf data |
| * when CRC check fails |
| * - returns -PFIFLASH_ERR_CRC_CHECK, err_buf matches text to err |
| * when bootenv can't be resized |
| * - returns -PFIFLASH_ERR_BOOTENV_SIZE, err_buf matches text to err |
| * when UBI system couldn't open a volume |
| * - returns -PFIFLASH_ERR_UBI_VOL_FOPEN, err_buf matches text to err |
| * when couldn't write bootenv data |
| * - returns -PFIFLASH_ERR_BOOTENV_WRITE, err_buf matches text to err |
| **/ |
| static int |
| write_bootenv_volume(int devno, uint32_t id, bootenv_t bootenv_old, |
| pdd_func_t pdd_f, FILE* fp_in, size_t fp_in_size, |
| uint32_t pfi_crc, |
| char *err_buf, size_t err_buf_size) |
| { |
| int rc, warnings, fd_out; |
| uint32_t crc; |
| char path[PATH_MAX]; |
| size_t update_size; |
| FILE *fp_out; |
| bootenv_t bootenv_new, bootenv_res; |
| libubi_t ulib; |
| |
| rc = 0; |
| warnings = 0; |
| crc = 0; |
| update_size = 0; |
| fp_out = NULL; |
| bootenv_new = NULL; |
| bootenv_res = NULL; |
| ulib = NULL; |
| |
| log_msg("[ ubiupdatevol bootenv id=%d, fp_in=%p", id, fp_in); |
| |
| /* Workflow: |
| * 1. Apply PDD operation and get the size of the returning |
| * bootenv_res section. Without the correct size it wouldn't |
| * be possible to call UBI update vol. |
| * 2. Call UBI update vol |
| * 3. Get FILE* to vol dev |
| * 4. Write to FILE* |
| */ |
| |
| ulib = libubi_open(); |
| if (ulib == NULL) { |
| rc = -PFIFLASH_ERR_UBI_OPEN; |
| EBUF("%s", PFIFLASH_ERRSTR[-rc]); |
| goto err; |
| } |
| |
| rc = bootenv_create(&bootenv_new); |
| if (rc != 0) { |
| rc = -PFIFLASH_ERR_BOOTENV_CREATE; |
| EBUF(PFIFLASH_ERRSTR[-rc], " 'new'"); |
| goto err; |
| } |
| |
| rc = bootenv_create(&bootenv_res); |
| if (rc != 0) { |
| rc = -PFIFLASH_ERR_BOOTENV_CREATE; |
| EBUF(PFIFLASH_ERRSTR[-rc], " 'res'"); |
| goto err; |
| } |
| |
| rc = bootenv_read_crc(fp_in, bootenv_new, fp_in_size, &crc); |
| if (rc != 0) { |
| rc = -PFIFLASH_ERR_BOOTENV_READ; |
| EBUF("%s", PFIFLASH_ERRSTR[-rc]); |
| goto err; |
| } else if (crc != pfi_crc) { |
| rc = -PFIFLASH_ERR_CRC_CHECK; |
| EBUF(PFIFLASH_ERRSTR[-rc], pfi_crc, crc); |
| goto err; |
| } |
| |
| rc = pdd_f(bootenv_old, bootenv_new, &bootenv_res, &warnings, |
| err_buf, err_buf_size); |
| if (rc != 0) { |
| EBUF_PREPEND("handling PDD"); |
| goto err; |
| } |
| else if (warnings) |
| /* TODO do something with warnings */ |
| dbg_msg("A warning in the PDD operation occured: %d", |
| warnings); |
| |
| rc = bootenv_size(bootenv_res, &update_size); |
| if (rc != 0) { |
| rc = -PFIFLASH_ERR_BOOTENV_SIZE; |
| EBUF("%s", PFIFLASH_ERRSTR[-rc]); |
| goto err; |
| } |
| |
| snprintf(path, PATH_MAX, DEFAULT_VOL_PATTERN, devno, id); |
| |
| fd_out = open(path, O_RDWR); |
| if (fd_out < 0) { |
| rc = -PFIFLASH_ERR_UBI_VOL_FOPEN; |
| EBUF(PFIFLASH_ERRSTR[-rc], id); |
| goto err; |
| } |
| fp_out = fdopen(fd_out, "r+"); |
| if (!fp_out) { |
| rc = -PFIFLASH_ERR_UBI_VOL_FOPEN; |
| EBUF(PFIFLASH_ERRSTR[-rc], id); |
| goto err; |
| } |
| rc = ubi_update_start(ulib, fd_out, update_size); |
| if (rc < 0) { |
| rc = -PFIFLASH_ERR_UBI_VOL_UPDATE; |
| EBUF(PFIFLASH_ERRSTR[-rc], id); |
| goto err; |
| } |
| |
| rc = bootenv_write(fp_out, bootenv_res); |
| if (rc != 0) { |
| rc = -PFIFLASH_ERR_BOOTENV_WRITE; |
| EBUF(PFIFLASH_ERRSTR[-rc], devno, id); |
| goto err; |
| } |
| |
| err: |
| if (ulib != NULL) |
| libubi_close(ulib); |
| if (bootenv_new != NULL) |
| bootenv_destroy(&bootenv_new); |
| if (bootenv_res != NULL) |
| bootenv_destroy(&bootenv_res); |
| if (fp_out) |
| fclose(fp_out); |
| |
| return rc; |
| } |
| |
| |
| /** |
| * write_normal_volume - writes data from PFI file int to regular UBI volume |
| * @devno UBI device number |
| * @id UBI volume id |
| * @update_size size of data stream |
| * @fp_in PFI data file pointer |
| * @pfi_crc CRC data from PFI header |
| * |
| * Error handling: |
| * when UBI system couldn't be opened |
| * - returns -PFIFLASH_ERR_UBI_OPEN, err_buf matches text to err |
| * when UBI system couldn't open a volume |
| * - returns -PFIFLASH_ERR_UBI_VOL_FOPEN, err_buf matches text to err |
| * when unexpected EOF is encountered |
| * - returns -PFIFLASH_ERR_EOF, err_buf matches text to err |
| * when file I/O error |
| * - returns -PFIFLASH_ERR_FIO, err_buf matches text to err |
| * when CRC check fails |
| * - retruns -PFIFLASH_ERR_CRC_CHECK, err_buf matches text to err |
| **/ |
| static int |
| write_normal_volume(int devno, uint32_t id, size_t update_size, FILE* fp_in, |
| uint32_t pfi_crc, |
| char *err_buf, size_t err_buf_size) |
| { |
| int rc, fd_out; |
| uint32_t crc, crc32_table[256]; |
| char path[PATH_MAX]; |
| size_t bytes_left; |
| FILE* fp_out; |
| libubi_t ulib; |
| |
| rc = 0; |
| crc = UBI_CRC32_INIT; |
| bytes_left = update_size; |
| fp_out = NULL; |
| ulib = NULL; |
| |
| log_msg("[ ubiupdatevol id=%d, update_size=%d fp_in=%p", |
| id, update_size, fp_in); |
| |
| ulib = libubi_open(); |
| if (ulib == NULL) { |
| rc = -PFIFLASH_ERR_UBI_OPEN; |
| EBUF("%s", PFIFLASH_ERRSTR[-rc]); |
| goto err; |
| } |
| |
| snprintf(path, PATH_MAX, DEFAULT_VOL_PATTERN, devno, id); |
| |
| fd_out = open(path, O_RDWR); |
| if (fd_out < 0) { |
| rc = -PFIFLASH_ERR_UBI_VOL_FOPEN; |
| EBUF(PFIFLASH_ERRSTR[-rc], id); |
| goto err; |
| } |
| fp_out = fdopen(fd_out, "r+"); |
| if (!fp_out) { |
| rc = -PFIFLASH_ERR_UBI_VOL_FOPEN; |
| EBUF(PFIFLASH_ERRSTR[-rc], id); |
| goto err; |
| } |
| rc = ubi_update_start(ulib, fd_out, update_size); |
| if (rc < 0) { |
| rc = -PFIFLASH_ERR_UBI_VOL_UPDATE; |
| EBUF(PFIFLASH_ERRSTR[-rc], id); |
| goto err; |
| } |
| |
| init_crc32_table(crc32_table); |
| while (bytes_left) { |
| char buf[1024]; |
| size_t to_rw = sizeof buf > bytes_left ? |
| bytes_left : sizeof buf; |
| if (fread(buf, 1, to_rw, fp_in) != to_rw) { |
| rc = -PFIFLASH_ERR_EOF; |
| EBUF("%s", PFIFLASH_ERRSTR[-rc]); |
| goto err; |
| } |
| crc = clc_crc32(crc32_table, crc, buf, to_rw); |
| if (fwrite(buf, 1, to_rw, fp_out) != to_rw) { |
| rc = -PFIFLASH_ERR_FIO; |
| EBUF("%s", PFIFLASH_ERRSTR[-rc]); |
| goto err; |
| } |
| bytes_left -= to_rw; |
| } |
| |
| if (crc != pfi_crc) { |
| rc = -PFIFLASH_ERR_CRC_CHECK; |
| EBUF(PFIFLASH_ERRSTR[-rc], pfi_crc, crc); |
| goto err; |
| } |
| |
| err: |
| if (fp_out) |
| fclose(fp_out); |
| if (ulib) |
| libubi_close(ulib); |
| |
| return rc; |
| } |
| |
| static int compare_bootenv(FILE *fp_pfi, FILE **fp_flash, uint32_t ids_size, |
| uint32_t data_size, pdd_func_t pdd_f, char *err_buf, |
| size_t err_buf_size) |
| { |
| int rc, warnings = 0; |
| unsigned int i; |
| bootenv_t bootenv_pfi, bootenv_res = NULL, bootenv_flash = NULL; |
| |
| rc = bootenv_create(&bootenv_pfi); |
| if (rc != 0) { |
| rc = -PFIFLASH_ERR_BOOTENV_CREATE; |
| goto err; |
| } |
| |
| rc = bootenv_create(&bootenv_res); |
| if (rc != 0) { |
| rc = -PFIFLASH_ERR_BOOTENV_CREATE; |
| goto err; |
| } |
| |
| rc = bootenv_read(fp_pfi, bootenv_pfi, data_size); |
| if (rc != 0) { |
| rc = -PFIFLASH_ERR_BOOTENV_READ; |
| goto err; |
| } |
| |
| for (i = 0; i < ids_size; i++) { |
| rc = bootenv_create(&bootenv_flash); |
| if (rc != 0) { |
| rc = -PFIFLASH_ERR_BOOTENV_CREATE; |
| goto err; |
| } |
| |
| rc = bootenv_read(fp_flash[i], bootenv_flash, BOOTENV_MAXSIZE); |
| if (rc != 0) { |
| rc = -PFIFLASH_ERR_BOOTENV_READ; |
| goto err; |
| } |
| |
| rc = pdd_f(bootenv_flash, bootenv_pfi, &bootenv_res, |
| &warnings, err_buf, err_buf_size); |
| if (rc != 0) { |
| rc = -PFIFLASH_ERR_PDD_UNKNOWN; |
| goto err; |
| } |
| |
| rc = bootenv_compare(bootenv_flash, bootenv_res); |
| if (rc > 0) { |
| rc = -PFIFLASH_CMP_DIFF; |
| goto err; |
| } else if (rc < 0) { |
| rc = -PFIFLASH_ERR_COMPARE; |
| goto err; |
| } |
| |
| bootenv_destroy(&bootenv_flash); |
| bootenv_flash = NULL; |
| } |
| |
| err: |
| if (bootenv_pfi) |
| bootenv_destroy(&bootenv_pfi); |
| if (bootenv_res) |
| bootenv_destroy(&bootenv_res); |
| if (bootenv_flash) |
| bootenv_destroy(&bootenv_flash); |
| |
| return rc; |
| } |
| |
| static int compare_data(FILE *fp_pfi, FILE **fp_flash, uint32_t ids_size, |
| uint32_t bytes_left) |
| { |
| unsigned int i; |
| size_t read_bytes, rc = 0; |
| char buf_pfi[COMPARE_BUFFER_SIZE]; |
| char *buf_flash[ids_size]; |
| |
| for (i = 0; i < ids_size; i++) { |
| buf_flash[i] = malloc(COMPARE_BUFFER_SIZE); |
| if (!buf_flash[i]) |
| return -PFIFLASH_ERR_COMPARE; |
| } |
| |
| while (bytes_left) { |
| if (bytes_left > COMPARE_BUFFER_SIZE) |
| read_bytes = COMPARE_BUFFER_SIZE; |
| else |
| read_bytes = bytes_left; |
| |
| rc = fread(buf_pfi, 1, read_bytes, fp_pfi); |
| if (rc != read_bytes) { |
| rc = -PFIFLASH_ERR_COMPARE; |
| goto err; |
| } |
| |
| for (i = 0; i < ids_size; i++) { |
| rc = fread(buf_flash[i], 1, read_bytes, fp_flash[i]); |
| if (rc != read_bytes) { |
| rc = -PFIFLASH_CMP_DIFF; |
| goto err; |
| } |
| |
| rc = memcmp(buf_pfi, buf_flash[i], read_bytes); |
| if (rc != 0) { |
| rc = -PFIFLASH_CMP_DIFF; |
| goto err; |
| } |
| } |
| |
| bytes_left -= read_bytes; |
| } |
| |
| err: |
| for (i = 0; i < ids_size; i++) |
| free(buf_flash[i]); |
| |
| return rc; |
| } |
| |
| static int compare_volumes(int devno, pfi_ubi_t u, FILE *fp_pfi, |
| pdd_func_t pdd_f, char *err_buf, size_t err_buf_size) |
| { |
| int rc, is_bootenv = 0; |
| unsigned int i; |
| char path[PATH_MAX]; |
| libubi_t ulib = NULL; |
| FILE *fp_flash[u->ids_size]; |
| |
| ulib = libubi_open(); |
| if (ulib == NULL) { |
| rc = -PFIFLASH_ERR_UBI_OPEN; |
| goto err; |
| } |
| |
| for (i = 0; i < u->ids_size; i++) { |
| if (u->ids[i] == EXAMPLE_BOOTENV_VOL_ID_1 || |
| u->ids[i] == EXAMPLE_BOOTENV_VOL_ID_2) |
| is_bootenv = 1; |
| |
| snprintf(path, PATH_MAX, DEFAULT_VOL_PATTERN, devno, u->ids[i]); |
| |
| fp_flash[i] = fopen(path, "r"); |
| if (fp_flash[i] == NULL) { |
| rc = -PFIFLASH_ERR_UBI_OPEN; |
| goto err; |
| } |
| } |
| |
| if (is_bootenv) |
| rc = compare_bootenv(fp_pfi, fp_flash, u->ids_size, |
| u->data_size, pdd_f, err_buf, err_buf_size); |
| else |
| rc = compare_data(fp_pfi, fp_flash, u->ids_size, u->data_size); |
| |
| err: |
| if (rc < 0) |
| EBUF("%s", PFIFLASH_ERRSTR[-rc]); |
| |
| for (i = 0; i < u->ids_size; i++) |
| fclose(fp_flash[i]); |
| if (ulib) |
| libubi_close(ulib); |
| |
| return rc; |
| } |
| |
| static int |
| erase_mtd_region(FILE* file_p, int start, int length) |
| { |
| int rc, fd; |
| erase_info_t erase; |
| mtd_info_t mtdinfo; |
| loff_t offset = start; |
| loff_t end = offset + length; |
| |
| fd = fileno(file_p); |
| if (fd < 0) |
| return -PFIFLASH_ERR_MTD_ERASE; |
| |
| rc = ioctl(fd, MEMGETINFO, &mtdinfo); |
| if (rc) |
| return -PFIFLASH_ERR_MTD_ERASE; |
| |
| /* check for bad blocks in case of NAND flash */ |
| if (mtdinfo.type == MTD_NANDFLASH) { |
| while (offset < end) { |
| rc = ioctl(fd, MEMGETBADBLOCK, &offset); |
| if (rc > 0) { |
| return -PFIFLASH_ERR_MTD_ERASE; |
| } |
| |
| offset += mtdinfo.erasesize; |
| } |
| } |
| |
| erase.start = start; |
| erase.length = length; |
| |
| rc = ioctl(fd, MEMERASE, &erase); |
| if (rc) { |
| return -PFIFLASH_ERR_MTD_ERASE; |
| } |
| |
| return rc; |
| } |
| |
| /** |
| * process_raw_volumes - writes the raw sections of the PFI data |
| * @pfi PFI data file pointer |
| * @pfi_raws list of PFI raw headers |
| * @rawdev device to use to write raw data |
| * |
| * Error handling: |
| * when early EOF in PFI data |
| * - returns -PFIFLASH_ERR_EOF, err_buf matches text to err |
| * when file I/O error |
| * - returns -PFIFLASH_ERR_FIO, err_buf matches text to err |
| * when CRC check fails |
| * - returns -PFIFLASH_ERR_CRC_CHECK, err_buf matches text to err |
| * when opening MTD device fails |
| * - reutrns -PFIFLASH_ERR_MTD_OPEN, err_buf matches text to err |
| * when closing MTD device fails |
| * - returns -PFIFLASH_ERR_MTD_CLOSE, err_buf matches text to err |
| **/ |
| static int |
| process_raw_volumes(FILE* pfi, list_t pfi_raws, const char* rawdev, |
| char* err_buf, size_t err_buf_size) |
| { |
| int rc; |
| char *pfi_data; |
| void *i; |
| uint32_t crc, crc32_table[256]; |
| size_t j, k; |
| FILE* mtd = NULL; |
| list_t ptr; |
| |
| if (is_empty(pfi_raws)) |
| return 0; |
| |
| if (rawdev == NULL) |
| return 0; |
| |
| rc = 0; |
| |
| pfi_data = NULL; |
| |
| log_msg("[ rawupdate dev=%s", rawdev); |
| |
| crc = UBI_CRC32_INIT; |
| init_crc32_table(crc32_table); |
| |
| /* most likely only one element in list, but just in case */ |
| foreach(i, ptr, pfi_raws) { |
| pfi_raw_t r = (pfi_raw_t)i; |
| |
| /* read in pfi data */ |
| if (pfi_data != NULL) |
| free(pfi_data); |
| pfi_data = malloc(r->data_size * sizeof(char)); |
| for (j = 0; j < r->data_size; j++) { |
| int c = fgetc(pfi); |
| if (c == EOF) { |
| rc = -PFIFLASH_ERR_EOF; |
| EBUF("%s", PFIFLASH_ERRSTR[-rc]); |
| goto err; |
| } else if (ferror(pfi)) { |
| rc = -PFIFLASH_ERR_FIO; |
| EBUF("%s", PFIFLASH_ERRSTR[-rc]); |
| goto err; |
| } |
| pfi_data[j] = (char)c; |
| } |
| crc = clc_crc32(crc32_table, crc, pfi_data, r->data_size); |
| |
| /* check crc */ |
| if (crc != r->crc) { |
| rc = -PFIFLASH_ERR_CRC_CHECK; |
| EBUF(PFIFLASH_ERRSTR[-rc], r->crc, crc); |
| goto err; |
| } |
| |
| /* open device */ |
| mtd = fopen(rawdev, "r+"); |
| if (mtd == NULL) { |
| rc = -PFIFLASH_ERR_MTD_OPEN; |
| EBUF(PFIFLASH_ERRSTR[-rc], rawdev); |
| goto err; |
| } |
| |
| for (j = 0; j < r->starts_size; j++) { |
| rc = erase_mtd_region(mtd, r->starts[j], r->data_size); |
| if (rc) { |
| EBUF("%s", PFIFLASH_ERRSTR[-rc]); |
| goto err; |
| } |
| |
| fseek(mtd, r->starts[j], SEEK_SET); |
| for (k = 0; k < r->data_size; k++) { |
| int c = fputc((int)pfi_data[k], mtd); |
| if (c == EOF) { |
| fclose(mtd); |
| rc = -PFIFLASH_ERR_EOF; |
| EBUF("%s", PFIFLASH_ERRSTR[-rc]); |
| goto err; |
| } |
| if ((char)c != pfi_data[k]) { |
| fclose(mtd); |
| rc = -1; |
| goto err; |
| } |
| } |
| } |
| rc = fclose(mtd); |
| mtd = NULL; |
| if (rc != 0) { |
| rc = -PFIFLASH_ERR_MTD_CLOSE; |
| EBUF(PFIFLASH_ERRSTR[-rc], rawdev); |
| goto err; |
| } |
| } |
| |
| err: |
| if (mtd != NULL) |
| fclose(mtd); |
| if (pfi_data != NULL) |
| free(pfi_data); |
| return rc; |
| } |
| |
| |
| /** |
| * erase_unmapped_ubi_volumes - skip volumes provided by PFI file, clear rest |
| * @devno UBI device number |
| * @pfi_ubis list of UBI header data |
| * |
| * Error handling: |
| * when UBI id is out of bounds |
| * - returns -PFIFLASH_ERR_UBI_VID_OOB, err_buf matches text to err |
| * when UBI volume can't be removed |
| * - passes rc, prepends err_buf with contextual aid |
| **/ |
| static int |
| erase_unmapped_ubi_volumes(int devno, list_t pfi_ubis, |
| char *err_buf, size_t err_buf_size) |
| { |
| int rc; |
| uint8_t ubi_volumes[PFI_UBI_MAX_VOLUMES]; |
| size_t i; |
| list_t ptr; |
| pfi_ubi_t u; |
| |
| rc = 0; |
| |
| for (i = 0; i < PFI_UBI_MAX_VOLUMES; i++) |
| ubi_volumes[i] = 1; |
| |
| foreach(u, ptr, pfi_ubis) { |
| /* iterate over each vol_id */ |
| for(i = 0; i < u->ids_size; i++) { |
| if (u->ids[i] >= PFI_UBI_MAX_VOLUMES) { |
| rc = -PFIFLASH_ERR_UBI_VID_OOB; |
| EBUF(PFIFLASH_ERRSTR[-rc], u->ids[i]); |
| goto err; |
| } |
| /* remove from removal list */ |
| ubi_volumes[u->ids[i]] = 0; |
| } |
| } |
| |
| for (i = 0; i < PFI_UBI_MAX_VOLUMES; i++) { |
| if (ubi_volumes[i]) { |
| rc = my_ubi_rmvol(devno, i, err_buf, err_buf_size); |
| if (rc != 0) { |
| EBUF_PREPEND("remove volume failed"); |
| goto err; |
| } |
| } |
| } |
| |
| err: |
| return rc; |
| } |
| |
| |
| /** |
| * process_ubi_volumes - delegate tasks regarding UBI volumes |
| * @pfi PFI data file pointer |
| * @seqnum sequence number |
| * @pfi_ubis list of UBI header data |
| * @bootenv_old storage for current system PDD |
| * @pdd_f function to handle PDD |
| * @ubi_update_process whether reading or writing |
| * |
| * Error handling: |
| * when and unknown ubi_update_process is given |
| * - returns -PFIFLASH_ERR_UBI_UNKNOWN, err_buf matches text to err |
| * otherwise |
| * - passes rc and err_buf |
| **/ |
| static int |
| process_ubi_volumes(FILE* pfi, int seqnum, list_t pfi_ubis, |
| bootenv_t bootenv_old, pdd_func_t pdd_f, |
| ubi_update_process_t ubi_update_process, |
| char *err_buf, size_t err_buf_size) |
| { |
| int rc; |
| pfi_ubi_t u; |
| list_t ptr; |
| |
| rc = 0; |
| |
| foreach(u, ptr, pfi_ubis) { |
| int s = seqnum; |
| |
| if (s > ((int)u->ids_size - 1)) |
| s = 0; /* per default use the first */ |
| u->curr_seqnum = s; |
| |
| switch (ubi_update_process) { |
| case UBI_REMOVE: |
| /* TODO are all these "EXAMPLE" vars okay? */ |
| if ((u->ids[s] == EXAMPLE_BOOTENV_VOL_ID_1) || |
| (u->ids[s] == EXAMPLE_BOOTENV_VOL_ID_2)) { |
| rc = read_bootenv_volume(EXAMPLE_UBI_DEVICE, |
| u->ids[s], bootenv_old, |
| err_buf, err_buf_size); |
| /* it's okay if there is no bootenv |
| * we're going to write one */ |
| if ((rc == -PFIFLASH_ERR_UBI_VOL_FOPEN) || |
| (rc == -PFIFLASH_ERR_BOOTENV_READ)) |
| rc = 0; |
| if (rc != 0) |
| goto err; |
| } |
| |
| rc = my_ubi_rmvol(EXAMPLE_UBI_DEVICE, u->ids[s], |
| err_buf, err_buf_size); |
| if (rc != 0) |
| goto err; |
| |
| break; |
| case UBI_WRITE: |
| rc = my_ubi_mkvol(EXAMPLE_UBI_DEVICE, s, u, |
| err_buf, err_buf_size); |
| if (rc != 0) { |
| EBUF_PREPEND("creating volume"); |
| goto err; |
| } |
| |
| if ((u->ids[s] == EXAMPLE_BOOTENV_VOL_ID_1) || |
| (u->ids[s] == EXAMPLE_BOOTENV_VOL_ID_2)) { |
| rc = write_bootenv_volume(EXAMPLE_UBI_DEVICE, |
| u->ids[s], |
| bootenv_old, pdd_f, |
| pfi, |
| u->data_size, |
| u->crc, |
| err_buf, |
| err_buf_size); |
| if (rc != 0) |
| EBUF_PREPEND("bootenv volume"); |
| } else { |
| rc = write_normal_volume(EXAMPLE_UBI_DEVICE, |
| u->ids[s], |
| u->data_size, pfi, |
| u->crc, |
| err_buf, |
| err_buf_size); |
| if (rc != 0) |
| EBUF_PREPEND("normal volume"); |
| } |
| if (rc != 0) |
| goto err; |
| |
| break; |
| case UBI_COMPARE: |
| rc = compare_volumes(EXAMPLE_UBI_DEVICE, u, pfi, pdd_f, |
| err_buf, err_buf_size); |
| if (rc != 0) { |
| EBUF_PREPEND("compare volume"); |
| goto err; |
| } |
| |
| break; |
| default: |
| rc = -PFIFLASH_ERR_UBI_UNKNOWN; |
| EBUF("%s", PFIFLASH_ERRSTR[-rc]); |
| goto err; |
| } |
| } |
| |
| err: |
| return rc; |
| } |
| |
| |
| /** |
| * mirror_ubi_volumes - mirror redundant pairs of volumes |
| * @devno UBI device number |
| * @pfi_ubis list of PFI header data |
| * |
| * Error handling: |
| * when UBI system couldn't be opened |
| * - returns -PFIFLASH_ERR_UBI_OPEN, err_buf matches text to err |
| **/ |
| static int |
| mirror_ubi_volumes(uint32_t devno, list_t pfi_ubis, |
| char *err_buf, size_t err_buf_size) |
| { |
| int rc; |
| uint32_t j; |
| list_t ptr; |
| pfi_ubi_t i; |
| libubi_t ulib; |
| |
| rc = 0; |
| ulib = NULL; |
| |
| log_msg("[ mirror ..."); |
| |
| ulib = libubi_open(); |
| if (ulib == NULL) { |
| rc = -PFIFLASH_ERR_UBI_OPEN; |
| EBUF("%s", PFIFLASH_ERRSTR[-rc]); |
| goto err; |
| } |
| |
| /** |
| * Execute all mirror operations on redundant groups. |
| * Create a volume within a redundant group if it does |
| * not exist already (this is a precondition of |
| * ubimirror). |
| */ |
| foreach(i, ptr, pfi_ubis) { |
| for (j = 0; j < i->ids_size; j++) { |
| /* skip self-match */ |
| if (i->ids[j] == i->ids[i->curr_seqnum]) |
| continue; |
| |
| rc = my_ubi_rmvol(devno, i->ids[j], |
| err_buf, err_buf_size); |
| if (rc != 0) |
| goto err; |
| |
| rc = my_ubi_mkvol(devno, j, i, |
| err_buf, err_buf_size); |
| if (rc != 0) |
| goto err; |
| } |
| } |
| |
| foreach(i, ptr, pfi_ubis) { |
| rc = ubimirror(devno, i->curr_seqnum, i->ids, i->ids_size, |
| err_buf, err_buf_size); |
| if (rc != 0) |
| goto err; |
| } |
| |
| |
| err: |
| if (ulib != NULL) |
| libubi_close(ulib); |
| |
| return rc; |
| } |
| |
| |
| /** |
| * pfiflash_with_options - exposed func to flash memory with a PFI file |
| * @pfi PFI data file pointer |
| * @complete flag to erase unmapped volumes |
| * @seqnum sequence number |
| * @compare flag to compare |
| * @pdd_handling method to handle pdd (keep, merge, overwrite...) |
| * |
| * Error handling: |
| * when bootenv can't be created |
| * - returns -PFIFLASH_ERR_BOOTENV_CREATE, err_buf matches text to err |
| * when PFI headers can't be read, or |
| * when fail to skip raw sections, or |
| * when error occurs while processing raw volumes, or |
| * when fail to erase unmapped UBI vols, or |
| * when error occurs while processing UBI volumes, or |
| * when error occurs while mirroring UBI volumes |
| * - passes rc, prepends err_buf with contextual aid |
| **/ |
| int |
| pfiflash_with_options(FILE* pfi, int complete, int seqnum, int compare, |
| pdd_handling_t pdd_handling, const char* rawdev, |
| char *err_buf, size_t err_buf_size) |
| { |
| int rc; |
| bootenv_t bootenv; |
| pdd_func_t pdd_f; |
| |
| if (pfi == NULL) |
| return -EINVAL; |
| |
| rc = 0; |
| pdd_f = NULL; |
| |
| /* If the user didnt specify a seqnum we start per default |
| * with the index 0 */ |
| int curr_seqnum = seqnum < 0 ? 0 : seqnum; |
| |
| list_t pfi_raws = mk_empty(); /* list of raw sections from a pfi */ |
| list_t pfi_ubis = mk_empty(); /* list of ubi sections from a pfi */ |
| |
| rc = bootenv_create(&bootenv); |
| if (rc != 0) { |
| rc = -PFIFLASH_ERR_BOOTENV_CREATE; |
| EBUF(PFIFLASH_ERRSTR[-rc], ""); |
| goto err; |
| } |
| |
| rc = read_pfi_headers(&pfi_raws, &pfi_ubis, pfi, err_buf, err_buf_size); |
| if (rc != 0) { |
| EBUF_PREPEND("reading PFI header"); |
| goto err; |
| } |
| |
| if (rawdev == NULL || compare) |
| rc = skip_raw_volumes(pfi, pfi_raws, err_buf, err_buf_size); |
| else |
| rc = process_raw_volumes(pfi, pfi_raws, rawdev, err_buf, |
| err_buf_size); |
| if (rc != 0) { |
| EBUF_PREPEND("handling raw section"); |
| goto err; |
| } |
| |
| if (complete && !compare) { |
| rc = erase_unmapped_ubi_volumes(EXAMPLE_UBI_DEVICE, pfi_ubis, |
| err_buf, err_buf_size); |
| if (rc != 0) { |
| EBUF_PREPEND("deleting unmapped UBI volumes"); |
| goto err; |
| } |
| } |
| |
| if (((int)pdd_handling >= 0) && |
| (pdd_handling < PDD_HANDLING_NUM)) |
| pdd_f = pdd_funcs[pdd_handling]; |
| else { |
| rc = -PFIFLASH_ERR_PDD_UNKNOWN; |
| EBUF("%s", PFIFLASH_ERRSTR[-rc]); |
| goto err; |
| } |
| |
| if (!compare) { |
| rc = process_ubi_volumes(pfi, curr_seqnum, pfi_ubis, bootenv, |
| pdd_f, UBI_REMOVE, err_buf, err_buf_size); |
| if (rc != 0) { |
| EBUF_PREPEND("removing UBI volumes"); |
| goto err; |
| } |
| |
| rc = process_ubi_volumes(pfi, curr_seqnum, pfi_ubis, bootenv, |
| pdd_f, UBI_WRITE, err_buf, err_buf_size); |
| if (rc != 0) { |
| EBUF_PREPEND("writing UBI volumes"); |
| goto err; |
| } |
| |
| if (seqnum < 0) { /* mirror redundant pairs */ |
| rc = mirror_ubi_volumes(EXAMPLE_UBI_DEVICE, pfi_ubis, |
| err_buf, err_buf_size); |
| if (rc != 0) { |
| EBUF_PREPEND("mirroring UBI volumes"); |
| goto err; |
| } |
| } |
| } else { |
| /* only compare volumes, don't alter the content */ |
| rc = process_ubi_volumes(pfi, curr_seqnum, pfi_ubis, bootenv, |
| pdd_f, UBI_COMPARE, err_buf, err_buf_size); |
| |
| if (rc == -PFIFLASH_CMP_DIFF) |
| /* update is necessary, return positive value */ |
| rc = 1; |
| |
| if (rc < 0) { |
| EBUF_PREPEND("comparing UBI volumes"); |
| goto err; |
| } |
| } |
| |
| err: |
| pfi_raws = remove_all((free_func_t)&free_pfi_raw, pfi_raws); |
| pfi_ubis = remove_all((free_func_t)&free_pfi_ubi, pfi_ubis); |
| bootenv_destroy(&bootenv); |
| return rc; |
| } |
| |
| |
| /** |
| * pfiflash - passes to pfiflash_with_options |
| * @pfi PFI data file pointer |
| * @complete flag to erase unmapped volumes |
| * @seqnum sequence number |
| * @pdd_handling method to handle pdd (keep, merge, overwrite...) |
| **/ |
| int |
| pfiflash(FILE* pfi, int complete, int seqnum, pdd_handling_t pdd_handling, |
| char *err_buf, size_t err_buf_size) |
| { |
| return pfiflash_with_options(pfi, complete, seqnum, 0, pdd_handling, |
| NULL, err_buf, err_buf_size); |
| } |