There are several use cases where a redundant Linux system is needed. The barebox bootchooser framework provides the building blocks to model different use cases without the need to start from the scratch over and over again. The bootchooser works on abstract boot targets, each with a set of properties and implements an algorithm which selects the highest priority target to boot. See the documentation contained in this patch for more information. Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de> Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de> Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
352 lines
7.8 KiB
C
352 lines
7.8 KiB
C
/*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; version 2.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*/
|
|
|
|
#include <environment.h>
|
|
#include <bootchooser.h>
|
|
#include <globalvar.h>
|
|
#include <magicvar.h>
|
|
#include <watchdog.h>
|
|
#include <command.h>
|
|
#include <readkey.h>
|
|
#include <common.h>
|
|
#include <blspec.h>
|
|
#include <libgen.h>
|
|
#include <malloc.h>
|
|
#include <bootm.h>
|
|
#include <glob.h>
|
|
#include <init.h>
|
|
#include <menu.h>
|
|
#include <fs.h>
|
|
|
|
#include <linux/stat.h>
|
|
|
|
int bootentries_add_entry(struct bootentries *entries, struct bootentry *entry)
|
|
{
|
|
list_add_tail(&entry->list, &entries->entries);
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct bootentries *bootentries_alloc(void)
|
|
{
|
|
struct bootentries *bootentries;
|
|
|
|
bootentries = xzalloc(sizeof(*bootentries));
|
|
INIT_LIST_HEAD(&bootentries->entries);
|
|
|
|
if (IS_ENABLED(CONFIG_MENU)) {
|
|
bootentries->menu = menu_alloc();
|
|
menu_add_title(bootentries->menu, basprintf("boot"));
|
|
}
|
|
|
|
return bootentries;
|
|
}
|
|
|
|
void bootentries_free(struct bootentries *bootentries)
|
|
{
|
|
struct bootentry *be, *tmp;
|
|
|
|
list_for_each_entry_safe(be, tmp, &bootentries->entries, list) {
|
|
list_del(&be->list);
|
|
free(be->title);
|
|
free(be->description);
|
|
free(be->me.display);
|
|
be->release(be);
|
|
}
|
|
|
|
if (bootentries->menu) {
|
|
int i;
|
|
for (i = 0; i < bootentries->menu->display_lines; i++)
|
|
free(bootentries->menu->display[i]);
|
|
free(bootentries->menu->display);
|
|
}
|
|
free(bootentries->menu);
|
|
free(bootentries);
|
|
}
|
|
|
|
struct bootentry_script {
|
|
struct bootentry entry;
|
|
char *scriptpath;
|
|
};
|
|
|
|
/*
|
|
* Start a single boot script. 'path' is a full path to a boot script.
|
|
*/
|
|
static int bootscript_boot(struct bootentry *entry, int verbose, int dryrun)
|
|
{
|
|
struct bootentry_script *bs = container_of(entry, struct bootentry_script, entry);
|
|
int ret;
|
|
|
|
struct bootm_data data = {};
|
|
|
|
if (dryrun) {
|
|
printf("Would run %s\n", bs->scriptpath);
|
|
return 0;
|
|
}
|
|
|
|
globalvar_set_match("linux.bootargs.dyn.", "");
|
|
|
|
ret = run_command(bs->scriptpath);
|
|
if (ret) {
|
|
printf("Running %s failed\n", bs->scriptpath);
|
|
goto out;
|
|
}
|
|
|
|
bootm_data_init_defaults(&data);
|
|
|
|
if (verbose)
|
|
data.verbose = verbose;
|
|
if (dryrun)
|
|
data.dryrun = dryrun;
|
|
|
|
ret = bootm_boot(&data);
|
|
if (ret)
|
|
pr_err("Booting '%s' failed: %s\n", basename(bs->scriptpath), strerror(-ret));
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static unsigned int boot_watchdog_timeout;
|
|
|
|
void boot_set_watchdog_timeout(unsigned int timeout)
|
|
{
|
|
boot_watchdog_timeout = timeout;
|
|
}
|
|
|
|
static int init_boot_watchdog_timeout(void)
|
|
{
|
|
return globalvar_add_simple_int("boot.watchdog_timeout",
|
|
&boot_watchdog_timeout, "%u");
|
|
}
|
|
late_initcall(init_boot_watchdog_timeout);
|
|
|
|
BAREBOX_MAGICVAR_NAMED(global_watchdog_timeout, global.boot.watchdog_timeout,
|
|
"Watchdog enable timeout in seconds before booting");
|
|
|
|
int boot_entry(struct bootentry *be, int verbose, int dryrun)
|
|
{
|
|
int ret;
|
|
|
|
printf("booting '%s'\n", be->title);
|
|
|
|
if (IS_ENABLED(CONFIG_WATCHDOG) && boot_watchdog_timeout) {
|
|
ret = watchdog_set_timeout(boot_watchdog_timeout);
|
|
if (ret)
|
|
pr_warn("Failed to enable watchdog: %s\n", strerror(-ret));
|
|
}
|
|
|
|
ret = be->boot(be, verbose, dryrun);
|
|
|
|
if (ret)
|
|
printf("booting '%s' failed: %s\n", be->title, strerror(-ret));
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void bootsource_action(struct menu *m, struct menu_entry *me)
|
|
{
|
|
struct bootentry *be = container_of(me, struct bootentry, me);
|
|
int ret;
|
|
|
|
ret = boot_entry(be, 0, 0);
|
|
if (ret)
|
|
printf("Booting failed with: %s\n", strerror(-ret));
|
|
|
|
printf("Press any key to continue\n");
|
|
|
|
read_key();
|
|
}
|
|
|
|
static void bootscript_entry_release(struct bootentry *entry)
|
|
{
|
|
struct bootentry_script *bs = container_of(entry, struct bootentry_script, entry);
|
|
|
|
free(bs->scriptpath);
|
|
free(bs);
|
|
}
|
|
|
|
/*
|
|
* bootscript_create_entry - create a boot entry from a script name
|
|
*/
|
|
static int bootscript_create_entry(struct bootentries *bootentries, const char *name)
|
|
{
|
|
struct bootentry_script *bs;
|
|
enum filetype type;
|
|
|
|
type = file_name_detect_type(name);
|
|
if (type != filetype_sh)
|
|
return -EINVAL;
|
|
|
|
bs = xzalloc(sizeof(*bs));
|
|
bs->entry.me.type = MENU_ENTRY_NORMAL;
|
|
bs->entry.release = bootscript_entry_release;
|
|
bs->entry.boot = bootscript_boot;
|
|
bs->scriptpath = xstrdup(name);
|
|
bs->entry.title = xstrdup(basename(bs->scriptpath));
|
|
bs->entry.description = basprintf("script: %s", name);
|
|
bootentries_add_entry(bootentries, &bs->entry);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* bootscript_scan_path - create boot entries from a path
|
|
*
|
|
* path can either be a full path to a bootscript or a full path to a diretory
|
|
* containing bootscripts.
|
|
*/
|
|
static int bootscript_scan_path(struct bootentries *bootentries, const char *path)
|
|
{
|
|
struct stat s;
|
|
char *files;
|
|
int ret, i;
|
|
int found = 0;
|
|
glob_t globb;
|
|
|
|
ret = stat(path, &s);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (!S_ISDIR(s.st_mode)) {
|
|
ret = bootscript_create_entry(bootentries, path);
|
|
if (ret)
|
|
return ret;
|
|
return 1;
|
|
}
|
|
|
|
files = basprintf("%s/*", path);
|
|
|
|
glob(files, 0, NULL, &globb);
|
|
|
|
for (i = 0; i < globb.gl_pathc; i++) {
|
|
char *bootscript_path = globb.gl_pathv[i];
|
|
|
|
if (*basename(bootscript_path) == '.')
|
|
continue;
|
|
|
|
bootscript_create_entry(bootentries, bootscript_path);
|
|
found++;
|
|
}
|
|
|
|
globfree(&globb);
|
|
free(files);
|
|
|
|
ret = found;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* bootentry_create_from_name - create boot entries from a name
|
|
*
|
|
* name can be:
|
|
* - a name of a boot script under /env/boot
|
|
* - a full path of a boot script
|
|
* - a device name
|
|
* - a cdev name
|
|
* - a full path of a directory containing bootloader spec entries
|
|
* - a full path of a directory containing bootscripts
|
|
* - a nfs:// path
|
|
*
|
|
* Returns the number of entries found or a negative error code.
|
|
*/
|
|
int bootentry_create_from_name(struct bootentries *bootentries,
|
|
const char *name)
|
|
{
|
|
int found = 0, ret;
|
|
|
|
if (IS_ENABLED(CONFIG_BLSPEC)) {
|
|
ret = blspec_scan_devicename(bootentries, name);
|
|
if (ret > 0)
|
|
found += ret;
|
|
|
|
if (*name == '/' || !strncmp(name, "nfs://", 6)) {
|
|
ret = blspec_scan_directory(bootentries, name);
|
|
if (ret > 0)
|
|
found += ret;
|
|
}
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BOOTCHOOSER) && !strcmp(name, "bootchooser")) {
|
|
ret = bootchooser_create_bootentry(bootentries);
|
|
if (ret > 0)
|
|
found += ret;
|
|
}
|
|
|
|
if (!found) {
|
|
char *path;
|
|
|
|
if (*name != '/')
|
|
path = basprintf("/env/boot/%s", name);
|
|
else
|
|
path = xstrdup(name);
|
|
|
|
ret = bootscript_scan_path(bootentries, path);
|
|
if (ret > 0)
|
|
found += ret;
|
|
|
|
free(path);
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
/*
|
|
* bootsources_menu - show a menu from an array of names
|
|
*/
|
|
void bootsources_menu(struct bootentries *bootentries, int timeout)
|
|
{
|
|
struct bootentry *entry;
|
|
struct menu_entry *back_entry;
|
|
|
|
if (!IS_ENABLED(CONFIG_MENU)) {
|
|
printf("no menu support available\n");
|
|
return;
|
|
}
|
|
|
|
bootentries_for_each_entry(bootentries, entry) {
|
|
if (!entry->me.display)
|
|
entry->me.display = xstrdup(entry->title);
|
|
entry->me.action = bootsource_action;
|
|
menu_add_entry(bootentries->menu, &entry->me);
|
|
}
|
|
|
|
back_entry = xzalloc(sizeof(*back_entry));
|
|
back_entry->display = "back";
|
|
back_entry->type = MENU_ENTRY_NORMAL;
|
|
back_entry->non_re_ent = 1;
|
|
menu_add_entry(bootentries->menu, back_entry);
|
|
|
|
if (timeout >= 0)
|
|
bootentries->menu->auto_select = timeout;
|
|
|
|
menu_show(bootentries->menu);
|
|
|
|
menu_remove_entry(bootentries->menu, back_entry);
|
|
free(back_entry);
|
|
}
|
|
|
|
/*
|
|
* bootsources_list - list boot entries from an array of names
|
|
*/
|
|
void bootsources_list(struct bootentries *bootentries)
|
|
{
|
|
struct bootentry *entry;
|
|
|
|
printf("%-20s\n", "title");
|
|
printf("%-20s\n", "------");
|
|
|
|
bootentries_for_each_entry(bootentries, entry)
|
|
printf("%-20s %s\n", entry->title, entry->description);
|
|
}
|
|
|
|
BAREBOX_MAGICVAR_NAMED(global_boot_default, global.boot.default, "default boot order");
|