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