Home | History | Annotate | Line # | Download | only in libsa
      1 /*	$NetBSD: bootcfg.c,v 1.10 2025/05/06 18:16:12 pgoyette 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
     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
    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, "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 {
    253 			command(key, value);
    254 		}
    255 	}
    256 
    257 	switch (bootcfg_info.menuformat) {
    258 	case MENUFORMAT_AUTO:
    259 		if (cmenu > 9 && bootcfg_info.timeout > 0)
    260 			bootcfg_info.menuformat = MENUFORMAT_LETTER;
    261 		else
    262 			bootcfg_info.menuformat = MENUFORMAT_NUMBER;
    263 		break;
    264 
    265 	case MENUFORMAT_NUMBER:
    266 		if (cmenu > 9 && bootcfg_info.timeout > 0)
    267 			cmenu = 9;
    268 		break;
    269 	}
    270 
    271 	bootcfg_info.nummenu = cmenu;
    272 	if (bootcfg_info.def < 0)
    273 		bootcfg_info.def = 0;
    274 	if (bootcfg_info.def >= cmenu)
    275 		bootcfg_info.def = cmenu - 1;
    276 
    277 	return 0;
    278 }
    279 
    280 void
    281 print_bootcfg_banner(const char *bootprog_name, const char *bootprog_rev)
    282 {
    283 	int n = 0;
    284 
    285 	if (bootcfg_info.banner[0]) {
    286 		for (; n < BOOTCFG_MAXBANNER && bootcfg_info.banner[n]; n++)
    287 			printf("%s\n", bootcfg_info.banner[n]);
    288 		return;
    289 	}
    290 
    291 	/* If the user has not specified a banner, print a default one. */
    292 
    293 	printf("\n");
    294 	printf("  \\\\-__,------,___.\n");
    295 	printf("   \\\\        __,---`  %s\n", bootprog_name);
    296 	printf("    \\\\       `---,_.  Revision %s\n", bootprog_rev);
    297 	printf("     \\\\-,_____,.---`\n");
    298 	printf("      \\\\\n");
    299 	printf("       \\\\\n");
    300 	printf("        \\\\\n\n");
    301 }
    302