/*      $NetBSD: bootcfg.c,v 1.10 2025/05/06 18:16:12 pgoyette 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
#ifndef DEFAULT_TIMEOUT
#define DEFAULT_TIMEOUT 10
#endif

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
* root: the root 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 command 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, resid;
       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) {
               /* file descriptor may not be backed by a libsa file-system */
               st.st_size = maxsz;
       }

       /* 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;
       resid = st.st_size;
       do {
               len = read(fd, bc + off, uimin(1024, resid));
               if (len <= 0)
                       break;
               off += len;
               resid -= len;
       } while (len > 0 && resid > 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, "root", 4)) {
                       bootcfg_info.root = 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 {
                       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;
}

void
print_bootcfg_banner(const char *bootprog_name, const char *bootprog_rev)
{
       int n = 0;

       if (bootcfg_info.banner[0]) {
               for (; n < BOOTCFG_MAXBANNER && bootcfg_info.banner[n]; n++)
                       printf("%s\n", bootcfg_info.banner[n]);
               return;
       }

       /* If the user has not specified a banner, print a default one. */

       printf("\n");
       printf("  \\\\-__,------,___.\n");
       printf("   \\\\        __,---`  %s\n", bootprog_name);
       printf("    \\\\       `---,_.  Revision %s\n", bootprog_rev);
       printf("     \\\\-,_____,.---`\n");
       printf("      \\\\\n");
       printf("       \\\\\n");
       printf("        \\\\\n\n");
}