u-boot/tools/env/fw_env.c
Rasmus Villemoes c62e6142b9 fw_setenv: avoid writing environment when nothing has changed
In the case where one deletes an already-non-existing variable, or sets
a variable to the value it already has, there is no point in writing the
environment back, thus reducing wear on the underlying storage
device.

In the case of redundant environments, if the two environments
differ (e.g. because one is corrupt), make sure that any call of
fw_setenv causes the two to become synchronized, even if the fw_setenv
call does not change anything in the good copy.

Signed-off-by: Rasmus Villemoes <rasmus.villemoes@prevas.dk>
Acked-by: Joe Hershberger <joe.hershberger@ni.com>
2019-12-20 10:02:24 -06:00

1820 lines
39 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* (C) Copyright 2000-2010
* Wolfgang Denk, DENX Software Engineering, wd@denx.de.
*
* (C) Copyright 2008
* Guennadi Liakhovetski, DENX Software Engineering, lg@denx.de.
*/
#define _GNU_SOURCE
#include <compiler.h>
#include <env.h>
#include <errno.h>
#include <env_flags.h>
#include <fcntl.h>
#include <libgen.h>
#include <linux/fs.h>
#include <linux/stringify.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <u-boot/crc.h>
#include <unistd.h>
#include <dirent.h>
#ifdef MTD_OLD
# include <stdint.h>
# include <linux/mtd/mtd.h>
#else
# define __user /* nothing */
# include <mtd/mtd-user.h>
#endif
#include <mtd/ubi-user.h>
#include "fw_env_private.h"
#include "fw_env.h"
struct env_opts default_opts = {
#ifdef CONFIG_FILE
.config_file = CONFIG_FILE
#endif
};
#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d))
#define min(x, y) ({ \
typeof(x) _min1 = (x); \
typeof(y) _min2 = (y); \
(void) (&_min1 == &_min2); \
_min1 < _min2 ? _min1 : _min2; })
struct envdev_s {
const char *devname; /* Device name */
long long devoff; /* Device offset */
ulong env_size; /* environment size */
ulong erase_size; /* device erase size */
ulong env_sectors; /* number of environment sectors */
uint8_t mtd_type; /* type of the MTD device */
int is_ubi; /* set if we use UBI volume */
};
static struct envdev_s envdevices[2] = {
{
.mtd_type = MTD_ABSENT,
}, {
.mtd_type = MTD_ABSENT,
},
};
static int dev_current;
#define DEVNAME(i) envdevices[(i)].devname
#define DEVOFFSET(i) envdevices[(i)].devoff
#define ENVSIZE(i) envdevices[(i)].env_size
#define DEVESIZE(i) envdevices[(i)].erase_size
#define ENVSECTORS(i) envdevices[(i)].env_sectors
#define DEVTYPE(i) envdevices[(i)].mtd_type
#define IS_UBI(i) envdevices[(i)].is_ubi
#define CUR_ENVSIZE ENVSIZE(dev_current)
static unsigned long usable_envsize;
#define ENV_SIZE usable_envsize
struct env_image_single {
uint32_t crc; /* CRC32 over data bytes */
char data[];
};
struct env_image_redundant {
uint32_t crc; /* CRC32 over data bytes */
unsigned char flags; /* active or obsolete */
char data[];
};
enum flag_scheme {
FLAG_NONE,
FLAG_BOOLEAN,
FLAG_INCREMENTAL,
};
struct environment {
void *image;
uint32_t *crc;
unsigned char *flags;
char *data;
enum flag_scheme flag_scheme;
int dirty;
};
static struct environment environment = {
.flag_scheme = FLAG_NONE,
};
static int have_redund_env;
#define DEFAULT_ENV_INSTANCE_STATIC
#include <env_default.h>
#define UBI_DEV_START "/dev/ubi"
#define UBI_SYSFS "/sys/class/ubi"
#define UBI_VOL_NAME_PATT "ubi%d_%d"
static int is_ubi_devname(const char *devname)
{
return !strncmp(devname, UBI_DEV_START, sizeof(UBI_DEV_START) - 1);
}
static int ubi_check_volume_sysfs_name(const char *volume_sysfs_name,
const char *volname)
{
char path[256];
FILE *file;
char *name;
int ret;
strcpy(path, UBI_SYSFS "/");
strcat(path, volume_sysfs_name);
strcat(path, "/name");
file = fopen(path, "r");
if (!file)
return -1;
ret = fscanf(file, "%ms", &name);
fclose(file);
if (ret <= 0 || !name) {
fprintf(stderr,
"Failed to read from file %s, ret = %d, name = %s\n",
path, ret, name);
return -1;
}
if (!strcmp(name, volname)) {
free(name);
return 0;
}
free(name);
return -1;
}
static int ubi_get_volnum_by_name(int devnum, const char *volname)
{
DIR *sysfs_ubi;
struct dirent *dirent;
int ret;
int tmp_devnum;
int volnum;
sysfs_ubi = opendir(UBI_SYSFS);
if (!sysfs_ubi)
return -1;
#ifdef DEBUG
fprintf(stderr, "Looking for volume name \"%s\"\n", volname);
#endif
while (1) {
dirent = readdir(sysfs_ubi);
if (!dirent)
return -1;
ret = sscanf(dirent->d_name, UBI_VOL_NAME_PATT,
&tmp_devnum, &volnum);
if (ret == 2 && devnum == tmp_devnum) {
if (ubi_check_volume_sysfs_name(dirent->d_name,
volname) == 0)
return volnum;
}
}
return -1;
}
static int ubi_get_devnum_by_devname(const char *devname)
{
int devnum;
int ret;
ret = sscanf(devname + sizeof(UBI_DEV_START) - 1, "%d", &devnum);
if (ret != 1)
return -1;
return devnum;
}
static const char *ubi_get_volume_devname(const char *devname,
const char *volname)
{
char *volume_devname;
int volnum;
int devnum;
int ret;
devnum = ubi_get_devnum_by_devname(devname);
if (devnum < 0)
return NULL;
volnum = ubi_get_volnum_by_name(devnum, volname);
if (volnum < 0)
return NULL;
ret = asprintf(&volume_devname, "%s_%d", devname, volnum);
if (ret < 0)
return NULL;
#ifdef DEBUG
fprintf(stderr, "Found ubi volume \"%s:%s\" -> %s\n",
devname, volname, volume_devname);
#endif
return volume_devname;
}
static void ubi_check_dev(unsigned int dev_id)
{
char *devname = (char *)DEVNAME(dev_id);
char *pname;
const char *volname = NULL;
const char *volume_devname;
if (!is_ubi_devname(DEVNAME(dev_id)))
return;
IS_UBI(dev_id) = 1;
for (pname = devname; *pname != '\0'; pname++) {
if (*pname == ':') {
*pname = '\0';
volname = pname + 1;
break;
}
}
if (volname) {
/* Let's find real volume device name */
volume_devname = ubi_get_volume_devname(devname, volname);
if (!volume_devname) {
fprintf(stderr, "Didn't found ubi volume \"%s\"\n",
volname);
return;
}
free(devname);
DEVNAME(dev_id) = volume_devname;
}
}
static int ubi_update_start(int fd, int64_t bytes)
{
if (ioctl(fd, UBI_IOCVOLUP, &bytes))
return -1;
return 0;
}
static int ubi_read(int fd, void *buf, size_t count)
{
ssize_t ret;
while (count > 0) {
ret = read(fd, buf, count);
if (ret > 0) {
count -= ret;
buf += ret;
continue;
}
if (ret == 0) {
/*
* Happens in case of too short volume data size. If we
* return error status we will fail it will be treated
* as UBI device error.
*
* Leave catching this error to CRC check.
*/
fprintf(stderr, "Warning: end of data on ubi volume\n");
return 0;
} else if (errno == EBADF) {
/*
* Happens in case of corrupted volume. The same as
* above, we cannot return error now, as we will still
* be able to successfully write environment later.
*/
fprintf(stderr, "Warning: corrupted volume?\n");
return 0;
} else if (errno == EINTR) {
continue;
}
fprintf(stderr, "Cannot read %u bytes from ubi volume, %s\n",
(unsigned int)count, strerror(errno));
return -1;
}
return 0;
}
static int ubi_write(int fd, const void *buf, size_t count)
{
ssize_t ret;
while (count > 0) {
ret = write(fd, buf, count);
if (ret <= 0) {
if (ret < 0 && errno == EINTR)
continue;
fprintf(stderr, "Cannot write %u bytes to ubi volume\n",
(unsigned int)count);
return -1;
}
count -= ret;
buf += ret;
}
return 0;
}
static int flash_io(int mode);
static int parse_config(struct env_opts *opts);
#if defined(CONFIG_FILE)
static int get_config(char *);
#endif
static char *skip_chars(char *s)
{
for (; *s != '\0'; s++) {
if (isblank(*s) || *s == '=')
return s;
}
return NULL;
}
static char *skip_blanks(char *s)
{
for (; *s != '\0'; s++) {
if (!isblank(*s))
return s;
}
return NULL;
}
/*
* s1 is either a simple 'name', or a 'name=value' pair.
* s2 is a 'name=value' pair.
* If the names match, return the value of s2, else NULL.
*/
static char *envmatch(char *s1, char *s2)
{
if (s1 == NULL || s2 == NULL)
return NULL;
while (*s1 == *s2++)
if (*s1++ == '=')
return s2;
if (*s1 == '\0' && *(s2 - 1) == '=')
return s2;
return NULL;
}
/**
* Search the environment for a variable.
* Return the value, if found, or NULL, if not found.
*/
char *fw_getenv(char *name)
{
char *env, *nxt;
for (env = environment.data; *env; env = nxt + 1) {
char *val;
for (nxt = env; *nxt; ++nxt) {
if (nxt >= &environment.data[ENV_SIZE]) {
fprintf(stderr, "## Error: "
"environment not terminated\n");
return NULL;
}
}
val = envmatch(name, env);
if (!val)
continue;
return val;
}
return NULL;
}
/*
* Search the default environment for a variable.
* Return the value, if found, or NULL, if not found.
*/
char *fw_getdefenv(char *name)
{
char *env, *nxt;
for (env = default_environment; *env; env = nxt + 1) {
char *val;
for (nxt = env; *nxt; ++nxt) {
if (nxt >= &default_environment[ENV_SIZE]) {
fprintf(stderr, "## Error: "
"default environment not terminated\n");
return NULL;
}
}
val = envmatch(name, env);
if (!val)
continue;
return val;
}
return NULL;
}
/*
* Print the current definition of one, or more, or all
* environment variables
*/
int fw_printenv(int argc, char *argv[], int value_only, struct env_opts *opts)
{
int i, rc = 0;
if (value_only && argc != 1) {
fprintf(stderr,
"## Error: `-n'/`--noheader' option requires exactly one argument\n");
return -1;
}
if (!opts)
opts = &default_opts;
if (fw_env_open(opts))
return -1;
if (argc == 0) { /* Print all env variables */
char *env, *nxt;
for (env = environment.data; *env; env = nxt + 1) {
for (nxt = env; *nxt; ++nxt) {
if (nxt >= &environment.data[ENV_SIZE]) {
fprintf(stderr, "## Error: "
"environment not terminated\n");
return -1;
}
}
printf("%s\n", env);
}
fw_env_close(opts);
return 0;
}
for (i = 0; i < argc; ++i) { /* print a subset of env variables */
char *name = argv[i];
char *val = NULL;
val = fw_getenv(name);
if (!val) {
fprintf(stderr, "## Error: \"%s\" not defined\n", name);
rc = -1;
continue;
}
if (value_only) {
puts(val);
break;
}
printf("%s=%s\n", name, val);
}
fw_env_close(opts);
return rc;
}
int fw_env_flush(struct env_opts *opts)
{
if (!opts)
opts = &default_opts;
if (!environment.dirty)
return 0;
/*
* Update CRC
*/
*environment.crc = crc32(0, (uint8_t *) environment.data, ENV_SIZE);
/* write environment back to flash */
if (flash_io(O_RDWR)) {
fprintf(stderr, "Error: can't write fw_env to flash\n");
return -1;
}
return 0;
}
/*
* Set/Clear a single variable in the environment.
* This is called in sequence to update the environment
* in RAM without updating the copy in flash after each set
*/
int fw_env_write(char *name, char *value)
{
int len;
char *env, *nxt;
char *oldval = NULL;
int deleting, creating, overwriting;
/*
* search if variable with this name already exists
*/
for (nxt = env = environment.data; *env; env = nxt + 1) {
for (nxt = env; *nxt; ++nxt) {
if (nxt >= &environment.data[ENV_SIZE]) {
fprintf(stderr, "## Error: "
"environment not terminated\n");
errno = EINVAL;
return -1;
}
}
oldval = envmatch(name, env);
if (oldval)
break;
}
deleting = (oldval && !(value && strlen(value)));
creating = (!oldval && (value && strlen(value)));
overwriting = (oldval && (value && strlen(value) &&
strcmp(oldval, value)));
/* check for permission */
if (deleting) {
if (env_flags_validate_varaccess(name,
ENV_FLAGS_VARACCESS_PREVENT_DELETE)) {
printf("Can't delete \"%s\"\n", name);
errno = EROFS;
return -1;
}
} else if (overwriting) {
if (env_flags_validate_varaccess(name,
ENV_FLAGS_VARACCESS_PREVENT_OVERWR)) {
printf("Can't overwrite \"%s\"\n", name);
errno = EROFS;
return -1;
} else if (env_flags_validate_varaccess(name,
ENV_FLAGS_VARACCESS_PREVENT_NONDEF_OVERWR)) {
const char *defval = fw_getdefenv(name);
if (defval == NULL)
defval = "";
if (strcmp(oldval, defval)
!= 0) {
printf("Can't overwrite \"%s\"\n", name);
errno = EROFS;
return -1;
}
}
} else if (creating) {
if (env_flags_validate_varaccess(name,
ENV_FLAGS_VARACCESS_PREVENT_CREATE)) {
printf("Can't create \"%s\"\n", name);
errno = EROFS;
return -1;
}
} else
/* Nothing to do */
return 0;
environment.dirty = 1;
if (deleting || overwriting) {
if (*++nxt == '\0') {
*env = '\0';
} else {
for (;;) {
*env = *nxt++;
if ((*env == '\0') && (*nxt == '\0'))
break;
++env;
}
}
*++env = '\0';
}
/* Delete only ? */
if (!value || !strlen(value))
return 0;
/*
* Append new definition at the end
*/
for (env = environment.data; *env || *(env + 1); ++env)
;
if (env > environment.data)
++env;
/*
* Overflow when:
* "name" + "=" + "val" +"\0\0" > CUR_ENVSIZE - (env-environment)
*/
len = strlen(name) + 2;
/* add '=' for first arg, ' ' for all others */
len += strlen(value) + 1;
if (len > (&environment.data[ENV_SIZE] - env)) {
fprintf(stderr,
"Error: environment overflow, \"%s\" deleted\n", name);
return -1;
}
while ((*env = *name++) != '\0')
env++;
*env = '=';
while ((*++env = *value++) != '\0')
;
/* end is marked with double '\0' */
*++env = '\0';
return 0;
}
/*
* Deletes or sets environment variables. Returns -1 and sets errno error codes:
* 0 - OK
* EINVAL - need at least 1 argument
* EROFS - certain variables ("ethaddr", "serial#") cannot be
* modified or deleted
*
*/
int fw_env_set(int argc, char *argv[], struct env_opts *opts)
{
int i;
size_t len;
char *name, **valv;
char *oldval;
char *value = NULL;
int valc;
int ret;
if (!opts)
opts = &default_opts;
if (argc < 1) {
fprintf(stderr, "## Error: variable name missing\n");
errno = EINVAL;
return -1;
}
if (fw_env_open(opts)) {
fprintf(stderr, "Error: environment not initialized\n");
return -1;
}
name = argv[0];
valv = argv + 1;
valc = argc - 1;
if (env_flags_validate_env_set_params(name, valv, valc) < 0) {
fw_env_close(opts);
return -1;
}
len = 0;
for (i = 0; i < valc; ++i) {
char *val = valv[i];
size_t val_len = strlen(val);
if (value)
value[len - 1] = ' ';
oldval = value;
value = realloc(value, len + val_len + 1);
if (!value) {
fprintf(stderr,
"Cannot malloc %zu bytes: %s\n",
len, strerror(errno));
free(oldval);
return -1;
}
memcpy(value + len, val, val_len);
len += val_len;
value[len++] = '\0';
}
fw_env_write(name, value);
free(value);
ret = fw_env_flush(opts);
fw_env_close(opts);
return ret;
}
/*
* Parse a file and configure the u-boot variables.
* The script file has a very simple format, as follows:
*
* Each line has a couple with name, value:
* <white spaces>variable_name<white spaces>variable_value
*
* Both variable_name and variable_value are interpreted as strings.
* Any character after <white spaces> and before ending \r\n is interpreted
* as variable's value (no comment allowed on these lines !)
*
* Comments are allowed if the first character in the line is #
*
* Returns -1 and sets errno error codes:
* 0 - OK
* -1 - Error
*/
int fw_parse_script(char *fname, struct env_opts *opts)
{
FILE *fp;
char *line = NULL;
size_t linesize = 0;
char *name;
char *val;
int lineno = 0;
int len;
int ret = 0;
if (!opts)
opts = &default_opts;
if (fw_env_open(opts)) {
fprintf(stderr, "Error: environment not initialized\n");
return -1;
}
if (strcmp(fname, "-") == 0)
fp = stdin;
else {
fp = fopen(fname, "r");
if (fp == NULL) {
fprintf(stderr, "I cannot open %s for reading\n",
fname);
return -1;
}
}
while ((len = getline(&line, &linesize, fp)) != -1) {
lineno++;
/*
* Read a whole line from the file. If the line is not
* terminated, reports an error and exit.
*/
if (line[len - 1] != '\n') {
fprintf(stderr,
"Line %d not correctly terminated\n",
lineno);
ret = -1;
break;
}
/* Drop ending line feed / carriage return */
line[--len] = '\0';
if (len && line[len - 1] == '\r')
line[--len] = '\0';
/* Skip comment or empty lines */
if (len == 0 || line[0] == '#')
continue;
/*
* Search for variable's name remove leading whitespaces
*/
name = skip_blanks(line);
if (!name)
continue;
/* The first white space is the end of variable name */
val = skip_chars(name);
len = strlen(name);
if (val) {
*val++ = '\0';
if ((val - name) < len)
val = skip_blanks(val);
else
val = NULL;
}
#ifdef DEBUG
fprintf(stderr, "Setting %s : %s\n",
name, val ? val : " removed");
#endif
if (env_flags_validate_type(name, val) < 0) {
ret = -1;
break;
}
/*
* If there is an error setting a variable,
* try to save the environment and returns an error
*/
if (fw_env_write(name, val)) {
fprintf(stderr,
"fw_env_write returns with error : %s\n",
strerror(errno));
ret = -1;
break;
}
}
free(line);
/* Close file if not stdin */
if (strcmp(fname, "-") != 0)
fclose(fp);
ret |= fw_env_flush(opts);
fw_env_close(opts);
return ret;
}
/**
* environment_end() - compute offset of first byte right after environment
* @dev - index of enviroment buffer
* Return:
* device offset of first byte right after environment
*/
off_t environment_end(int dev)
{
/* environment is block aligned */
return DEVOFFSET(dev) + ENVSECTORS(dev) * DEVESIZE(dev);
}
/*
* Test for bad block on NAND, just returns 0 on NOR, on NAND:
* 0 - block is good
* > 0 - block is bad
* < 0 - failed to test
*/
static int flash_bad_block(int fd, uint8_t mtd_type, loff_t blockstart)
{
if (mtd_type == MTD_NANDFLASH) {
int badblock = ioctl(fd, MEMGETBADBLOCK, &blockstart);
if (badblock < 0) {
perror("Cannot read bad block mark");
return badblock;
}
if (badblock) {
#ifdef DEBUG
fprintf(stderr, "Bad block at 0x%llx, skipping\n",
(unsigned long long)blockstart);
#endif
return badblock;
}
}
return 0;
}
/*
* Read data from flash at an offset into a provided buffer. On NAND it skips
* bad blocks but makes sure it stays within ENVSECTORS (dev) starting from
* the DEVOFFSET (dev) block. On NOR the loop is only run once.
*/
static int flash_read_buf(int dev, int fd, void *buf, size_t count,
off_t offset)
{
size_t blocklen; /* erase / write length - one block on NAND,
0 on NOR */
size_t processed = 0; /* progress counter */
size_t readlen = count; /* current read length */
off_t block_seek; /* offset inside the current block to the start
of the data */
loff_t blockstart; /* running start of the current block -
MEMGETBADBLOCK needs 64 bits */
int rc;
blockstart = (offset / DEVESIZE(dev)) * DEVESIZE(dev);
/* Offset inside a block */
block_seek = offset - blockstart;
if (DEVTYPE(dev) == MTD_NANDFLASH) {
/*
* NAND: calculate which blocks we are reading. We have
* to read one block at a time to skip bad blocks.
*/
blocklen = DEVESIZE(dev);
/* Limit to one block for the first read */
if (readlen > blocklen - block_seek)
readlen = blocklen - block_seek;
} else {
blocklen = 0;
}
/* This only runs once on NOR flash */
while (processed < count) {
rc = flash_bad_block(fd, DEVTYPE(dev), blockstart);
if (rc < 0) /* block test failed */
return -1;
if (blockstart + block_seek + readlen > environment_end(dev)) {
/* End of range is reached */
fprintf(stderr, "Too few good blocks within range\n");
return -1;
}
if (rc) { /* block is bad */
blockstart += blocklen;
continue;
}
/*
* If a block is bad, we retry in the next block at the same
* offset - see env/nand.c::writeenv()
*/
lseek(fd, blockstart + block_seek, SEEK_SET);
rc = read(fd, buf + processed, readlen);
if (rc != readlen) {
fprintf(stderr, "Read error on %s: %s\n",
DEVNAME(dev), strerror(errno));
return -1;
}
#ifdef DEBUG
fprintf(stderr, "Read 0x%x bytes at 0x%llx on %s\n",
rc, (unsigned long long)blockstart + block_seek,
DEVNAME(dev));
#endif
processed += readlen;
readlen = min(blocklen, count - processed);
block_seek = 0;
blockstart += blocklen;
}
return processed;
}
/*
* Write count bytes from begin of environment, but stay within
* ENVSECTORS(dev) sectors of
* DEVOFFSET (dev). Similar to the read case above, on NOR and dataflash we
* erase and write the whole data at once.
*/
static int flash_write_buf(int dev, int fd, void *buf, size_t count)
{
void *data;
struct erase_info_user erase;
size_t blocklen; /* length of NAND block / NOR erase sector */
size_t erase_len; /* whole area that can be erased - may include
bad blocks */
size_t erasesize; /* erase / write length - one block on NAND,
whole area on NOR */
size_t processed = 0; /* progress counter */
size_t write_total; /* total size to actually write - excluding
bad blocks */
off_t erase_offset; /* offset to the first erase block (aligned)
below offset */
off_t block_seek; /* offset inside the erase block to the start
of the data */
loff_t blockstart; /* running start of the current block -
MEMGETBADBLOCK needs 64 bits */
int rc;
/*
* For mtd devices only offset and size of the environment do matter
*/
if (DEVTYPE(dev) == MTD_ABSENT) {
blocklen = count;
erase_len = blocklen;
blockstart = DEVOFFSET(dev);
block_seek = 0;
write_total = blocklen;
} else {
blocklen = DEVESIZE(dev);
erase_offset = DEVOFFSET(dev);
/* Maximum area we may use */
erase_len = environment_end(dev) - erase_offset;
blockstart = erase_offset;
/* Offset inside a block */
block_seek = DEVOFFSET(dev) - erase_offset;
/*
* Data size we actually write: from the start of the block
* to the start of the data, then count bytes of data, and
* to the end of the block
*/
write_total = ((block_seek + count + blocklen - 1) /
blocklen) * blocklen;
}
/*
* Support data anywhere within erase sectors: read out the complete
* area to be erased, replace the environment image, write the whole
* block back again.
*/
if (write_total > count) {
data = malloc(erase_len);
if (!data) {
fprintf(stderr,
"Cannot malloc %zu bytes: %s\n",
erase_len, strerror(errno));
return -1;
}
rc = flash_read_buf(dev, fd, data, write_total, erase_offset);
if (write_total != rc)
return -1;
#ifdef DEBUG
fprintf(stderr, "Preserving data ");
if (block_seek != 0)
fprintf(stderr, "0x%x - 0x%lx", 0, block_seek - 1);
if (block_seek + count != write_total) {
if (block_seek != 0)
fprintf(stderr, " and ");
fprintf(stderr, "0x%lx - 0x%lx",
(unsigned long)block_seek + count,
(unsigned long)write_total - 1);
}
fprintf(stderr, "\n");
#endif
/* Overwrite the old environment */
memcpy(data + block_seek, buf, count);
} else {
/*
* We get here, iff offset is block-aligned and count is a
* multiple of blocklen - see write_total calculation above
*/
data = buf;
}
if (DEVTYPE(dev) == MTD_NANDFLASH) {
/*
* NAND: calculate which blocks we are writing. We have
* to write one block at a time to skip bad blocks.
*/
erasesize = blocklen;
} else {
erasesize = erase_len;
}
erase.length = erasesize;
/* This only runs once on NOR flash and SPI-dataflash */
while (processed < write_total) {
rc = flash_bad_block(fd, DEVTYPE(dev), blockstart);
if (rc < 0) /* block test failed */
return rc;
if (blockstart + erasesize > environment_end(dev)) {
fprintf(stderr, "End of range reached, aborting\n");
return -1;
}
if (rc) { /* block is bad */
blockstart += blocklen;
continue;
}
if (DEVTYPE(dev) != MTD_ABSENT) {
erase.start = blockstart;
ioctl(fd, MEMUNLOCK, &erase);
/* These do not need an explicit erase cycle */
if (DEVTYPE(dev) != MTD_DATAFLASH)
if (ioctl(fd, MEMERASE, &erase) != 0) {
fprintf(stderr,
"MTD erase error on %s: %s\n",
DEVNAME(dev), strerror(errno));
return -1;
}
}
if (lseek(fd, blockstart, SEEK_SET) == -1) {
fprintf(stderr,
"Seek error on %s: %s\n",
DEVNAME(dev), strerror(errno));
return -1;
}
#ifdef DEBUG
fprintf(stderr, "Write 0x%llx bytes at 0x%llx\n",
(unsigned long long)erasesize,
(unsigned long long)blockstart);
#endif
if (write(fd, data + processed, erasesize) != erasesize) {
fprintf(stderr, "Write error on %s: %s\n",
DEVNAME(dev), strerror(errno));
return -1;
}
if (DEVTYPE(dev) != MTD_ABSENT)
ioctl(fd, MEMLOCK, &erase);
processed += erasesize;
block_seek = 0;
blockstart += erasesize;
}
if (write_total > count)
free(data);
return processed;
}
/*
* Set obsolete flag at offset - NOR flash only
*/
static int flash_flag_obsolete(int dev, int fd, off_t offset)
{
int rc;
struct erase_info_user erase;
char tmp = ENV_REDUND_OBSOLETE;
erase.start = DEVOFFSET(dev);
erase.length = DEVESIZE(dev);
/* This relies on the fact, that ENV_REDUND_OBSOLETE == 0 */
rc = lseek(fd, offset, SEEK_SET);
if (rc < 0) {
fprintf(stderr, "Cannot seek to set the flag on %s\n",
DEVNAME(dev));
return rc;
}
ioctl(fd, MEMUNLOCK, &erase);
rc = write(fd, &tmp, sizeof(tmp));
ioctl(fd, MEMLOCK, &erase);
if (rc < 0)
perror("Could not set obsolete flag");
return rc;
}
static int flash_write(int fd_current, int fd_target, int dev_target)
{
int rc;
switch (environment.flag_scheme) {
case FLAG_NONE:
break;
case FLAG_INCREMENTAL:
(*environment.flags)++;
break;
case FLAG_BOOLEAN:
*environment.flags = ENV_REDUND_ACTIVE;
break;
default:
fprintf(stderr, "Unimplemented flash scheme %u\n",
environment.flag_scheme);
return -1;
}
#ifdef DEBUG
fprintf(stderr, "Writing new environment at 0x%llx on %s\n",
DEVOFFSET(dev_target), DEVNAME(dev_target));
#endif
if (IS_UBI(dev_target)) {
if (ubi_update_start(fd_target, CUR_ENVSIZE) < 0)
return 0;
return ubi_write(fd_target, environment.image, CUR_ENVSIZE);
}
rc = flash_write_buf(dev_target, fd_target, environment.image,
CUR_ENVSIZE);
if (rc < 0)
return rc;
if (environment.flag_scheme == FLAG_BOOLEAN) {
/* Have to set obsolete flag */
off_t offset = DEVOFFSET(dev_current) +
offsetof(struct env_image_redundant, flags);
#ifdef DEBUG
fprintf(stderr,
"Setting obsolete flag in environment at 0x%llx on %s\n",
DEVOFFSET(dev_current), DEVNAME(dev_current));
#endif
flash_flag_obsolete(dev_current, fd_current, offset);
}
return 0;
}
static int flash_read(int fd)
{
int rc;
if (IS_UBI(dev_current)) {
DEVTYPE(dev_current) = MTD_ABSENT;
return ubi_read(fd, environment.image, CUR_ENVSIZE);
}
rc = flash_read_buf(dev_current, fd, environment.image, CUR_ENVSIZE,
DEVOFFSET(dev_current));
if (rc != CUR_ENVSIZE)
return -1;
return 0;
}
static int flash_open_tempfile(const char **dname, const char **target_temp)
{
char *dup_name = strdup(DEVNAME(dev_current));
char *temp_name = NULL;
int rc = -1;
if (!dup_name)
return -1;
*dname = dirname(dup_name);
if (!*dname)
goto err;
rc = asprintf(&temp_name, "%s/XXXXXX", *dname);
if (rc == -1)
goto err;
rc = mkstemp(temp_name);
if (rc == -1) {
/* fall back to in place write */
fprintf(stderr,
"Can't create %s: %s\n", temp_name, strerror(errno));
free(temp_name);
} else {
*target_temp = temp_name;
/* deliberately leak dup_name as dname /might/ point into
* it and we need it for our caller
*/
dup_name = NULL;
}
err:
if (dup_name)
free(dup_name);
return rc;
}
static int flash_io_write(int fd_current)
{
int fd_target = -1, rc, dev_target;
const char *dname, *target_temp = NULL;
if (have_redund_env) {
/* switch to next partition for writing */
dev_target = !dev_current;
/* dev_target: fd_target, erase_target */
fd_target = open(DEVNAME(dev_target), O_RDWR);
if (fd_target < 0) {
fprintf(stderr,
"Can't open %s: %s\n",
DEVNAME(dev_target), strerror(errno));
rc = -1;
goto exit;
}
} else {
struct stat sb;
if (fstat(fd_current, &sb) == 0 && S_ISREG(sb.st_mode)) {
/* if any part of flash_open_tempfile() fails we fall
* back to in-place writes
*/
fd_target = flash_open_tempfile(&dname, &target_temp);
}
dev_target = dev_current;
if (fd_target == -1)
fd_target = fd_current;
}
rc = flash_write(fd_current, fd_target, dev_target);
if (fsync(fd_current) && !(errno == EINVAL || errno == EROFS)) {
fprintf(stderr,
"fsync failed on %s: %s\n",
DEVNAME(dev_current), strerror(errno));
}
if (fd_current != fd_target) {
if (fsync(fd_target) &&
!(errno == EINVAL || errno == EROFS)) {
fprintf(stderr,
"fsync failed on %s: %s\n",
DEVNAME(dev_current), strerror(errno));
}
if (close(fd_target)) {
fprintf(stderr,
"I/O error on %s: %s\n",
DEVNAME(dev_target), strerror(errno));
rc = -1;
}
if (rc >= 0 && target_temp) {
int dir_fd;
dir_fd = open(dname, O_DIRECTORY | O_RDONLY);
if (dir_fd == -1)
fprintf(stderr,
"Can't open %s: %s\n",
dname, strerror(errno));
if (rename(target_temp, DEVNAME(dev_target))) {
fprintf(stderr,
"rename failed %s => %s: %s\n",
target_temp, DEVNAME(dev_target),
strerror(errno));
rc = -1;
}
if (dir_fd != -1 && fsync(dir_fd))
fprintf(stderr,
"fsync failed on %s: %s\n",
dname, strerror(errno));
if (dir_fd != -1 && close(dir_fd))
fprintf(stderr,
"I/O error on %s: %s\n",
dname, strerror(errno));
}
}
exit:
return rc;
}
static int flash_io(int mode)
{
int fd_current, rc;
/* dev_current: fd_current, erase_current */
fd_current = open(DEVNAME(dev_current), mode);
if (fd_current < 0) {
fprintf(stderr,
"Can't open %s: %s\n",
DEVNAME(dev_current), strerror(errno));
return -1;
}
if (mode == O_RDWR) {
rc = flash_io_write(fd_current);
} else {
rc = flash_read(fd_current);
}
if (close(fd_current)) {
fprintf(stderr,
"I/O error on %s: %s\n",
DEVNAME(dev_current), strerror(errno));
return -1;
}
return rc;
}
/*
* Prevent confusion if running from erased flash memory
*/
int fw_env_open(struct env_opts *opts)
{
int crc0, crc0_ok;
unsigned char flag0;
void *addr0 = NULL;
int crc1, crc1_ok;
unsigned char flag1;
void *addr1 = NULL;
int ret;
struct env_image_single *single;
struct env_image_redundant *redundant;
if (!opts)
opts = &default_opts;
if (parse_config(opts)) /* should fill envdevices */
return -EINVAL;
addr0 = calloc(1, CUR_ENVSIZE);
if (addr0 == NULL) {
fprintf(stderr,
"Not enough memory for environment (%ld bytes)\n",
CUR_ENVSIZE);
ret = -ENOMEM;
goto open_cleanup;
}
/* read environment from FLASH to local buffer */
environment.image = addr0;
if (have_redund_env) {
redundant = addr0;
environment.crc = &redundant->crc;
environment.flags = &redundant->flags;
environment.data = redundant->data;
} else {
single = addr0;
environment.crc = &single->crc;
environment.flags = NULL;
environment.data = single->data;
}
dev_current = 0;
if (flash_io(O_RDONLY)) {
ret = -EIO;
goto open_cleanup;
}
crc0 = crc32(0, (uint8_t *)environment.data, ENV_SIZE);
crc0_ok = (crc0 == *environment.crc);
if (!have_redund_env) {
if (!crc0_ok) {
fprintf(stderr,
"Warning: Bad CRC, using default environment\n");
memcpy(environment.data, default_environment,
sizeof(default_environment));
environment.dirty = 1;
}
} else {
flag0 = *environment.flags;
dev_current = 1;
addr1 = calloc(1, CUR_ENVSIZE);
if (addr1 == NULL) {
fprintf(stderr,
"Not enough memory for environment (%ld bytes)\n",
CUR_ENVSIZE);
ret = -ENOMEM;
goto open_cleanup;
}
redundant = addr1;
/*
* have to set environment.image for flash_read(), careful -
* other pointers in environment still point inside addr0
*/
environment.image = addr1;
if (flash_io(O_RDONLY)) {
ret = -EIO;
goto open_cleanup;
}
/* Check flag scheme compatibility */
if (DEVTYPE(dev_current) == MTD_NORFLASH &&
DEVTYPE(!dev_current) == MTD_NORFLASH) {
environment.flag_scheme = FLAG_BOOLEAN;
} else if (DEVTYPE(dev_current) == MTD_NANDFLASH &&
DEVTYPE(!dev_current) == MTD_NANDFLASH) {
environment.flag_scheme = FLAG_INCREMENTAL;
} else if (DEVTYPE(dev_current) == MTD_DATAFLASH &&
DEVTYPE(!dev_current) == MTD_DATAFLASH) {
environment.flag_scheme = FLAG_BOOLEAN;
} else if (DEVTYPE(dev_current) == MTD_UBIVOLUME &&
DEVTYPE(!dev_current) == MTD_UBIVOLUME) {
environment.flag_scheme = FLAG_INCREMENTAL;
} else if (DEVTYPE(dev_current) == MTD_ABSENT &&
DEVTYPE(!dev_current) == MTD_ABSENT &&
IS_UBI(dev_current) == IS_UBI(!dev_current)) {
environment.flag_scheme = FLAG_INCREMENTAL;
} else {
fprintf(stderr, "Incompatible flash types!\n");
ret = -EINVAL;
goto open_cleanup;
}
crc1 = crc32(0, (uint8_t *)redundant->data, ENV_SIZE);
crc1_ok = (crc1 == redundant->crc);
flag1 = redundant->flags;
/*
* environment.data still points to ((struct
* env_image_redundant *)addr0)->data. If the two
* environments differ, or one has bad crc, force a
* write-out by marking the environment dirty.
*/
if (memcmp(environment.data, redundant->data, ENV_SIZE) ||
!crc0_ok || !crc1_ok)
environment.dirty = 1;
if (crc0_ok && !crc1_ok) {
dev_current = 0;
} else if (!crc0_ok && crc1_ok) {
dev_current = 1;
} else if (!crc0_ok && !crc1_ok) {
fprintf(stderr,
"Warning: Bad CRC, using default environment\n");
memcpy(environment.data, default_environment,
sizeof(default_environment));
environment.dirty = 1;
dev_current = 0;
} else {
switch (environment.flag_scheme) {
case FLAG_BOOLEAN:
if (flag0 == ENV_REDUND_ACTIVE &&
flag1 == ENV_REDUND_OBSOLETE) {
dev_current = 0;
} else if (flag0 == ENV_REDUND_OBSOLETE &&
flag1 == ENV_REDUND_ACTIVE) {
dev_current = 1;
} else if (flag0 == flag1) {
dev_current = 0;
} else if (flag0 == 0xFF) {
dev_current = 0;
} else if (flag1 == 0xFF) {
dev_current = 1;
} else {
dev_current = 0;
}
break;
case FLAG_INCREMENTAL:
if (flag0 == 255 && flag1 == 0)
dev_current = 1;
else if ((flag1 == 255 && flag0 == 0) ||
flag0 >= flag1)
dev_current = 0;
else /* flag1 > flag0 */
dev_current = 1;
break;
default:
fprintf(stderr, "Unknown flag scheme %u\n",
environment.flag_scheme);
return -1;
}
}
/*
* If we are reading, we don't need the flag and the CRC any
* more, if we are writing, we will re-calculate CRC and update
* flags before writing out
*/
if (dev_current) {
environment.image = addr1;
environment.crc = &redundant->crc;
environment.flags = &redundant->flags;
environment.data = redundant->data;
free(addr0);
} else {
environment.image = addr0;
/* Other pointers are already set */
free(addr1);
}
#ifdef DEBUG
fprintf(stderr, "Selected env in %s\n", DEVNAME(dev_current));
#endif
}
return 0;
open_cleanup:
if (addr0)
free(addr0);
if (addr1)
free(addr1);
return ret;
}
/*
* Simply free allocated buffer with environment
*/
int fw_env_close(struct env_opts *opts)
{
if (environment.image)
free(environment.image);
environment.image = NULL;
return 0;
}
static int check_device_config(int dev)
{
struct stat st;
int32_t lnum = 0;
int fd, rc = 0;
/* Fills in IS_UBI(), converts DEVNAME() with ubi volume name */
ubi_check_dev(dev);
fd = open(DEVNAME(dev), O_RDONLY);
if (fd < 0) {
fprintf(stderr,
"Cannot open %s: %s\n", DEVNAME(dev), strerror(errno));
return -1;
}
rc = fstat(fd, &st);
if (rc < 0) {
fprintf(stderr, "Cannot stat the file %s\n", DEVNAME(dev));
goto err;
}
if (IS_UBI(dev)) {
rc = ioctl(fd, UBI_IOCEBISMAP, &lnum);
if (rc < 0) {
fprintf(stderr, "Cannot get UBI information for %s\n",
DEVNAME(dev));
goto err;
}
} else if (S_ISCHR(st.st_mode)) {
struct mtd_info_user mtdinfo;
rc = ioctl(fd, MEMGETINFO, &mtdinfo);
if (rc < 0) {
fprintf(stderr, "Cannot get MTD information for %s\n",
DEVNAME(dev));
goto err;
}
if (mtdinfo.type != MTD_NORFLASH &&
mtdinfo.type != MTD_NANDFLASH &&
mtdinfo.type != MTD_DATAFLASH &&
mtdinfo.type != MTD_UBIVOLUME) {
fprintf(stderr, "Unsupported flash type %u on %s\n",
mtdinfo.type, DEVNAME(dev));
goto err;
}
DEVTYPE(dev) = mtdinfo.type;
if (DEVESIZE(dev) == 0)
/* Assume the erase size is the same as the env-size */
DEVESIZE(dev) = ENVSIZE(dev);
} else {
uint64_t size;
DEVTYPE(dev) = MTD_ABSENT;
if (DEVESIZE(dev) == 0)
/* Assume the erase size to be 512 bytes */
DEVESIZE(dev) = 0x200;
/*
* Check for negative offsets, treat it as backwards offset
* from the end of the block device
*/
if (DEVOFFSET(dev) < 0) {
rc = ioctl(fd, BLKGETSIZE64, &size);
if (rc < 0) {
fprintf(stderr,
"Could not get block device size on %s\n",
DEVNAME(dev));
goto err;
}
DEVOFFSET(dev) = DEVOFFSET(dev) + size;
#ifdef DEBUG
fprintf(stderr,
"Calculated device offset 0x%llx on %s\n",
DEVOFFSET(dev), DEVNAME(dev));
#endif
}
}
if (ENVSECTORS(dev) == 0)
/* Assume enough sectors to cover the environment */
ENVSECTORS(dev) = DIV_ROUND_UP(ENVSIZE(dev), DEVESIZE(dev));
if (DEVOFFSET(dev) % DEVESIZE(dev) != 0) {
fprintf(stderr,
"Environment does not start on (erase) block boundary\n");
errno = EINVAL;
return -1;
}
if (ENVSIZE(dev) > ENVSECTORS(dev) * DEVESIZE(dev)) {
fprintf(stderr,
"Environment does not fit into available sectors\n");
errno = EINVAL;
return -1;
}
err:
close(fd);
return rc;
}
static int parse_config(struct env_opts *opts)
{
int rc;
if (!opts)
opts = &default_opts;
#if defined(CONFIG_FILE)
/* Fills in DEVNAME(), ENVSIZE(), DEVESIZE(). Or don't. */
if (get_config(opts->config_file)) {
fprintf(stderr, "Cannot parse config file '%s': %m\n",
opts->config_file);
return -1;
}
#else
DEVNAME(0) = DEVICE1_NAME;
DEVOFFSET(0) = DEVICE1_OFFSET;
ENVSIZE(0) = ENV1_SIZE;
/* Set defaults for DEVESIZE, ENVSECTORS later once we
* know DEVTYPE
*/
#ifdef DEVICE1_ESIZE
DEVESIZE(0) = DEVICE1_ESIZE;
#endif
#ifdef DEVICE1_ENVSECTORS
ENVSECTORS(0) = DEVICE1_ENVSECTORS;
#endif
#ifdef HAVE_REDUND
DEVNAME(1) = DEVICE2_NAME;
DEVOFFSET(1) = DEVICE2_OFFSET;
ENVSIZE(1) = ENV2_SIZE;
/* Set defaults for DEVESIZE, ENVSECTORS later once we
* know DEVTYPE
*/
#ifdef DEVICE2_ESIZE
DEVESIZE(1) = DEVICE2_ESIZE;
#endif
#ifdef DEVICE2_ENVSECTORS
ENVSECTORS(1) = DEVICE2_ENVSECTORS;
#endif
have_redund_env = 1;
#endif
#endif
rc = check_device_config(0);
if (rc < 0)
return rc;
if (have_redund_env) {
rc = check_device_config(1);
if (rc < 0)
return rc;
if (ENVSIZE(0) != ENVSIZE(1)) {
fprintf(stderr,
"Redundant environments have unequal size\n");
return -1;
}
}
usable_envsize = CUR_ENVSIZE - sizeof(uint32_t);
if (have_redund_env)
usable_envsize -= sizeof(char);
return 0;
}
#if defined(CONFIG_FILE)
static int get_config(char *fname)
{
FILE *fp;
int i = 0;
int rc;
char *line = NULL;
size_t linesize = 0;
char *devname;
fp = fopen(fname, "r");
if (fp == NULL)
return -1;
while (i < 2 && getline(&line, &linesize, fp) != -1) {
/* Skip comment strings */
if (line[0] == '#')
continue;
rc = sscanf(line, "%ms %lli %lx %lx %lx",
&devname,
&DEVOFFSET(i),
&ENVSIZE(i), &DEVESIZE(i), &ENVSECTORS(i));
if (rc < 3)
continue;
DEVNAME(i) = devname;
/* Set defaults for DEVESIZE, ENVSECTORS later once we
* know DEVTYPE
*/
i++;
}
free(line);
fclose(fp);
have_redund_env = i - 1;
if (!i) { /* No valid entries found */
errno = EINVAL;
return -1;
} else
return 0;
}
#endif