#include	"stddef.h"
#include	"string.h"
#include	"printf.h"
#include	"ansiesc.h"
#include	"misc.h"
#include	"linux-asm-io.h"
#include	"etherboot.h"
#include	"startmenu.h"
#include	"elf_boot.h"
#include	"bootmenu.h"

/*
 * 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, or (at
 * your option) any later version.
 */

/*

This is an example program which shows how the extension routine
feature in Etherboot 5.0 works.

This program is linked to run at 0x60000, and expects to find config
data if any at 0x70000. This means the code can be up to 64kB long.

When the program starts it receives 3 parameters from Etherboot:

Pointer to ebinfo structure
Pointer to image header structure (either a tagged or ELF image header)
Pointer to bootp/DHCP reply obtained by Etherboot from bootpd or DHCPD

Etherboot expects this program to return an int. The values have these
meanings:

<0	Do not use
0	Same as 1, for implementation reasons
1	Redo tftp with possibly modified bootp record
2	Redo bootp and tftp
255	Exit Etherboot

Observe that this program causes Etherboot to load a different program
next by modifying the contents of the filename field in the bootp record
and then returning 1. It can also send parameters to the next program by
modifying tag 129 in the bootp record.

*/

/*

Memory layout assumed by mknbi and this program

0x60000-0x6FFFF    64 kB	Menu program
0x70000-0x7FFFF    64 kB	Menu data (initial)

*/

static unsigned char	*vendortags;
static unsigned char	*end_of_rfc1533;
#ifdef IMAGE_FREEBSD
	/* yes this is a pain FreeBSD uses this for swap, however,
	   there are cases when you don't want swap and then
	   you want this set to get the extra features so lets
	   just set if dealing with FreeBSD.  I haven't run into
	   any troubles with this but I have without it
	*/
static int vendorext_is_valid = 1;
#else
static int vendorext_is_valid = 0;
#endif
static unsigned char	*motd[RFC1533_VENDOR_NUMOFMOTD] = { 0 };
static unsigned char	*imagelist[RFC1533_VENDOR_NUMOFIMG] = { 0 };

static inline void checkvendor(void)
{
	union {
		unsigned long	l;
		unsigned char	c[4];
	} u;

        memcpy(u.c, vendortags, sizeof(u));
        if (u.l == RFC_1048 || u.l == VEND_CMU || u.l == VEND_STAN)
                vendortags += 4;
        else
                vendortags = 0;
}

static void parsebootp()
{
	unsigned char		*p;
	unsigned int		c;
	static unsigned char	vendorext_magic[] = {0xE4,0x45,0x74,0x68}; /* äEth */

	memset(motd, 0, sizeof(motd));
	memset(imagelist, 0, sizeof(imagelist));
	if (vendortags == 0)
		return;
	for (p = vendortags; (c = *p) != RFC1533_END; ) {
		if (c == RFC1533_PAD) {
			p++;
			continue;
		}
#if	DEBUG > 1
		printf("Tag %d\n", c);
#endif
		switch (c) {
		case RFC1533_VENDOR_MAGIC:
			if (TAG_LEN(p) >= 6
				&& !memcmp(p+2, vendorext_magic, 4)
				&& p[6] == RFC1533_VENDOR_MAJOR)
				vendorext_is_valid = 1;
			break;
		case RFC1533_VENDOR_MENUOPTS:
			parse_menuopts(p+2, TAG_LEN(p));
			break;
		default:
			if (c >= RFC1533_VENDOR_MOTD && c < RFC1533_VENDOR_MOTD + RFC1533_VENDOR_NUMOFMOTD)
				motd[c-RFC1533_VENDOR_MOTD] = p;
			else if (c >= RFC1533_VENDOR_IMG && c < RFC1533_VENDOR_IMG + RFC1533_VENDOR_NUMOFIMG) 
				imagelist[c-RFC1533_VENDOR_IMG] = p;
			break;
		}
		p += p[1] + 2;
	}
	end_of_rfc1533 = p;
}

static void parse_elf_boot_notes(
	void *notes, union infoblock **rheader, struct bootpd_t **rbootp)
{
	unsigned char *note, *end;
	Elf_Bhdr *bhdr;
	Elf_Nhdr *hdr;

	bhdr = notes;
	if (bhdr->b_signature != ELF_BHDR_MAGIC) {
		return;
	}

	note = ((char *)bhdr) + sizeof(*bhdr);
	end  = ((char *)bhdr) + bhdr->b_size;
	while (note < end) {
		unsigned char *n_name, *n_desc, *next;
		hdr = (Elf_Nhdr *)note;
		n_name = note + sizeof(*hdr);
		n_desc = n_name + ((hdr->n_namesz + 3) & ~3);
		next = n_desc + ((hdr->n_descsz + 3) & ~3);
		if (next > end) 
			break;
#if 0
		printf("n_type: %x n_name(%d): n_desc(%d): \n", 
			hdr->n_type, hdr->n_namesz, hdr->n_descsz);
#endif

		if ((hdr->n_namesz == 10) &&
			(memcmp(n_name, "Etherboot", 10) == 0)) {
			switch(hdr->n_type) {
			case EB_BOOTP_DATA:
				*rbootp = *((void **)n_desc);
				break;
			case EB_HEADER:
				*rheader = *((void **)n_desc);
				break;
			default:
				break;
			}
		}
		note = next;
	}
}

int menu(struct ebinfo *eb, union infoblock *header, struct bootpd_t *bootp)
{
	int		i;
	extern int	serial_init(void);
	extern void	serial_fini(void);

#ifdef	DEBUG
	printf(MKNBI_VERSION "\n");
#endif
	parse_elf_boot_notes(eb, &header, &bootp);
	/* Sanity check */
	if (header->img.magic != ELF_MAGIC && header->img.magic != TAG_MAGIC) {
		printf("Bad argument passed from Etherboot\n");
		return (255);
	}
	vendortags = (unsigned char *)bootp->bootp_reply.bp_vend;
	checkvendor();
	parsebootp();
	if (!vendorext_is_valid) {
		printf("No menu vendor tags found, returning to Etherboot\n");
		sleep(10);
		return(2);
	}
#ifdef CONSOLE_SERIAL
	serial_init();
#endif
#ifdef	ANSIESC
	ansi_reset();
#endif
	show_motd(motd);
	i = selectImage(bootp, imagelist, end_of_rfc1533);
	if (i == 2) {
		printf("No selection, returning to Etherboot\n");
		sleep(10);
	}
#ifdef CONSOLE_SERIAL
	serial_fini();
#endif
	return (i);
}


syntax highlighted by Code2HTML, v. 0.9.1