#ifndef lint
static char *sccsid = "@(#)dmagic.c	1.2 (UKC) 10/1/87";
#endif  lint
/*
 *	dmagic: change all NMAGIC binaries to ZMAGIC for greater system
 *	efficiency.
 *
 *	Usage: dmagic [-n] file [file] ...
 *
 *	-n	change all ZMAGIC files to NMAGIC.
 *	-z	change NMAGIC to ZMAGIC (the default)
 *
 *	If a file in the argument list is a directory, do a recursive descent
 *	from that point changing the magic numbers in all [NZ]MAGIC files found.
 *	I only ever touch files which are executable and are NMAGIC or ZMAGIC.
 *
 *	The names of files which were converted emerges on stdout, one per line.
 *
 *	Effects on paging:
 * 	OMAGIC: writable text segment, otherwise as NMAGIC.
 *
 *	NMAGIC:	header and text and data segments follow each other
 *	immediately. Text is copied into memory and swap when prog is
 *	started ("preloaded").
 *
 *	ZMAGIC:	text and data begin on 4096-byte boundaries. Text is swapped
 *	directly from the disk blocks of the binary. Object files are bigger,
 *	but less disk I/O is required at startup, partly because some pages
 *	may not be loaded, but also because the text is not copied into swap.
 *
 *	Observations:
 *	the text and data segments of NMAGIC and ZMAGIC files are identical,
 *	just in different places. OMAGIC actually contains slightly different
 *	code.
 *	.o files are OMAGIC. (and not executable anyway)
 *
 *	Turning an NMAGIC into ZMAGIC and then back into NMAGIC is not a no-op
 *	because the sizes of the text and data segments increase to a 4096-byte
 *	boundary.
 *
 *	Martin Guy, UKC, November 1986
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <a.out.h>
#include <signal.h>

long lseek();

int deltamagic();
int coredump();

char *progname;
int debug = 0;
long newmagic = ZMAGIC;		/* what to change the magic number to */

main(argc,argv)
char **argv;
{
	register int i;

	progname = argv[0];

	/* It's a real bugger trying to find the core dumps from this thing! */
	(void) signal(SIGSEGV, coredump);
	(void) signal(SIGFPE, coredump);
	(void) signal(SIGBUS, coredump);

	for (i=1; i<argc; i++) {
		if (argv[i][0] == '-') {
			switch(argv[i][1]) {
			case 'd':
				debug = 1;
				break;
			case 'n':
				newmagic = NMAGIC;
				break;
			case 'z':
				newmagic = ZMAGIC;
				break;
			default:
				fprintf(stderr, "%s: Unknown option \"%s\".\n", progname, argv[i]);
				break;
			}
		} else {
			switch (descend(argv[i], deltamagic)) {
			case -1:
				fprintf(stderr, "%s: Search from \"%s\" aborted.", progname, argv[i]);
				exit(1);
				break;
			}
		}
	}

	exit(0);
}

int
coredump(sig)
int sig;
{
	(void) chdir("/tmp");
	(void) signal(sig, SIG_DFL);
	kill(getpid(), sig);
	abort();
}

/*
 * Arguments to fn are:
 * name of file relative to cwd.
 * full name of file from starting directory.
 * pointer to stat structure for that file.
 *	(may be NULL if not stattable)
 */
int
deltamagic(name, fullname, stbuf)
char *name;
char *fullname;
struct stat *stbuf;
{
	/*
	 * Structures used to read in, pad and write out the executable file.
	 */

#	define BLKSIZ 4096 /* size to which text and data have to be aligned */
#	define ROUNDUP(x) ((x)+(BLKSIZ-1) & ~(BLKSIZ-1))

	/* somewhere for the header, with 0 padding for writing ZMAGIC */
	static union {
		struct exec header;
		char pad[BLKSIZ];
	} u;

	static char *tbuf;	/* somewhere to put the text segment */
	static char *dbuf;	/* and the data segment */

	static int tbufsiz = 0;	/* size of buffer pointed to by tbuf */
	static int dbufsiz = 0;	/* and by dbuf */
	long oldmagic;		/* the magic number from the file */
	int oldtsiz, olddsiz;	/* size of text and data segments */
	int newtsiz, newdsiz;	/* and when padded out to block boundary */
	long oldhsiz, newhsiz;	/* size of a.out header (long 'cos lseeked to) */
	static char *sbuf;	/* symbol table etc */
	static int sbufsiz = 0;
	int ssiz;		/* size of symbol table etc */

	register int fd;	/* fd open to the executable file */

	/* somewhere to put the header */

	if (stbuf == NULL) return(0);

	/* don't open it unless it's a likely binary on the info we have */

	/* binaries have to be regular files */
	if ((stbuf->st_mode & S_IFMT) != S_IFREG) return(0);

	/* and be executable */
	if (!(stbuf->st_mode & 0111)) return(0);

	/* and of a certain size */
	if (stbuf->st_size < sizeof(struct exec)) return(0);

	/* open it and inspect its magic number */
	fd = open(name, 2);	/* read and write */
	if (fd < 0) {
		fprintf(stderr, "%s: Cannot open ", progname);
		perror(fullname);
		return(0);
	}

