/*
** bpatch v1.2
**
** Binary patch
**
** Copyright (C) 11.11.96 by Andreas Ley <ley@rz.uni-karlsruhe.de>
**
** 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 program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
**
** This program has been tested on a HP9000/715 with HP-UX A.09.07
** In this environment, neither lint -u nor gcc -Wall -fsyntax-only produce
** any messages. If you encounter any errors or need to make any changes to
** port it to another platform, please contact me.
**
** Version history
**
** Version 1.2 - 28.9.1999
**	Added direct offset method
**
** Version 1.1 - 19.11.96
**	Fixed write bug on files whose size is not a multiple of bufsize
**
** Version 1.0 - 11.11.96
**	Initial version
*/

static char copyright[] = "@(#)Copyright (C) 1996,1999 by Andreas Ley (ley@rz.uni-karlsruhe.de)";
static char sccsid[] = "@(#)bpatch v1.2 - Binary patch";


#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>

#ifndef FALSE
#define	FALSE	(0)		/* This is the naked Truth */
#define	TRUE	(1)		/* and this is the Light */
#endif


#define	MAXPATCHES	1024	/* Max. number of patches */
#define	CHUNKSIZE	8192	/* Memory chunk size */

typedef struct patch_t {
	size_t	size;
	char	*addr;
	} patch_t;

char	*image;
patch_t	*old[MAXPATCHES],*new[MAXPATCHES];
int	debug=0,fill=-1,multiple=FALSE,patches=0;
size_t	maxpatchsize=0;


static patch_t *readfile(fname)
char	*fname;
{
	patch_t	*ptr;
	int	src;
	size_t	len,retval;

	if (debug)
		(void)fprintf(stderr,"readfile(\"%s\")\n",fname);

	if (!(ptr=(patch_t *)malloc(sizeof(patch_t)))) {
		(void)fprintf(stderr,"%s: can't malloc",image);
		perror("");
		return(NULL);
	}
	ptr->size=0;
	ptr->addr=NULL;
	len=0;

	if ((src=open(fname,O_RDONLY))<1) {
		(void)fprintf(stderr,"%s: can't open ",image);
		perror(fname);
		return(NULL);
	}
	do {
		if (ptr->size-len<CHUNKSIZE) {
			ptr->size+=CHUNKSIZE;
			if (!(ptr->addr=(char *)realloc((void *)ptr->addr,ptr->size))) {
				(void)fprintf(stderr,"%s: can't realloc while reading ",image);
				perror(fname);
				return(NULL);
			}
		}
		retval=read(src,(void *)(ptr->addr+len),ptr->size-len);
		if ((long)retval<0) {
			(void)fprintf(stderr,"%s: can't read ",image);
			perror(fname);
			return(NULL);
		}
		if (debug>2)
			(void)fprintf(stderr,"readfile: read %d bytes\n",retval);
		len+=retval;
	} while (retval);
	if (close(src)) {
		(void)fprintf(stderr,"%s: can't close ",image);
		perror(fname);
		return(NULL);
	}
	ptr->size=len;
	if (!(ptr->addr=(char *)realloc((void *)ptr->addr,ptr->size))) {
		(void)fprintf(stderr,"%s: can't realloc while reading ",image);
		perror(fname);
		return(NULL);
	}
	if (ptr->size>maxpatchsize)
		maxpatchsize=ptr->size;
	if (debug>2)
		(void)fprintf(stderr,"readfile(\"%s\")={%d,0x%08lx}\n",fname,ptr->size,(unsigned long)ptr->addr);
	return(ptr);
}



