/*
 * loadem: bare-bones load and execute something with kexec
 *
 * Copyright (C) 2004 - 2005 Milton D Miller II, IBM Corporation
 *
 * 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 (version 2 of the License).
 *
 * 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.
 */

#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdarg.h>
#include <malloc.h>

struct proper_kexec_segment {
	void *buf;
	size_t bufsz;
	size_t mem;
	size_t memsz;
};

struct kexec_segment {
	void *buf;
	size_t bufsz;
	void *mem;
	size_t memsz;
};

extern void __fix_the_kexec_segment_structures(void);

#include "kexec-syscall.h"

void err(char *what, char *which);
void use(char *why);

#define __str(x)  #x
#define str(x)  __str(x)

struct proper_kexec_segment seg[KEXEC_MAX_SEGMENTS];
unsigned long entry;
unsigned long flags;
unsigned long mask;



struct stat statbuf[1];

char *myname;

#include <errno.h>

int main(int argc, char *argv[])
{
	int f=-1, fd, t, arg;
	unsigned long *lp;
	char *cp;
	void *p;

	if (sizeof(struct proper_kexec_segment) != sizeof(struct kexec_segment))
		__fix_the_kexec_segment_structures();

	myname = argv[0];
	for (cp = myname; *cp; cp++)
		if (*cp == '/')
			myname = cp + 1;

	for(;;)
		switch (arg = getopt(argc, argv, "a:c:ef:kpr:st:z:48")) {
		case 'a': /* at Address */
			if (++f >= KEXEC_MAX_SEGMENTS)
				use("Maximum number of segments (" 
						str(KEXEC_MAX_SEGMENT)
						") exceeded.\n");
			seg[f].mem = strtoul(optarg, NULL, 0);
			break;
		case 'c': /* Chain previous */
			if (f < 1)
				use("-c: can not chain first file");

			lp = seg[f-1].buf + strtoul(optarg, NULL, 0);
			lp[-1] = seg[f].mem; /* Big endian, LE use 0 */
			break;
		case 'e': /* Entry point */
			entry = seg[f].mem + seg[f].memsz;
			break;
		case 'f': /* load File */
			fd = open(optarg, O_RDONLY);
			if (fd == -1)
				err("open", optarg);
			if (fstat(fd, statbuf))
				err("stat", optarg);
			t = statbuf[0].st_size;
			p = realloc(seg[f].buf, seg[f].bufsz + t);
			if (!p)
				err("malloc", optarg);
			seg[f].buf = p;
			if (read(fd, seg[f].buf + seg[f].bufsz, t) != t)
				err("read", optarg);
			close(fd);
			seg[f].bufsz += t;
			seg[f].memsz += t;
			break;
		case 'k': /* Kexec now */
			kexec_reboot();
			break;
		case 'p': /* load Panic kernel */
			flags |= KEXEC_FLAG_ON_PANIC;
			break;
		case 's': /* load Stdin */
#define CHUNK 4096
			t = 0;
			do {
				seg[f].bufsz += t;
				seg[f].memsz += t;
				seg[f].buf = realloc(seg[f].buf,
						seg[f].bufsz + CHUNK);
				if (!seg[f].buf)
					err("realloc","<stdin>");
				t = read(0, seg[f].buf + seg[f].bufsz, CHUNK);
				} while (t > 0);
			seg[f].buf = realloc(seg[f].buf, seg[f].bufsz);
			if (!seg[f].buf)
				err("realloc","<stdin>");
			break;
#undef CHUNK
		case '8': /* pad to 8 byte bounary */
		case '4': /* pad to 4 byte bounary */
			t = arg - '0';
			t -= seg[f].bufsz & (t-1);
			if (t == arg - '0')
				break;
			seg[f].buf = realloc(seg[f].buf, seg[f].bufsz + t);
			if (!seg[f].buf)
				err("malloc", "<align>");
			cp = seg[f].buf + seg[f].bufsz;
			seg[f].memsz += t;
			seg[f].bufsz += t;
			while (t--)
				*cp++ = 0;
			break;
		case 'r': /* Round up page size; move this to kernel */
			mask = strtoul(optarg, NULL, 0);
			if (mask & (mask-1))
				use("round value must be a power of 2\n");
			mask--; /* convert to a mask */
			break;
		case 't': /* set architecture Type */
			t = strtoul(optarg, NULL, 0);
			flags |= t << 16;
			break;
		case 'z': /* Zero extend segment by bumping memsz */
			seg[f].memsz += strtoul(optarg, NULL, 0);
			break;
		case -1:
			if (optind < argc)
				use("unexpected option or argument\n");
			for (t=0; t <= f; t++) { /* move this to the kernel */
				seg[t].memsz += mask;
				seg[t].memsz &= ~mask;
			}
			if (errno = -kexec_load((void *)entry, ++f, 
					(struct kexec_segment *)seg, flags))
				err("kexec", "syscall");
			exit(0);
		default:
			use("unknown option or argument\n");
		}
}

/* a bit of user frendliness, while still minimizing library usage for size */

#define eput(s) write(2, s, sizeof(s)-1)
#define vput(s) {for (l=0; s[l]; l++); write(2, s, l);}

void use(char *why)
{
	int l;

	eput("Usage: ");
	vput(myname);
	eput(" options\n"
			"\t where options are:\n"
			"\t-a address	start a new segment At address\n"
			"\t-c offset	at offset in previous segment"
					" put a Chain pointer to here\n"
			"\t-e		assign Entry point here\n"
			"\t-f file	load a File\n"
			"\t-k		kexec the new kernel NOW\n"
			"\t-p		load panic kernel\n"
			"\t-r size	round up segments memsz to size\n"
			"\t-s		read from Standard input\n"
			"\t-t type	set architecture flag to given number\n"
			"\t-4		4-byte align size, padding with 0\n"
			"\t-8		8-byte align size, padding with 0\n"
			"\t-z count	zero extend segment by count bytes"
					" after all files.\n");
	exit(1);
}

#include <errno.h>
#include <string.h>

#define TOP_NIB_SHIFT(x) (8*sizeof(x) - 4)
#define TOP_NIB(x) (x >> TOP_NIB_SHIFT(x))

void err(char *what, char *which)
{
	unsigned err = errno;
	int l, i;
	char erro[2*sizeof(err)+6];

	for (l=0; l < 2*sizeof(err); l++, err <<= 4)
		if (TOP_NIB(err))
			break;

	for(i=0; i < 2*sizeof(err) - l; i++, err <<= 4)
		erro[i+4] = "0123456789abcdef"[TOP_NIB(err)];
	erro[0] = ' ';
	erro[1] = '(';
	erro[2] = '0';
	erro[3] = 'x';
	erro[4+i+0] = ')';
	erro[4+i+1] = '\n';
	erro[4+i+2] = '\0';

	err = errno;	/* save again before the writes below */
	vput(myname);
	eput(": ");
	vput(what);
	eput(" ");
	vput(which);
	eput(": ");
	vput(strerror(err));
	vput(erro);

	exit(2);
}
