Training courses

Kernel and Embedded Linux

Bootlin training courses

Embedded Linux, kernel,
Yocto Project, Buildroot, real-time,
graphics, boot time, debugging...

Bootlin logo

Elixir Cross Referencer

/*	$NetBSD: bootcfg.c,v 1.4 2019/03/31 20:08:45 christos Exp $	*/

/*-
 * Copyright (c) 2008 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/types.h>
#include <sys/reboot.h>

#include <lib/libsa/stand.h>
#include <lib/libsa/bootcfg.h>
#include <lib/libkern/libkern.h>

#define MENUFORMAT_AUTO   0
#define MENUFORMAT_NUMBER 1
#define MENUFORMAT_LETTER 2

#define DEFAULT_FORMAT  MENUFORMAT_AUTO
#define DEFAULT_TIMEOUT 10

struct bootcfg_def bootcfg_info;

void
bootcfg_do_noop(const char *cmd, char *arg)
{
	/* noop, do nothing */
}

/*
 * This function parses a boot.cfg file in the root of the filesystem
 * (if present) and populates the global boot configuration.
 *
 * The file consists of a number of lines each terminated by \n
 * The lines are in the format keyword=value. There should not be spaces
 * around the = sign.
 *
 * perform_bootcfg(conf, command, maxsz)
 *
 * conf		Path to boot.cfg to be passed verbatim to open()
 *
 * command	Pointer to a function that will be called when
 * 		perform_bootcfg() encounters a key (command) it does not
 *		recognize.
 *		The command function is provided both the keyword and
 *		value parsed as arguments to the function.
 *
 * maxsz	Limit the size of the boot.cfg perform_bootcfg() will parse.
 * 		- If maxsz is < 0 boot.cfg will not be processed.
 * 		- If maxsz is = 0 no limit will be imposed but parsing may
 *		  fail due to platform or other constraints e.g. maximum
 *		  segment size.
 *		- If 0 < maxsz and boot.cfg exceeds maxsz it will not be
 *		  parsed, otherwise it will be parsed.
 *
 * The recognised keywords are:
 * banner: text displayed instead of the normal welcome text
 * menu: Descriptive text:command to use
 * timeout: Timeout in seconds (overrides that set by installboot)
 * default: the default menu option to use if Return is pressed
 * consdev: the console device to use
 * format: how menu choices are displayed: (a)utomatic, (n)umbers or (l)etters
 * clear: whether to clear the screen or not
 *
 * Example boot.cfg file:
 * banner=Welcome to NetBSD
 * banner=Please choose the boot type from the following menu
 * menu=Boot NetBSD:boot netbsd
 * menu=Boot into single user mode:boot netbsd -s
 * menu=:boot hd1a:netbsd -cs
 * menu=Goto boot comand line:prompt
 * timeout=10
 * consdev=com0
 * default=1
*/
int
perform_bootcfg(const char *conf, bootcfg_command command, const off_t maxsz)
{
	char *bc, *c;
	int cmenu, cbanner;
	ssize_t len, off;
	int fd, err;
	struct stat st;
	char *next, *key, *value, *v2;

	/* clear bootcfg structure */
	memset(&bootcfg_info, 0, sizeof(bootcfg_info));

	/* set default timeout */
	bootcfg_info.timeout = DEFAULT_TIMEOUT;

	/* automatically switch between letter and numbers on menu */
	bootcfg_info.menuformat = DEFAULT_FORMAT;

	fd = open(conf, 0);
	if (fd < 0)
		return ENOENT;

	err = fstat(fd, &st);
	if (err == -1) {
		close(fd);
		return EIO;
	}

	/* if a maximum size is being requested for the boot.cfg enforce it. */
	if (0 < maxsz && st.st_size > maxsz) {
		close(fd);
		return EFBIG;
	}

	bc = alloc((size_t)st.st_size + 1);
	if (bc == NULL) {
		printf("Could not allocate memory for boot configuration\n");
		close(fd);
		return ENOMEM;
	}

	/*
	 * XXX original code, assumes error or eof return from read()
	 *     results in the entire boot.cfg being buffered.
	 *     - should bail out on read() failing.
	 *     - assumption is made that the file size doesn't change between
	 *       fstat() and read()ing.  probably safe in this context
	 *       arguably should check that reading the file won't overflow
	 *       the storage anyway.
	 */
	off = 0;
	do {
		len = read(fd, bc + off, 1024);
		if (len <= 0)
			break;
		off += len;
	} while (len > 0);
	bc[off] = '\0';

	close(fd);

	/* bc is now assumed to contain the whole boot.cfg file (see above) */

	cmenu = 0;
	cbanner = 0;
	for (c = bc; *c; c = next) {
		key = c;
		/* find end of line */
		for (; *c && *c != '\n'; c++)
			/* zero terminate line on start of comment */
			if (*c == '#')
				*c = 0;
		/* zero terminate line */
		if (*(next = c))
			*next++ = 0;
		/* Look for = separator between key and value */
		for (c = key; *c && *c != '='; c++)
			continue;
		/* Ignore lines with no key=value pair */
		if (*c == '\0')
			continue;

		/* zero terminate key which points to keyword */
		*c++ = 0;
		value = c;
		/* Look for end of line (or file) and zero terminate value */
		for (; *c && *c != '\n'; c++)
			continue;
		*c = 0;

		if (!strncmp(key, "menu", 4)) {
			/*
			 * Parse "menu=<description>:<command>".  If the
			 * description is empty ("menu=:<command>)",
			 * then re-use the command as the description.
			 * Note that the command may contain embedded
			 * colons.
			 */
			if (cmenu >= BOOTCFG_MAXMENU)
				continue;
			bootcfg_info.desc[cmenu] = value;
			for (v2 = value; *v2 && *v2 != ':'; v2++)
				continue;
			if (*v2) {
				*v2++ = 0;
				bootcfg_info.command[cmenu] = v2;
				if (! *value)
					bootcfg_info.desc[cmenu] = v2;
				cmenu++;
			} else {
				/* No delimiter means invalid line */
				bootcfg_info.desc[cmenu] = NULL;
			}
		} else if (!strncmp(key, "banner", 6)) {
			if (cbanner < BOOTCFG_MAXBANNER)
				bootcfg_info.banner[cbanner++] = value;
		} else if (!strncmp(key, "timeout", 7)) {
			if (!isdigit(*value))
				bootcfg_info.timeout = -1;
			else
				bootcfg_info.timeout = atoi(value);
		} else if (!strncmp(key, "default", 7)) {
			bootcfg_info.def = atoi(value) - 1;
		} else if (!strncmp(key, "consdev", 7)) {
			bootcfg_info.consdev = value;
		} else if (!strncmp(key, BOOTCFG_CMD_LOAD, 4)) {
			command(BOOTCFG_CMD_LOAD, value);
		} else if (!strncmp(key, "format", 6)) {
			printf("value:%c\n", *value);
			switch (*value) {
			case 'a':
			case 'A':
				bootcfg_info.menuformat = MENUFORMAT_AUTO;
				break;

			case 'n':
			case 'N':
			case 'd':
			case 'D':
				bootcfg_info.menuformat = MENUFORMAT_NUMBER;
				break;

			case 'l':
			case 'L':
				bootcfg_info.menuformat = MENUFORMAT_LETTER;
				break;
			}
		} else if (!strncmp(key, "clear", 5)) {
			bootcfg_info.clear = !!atoi(value);
		} else if (!strncmp(key, BOOTCFG_CMD_USERCONF, 8)) {
			command(BOOTCFG_CMD_USERCONF, value);
		} else {
			command(key, value);
		}
	}

	switch (bootcfg_info.menuformat) {
	case MENUFORMAT_AUTO:
		if (cmenu > 9 && bootcfg_info.timeout > 0)
			bootcfg_info.menuformat = MENUFORMAT_LETTER;
		else
			bootcfg_info.menuformat = MENUFORMAT_NUMBER;
		break;

	case MENUFORMAT_NUMBER:
		if (cmenu > 9 && bootcfg_info.timeout > 0)
			cmenu = 9;
		break;
	}

	bootcfg_info.nummenu = cmenu;
	if (bootcfg_info.def < 0)
		bootcfg_info.def = 0;
	if (bootcfg_info.def >= cmenu)
		bootcfg_info.def = cmenu - 1;

	return 0;
}