rockbox/lib/x1000-installer/src/xf_flashmap.c
Aidan MacDonald 06423cab58 x1000-installer: Initial commit of new framework
This is a new flash installer framework for the X1000 targets.

A bunch of this code is *UNTESTED* but there is an external test
harness which allows the library to be built and tested on a PC.

Once tests are written and the bugs are ironed out this framework
will replace the existing installer code.

New features:

- Update tarballs are MD5-checksummed to guarantee integrity.
- The flash map is no longer fixed -- updates are self describing and
  carry a map file which specifies the areas to update.
- Can take full or partial backups with checksums computed on the fly.
- Supports an additional verification mode which reads back data after
  writing to ensure the flash contents were not silently corrupted.

Change-Id: I29a89190c7ff566019f6a844ad0571f01fb7192f
2021-11-27 15:28:19 -05:00

313 lines
8.4 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2021 Aidan MacDonald
*
* 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "xf_flashmap.h"
#include "xf_error.h"
#include <stdbool.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
static int xdigit_to_int(char c)
{
if(c >= 'a' && c <= 'f')
return 10 + (c - 'a');
if(c >= 'A' && c <= 'F')
return 10 + (c - 'A');
if(c >= '0' && c <= '9')
return c - '0';
return -1;
}
int xf_map_parseline(const char* line, struct xf_map* map)
{
enum {
s_name,
s_md5,
s_first_num,
s_file_len = s_first_num,
s_offset,
s_length,
s_done,
};
#define skipws() do { while(*line == ' ' || *line == '\t') ++line; } while(0)
#define nextstate() do { ++state; length = 0; ++line; skipws(); } while(0)
int state = s_name;
int length = 0;
int digit_val;
uint32_t int_val;
uint32_t* num_ptr[3] = {&map->file_length, &map->offset, &map->length};
bool has_md5 = true;
skipws();
while(*line && *line != '\n') {
switch(state) {
case s_name:
if(*line == ' ' || *line == '\t') {
nextstate();
continue;
} else if(isgraph((unsigned char)*line)) {
if(length == XF_MAP_NAMELEN)
return XF_E_FILENAME_TOO_LONG;
map->name[length++] = *line++;
map->name[length] = '\0';
continue;
} else {
return XF_E_SYNTAX_ERROR;
}
case s_md5:
if(*line == '-') {
memset(map->md5, 0, 16);
map->file_length = 0;
has_md5 = false;
++line;
} else {
for(int i = 0; i < 16; ++i) {
int_val = 0;
for(int j = 0; j < 2; ++j) {
digit_val = xdigit_to_int(*line++);
if(digit_val < 0)
return XF_E_SYNTAX_ERROR;
int_val <<= 4;
int_val |= digit_val;
}
map->md5[i] = int_val;
}
}
if(*line == ' ' || *line == '\t') {
/* skip file length if md5 is not present */
if(!has_md5)
++state;
nextstate();
continue;
} else {
return XF_E_SYNTAX_ERROR;
}
case s_file_len:
case s_offset:
case s_length:
int_val = 0;
if(*line == '0') {
++line;
if(*line == 'x' || *line == 'X') {
++line;
while((digit_val = xdigit_to_int(*line)) >= 0) {
++line;
if(int_val > UINT32_MAX/16)
return XF_E_INT_OVERFLOW;
int_val *= 16;
if(int_val > UINT32_MAX - digit_val)
return XF_E_INT_OVERFLOW;
int_val |= digit_val;
}
}
} else if(*line >= '1' && *line <= '9') {
do {
if(int_val > UINT32_MAX/10)
return XF_E_INT_OVERFLOW;
int_val *= 10;
digit_val = *line++ - '0';
if(int_val > UINT32_MAX - digit_val)
return XF_E_INT_OVERFLOW;
int_val += digit_val;
} while(*line >= '0' && *line <= '9');
}
*num_ptr[state - s_first_num] = int_val;
if(*line == ' ' || *line == '\t') {
nextstate();
continue;
} else if(state+1 == s_done && *line == '\0') {
/* end of input */
continue;
} else {
return XF_E_SYNTAX_ERROR;
}
case s_done:
if(isspace(*line)) {
line++;
continue; /* swallow trailing spaces, carriage return, etc */
} else
return XF_E_SYNTAX_ERROR;
}
}
#undef skipws
#undef nextstate
/* one last overflow check - ensure mapped range is addressable */
if(map->offset > UINT32_MAX - map->length)
return XF_E_INT_OVERFLOW;
if(has_md5)
map->flags = XF_MAP_HAS_MD5 | XF_MAP_HAS_FILE_LENGTH;
else
map->flags = 0;
return XF_E_SUCCESS;
}
struct map_parse_args {
struct xf_map* map;
int num;
int maxnum;
};
int map_parse_line_cb(int n, char* buf, void* arg)
{
(void)n;
struct map_parse_args* args = arg;
/* ignore comments and blank lines */
if(*buf == '#' || *buf == '\0')
return 0;
struct xf_map dummy_map;
struct xf_map* dst_map;
if(args->num < args->maxnum)
dst_map = &args->map[args->num];
else
dst_map = &dummy_map;
int rc = xf_map_parseline(buf, dst_map);
if(rc)
return rc;
args->num++;
return 0;
}
int xf_map_parse(struct xf_stream* s, struct xf_map* map, int maxnum)
{
char buf[200];
struct map_parse_args args;
args.map = map;
args.num = 0;
args.maxnum = maxnum;
int rc = xf_stream_read_lines(s, buf, sizeof(buf),
map_parse_line_cb, &args);
if(rc < 0)
return rc;
return args.num;
}
static int xf_map_compare(const void* a, const void* b)
{
const struct xf_map* mapA = a;
const struct xf_map* mapB = b;
if(mapA->offset < mapB->offset)
return -1;
else if(mapA->offset == mapB->offset)
return 0;
else
return 1;
}
int xf_map_sort(struct xf_map* map, int num)
{
qsort(map, num, sizeof(struct xf_map), xf_map_compare);
return xf_map_validate(map, num);
}
int xf_map_validate(const struct xf_map* map, int num)
{
for(int i = 1; i < num; ++i)
if(map[i].offset <= map[i-1].offset)
return -1;
for(int i = 1; i < num; ++i)
if(map[i-1].offset + map[i-1].length > map[i].offset)
return i;
return 0;
}
int xf_map_write(struct xf_map* map, int num, struct xf_stream* s)
{
static const char hex[] = "0123456789abcdef";
char buf[200];
char md5str[33];
int total_len = 0;
md5str[32] = '\0';
for(int i = 0; i < num; ++i) {
bool has_md5 = false;
if(map->flags & XF_MAP_HAS_MD5) {
if(!(map->flags & XF_MAP_HAS_FILE_LENGTH))
return XF_E_INVALID_PARAMETER;
has_md5 = true;
for(int j = 0; j < 16; ++j) {
uint8_t byte = map[i].md5[j];
md5str[2*j] = hex[(byte >> 4) & 0xf];
md5str[2*j+1] = hex[byte & 0xf];
}
}
int len;
if(!has_md5) {
len = snprintf(buf, sizeof(buf), "%s - %lx %lu\n",
map[i].name,
(unsigned long)map[i].offset,
(unsigned long)map[i].length);
} else {
len = snprintf(buf, sizeof(buf), "%s %s %lu 0x%lx %lu\n",
map[i].name, md5str,
(unsigned long)map[i].file_length,
(unsigned long)map[i].offset,
(unsigned long)map[i].length);
}
if(len < 0 || (size_t)len >= sizeof(buf))
return XF_E_LINE_TOO_LONG;
if(s) {
int rc = xf_stream_write(s, buf, len);
if(rc != len)
return XF_E_IO;
}
total_len += len;
}
return total_len;
}