379 lines
12 KiB
C
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
|