/*      $NetBSD: boot.c,v 1.8 2016/06/11 06:58:42 dholland Exp $        */

/*
* Copyright (c) 2009 NONAKA Kimihiro <[email protected]>
* 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 AUTHOR ``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 AUTHOR 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/bootblock.h>
#include <sys/boot_flag.h>

#include "boot.h"
#include "bootinfo.h"
#include "bootmenu.h"
#include "disk.h"
#include "unixdev.h"
#include "pathnames.h"

#include <lib/libsa/loadfile.h>
#include <lib/libsa/ufs.h>

#include "compat_linux.h"

static const char * const names[][2] = {
       { "netbsd", "netbsd.gz" },
       { "netbsd.old", "netbsd.old.gz", },
       { "onetbsd", "onetbsd.gz" },
};

char *default_devname;
uint default_unit, default_partition;
const char *default_filename;
int default_timeout = 5;

static char probed_disks[256];
static char bootconfpath[1024];

static void bootcmd_help(char *);
static void bootcmd_ls(char *);
static void bootcmd_quit(char *);
static void bootcmd_boot(char *);
static void bootcmd_disk(char *);
#ifdef SUPPORT_CONSDEV
static void bootcmd_consdev(char *);
#endif

static const struct bootblk_command {
       const char *c_name;
       void (*c_fn)(char *arg);
} bootcmds[] = {
       { "help",       bootcmd_help },
       { "?",          bootcmd_help },
       { "quit",       bootcmd_quit },
       { "ls",         bootcmd_ls },
       { "boot",       bootcmd_boot },
       { "disk",       bootcmd_disk },
#ifdef SUPPORT_CONSDEV
       { "consdev",    bootcmd_consdev },
#endif
       { NULL,         NULL },
};

static struct btinfo_howto bi_howto;

static void print_banner(void);
static int exec_netbsd(const char *file, int howto);

int
parsebootfile(const char *fname, char **fsname, char **devname,
       uint *unit, uint *partition, const char **file)
{
       const char *col;

       *fsname = "ufs";
       *devname = default_devname;
       *unit = default_unit;
       *partition = default_partition;
       *file = default_filename;

       if (fname == NULL)
               return 0;

       if ((col = strchr(fname, ':'))) {       /* device given */
               static char savedevname[MAXDEVNAME+1];
               int devlen;
               unsigned int u = 0, p = 0;
               int i = 0;

               devlen = col - fname;
               if (devlen > MAXDEVNAME)
                       return EINVAL;

#define isvalidname(c) ((c) >= 'a' && (c) <= 'z')
               if (!isvalidname(fname[i]))
                       return EINVAL;
               do {
                       savedevname[i] = fname[i];
                       i++;
               } while (isvalidname(fname[i]));
               savedevname[i] = '\0';

#define isnum(c) ((c) >= '0' && (c) <= '9')
               if (i < devlen) {
                       if (!isnum(fname[i]))
                               return (EUNIT);
                       do {
                               u *= 10;
                               u += fname[i++] - '0';
                       } while (isnum(fname[i]));
               }

#define isvalidpart(c) ((c) >= 'a' && (c) < 'a' + MAXPARTITIONS)
               if (i < devlen) {
                       if (!isvalidpart(fname[i]))
                               return (EPART);
                       p = fname[i++] - 'a';
               }

               if (i != devlen)
                       return ENXIO;

               *devname = savedevname;
               *unit = u;
               *partition = p;
               fname = col + 1;
       }

       if (*fname)
               *file = fname;

       return 0;
}

char *
sprint_bootsel(const char *filename)
{
       static char buf[80];
       char *fsname, *devname;
       uint unit, partition;
       const char *file;

       if (parsebootfile(filename, &fsname, &devname, &unit, &partition,
           &file) == 0) {
               snprintf(buf, sizeof(buf), "%s%d%c:%s", devname, unit,
                   'a' + partition, file);
               return buf;
       }
       return "(invalid)";
}

