bootcfg.c revision 1.4 1 /* $NetBSD: bootcfg.c,v 1.4 2019/03/31 20:08:45 christos 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;
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 close(fd);
123 return EIO;
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 do {
150 len = read(fd, bc + off, 1024);
151 if (len <= 0)
152 break;
153 off += len;
154 } while (len > 0);
155 bc[off] = '\0';
156
157 close(fd);
158
159 /* bc is now assumed to contain the whole boot.cfg file (see above) */
160
161 cmenu = 0;
162 cbanner = 0;
163 for (c = bc; *c; c = next) {
164 key = c;
165 /* find end of line */
166 for (; *c && *c != '\n'; c++)
167 /* zero terminate line on start of comment */
168 if (*c == '#')
169 *c = 0;
170 /* zero terminate line */
171 if (*(next = c))
172 *next++ = 0;
173 /* Look for = separator between key and value */
174 for (c = key; *c && *c != '='; c++)
175 continue;
176 /* Ignore lines with no key=value pair */
177 if (*c == '\0')
178 continue;
179
180 /* zero terminate key which points to keyword */
181 *c++ = 0;
182 value = c;
183 /* Look for end of line (or file) and zero terminate value */
184 for (; *c && *c != '\n'; c++)
185 continue;
186 *c = 0;
187
188 if (!strncmp(key, "menu", 4)) {
189 /*
190 * Parse "menu=<description>:<command>". If the
191 * description is empty ("menu=:<command>)",
192 * then re-use the command as the description.
193 * Note that the command may contain embedded
194 * colons.
195 */
196 if (cmenu >= BOOTCFG_MAXMENU)
197 continue;
198 bootcfg_info.desc[cmenu] = value;
199 for (v2 = value; *v2 && *v2 != ':'; v2++)
200 continue;
201 if (*v2) {
202 *v2++ = 0;
203 bootcfg_info.command[cmenu] = v2;
204 if (! *value)
205 bootcfg_info.desc[cmenu] = v2;
206 cmenu++;
207 } else {
208 /* No delimiter means invalid line */
209 bootcfg_info.desc[cmenu] = NULL;
210 }
211 } else if (!strncmp(key, "banner", 6)) {
212 if (cbanner < BOOTCFG_MAXBANNER)
213 bootcfg_info.banner[cbanner++] = value;
214 } else if (!strncmp(key, "timeout", 7)) {
215 if (!isdigit(*value))
216 bootcfg_info.timeout = -1;
217 else
218 bootcfg_info.timeout = atoi(value);
219 } else if (!strncmp(key, "default", 7)) {
220 bootcfg_info.def = atoi(value) - 1;
221 } else if (!strncmp(key, "consdev", 7)) {
222 bootcfg_info.consdev = value;
223 } else if (!strncmp(key, BOOTCFG_CMD_LOAD, 4)) {
224 command(BOOTCFG_CMD_LOAD, value);
225 } else if (!strncmp(key, "format", 6)) {
226 printf("value:%c\n", *value);
227 switch (*value) {
228 case 'a':
229 case 'A':
230 bootcfg_info.menuformat = MENUFORMAT_AUTO;
231 break;
232
233 case 'n':
234 case 'N':
235 case 'd':
236 case 'D':
237 bootcfg_info.menuformat = MENUFORMAT_NUMBER;
238 break;
239
240 case 'l':
241 case 'L':
242 bootcfg_info.menuformat = MENUFORMAT_LETTER;
243 break;
244 }
245 } else if (!strncmp(key, "clear", 5)) {
246 bootcfg_info.clear = !!atoi(value);
247 } else if (!strncmp(key, BOOTCFG_CMD_USERCONF, 8)) {
248 command(BOOTCFG_CMD_USERCONF, value);
249 } else {
250 command(key, value);
251 }
252 }
253
254 switch (bootcfg_info.menuformat) {
255 case MENUFORMAT_AUTO:
256 if (cmenu > 9 && bootcfg_info.timeout > 0)
257 bootcfg_info.menuformat = MENUFORMAT_LETTER;
258 else
259 bootcfg_info.menuformat = MENUFORMAT_NUMBER;
260 break;
261
262 case MENUFORMAT_NUMBER:
263 if (cmenu > 9 && bootcfg_info.timeout > 0)
264 cmenu = 9;
265 break;
266 }
267
268 bootcfg_info.nummenu = cmenu;
269 if (bootcfg_info.def < 0)
270 bootcfg_info.def = 0;
271 if (bootcfg_info.def >= cmenu)
272 bootcfg_info.def = cmenu - 1;
273
274 return 0;
275 }
276