Bring up to date with 1.4.4 (belatedly)
[mknbi.git] / menu-simple.c
1 #include        "stddef.h"
2 #include        "string.h"
3 #include        "linux-asm-io.h"
4 #include        "string.h"
5 #include        "etherboot.h"
6 #include        "startmenu.h"
7 #include        "elf_boot.h"
8
9 /*
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License as
12  * published by the Free Software Foundation; either version 2, or (at
13  * your option) any later version.
14  */
15
16 /*
17
18 This is an example program which shows how the extension routine
19 feature in Etherboot 5.0 works.
20
21 This program presents a list of valid boot images from a data segment
22 that is loaded into another area of memory, and prompts the user for a
23 number, and modifies the bootp record to the filename to be loaded.  You
24 can make the menu program as elaborate as you like, the sky is the
25 limit.
26
27 Ideally, there should be a menu generation program that takes a
28 high-level description of menus and valid inputs and creates a data
29 file to be loaded to the data area. The menu program should agree with
30 the menu generator on the layout of the data area.
31
32 This program is linked to run at 0x60000, and expects to find config
33 data at 0x70000. This means the code can be up to 64kB long.
34
35 When the program starts it receives 3 parameters from Etherboot:
36
37 Pointer to ebinfo structure
38 Pointer to image header structure (either a tagged or ELF image header)
39 Pointer to bootp/DHCP reply obtained by Etherboot from bootpd or DHCPD
40
41 Etherboot expects this program to return an int. The values have these
42 meanings:
43
44 <0      Do not use
45 0       Same as 1, for implementation reasons
46 1       Redo tftp with possibly modified bootp record
47 2       Redo bootp and tftp
48 255     Exit Etherboot
49
50 Observe that this program causes Etherboot to load a different program
51 next by modifying the contents of the filename field in the bootp record
52 and then returning 1. It can also send parameters to the next program by
53 modifying tag 129 in the bootp record. This is how the menu system
54 works.
55
56 The data segment that this particular program expects is of the form
57
58         choice 1\nchoice 2\nchoice 3\n\0
59
60 where the \n are newlines and the \0 the teminating zero byte. Therefore
61 you can create this file with a Unix text editor (but see next
62 paragraph). choice 1, etc are the pathnames or filenames of the next
63 file to load by TFTP. If the string starts with / then it's assumed to
64 be a pathname and the whole of the bootp filename area is replaced by
65 it, otherwise just the filename portion of the pathname, i.e. the same
66 directory as the menu file is assumed.
67
68 This program also illustrates the use of a timeout to select a default
69 item by using currticks() to obtain the value of the BIOS clock and
70 console_ischar to determine if a character has been typed at the
71 keyboard.
72
73 Commentary: This program is just to illustrate a very simple menu
74 system.  There are known bugs:
75
76 mknbi-menu/mkelf-menu does not add the ending NUL byte, but this is
77 present due to the NUL fill to the next block boundary. If the size of
78 the data goes exactly up to a block boundary, then it is possible there
79 will be a spurious final item that goes up the next NUL byte in memory.
80
81 Another bug is that there is no overflow checking when writing into
82 bootp->bp_file.
83
84 Yet another bug is that there is no facility to correct input entry on
85 lines. getline() should be smarter.
86
87 */
88
89 /*
90
91 Memory layout assumed by mknbi and this program
92
93 0x60000-0x6FFFF    64 kB        Menu program
94 0x70000-0x7FFFF    64 kB        Menu data (initial)
95
96 */
97
98 #define TIMEOUT         10                      /* seconds */
99 #define MENU_DATA       ((char *)0x70000)
100
101 static char     *items[10];
102
103 extern void printf(const char *, ...);
104 extern void ansi_putc(unsigned int);
105
106 void putchar(int c)
107 {
108         if (c == '\n')
109                 ansi_putc('\r');
110         ansi_putc(c);
111 }
112
113 int getchar(void)
114 {
115         int     c;
116
117         c = console_getc();
118         if (c == '\r')
119                 c = '\n';
120         return (c);
121 }
122
123 /*
124  *      Get a line, ignore characters after array limit reached.
125  *      Echo the character if the flag says so.
126  */
127 int getline(char *line, int length, int echo)
128 {
129         int     c;
130         char    *p = line;
131
132         while ((c = getchar()) != '\n') {
133                 if (p < &line[length-1]) {      /* within array? */
134                         if (echo)
135                                 ansi_putc(c);
136                         *p++ = c;
137                 }
138         }
139         *p++ = '\0';
140         if (echo)
141                 ansi_putc('\n');
142         return (line - p);
143 }
144
145 int scan_items(void)
146 {
147         int             i;
148         char            *p;
149
150         for (i = 1, p = MENU_DATA; i < 10 && *p != '\0'; i++) {
151                 items[i] = p;
152                 while (*p != '\0' && *p != '\n')
153                         p++;
154                 if (*p == '\n')
155                         *p++ = '\0';
156         }
157         return (i);
158 }
159
160 /*
161  *      Find the location of the last / of the filename in the bootp
162  *      pathname, and return the next location, where the filename
163  *      starts. If no / exists, return the beginning of input string.
164  */
165 char *locate_file(char *pathname)
166 {
167         char            *p;
168
169         if ((p = strrchr(pathname, '/')) == 0)
170                 return (pathname);
171         return (p + 1);
172 }
173
174 /*
175  *      Return an index from 1..last-1 if valid character, else 0
176  *      If timeout after 10 seconds occurs, return -1.
177  */
178 int get_index(int last)
179 {
180         int             i;
181         char            line[2];
182         unsigned long   now, timeout;
183
184         timeout = currticks() + TIMEOUT * TICKS_PER_SEC;
185         while ((now = currticks()) < timeout && !console_ischar())
186                 ;
187         if (now >= timeout)
188                 return (-1);
189         getline(line, sizeof(line), 1);
190         i = line[0];
191         if (i >= '1' && i <= '0' + last - 1)
192                 return (i - '0');
193         return (0);
194 }
195
196 static void parse_elf_boot_notes(
197         void *notes, union infoblock **rheader, struct bootp_t **rbootp)
198 {
199         unsigned char *note, *end;
200         Elf_Bhdr *bhdr;
201         Elf_Nhdr *hdr;
202
203         bhdr = notes;
204         if (bhdr->b_signature != ELF_BHDR_MAGIC) {
205                 return;
206         }
207
208         note = ((char *)bhdr) + sizeof(*bhdr);
209         end  = ((char *)bhdr) + bhdr->b_size;
210         while (note < end) {
211                 unsigned char *n_name, *n_desc, *next;
212                 hdr = (Elf_Nhdr *)note;
213                 n_name = note + sizeof(*hdr);
214                 n_desc = n_name + ((hdr->n_namesz + 3) & ~3);
215                 next = n_desc + ((hdr->n_descsz + 3) & ~3);
216                 if (next > end) 
217                         break;
218 #if 0
219                 printf("n_type: %x n_name(%d): n_desc(%d): \n", 
220                         hdr->n_type, hdr->n_namesz, hdr->n_descsz);
221 #endif
222
223                 if ((hdr->n_namesz == 10) &&
224                         (memcmp(n_name, "Etherboot", 10) == 0)) {
225                         switch(hdr->n_type) {
226                         case EB_BOOTP_DATA:
227                                 *rbootp = *((void **)n_desc);
228                                 break;
229                         case EB_HEADER:
230                                 *rheader = *((void **)n_desc);
231                                 break;
232                         default:
233                                 break;
234                         }
235                 }
236                 note = next;
237         }
238 }
239
240 int menu(struct ebinfo *eb, union infoblock *header, struct bootp_t *bootp)
241 {
242         int             i, nitems;
243         char            *path, *file;   /* place to insert filename */
244
245         parse_elf_boot_notes(eb, &header, &bootp);
246         path = bootp->bp_file;
247         file = locate_file(path);
248         nitems = scan_items();
249         printf("\033[J\033[34mEtherboot menu system called from Etherboot %d.%d\033[37m\n\n", eb->major, eb->minor);
250         printf("Available images:\n\n");
251         for (i = 1; i < nitems; ++i)
252                 printf("%d. %s\n", i, items[i]);
253         printf("\n");
254         do {
255                 printf("Make a selection (timeout %d seconds => 1): ", TIMEOUT);
256                 i = get_index(nitems);
257         } while (i == 0);
258         if (i == -1) {
259                 ansi_putc('1');
260                 ansi_putc('\n');
261                 i = 1;          /* pick the first one if timeout */
262         }
263         if (*items[i] == '/')   /* absolute path? overwrite pathname */
264                 strcpy(path, items[i]);
265         else                    /* use directory of current pathname */
266                 strcpy(file, items[i]);
267         return (1);
268 }