xref: /netbsd-src/sys/lib/libsa/bootcfg.c (revision e670fd5c413e99c2f6a37901bb21c537fcd322d2)
1 /*	$NetBSD: bootcfg.c,v 1.6 2021/05/30 05:59:23 mlelstv 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  * root: the root device to use
84  * format: how menu choices are displayed: (a)utomatic, (n)umbers or (l)etters
85  * clear: whether to clear the screen or not
86  *
87  * Example boot.cfg file:
88  * banner=Welcome to NetBSD
89  * banner=Please choose the boot type from the following menu
90  * menu=Boot NetBSD:boot netbsd
91  * menu=Boot into single user mode:boot netbsd -s
92  * menu=:boot hd1a:netbsd -cs
93  * menu=Goto boot comand line:prompt
94  * timeout=10
95  * consdev=com0
96  * default=1
97 */
98 int
99 perform_bootcfg(const char *conf, bootcfg_command command, const off_t maxsz)
100 {
101 	char *bc, *c;
102 	int cmenu, cbanner;
103 	ssize_t len, off, resid;
104 	int fd, err;
105 	struct stat st;
106 	char *next, *key, *value, *v2;
107 
108 	/* clear bootcfg structure */
109 	memset(&bootcfg_info, 0, sizeof(bootcfg_info));
110 
111 	/* set default timeout */
112 	bootcfg_info.timeout = DEFAULT_TIMEOUT;
113 
114 	/* automatically switch between letter and numbers on menu */
115 	bootcfg_info.menuformat = DEFAULT_FORMAT;
116 
117 	fd = open(conf, 0);
118 	if (fd < 0)
119 		return ENOENT;
120 
121 	err = fstat(fd, &st);
122 	if (err == -1) {
123 		/* file descriptor may not be backed by a libsa file-system */
124 		st.st_size = maxsz;
125 	}
126 
127 	/* if a maximum size is being requested for the boot.cfg enforce it. */
128 	if (0 < maxsz && st.st_size > maxsz) {
129 		close(fd);
130 		return EFBIG;
131 	}
132 
133 	bc = alloc((size_t)st.st_size + 1);
134 	if (bc == NULL) {
135 		printf("Could not allocate memory for boot configuration\n");
136 		close(fd);
137 		return ENOMEM;
138 	}
139 
140 	/*
141 	 * XXX original code, assumes error or eof return from read()
142 	 *     results in the entire boot.cfg being buffered.
143 	 *     - should bail out on read() failing.
144 	 *     - assumption is made that the file size doesn't change between
145 	 *       fstat() and read()ing.  probably safe in this context
146 	 *       arguably should check that reading the file won't overflow
147 	 *       the storage anyway.
148 	 */
149 	off = 0;
150 	resid = st.st_size;
151 	do {
152 		len = read(fd, bc + off, uimin(1024, resid));
153 		if (len <= 0)
154 			break;
155 		off += len;
156 		resid -= len;
157 	} while (len > 0 && resid > 0);
158 	bc[off] = '\0';
159 
160 	close(fd);
161 
162 	/* bc is now assumed to contain the whole boot.cfg file (see above) */
163 
164 	cmenu = 0;
165 	cbanner = 0;
166 	for (c = bc; *c; c = next) {
167 		key = c;
168 		/* find end of line */
169 		for (; *c && *c != '\n'; c++)
170 			/* zero terminate line on start of comment */
171 			if (*c == '#')
172 				*c = 0;
173 		/* zero terminate line */
174 		if (*(next = c))
175 			*next++ = 0;
176 		/* Look for = separator between key and value */
177 		for (c = key; *c && *c != '='; c++)
178 			continue;
179 		/* Ignore lines with no key=value pair */
180 		if (*c == '\0')
181 			continue;
182 
183 		/* zero terminate key which points to keyword */
184 		*c++ = 0;
185 		value = c;
186 		/* Look for end of line (or file) and zero terminate value */
187 		for (; *c && *c != '\n'; c++)
188 			continue;
189 		*c = 0;
190 
191 		if (!strncmp(key, "menu", 4)) {
192 			/*
193 			 * Parse "menu=<description>:<command>".  If the
194 			 * description is empty ("menu=:<command>)",
195 			 * then re-use the command as the description.
196 			 * Note that the command may contain embedded
197 			 * colons.
198 			 */
199 			if (cmenu >= BOOTCFG_MAXMENU)
200 				continue;
201 			bootcfg_info.desc[cmenu] = value;
202 			for (v2 = value; *v2 && *v2 != ':'; v2++)
203 				continue;
204 			if (*v2) {
205 				*v2++ = 0;
206 				bootcfg_info.command[cmenu] = v2;
207 				if (! *value)
208 					bootcfg_info.desc[cmenu] = v2;
209 				cmenu++;
210 			} else {
211 				/* No delimiter means invalid line */
212 				bootcfg_info.desc[cmenu] = NULL;
213 			}
214 		} else if (!strncmp(key, "banner", 6)) {
215 			if (cbanner < BOOTCFG_MAXBANNER)
216 				bootcfg_info.banner[cbanner++] = value;
217 		} else if (!strncmp(key, "timeout", 7)) {
218 			if (!isdigit(*value))
219 				bootcfg_info.timeout = -1;
220 			else
221 				bootcfg_info.timeout = atoi(value);
222 		} else if (!strncmp(key, "default", 7)) {
223 			bootcfg_info.def = atoi(value) - 1;
224 		} else if (!strncmp(key, "consdev", 7)) {
225 			bootcfg_info.consdev = value;
226 		} else if (!strncmp(key, "root", 4)) {
227 			bootcfg_info.root = value;
228 		} else if (!strncmp(key, BOOTCFG_CMD_LOAD, 4)) {
229 			command(BOOTCFG_CMD_LOAD, value);
230 		} else if (!strncmp(key, "format", 6)) {
231 			printf("value:%c\n", *value);
232 			switch (*value) {
233 			case 'a':
234 			case 'A':
235 				bootcfg_info.menuformat = MENUFORMAT_AUTO;
236 				break;
237 
238 			case 'n':
239 			case 'N':
240 			case 'd':
241 			case 'D':
242 				bootcfg_info.menuformat = MENUFORMAT_NUMBER;
243 				break;
244 
245 			case 'l':
246 			case 'L':
247 				bootcfg_info.menuformat = MENUFORMAT_LETTER;
248 				break;
249 			}
250 		} else if (!strncmp(key, "clear", 5)) {
251 			bootcfg_info.clear = !!atoi(value);
252 		} else if (!strncmp(key, BOOTCFG_CMD_USERCONF, 8)) {
253 			command(BOOTCFG_CMD_USERCONF, value);
254 		} else {
255 			command(key, value);
256 		}
257 	}
258 
259 	switch (bootcfg_info.menuformat) {
260 	case MENUFORMAT_AUTO:
261 		if (cmenu > 9 && bootcfg_info.timeout > 0)
262 			bootcfg_info.menuformat = MENUFORMAT_LETTER;
263 		else
264 			bootcfg_info.menuformat = MENUFORMAT_NUMBER;
265 		break;
266 
267 	case MENUFORMAT_NUMBER:
268 		if (cmenu > 9 && bootcfg_info.timeout > 0)
269 			cmenu = 9;
270 		break;
271 	}
272 
273 	bootcfg_info.nummenu = cmenu;
274 	if (bootcfg_info.def < 0)
275 		bootcfg_info.def = 0;
276 	if (bootcfg_info.def >= cmenu)
277 		bootcfg_info.def = cmenu - 1;
278 
279 	return 0;
280 }
281