mirror of
https://github.com/gnuton/asuswrt-merlin.ng.git
synced 2025-05-19 07:51:46 +02:00
464 lines
19 KiB
C
464 lines
19 KiB
C
/*
|
|
Copyright 2000-2015 Broadcom Corporation
|
|
<:label-BRCM:2015:DUAL/GPL:standard
|
|
|
|
Unless you and Broadcom execute a separate written software license
|
|
agreement governing use of this software, this software is licensed
|
|
to you under the terms of the GNU General Public License version 2
|
|
(the "GPL"), available at http://www.broadcom.com/licenses/GPLv2.php,
|
|
with the following added to such license:
|
|
|
|
As a special exception, the copyright holders of this software give
|
|
you permission to link this software with independent modules, and
|
|
to copy and distribute the resulting executable under terms of your
|
|
choice, provided that you also meet, for each linked independent
|
|
module, the terms and conditions of the license of that module.
|
|
An independent module is a module which is not derived from this
|
|
software. The special exception does not apply to any modifications
|
|
of the software.
|
|
|
|
Not withstanding the above, under no circumstances may you combine
|
|
this software in any way with any other Broadcom software provided
|
|
under a license other than the GPL, without Broadcom's express prior
|
|
written consent.
|
|
|
|
:>
|
|
*/
|
|
|
|
/***************************************************************************
|
|
* File Name : bcm_ubi.c
|
|
*
|
|
* Description: This file contains the universal UBI volume parser
|
|
***************************************************************************/
|
|
|
|
|
|
/** Includes. */
|
|
|
|
#include "bcm_ubi.h"
|
|
|
|
#ifdef _CFE_
|
|
|
|
#ifndef CFG_RAMAPP
|
|
#define CFEROM 1
|
|
#endif
|
|
|
|
#include "lib_types.h"
|
|
#include "lib_printf.h"
|
|
#include "lib_crc.h"
|
|
#include "lib_byteorder.h"
|
|
|
|
#define getCrc32 lib_get_crc32
|
|
|
|
#else // Linux
|
|
|
|
#ifndef __KERNEL__
|
|
#define be32_to_cpu be32toh
|
|
#endif
|
|
|
|
#endif
|
|
|
|
#ifndef __KERNEL__
|
|
#define PRINT printf
|
|
#else
|
|
#define PRINT printk
|
|
#endif
|
|
|
|
|
|
#define CRC_INIT -1
|
|
#define CRC_LENGTH 4
|
|
|
|
/* -------------------------------------------------------------------
|
|
this function scans a UBI device for a particular volume and then a
|
|
data name/value element within that volume, and either sets it
|
|
if the write callback function pointer is non-zero, or returns
|
|
the data. It requires a single block sized buffer created
|
|
by the calling code
|
|
-------------------------------------------------------------------
|
|
*/
|
|
unsigned int parse_ubi(
|
|
unsigned char * start, // pointer to start of image buffer
|
|
unsigned char * buf, // single block buffer created outside this function we can use
|
|
unsigned int start_blk,
|
|
unsigned int end_blk,
|
|
unsigned int blk_size,
|
|
int volume_id, // volume id can be from 0 to 127, -1 signifies we are bypassing verification of the header (reading UBI data instead of direct from mtd which has UBI header data)
|
|
char * name,
|
|
char * data,
|
|
char ** dataP, // if populated, returns pointer to location of volume_id block
|
|
int header,
|
|
unsigned int (*readblk)(unsigned char * start, unsigned int blk, unsigned int offset, unsigned int blk_size, unsigned char * buf, unsigned int amount, void * mtd, int mtd_fd),
|
|
unsigned int (*writeblk)(unsigned char * start, unsigned int blk, unsigned int offset, unsigned int blk_size, unsigned char * buf, unsigned int amount, void * mtd, int mtd_fd), // leave NULL if reading
|
|
unsigned int (*eraseblk)(unsigned int blk, unsigned int blk_size, void * mtd, int mtd_fd),
|
|
void * mtd,
|
|
int mtd_fd
|
|
)
|
|
{
|
|
unsigned int i;
|
|
|
|
struct ubi_ec_hdr *ec = (struct ubi_ec_hdr *) buf;
|
|
struct ubi_vid_hdr *vid;
|
|
|
|
unsigned int lnum = 0;
|
|
unsigned int end;
|
|
unsigned int state = 1;
|
|
unsigned int from; // file buffer
|
|
unsigned int to = 0; // local buffers
|
|
unsigned int next_entry = 0; // length of record (length to next entry)
|
|
unsigned int data_point = 0; // pointer to entry data name
|
|
unsigned int data_length = 0; // length of entry data
|
|
unsigned int retry = 0;
|
|
unsigned int status = 0;
|
|
unsigned int crc_calc = -1, crc_grab = -1; // data crc
|
|
|
|
RETRY:
|
|
for( i = start_blk; i < end_blk; i++ )
|
|
{ // read ec header
|
|
#ifndef _CFE_
|
|
if (volume_id == -1)
|
|
{
|
|
if ((end = readblk(start, i, 0, blk_size, buf, blk_size, mtd, mtd_fd)))
|
|
{
|
|
vid = 0; // won't be used but need to set it to something
|
|
from = 0; // file buffer
|
|
goto CONT;
|
|
}
|
|
else
|
|
continue;
|
|
}
|
|
#endif
|
|
if (!readblk(start, i, 0, blk_size, buf, UBI_EC_HDR_SIZE, mtd, mtd_fd))
|
|
continue;
|
|
|
|
if ( (be32_to_cpu(ec->magic) == UBI_EC_HDR_MAGIC) && (getCrc32((void *)ec, UBI_EC_HDR_SIZE-4, CRC_INIT) == be32_to_cpu(ec->hdr_crc)) )
|
|
{ // read up to vid header
|
|
if(!readblk(start, i, UBI_EC_HDR_SIZE, blk_size, buf, be32_to_cpu(ec->vid_hdr_offset) + UBI_VID_HDR_SIZE - UBI_EC_HDR_SIZE, mtd, mtd_fd))
|
|
continue;
|
|
|
|
vid = (struct ubi_vid_hdr *) (buf + be32_to_cpu(ec->vid_hdr_offset));
|
|
|
|
if( (be32_to_cpu(vid->magic) == UBI_VID_HDR_MAGIC) && (be32_to_cpu(vid->vol_id) == (unsigned int)volume_id) && (be32_to_cpu(vid->lnum) == lnum) && (getCrc32((void *)vid, UBI_VID_HDR_SIZE-4, -1) == be32_to_cpu(vid->hdr_crc)) )
|
|
{ // read rest of block
|
|
if(!readblk(start, i, be32_to_cpu(ec->vid_hdr_offset) + UBI_VID_HDR_SIZE, blk_size, buf, blk_size - (be32_to_cpu(ec->vid_hdr_offset) + UBI_VID_HDR_SIZE), mtd, mtd_fd))
|
|
continue;
|
|
|
|
if ( (vid->vol_type == UBI_VID_DYNAMIC) || (getCrc32((void *)buf + be32_to_cpu(ec->data_offset), be32_to_cpu(vid->data_size), CRC_INIT) == be32_to_cpu(vid->data_crc)) )
|
|
{ // if dynamic volume or data crc matches
|
|
retry = 1; // found our block, allow for spin through blocks again if we get to the end just in case they are out of order
|
|
|
|
/* Dynamic volumes can have the data_size set to non zero after being scrubbed
|
|
* by UBI (copy_flag set). In this case the data_size can be unreliable as
|
|
* UBI truncates the ending 0xff from the byte count (see ubi_eba_copy_leb() in
|
|
* drivers/mtd/ubi/eba.c). Instead, keep using the block size as the files stored
|
|
* in UBI raw volumes and their offset/size are calculated based on the block size
|
|
*/
|
|
if ((vid->vol_type == UBI_VID_STATIC) && vid->data_size)
|
|
end = be32_to_cpu(vid->data_size) + be32_to_cpu(ec->data_offset);
|
|
else
|
|
end = blk_size; // assume data takes the rest of block if not told otherwise
|
|
|
|
from = be32_to_cpu(ec->data_offset);
|
|
CONT:
|
|
switch(state)
|
|
{
|
|
default:
|
|
if (volume_id != -1)
|
|
from = be32_to_cpu(ec->data_offset);
|
|
else
|
|
from = 0;
|
|
|
|
to = 0;
|
|
state = 0;
|
|
// fall through
|
|
|
|
case 0: // advance to next entry
|
|
if (next_entry >= (end - from))
|
|
{ // go to next entry
|
|
next_entry -= (end - from);
|
|
//lnum += length / (len - offset);
|
|
//from = length % (len - offset);
|
|
lnum++;
|
|
break;
|
|
}
|
|
|
|
from += next_entry;
|
|
state++;
|
|
// fall through
|
|
|
|
case 1: // advance to next entry
|
|
crc_calc = -1;
|
|
to = 0;
|
|
state++;
|
|
// fall through
|
|
|
|
case 2: // get pointer to next entry
|
|
while((from < end) && (to < 4))
|
|
{
|
|
crc_calc = getCrc32(&buf[from], 1, crc_calc);
|
|
to++;
|
|
next_entry <<= 8;
|
|
next_entry |= buf[from++];
|
|
}
|
|
|
|
if (from >= end) // get more data
|
|
{
|
|
lnum++;
|
|
break;
|
|
}
|
|
|
|
if (!next_entry)
|
|
{ // we are past the last entry which means we found our metadata block but didn't find our entry
|
|
return(0);
|
|
}
|
|
|
|
to = 0;
|
|
state++;
|
|
// fall through
|
|
|
|
case 3: // get pointer to entry data
|
|
while((from < end) && (to < 4))
|
|
{
|
|
crc_calc = getCrc32(&buf[from], 1, crc_calc);
|
|
to++;
|
|
data_point <<= 8;
|
|
data_point |= buf[from++];
|
|
next_entry--;
|
|
}
|
|
|
|
if (from >= end) // get more data
|
|
{
|
|
lnum++;
|
|
break;
|
|
}
|
|
|
|
if (data_point > next_entry)
|
|
{
|
|
PRINT("ERROR!!! Data pointer greater than total entry size\n");
|
|
|
|
return(0);
|
|
}
|
|
|
|
to = 0;
|
|
state++;
|
|
// fall through
|
|
|
|
case 4: // get length of entry data
|
|
while((from < end) && (to < 4))
|
|
{
|
|
crc_calc = getCrc32(&buf[from], 1, crc_calc);
|
|
to++;
|
|
data_length <<= 8;
|
|
data_length |= buf[from++];
|
|
data_point--;
|
|
next_entry--;
|
|
}
|
|
|
|
if (from >= end) // get more data
|
|
{
|
|
lnum++;
|
|
break;
|
|
}
|
|
|
|
if (data_length > next_entry)
|
|
{
|
|
PRINT("ERROR!!! Data length greater than total entry size\n");
|
|
|
|
return(0);
|
|
}
|
|
|
|
to = 0;
|
|
state++;
|
|
// fall through
|
|
|
|
case 5: // check header crc
|
|
while((from < end) && (to < 4))
|
|
{
|
|
crc_grab = (crc_grab << 8) | buf[from++];
|
|
to++;
|
|
data_point--;
|
|
next_entry--;
|
|
}
|
|
|
|
if (from >= end) // get more data
|
|
{
|
|
lnum++;
|
|
break;
|
|
}
|
|
|
|
if (crc_calc != crc_grab)
|
|
return(0);
|
|
|
|
crc_calc = -1;
|
|
to = 0;
|
|
state++;
|
|
// fall through
|
|
|
|
case 6: // compare entry name to what we're searching for
|
|
while((from < end) && (buf[from] >= 0x20) && (name[to] >= 0x20) && (buf[from] == name[to]) && data_point)
|
|
{
|
|
crc_calc = getCrc32(&buf[from++], 1, crc_calc);
|
|
to++;
|
|
data_point--;
|
|
next_entry--;
|
|
}
|
|
|
|
if (from >= end) // get more data
|
|
{
|
|
lnum++;
|
|
break;
|
|
}
|
|
|
|
if ((buf[from] >= 0x20) || (name[to] >= 0x20))
|
|
{ // entry name doesn't match what we're searching for
|
|
state = 0;
|
|
goto CONT;
|
|
}
|
|
|
|
if (header < 0) // return data length if that's all that's requested
|
|
return(data_length);
|
|
|
|
state++;
|
|
// fall through
|
|
|
|
case 7: // move past entry name
|
|
while((from < end) && data_point)
|
|
{
|
|
crc_calc = getCrc32(&buf[from++], 1, crc_calc);
|
|
data_point--;
|
|
}
|
|
|
|
if (from >= end) // get more data
|
|
{
|
|
lnum++;
|
|
break;
|
|
}
|
|
|
|
if (header > 0)
|
|
{ // grab just the header if requested, for example just the cferam header info instead of the whole cferam
|
|
data_length = header;
|
|
|
|
if (data_length > next_entry)
|
|
{
|
|
PRINT("ERROR!!! Requested data header length greater than total entry size\n");
|
|
|
|
return(0);
|
|
}
|
|
}
|
|
|
|
to = 0;
|
|
state++;
|
|
// fall through
|
|
|
|
case 8: // get/put data
|
|
while((from < end) && data_length) // 4=crc length
|
|
{
|
|
#ifndef CFEROM
|
|
if (writeblk)
|
|
buf[from] = data[to];
|
|
else
|
|
#endif
|
|
data[to] = buf[from];
|
|
|
|
crc_calc = getCrc32(&buf[from++], 1, crc_calc);
|
|
|
|
to++;
|
|
data_length--;
|
|
}
|
|
#ifndef CFEROM
|
|
if (writeblk && to && (from >= end))
|
|
{ // update only if we touched data and are at the end of the block
|
|
if ((volume_id != -1) && (vid->vol_type == UBI_VID_STATIC) && vid->data_size)
|
|
{ // only update the CRC's if static volume, otherwise changing data CRC or size from zero on dynamic volume will cause kernel panic
|
|
vid->data_crc = be32_to_cpu(getCrc32((void *)buf, be32_to_cpu(vid->data_size), CRC_INIT));
|
|
vid->hdr_crc = be32_to_cpu(getCrc32((void *)vid, UBI_VID_HDR_SIZE-4, CRC_INIT));
|
|
}
|
|
|
|
if (eraseblk)
|
|
if (!eraseblk(i, blk_size, mtd, mtd_fd))
|
|
return(0);
|
|
|
|
if(!writeblk(start, i, 0, blk_size, buf, blk_size, mtd, mtd_fd))
|
|
return(0);
|
|
}
|
|
#endif
|
|
if (from >= end) // get more data
|
|
{
|
|
lnum++;
|
|
break;
|
|
}
|
|
|
|
status = to;
|
|
data_length = CRC_LENGTH;
|
|
state++;
|
|
// fall through
|
|
|
|
case 9: // check/put data crc
|
|
while((from < end) && data_length)
|
|
{
|
|
if (writeblk)
|
|
{
|
|
buf[from] = crc_calc >> 24;
|
|
crc_calc <<= 8;
|
|
}
|
|
else
|
|
{
|
|
crc_grab = (crc_grab << 8) | buf[from];
|
|
}
|
|
|
|
data_length--;
|
|
from++;
|
|
}
|
|
#ifndef CFEROM
|
|
if (writeblk)
|
|
{ // we fell through, write back since data would have been updated in previous state
|
|
if ((volume_id != -1) && (vid->vol_type == UBI_VID_STATIC) && vid->data_size)
|
|
{ // only update the CRC's if static volume, otherwise changing data CRC or size from zero on dynamic volume will cause kernel panic
|
|
vid->data_crc = be32_to_cpu(getCrc32((void *)buf, be32_to_cpu(vid->data_size), CRC_INIT));
|
|
vid->hdr_crc = be32_to_cpu(getCrc32((void *)vid, UBI_VID_HDR_SIZE-4, CRC_INIT));
|
|
}
|
|
|
|
if (eraseblk)
|
|
if (!eraseblk(i, blk_size, mtd, mtd_fd))
|
|
return(0);
|
|
|
|
if(!writeblk(start, i, 0, blk_size, buf, blk_size, mtd, mtd_fd))
|
|
return(0);
|
|
}
|
|
#endif
|
|
if (data_length) // get more data, but only if we need it
|
|
{
|
|
lnum++;
|
|
break;
|
|
}
|
|
#ifndef CFEROM
|
|
if (writeblk)
|
|
{
|
|
if (dataP) // return block which matches found entry
|
|
*dataP = (char *)start + (i * blk_size);
|
|
|
|
return(status);
|
|
}
|
|
else
|
|
#endif
|
|
if (crc_calc == crc_grab)
|
|
return(status);
|
|
else
|
|
return(0);
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (retry) // spin through blocks again in case they are out of order
|
|
{
|
|
retry = 0;
|
|
|
|
goto RETRY;
|
|
}
|
|
|
|
return(0);
|
|
}
|
|
|
|
|
|
|