blob: 1c0b1e16453432b1658849bd0ece8da2045393a4 [file] [log] [blame]
/* Copyright 2012 Google Inc. All Rights Reserved.
* Author: weixiaofeng@google.com (Xiaofeng Wei)
*/
#include "sysvarlib.h"
int mtd_dev[SYSVAR_MTD_DEVICE] = {-1, -1, -1, -1};
int mtd_dev_unlocked[SYSVAR_MTD_DEVICE];
bool verbose = false;
struct sysvar_buf rw_buf;
struct sysvar_buf ro_buf;
char *mtd_name[SYSVAR_MTD_DEVICE] = {
SYSVAR_RW_NAME0, SYSVAR_RW_NAME1, SYSVAR_RO_NAME0, SYSVAR_RO_NAME1
};
/* Static function prototypes */
static int check_mtd(void);
static int data_load(struct sysvar_buf *buf, int idx);
static int data_recovery(struct sysvar_buf *buf, int idx);
static int data_save(struct sysvar_buf *buf, int *idx);
static int erase_mtd(int idx);
static void print_err(char *err, int idx);
static bool sysvar_buf_init(struct sysvar_buf *buf, bool is_ro);
/*
* sysvar_buf_init - Initializes a sysvar_buf struct.
*/
static bool sysvar_buf_init(struct sysvar_buf *buf, bool is_ro) {
if (buf == NULL) {
return false;
}
memset(buf, 0, sizeof(*buf));
buf->data = (unsigned char *) malloc(SYSVAR_BLOCK_SIZE);
if (buf->data == NULL) {
print_err("allocate data buffer ", -1);
return false;
}
buf->list = (struct sysvar_list *)malloc(sizeof(struct sysvar_list));
if (buf->list == NULL) {
print_err("allocate data list ", -1);
free(buf->data);
return false;
}
buf->data_len = SYSVAR_BLOCK_SIZE;
buf->total_len = SYSVAR_BLOCK_SIZE - SYSVAR_HEAD;
buf->free_len = buf->total_len;
buf->readonly = is_ro;
snprintf(buf->list->name, sizeof(buf->list->name), "%s", is_ro ? "ro" : "rw");
buf->list->value = NULL;
buf->list->len = SYSVAR_NAME + 2;
buf->list->next = NULL;
buf->loaded = false;
return true;
}
/*
* print_err - print the error message
*/
static void print_err(char *err, int idx) {
if (verbose) {
printf("error(sv): failed to %s", err);
switch (idx) {
case 0:
printf("'%s'", SYSVAR_RW_NAME0);
break;
case 1:
printf("'%s'", SYSVAR_RW_NAME1);
break;
case 2:
printf("'%s'", SYSVAR_RO_NAME0);
break;
case 3:
printf("'%s'", SYSVAR_RO_NAME1);
break;
default:
break;
}
printf(" (%s)\n", strerror(errno));
}
}
/*
* check_mtd - check MTD device opened
*/
static int check_mtd(void) {
int i;
for (i = 0; i < SYSVAR_MTD_DEVICE; i++) {
if (mtd_dev[i] < 0) {
print_err("open MTD device ", i);
return SYSVAR_OPEN_ERR;
}
}
return SYSVAR_SUCCESS;
}
/*
* erase_mtd - erase MTD device
*/
static int erase_mtd(int idx) {
int res;
struct mtd_info_user mi;
struct erase_info_user ei;
/* get MTD device information */
if (ioctl(mtd_dev[idx], MEMGETINFO, &mi)) {
print_err("getinfo MTD device ", idx);
return SYSVAR_ERASE_ERR;
}
ei.start = 0;
ei.length = mi.erasesize;
while (ei.start < SYSVAR_BLOCK_SIZE) {
/* For select devices, unlocking is not implemented. So, if errno
* is set to something like EOPNOTSUPP, it is not an error. It is simply
* not required to write/erase a block of the device. */
res = ioctl(mtd_dev[idx], MEMUNLOCK, &ei);
if (res != 0 && errno != EOPNOTSUPP) {
print_err("unlock MTD device ", idx);
return SYSVAR_ERASE_ERR;
}
/* Only mark the device as unlocked if the unlock was successful. */
if (res == 0) {
mtd_dev_unlocked[idx] = 1;
}
if (ioctl(mtd_dev[idx], MEMERASE, &ei)) {
print_err("erase MTD device ", idx);
return SYSVAR_ERASE_ERR;
}
/* move to next block */
ei.start += ei.length;
}
return SYSVAR_SUCCESS;
}
/*
* set_mtd_verbose - set verbose print
*/
void set_mtd_verbose(bool v) {
verbose = v;
}
/*
* data_recovery - system variables recovering routine
*/
static int data_recovery(struct sysvar_buf *buf, int idx) {
int i, j, bytes;
/* load the system vriables */
for (i = idx, j = idx + 1; i < idx + 2; i++, j--) {
/* read the data buffer from MTD device */
lseek(mtd_dev[i], SYSVAR_MTD_OFFSET, SEEK_SET);
bytes = read(mtd_dev[i], buf->data, buf->data_len);
if (bytes != buf->data_len)
continue;
/* check crc32 and wc32 (write count) */
if (check_var(buf, SYSVAR_LOAD_MODE) == SYSVAR_SUCCESS) {
/* erase MTD device */
if (erase_mtd(j))
goto recovery_err;
/* check crc32 and wc32 (write count) */
if (check_var(buf, SYSVAR_SAVE_MODE))
goto recovery_err;
/* write the data buffer to MTD device */
lseek(mtd_dev[j], SYSVAR_MTD_OFFSET, SEEK_SET);
bytes = write(mtd_dev[j], buf->data, buf->data_len);
if (bytes != buf->data_len) {
print_err("write MTD device ", j);
goto recovery_err;
}
buf->loaded = true;
return SYSVAR_SUCCESS;
}
}
recovery_err:
clear_buf(buf);
print_err("recover MTD device ", idx);
return SYSVAR_SUCCESS;
}
/*
* data_load - load the data from MTD device to data buffer
*/
static int data_load(struct sysvar_buf *buf, int idx) {
int i, j, bytes;
buf->loaded = true;
if (check_mtd())
return SYSVAR_OPEN_ERR;
/* load the system vriables */
for (i = idx, j = 0; i < idx + 2; i++, j++) {
buf->failed[j] = false;
/* read the data buffer from MTD device */
lseek(mtd_dev[i], SYSVAR_MTD_OFFSET, SEEK_SET);
bytes = read(mtd_dev[i], buf->data, buf->data_len);
if (bytes != buf->data_len)
buf->failed[j] = true;
/* check crc32 and wc32 (write count) */
if (check_var(buf, SYSVAR_LOAD_MODE))
buf->failed[j] = true;
}
if (buf->failed[0] || buf->failed[1])
return data_recovery(buf, idx);
return SYSVAR_SUCCESS;
}
/*
* data_save - save the data from data buffer to MTD device
*/
static int data_save(struct sysvar_buf *buf, int *idx) {
int i, j, bytes;
if (check_mtd())
return SYSVAR_OPEN_ERR;
/* save the system vriables(RW) */
for (j = 0; j < 2; j++) {
i = idx[j];
/* erase MTD device */
if (erase_mtd(i))
return SYSVAR_ERASE_ERR;
/* check crc32 and wc32 (write count) */
if (check_var(buf, SYSVAR_SAVE_MODE)) {
print_err("save MTD device ", i);
return SYSVAR_SAVE_ERR;
}
/* write the data buffer to MTD device */
lseek(mtd_dev[i], SYSVAR_MTD_OFFSET, SEEK_SET);
bytes = write(mtd_dev[i], buf->data, buf->data_len);
if (bytes != buf->data_len) {
print_err("write MTD device ", i);
return SYSVAR_WRITE_ERR;
}
}
return SYSVAR_SUCCESS;
}
/*
* sv_buf - return the data buffer of system variables
*/
struct sysvar_buf *sv_buf(int idx) {
if (idx < SYSVAR_RO_BUF)
return &rw_buf;
return &ro_buf;
}
/*
* open_mtd - open MTD device and allocate the data buffer
*/
int open_mtd(void) {
int i;
/* check MTD devices */
for (i = 0; i < SYSVAR_MTD_DEVICE; i++) {
if (mtd_dev[i] >= 0) {
return SYSVAR_SUCCESS;
}
}
/* open MTD devices */
for (i = 0; i < SYSVAR_MTD_DEVICE; i++) {
mtd_dev[i] = open(mtd_name[i], O_RDWR | O_SYNC);
if (mtd_dev[i] < 0) {
print_err("open MTD device ", i);
goto open_err;
}
}
if (!sysvar_buf_init(&rw_buf, false) ||
!sysvar_buf_init(&ro_buf, true) ||
loadvar()) {
goto open_err;
}
return SYSVAR_SUCCESS;
open_err:
close_mtd();
return SYSVAR_OPEN_ERR;
}
/*
* close_mtd - close MTD device and release the data buffer
*/
void close_mtd(void) {
int i;
struct erase_info_user ei;
/* release data lists */
if (rw_buf.list != NULL) {
clear_var(&rw_buf);
free(rw_buf.list);
}
if (ro_buf.list != NULL) {
clear_var(&ro_buf);
free(ro_buf.list);
}
/* release data buffers */
if (rw_buf.data != NULL)
free(rw_buf.data);
if (ro_buf.data != NULL)
free(ro_buf.data);
/* close MTD devices */
for (i = 0; i < SYSVAR_MTD_DEVICE; i++) {
if (mtd_dev[i] > 0) {
if (mtd_dev_unlocked[i]) {
ei.start = 0;
ei.length = SYSVAR_BLOCK_SIZE;
if (ioctl(mtd_dev[i], MEMLOCK, &ei))
print_err("lock MTD device ", i);
else
mtd_dev_unlocked[i] = 0;
}
close(mtd_dev[i]);
}
mtd_dev[i] = -1;
}
}
/*
* loadvar - load the data from MTD device to data buffer
*/
int loadvar(void) {
if (data_load(&rw_buf, SYSVAR_RW_BUF))
return SYSVAR_LOAD_ERR;
/* move the data from data buffer to data list */
if (load_var(&rw_buf))
return SYSVAR_LOAD_ERR;
if (data_load(&ro_buf, SYSVAR_RO_BUF))
return SYSVAR_LOAD_ERR;
/* move the data from data buffer to data list */
return load_var(&ro_buf);
}
/*
* savevar - save the data from data buffer to MTD device(RW)
*/
int savevar(void) {
int save_idx[2];
/* move the data from data list to data buffer */
if (save_var(&rw_buf))
return SYSVAR_SAVE_ERR;
/* erase failed partition first
* part0 part1 erase
* ----- ----- -----
* ok ok 0, 1
* failed ok 0, 1
* ok failed 1, 0
* failed failed 0, 1
*/
if (rw_buf.failed[1]) {
save_idx[0] = SYSVAR_RW_BUF + 1;
save_idx[1] = SYSVAR_RW_BUF;
} else {
save_idx[0] = SYSVAR_RW_BUF;
save_idx[1] = SYSVAR_RW_BUF + 1;
}
return data_save(&rw_buf, save_idx);
}
/*
* getvar - get or print the system variable from data list
*/
int getvar(char *name, char *value, int len) {
struct sysvar_list *var = NULL;
if (check_mtd())
return SYSVAR_OPEN_ERR;
if (name == NULL) {
/* print all system variables(RO) */
print_var(&ro_buf);
/* print all system variables(RW) */
print_var(&rw_buf);
return SYSVAR_SUCCESS;
}
/* find the system variable(RO) */
var = find_var(&ro_buf, name);
if (var != NULL)
goto get_data;
/* find the system variable(RW) */
var = find_var(&rw_buf, name);
if (var != NULL)
goto get_data;
/* system variable not found */
return SYSVAR_GET_ERR;
get_data:
return get_var(var, name, value, len);
}
/*
* setvar - add or delete the system variable(RW) in data list
*/
int setvar(char *name, char *value) {
struct sysvar_list *var = NULL;
int ret = SYSVAR_SUCCESS;
if (check_mtd())
return SYSVAR_OPEN_ERR;
if (name != NULL) {
/* read only variable? */
var = find_var(&ro_buf, name);
if (var != NULL)
return SYSVAR_READONLY_ERR;
/* find system variable */
var = find_var(&rw_buf, name);
if (var != NULL) {
/* delete system variable(RW) */
ret = delete_var(&rw_buf, var);
if (ret != SYSVAR_SUCCESS)
return SYSVAR_DELETE_ERR;
/* add system variable(RW) */
if (value != NULL) {
ret = set_var(&rw_buf, name, value);
}
} else {
/* add system variable(RW) */
if (value != NULL) {
ret = set_var(&rw_buf, name, value);
} else {
ret = SYSVAR_EXISTED_ERR;
}
}
} else {
/* delete all of system variables(RW) */
ret = clear_var(&rw_buf);
}
return ret;
}
/*
* sysvar_info - get the system variables information
*/
void sysvar_info(int idx) {
struct sysvar_buf *buf = sv_buf(idx);
if (check_mtd())
return;
printf("System Variables(%s):\n", (idx < SYSVAR_RO_BUF) ? "RW" : "RO");
if (idx == 0)
printf("device : %s\n", SYSVAR_RW_NAME0);
else if (idx == 1)
printf("device : %s\n", SYSVAR_RW_NAME1);
else if (idx == 2)
printf("device : %s\n", SYSVAR_RO_NAME0);
else if (idx == 3)
printf("device : %s\n", SYSVAR_RO_NAME1);
else
printf("device : ?\n");
printf("size : %d bytes\n", buf->data_len);
printf("total : %d bytes\n", buf->total_len);
printf("used : %d bytes\n", buf->used_len);
printf("wc32 : 0x%08lx\n", get_wc32(buf));
printf("crc32 : 0x%08lx\n", get_crc32(buf));
}
/*
* sysvar_dump - dump the data buffer in binary/ascii format
*/
void sysvar_dump(int idx, int start, int len) {
if (check_mtd())
return;
dump_buf(sv_buf(idx), start, len);
}
/*
* sysvar_io - MTD device IO operations
*/
int sysvar_io(int idx, int mode) {
struct sysvar_buf *buf = sv_buf(idx);
int bytes;
if (check_mtd())
return SYSVAR_OPEN_ERR;
if (mode == SYSVAR_MTD_WRITE) {
/* write the data buffer to MTD device */
lseek(mtd_dev[idx], SYSVAR_MTD_OFFSET, SEEK_SET);
bytes = write(mtd_dev[idx], buf->data, buf->data_len);
if (bytes != buf->data_len) {
print_err("write MTD device ", idx);
return SYSVAR_WRITE_ERR;
}
} else if (mode == SYSVAR_MTD_ERASE) {
/* erase MTD device */
if (erase_mtd(idx))
return SYSVAR_ERASE_ERR;
}
/* read the data buffer from MTD device */
lseek(mtd_dev[idx], SYSVAR_MTD_OFFSET, SEEK_SET);
bytes = read(mtd_dev[idx], buf->data, buf->data_len);
if (bytes != buf->data_len) {
print_err("read MTD device ", idx);
return SYSVAR_READ_ERR;
}
rw_buf.loaded = false;
ro_buf.loaded = false;
return SYSVAR_SUCCESS;
}