2025-05-10 21:49:39 +08:00

379 lines
12 KiB
C

/*-
* Copyright 2003-2005 Colin Percival
* All rights reserved
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted providing that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#if 0
__FBSDID("$FreeBSD: src/usr.bin/bsdiff/bspatch/bspatch.c,v 1.1 2005/08/06 01:59:06 cperciva Exp $");
#endif
#include <bzlib.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <err.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <limits.h>
#include <errno.h>
#include <stdbool.h>
#include "md5sum.h"
#include "log.h"
static off_t offtin(unsigned char *buf)
{
off_t y;
y = buf[7] & 0x7F;
y = y * 256; y += buf[6];
y = y * 256; y += buf[5];
y = y * 256; y += buf[4];
y = y * 256; y += buf[3];
y = y * 256; y += buf[2];
y = y * 256; y += buf[1];
y = y * 256; y += buf[0];
if (buf[7] & 0x80) y = -y;
return y;
}
/* Return:
* -1 patching error,
* 0 not a diff img,
* >0 the new image size
* dst_file size if patch successfully
*/
int do_patch_rkimg(const char *img, ssize_t offset, ssize_t size,
const char *blk_dev, const char *dst_file)
{
#define TAIL_SIZE 80
#define TID_HEAD 0
#define TID_NAME 1
#define TID_OLD_SIZE 2
#define TID_NEW_SIZE 3
#define TID_MD5SUM 4
#define TOKENS 5
#define MAGIC_TAIL "DIFF"
#define MD5SUM_LEN 32
// For tail parsing.
// Tail size is 80 bytes as like,
// "DIFF:%-15s:%-12s:%-12s:%-32s:"
// $name $old_size $new_size $md5sum
int fd_img;
const char *split = ": "; //space is a split char too
char tail[TAIL_SIZE];
ssize_t len, ret;
ssize_t oldsize, newsize;
char *saveptr, *str, *name, *md5sum, *token[TOKENS];
int j;
// For patching
FILE * f = NULL, * cpf = NULL, * dpf = NULL, * epf = NULL;
BZFILE * cpfbz2, * dpfbz2, * epfbz2;
int cbz2err, dbz2err, ebz2err;
int fd;
ssize_t bzctrllen, bzdatalen;
unsigned char header[32], buf[8];
unsigned char *old = NULL, *new_ptr = NULL;
off_t oldpos, newpos;
off_t ctrl[3];
off_t lenread;
off_t i;
struct stat old_stat, dst_stat;
if ((fd_img = open(img, O_RDONLY, 0)) < 0) {
LOGE("open %s failed\n", img);
return -1;
}
if (lseek(fd_img, offset + size - TAIL_SIZE, SEEK_SET) < 0) {
LOGE("%s: lseek to: %ld failed: %s\n", img,
offset + size - TAIL_SIZE, strerror(errno));
close(fd_img);
return -1;
}
len = 0;
while (len != TAIL_SIZE) {
ret = read(fd_img, tail + len, TAIL_SIZE - len);
if (ret < 0) {
LOGE("read %s tail err\n", img);
close(fd_img);
return -1;
}
len += ret;
}
close(fd_img);
tail[TAIL_SIZE - 1] = '\0';
for (j = 0, str = tail; j < TOKENS; j++, str = NULL) {
token[j] = strtok_r(str, split, &saveptr);
if (token[j] == NULL)
break;
}
name = token[TID_NAME];
md5sum = token[TID_MD5SUM];
/* When unexpected reboot during patching/writing happened,
* if dst_file is in correct state, then old image may already broken
*/
if (stat(dst_file, &dst_stat) == 0 &&
compareMd5sum(dst_file, (unsigned char *)md5sum, 0, dst_stat.st_size)) {
LOGI("Recovery from unecptected reboot successfully.");
return dst_stat.st_size;
}
/* If dst_file exist but md5sum is wrong, old image file is clean, hopefully */
//check tail magic, return 0 if not exist
if (j == 0 || strncmp(MAGIC_TAIL, token[TID_HEAD], strlen(MAGIC_TAIL)) != 0) {
LOGW("Not a diff image, ret = %ld\n", ret);
return 0;
}
LOGI("This is a diff image, patching...\n");
if (j != TOKENS ||
(oldsize = strtol(token[TID_OLD_SIZE], &saveptr, 10)) == 0 ||
(errno == ERANGE && (oldsize == LONG_MAX || oldsize == LONG_MIN)) ||
saveptr == token[TID_OLD_SIZE] ||
(newsize = strtol(token[TID_NEW_SIZE], &saveptr, 10)) == 0 ||
(errno == ERANGE && (newsize == LONG_MAX || newsize == LONG_MIN)) ||
saveptr == token[TID_NEW_SIZE] ||
strlen(token[TID_MD5SUM]) != MD5SUM_LEN) {
LOGE("Bad Tail header of bsdiff patch\n");
return -1;
}
//TODO: check dst_file dir size, return -1 if space too small.
/* Open patch file */
if ((f = fopen(img, "r")) == NULL) {
LOGE("fopen %s err\n", img);
return -1;
}
if (fseeko(f, offset, SEEK_SET)) {
LOGE("fseeko %s err\n", img);
fclose(f);
return -1;
}
/*
File format:
0 8 "BSDIFF40"
8 8 X
16 8 Y
24 8 sizeof(newfile)
32 X bzip2(control block)
32+X Y bzip2(diff block)
32+X+Y ??? bzip2(extra block)
with control block a set of triples (x,y,z) meaning "add x bytes
from oldfile to x bytes from the diff block; copy y bytes from the
extra block; seek forwards in oldfile by z bytes".
*/
/* Read header */
if (fread(header, 1, 32, f) < 32) {
LOGE("Read header err\n");
fclose(f);
return -1;
}
fclose(f);
f = NULL;
/* Check for appropriate magic */
if (memcmp(header, "BSDIFF40", 8) != 0) {
LOGE("Bad header, Corrupt patch\n");
return -1;
}
/* Read lengths from header */
bzctrllen = offtin(header + 8);
bzdatalen = offtin(header + 16);
newsize = offtin(header + 24);
if ((bzctrllen < 0) || (bzdatalen < 0) || (newsize <= 0)) {
LOGE("Bad header len, Corrupt patch\n");
return -1;
}
/* re-open patch file via libbzip2 at the right places */
if ((cpf = fopen(img, "r")) == NULL)
return -1;
if (fseeko(cpf, offset + 32, SEEK_SET)) {
LOGE("fseeko(%s, %lld) err\n", img, (long long)(32 + offset));
goto cleanup;
}
if ((cpfbz2 = BZ2_bzReadOpen(&cbz2err, cpf, 0, 0, NULL, 0)) == NULL) {
LOGE("BZ2_bzReadOpen, bz2err = %d, err\n", cbz2err);
goto cleanup;
}
if ((dpf = fopen(img, "r")) == NULL)
goto cleanup;
if (fseeko(dpf, offset + 32 + bzctrllen, SEEK_SET)) {
LOGE("fseeko(%s, %lld) err\n", img,
(long long)(offset + 32 + bzctrllen));
goto cleanup;
}
if ((dpfbz2 = BZ2_bzReadOpen(&dbz2err, dpf, 0, 0, NULL, 0)) == NULL) {
LOGE("BZ2_bzReadOpen, bz2err = %d, err\n", dbz2err);
goto cleanup;
}
if ((epf = fopen(img, "r")) == NULL) {
LOGE("fopen(%s) err\n", img);
goto cleanup;
}
if (fseeko(epf, offset + 32 + bzctrllen + bzdatalen, SEEK_SET)) {
LOGE("fseeko(%s, %lld) err\n", img,
(long long)(offset + 32 + bzctrllen + bzdatalen));
goto cleanup;
}
if ((epfbz2 = BZ2_bzReadOpen(&ebz2err, epf, 0, 0, NULL, 0)) == NULL) {
LOGE("BZ2_bzReadOpen, bz2err = %d\n", ebz2err);
goto cleanup;
}
if (((fd = open(blk_dev, O_RDONLY, 0)) < 0) ||
((old = (unsigned char *)mmap(NULL, oldsize, PROT_READ,
MAP_SHARED | MAP_POPULATE, fd, 0)) == MAP_FAILED)) {
LOGE("open %s err\n", blk_dev);
goto cleanup;
}
close(fd);
fd = -1;
/* mmap the new file */
if (((fd = open(dst_file, O_CREAT | O_TRUNC | O_RDWR, 0666)) < 0) ||
(lseek(fd, newsize - 1, SEEK_SET) != (newsize - 1)) ||
(write(fd, "E", 1) != 1) ||
(lseek(fd, 0, SEEK_SET) != 0) ||
((new_ptr = (unsigned char *)mmap(NULL, newsize, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0)) == MAP_FAILED)) {
LOGE("mmap %s err\n", dst_file);
goto cleanup;
}
close(fd);
fd = -1;
oldpos = 0; newpos = 0;
while (newpos < newsize) {
/* Read control data */
for (i = 0; i <= 2; i++) {
lenread = BZ2_bzRead(&cbz2err, cpfbz2, buf, 8);
if ((lenread < 8) || ((cbz2err != BZ_OK) &&
(cbz2err != BZ_STREAM_END))) {
LOGE("Read control data: Corrupt patch\n");
goto cleanup;
}
ctrl[i] = offtin(buf);
};
/* Sanity-check */
if (newpos + ctrl[0] > newsize) {
LOGE("Sanity-check: Corrupt patch\n");
goto cleanup;
}
/* Read diff string */
lenread = BZ2_bzRead(&dbz2err, dpfbz2, new_ptr + newpos, ctrl[0]);
if ((lenread < ctrl[0]) ||
((dbz2err != BZ_OK) && (dbz2err != BZ_STREAM_END))) {
LOGE("Read diff string: Corrupt patch\n");
goto cleanup;
}
/* Add old data to diff string */
for (i = 0; i < ctrl[0]; i++)
if ((oldpos + i >= 0) && (oldpos + i < oldsize))
new_ptr[newpos + i] += old[oldpos + i];
/* Adjust pointers */
newpos += ctrl[0];
oldpos += ctrl[0];
/* Sanity-check */
if (newpos + ctrl[1] > newsize) {
LOGE("Sanity-check: Corrupt patch\n");
goto cleanup;
}
/* Read extra string */
lenread = BZ2_bzRead(&ebz2err, epfbz2, new_ptr + newpos, ctrl[1]);
if ((lenread < ctrl[1]) ||
((ebz2err != BZ_OK) && (ebz2err != BZ_STREAM_END))) {
LOGE("Read extra string: Corrupt patch\n");
goto cleanup;
}
/* Adjust pointers */
newpos += ctrl[1];
oldpos += ctrl[2];
};
/* Clean up the bzip2 reads */
BZ2_bzReadClose(&cbz2err, cpfbz2);
BZ2_bzReadClose(&dbz2err, dpfbz2);
BZ2_bzReadClose(&ebz2err, epfbz2);
fclose(cpf);
fclose(dpf);
fclose(epf);
munmap(new_ptr, newsize);
munmap(old, oldsize);
sync();
//check md5sum
if (!compareMd5sum(dst_file, (unsigned char *)md5sum, 0, newsize))
return -1;
LOGI("Diff patch apply successfully for %s, size: %ld\n",
name, newsize);
return newsize;
cleanup:
if (new_ptr != NULL)
munmap(new_ptr, newsize);
if (old != NULL)
munmap(old, oldsize);
if (fd >= 0)
close(fd);
if (cpf != NULL)
fclose(cpf);
if (dpf != NULL)
fclose(dpf);
if (epf != NULL)
fclose(epf);
return -1;
}
#if 0
int main(int argc, char *argv[])
{
do_patch_rkimg("./update.img", 676276, 16964, "OLD/Image/uboot.img", "uboot.img");
do_patch_rkimg("./update.img", 672180, 3589, "OLD/Image/trust.img", "trust.img");
do_patch_rkimg("./update.img", 743860, 532546, "OLD/Image/boot.img", "boot.img");
do_patch_rkimg("./update.img", 15630772, 88186412, "OLD/Image/rootfs.img", "rootfs.img");
return 0;
}
#endif