xref: /netbsd-src/sys/lib/libsa/bootcfg.c (revision 627f7eb200a4419d89b531d55fccd2ee3ffdcde0)
1 /*	$NetBSD: bootcfg.c,v 1.5 2020/06/27 17:22:12 jmcneill 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 #define MENUFORMAT_AUTO   0
37 #define MENUFORMAT_NUMBER 1
38 #define MENUFORMAT_LETTER 2
39 
40 #define DEFAULT_FORMAT  MENUFORMAT_AUTO
41 #define DEFAULT_TIMEOUT 10
42 
43 struct bootcfg_def bootcfg_info;
44 
45 void
46 bootcfg_do_noop(const char *cmd, char *arg)
47 {
48 	/* noop, do nothing */
49 }
50 
51 /*
52  * This function parses a boot.cfg file in the root of the filesystem
53  * (if present) and populates the global boot configuration.
54  *
55  * The file consists of a number of lines each terminated by \n
56  * The lines are in the format keyword=value. There should not be spaces
57  * around the = sign.
58  *
59  * perform_bootcfg(conf, command, maxsz)
60  *
61  * conf		Path to boot.cfg to be passed verbatim to open()
62  *
63  * command	Pointer to a function that will be called when
64  * 		perform_bootcfg() encounters a key (command) it does not
65  *		recognize.
66  *		The command function is provided both the keyword and
67  *		value parsed as arguments to the function.
68  *
69  * maxsz	Limit the size of the boot.cfg perform_bootcfg() will parse.
70  * 		- If maxsz is < 0 boot.cfg will not be processed.
71  * 		- If maxsz is = 0 no limit will be imposed but parsing may
72  *		  fail due to platform or other constraints e.g. maximum
73  *		  segment size.
74  *		- If 0 < maxsz and boot.cfg exceeds maxsz it will not be
75  *		  parsed, otherwise it will be parsed.
76  *
77  * The recognised keywords are:
78  * banner: text displayed instead of the normal welcome text
79  * menu: Descriptive text:command to use
80  * timeout: Timeout in seconds (overrides that set by installboot)
81  * default: the default menu option to use if Return is pressed
82  * consdev: the console device to use
83  * format: how menu choices are displayed: (a)utomatic, (n)umbers or (l)etters
84  * clear: whether to clear the screen or not
85  *
86  * Example boot.cfg file:
87  * banner=Welcome to NetBSD
88  * banner=Please choose the boot type from the following menu
89  * menu=Boot NetBSD:boot netbsd
90  * menu=Boot into single user mode:boot netbsd -s
91  * menu=:boot hd1a:netbsd -cs
92  * menu=Goto boot comand line:prompt
93  * timeout=10
94  * consdev=com0
95  * default=1
96 */
97 int
98 perform_bootcfg(const char *conf, bootcfg_command command, const off_t maxsz)
99 {
100 	char *bc, *c;
101 	int cmenu, cbanner;
102 	ssize_t len, off, resid;
103 	int fd, err;
104 	struct stat st;
105 	char *next, *key, *value, *v2;
106 
107 	/* clear bootcfg structure */
108 	memset(&bootcfg_info, 0, sizeof(bootcfg_info));
109 
110 	/* set default timeout */
111 	bootcfg_info.timeout = DEFAULT_TIMEOUT;
112 
113 	/* automatically switch between letter and numbers on menu */
114 	bootcfg_info.menuformat = DEFAULT_FORMAT;
115 
116 	fd = open(conf, 0);
117 	if (fd < 0)
118 		return ENOENT;
119 
120 	err = fstat(fd, &st);
121 	if (err == -1) {
122 		/* file descriptor may not be backed by a libsa file-system */
123 		st.st_size = maxsz;
124 	}
125 
126 	/* if a maximum size is being requested for the boot.cfg enforce it. */
127 	if (0 < maxsz && st.st_size > maxsz) {
128 		close(fd);
129 		return EFBIG;
130 	}
131 
132 	bc = alloc((size_t)st.st_size + 1);
133 	if (bc == NULL) {
134 		printf("Could not allocate memory for boot configuration\n");
135 		close(fd);
136 		return ENOMEM;
137 	}
138 
139 	/*
140 	 * XXX original code, assumes error or eof return from read()
141 	 *     results in the entire boot.cfg being buffered.
142 	 *     - should bail out on read() failing.
143 	 *     - assumption is made that the file size doesn't change between
144 	 *       fstat() and read()ing.  probably safe in this context
145 	 *       arguably should check that reading the file won't overflow
146 	 *       the storage anyway.
147 	 */
148 	off = 0;
149 	resid = st.st_size;
150 	do {
151 		len = read(fd, bc + off, uimin(1024, resid));
152 		if (len <= 0)
153 			break;
154 		off += len;
155 		resid -= len;
156 	} while (len > 0 && resid > 0);
157 	bc[off] = '\0';
158 
159 	close(fd);
160 
161 	/* bc is now assumed to contain the whole boot.cfg file (see above) */
162 
163 	cmenu = 0;
164 	cbanner = 0;
165 	for (c = bc; *c; c = next) {
166 		key = c;
167 		/* find end of line */
168 		for (; *c && *c != '\n'; c++)
169 			/* zero terminate line on start of comment */
170 			if (*c == '#')
171 				*c = 0;
172 		/* zero terminate line */
173 		if (*(next = c))
174 			*next++ = 0;
175 		/* Look for = separator between key and value */
176 		for (c = key; *c && *c != '='; c++)
177 			continue;
178 		/* Ignore lines with no key=value pair */
179 		if (*c == '\0')
180 			continue;
181 
182 		/* zero terminate key which points to keyword */
183 		*c++ = 0;
184 		value = c;
185 		/* Look for end of line (or file) and zero terminate value */
186 		for (; *c && *c != '\n'; c++)
187 			continue;
188 		*c = 0;
189 
190 		if (!strncmp(key, "menu", 4)) {
191 			/*
192 			 * Parse "menu=<description>:<command>".  If the
193 			 * description is empty ("menu=:<command>)",
194 			 * then re-use the command as the description.
195 			 * Note that the command may contain embedded
196 			 * colons.
197 			 */
198 			if (cmenu >= BOOTCFG_MAXMENU)
199 				continue;
200 			bootcfg_info.desc[cmenu] = value;
201 			for (v2 = value; *v2 && *v2 != ':'; v2++)
202 				continue;
203 			if (*v2) {
204 				*v2++ = 0;
205 				bootcfg_info.command[cmenu] = v2;
206 				if (! *value)
207 					bootcfg_info.desc[cmenu] = v2;
208 				cmenu++;
209 			} else {
210 				/* No delimiter means invalid line */
211 				bootcfg_info.desc[cmenu] = NULL;
212 			}
213 		} else if (!strncmp(key, "banner", 6)) {
214 			if (cbanner < BOOTCFG_MAXBANNER)
215 				bootcfg_info.banner[cbanner++] = value;
216 		} else if (!strncmp(key, "timeout", 7)) {
217 			if (!isdigit(*value))
218 				bootcfg_info.timeout = -1;
219 			else
220 				bootcfg_info.timeout = atoi(value);
221 		} else if (!strncmp(key, "default", 7)) {
222 			bootcfg_info.def = atoi(value) - 1;
223 		} else if (!strncmp(key, "consdev", 7)) {
224 			bootcfg_info.consdev = value;
225 		} else if (!strncmp(key, BOOTCFG_CMD_LOAD, 4)) {
226 			command(BOOTCFG_CMD_LOAD, value);
227 		} else if (!strncmp(key, "format", 6)) {
228 			printf("value:%c\n", *value);
229 			switch (*value) {
230 			case 'a':
231 			case 'A':
232 				bootcfg_info.menuformat = MENUFORMAT_AUTO;
233 				break;
234 
235 			case 'n':
236 			case 'N':
237 			case 'd':
238 			case 'D':
239 				bootcfg_info.menuformat = MENUFORMAT_NUMBER;
240 				break;
241 
242 			case 'l':
243 			case 'L':
244 				bootcfg_info.menuformat = MENUFORMAT_LETTER;
245 				break;
246 			}
247 		} else if (!strncmp(key, "clear", 5)) {
248 			bootcfg_info.clear = !!atoi(value);
249 		} else if (!strncmp(key, BOOTCFG_CMD_USERCONF, 8)) {
250 			command(BOOTCFG_CMD_USERCONF, value);
251 		} else {
252 			command(key, value);
253 		}
254 	}
255 
256 	switch (bootcfg_info.menuformat) {
257 	case MENUFORMAT_AUTO:
258 		if (cmenu > 9 && bootcfg_info.timeout > 0)
259 			bootcfg_info.menuformat = MENUFORMAT_LETTER;
260 		else
261 			bootcfg_info.menuformat = MENUFORMAT_NUMBER;
262 		break;
263 
264 	case MENUFORMAT_NUMBER:
265 		if (cmenu > 9 && bootcfg_info.timeout > 0)
266 			cmenu = 9;
267 		break;
268 	}
269 
270 	bootcfg_info.nummenu = cmenu;
271 	if (bootcfg_info.def < 0)
272 		bootcfg_info.def = 0;
273 	if (bootcfg_info.def >= cmenu)
274 		bootcfg_info.def = cmenu - 1;
275 
276 	return 0;
277 }
278