
/*

$Revision: 1.1 $
$Date: 2009/01/24 00:38:20 $


	binpatch is for binary patching of an a.out or any other
	file.

	Written by David B. Anderson.  October, 1996.
	Silicon Graphics.
	No restrictions of any kind on use or distribution of the
	source or binary.

	example:
		binpatch 1356 0xff32  0xef31 filetopatch
	which patches the two bytes which contain xff32 to contain
	the two bytes 0xef31 instead.

	All messages come out on standard output.
	Exit code is 0 in case of success. 
	Exits with a small positive number in case of failure.

	Usage:
	binpatch offset oldvalue newvalue filename

	where
	  offset is a file offset. 
		The offset can be plain decimal or it can
		be octal with a leading 0 or it can be
		hex with a leading 0x.
		examples:
				16
				0x10

	  oldvalue is a hex representation of as many bytes as are to
		be updated.  The representation must match exactly the
		bytes in the file being patched. In otherwords, this
		is a representation of the existing values in the file
		starting at offset 'offset'.
		A leading 0x (to emphasize the hex representation)
		is reqired.

	  newvalue is a hex representation of those same bytes, but
		here the value is the new value to be plugged into
		the file.

	  Both oldvalue and newvalue must have an even number of hex
	  	digits.  Both must have exactly the same number of
		digits and each digit must be 
		one of 0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F
		(after the required leading 0x)

	  filename is the name of the file to be changed.
		The file will be modified in place if and only if
		the oldvalue matches the appropriate bytes in
		the file and all the digits are valid and
		the oldvalue and newvalue are equal length.

	  The file must exist and be writable.


*/

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <alloca.h>
#include <malloc.h>

char *explanation[] = {
"        binpatch offset oldvalue newvalue filename",
"",
"        where",
"          offset is a file offset.",
"                The offset can be plain decimal or it can",
"                be octal with a leading 0 or it can be",
"                hex with a leading 0x.",
"                examples:",
"                                16",
"                                0x10",
"",
"          oldvalue is a hex representation of as many bytes as are to",
"                be updated.  The representation must match exactly the",
"                bytes in the file being patched. In otherwords, this",
"                is a representation of the existing values in the file",
"                starting at offset 'offset'.",
"                A leading 0x (to emphasize the hex representation)",
"                is reqired.",
"",
"          newvalue is a hex representation of those same bytes, but",
"                here the value is the new value to be plugged into",
"                the file.",
"",
"          Both oldvalue and newvalue must have an even number of hex",
"                digits.  Both must have exactly the same number of",
"                digits and each digit must be",
"                one of 0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F",
"                (after the required leading 0x)",
"",
"          filename is the name of the file to be changed.",
"                The file will be modified in place if and only if",
"                the oldvalue matches the appropriate bytes in",
"                the file and all the digits are valid and",
"                the oldvalue and newvalue are equal length.",
"",
"          The file must exist and be writable.",
0
};


static void usage(void)
{
  int i;
  printf("Usage:  \n");
  for (i = 0; explanation[i]; ++i) {
	printf("%s\n",explanation[i]);
  }

}
static void updatethefile(void);

static char *offstring;
off_t fileoff;
static char *oldvalstring;
static char *old_val_bytes;
static char *newvalstring;
static int  bytes_to_update;
static char *new_val_bytes;
static char *filenamestring;


static void do_the_update(int filefd);
static void verify_the_args(void);
static void validatehexstr(char *instring, char *msg,char *target);


int main(int argc, char **argv) 

{
	if(argc != 5) {
		usage();
		exit(1);
	}
	offstring = argv[1];
	oldvalstring = argv[2];
	newvalstring = argv[3];
	filenamestring = argv[4];
	verify_the_args();
	updatethefile();
	return 0;
}

/*
verify the arguments and create the internal form of each
*/
static void verify_the_args(void)
{
	char * endptr = 0;
	int slen1;
	int slen2;

	errno = 0;
	fileoff = strtoul(offstring, &endptr,0);
	if(fileoff == 0 && errno != 0) {
		printf("offset is not a valid number\n");
		printf("Nothing done\n");
		exit(2);
	}
	if(endptr != offstring + strlen(offstring)) {
		printf("offset had some unrecognizable non-digit characters\n");
		printf("Nothing done\n");
		exit(2);
	}

	slen1 = strlen(oldvalstring);
	slen2 = strlen(newvalstring);


	if (slen1 != slen2) {
	  printf(" oldvalue had %d characters, newvalue had %d characters.\n",
			slen1,slen2);
		printf("Nothing done\n");
		exit(2);
	}
	if(slen1 < 3) {
	  printf(
	   "oldvalue is not a valid hex value starting with 0x (is %s )\n",
		oldvalstring);
		printf("Nothing done\n");
		exit(2);
	}


	bytes_to_update = (slen1-2)/2;

	old_val_bytes = malloc(bytes_to_update);
	if(old_val_bytes == 0) {
	  printf("%d bytes could not be malloc'd.\n",bytes_to_update);
	  printf("Nothing done\n");
          exit(2);
	}
	new_val_bytes = malloc(bytes_to_update);
	if(new_val_bytes == 0) {
	  printf("%d bytes could not be malloc'd..\n",bytes_to_update);
	  printf("Nothing done\n");
          exit(2);
	}


	validatehexstr(oldvalstring,"oldvalue",old_val_bytes);
	validatehexstr(newvalstring,"newvalue",new_val_bytes);


	

	
	
}
static void updatethefile(void)
{
	int filefd;
	int res;

	filefd = open(filenamestring,O_RDWR,0);
	if(filefd < 0) {
		int myerr = errno;
		printf("Cannot open %s read/write. %s (errno %d)\n",
				filenamestring,
				strerror(myerr),myerr);
                printf("Nothing done\n");
		exit(1);
	}

	
	do_the_update(filefd);

	
	res = close(filefd);
	if(res != 0) {
		int myerr = errno;
		printf("Cannot close %s -- %s (errno %d)\n",
				filenamestring,
				strerror(myerr),myerr);
		exit(1);
	}

}

