xref: /netbsd-src/sys/lib/libsa/bootcfg.c (revision b7b7574d3bf8eeb51a1fa3977b59142ec6434a55)
1 /*	$NetBSD: bootcfg.c,v 1.1 2014/06/28 09:16:18 rtr Exp $	*/
2 
3 /*-
4  * Copyright (c) 2008 The NetBSD Foundation, Inc.
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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS
17  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
20  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  * POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #include <sys/types.h>
30 #include <sys/reboot.h>
31 
32 #include <lib/libsa/stand.h>
33 #include <lib/libsa/bootcfg.h>
34 #include <lib/libkern/libkern.h>
35 
36 static int atoi(const char *);
37 
38 #define isnum(c) ((c) >= '0' && (c) <= '9')
39 
40 #define MENUFORMAT_AUTO   0
41 #define MENUFORMAT_NUMBER 1
42 #define MENUFORMAT_LETTER 2
43 
44 #define DEFAULT_FORMAT  MENUFORMAT_AUTO
45 #define DEFAULT_TIMEOUT 10
46 
47 struct bootcfg_def bootcfg_info;
48 
49 int
50 atoi(const char *in)
51 {
52 	char *c;
53 	int ret;
54 
55 	ret = 0;
56 	c = (char *)in;
57 	if (*c == '-')
58 		c++;
59 	for (; isnum(*c); c++)
60 		ret = (ret * 10) + (*c - '0');
61 
62 	return (*in == '-') ? -ret : ret;
63 }
64 
65 void
66 bootcfg_do_noop(const char *cmd, char *arg)
67 {
68 	/* noop, do nothing */
69 }
70 
71 /*
72  * This function parses a boot.cfg file in the root of the filesystem
73  * (if present) and populates the global boot configuration.
74  *
75  * The file consists of a number of lines each terminated by \n
76  * The lines are in the format keyword=value. There should not be spaces
77  * around the = sign.
78  *
79  * perform_bootcfg(conf, command, maxsz)
80  *
81  * conf		Path to boot.cfg to be passed verbatim to open()
82  *
83  * command	Pointer to a function that will be called when
84  * 		perform_bootcfg() encounters a key (command) it does not
85  *		recognize.
86  *		The command function is provided both the keyword and
87  *		value parsed as arguments to the function.
88  *
89  * maxsz	Limit the size of the boot.cfg perform_bootcfg() will parse.
90  * 		- If maxsz is < 0 boot.cfg will not be processed.
91  * 		- If maxsz is = 0 no limit will be imposed but parsing may
92  *		  fail due to platform or other constraints e.g. maximum
93  *		  segment size.
94  *		- If 0 < maxsz and boot.cfg exceeds maxsz it will not be
95  *		  parsed, otherwise it will be parsed.
96  *
97  * The recognised keywords are:
98  * banner: text displayed instead of the normal welcome text
99  * menu: Descriptive text:command to use
100  * timeout: Timeout in seconds (overrides that set by installboot)
101  * default: the default menu option to use if Return is pressed
102  * consdev: the console device to use
103  * format: how menu choices are displayed: (a)utomatic, (n)umbers or (l)etters
104  * clear: whether to clear the screen or not
105  *
106  * Example boot.cfg file:
107  * banner=Welcome to NetBSD
108  * banner=Please choose the boot type from the following menu
109  * menu=Boot NetBSD:boot netbsd
110  * menu=Boot into single user mode:boot netbsd -s
111  * menu=:boot hd1a:netbsd -cs
112  * menu=Goto boot comand line:prompt
113  * timeout=10
114  * consdev=com0
115  * default=1
116 */
117 void
118 perform_bootcfg(const char *conf, bootcfg_command command, const off_t maxsz)
119 {
120 	char *bc, *c;
121 	int cmenu, cbanner, len;
122 	int fd, err, off;
123 	struct stat st;
124 	char *next, *key, *value, *v2;
125 
126 	/* clear bootcfg structure */
127 	memset(&bootcfg_info, 0, sizeof(bootcfg_info));
128 
129 	/* set default timeout */
130 	bootcfg_info.timeout = DEFAULT_TIMEOUT;
131 
132 	/* automatically switch between letter and numbers on menu */
133 	bootcfg_info.menuformat = DEFAULT_FORMAT;
134 
135 	fd = open(conf, 0);
136 	if (fd < 0)
137 		return;
138 
139 	err = fstat(fd, &st);
140 	if (err == -1) {
141 		close(fd);
142 		return;
143 	}
144 
145 	/* if a maximum size is being requested for the boot.cfg enforce it. */
146 	if (0 < maxsz && st.st_size > maxsz) {
147 		close(fd);
148 		return;
149 	}
150 
151 	bc = alloc(st.st_size + 1);
152 	if (bc == NULL) {
153 		printf("Could not allocate memory for boot configuration\n");
154 		close(fd);
155 		return;
156 	}
157 
158 	/*
159 	 * XXX original code, assumes error or eof return from read()
160 	 *     results in the entire boot.cfg being buffered.
161 	 *     - should bail out on read() failing.
162 	 *     - assumption is made that the file size doesn't change between
163 	 *       fstat() and read()ing.  probably safe in this context
164 	 *       arguably should check that reading the file won't overflow
165 	 *       the storage anyway.
166 	 */
167 	off = 0;
168 	do {
169 		len = read(fd, bc + off, 1024);
170 		if (len <= 0)
171 			break;
172 		off += len;
173 	} while (len > 0);
174 	bc[off] = '\0';
175 
176 	close(fd);
177 
178 	/* bc is now assumed to contain the whole boot.cfg file (see above) */
179 
180 	cmenu = 0;
181 	cbanner = 0;
182 	for (c = bc; *c; c = next) {
183 		key = c;
184 		/* find end of line */
185 		for (; *c && *c != '\n'; c++)
186 			/* zero terminate line on start of comment */
187 			if (*c == '#')
188 				*c = 0;
189 		/* zero terminate line */
190 		if (*(next = c))
191 			*next++ = 0;
192 		/* Look for = separator between key and value */
193 		for (c = key; *c && *c != '='; c++)
194 			continue;
195 		/* Ignore lines with no key=value pair */
196 		if (*c == '\0')
197 			continue;
198 
199 		/* zero terminate key which points to keyword */
200 		*c++ = 0;
201 		value = c;
202 		/* Look for end of line (or file) and zero terminate value */
203 		for (; *c && *c != '\n'; c++)
204 			continue;
205 		*c = 0;
206 
207 		if (!strncmp(key, "menu", 4)) {
208 			/*
209 			 * Parse "menu=<description>:<command>".  If the
210 			 * description is empty ("menu=:<command>)",
211 			 * then re-use the command as the description.
212 			 * Note that the command may contain embedded
213 			 * colons.
214 			 */
215 			if (cmenu >= BOOTCFG_MAXMENU)
216 				continue;
217 			bootcfg_info.desc[cmenu] = value;
218 			for (v2 = value; *v2 && *v2 != ':'; v2++)
219 				continue;
220 			if (*v2) {
221 				*v2++ = 0;
222 				bootcfg_info.command[cmenu] = v2;
223 				if (! *value)
224 					bootcfg_info.desc[cmenu] = v2;
225 				cmenu++;
226 			} else {
227 				/* No delimiter means invalid line */
228 				bootcfg_info.desc[cmenu] = NULL;
229 			}
230 		} else if (!strncmp(key, "banner", 6)) {
231 			if (cbanner < BOOTCFG_MAXBANNER)
232 				bootcfg_info.banner[cbanner++] = value;
233 		} else if (!strncmp(key, "timeout", 7)) {
234 			if (!isnum(*value))
235 				bootcfg_info.timeout = -1;
236 			else
237 				bootcfg_info.timeout = atoi(value);
238 		} else if (!strncmp(key, "default", 7)) {
239 			bootcfg_info.def = atoi(value) - 1;
240 		} else if (!strncmp(key, "consdev", 7)) {
241 			bootcfg_info.consdev = value;
242 		} else if (!strncmp(key, BOOTCFG_CMD_LOAD, 4)) {
243 			command(BOOTCFG_CMD_LOAD, value);
244 		} else if (!strncmp(key, "format", 6)) {
245 			printf("value:%c\n", *value);
246 			switch (*value) {
247 			case 'a':
248 			case 'A':
249 				bootcfg_info.menuformat = MENUFORMAT_AUTO;
250 				break;
251 
252 			case 'n':
253 			case 'N':
254 			case 'd':
255 			case 'D':
256 				bootcfg_info.menuformat = MENUFORMAT_NUMBER;
257 				break;
258 
259 			case 'l':
260 			case 'L':
261 				bootcfg_info.menuformat = MENUFORMAT_LETTER;
262 				break;
263 			}
264 		} else if (!strncmp(key, "clear", 5)) {
265 			bootcfg_info.clear = !!atoi(value);
266 		} else if (!strncmp(key, BOOTCFG_CMD_USERCONF, 8)) {
267 			command(BOOTCFG_CMD_USERCONF, value);
268 		} else {
269 			command(key, value);
270 		}
271 	}
272 
273 	switch (bootcfg_info.menuformat) {
274 	case MENUFORMAT_AUTO:
275 		if (cmenu > 9 && bootcfg_info.timeout > 0)
276 			bootcfg_info.menuformat = MENUFORMAT_LETTER;
277 		else
278 			bootcfg_info.menuformat = MENUFORMAT_NUMBER;
279 		break;
280 
281 	case MENUFORMAT_NUMBER:
282 		if (cmenu > 9 && bootcfg_info.timeout > 0)
283 			cmenu = 9;
284 		break;
285 	}
286 
287 	bootcfg_info.nummenu = cmenu;
288 	if (bootcfg_info.def < 0)
289 		bootcfg_info.def = 0;
290 	if (bootcfg_info.def >= cmenu)
291 		bootcfg_info.def = cmenu - 1;
292 }
293