static int bpatch()
{
	size_t		bufsiz,buffsiz,len,wlen,retval,offset;
	unsigned char	*buffer,*ptr;
	int		patch;

	if (debug)
		(void)fprintf(stderr,"bpatch()\n");

	bufsiz=CHUNKSIZE;
	if (bufsiz<maxpatchsize)
		bufsiz=maxpatchsize+(8-maxpatchsize%8);
	buffsiz=bufsiz*2;
	if (debug>2)
		(void)fprintf(stderr,"bpatch: bufsiz=0x%08lx, buffsiz=0x%08lx\n",(unsigned long)bufsiz,(unsigned long)buffsiz);

	if (!(buffer=(unsigned char *)malloc(buffsiz))) {
		(void)fprintf(stderr,"%s: can't malloc",image);
		perror("");
		return(-1);
	}
	len=0;
	offset=0;

	do {
		/* Fill the buffer until no more data available */
		do {
			retval=read(0,(void *)(buffer+len),buffsiz-len);
			if ((long)retval<0) {
				(void)fprintf(stderr,"%s: can't read ",image);
				perror("stdin");
				return(-1);
			}
			if (debug>2)
				(void)fprintf(stderr,"bpatch: read %d bytes\n",retval);
			len+=retval;
		} while (retval>0&&len<buffsiz);
		/*
		**  Calculate lower buffer half
		*/
		wlen=bufsiz<len?bufsiz:len;
		/* patch in buffer */
		/*
		**  The buffer is at least twice as big as the biggest patch
		**  chunk, so if we locate the beginning of a patch chunk in
		**  the lower buffer half, the complete chunk is in core and
		**  can be patched in core.
		*/
		for (patch=0;patch<patches;patch++) {
			if (old[patch]->addr) {
				for (ptr=buffer;ptr-buffer<bufsiz&&(ptr=memchr((void *)ptr,*(old[patch]->addr),bufsiz-(ptr-buffer)));ptr++)
					if (!(memcmp((void *)ptr,(void *)old[patch]->addr,old[patch]->size))) {
						if (debug>1)
							(void)fprintf(stderr,"bpatch: found patch %d at 0x%08lx\n",patch,(unsigned long)ptr);
						(void)memcpy((void *)ptr,(void *)new[patch]->addr,new[patch]->size);
						ptr+=old[patch]->size-1;
					}
			}
			else {
				if (offset<=old[patch]->size&&old[patch]->size<offset+wlen&&new[patch]->size<len)
					(void)memcpy((void *)(buffer+(old[patch]->size-offset)),(void *)new[patch]->addr,new[patch]->size);
			}
		}
		/* write lower buffer half */
		retval=write(1,(void *)buffer,wlen);
		if (debug>2)
			(void)fprintf(stderr,"bpatch: wrote %d bytes\n",retval);
		if (retval<wlen) {
			(void)fprintf(stderr,"%s: can't write ",image);
			perror("stdout");
			return(-1);
		}
		/* move upper buffer half to lower buffer half */
		(void)memcpy((void *)buffer,(void *)(buffer+wlen),buffsiz-wlen);
		len-=wlen;
		offset+=wlen;
	} while (len>0);
	return(0);
}



static void usage()
{
	(void)fprintf(stderr,"Usage: %s [-0|-b] [-m] offset new-file [offset new-file...]\n",image);
	(void)fprintf(stderr,"   or: %s [-0|-b] [-m] old-file new-file [old-file new-file...]\n",image);
	(void)fprintf(stderr,"-0  If new < old, fill with zeros\n");
	(void)fprintf(stderr,"-b  If new < old, fill with blanks\n");
	(void)fprintf(stderr,"-m  Apply patch multiple times\n");
	(void)fprintf(stderr,"If old-file starts with a number, use ./old-file\n");
	(void)fprintf(stderr,"Original file on stdin, output on stdout\n");
	exit(1);
}


int main(argc,argv)
int	argc;
char	*argv[];
{
	int		c;
	extern char	*optarg;
	extern int	optind;

	if ((image=strrchr(argv[0],'/')))
		image++;
	else
		image=argv[0];

	while ((c=getopt(argc,argv,"D0bmvh?"))!=EOF)
		switch ((char)c) {
		case 'D':
			debug++;
			break;
		case '0':
			if (fill!=-1)
				usage();
			fill='\0';
			break;
		case 'b':
			if (fill!=-1)
				usage();
			fill=' ';
			break;
		case 'm':
			multiple=TRUE;
			break;
		case 'v':
			(void)fprintf(stderr,"%s\n",sccsid+4);
			(void)fprintf(stderr,"%s\n",copyright+4);
			exit(0);
		case 'h':
			(void)fprintf(stderr,"%s\n",sccsid+4);
			(void)fprintf(stderr,"%s\n",copyright+4);
		case '?':
			usage();
		}

	if (optind>=argc||(argc-optind)%2!=0)
		usage();

	while (optind<argc)
		if (*argv[optind]>='0'&&*argv[optind]<='9') {
			if (!(old[patches]=(patch_t *)malloc(sizeof(patch_t)))) {
				(void)fprintf(stderr,"%s: can't malloc",image);
				perror("");
				exit(1);
			}
			old[patches]->size=atol(argv[optind++]);
			old[patches]->addr=NULL;
			if ((new[patches]=readfile(argv[optind++])))
				patches++;
			else
				return(-1);
		}
		else
			if ((old[patches]=readfile(argv[optind++]))&&(new[patches]=readfile(argv[optind++])))
				patches++;
			else
				return(-1);

	if (bpatch())
		return(-1);

	return(0);
}