	if (read(fd, (char *) &u.header, sizeof(struct exec)) != sizeof(struct exec)) {
		(void) close(fd);
		fprintf(stderr, "%s: cannot read header from ", progname);
		perror(fullname);
		return(0);
	}

	oldmagic = u.header.a_magic;
	switch (oldmagic) {
	case NMAGIC:
	case ZMAGIC:
		if (newmagic != oldmagic) break; /* to rest of code */
		/* otherwise magic is already right so drop through & return */
	default:
		/* includes OMAGIC, which I don't know what to do about */
		(void) close(fd);
		return(0);
	}

	/* check that there is no relocation info */
	if (u.header.a_trsize != 0 || u.header.a_drsize != 0) {
		fprintf(stderr, "%s: %s is not fully loaded.\n", progname, fullname);
		(void) close(fd);
		return(0);
	}

	/* We now definitely want to change the magic number. */

	switch (oldmagic) {
	case NMAGIC:
		oldhsiz = sizeof(struct exec);
		break;
	case ZMAGIC:
		oldhsiz = BLKSIZ;
		break;
	default:
		abort("oldmagic switch");
	}

	/* extract interestiong info from header */
	oldtsiz = u.header.a_text;
	olddsiz = u.header.a_data;
	ssiz = stbuf->st_size - oldhsiz - oldtsiz - olddsiz;
	if (ssiz < 0) {
		fprintf(stderr, "%s: Corrupt binary %s.\n", progname, fullname);
		return(0);
	}

	switch (newmagic) {
	case ZMAGIC:
		/* round sizes up to block boundary. Assumes BLKSIZ is power of 2 */
		newtsiz = ROUNDUP(oldtsiz);
		newdsiz = ROUNDUP(olddsiz);
		newhsiz = BLKSIZ;
		break;
	case NMAGIC:
		newtsiz = oldtsiz;
		newdsiz = olddsiz;
		newhsiz = sizeof(struct exec);
		break;
	default:
		abort("newmagic switch");
	}

	if (debug) {
		fprintf(stderr,
		"%s:\noldmagic=0%O oldhsiz=%d oldtsiz=%d olddsiz=%d ssiz=%d\n",
		fullname, oldmagic, oldhsiz, oldtsiz, olddsiz, ssiz);
		fprintf(stderr, "newmagic=0%O newhsiz=%d newtsiz=%d newdsiz=%d\n",
		newmagic, newhsiz, newtsiz, newdsiz);
	}

	/* make sure the buffers are big enough */
	if (!expandbuf(&tbuf, &tbufsiz, newtsiz) ||
	    !expandbuf(&dbuf, &dbufsiz, newdsiz) ||
	    (ssiz > 0 ? !expandbuf(&sbuf, &sbufsiz, ssiz) : 0)
	    ) {
		fprintf(stderr, "%s: Out of memory on %s.\n", progname, fullname);
		return(-1);	/* abort search */
	}

	/* read in segments */
	if (	lseek(fd, oldhsiz, 0) != oldhsiz ||
		read(fd, tbuf, oldtsiz) != oldtsiz ||
		read(fd, dbuf, olddsiz) != olddsiz ||
		(ssiz > 0 ? read(fd, sbuf, ssiz) != ssiz : 0)
	    ) {
		fprintf(stderr, "%s: Error reading from ", progname);
		perror(fullname);
		(void) close(fd);
		return(0);
	}

	/* modify header */
	u.header.a_magic = newmagic;
	u.header.a_text = newtsiz;
	u.header.a_data = newdsiz;

	if (newmagic == ZMAGIC) {
		/* ensure padding is nullified */
		bzero(tbuf+oldtsiz, newtsiz - oldtsiz);
		bzero(dbuf+olddsiz, newdsiz - olddsiz);
	}

	/* write out new file */
	/* file will shrink if going from Z to N */
	if (newmagic == NMAGIC && ftruncate(fd, 0) != 0) {
		fprintf(stderr, "%s: Cannot truncate", progname);
		perror(fullname);
	}

	if (lseek(fd, 0L, 0) != 0) {
		fprintf(stderr, "%s: Cannot rewind", progname);
		perror(fullname);
		close(fd);
		return(0);
	}

	if (	write(fd, u.pad, newhsiz) != newhsiz ||
		write(fd, tbuf, newtsiz) != newtsiz ||
		write(fd, dbuf, newdsiz) != newdsiz ||
		(ssiz > 0 ? write(fd, sbuf, ssiz) != ssiz : 0) ) {
		fprintf(stderr, "%s: Error rewriting ", progname);
		perror(fullname);
		(void) close(fd);
		return(-1);
	}
	
	close(fd);
	puts(fullname);
	return(1);
}

/*
 *	Ensure char buffer is big enough. modifies buf pointer and size.
 *	returns 1 on success, 0 on failure.
 */
expandbuf(bufp, oldsizp, newsiz)
char **bufp;
int *oldsizp;
{
	int oldsiz = *oldsizp;
	char *buf = *bufp;
	char *malloc(), *realloc();

	if (newsiz <= oldsiz) return(1);	/* already big enough */

	if (buf == NULL) {
		/* new buffer */
		buf = malloc((unsigned) newsiz);
	} else {
		buf = realloc(buf, (unsigned)newsiz);
	}
	if (buf == NULL) return(0);	/* out of memory */

	*bufp = buf; *oldsizp = newsiz;
	return(1);
}