static void
print_banner(void)
{
       extern const char bootprog_name[];
       extern const char bootprog_rev[];

       printf("\n");
       printf(">> %s, Revision %s\n", bootprog_name, bootprog_rev);
}

void
boot(dev_t bootdev)
{
       extern char twiddle_toggle;
       int currname;
       int c;

       consinit(CONSDEV_GLASS, -1);

       twiddle_toggle = 1;     /* no twiddling until we're ready */

       /* set default value: hd0a:netbsd */
       default_devname = "hd";
       default_unit = 0;
       default_partition = 0;
       default_filename = names[0][0];

       diskprobe(probed_disks, sizeof(probed_disks));

       snprintf(bootconfpath, sizeof(bootconfpath), "%s%d%c:%s",
           default_devname, default_unit, 'a' + default_partition,
           BOOTCFG_FILENAME);
       parsebootconf(bootconfpath);

#ifdef SUPPORT_CONSDEV
       /*
        * If console set in boot.cfg, switch to it.
        * This will print the banner, so we don't need to explicitly do it
        */
       if (bootcfg_info.consdev)
               bootcmd_consdev(bootcfg_info.consdev);
       else
#endif
               print_banner();

       printf("\ndisks: %s\n", probed_disks);

       /* Display the menu, if applicable */
       twiddle_toggle = 0;
       if (bootcfg_info.nummenu > 0) {
               /* Does not return */
               doboottypemenu();
       }

       printf("Press return to boot now, any other key for boot menu\n");
       currname = 0;
       for (currname = 0; currname < __arraycount(names); currname++) {
               printf("booting %s - starting in ",
                   sprint_bootsel(names[currname][0]));

               c = awaitkey((bootcfg_info.timeout < 0) ? 0
                   : bootcfg_info.timeout, 1);
               if ((c != '\r') && (c != '\n') && (c != '\0')) {
                       printf("type \"?\" or \"help\" for help.\n");
                       bootmenu(); /* does not return */
               }

               /*
                * try pairs of names[] entries, foo and foo.gz
                */
               /* don't print "booting..." again */
               bootit(names[currname][0], 0, 0);
               /* since it failed, try compressed bootfile. */
               bootit(names[currname][1], 0, 1);
       }

       bootmenu(); /* does not return */
}

void
bootit(const char *filename, int howto, int tell)
{

       if (tell) {
               printf("booting %s", sprint_bootsel(filename));
               if (howto)
                       printf(" (howto 0x%x)", howto);
               printf("\n");
       }

       if (exec_netbsd(filename, howto) < 0) {
               printf("boot: %s: %s\n", sprint_bootsel(filename),
                      strerror(errno));
       } else {
               printf("boot returned\n");
       }
}

static int
exec_netbsd(const char *file, int howto)
{
       u_long marks[MARK_MAX];

       BI_ALLOC(BTINFO_MAX);

       bi_howto.howto = howto;
       BI_ADD(&bi_howto, BTINFO_HOWTO, sizeof(bi_howto));

       if (loadfile_zboot(file, marks, LOAD_KERNEL) == -1)
               goto out;

       /*NOTREACHED*/
       return 0;

out:
       BI_FREE();
       bootinfo = 0;
       return -1;
}

/*
* bootmenu
*/
static char *gettrailer(char *arg);
static int parseopts(const char *opts, int *howto);
static int parseboot(char *arg, char **filename, int *howto);

/* ARGSUSED */
static void
bootcmd_help(char *arg)
{

       printf("commands are:\n"
           "boot [xdNx:][filename] [-1acdqsv]\n"
           "     (ex. \"boot hd0a:netbsd.old -s\")\n"
           "     (ex. \"boot path:/mnt/card/netbsd -1\")\n"
           "ls [path]\n"
#ifdef SUPPORT_CONSDEV
           "consdev {glass|com [speed]}\n"
#endif
           "disk\n"
           "help|?\n"
           "quit\n");
}

