1/*	$NetBSD: boot.c,v 1.4 2011/12/25 06:09:10 tsutsui Exp $	*/
2
3/*
4 * Copyright (c) 2009 NONAKA Kimihiro <nonaka@netbsd.org>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include <sys/types.h>
29#include <sys/bootblock.h>
30#include <sys/boot_flag.h>
31
32#include "boot.h"
33#include "bootinfo.h"
34#include "bootmenu.h"
35#include "disk.h"
36#include "unixdev.h"
37#include "pathnames.h"
38
39#include <lib/libsa/loadfile.h>
40#include <lib/libsa/ufs.h>
41
42#include "compat_linux.h"
43
44static const char * const names[][2] = {
45	{ "netbsd", "netbsd.gz" },
46	{ "netbsd.old", "netbsd.old.gz", },
47	{ "onetbsd", "onetbsd.gz" },
48};
49
50char *default_devname;
51uint default_unit, default_partition;
52const char *default_filename;
53int default_timeout = 5;
54
55static char probed_disks[256];
56static char bootconfpath[1024];
57
58static void bootcmd_help(char *);
59static void bootcmd_ls(char *);
60static void bootcmd_quit(char *);
61static void bootcmd_boot(char *);
62static void bootcmd_disk(char *);
63#ifdef SUPPORT_CONSDEV
64static void bootcmd_consdev(char *);
65#endif
66
67static const struct bootblk_command {
68	const char *c_name;
69	void (*c_fn)(char *arg);
70} bootcmds[] = {
71	{ "help",	bootcmd_help },
72	{ "?",		bootcmd_help },
73	{ "quit",	bootcmd_quit },
74	{ "ls",		bootcmd_ls },
75	{ "boot",	bootcmd_boot },
76	{ "disk",	bootcmd_disk },
77#ifdef SUPPORT_CONSDEV
78	{ "consdev",	bootcmd_consdev },
79#endif
80	{ NULL,		NULL },
81};
82
83static struct btinfo_howto bi_howto;
84
85static void print_banner(void);
86static int exec_netbsd(const char *file, int howto);
87
88int
89parsebootfile(const char *fname, char **fsname, char **devname,
90	uint *unit, uint *partition, const char **file)
91{
92	const char *col;
93
94	*fsname = "ufs";
95	*devname = default_devname;
96	*unit = default_unit;
97	*partition = default_partition;
98	*file = default_filename;
99
100	if (fname == NULL)
101		return 0;
102
103	if ((col = strchr(fname, ':'))) {	/* device given */
104		static char savedevname[MAXDEVNAME+1];
105		int devlen;
106		unsigned int u = 0, p = 0;
107		int i = 0;
108
109		devlen = col - fname;
110		if (devlen > MAXDEVNAME)
111			return EINVAL;
112
113#define isvalidname(c) ((c) >= 'a' && (c) <= 'z')
114		if (!isvalidname(fname[i]))
115			return EINVAL;
116		do {
117			savedevname[i] = fname[i];
118			i++;
119		} while (isvalidname(fname[i]));
120		savedevname[i] = '\0';
121
122#define isnum(c) ((c) >= '0' && (c) <= '9')
123		if (i < devlen) {
124			if (!isnum(fname[i]))
125				return (EUNIT);
126			do {
127				u *= 10;
128				u += fname[i++] - '0';
129			} while (isnum(fname[i]));
130		}
131
132#define isvalidpart(c) ((c) >= 'a' && (c) < 'a' + MAXPARTITIONS)
133		if (i < devlen) {
134			if (!isvalidpart(fname[i]))
135				return (EPART);
136			p = fname[i++] - 'a';
137		}
138
139		if (i != devlen)
140			return ENXIO;
141
142		*devname = savedevname;
143		*unit = u;
144		*partition = p;
145		fname = col + 1;
146	}
147
148	if (*fname)
149		*file = fname;
150
151	return 0;
152}
153
154char *
155sprint_bootsel(const char *filename)
156{
157	static char buf[80];
158	char *fsname, *devname;
159	uint unit, partition;
160	const char *file;
161
162	if (parsebootfile(filename, &fsname, &devname, &unit, &partition,
163	    &file) == 0) {
164		snprintf(buf, sizeof(buf), "%s%d%c:%s", devname, unit,
165		    'a' + partition, file);
166		return buf;
167	}
168	return "(invalid)";
169}
170
171static void
172print_banner(void)
173{
174	extern const char bootprog_name[];
175	extern const char bootprog_rev[];
176
177	printf("\n");
178	printf(">> %s, Revision %s\n", bootprog_name, bootprog_rev);
179}
180
181void
182boot(dev_t bootdev)
183{
184	extern char twiddle_toggle;
185	int currname;
186	int c;
187
188	consinit(CONSDEV_GLASS, -1);
189
190	twiddle_toggle = 1;	/* no twiddling until we're ready */
191
192	/* set default value: hd0a:netbsd */
193	default_devname = "hd";
194	default_unit = 0;
195	default_partition = 0;
196	default_filename = names[0][0];
197
198	diskprobe(probed_disks, sizeof(probed_disks));
199
200	snprintf(bootconfpath, sizeof(bootconfpath), "%s%d%c:%s",
201	    default_devname, default_unit, 'a' + default_partition,
202	    _PATH_BOOTCONF);
203	parsebootconf(bootconfpath);
204
205#ifdef SUPPORT_CONSDEV
206	/*
207	 * If console set in boot.cfg, switch to it.
208	 * This will print the banner, so we don't need to explicitly do it
209	 */
210	if (bootconf.consdev)
211		bootcmd_consdev(bootconf.consdev);
212	else
213#endif
214		print_banner();
215
216	printf("\ndisks: %s\n", probed_disks);
217
218	/* Display the menu, if applicable */
219	twiddle_toggle = 0;
220	if (bootconf.nummenu > 0) {
221		/* Does not return */
222		doboottypemenu();
223	}
224
225	printf("Press return to boot now, any other key for boot menu\n");
226	currname = 0;
227	for (currname = 0; currname < __arraycount(names); currname++) {
228		printf("booting %s - starting in ",
229		    sprint_bootsel(names[currname][0]));
230
231		c = awaitkey((bootconf.timeout < 0) ? 0 : bootconf.timeout, 1);
232		if ((c != '\r') && (c != '\n') && (c != '\0')) {
233			printf("type \"?\" or \"help\" for help.\n");
234			bootmenu(); /* does not return */
235		}
236
237		/*
238		 * try pairs of names[] entries, foo and foo.gz
239		 */
240		/* don't print "booting..." again */
241		bootit(names[currname][0], 0, 0);
242		/* since it failed, try compressed bootfile. */
243		bootit(names[currname][1], 0, 1);
244	}
245
246	bootmenu(); /* does not return */
247}
248
249void
250bootit(const char *filename, int howto, int tell)
251{
252
253	if (tell) {
254		printf("booting %s", sprint_bootsel(filename));
255		if (howto)
256			printf(" (howto 0x%x)", howto);
257		printf("\n");
258	}
259
260	if (exec_netbsd(filename, howto) < 0) {
261		printf("boot: %s: %s\n", sprint_bootsel(filename),
262		       strerror(errno));
263	} else {
264		printf("boot returned\n");
265	}
266}
267
268static int
269exec_netbsd(const char *file, int howto)
270{
271	u_long marks[MARK_MAX];
272
273	BI_ALLOC(BTINFO_MAX);
274
275	bi_howto.howto = howto;
276	BI_ADD(&bi_howto, BTINFO_HOWTO, sizeof(bi_howto));
277
278	if (loadfile_zboot(file, marks, LOAD_KERNEL) == -1)
279		goto out;
280
281	/*NOTREACHED*/
282	return 0;
283
284out:
285	BI_FREE();
286	bootinfo = 0;
287	return -1;
288}
289
290/*
291 * bootmenu
292 */
293static char *gettrailer(char *arg);
294static int parseopts(const char *opts, int *howto);
295static int parseboot(char *arg, char **filename, int *howto);
296
297/* ARGSUSED */
298static void
299bootcmd_help(char *arg)
300{
301
302	printf("commands are:\n"
303	    "boot [xdNx:][filename] [-1acdqsv]\n"
304	    "     (ex. \"boot hd0a:netbsd.old -s\")\n"
305	    "     (ex. \"boot path:/mnt/card/netbsd -1\")\n"
306	    "ls [path]\n"
307#ifdef SUPPORT_CONSDEV
308	    "consdev {glass|com [speed]}\n"
309#endif
310	    "disk\n"
311	    "help|?\n"
312	    "quit\n");
313}
314
315/* ARGSUSED */
316static void
317bootcmd_quit(char *arg)
318{
319
320	printf("Exiting...\n");
321	exit(0);
322	/*NOTREACHED*/
323}
324
325static void
326bootcmd_ls(char *arg)
327{
328	const char *save = default_filename;
329
330	default_filename = "/";
331	ls(arg);
332	default_filename = save;
333}
334
335static void
336bootcmd_boot(char *arg)
337{
338	char *filename;
339	int howto;
340
341	if (parseboot(arg, &filename, &howto)) {
342		bootit(filename, howto, 1);
343	}
344}
345
346/* ARGSUSED */
347static void
348bootcmd_disk(char *arg)
349{
350
351	printf("disks: %s\n", probed_disks);
352}
353
354#ifdef SUPPORT_CONSDEV
355static const struct cons_devs {
356	const char	*name;
357	int		tag;
358} cons_devs[] = {
359	{ "glass",	CONSDEV_GLASS },
360	{ "com",	CONSDEV_COM0 },
361	{ "com0",	CONSDEV_COM0 },
362	{ NULL,		0 }
363};
364
365static void
366bootcmd_consdev(char *arg)
367{
368	const struct cons_devs *cdp;
369	char *p;
370	int speed = 9600;
371
372	p = strchr(arg, ' ');
373	if (p != NULL) {
374		*p++ = '\0';
375		speed = atoi(p);
376	}
377	for (cdp = cons_devs; cdp->name != NULL; cdp++) {
378		if (strcmp(arg, cdp->name) == 0) {
379			consinit(cdp->tag, speed);
380			print_banner();
381			return;
382		}
383	}
384	printf("invalid console device.\n");
385}
386#endif
387
388void
389docommand(char *arg)
390{
391	char *options;
392	int i;
393
394	options = gettrailer(arg);
395
396	for (i = 0; bootcmds[i].c_name != NULL; i++) {
397		if (strcmp(arg, bootcmds[i].c_name) == 0) {
398			(*bootcmds[i].c_fn)(options);
399			return;
400		}
401	}
402
403	printf("unknown command\n");
404	bootcmd_help(NULL);
405}
406
407void
408bootmenu(void)
409{
410	char input[256];
411	char *c;
412
413	for (;;) {
414		c = input;
415
416		input[0] = '\0';
417		printf("> ");
418		gets(input);
419
420		/*
421		 * Skip leading whitespace.
422		 */
423		while (*c == ' ') {
424			c++;
425		}
426		if (*c != '\0') {
427			docommand(c);
428		}
429	}
430}
431
432/*
433 * chops the head from the arguments and returns the arguments if any,
434 * or possibly an empty string.
435 */
436static char *
437gettrailer(char *arg)
438{
439	char *options;
440
441	if ((options = strchr(arg, ' ')) == NULL)
442		return ("");
443	else
444		*options++ = '\0';
445
446	/* trim leading blanks */
447	while (*options && *options == ' ')
448		options++;
449
450	return options;
451}
452
453static int
454parseopts(const char *opts, int *howto)
455{
456	int r, tmpopt = 0;
457
458	opts++; 	/* skip - */
459	while (*opts && *opts != ' ') {
460		r = 0;
461		BOOT_FLAG(*opts, r);
462		if (r == 0) {
463			printf("-%c: unknown flag\n", *opts);
464			bootcmd_help(NULL);
465			return 0;
466		}
467		tmpopt |= r;
468		opts++;
469	}
470
471	*howto = tmpopt;
472	return 1;
473}
474
475static int
476parseboot(char *arg, char **filename, int *howto)
477{
478	char *opts = NULL;
479
480	*filename = 0;
481	*howto = 0;
482
483	/* if there were no arguments */
484	if (arg == NULL || *arg == '\0')
485		return 1;
486
487	/* format is... */
488	/* [[xxNx:]filename] [-adqsv] */
489
490	/* check for just args */
491	if (arg[0] == '-') {
492		opts = arg;
493	} else {
494		/* there's a file name */
495		*filename = arg;
496
497		opts = gettrailer(arg);
498		if (opts == NULL || *opts == '\0') {
499			opts = NULL;
500		} else if (*opts != '-') {
501			printf("invalid arguments\n");
502			bootcmd_help(NULL);
503			return 0;
504		}
505	}
506
507	/* at this point, we have dealt with filenames. */
508
509	/* now, deal with options */
510	if (opts) {
511		if (parseopts(opts, howto) == 0) {
512			return 0;
513		}
514	}
515	return 1;
516}
517