/* verify that there is a match with oldvalue and, if present,
   write in the newvalue stuff.

*/
static void do_the_update(int filefd)
{

	char *filebuffer;	
	size_t readres;
	size_t bytes_to_write;
	size_t total_written = 0;
	char *outbuff;
	off_t res_off;
	int i;
	int res;

	bytes_to_write = bytes_to_update;

	filebuffer = alloca(bytes_to_write);
        res_off = lseek(filefd,fileoff,SEEK_SET);
        if(res == (off_t)-1) {
                int myerr = errno;
                printf("Cannot lseek to %lu in  %s -- %s (errno %d)\n",
                                (unsigned long)fileoff,
                                filenamestring,
                                strerror(myerr),myerr);
                printf("Nothing done\n");

                exit(1);
        }



	readres = read(filefd,filebuffer,bytes_to_write);

	if(readres == (size_t)-1) {
	  int myerr = errno;
	  printf("Read  %lu bytes failed %s (errno %d)\n",
			(unsigned long)bytes_to_write,
			strerror(myerr),myerr);
			
	  printf("Nothing done\n");
	  exit(3);
        }
	if(readres != bytes_to_write) {
	  printf("Read %lu bytes, not the %lu requested to update\n",
			(unsigned long)readres,(unsigned long)bytes_to_write);
	  printf("Nothing done\n");
	  exit(3);
	}
	
	for(i = 0; i < bytes_to_write; ++i) {
		if(filebuffer[i] != old_val_bytes[i]) {
			printf("old value does not match file value "
			 "in byte %lu of the bytes you are attempting "
			 "to change (file byte %ld)\n",
				(unsigned long)i,
				(unsigned long)(fileoff + i));
			printf("file has 0x%x, old_val_bytes has 0x%x\n",
				(int)filebuffer[i]&0xff,
				(int)old_val_bytes[i]&0xff);
			printf("Nothing done\n");
			exit(3);
		}
	}
	
	
	/* we passed the tests. */
        res_off = lseek(filefd,fileoff,SEEK_SET);
        if(res == (off_t)-1) {
                int myerr = errno;
                printf("Cannot re-lseek to %lu in  %s -- %s (errno %d)\n",
                                (unsigned long)fileoff,
                                filenamestring,
                                strerror(myerr),myerr);
                printf("Nothing done\n");

                exit(1);
        }

	/* write out the new bytes */
	outbuff = new_val_bytes;
	while(bytes_to_write > 0 ) {
	    errno = 0;
	    readres = write(filefd,outbuff,bytes_to_write);
	    if(readres < 0) {
		int myerr = errno;
		printf("Write failed on %lu bytes: %s (%d)\n",
			(unsigned long)bytes_to_write,
			strerror(myerr), myerr);
	 	if(total_written > 0) {
			printf("Wrote %lu bytes\n",
			(unsigned long) total_written);
		} else {
			printf("Nothing done.\n");
		}
		
			
	    }
	    if(readres == 0) {
		printf("Zero length write: giving up\n");
	 	if(total_written > 0) {
			printf("Wrote %lu bytes\n",
			(unsigned long) total_written);
		} else {
			printf("Nothing done.\n");
		}
	    }
	    bytes_to_write -= readres;
	    outbuff += readres;
	    total_written += readres;
	}
	
}

/*
	precondition: input chars must be valid hex.
	Returns int with low 4 bits being the nibble for the char.
*/
static int 
makehex(int in)
{

	if(isdigit(in)) {
		return in - '0';
	}
	if(isupper(in)) {
	  return in = 'A' + 10;
	}
	return  in - 'a' + 10;
}
/*
	Ensure string is
	0x followed by hex digits (an even number of them)
*/
static void 
validatehexstr(char *instring, char *msg, char *bytebuff)
{
	int len;
	char *curloc;

	if(instring[0] != '0') {
		printf("%s %s does not begin with 0x\n",msg,instring);
		printf("Nothing done\n");
		exit(3);
	}

	if(instring[1] != 'x' && instring[1] != 'X') {
		printf("%s %s does not begin with 0x\n",msg,instring);
		printf("Nothing done\n");
		exit(3);
	}

	len = strlen(instring);

	if((len&1) != 0) {
		printf(" %s%s does not have an even number of hex digits\n",
		msg,instring);
		printf("Nothing done\n");
		exit(3);
	}


	curloc = instring+2;
	
	for( ; *curloc ; curloc+= 2,++bytebuff) {
		if(!isxdigit(curloc[0])) {
			printf(" Non-hex digit %c found in %s\n",
				curloc[0],msg);
			printf("Nothing done\n");
			exit(3);
		}
		if(!isxdigit(curloc[1])) {
			printf(" Non-hex digit %c found in %s\n",
				curloc[0],msg);
			printf("Nothing done\n");
			exit(3);
		}
		bytebuff[0] = (makehex(curloc[0])<<4) | makehex(curloc[1]);
	}
		

	return;
	
	
}

