blob: b0d454ac4e0404ff9ab219c7986df630286b8169 [file] [log] [blame]
/*
* 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);
}