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