/* ARGSUSED */
static void
bootcmd_quit(char *arg)
{

       printf("Exiting...\n");
       exit(0);
       /*NOTREACHED*/
}

static void
bootcmd_ls(char *arg)
{
       const char *save = default_filename;

       default_filename = "/";
       ls(arg);
       default_filename = save;
}

static void
bootcmd_boot(char *arg)
{
       char *filename;
       int howto;

       if (parseboot(arg, &filename, &howto)) {
               bootit(filename, howto, 1);
       }
}

/* ARGSUSED */
static void
bootcmd_disk(char *arg)
{

       printf("disks: %s\n", probed_disks);
}

#ifdef SUPPORT_CONSDEV
static const struct cons_devs {
       const char      *name;
       int             tag;
} cons_devs[] = {
       { "glass",      CONSDEV_GLASS },
       { "com",        CONSDEV_COM0 },
       { "com0",       CONSDEV_COM0 },
       { NULL,         0 }
};

static void
bootcmd_consdev(char *arg)
{
       const struct cons_devs *cdp;
       char *p;
       int speed = 9600;

       p = strchr(arg, ' ');
       if (p != NULL) {
               *p++ = '\0';
               speed = atoi(p);
       }
       for (cdp = cons_devs; cdp->name != NULL; cdp++) {
               if (strcmp(arg, cdp->name) == 0) {
                       consinit(cdp->tag, speed);
                       print_banner();
                       return;
               }
       }
       printf("invalid console device.\n");
}
#endif

void
docommand(char *arg)
{
       char *options;
       int i;

       options = gettrailer(arg);

       for (i = 0; bootcmds[i].c_name != NULL; i++) {
               if (strcmp(arg, bootcmds[i].c_name) == 0) {
                       (*bootcmds[i].c_fn)(options);
                       return;
               }
       }

       printf("unknown command\n");
       bootcmd_help(NULL);
}

void
bootmenu(void)
{
       char input[256];
       char *c;

       for (;;) {
               c = input;

               input[0] = '\0';
               printf("> ");
               kgets(input, sizeof(input));

               /*
                * Skip leading whitespace.
                */
               while (*c == ' ') {
                       c++;
               }
               if (*c != '\0') {
                       docommand(c);
               }
       }
}

/*
* chops the head from the arguments and returns the arguments if any,
* or possibly an empty string.
*/
static char *
gettrailer(char *arg)
{
       char *options;

       if ((options = strchr(arg, ' ')) == NULL)
               return ("");
       else
               *options++ = '\0';

       /* trim leading blanks */
       while (*options == ' ')
               options++;

       return options;
}

static int
parseopts(const char *opts, int *howto)
{
       int r, tmpopt = 0;

       opts++;         /* skip - */
       while (*opts && *opts != ' ') {
               r = 0;
               BOOT_FLAG(*opts, r);
               if (r == 0) {
                       printf("-%c: unknown flag\n", *opts);
                       bootcmd_help(NULL);
                       return 0;
               }
               tmpopt |= r;
               opts++;
       }

       *howto = tmpopt;
       return 1;
}

static int
parseboot(char *arg, char **filename, int *howto)
{
       char *opts = NULL;

       *filename = 0;
       *howto = 0;

       /* if there were no arguments */
       if (arg == NULL || *arg == '\0')
               return 1;

       /* format is... */
       /* [[xxNx:]filename] [-adqsv] */

       /* check for just args */
       if (arg[0] == '-') {
               opts = arg;
       } else {
               /* there's a file name */
               *filename = arg;

               opts = gettrailer(arg);
               if (opts == NULL || *opts == '\0') {
                       opts = NULL;
               } else if (*opts != '-') {
                       printf("invalid arguments\n");
                       bootcmd_help(NULL);
                       return 0;
               }
       }

       /* at this point, we have dealt with filenames. */

       /* now, deal with options */
       if (opts) {
               if (parseopts(opts, howto) == 0) {
                       return 0;
               }
       }
       return 1;
}