summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml18
-rw-r--r--TODO39
-rw-r--r--configure.ac10
-rw-r--r--discover/Makefile.am7
-rw-r--r--discover/boot.c147
-rw-r--r--discover/device-handler.c2
-rw-r--r--discover/discover-server.c15
-rw-r--r--discover/elf.c86
-rw-r--r--discover/elf.h29
-rw-r--r--discover/event.c1
-rw-r--r--discover/grub2/blscfg.c19
-rw-r--r--discover/grub2/builtins.c208
-rw-r--r--discover/grub2/env.c2
-rw-r--r--discover/grub2/grub2-parser.y17
-rw-r--r--discover/grub2/grub2.c110
-rw-r--r--discover/grub2/grub2.h32
-rw-r--r--discover/grub2/script.c29
-rw-r--r--discover/ipmi.c1
-rw-r--r--discover/ipmi.h23
-rw-r--r--discover/paths.c3
-rw-r--r--discover/platform-powerpc.c187
-rw-r--r--discover/platform.c15
-rw-r--r--discover/platform.h5
-rw-r--r--discover/pxe-parser.c7
-rw-r--r--discover/user-event.c19
-rw-r--r--discover/yaboot-parser.c4
-rw-r--r--doc/Makefile19
-rw-r--r--doc/components.rst29
-rw-r--r--doc/conf.py57
-rw-r--r--doc/dev/design.rst31
-rw-r--r--doc/dev/linux-image.rst35
-rw-r--r--doc/dev/submitting.rst26
-rw-r--r--doc/func/autoboot.rst32
-rw-r--r--doc/func/ipmi.rst29
-rw-r--r--doc/func/parsers.rst18
-rw-r--r--doc/func/plugins.rst27
-rw-r--r--doc/func/snapshots.rst34
-rw-r--r--doc/func/user_interface.rst51
-rw-r--r--doc/index.rst53
-rw-r--r--doc/overview.rst12
-rw-r--r--doc/platforms.rst16
-rw-r--r--docker/Dockerfile.builder2
-rwxr-xr-xdocker/build-pb2
-rw-r--r--lib/param_list/param_list.c1
-rw-r--r--lib/pb-config/pb-config.c12
-rw-r--r--lib/pb-protocol/pb-protocol.c70
-rw-r--r--lib/types/types.h7
-rw-r--r--s0
-rw-r--r--test/parser/Makefile.am15
-rw-r--r--test/parser/data/grub2-rhcos-ootpa.conf194
-rw-r--r--test/parser/data/grub2-rhel8.conf190
-rw-r--r--test/parser/test-grub2-default-id-space.c34
-rw-r--r--test/parser/test-grub2-default-id.c34
-rw-r--r--test/parser/test-grub2-default-multiword.c4
-rw-r--r--test/parser/test-grub2-devpath-scripting.c56
-rw-r--r--test/parser/test-grub2-devpath.c88
-rw-r--r--test/parser/test-grub2-rhcos-ootpa.c38
-rw-r--r--test/parser/test-grub2-rhel8.c21
-rw-r--r--test/parser/test-grub2-search-args.c35
-rw-r--r--test/parser/test-grub2-search-label.c47
-rw-r--r--test/parser/test-grub2-search-uuid.c55
-rw-r--r--test/parser/test-grub2-source-functions.c46
-rw-r--r--test/parser/test-grub2-source-recursion-infinite.c43
-rw-r--r--test/parser/test-grub2-source-recursion.c58
-rw-r--r--test/parser/test-grub2-source.c54
-rw-r--r--ui/ncurses/nc-config-help.c8
-rw-r--r--ui/ncurses/nc-config.c44
-rw-r--r--ui/ncurses/nc-cui.c4
-rw-r--r--ui/ncurses/nc-sysinfo.c9
-rw-r--r--utils/Makefile.am3
-rw-r--r--utils/hooks/30-dtb-updates.c2
-rw-r--r--utils/pb-console6
-rwxr-xr-xutils/pb-exec13
-rwxr-xr-xutils/pb-plugin4
74 files changed, 2428 insertions, 275 deletions
diff --git a/.travis.yml b/.travis.yml
index cff60dc..5256628 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,11 +8,29 @@ matrix:
include:
- os: linux
name: "linux"
+ env: DOCS="true"
- os: linux-ppc64le
name: "linux-ppc64le"
+addons:
+ apt:
+ packages:
+ - python-pip
+
before_script:
- ./docker/build-builder
+install:
+ - "sudo pip install sphinx"
script:
- ./docker/build-pb --check --verbose --configure-opts "--enable-platform-arm64 --enable-platform-powerpc"
+ - if [ "$DOCS" == "true" ]; then cd doc/; make html; fi
+
+deploy:
+ provider: pages
+ skip-cleanup: true
+ github-token: $GITHUB_TOKEN
+ local_dir: "doc/_build/html"
+ on:
+ branch: master
+ condition: "$DOCS = true"
diff --git a/TODO b/TODO
deleted file mode 100644
index 359e302..0000000
--- a/TODO
+++ /dev/null
@@ -1,39 +0,0 @@
-
-Todo for petitboot (LOTS !):
-
- * GUI code is still based on the original mockup hack. Needs to be completely
- refactored. We need to define some useable canvas/widget classes in twin and
- use them
- * Add some useable GUI for graphic modes selection and return to GameOS instead
- of the current hacks
- * Rework keyboard handling. twin needs a proper input method support, including
- at least a way to use the kernel keymaps and modifiers key handling. Remove
- some of the keyboard hacks from twin_fbdev and move console switching to
- petitboot
- * Mouse support (twin/petitboot supports it somewhat but it's not working,
- check why, probably an udev issue or missing kernel driver)
- * Saving settings in nvram
- - default boot option
- - default language (when language support is in)
- - video mode (can we read gameos' video settings instead?)
- * uncrappify focus box animation code (do it differently so it doesn't totally
- sucks on the left pane due to the time needed for the alpha blending when the
- box "touches" an icon)
- * Fix issues with yaboot.conf parsing and kboot.conf parsing. Add a proper
- native file format instead of the current hack (or make yaboot.conf native?),
- add icon support to all formats
- * Fixup issues with races vs. udev-helper. Maybe have a global way to keep
- track of present devices, properly make sure add/remove are ordered, etc...
- * Get rid of libm by building libpng without floating point support (and build
- it without write support while at it, to save more space)
- * Define a proper binary format for converted twin fonts and use a proper font
- with multiple language support rather than the stroke font which does US
- ASCII only at the moment
- * i18n support in config files
- * fix title/subtitles "leaking" out of the right side focus box
- * get YDL to fix their config file on their install CD or add a hack to
- recognize it and fix it up
-
-PATCHES WELCOME !
-
-
diff --git a/configure.ac b/configure.ac
index 5d541fb..6683be6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -66,6 +66,15 @@ AC_CHECK_LIB([udev], [udev_new],
[AC_MSG_FAILURE([The libudev development library is required by petitboot. Try installing the package libudev-dev or libudev-devel.])]
)
+AC_CHECK_LIB([elf], [elf_begin],
+ [ELF_LIBS=-lelf],
+ [AC_MSG_FAILURE([The libelf development library is required by petitboot. Try installing the package libelf-dev or elfutils-libelf-devel.])]
+)
+
+AC_CHECK_HEADERS([elfutils/libdw.h],
+ [],
+ [AC_MSG_FAILURE([elfutils/libdw.h not found. Try installing the package libdw-dev or elfutils-devel.])]
+)
PKG_CHECK_EXISTS(libudev >= 218, [old_udev=no], [old_udev=yes])
if test "$old_udev" = "yes" ; then
AC_DEFINE(UDEV_LOGGING, 1, [Support old udev logging interface])
@@ -476,6 +485,7 @@ AS_IF(
)
AC_SUBST([UDEV_LIBS])
+AC_SUBST([ELF_LIBS])
AC_SUBST([DEVMAPPER_LIBS])
AC_SUBST([CRYPT_LIBS])
AC_SUBST([FDT_LIBS])
diff --git a/discover/Makefile.am b/discover/Makefile.am
index bfe33fa..2339203 100644
--- a/discover/Makefile.am
+++ b/discover/Makefile.am
@@ -58,7 +58,8 @@ discover_pb_discover_LDADD = \
discover/native/native-parser.ro \
discover/platform.ro \
$(core_lib) \
- $(UDEV_LIBS)
+ $(UDEV_LIBS) \
+ $(ELF_LIBS)
discover_pb_discover_LDFLAGS = \
$(AM_LDFLAGS) \
@@ -79,7 +80,9 @@ discover_platform_ro_SOURCES = \
discover/ipmi.h \
discover/dt.c \
discover/dt.h \
- discover/hostboot.h
+ discover/hostboot.h \
+ discover/elf.c \
+ discover/elf.h
if PLATFORM_ARM64
discover_platform_ro_SOURCES += discover/platform-arm64.c
diff --git a/discover/boot.c b/discover/boot.c
index 91fc46d..00507b7 100644
--- a/discover/boot.c
+++ b/discover/boot.c
@@ -25,6 +25,7 @@
#include "paths.h"
#include "resource.h"
#include "platform.h"
+#include "sysinfo.h"
#include <security/security.h>
@@ -60,30 +61,50 @@ static void __attribute__((format(__printf__, 4, 5))) update_status(
*/
static int kexec_load(struct boot_task *boot_task)
{
+ const char *load_args[] = {"-l", "-s"};
+ const struct system_info *sysinfo;
struct process *process;
char *s_initrd = NULL;
char *s_args = NULL;
const char *argv[8];
char *s_dtb = NULL;
const char **p;
+ char *err_buf;
int result;
+ size_t i;
+ sysinfo = system_info_get();
boot_task->local_initrd_override = NULL;
boot_task->local_dtb_override = NULL;
boot_task->local_image_override = NULL;
- if ((result = validate_boot_files(boot_task))) {
- if (result == KEXEC_LOAD_DECRYPTION_FALURE) {
- pb_log("%s: Aborting kexec due to"
- " decryption failure\n", __func__);
- }
- if (result == KEXEC_LOAD_SIGNATURE_FAILURE) {
- pb_log("%s: Aborting kexec due to signature"
- " verification failure\n", __func__);
+ result = validate_boot_files(boot_task);
+ if (result) {
+ const char *msg;
+
+ switch (result) {
+ case KEXEC_LOAD_DECRYPTION_FALURE:
+ msg = _("decryption failed");
+ break;
+ case KEXEC_LOAD_SIGNATURE_FAILURE:
+ msg = _("signature verification failed");
+ break;
+ case KEXEC_LOAD_SIG_SETUP_INVALID:
+ msg = _("invalid signature configuration");
+ break;
+ default:
+ msg = _("unknown verification failure");
}
- goto abort_kexec;
+ update_status(boot_task->status_fn, boot_task->status_arg,
+ STATUS_ERROR,
+ _("Boot verification failure: %s"), msg);
+ pb_log_fn("Aborting kexec due to verification failure: %s",
+ msg);
+
+ validate_boot_files_cleanup(boot_task);
+ return result;
}
const char* local_initrd = (boot_task->local_initrd_override) ?
@@ -93,20 +114,10 @@ static int kexec_load(struct boot_task *boot_task)
const char* local_image = (boot_task->local_image_override) ?
boot_task->local_image_override : boot_task->local_image;
- process = process_create(boot_task);
- if (!process) {
- pb_log_fn("failed to create process\n");
- return -1;
- }
-
- process->path = pb_system_apps.kexec;
- process->argv = argv;
- process->keep_stdout = true;
- process->add_stderr = true;
-
+ /* set up process arguments */
p = argv;
*p++ = pb_system_apps.kexec; /* 1 */
- *p++ = "-l"; /* 2 */
+ *p++ = NULL; /* 2; modified below */
if (pb_log_get_debug()) {
*p++ = "--debug"; /* 3 */
@@ -134,21 +145,57 @@ static int kexec_load(struct boot_task *boot_task)
*p++ = local_image; /* 7 */
*p++ = NULL; /* 8 */
- result = process_run_sync(process);
- if (result) {
- pb_log_fn("failed to run process\n");
- goto abort_kexec;
- }
+ err_buf = NULL;
- result = process->exit_status;
+ for (i = 0; i < ARRAY_SIZE(load_args); i++) {
+ /* our first argument is the action: -s or -l */
+ argv[1] = load_args[i];
- if (result) {
- pb_log_fn("failed: (%d)\n", result);
- update_status(boot_task->status_fn, boot_task->status_arg,
- STATUS_ERROR, "%s", process->stdout_buf);
+ /* if we're enforcing, we know a -l load will fail; skip */
+ if (sysinfo->stb_os_enforcing && argv[1][1] == 'l')
+ continue;
+
+ process = process_create(boot_task);
+ if (!process) {
+ result = -1;
+ pb_log_fn("failed to create process\n");
+ continue;
+ }
+
+ process->path = pb_system_apps.kexec;
+ process->argv = argv;
+ process->keep_stdout = true;
+ process->add_stderr = true;
+
+ result = process_run_sync(process);
+ if (result) {
+ pb_log_fn("failed to run process\n");
+ continue;
+ }
+
+ result = 0;
+
+ if (process_exit_ok(process))
+ break;
+
+ result = -1;
+
+ if (process->stdout_len)
+ err_buf = talloc_strndup(boot_task, process->stdout_buf,
+ process->stdout_len);
+
+ pb_log_fn("kexec load (%s) failed (rc %d): %s\n", argv[1],
+ WEXITSTATUS(process->exit_status),
+ err_buf ?: "");
+
+ process_release(process);
}
-abort_kexec:
+ if (result)
+ update_status(boot_task->status_fn, boot_task->status_arg,
+ STATUS_ERROR, _("kexec load failed: %s"),
+ err_buf ?: "(no output)");
+
validate_boot_files_cleanup(boot_task);
return result;
@@ -401,6 +448,24 @@ static void cleanup_cancellations(struct boot_task *task,
talloc_free(task);
}
+static bool preboot_check(struct boot_task *task)
+{
+ const char *local_image = (task->local_image_override) ?
+ task->local_image_override : task->local_image;
+
+ char *preboot_check_err_msg = NULL;
+ bool preboot_check_ret = platform_preboot_check(local_image,
+ &preboot_check_err_msg);
+
+ if (preboot_check_err_msg) {
+ update_status(task->status_fn, task->status_arg,
+ STATUS_ERROR, "%s", preboot_check_err_msg);
+ talloc_free(preboot_check_err_msg);
+ }
+
+ return preboot_check_ret;
+}
+
static void boot_process(struct load_url_result *result, void *data)
{
struct boot_task *task = data;
@@ -424,25 +489,13 @@ static void boot_process(struct load_url_result *result, void *data)
run_boot_hooks(task);
+ if (!preboot_check(task))
+ return;
+
update_status(task->status_fn, task->status_arg, STATUS_INFO,
_("Performing kexec load"));
rc = kexec_load(task);
- pb_log_fn("kexec_load returned %d\n", rc);
- if (rc == KEXEC_LOAD_DECRYPTION_FALURE) {
- update_status(task->status_fn, task->status_arg,
- STATUS_ERROR, _("Decryption failed"));
- }
- else if (rc == KEXEC_LOAD_SIGNATURE_FAILURE) {
- update_status(task->status_fn, task->status_arg,
- STATUS_ERROR,
- _("Signature verification failed"));
- }
- else if (rc == KEXEC_LOAD_SIG_SETUP_INVALID) {
- update_status(task->status_fn, task->status_arg,
- STATUS_ERROR,
- _("Invalid signature configuration"));
- }
no_load:
list_for_each_entry(&task->resources, resource, list)
diff --git a/discover/device-handler.c b/discover/device-handler.c
index d41bb4b..d85f1af 100644
--- a/discover/device-handler.c
+++ b/discover/device-handler.c
@@ -1209,7 +1209,7 @@ void device_handler_add_ramdisk(struct device_handler *handler,
}
handler->ramdisks[i] = dev;
- i = handler->n_ramdisks++;
+ handler->n_ramdisks++;
}
struct ramdisk_device *device_handler_get_ramdisk(
diff --git a/discover/discover-server.c b/discover/discover-server.c
index 1a332cb..e29ce27 100644
--- a/discover/discover-server.c
+++ b/discover/discover-server.c
@@ -298,7 +298,7 @@ static int discover_server_handle_auth_message(struct client *client,
{
struct status *status;
char *hash;
- int rc;
+ int rc = 0;
status = talloc_zero(client, struct status);
@@ -403,7 +403,7 @@ static int discover_server_process_message(void *arg)
struct client *client = arg;
struct config *config;
char *url;
- int rc;
+ int rc = 0;
message = pb_protocol_read_message(client, client->fd);
@@ -460,7 +460,7 @@ static int discover_server_process_message(void *arg)
talloc_free(status);
}
}
- return 0;
+ return rc;
}
switch (message->action) {
@@ -537,7 +537,7 @@ static int discover_server_process_message(void *arg)
break;
}
- rc = discover_server_handle_auth_message(client, auth_msg);
+ discover_server_handle_auth_message(client, auth_msg);
talloc_free(auth_msg);
break;
default:
@@ -791,8 +791,11 @@ struct discover_server *discover_server_init(struct waitset *waitset)
/* Allow all clients to communicate on this socket */
group = getgrnam("petitgroup");
if (group) {
- chown(PB_SOCKET_PATH, 0, group->gr_gid);
- chmod(PB_SOCKET_PATH, 0660);
+ if (chown(PB_SOCKET_PATH, 0, group->gr_gid))
+ pb_log_fn("Error setting socket ownership: %m\n");
+ errno = 0;
+ if (chmod(PB_SOCKET_PATH, 0660))
+ pb_log_fn("Error setting socket permissions: %m\n");
}
if (listen(server->socket, 8)) {
diff --git a/discover/elf.c b/discover/elf.c
new file mode 100644
index 0000000..8c2711e
--- /dev/null
+++ b/discover/elf.c
@@ -0,0 +1,86 @@
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include <log/log.h>
+#include "elf.h"
+
+Elf *elf_open_image(const char *image)
+{
+ int fd;
+ Elf *elf = NULL;
+ int err;
+
+ if (!image) {
+ pb_log_fn("kernel image path is null\n");
+ return NULL;
+ }
+
+ if ((elf_version(EV_CURRENT) == EV_NONE) ||
+ ((fd = open(image, O_RDONLY, 0)) == -1) ||
+ (!(elf = elf_begin(fd, ELF_C_READ, NULL)))) {
+ err = elf_errno();
+ if (err)
+ pb_log_fn("failed to read %s elf: %s\n",
+ image, elf_errmsg(err));
+ }
+
+ return elf;
+}
+
+static bool elf_getnote_offset(Elf_Data * const edata,
+ const char *namespace,
+ const uint32_t type, GElf_Nhdr *nhdr,
+ size_t *n_off, size_t *d_off)
+{
+ size_t off = 0;
+ size_t next;
+
+ /* Iterate through notes */
+ while ((next = gelf_getnote(edata, off, nhdr, n_off, d_off)) > 0) {
+ char *note_ns = (char *) edata->d_buf + (*n_off);
+ if ((strcmp(note_ns, namespace) == 0) && (nhdr->n_type == type))
+ return true;
+
+ off = next;
+ }
+ return false;
+}
+
+void *elf_getnote_desc(Elf *elf,
+ const char *namespace,
+ uint32_t type)
+{
+ Elf_Scn *scn = NULL;
+ Elf_Data *edata = NULL;
+ GElf_Shdr shdr;
+ GElf_Nhdr nhdr;
+
+ size_t n_off;
+ size_t d_off;
+ void *desc = NULL;
+
+ if (!elf || !namespace)
+ return NULL;
+
+ /* Iterate through sections */
+ while ((scn = elf_nextscn(elf, scn))) {
+ gelf_getshdr(scn, &shdr);
+
+ /* ELF might have more than one SHT_NOTE section but
+ only one has the 'namespace' note */
+ if (shdr.sh_type == SHT_NOTE) {
+ edata = elf_getdata(scn, NULL);
+ if (elf_getnote_offset(edata, namespace, type,
+ &nhdr, &n_off, &d_off)) {
+ desc = calloc(nhdr.n_descsz, sizeof(char));
+ memcpy(desc, edata->d_buf + d_off,
+ nhdr.n_descsz);
+ break;
+ }
+ }
+ }
+
+ return desc;
+}
+
diff --git a/discover/elf.h b/discover/elf.h
new file mode 100644
index 0000000..742b791
--- /dev/null
+++ b/discover/elf.h
@@ -0,0 +1,29 @@
+#ifndef _PB_ELF_H
+#define _PB_ELF_H
+
+#include <elfutils/libdw.h>
+#include <libelf.h>
+
+/*
+ * The PowerPC namespace in an ELF Note of the kernel binary is used to store
+ * capabilities and information which can be used by a bootloader or userland
+ *
+ * docs: Documentation/powerpc/elfnote.rst
+ */
+#define POWERPC_ELFNOTE_NAMESPACE "PowerPC"
+
+/*
+ * The capabilities supported/required by the kernel
+ * This type uses a bitmap as "desc" field.
+ */
+#define PPC_ELFNOTE_CAPABILITIES 0x1
+
+/* bitmap fields: */
+#define PPCCAP_ULTRAVISOR_BIT 0x1
+
+Elf *elf_open_image(const char *image);
+void *elf_getnote_desc(Elf *elf,
+ const char *namespace,
+ uint32_t type);
+
+#endif /* _PB_ELF_H */
diff --git a/discover/event.c b/discover/event.c
index ec5537a..4c46d41 100644
--- a/discover/event.c
+++ b/discover/event.c
@@ -101,7 +101,6 @@ static void event_parse_params(struct event *event, const char *buf, int len)
sep = memchr(buf, '=', param_len);
if (!sep) {
name_len = param_len;
- value_len = 0;
param->value = "";
} else {
name_len = sep - buf;
diff --git a/discover/grub2/blscfg.c b/discover/grub2/blscfg.c
index d4754aa..d08f8f0 100644
--- a/discover/grub2/blscfg.c
+++ b/discover/grub2/blscfg.c
@@ -166,7 +166,6 @@ static void bls_finish(struct conf_context *conf)
struct discover_context *dc = conf->dc;
struct discover_boot_option *opt = state->opt;
struct boot_option *option = opt->option;
- const char *root;
char *filename;
if (!state->image) {
@@ -192,23 +191,21 @@ static void bls_finish(struct conf_context *conf)
else
option->name = talloc_strdup(option, state->image);
- root = script_env_get(state->script, "root");
-
- opt->boot_image = create_grub2_resource(opt, conf->dc->device,
- root, state->image);
+ opt->boot_image = create_grub2_resource(state->script, opt,
+ state->image);
if (state->initrd)
- opt->initrd = create_grub2_resource(opt, conf->dc->device,
- root, state->initrd);
+ opt->initrd = create_grub2_resource(state->script, opt,
+ state->initrd);
if (state->dtb)
- opt->dtb = create_grub2_resource(opt, conf->dc->device,
- root, state->dtb);
+ opt->dtb = create_grub2_resource(state->script, opt,
+ state->dtb);
char* args_sigfile_default = talloc_asprintf(opt,
"%s.cmdline.sig", state->image);
- opt->args_sig_file = create_grub2_resource(opt, conf->dc->device,
- root, args_sigfile_default);
+ opt->args_sig_file = create_grub2_resource(state->script, opt,
+ args_sigfile_default);
talloc_free(args_sigfile_default);
option->is_default = option_is_default(state, option);
diff --git a/discover/grub2/builtins.c b/discover/grub2/builtins.c
index 7e92299..ab1407a 100644
--- a/discover/grub2/builtins.c
+++ b/discover/grub2/builtins.c
@@ -1,4 +1,7 @@
+#define _GNU_SOURCE
+
+#include <getopt.h>
#include <stdio.h>
#include <string.h>
@@ -43,7 +46,6 @@ static int builtin_linux(struct grub2_script *script,
int argc, char *argv[])
{
struct discover_boot_option *opt = script->opt;
- const char *root;
int i;
if (!opt) {
@@ -58,10 +60,7 @@ static int builtin_linux(struct grub2_script *script,
return -1;
}
- root = script_env_get(script, "root");
-
- opt->boot_image = create_grub2_resource(opt, script->ctx->device,
- root, argv[1]);
+ opt->boot_image = create_grub2_resource(script, opt, argv[1]);
opt->option->boot_args = NULL;
if (argc > 2)
@@ -74,8 +73,8 @@ static int builtin_linux(struct grub2_script *script,
char* args_sigfile_default = talloc_asprintf(opt,
"%s.cmdline.sig", argv[1]);
- opt->args_sig_file = create_grub2_resource(opt, script->ctx->device,
- root, args_sigfile_default);
+ opt->args_sig_file = create_grub2_resource(script, opt,
+ args_sigfile_default);
talloc_free(args_sigfile_default);
return 0;
}
@@ -85,7 +84,6 @@ static int builtin_initrd(struct grub2_script *script,
int argc, char *argv[])
{
struct discover_boot_option *opt = script->opt;
- const char *root;
if (!opt) {
pb_log("grub2 syntax error: 'initrd' statement outside "
@@ -99,35 +97,128 @@ static int builtin_initrd(struct grub2_script *script,
return -1;
}
- root = script_env_get(script, "root");
- opt->initrd = create_grub2_resource(opt, script->ctx->device,
- root, argv[1]);
+ opt->initrd = create_grub2_resource(script, opt, argv[1]);
return 0;
}
+static const struct option search_options[] = {
+ {
+ .name = "set",
+ .has_arg = required_argument,
+ .val = 's',
+ },
+ {
+ .name = "file",
+ .has_arg = no_argument,
+ .val = 'f',
+ },
+ {
+ .name = "label",
+ .has_arg = no_argument,
+ .val = 'l',
+ },
+ {
+ .name = "fs-uuid",
+ .has_arg = no_argument,
+ .val = 'u',
+ },
+ { 0 },
+};
+
static int builtin_search(struct grub2_script *script,
void *data __attribute__((unused)),
int argc, char *argv[])
{
- const char *env_var, *spec;
- int i;
-
- env_var = NULL;
+ const char *env_var, *spec, *res;
+ struct discover_device *dev;
+ enum {
+ LOOKUP_UUID = 'u',
+ LOOKUP_LABEL = 'l',
+ LOOKUP_FILE = 'f',
+ } lookup_type;
+
+ env_var = "root";
+ optind = 0;
+
+ /* Default to UUID, for backwards compat with earlier petitboot
+ * versions. This argument is non-optional in GRUB. */
+ lookup_type = LOOKUP_UUID;
+
+ for (;;) {
+ int c = getopt_long(argc, argv, ":flu", search_options, NULL);
+ if (c == -1)
+ break;
- for (i = 1; i < argc - 1; i++) {
- if (!strncmp(argv[i], "--set=", strlen("--set="))) {
- env_var = argv[i] + strlen("--set=");
+ switch (c) {
+ case 's':
+ env_var = optarg;
+ break;
+ case LOOKUP_UUID:
+ case LOOKUP_LABEL:
+ case LOOKUP_FILE:
+ lookup_type = c;
+ break;
+ case '?':
+ case ':':
break;
}
}
- if (!env_var)
+ if (!strlen(env_var))
return 0;
- spec = argv[argc - 1];
+ if (optind >= argc)
+ return -1;
+
+ spec = argv[optind];
+ res = NULL;
+
+ switch (lookup_type) {
+ case LOOKUP_UUID:
+ dev = device_lookup_by_uuid(script->ctx->handler,
+ spec);
+ res = dev ? dev->device->id : spec;
+ break;
+ case LOOKUP_LABEL:
+ dev = device_lookup_by_label(script->ctx->handler,
+ spec);
+ if (dev)
+ res = dev->device->id;
+ break;
+ case LOOKUP_FILE:
+ /* not yet implemented */
+ break;
+ }
+
+ if (res)
+ script_env_set(script, env_var, res);
+
+ return 0;
+}
+
+static int parse_to_device_path(struct grub2_script *script,
+ const char *desc, struct discover_device **devp,
+ char **pathp)
+{
+ struct discover_device *dev;
+ struct grub2_file *file;
+
+ file = grub2_parse_file(script, desc);
+ if (!file)
+ return -1;
+
+ dev = script->ctx->device;
+ if (file->dev)
+ dev = grub2_lookup_device(script->ctx->handler, file->dev);
+
+ if (!dev)
+ return -1;
+
+ *devp = dev;
+ *pathp = talloc_strdup(script, file->path);
- script_env_set(script, env_var, spec);
+ talloc_free(file);
return 0;
}
@@ -139,12 +230,17 @@ static int builtin_search(struct grub2_script *script,
static bool builtin_test_op_file(struct grub2_script *script, char op,
const char *file)
{
+ struct discover_device *dev;
+ struct stat statbuf;
bool result;
+ char *path;
int rc;
- struct stat statbuf;
- rc = parser_stat_path(script->ctx, script->ctx->device,
- file, &statbuf);
+ rc = parse_to_device_path(script, file, &dev, &path);
+ if (rc)
+ return false;
+
+ rc = parser_stat_path(script->ctx, dev, path, &statbuf);
if (rc)
return false;
@@ -172,16 +268,21 @@ static bool builtin_test_op_file(struct grub2_script *script, char op,
static bool builtin_test_op_dir(struct grub2_script *script, char op,
const char *dir)
{
- int rc;
+ struct discover_device *dev;
struct stat statbuf;
+ char *path;
+ int rc;
if (op != 'd')
return false;
- rc = parser_stat_path(script->ctx, script->ctx->device, dir, &statbuf);
- if (rc) {
+ rc = parse_to_device_path(script, dir, &dev, &path);
+ if (rc)
+ return false;
+
+ rc = parser_stat_path(script->ctx, dev, path, &statbuf);
+ if (rc)
return false;
- }
return S_ISDIR(statbuf.st_mode);
}
@@ -300,6 +401,51 @@ static int builtin_test(struct grub2_script *script,
return rc ? 0 : 1;
}
+static int builtin_source(struct grub2_script *script,
+ void *data __attribute__((unused)),
+ int argc, char *argv[])
+{
+ struct grub2_statements *statements;
+ struct discover_device *dev;
+ const char *filename;
+ char *path, *buf;
+ int rc, len;
+
+ if (argc != 2)
+ return false;
+
+ /* limit script recursion */
+ if (script->include_depth >= 10)
+ return false;
+
+ rc = parse_to_device_path(script, argv[1], &dev, &path);
+ if (rc)
+ return false;
+
+ rc = parser_request_file(script->ctx, dev, path, &buf, &len);
+ if (rc)
+ return false;
+
+ /* save current script state */
+ statements = script->statements;
+ filename = script->filename;
+ script->include_depth++;
+
+ rc = grub2_parser_parse(script->parser, argv[1], buf, len);
+
+ if (!rc)
+ statements_execute(script, script->statements);
+
+ talloc_free(script->statements);
+
+ /* restore state */
+ script->statements = statements;
+ script->filename = filename;
+ script->include_depth--;
+
+ return !rc;
+}
+
static int builtin_true(struct grub2_script *script __attribute__((unused)),
void *data __attribute__((unused)),
int argc __attribute__((unused)),
@@ -390,7 +536,11 @@ static struct {
{
.name = "blscfg",
.fn = builtin_blscfg,
- }
+ },
+ {
+ .name = "source",
+ .fn = builtin_source,
+ },
};
static const char *nops[] = {
diff --git a/discover/grub2/env.c b/discover/grub2/env.c
index 7eda095..74d5729 100644
--- a/discover/grub2/env.c
+++ b/discover/grub2/env.c
@@ -86,6 +86,8 @@ int builtin_load_env(struct grub2_script *script,
if (!rc) {
rc = parse_buf_to_env(script, buf, len);
+ if (rc)
+ pb_debug_fn("Failed to set env\n");
talloc_free(buf);
}
diff --git a/discover/grub2/grub2-parser.y b/discover/grub2/grub2-parser.y
index 527a61c..f99bbfd 100644
--- a/discover/grub2/grub2-parser.y
+++ b/discover/grub2/grub2-parser.y
@@ -331,14 +331,15 @@ struct grub2_parser *grub2_parser_create(struct discover_context *ctx)
return parser;
}
-void grub2_parser_parse(struct grub2_parser *parser, const char *filename,
+/* performs a parse on buf, setting parser->script->statements */
+int grub2_parser_parse(struct grub2_parser *parser, const char *filename,
char *buf, int len)
{
YY_BUFFER_STATE bufstate;
int rc;
if (!len)
- return;
+ return -1;
parser->script->filename = filename;
@@ -349,6 +350,18 @@ void grub2_parser_parse(struct grub2_parser *parser, const char *filename,
yy_delete_buffer(bufstate, parser->scanner);
+ parser->inter_word = false;
+
+ return rc;
+}
+
+void grub2_parser_parse_and_execute(struct grub2_parser *parser,
+ const char *filename, char *buf, int len)
+{
+ int rc;
+
+ rc = grub2_parser_parse(parser, filename, buf, len);
+
if (!rc)
script_execute(parser->script);
}
diff --git a/discover/grub2/grub2.c b/discover/grub2/grub2.c
index f62ccdd..b176ce2 100644
--- a/discover/grub2/grub2.c
+++ b/discover/grub2/grub2.c
@@ -33,18 +33,35 @@ static const char *const grub2_conf_files[] = {
NULL
};
-struct grub2_resource_info {
- char *root;
- char *path;
-};
+struct discover_device *grub2_lookup_device(struct device_handler *handler,
+ const char *desc)
+{
+ struct discover_device *dev;
+
+ if (!desc || !*desc)
+ return NULL;
+
+ dev = device_lookup_by_id(handler, desc);
+ if (dev)
+ return dev;
+
+ /* for now, only lookup by UUID */
+ dev = device_lookup_by_uuid(handler, desc);
+ if (dev)
+ return dev;
+
+ return NULL;
+}
/* we use slightly different resources for grub2 */
-struct resource *create_grub2_resource(struct discover_boot_option *opt,
- struct discover_device *orig_device,
- const char *root, const char *path)
+struct resource *create_grub2_resource(struct grub2_script *script,
+ struct discover_boot_option *opt,
+ const char *path)
{
- struct grub2_resource_info *info;
+ struct discover_device *dev;
+ struct grub2_file *file;
struct resource *res;
+ const char *root;
if (strstr(path, "://")) {
struct pb_url *url = pb_url_parse(opt, path);
@@ -52,19 +69,29 @@ struct resource *create_grub2_resource(struct discover_boot_option *opt,
return create_url_resource(opt, url);
}
+ file = grub2_parse_file(script, path);
+ if (!file)
+ return NULL;
+
res = talloc(opt, struct resource);
+ root = script_env_get(script, "root");
- if (root) {
- info = talloc(res, struct grub2_resource_info);
- talloc_reference(info, root);
- info->root = talloc_strdup(info, root);
- info->path = talloc_strdup(info, path);
+ if (!file->dev && root && strlen(root))
+ file->dev = talloc_strdup(file, root);
- res->resolved = false;
- res->info = info;
+ /* if we don't have a device specified, or the lookup succeeds now,
+ * then we can resolve the resource right away */
+ if (file->dev)
+ dev = grub2_lookup_device(script->ctx->handler, file->dev);
+ else
+ dev = script->ctx->device;
- } else
- resolve_resource_against_device(res, orig_device, path);
+ if (dev) {
+ resolve_resource_against_device(res, dev, file->path);
+ } else {
+ res->resolved = false;
+ res->info = talloc_steal(opt, file);
+ }
return res;
}
@@ -72,22 +99,59 @@ struct resource *create_grub2_resource(struct discover_boot_option *opt,
bool resolve_grub2_resource(struct device_handler *handler,
struct resource *res)
{
- struct grub2_resource_info *info = res->info;
+ struct grub2_file *file = res->info;
struct discover_device *dev;
assert(!res->resolved);
- dev = device_lookup_by_uuid(handler, info->root);
-
+ dev = grub2_lookup_device(handler, file->dev);
if (!dev)
return false;
- resolve_resource_against_device(res, dev, info->path);
- talloc_free(info);
+ resolve_resource_against_device(res, dev, file->path);
+ talloc_free(file);
return true;
}
+struct grub2_file *grub2_parse_file(struct grub2_script *script,
+ const char *str)
+{
+ struct grub2_file *file;
+ size_t dev_len;
+ char *pos;
+
+ if (!str)
+ return NULL;
+
+ file = talloc_zero(script, struct grub2_file);
+
+ if (*str != '(') {
+ /* just a path - no device, return path as-is */
+ file->path = talloc_strdup(file, str);
+
+ } else {
+ /* device plus path - split into components */
+
+ pos = strchr(str, ')');
+
+ /* no closing bracket, or zero-length path? */
+ if (!pos || *(pos+1) == '\0') {
+ talloc_free(file);
+ return NULL;
+ }
+
+ file->path = talloc_strdup(file, pos + 1);
+
+ dev_len = pos - str - 1;
+ if (dev_len)
+ file->dev = talloc_strndup(file, str + 1, dev_len);
+ }
+
+ return file;
+}
+
+
static int grub2_parse(struct discover_context *dc)
{
const char * const *filename;
@@ -105,7 +169,7 @@ static int grub2_parse(struct discover_context *dc)
continue;
parser = grub2_parser_create(dc);
- grub2_parser_parse(parser, *filename, buf, len);
+ grub2_parser_parse_and_execute(parser, *filename, buf, len);
device_handler_status_dev_info(dc->handler, dc->device,
_("Parsed GRUB configuration from %s"),
*filename);
diff --git a/discover/grub2/grub2.h b/discover/grub2/grub2.h
index 68176fb..75f6aa0 100644
--- a/discover/grub2/grub2.h
+++ b/discover/grub2/grub2.h
@@ -91,6 +91,7 @@ struct grub2_statement_for {
};
struct grub2_script {
+ struct grub2_parser *parser;
struct grub2_statements *statements;
struct list environment;
struct list symtab;
@@ -99,6 +100,7 @@ struct grub2_script {
const char *filename;
unsigned int n_options;
struct list options;
+ int include_depth;
};
struct grub2_parser {
@@ -107,6 +109,14 @@ struct grub2_parser {
bool inter_word;
};
+/* References to files in grub2 consist of an optional device and a path
+ * (specified here by UUID). If the dev is unspecified, we fall back to a
+ * default - usually the 'root' environment variable. */
+struct grub2_file {
+ char *dev;
+ char *path;
+};
+
/* type for builtin functions */
typedef int (*grub2_function)(struct grub2_script *script, void *data,
int argc, char *argv[]);
@@ -156,6 +166,9 @@ void word_append(struct grub2_word *w1, struct grub2_word *w2);
/* script interface */
void script_execute(struct grub2_script *script);
+int statements_execute(struct grub2_script *script,
+ struct grub2_statements *stmts);
+
int statement_simple_execute(struct grub2_script *script,
struct grub2_statement *statement);
int statement_block_execute(struct grub2_script *script,
@@ -183,16 +196,25 @@ void script_register_function(struct grub2_script *script,
void register_builtins(struct grub2_script *script);
/* resources */
-struct resource *create_grub2_resource(struct discover_boot_option *opt,
- struct discover_device *orig_device,
- const char *root, const char *path);
+struct resource *create_grub2_resource(struct grub2_script *script,
+ struct discover_boot_option *opt, const char *path);
bool resolve_grub2_resource(struct device_handler *handler,
struct resource *res);
+/* grub-style device+path parsing */
+struct grub2_file *grub2_parse_file(struct grub2_script *script,
+ const char *str);
+struct discover_device *grub2_lookup_device(struct device_handler *handler,
+ const char *desc);
+
+/* internal parse api */
+int grub2_parser_parse(struct grub2_parser *parser, const char *filename,
+ char *buf, int len);
+
/* external parser api */
struct grub2_parser *grub2_parser_create(struct discover_context *ctx);
-void grub2_parser_parse(struct grub2_parser *parser, const char *filename,
- char *buf, int len);
+void grub2_parser_parse_and_execute(struct grub2_parser *parser,
+ const char *filename, char *buf, int len);
#endif /* GRUB2_H */
diff --git a/discover/grub2/script.c b/discover/grub2/script.c
index 1a802b9..14931f9 100644
--- a/discover/grub2/script.c
+++ b/discover/grub2/script.c
@@ -117,12 +117,10 @@ static bool option_is_default(struct grub2_script *script,
if (end != var && *end == '\0')
return default_idx == script->n_options;
- /* if we don't have an explicit id for this option, fall back to
- * the name */
- if (!id)
- id = opt->option->name;
+ if (id && !strcmp(id, var))
+ return true;
- return !strcmp(id, var);
+ return !strcmp(opt->option->name, var);
}
static void append_text_to_current_arg(struct grub2_argv *argv,
@@ -227,11 +225,11 @@ static void process_expansions(struct grub2_script *script,
}
/* we may have allocated an extra argv element but not populated it */
- if (!argv->argv[argv->argc - 1])
+ if (argv->argv && !argv->argv[argv->argc - 1])
argv->argc--;
}
-static int statements_execute(struct grub2_script *script,
+int statements_execute(struct grub2_script *script,
struct grub2_statements *stmts)
{
struct grub2_statement *stmt;
@@ -341,9 +339,16 @@ int statement_menuentry_execute(struct grub2_script *script,
* implementation to get --id= working.
*/
for (i = 1; i < st->argv->argc; ++i) {
- if (strncmp("--id=", st->argv->argv[i], 5) == 0) {
- id = st->argv->argv[i] + 5;
- break;
+ if (strncmp("--id", st->argv->argv[i], strlen("--id")) == 0) {
+ if (strlen(st->argv->argv[i]) > strlen("--id=")) {
+ id = st->argv->argv[i] + strlen("--id=");
+ break;
+ }
+
+ if (i + 1 < st->argv->argc) {
+ id = st->argv->argv[i + 1];
+ break;
+ }
}
}
if (st->argv->argc > 0)
@@ -489,6 +494,9 @@ void script_execute(struct grub2_script *script)
{
struct discover_boot_option *opt, *tmp;
+ if (!script)
+ return;
+
init_env(script);
statements_execute(script, script->statements);
@@ -510,6 +518,7 @@ struct grub2_script *create_script(struct grub2_parser *parser,
script = talloc_zero(parser, struct grub2_script);
script->ctx = ctx;
+ script->parser = parser;
list_init(&script->symtab);
list_init(&script->options);
diff --git a/discover/ipmi.c b/discover/ipmi.c
index ae02bb0..66b465e 100644
--- a/discover/ipmi.c
+++ b/discover/ipmi.c
@@ -306,7 +306,6 @@ int parse_ipmi_interface_override(struct config *config, uint8_t *buf,
return -1;
}
ifconf->static_config.gateway = gatewaystr;
- i += ipsize;
}
ifconf->override = true;
diff --git a/discover/ipmi.h b/discover/ipmi.h
index c8ccb46..f8672b0 100644
--- a/discover/ipmi.h
+++ b/discover/ipmi.h
@@ -29,6 +29,29 @@ enum ipmi_sensor_ids {
struct ipmi;
+#define CHASSIS_BOOT_MBOX_IANA_SZ 3
+#define CHASSIS_BOOT_MBOX_DATA_SZ 16
+#define CHASSIS_BOOT_MBOX_BLOCK0_DATA_SZ \
+ (CHASSIS_BOOT_MBOX_DATA_SZ - CHASSIS_BOOT_MBOX_IANA_SZ)
+
+typedef struct __attribute__((packed)) {
+ uint8_t iana[CHASSIS_BOOT_MBOX_IANA_SZ];
+ uint8_t data[CHASSIS_BOOT_MBOX_BLOCK0_DATA_SZ];
+} mbox_block0_t;
+
+typedef union {
+ uint8_t data[CHASSIS_BOOT_MBOX_DATA_SZ];
+ mbox_block0_t b0;
+} mbox_t;
+
+typedef struct __attribute__((packed)) {
+ uint8_t cc;
+ uint8_t param_version;
+ uint8_t param_valid;
+ uint8_t block_selector;
+ mbox_t mbox;
+} ipmi_mbox_response_t;
+
static const int ipmi_timeout = 10000; /* milliseconds. */
bool ipmi_present(void);
diff --git a/discover/paths.c b/discover/paths.c
index 54b843e..16fdd59 100644
--- a/discover/paths.c
+++ b/discover/paths.c
@@ -450,7 +450,8 @@ static void load_local(struct load_task *task)
result->status = LOAD_OK;
}
- task->async_cb(task->result, task->async_data);
+ if (task->async_cb)
+ task->async_cb(task->result, task->async_data);
}
static void load_url_async_start_pending(struct load_task *task, int flags)
diff --git a/discover/platform-powerpc.c b/discover/platform-powerpc.c
index 5d7cc59..c6ab416 100644
--- a/discover/platform-powerpc.c
+++ b/discover/platform-powerpc.c
@@ -21,6 +21,7 @@
#include "platform.h"
#include "ipmi.h"
#include "dt.h"
+#include "elf.h"
static const char *partition = "common";
static const char *sysparams_dir = "/sys/firmware/opal/sysparams/";
@@ -222,6 +223,13 @@ static void params_update_all(struct param_list *pl,
params_update_network_values(pl, "petitboot,network", config);
params_update_bootdev_values(pl, "petitboot,bootdevs", config);
+
+ if (config->preboot_check_enabled == defaults->preboot_check_enabled)
+ val = "";
+ else
+ val = config->preboot_check_enabled ? "true" : "false";
+
+ param_list_set_non_empty(pl, "petitboot,preboot-check", val, true);
}
static void config_set_ipmi_bootdev(struct config *config, enum ipmi_bootdev bootdev,
@@ -437,10 +445,10 @@ static int get_ipmi_bootdev_ipmi(struct platform_powerpc *platform,
}
static int get_ipmi_boot_mailbox_block(struct platform_powerpc *platform,
- char *buf, uint8_t block)
+ mbox_t *mailbox, uint8_t block)
{
size_t blocksize = 16;
- uint8_t resp[3 + 16];
+ ipmi_mbox_response_t ipmi_mbox_resp = { .cc = 0xFF };
uint16_t resp_len;
char *debug_buf;
int rc;
@@ -449,60 +457,71 @@ static int get_ipmi_boot_mailbox_block(struct platform_powerpc *platform,
block, /* set selector */
0x00, /* no block selector */
};
+ size_t ipmi_header_len = sizeof(ipmi_mbox_response_t) - sizeof(mbox_t);
- resp_len = sizeof(resp);
+ resp_len = sizeof(ipmi_mbox_response_t);
rc = ipmi_transaction(platform->ipmi, IPMI_NETFN_CHASSIS,
IPMI_CMD_CHASSIS_GET_SYSTEM_BOOT_OPTIONS,
req, sizeof(req),
- resp, &resp_len,
+ (uint8_t*)&ipmi_mbox_resp, &resp_len,
ipmi_timeout);
if (rc) {
pb_log("platform: error reading IPMI boot options\n");
return -1;
}
- if (resp_len < sizeof(resp)) {
- if (resp_len < 3) {
- pb_log("platform: unexpected length (%d) in "
- "boot options mailbox response\n",
- resp_len);
- return -1;
- }
-
- if (resp_len == 4) {
- pb_debug_fn("block %hu empty\n", block);
- return 0;
- }
+ if (resp_len > sizeof(ipmi_mbox_response_t)) {
+ pb_debug("platform: invalid mailbox response size!\n");
+ return -1;
+ }
- blocksize = sizeof(resp) - 3;
- pb_debug_fn("Mailbox block %hu returns only %zu bytes in block\n",
- block, blocksize);
+ if (resp_len < ipmi_header_len) {
+ pb_log("platform: unexpected length (%d) in "
+ "boot options mailbox response\n",
+ resp_len);
+ return -1;
}
- debug_buf = format_buffer(platform, resp, resp_len);
+ blocksize = resp_len - ipmi_header_len;
+ pb_debug_fn("Mailbox block %hu returns only %zu bytes in block\n",
+ block, blocksize);
+
+ debug_buf = format_buffer(platform, (uint8_t*)&ipmi_mbox_resp, resp_len);
pb_debug_fn("IPMI bootdev mailbox block %hu:\n%s\n", block, debug_buf);
talloc_free(debug_buf);
- if (resp[0] != 0) {
+ if (ipmi_mbox_resp.cc != 0) {
pb_log("platform: non-zero completion code %d from IPMI req\n",
- resp[0]);
+ ipmi_mbox_resp.cc);
return -1;
}
/* check for correct parameter version */
- if ((resp[1] & 0xf) != 0x1) {
+ if ((ipmi_mbox_resp.param_version & 0xf) != 0x1) {
pb_log("platform: unexpected version (0x%x) in "
- "boot mailbox response\n", resp[0]);
+ "boot mailbox response\n", ipmi_mbox_resp.param_version);
return -1;
}
/* check for valid paramters */
- if (resp[2] & 0x80) {
+ if (ipmi_mbox_resp.param_valid & 0x80) {
pb_debug("platform: boot mailbox parameters are invalid/locked\n");
return -1;
}
- memcpy(buf, &resp[3], blocksize);
+ /* check for block number */
+ if (ipmi_mbox_resp.block_selector != block) {
+ pb_debug("platform: returned boot mailbox block doesn't match "
+ "requested\n");
+ return -1;
+ }
+
+ if (!blocksize) {
+ pb_debug_fn("block %hu empty\n", block);
+ return 0;
+ }
+
+ memcpy(mailbox, &ipmi_mbox_resp.mbox, blocksize);
return blocksize;
}
@@ -511,12 +530,10 @@ static int get_ipmi_boot_mailbox(struct platform_powerpc *platform,
char **buf)
{
char *mailbox_buffer, *prefix;
- const size_t blocksize = 16;
- char block_buffer[blocksize];
+ mbox_t mailbox;
size_t mailbox_size;
int content_size;
uint8_t i;
- int rc;
mailbox_buffer = NULL;
mailbox_size = 0;
@@ -527,15 +544,16 @@ static int get_ipmi_boot_mailbox(struct platform_powerpc *platform,
* on higher numbers.
*/
for (i = 0; i < UCHAR_MAX; i++) {
- rc = get_ipmi_boot_mailbox_block(platform, block_buffer, i);
- if (rc < 3 && i == 0) {
+ uint8_t *boot_opt_data;
+ int block_size = get_ipmi_boot_mailbox_block(platform, &mailbox, i);
+ if (block_size < CHASSIS_BOOT_MBOX_IANA_SZ && i == 0) {
/*
* Immediate failure, no blocks read or missing IANA
* number.
*/
return -1;
}
- if (rc < 1) {
+ if (block_size < 1) {
/* Error or no bytes read */
break;
}
@@ -543,28 +561,33 @@ static int get_ipmi_boot_mailbox(struct platform_powerpc *platform,
if (i == 0) {
/*
* The first three bytes of block zero are an IANA
- * Enterprise ID number. Check it matches the IBM
- * number, '2'.
+ * Enterprise ID number
*/
- if (block_buffer[0] != 0x02 ||
- block_buffer[1] != 0x00 ||
- block_buffer[2] != 0x00) {
+ block_size -= CHASSIS_BOOT_MBOX_IANA_SZ;
+ boot_opt_data = &mailbox.b0.data;
+
+ /* Check IANA matches the IBM number, '2' */
+ if (mailbox.b0.iana[0] != 0x02 ||
+ mailbox.b0.iana[1] != 0x00 ||
+ mailbox.b0.iana[2] != 0x00) {
pb_log_fn("IANA number unrecognised: 0x%x:0x%x:0x%x\n",
- block_buffer[0],
- block_buffer[1],
- block_buffer[2]);
+ mailbox.b0.iana[0],
+ mailbox.b0.iana[1],
+ mailbox.b0.iana[2]);
return -1;
}
+ } else {
+ boot_opt_data = &mailbox.data;
}
mailbox_buffer = talloc_realloc(platform, mailbox_buffer,
- char, mailbox_size + rc);
+ char, mailbox_size + block_size);
if (!mailbox_buffer) {
pb_log_fn("Failed to allocate mailbox buffer\n");
return -1;
}
- memcpy(mailbox_buffer + mailbox_size, block_buffer, rc);
- mailbox_size += rc;
+ memcpy(mailbox_buffer + mailbox_size, boot_opt_data, block_size);
+ mailbox_size += block_size;
}
if (i < 5)
@@ -574,10 +597,10 @@ static int get_ipmi_boot_mailbox(struct platform_powerpc *platform,
else
pb_debug_fn("%hu blocks read (%zu bytes)\n", i, mailbox_size);
- if (mailbox_size < 3 + strlen("petitboot,bootdevs="))
+ if (mailbox_size < strlen("petitboot,bootdevs="))
return -1;
- prefix = talloc_strndup(mailbox_buffer, mailbox_buffer + 3,
+ prefix = talloc_strndup(mailbox_buffer, mailbox_buffer,
strlen("petitboot,bootdevs="));
if (!prefix) {
pb_log_fn("Couldn't check prefix\n");
@@ -595,9 +618,9 @@ static int get_ipmi_boot_mailbox(struct platform_powerpc *platform,
}
/* Don't include IANA number in buffer */
- content_size = mailbox_size - 3 - strlen("petitboot,bootdevs=");
+ content_size = mailbox_size - strlen("petitboot,bootdevs=");
*buf = talloc_memdup(platform,
- mailbox_buffer + 3 + strlen("petitboot,bootdevs="),
+ mailbox_buffer + strlen("petitboot,bootdevs="),
content_size + 1);
(*buf)[content_size] = '\0';
@@ -923,6 +946,72 @@ static void pre_boot(struct platform *p, const struct config *config)
platform->set_os_boot_sensor(platform);
}
+static bool preboot_check(struct platform *p,
+ const struct config *config,
+ const char *image,
+ char **err_msg)
+{
+ struct platform_powerpc *platform = p->platform_data;
+ unsigned int *ppc_cap_bitmap = NULL;
+ bool ultravisor_enabled;
+ struct stat statbuf;
+ bool ret = true;
+
+ /* check if ultravisor-system is enabled */
+ ultravisor_enabled = stat("/proc/device-tree/ibm,ultravisor",
+ &statbuf) == 0;
+
+ /* if ultravisor-system is disabled, continue the boot process */
+ if (!ultravisor_enabled)
+ return true;
+
+ ppc_cap_bitmap = elf_getnote_desc(elf_open_image(image),
+ POWERPC_ELFNOTE_NAMESPACE,
+ PPC_ELFNOTE_CAPABILITIES);
+
+ if ((ppc_cap_bitmap) && (*ppc_cap_bitmap & PPCCAP_ULTRAVISOR_BIT)) {
+ pb_debug("kernel capabilities: ultravisor mode found.\n");
+ } else {
+ ret = false;
+ pb_log_fn("kernel capabilities failed:"
+ " IBM Ultravisor mode is required.\n");
+ *err_msg = talloc_strdup(platform, "IBM Ultravisor capability"
+ " not found");
+ }
+ free(ppc_cap_bitmap);
+
+ /* if preboot_check is disabled, continue the boot process */
+ if (!config->preboot_check_enabled)
+ return true;
+
+ return ret;
+}
+
+static void get_sysinfo_stb(struct platform_powerpc *platform,
+ struct system_info *sysinfo)
+{
+ char *filename;
+ unsigned int i;
+ int rc;
+ struct {
+ const char *name;
+ bool *flag;
+ } props[] = {
+ { "secure-enabled", &sysinfo->stb_fw_enforcing },
+ { "trusted-enabled", &sysinfo->stb_fw_measurement },
+ { "os-secureboot-enforcing", &sysinfo->stb_os_enforcing },
+ };
+
+ for (i = 0; i < ARRAY_SIZE(props); i++) {
+ struct stat statbuf;
+ filename = talloc_asprintf(platform, "%sibm,secureboot/%s",
+ devtree_dir, props[i].name);
+ rc = stat(filename, &statbuf);
+ *props[i].flag = (rc == 0);
+ talloc_free(filename);
+ }
+}
+
static int get_sysinfo(struct platform *p, struct system_info *sysinfo)
{
struct platform_powerpc *platform = p->platform_data;
@@ -951,6 +1040,9 @@ static int get_sysinfo(struct platform *p, struct system_info *sysinfo)
if (platform->get_platform_versions)
platform->get_platform_versions(sysinfo);
+ get_sysinfo_stb(platform, sysinfo);
+
+
return 0;
}
@@ -1028,6 +1120,7 @@ static struct platform platform_powerpc = {
.get_sysinfo = get_sysinfo,
.restrict_clients = restrict_clients,
.set_password = set_password,
+ .preboot_check = preboot_check,
};
register_platform(platform_powerpc);
diff --git a/discover/platform.c b/discover/platform.c
index 8ce52fc..38bd241 100644
--- a/discover/platform.c
+++ b/discover/platform.c
@@ -159,6 +159,7 @@ void config_set_defaults(struct config *config)
else
config->lang = NULL;
+ config->preboot_check_enabled = true;
}
int platform_init(void *ctx)
@@ -206,6 +207,17 @@ void platform_pre_boot(void)
platform->pre_boot(platform, config);
}
+bool platform_preboot_check(const char *image, char **err_msg)
+{
+ const struct config *config = config_get();
+
+ if (platform && config && platform->preboot_check)
+ return platform->preboot_check(platform, config,
+ image, err_msg);
+
+ return true;
+}
+
int platform_get_sysinfo(struct system_info *info)
{
if (platform && platform->get_sysinfo)
@@ -572,6 +584,9 @@ void config_populate_all(struct config *config, const struct param_list *pl)
val = param_list_get_value(pl, "petitboot,https_proxy");
if (val)
config->https_proxy = talloc_strdup(config, val);
+
+ val = param_list_get_value(pl, "petitboot,preboot-check");
+ config->preboot_check_enabled = !val || strcmp(val, "false");
}
static char *interface_config_str(void *ctx, struct interface_config *config)
diff --git a/discover/platform.h b/discover/platform.h
index 5a5c990..fe3d390 100644
--- a/discover/platform.h
+++ b/discover/platform.h
@@ -14,6 +14,10 @@ struct platform {
int (*get_sysinfo)(struct platform *, struct system_info *);
bool (*restrict_clients)(struct platform *);
int (*set_password)(struct platform *, const char *hash);
+ bool (*preboot_check)(struct platform *,
+ const struct config *,
+ const char *image,
+ char **err_msg);
uint16_t dhcp_arch_id;
void *platform_data;
};
@@ -25,6 +29,7 @@ int platform_get_sysinfo(struct system_info *info);
bool platform_restrict_clients(void);
int platform_set_password(const char *hash);
void platform_pre_boot(void);
+bool platform_preboot_check(const char *image, char **err_msg);
/* configuration interface */
const struct config *config_get(void);
diff --git a/discover/pxe-parser.c b/discover/pxe-parser.c
index ba0f81c..035794c 100644
--- a/discover/pxe-parser.c
+++ b/discover/pxe-parser.c
@@ -292,9 +292,14 @@ static bool ipxe_simple_parser(struct conf_context *ctx, char *buf, int len)
continue;
}
+ if (!name) {
+ pb_debug_fn("missing name from conf_get_pair\n");
+ continue;
+ }
+
/* All other parameters require a value */
if (!value) {
- pb_debug("%s: '%s' missing value\n", __func__, name);
+ pb_debug_fn("'%s' missing value\n", name);
continue;
}
diff --git a/discover/user-event.c b/discover/user-event.c
index d3d4a5e..cc03ffd 100644
--- a/discover/user-event.c
+++ b/discover/user-event.c
@@ -657,10 +657,10 @@ static void user_event_handle_message(struct user_event *uev, char *buf,
break;
case EVENT_ACTION_URL:
result = user_event_url(uev, event);
- goto out;
+ break;
case EVENT_ACTION_DHCP:
result = user_event_dhcp(uev, event);
- goto out;
+ break;
case EVENT_ACTION_BOOT:
result = user_event_boot(uev, event);
break;
@@ -671,13 +671,17 @@ static void user_event_handle_message(struct user_event *uev, char *buf,
result = user_event_plugin(uev, event);
break;
default:
+ result = -1;
break;
}
+ if (result)
+ pb_log_fn("failed to handle action %d\n", event->action);
+
/* user_event_url() and user_event_dhcp() will steal the event context,
* but all others still need to free */
- talloc_free(event);
-out:
+ if (talloc_parent(event) == uev)
+ talloc_free(event);
return;
}
@@ -751,8 +755,11 @@ struct user_event *user_event_init(struct device_handler *handler,
}
/* Don't allow events from non-priviledged users */
- chown(PBOOT_USER_EVENT_SOCKET, 0, 0);
- chmod(PBOOT_USER_EVENT_SOCKET, 0660);
+ if (chown(PBOOT_USER_EVENT_SOCKET, 0, 0))
+ pb_log_fn("Error setting socket ownership: %m\n");
+ errno = 0;
+ if (chmod(PBOOT_USER_EVENT_SOCKET, 0660))
+ pb_log_fn("Error setting socket permissions: %m\n");
waiter_register_io(waitset, uev->socket, WAIT_IN,
user_event_process, uev);
diff --git a/discover/yaboot-parser.c b/discover/yaboot-parser.c
index b06248f..d0a40b1 100644
--- a/discover/yaboot-parser.c
+++ b/discover/yaboot-parser.c
@@ -213,6 +213,8 @@ static void yaboot_process_pair(struct conf_context *conf, const char *name,
/* Then start the new image. */
opt = state_start_new_option(conf, state);
+ if (!opt)
+ pb_debug_fn("new opt is NULL\n");
state->boot_image = talloc_strdup(state, value);
@@ -235,6 +237,8 @@ static void yaboot_process_pair(struct conf_context *conf, const char *name,
/* Then start the new image. */
opt = state_start_new_option(conf, state);
+ if (!opt)
+ pb_debug_fn("new opt is NULL\n");
if (*value == '/') {
state->boot_image = talloc_strdup(state, value);
diff --git a/doc/Makefile b/doc/Makefile
new file mode 100644
index 0000000..298ea9e
--- /dev/null
+++ b/doc/Makefile
@@ -0,0 +1,19 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+SOURCEDIR = .
+BUILDDIR = _build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file
diff --git a/doc/components.rst b/doc/components.rst
new file mode 100644
index 0000000..fed52a1
--- /dev/null
+++ b/doc/components.rst
@@ -0,0 +1,29 @@
+Petitboot Components
+====================
+
+Server
+------
+
+The core of Petitboot is the ``pb-discover`` process. This performs initial setup, discovers and configures devices, and handles configuration options or quirks saved or set by the platform.
+
+UI Clients
+----------
+
+The ``ui/`` directory contains client processes that implement a user interface. The primary interface is ``petitboot-nc`` which is based on ncurses and provides the full range of options for interaction and configuration.
+
+There is also a twin-based interface under ``ui/twin/`` however this is largely a remnant from the PS3 and does not implement more recent functionality.
+
+Clients generally take no direct action themselves, instead communcating via the "pb-protocol" interface to the ``pb-discover`` server to request actions.
+
+Utilities
+---------
+
+A number of smaller utilities exist to perform some specific tasks, including:
+
+pb-console: Initial console setup and UI startup
+pb-config: Trimmed down 'client' that can request information from the ``pb-discover`` server.
+pb-event: Provides a callable script to send user events to ``pb-discover`` - usually used by other utilities such as ``udhcpc`` to report network information.
+pb-exec: Simple wrapper for running programs from the UI
+pb-plugin: Implements the petitboot-plugin interface
+pb-sos: Collects diagnostic and crash information
+boot hooks: Small hooks to be run immediately before boot.
diff --git a/doc/conf.py b/doc/conf.py
new file mode 100644
index 0000000..ecef304
--- /dev/null
+++ b/doc/conf.py
@@ -0,0 +1,57 @@
+# Configuration file for the Sphinx documentation builder.
+#
+# This file only contains a selection of the most common options. For a full
+# list see the documentation:
+# http://www.sphinx-doc.org/en/master/config
+
+# -- Path setup --------------------------------------------------------------
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+# import os
+# import sys
+# sys.path.insert(0, os.path.abspath('.'))
+
+
+# -- Project information -----------------------------------------------------
+
+project = 'Petitboot'
+copyright = '2019, Samuel Mendoza-Jonas, IBM'
+author = 'Samuel Mendoza-Jonas'
+
+# The full version, including alpha/beta/rc tags
+release = 'v1.10.3'
+
+
+# -- General configuration ---------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = ['sphinx.ext.githubpages']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+#
+html_theme = 'alabaster'
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# Older Sphinx defaults to 'contents'
+master_doc = 'index'
diff --git a/doc/dev/design.rst b/doc/dev/design.rst
new file mode 100644
index 0000000..7c3fa22
--- /dev/null
+++ b/doc/dev/design.rst
@@ -0,0 +1,31 @@
+Design Topics
+=============
+
+When considering new functionality or other changes to Petitboot there are a few guidelines we do our best to adhere to:
+
+Be Generic
+----------
+
+Avoid adding code that ties Petitboot to a particular platform or functionality by default. Avoid making assumptions about the system that Petitboot may be running on. Platform specific functionality should as much as possible be confined to `platform-` files. Especially when thinking about dependencies on outside utilities or tools consider using the pb-plugin interface instead.
+
+Don't Surprise The User
+-----------------------
+
+Communicate clearly to the user what is happening. What the UI says is happening should be what actually happens.
+A particular example of this is the kernel command line: there are a few problems that would be easier to solve if we could modify the command line of the kernel to be booted, but this would be something that interferes with the users expectations and depending on their scenario could cause them problems.
+
+Avoid Dependencies On The Target Operating System
+-------------------------------------------------
+
+Supporting multiple configuration formats means that Petitboot can be dropped in as the bootloader for most existing systems without any change in the target operating system or its bootloader configuration. Avoid any changes that would require a corresponding update in the target operating system or dependencies on any particular version of Petitboot.
+
+Maintain Server-Client Separation
+---------------------------------
+
+As much as possible the server should perform any actions, and the clients should only makes requests to the server. This is particularly true with the introduction of Petitboot "users" and password restrictions.
+An exception to this is the running of Petitboot plugins since these need to be run in the visible shell; however these are still subject to user permissions if enabled.
+
+Abstract Dependencies
+---------------------
+
+Avoid direct dependencies on certain utilities or versions of utilities. If there are several versions or interfaces to a utility that Petitboot might access, abstract these differences where possible. (See handling of different utilities such as tftp, udhcpc, etc).
diff --git a/doc/dev/linux-image.rst b/doc/dev/linux-image.rst
new file mode 100644
index 0000000..ba2b65f
--- /dev/null
+++ b/doc/dev/linux-image.rst
@@ -0,0 +1,35 @@
+Linux Image Requirements
+========================
+
+Petitboot has a few requirements for the linux image it is built in to.
+For an in-practice example of how to build a Petitboot image see op-build_.
+
+For build-time dependencies see configure.ac_.
+
+.. _op-build: https://github.com/open-power/op-build/tree/master/openpower/package/petitboot
+.. _configure.ac: https://github.com/open-power/petitboot/blob/master/configure.ac
+
+Core dependencies
+-----------------
+
+* pb-discover expects to be run as root, or at least have permission for device management, executing kexec, etc.
+* udev: pb-discover discovers devices via libudev enumeration so a udev implementation must be present.
+ Following this any udev rules required for certain device types must also be present. Eg. op-build's inclusions_.
+* network utilities: pb-discover expects to have ``udhcpc`` available for DHCP, or a call-equivalent version. Similarly it expects ``tftp`` and ``wget`` binaries in order to download boot resources.
+* kexec: A kexec binary must be available. This is commonly kexec-lite_ however kexec-tools should also work.
+* LVM: Petitboot depends on libdevmapper, and also requires ``vgscan`` and ``vgchange`` to be available if order to setup logical volumes.
+
+.. _inclusions: https://github.com/open-power/op-build/blob/master/openpower/package/petitboot/63-md-raid-arrays.rules
+.. _kexec-lite: https://github.com/open-power/kexec-lite
+
+Optional dependencies
+---------------------
+
+* ``mdadm`` for software RAID handling.
+* libflash: On OPAL platforms Petitboot will use libflash to load firmware version information.
+* ipmi: If ``/dev/ipmi`` is available Petitboot will use it to source information from the BMC.
+* console setup: unless you have other requirements you probably want to be starting the UI through ``pb-console`` in which case it is useful to have this called by udev.
+ For example: petitboot-console-ui.rules_, or depending on if you're using ``agetty -l`` to log in a user, shell_profile_.
+
+.. _petitboot-console-ui.rules: https://github.com/open-power/op-build/blob/master/openpower/package/petitboot/petitboot-console-ui.rules
+.. _shell_profile: https://github.com/open-power/op-build/blob/master/openpower/package/petitboot/shell_profile
diff --git a/doc/dev/submitting.rst b/doc/dev/submitting.rst
new file mode 100644
index 0000000..fb968cd
--- /dev/null
+++ b/doc/dev/submitting.rst
@@ -0,0 +1,26 @@
+Development and Submitting Patches
+==================================
+
+Petitboot is largely written in C and follows the `Linux kernel coding style <https://github.com/torvalds/linux/blob/master/Documentation/process/coding-style.rst>`_.
+
+Development occurs on the `Petitboot mailing list <https://lists.ozlabs.org/listinfo/petitboot>`_.
+
+Petitboot also has a `Patchwork instance <http://patchwork.ozlabs.org/project/petitboot/list/>`_ that watches the list.
+
+Patch Guidelines
+----------------
+
+Patches should be sent to the mailing list generally following what you would see in `submitting-patches <https://github.com/torvalds/linux/blob/master/Documentation/process/submitting-patches.rst>`_
+
+In general if you generate the patch with ``git format-patch`` or ``git send-email`` you should be fine.
+
+Patches should have an obvious title and where necessary a clear commit message describing the changes.
+Avoid lumping unrelated changes together, instead putting them in separate patches in a logical order.
+If a patch is generally contained to one area (and it should be), it should generally be prefixed with the path of what it is changing, for example "discover/grub:" or "ui/ncurses:".
+
+If sending a new revision of a patch update the title to mention the verson (hint: ``git format-patch -v 2``) and include a short changelog under the ``---`` describing what changed between versions. Check out the list archives for `some examples <https://lists.ozlabs.org/pipermail/petitboot/2018-November/001188.html>`_.
+
+Stable Patches
+--------------
+
+Patches or upstream commits that need to be applied to a stable branch should be restricted to small, self-contained fixes as much as possible. Avoid backporting new features or invasive changes.
diff --git a/doc/func/autoboot.rst b/doc/func/autoboot.rst
new file mode 100644
index 0000000..a6379f9
--- /dev/null
+++ b/doc/func/autoboot.rst
@@ -0,0 +1,32 @@
+.. _auto-boot:
+
+Auto Boot Order
+===============
+
+With autoboot enabled Petitboot will consider the relative priority of each new default boot option it discovers to determine what should be booted automatically.
+
+Note that a boot option must be marked for autoboot ("default") in its own configuration file for Petitboot to autoboot it. For example:
+
+.. code-block:: none
+
+ default linux
+
+ label linux
+ kernel ./pxe/de-ad-de-ad-be-ef.vmlinuz
+ append command line initrd=./pxe/de-ad-de-ad-be-ef.initrd
+
+Boot Priority
+-------------
+
+Petitboot can prioritise boot options based on the following attributes:
+
+* Device type (Disk, Network, USB..)
+* Partition (eg, sda2)
+* Interface name (eg. eth0)
+* LVM logical-volume name (eg. "rhel7-boot-lv")
+
+Once started Petitboot will begin to countdown from the configured timeout (default 10 seconds). During this time any default boot option that is discovered is compared against the configured priority list and the current option with the highest priority. If the new option has a higher priority based on the boot order than the current option then it becomes the current option and will be booted once the countdown completes.
+
+Note that unlike some other bootloaders Petitboot does *not* wait for devices in the boot order. For example if the boot order was "Network, Disk" with a 10 second default and a disk option was found but a hypothetic network option would take longer than 10 seconds to be found (eg. slow network or DHCP server), then Petitboot won't know about it and will boot the disk option. In most cases the appropriate solution if a user runs into this is to increase the timeout value to a suitable length of time for their environment.
+
+Note that :ref:`ipmi` overrides will take precedence over any configured boot order.
diff --git a/doc/func/ipmi.rst b/doc/func/ipmi.rst
new file mode 100644
index 0000000..8f9fa05
--- /dev/null
+++ b/doc/func/ipmi.rst
@@ -0,0 +1,29 @@
+.. _ipmi:
+
+IPMI
+====
+
+Petitboot uses inband-IPMI_ on platforms that support it to report information or modify booting behaviour:
+
+.. _inband-IPMI: https://en.wikipedia.org/wiki/Intelligent_Platform_Management_Interface
+
+Platform Information
+--------------------
+
+Basic BMC information is read via the "Get Device ID" and "Get LAN Configuration Parameters" commands and shown in the System Information screen. On some platforms with an AMI BMC the OEM command "Get Device ID Golden" will also be issued to get the BMC's secondary side information.
+
+Just before successful boot Petitboot will set the OS boot sensor (command `0x30`) to notify the BMC of a successful boot.
+
+Boot Device Overrides
+---------------------
+
+Petitboot supports IPMI boot device overrides. These can be set on the BMC to temporarily override the autoboot behaviour. These are limited to the broad types of "Network", "Disk", "Safe", "CDROM", and "Setup".
+
+If a boot option matches one of these options it receives the highest priority regardless of the boot order. The exceptions are "Setup" which temporarily disables autoboot, and "Safe" which does the same and also enters "safe mode" which prevents setting up any devices until the user issues a "Rescan" command.
+
+Boot Initiator Mailbox
+----------------------
+
+Petitboot also supports the "Boot Initiator Mailbox" parameter which behaves similarly to an override but lets a full replacement boot order be set. Support for this on the BMC side is relatively limited so far, but more details can be found here_.
+
+.. _here: https://sthbrx.github.io/blog/2018/12/19/ipmi-initiating-better-overrides/
diff --git a/doc/func/parsers.rst b/doc/func/parsers.rst
new file mode 100644
index 0000000..92281bd
--- /dev/null
+++ b/doc/func/parsers.rst
@@ -0,0 +1,18 @@
+Configuration Parsers
+=====================
+
+Petitboot can read a variety of configuration formats. Generally it does this in one of two ways:
+
+Bison/Flex Parsers
+------------------
+
+The "grub2" and "native" parsers are implemented with the Bison parser and Flex lexer combo to define a grammar describing the configuration format. These parsers can hook into other Petitboot or C code as needed but for most scenarios just need a way to resolve resources (resource.h) and create boot options (device-handler.h).
+
+Writing a parser/grammar this way can be a bit more difficult at first but does lend itself to a more robust solution in the long term.
+
+Process-Pair Parsers
+--------------------
+
+Other parsers such as for PXE and SYSLINUX use a simpler mechanism provided by `parser-conf.c` In short this provides a way to load the configuration into a buffer and process each line as a key:value format. Where applicable the parser can provide it's own callbacks for these functions.
+
+This method is a lot easier to quickly construct a parser, especially for formats with relatively basic formats.
diff --git a/doc/func/plugins.rst b/doc/func/plugins.rst
new file mode 100644
index 0000000..53cf7a8
--- /dev/null
+++ b/doc/func/plugins.rst
@@ -0,0 +1,27 @@
+.. _plugins:
+
+Plugins
+=======
+
+Petitboot "plugins" provide a convenient way to package and run binaries in the Petitboot shell that would be difficult to distribute in the Linux image due to size, dependency, or licensing constraints.
+
+Plugin usage and the plugin ABI are well documented in the OpenPOWER docs repository here:
+
+`Plugin Usage & Construction <https://github.com/open-power/docs/blob/master/opal/petitboot-plugins.txt>`_
+
+`Plugin Specification & ABI <https://github.com/open-power/docs/blob/master/opal/petitboot-plugin-spec.txt>`_
+
+Petitboot will scan local devices for pb-plugin files, and will also recognise the "plugin" label in PXE network files, eg:
+
+.. code-block:: none
+
+ label Install Ubuntu 18.04
+ kernel tftp://server/ubuntu-18.04-ppc64el/vmlinux
+ initrd tftp://server/ubuntu-18.04-ppc64el/initrd.gz
+ append tasks=standard pkgsel/language-pack-patterns= pkgsel/install-language-support=false
+
+ plugin RAID Setup
+ tarball http://server/path/to/plugin-raid-1.0.pb-plugin
+
+Discovered plugins will be listed in the Plugin screen and can be installed and run from there, or run manually from the shell.
+
diff --git a/doc/func/snapshots.rst b/doc/func/snapshots.rst
new file mode 100644
index 0000000..c4932f3
--- /dev/null
+++ b/doc/func/snapshots.rst
@@ -0,0 +1,34 @@
+.. _snapshots:
+
+Snapshots
+=========
+
+By default Petitboot does not directly mount any block devices. Instead it uses the device-mapper snapshot_ device to mount an in-memory representation of the device. Any writes Petitboot or another part of the system may make to the device are written to memory rather than the physical device, providing a "real" read-only guarantee beyond just that provided by the filesystem.
+
+In normal operation this is completely transparent but there are two scenarios where actual writes are desired:
+
+.. _snapshot: https://www.kernel.org/doc/Documentation/device-mapper/snapshot.txt
+
+Saving bootloader data
+----------------------
+
+Some configuration formats include directives to save some data before boot - like GRUB's ability to record the last option booted. Snapshots will prevent this but Petitboot can be configured to merge these writes back to the source device by enabling the ""Allow bootloader scripts to modify disks" option in the System Configuration screen.
+
+Manual interaction
+------------------
+
+Any writes to a block device first mounted by Petitboot will by default be thrown away. If a user makes changes to a disk or USB device for example that they want to preserve they can tell Petitboot to merge these writes to the source device with the "sync" event. For example if the user had written something to the sda2 partition, in the shell they can run:
+
+.. code-block:: none
+
+ pb-event sync@sda2
+
+Petitboot will handle the merging itself and remount the device read-only.
+
+If desired snapshots can also be disabled via the "petitboot,snapshots?" parameter. For example:
+
+.. code-block:: none
+
+ nvram --update-config petitboot,snapshots?=false
+
+As of the next boot Petitboot will mount block devices directly.
diff --git a/doc/func/user_interface.rst b/doc/func/user_interface.rst
new file mode 100644
index 0000000..f528e8a
--- /dev/null
+++ b/doc/func/user_interface.rst
@@ -0,0 +1,51 @@
+User Interface
+==============
+
+There are two UI implementations but in practice the ncurses client `petitboot-nc` is the primary interface.
+
+The default view includes a list of discovered boot options and links to other sub screens.
+Common shortcuts include:
+
+======== =====================================
+Shortcut
+======== =====================================
+i Open the System Information screen
+e View/Edit a boot option details
+g View the status update log
+l Open the Language screen
+c Open the System Configuration screen
+n Create/Edit a boot option
+h Show help text for the current screen
+x Exit to the shell
+Ctrl-L Refresh/Redraw the display
+======== =====================================
+
+System Information
+------------------
+
+Provides an overview of the system, in particular discovered partitions and network interfaces. Depending on the platform can also include information on firmware versions, BMC information, etc.
+
+System Configuration
+--------------------
+
+The primary method to configure Petitboot. From here the user can configure the :ref:`auto-boot` order, network interface behaviour, :ref:`snapshots` settings, and handle platform-specific options like :ref:`ipmi` overrides and default consoles.
+
+Boot Option Editor
+------------------
+
+The boot option editor displays the source device and boot resources for a given option. All fields are editable; paths are relative to the source device or full URLs for remote resources.
+
+Status Log
+----------
+
+The status log keeps a running log of all status updates that normally appear in the bottom line of the interface.
+
+Plugin Menu
+-----------
+
+The plugin menu displays a list of all known :ref:`plugins` and whether they are installed or not. "Enter" installs a plugin, and "e" on an installed interface enters a detailed view of the plugin from which a user can run the plugin.
+
+Shell
+-----
+
+At any time the user may drop to a shell by exiting the UI, depending on how Petitboot has been built. This is usually a relatively feature-ful ``sh`` shell with Busybox utilities but Petitboot doesn't make any particular guarantees about available tools aside from those it uses itself.
diff --git a/doc/index.rst b/doc/index.rst
new file mode 100644
index 0000000..fe95699
--- /dev/null
+++ b/doc/index.rst
@@ -0,0 +1,53 @@
+.. Petitboot documentation master file, created by
+ sphinx-quickstart on Fri May 31 11:49:47 2019.
+ You can adapt this file completely to your liking, but it should at least
+ contain the root `toctree` directive.
+
+Petitboot Documentation
+=====================================
+
+Overview
+========
+
+.. toctree::
+ :maxdepth: 2
+
+ overview
+ components
+ platforms
+
+Development
+===========
+
+.. toctree::
+ :maxdepth: 1
+
+ dev/design
+ dev/submitting
+ dev/linux-image
+
+Functionality
+=============
+
+.. toctree::
+ :maxdepth: 1
+
+ func/parsers
+ func/user_interface
+ func/autoboot
+ func/snapshots
+ func/ipmi
+ func/plugins
+
+.. toctree::
+ :maxdepth: 2
+
+
+.. only:: latex
+
+ Indices and tables
+ ==================
+
+ * :ref:`genindex`
+ * :ref:`modindex`
+ * :ref:`search`
diff --git a/doc/overview.rst b/doc/overview.rst
new file mode 100644
index 0000000..57b6459
--- /dev/null
+++ b/doc/overview.rst
@@ -0,0 +1,12 @@
+Petitboot Overview
+==================
+
+Petitboot is a set of userspace programs to implement a kexec-based bootloader in a small Linux image. Broadly speaking Petitboot is split into a 'server' half which manages devices and finds bootable images, and a 'client' half which provides a user interface.
+
+Petitboot uses udev to discover block devices and network interfaces and parses a set of known locations for bootloader configurations. Petitboot supports a number of configuration formats including GRUB, SYSLINUX, and Yaboot. Booting is performed via the kexec mechanism.
+
+Booting behaviour can be configured in a number of ways such as boot priority based on device, network-boot behaviour, user permissions, and platform-specific options.
+
+The primary user interface is a ncurses based menu which displays boot options and system information, and provides methods to change configuration options, execute plugins, and drop to a normal shell.
+
+Petitboot is intended to be built as part of a small Linux image, for example one built via tool such as Buildroot. A good example of such an environment is the op-build Buildroot layer used for OpenPOWER: https://github.com/open-power/op-build
diff --git a/doc/platforms.rst b/doc/platforms.rst
new file mode 100644
index 0000000..b4d1d5f
--- /dev/null
+++ b/doc/platforms.rst
@@ -0,0 +1,16 @@
+Platforms
+=========
+
+Petitboot is indented to be platform agnostic and provide at least basic functionality on any platform that supports the kexec mechanism
+
+The 'platform' interface in ``pb-discover`` allows additional support for specific platforms to be built and dynamically enabled based on the running system. The current implementations of this are:
+
+platform-powerpc
+----------------
+
+Intended for "powernv" POWER platforms, which generally means those running the OPAL firmware stack. This implements support for inband IPMI functions (boot overrides, system information, etc), console interface descriptions, and loading and storing parameters from NVRAM flash storage.
+
+platform-arm64
+--------------
+
+A fairly general implementation for basic parameter support for ARM64 platforms, but is generally applicable to platforms that provide an efivarfs interface.
diff --git a/docker/Dockerfile.builder b/docker/Dockerfile.builder
index 321629f..8757871 100644
--- a/docker/Dockerfile.builder
+++ b/docker/Dockerfile.builder
@@ -25,6 +25,8 @@ RUN apt-get update && apt-get install -y \
libncurses-dev \
libssl-dev \
libuv-dev \
+ libelf-dev \
+ libdw-dev \
pkg-config \
strace \
&& rm -rf /var/lib/apt/lists/*
diff --git a/docker/build-pb b/docker/build-pb
index 6229550..e892177 100755
--- a/docker/build-pb
+++ b/docker/build-pb
@@ -137,4 +137,4 @@ cd "${TOP_DIR}"
docker_args="${docker_base_args} ${docker_user_args}"
run_cmd "docker run ${docker_args} ${DOCKER_TAG} /bin/bash \
-e ${bash_debug} \
- -c './bootstrap && ${flags} ./configure ${configure_opts[@]} && ${makecmd} && ${docker_extra}'"
+ -c './bootstrap && d=\$(mktemp -d) && cd \$d && ${flags} /opt/pb/configure ${configure_opts[@]} && ${makecmd} && ${docker_extra}'"
diff --git a/lib/param_list/param_list.c b/lib/param_list/param_list.c
index 9a01be6..9dc3228 100644
--- a/lib/param_list/param_list.c
+++ b/lib/param_list/param_list.c
@@ -23,6 +23,7 @@ const char **common_known_params(void)
"petitboot,http_proxy",
"petitboot,https_proxy",
"petitboot,password",
+ "petitboot,preboot-check",
NULL,
};
diff --git a/lib/pb-config/pb-config.c b/lib/pb-config/pb-config.c
index a802c92..735cd98 100644
--- a/lib/pb-config/pb-config.c
+++ b/lib/pb-config/pb-config.c
@@ -43,6 +43,9 @@ struct config *config_copy(void *ctx, const struct config *src)
struct config *dest;
unsigned int i;
+ if (!src)
+ return NULL;
+
dest = talloc_zero(ctx, struct config);
dest->autoboot_enabled = src->autoboot_enabled;
dest->autoboot_timeout_sec = src->autoboot_timeout_sec;
@@ -88,11 +91,14 @@ struct config *config_copy(void *ctx, const struct config *src)
dest->allow_writes = src->allow_writes;
dest->n_consoles = src->n_consoles;
- if (src->consoles)
+ if (src->consoles) {
dest->consoles = talloc_array(dest, char *, src->n_consoles);
- for (i = 0; i < src->n_consoles && src->n_consoles; i++)
- dest->consoles[i] = talloc_strdup(dest->consoles,
+ for (i = 0; i < src->n_consoles && src->n_consoles; i++)
+ if (src->consoles[i])
+ dest->consoles[i] = talloc_strdup(
+ dest->consoles,
src->consoles[i]);
+ }
if (src->boot_console)
dest->boot_console = talloc_strdup(dest, src->boot_console);
diff --git a/lib/pb-protocol/pb-protocol.c b/lib/pb-protocol/pb-protocol.c
index 33bd4e6..aff1946 100644
--- a/lib/pb-protocol/pb-protocol.c
+++ b/lib/pb-protocol/pb-protocol.c
@@ -99,13 +99,17 @@ int pb_protocol_serialise_string(char *pos, const char *str)
{
int len = 0;
+ if (!pos)
+ return 0;
+
if (str)
len = strlen(str);
*(uint32_t *)pos = __cpu_to_be32(len);
pos += sizeof(uint32_t);
- memcpy(pos, str, len);
+ if (str)
+ memcpy(pos, str, len);
return len + sizeof(uint32_t);
}
@@ -249,6 +253,9 @@ int pb_protocol_system_info_len(const struct system_info *sysinfo)
for (i = 0; i < sysinfo->n_bmc_golden; i++)
len += 4 + optional_strlen(sysinfo->bmc_golden[i]);
+ /* BMC MAC */
+ len += HWADDR_SIZE;
+
for (i = 0; i < sysinfo->n_interfaces; i++) {
struct interface_info *if_info = sysinfo->interfaces[i];
len += 4 + if_info->hwaddr_size +
@@ -265,8 +272,8 @@ int pb_protocol_system_info_len(const struct system_info *sysinfo)
4 + optional_strlen(bd_info->mountpoint);
}
- /* BMC MAC */
- len += HWADDR_SIZE;
+ /* stb info */
+ len += 3 * sizeof(bool);
return len;
}
@@ -337,6 +344,8 @@ int pb_protocol_config_len(const struct config *config)
len += 4 + optional_strlen(config->lang);
+ len += 4; /* preboot check */
+
return len;
}
@@ -417,9 +426,8 @@ int pb_protocol_serialise_device(const struct device *dev,
pos += pb_protocol_serialise_string(pos, dev->icon_file);
assert(pos <= buf + buf_len);
- (void)buf_len;
- return 0;
+ return (pos <= buf + buf_len) ? 0 : -1;
}
int pb_protocol_serialise_boot_option(const struct boot_option *opt,
@@ -447,9 +455,8 @@ int pb_protocol_serialise_boot_option(const struct boot_option *opt,
pos += 4;
assert(pos <= buf + buf_len);
- (void)buf_len;
- return 0;
+ return (pos <= buf + buf_len) ? 0 : -1;
}
int pb_protocol_serialise_boot_command(const struct boot_command *boot,
@@ -466,9 +473,8 @@ int pb_protocol_serialise_boot_command(const struct boot_command *boot,
pos += pb_protocol_serialise_string(pos, boot->console);
assert(pos <= buf + buf_len);
- (void)buf_len;
- return 0;
+ return (pos <= buf + buf_len) ? 0 : -1;
}
int pb_protocol_serialise_boot_status(const struct status *status,
@@ -488,9 +494,8 @@ int pb_protocol_serialise_boot_status(const struct status *status,
pos += sizeof(bool);
assert(pos <= buf + buf_len);
- (void)buf_len;
- return 0;
+ return (pos <= buf + buf_len) ? 0 : -1;
}
int pb_protocol_serialise_system_info(const struct system_info *sysinfo,
@@ -560,10 +565,16 @@ int pb_protocol_serialise_system_info(const struct system_info *sysinfo,
memset(pos, 0, HWADDR_SIZE);
pos += HWADDR_SIZE;
+ *(bool *)pos = sysinfo->stb_fw_measurement;
+ pos += sizeof(bool);
+ *(bool *)pos = sysinfo->stb_fw_enforcing;
+ pos += sizeof(bool);
+ *(bool *)pos = sysinfo->stb_os_enforcing;
+ pos += sizeof(bool);
+
assert(pos <= buf + buf_len);
- (void)buf_len;
- return 0;
+ return (pos <= buf + buf_len) ? 0 : -1;
}
static int pb_protocol_serialise_config_interface(char *buf,
@@ -668,10 +679,12 @@ int pb_protocol_serialise_config(const struct config *config,
pos += pb_protocol_serialise_string(pos, config->lang);
+ *(uint32_t *)pos = config->preboot_check_enabled;
+ pos += 4;
+
assert(pos <= buf + buf_len);
- (void)buf_len;
- return 0;
+ return (pos <= buf + buf_len) ? 0 : -1;
}
int pb_protocol_serialise_url(const char *url, char *buf, int buf_len)
@@ -681,9 +694,8 @@ int pb_protocol_serialise_url(const char *url, char *buf, int buf_len)
pos += pb_protocol_serialise_string(pos, url);
assert(pos <=buf+buf_len);
- (void)buf_len;
- return 0;
+ return (pos <= buf + buf_len) ? 0 : -1;
}
int pb_protocol_serialise_plugin_option(const struct plugin_option *opt,
@@ -707,9 +719,8 @@ int pb_protocol_serialise_plugin_option(const struct plugin_option *opt,
pos += pb_protocol_serialise_string(pos, opt->executables[i]);
assert(pos <= buf + buf_len);
- (void)buf_len;
- return 0;
+ return (pos <= buf + buf_len) ? 0 : -1;
}
int pb_protocol_serialise_temp_autoboot(const struct autoboot_option *opt,
@@ -727,9 +738,9 @@ int pb_protocol_serialise_temp_autoboot(const struct autoboot_option *opt,
pos += pb_protocol_serialise_string(pos, opt->uuid);
}
- (void)buf_len;
+ assert(pos <= buf + buf_len);
- return 0;
+ return (pos <= buf + buf_len) ? 0 : -1;
}
int pb_protocol_serialise_authenticate(struct auth_message *msg,
@@ -766,9 +777,8 @@ int pb_protocol_serialise_authenticate(struct auth_message *msg,
};
assert(pos <= buf + buf_len);
- (void)buf_len;
- return 0;
+ return (pos <= buf + buf_len) ? 0 : -1;
}
int pb_protocol_write_message(int fd, struct pb_protocol_message *message)
@@ -948,7 +958,7 @@ int pb_protocol_deserialise_boot_option(struct boot_option *opt,
if (read_u32(&pos, &len, &opt->type))
return -1;
- rc = 0;
+ rc = (pos <= message->payload + message->payload_len) ? 0 : -1;
out:
return rc;
@@ -1165,6 +1175,14 @@ int pb_protocol_deserialise_system_info(struct system_info *sysinfo,
pos += HWADDR_SIZE;
len -= HWADDR_SIZE;
+ sysinfo->stb_fw_measurement = !!*pos;
+ pos += sizeof(bool);
+ sysinfo->stb_fw_enforcing = !!*pos;
+ pos += sizeof(bool);
+ sysinfo->stb_os_enforcing = !!*pos;
+ pos += sizeof(bool);
+ len -= 3 * sizeof(bool);
+
rc = 0;
out:
return rc;
@@ -1322,6 +1340,10 @@ int pb_protocol_deserialise_config(struct config *config,
config->lang = str;
+ if (read_u32(&pos, &len, &tmp))
+ goto out;
+ config->preboot_check_enabled = !!tmp;
+
rc = 0;
out:
diff --git a/lib/types/types.h b/lib/types/types.h
index 433a37b..8018fde 100644
--- a/lib/types/types.h
+++ b/lib/types/types.h
@@ -133,11 +133,14 @@ struct system_info {
char **bmc_golden;
unsigned int n_bmc_current;
unsigned int n_bmc_golden;
- uint8_t *bmc_mac;
struct interface_info **interfaces;
unsigned int n_interfaces;
struct blockdev_info **blockdevs;
unsigned int n_blockdevs;
+ uint8_t *bmc_mac;
+ bool stb_fw_measurement;
+ bool stb_fw_enforcing;
+ bool stb_os_enforcing;
};
#define HWADDR_SIZE 6
@@ -184,6 +187,8 @@ struct config {
unsigned int autoboot_timeout_sec;
struct network_config network;
+ bool preboot_check_enabled;
+
struct autoboot_option *autoboot_opts;
unsigned int n_autoboot_opts;
diff --git a/s b/s
deleted file mode 100644
index e69de29..0000000
--- a/s
+++ /dev/null
diff --git a/test/parser/Makefile.am b/test/parser/Makefile.am
index f9083bd..866e9d4 100644
--- a/test/parser/Makefile.am
+++ b/test/parser/Makefile.am
@@ -19,6 +19,8 @@ parser_TESTS = \
test/parser/test-grub2-noeol \
test/parser/test-grub2-menuentry-formats \
test/parser/test-grub2-if-formats \
+ test/parser/test-grub2-default-id \
+ test/parser/test-grub2-default-id-space \
test/parser/test-grub2-default-index \
test/parser/test-grub2-default-multiword \
test/parser/test-grub2-implicit-default-unset \
@@ -27,6 +29,11 @@ parser_TESTS = \
test/parser/test-grub2-multiple-id \
test/parser/test-grub2-single-line-if \
test/parser/test-grub2-pos-param \
+ test/parser/test-grub2-search-args \
+ test/parser/test-grub2-search-uuid \
+ test/parser/test-grub2-search-label \
+ test/parser/test-grub2-devpath \
+ test/parser/test-grub2-devpath-scripting \
test/parser/test-grub2-load-env \
test/parser/test-grub2-save-env \
test/parser/test-grub2-save-env-dash-f \
@@ -36,9 +43,15 @@ parser_TESTS = \
test/parser/test-grub2-f20-ppc64 \
test/parser/test-grub2-ubuntu-13_04-x86 \
test/parser/test-grub2-sles-btrfs-snapshot \
+ test/parser/test-grub2-rhel8 \
+ test/parser/test-grub2-rhcos-ootpa \
test/parser/test-grub2-lexer-error \
test/parser/test-grub2-parser-error \
test/parser/test-grub2-test-file-ops \
+ test/parser/test-grub2-source \
+ test/parser/test-grub2-source-functions \
+ test/parser/test-grub2-source-recursion \
+ test/parser/test-grub2-source-recursion-infinite \
test/parser/test-grub2-single-yocto \
test/parser/test-grub2-blscfg-default-filename \
test/parser/test-grub2-blscfg-default-index \
@@ -102,6 +115,8 @@ check_DATA += \
test/parser/data/grub2-f18-ppc64.conf \
test/parser/data/grub2-f20-ppc.conf \
test/parser/data/grub2-ubuntu-13_04-x86.conf \
+ test/parser/data/grub2-rhel8.conf \
+ test/parser/data/grub2-rhcos-ootpa.conf \
test/parser/data/yaboot-rh8-ppc64.conf \
test/parser/data/syslinux-include-root.cfg \
test/parser/data/syslinux-include-nest-1.cfg \
diff --git a/test/parser/data/grub2-rhcos-ootpa.conf b/test/parser/data/grub2-rhcos-ootpa.conf
new file mode 100644
index 0000000..329980e
--- /dev/null
+++ b/test/parser/data/grub2-rhcos-ootpa.conf
@@ -0,0 +1,194 @@
+#
+# DO NOT EDIT THIS FILE
+#
+# It is automatically generated by grub2-mkconfig using templates
+# from /etc/grub.d and settings from /etc/default/grub
+#
+
+### BEGIN /etc/grub.d/00_header ###
+set pager=1
+
+if [ -f ${config_directory}/grubenv ]; then
+ load_env -f ${config_directory}/grubenv
+elif [ -s $prefix/grubenv ]; then
+ load_env
+fi
+if [ "${next_entry}" ] ; then
+ set default="${next_entry}"
+ set next_entry=
+ save_env next_entry
+ set boot_once=true
+else
+ set default="${saved_entry}"
+fi
+
+if [ x"${feature_menuentry_id}" = xy ]; then
+ menuentry_id_option="--id"
+else
+ menuentry_id_option=""
+fi
+
+export menuentry_id_option
+
+if [ "${prev_saved_entry}" ]; then
+ set saved_entry="${prev_saved_entry}"
+ save_env saved_entry
+ set prev_saved_entry=
+ save_env prev_saved_entry
+ set boot_once=true
+fi
+
+function savedefault {
+ if [ -z "${boot_once}" ]; then
+ saved_entry="${chosen}"
+ save_env saved_entry
+ fi
+}
+
+function load_video {
+ if [ x$feature_all_video_module = xy ]; then
+ insmod all_video
+ else
+ insmod efi_gop
+ insmod efi_uga
+ insmod ieee1275_fb
+ insmod vbe
+ insmod vga
+ insmod video_bochs
+ insmod video_cirrus
+ fi
+}
+
+terminal_output ofconsole
+if [ x$feature_timeout_style = xy ] ; then
+ set timeout_style=menu
+ set timeout=1
+# Fallback normal timeout code in case the timeout_style feature is
+# unavailable.
+else
+ set timeout=1
+fi
+### END /etc/grub.d/00_header ###
+
+### BEGIN /etc/grub.d/01_menu_auto_hide ###
+if [ "${boot_success}" = "1" -o "${boot_indeterminate}" = "1" ]; then
+ set last_boot_ok=1
+else
+ set last_boot_ok=0
+fi
+
+# Reset boot_indeterminate after a successful boot
+if [ "${boot_success}" = "1" ] ; then
+ set boot_indeterminate=0
+# Avoid boot_indeterminate causing the menu to be hidden more then once
+elif [ "${boot_indeterminate}" = "1" ]; then
+ set boot_indeterminate=2
+fi
+set boot_success=0
+save_env boot_success boot_indeterminate
+
+if [ x$feature_timeout_style = xy ] ; then
+ if [ "${menu_show_once}" ]; then
+ unset menu_show_once
+ save_env menu_show_once
+ set timeout_style=menu
+ set timeout=60
+ elif [ "${menu_auto_hide}" -a "${last_boot_ok}" = "1" ]; then
+ set orig_timeout_style=${timeout_style}
+ set orig_timeout=${timeout}
+ if [ "${fastboot}" = "1" ]; then
+ # timeout_style=menu + timeout=0 avoids the countdown code keypress check
+ set timeout_style=menu
+ set timeout=0
+ else
+ set timeout_style=hidden
+ set timeout=1
+ fi
+ fi
+fi
+### END /etc/grub.d/01_menu_auto_hide ###
+
+### BEGIN /etc/grub.d/01_users ###
+if [ -f ${prefix}/user.cfg ]; then
+ source ${prefix}/user.cfg
+ if [ -n "${GRUB2_PASSWORD}" ]; then
+ set superusers="root"
+ export superusers
+ password_pbkdf2 root ${GRUB2_PASSWORD}
+ fi
+fi
+### END /etc/grub.d/01_users ###
+
+### BEGIN /etc/grub.d/02_ignition_firstboot ###
+# We store the file on the /boot/ partition so find the
+# boot partition. On UEFI this may different than the grub
+# $root so we search for it here.
+# https://github.com/coreos/ignition-dracut/issues/51
+search --set=bootpart --label boot
+# Determine if this is a first boot and set the variable
+# to be used later on the kernel command line.
+set ignition_firstboot=""
+if [ -f "(${bootpart})/ignition.firstboot" ]; then
+ # default to dhcp networking parameters to be used with ignition
+ set ignition_network_kcmdline='rd.neednet=1 ip=dhcp'
+
+ # source in the `ignition.firstboot` file which could override the
+ # above $ignition_network_kcmdline with static networking config.
+ # This override feature is primarily used by coreos-installer to
+ # persist static networking config provided during install to the
+ # first boot of the machine.
+ source "(${bootpart})/ignition.firstboot"
+
+ # we support setting variables in the
+ set ignition_firstboot="ignition.firstboot $ignition_network_kcmdline $ignition_extra_kcmdline"
+fi
+### END /etc/grub.d/02_ignition_firstboot ###
+
+### BEGIN /etc/grub.d/10_linux_bls ###
+
+### END /etc/grub.d/10_linux_bls ###
+
+### BEGIN /etc/grub.d/15_ostree ###
+menuentry 'Red Hat Enterprise Linux CoreOS 42.80.20191030.0 (Ootpa) (ostree)' --class gnu-linux --class gnu --class os --unrestricted 'ostree-0-645e1535-a6f3-4fa6-a82c-b8c032619a7b' {
+load_video
+set gfxpayload=keep
+insmod gzio
+insmod part_gpt
+insmod ext2
+set root='hd0,gpt2'
+if [ x$feature_platform_search_hint = xy ]; then
+ search --no-floppy --fs-uuid --set=root --hint='hd0,gpt2' 645e1535-a6f3-4fa6-a82c-b8c032619a7b
+else
+ search --no-floppy --fs-uuid --set=root 645e1535-a6f3-4fa6-a82c-b8c032619a7b
+fi
+linux /ostree/rhcos-6264e4be818e20cf1021bd6e7aa8c76147ce07dec186468c7dfbbc9c5dfc7d8b/vmlinuz-4.18.0-80.11.2.el8_0.ppc64le console=tty0 console=hvc0,115200n8 rootflags=defaults,prjquota rw $ignition_firstboot root=UUID=8d8a5c3b-97e6-4d7b-bb87-206af5a9d851 ostree=/ostree/boot.0/rhcos/6264e4be818e20cf1021bd6e7aa8c76147ce07dec186468c7dfbbc9c5dfc7d8b/0 ignition.platform.id=openstack
+initrd /ostree/rhcos-6264e4be818e20cf1021bd6e7aa8c76147ce07dec186468c7dfbbc9c5dfc7d8b/initramfs-4.18.0-80.11.2.el8_0.ppc64le.img
+}
+### END /etc/grub.d/15_ostree ###
+
+### BEGIN /etc/grub.d/20_linux_xen ###
+### END /etc/grub.d/20_linux_xen ###
+
+### BEGIN /etc/grub.d/20_ppc_terminfo ###
+ terminfo -g 80x24 ofconsole
+### END /etc/grub.d/20_ppc_terminfo ###
+
+### BEGIN /etc/grub.d/30_os-prober ###
+### END /etc/grub.d/30_os-prober ###
+
+### BEGIN /etc/grub.d/30_uefi-firmware ###
+### END /etc/grub.d/30_uefi-firmware ###
+
+### BEGIN /etc/grub.d/40_custom ###
+# This file provides an easy way to add custom menu entries. Simply type the
+# menu entries you want to add after this comment. Be careful not to change
+# the 'exec tail' line above.
+### END /etc/grub.d/40_custom ###
+
+### BEGIN /etc/grub.d/41_custom ###
+if [ -f ${config_directory}/custom.cfg ]; then
+ source ${config_directory}/custom.cfg
+elif [ -z "${config_directory}" -a -f $prefix/custom.cfg ]; then
+ source $prefix/custom.cfg;
+fi
+### END /etc/grub.d/41_custom ###
diff --git a/test/parser/data/grub2-rhel8.conf b/test/parser/data/grub2-rhel8.conf
new file mode 100644
index 0000000..563b3e5
--- /dev/null
+++ b/test/parser/data/grub2-rhel8.conf
@@ -0,0 +1,190 @@
+#
+# DO NOT EDIT THIS FILE
+#
+# It is automatically generated by grub2-mkconfig using templates
+# from /etc/grub.d and settings from /etc/default/grub
+#
+
+### BEGIN /etc/grub.d/00_header ###
+set pager=1
+
+if [ -f ${config_directory}/grubenv ]; then
+ load_env -f ${config_directory}/grubenv
+elif [ -s $prefix/grubenv ]; then
+ load_env
+fi
+if [ "${next_entry}" ] ; then
+ set default="${next_entry}"
+ set next_entry=
+ save_env next_entry
+ set boot_once=true
+else
+ set default="${saved_entry}"
+fi
+
+if [ x"${feature_menuentry_id}" = xy ]; then
+ menuentry_id_option="--id"
+else
+ menuentry_id_option=""
+fi
+
+export menuentry_id_option
+
+if [ "${prev_saved_entry}" ]; then
+ set saved_entry="${prev_saved_entry}"
+ save_env saved_entry
+ set prev_saved_entry=
+ save_env prev_saved_entry
+ set boot_once=true
+fi
+
+function savedefault {
+ if [ -z "${boot_once}" ]; then
+ saved_entry="${chosen}"
+ save_env saved_entry
+ fi
+}
+
+function load_video {
+ if [ x$feature_all_video_module = xy ]; then
+ insmod all_video
+ else
+ insmod efi_gop
+ insmod efi_uga
+ insmod ieee1275_fb
+ insmod vbe
+ insmod vga
+ insmod video_bochs
+ insmod video_cirrus
+ fi
+}
+
+terminal_output ofconsole
+if [ x$feature_timeout_style = xy ] ; then
+ set timeout_style=menu
+ set timeout=5
+# Fallback normal timeout code in case the timeout_style feature is
+# unavailable.
+else
+ set timeout=5
+fi
+### END /etc/grub.d/00_header ###
+
+### BEGIN /etc/grub.d/00_tuned ###
+set tuned_params=""
+set tuned_initrd=""
+### END /etc/grub.d/00_tuned ###
+
+### BEGIN /etc/grub.d/01_menu_auto_hide ###
+if [ "${boot_success}" = "1" -o "${boot_indeterminate}" = "1" ]; then
+ set last_boot_ok=1
+else
+ set last_boot_ok=0
+fi
+
+# Reset boot_indeterminate after a successful boot
+if [ "${boot_success}" = "1" ] ; then
+ set boot_indeterminate=0
+# Avoid boot_indeterminate causing the menu to be hidden more then once
+elif [ "${boot_indeterminate}" = "1" ]; then
+ set boot_indeterminate=2
+fi
+set boot_success=0
+save_env boot_success boot_indeterminate
+
+if [ x$feature_timeout_style = xy ] ; then
+ if [ "${menu_show_once}" ]; then
+ unset menu_show_once
+ save_env menu_show_once
+ set timeout_style=menu
+ set timeout=60
+ elif [ "${menu_auto_hide}" -a "${last_boot_ok}" = "1" ]; then
+ set orig_timeout_style=${timeout_style}
+ set orig_timeout=${timeout}
+ if [ "${fastboot}" = "1" ]; then
+ # timeout_style=menu + timeout=0 avoids the countdown code keypress check
+ set timeout_style=menu
+ set timeout=0
+ else
+ set timeout_style=hidden
+ set timeout=1
+ fi
+ fi
+fi
+### END /etc/grub.d/01_menu_auto_hide ###
+
+### BEGIN /etc/grub.d/01_users ###
+if [ -f ${prefix}/user.cfg ]; then
+ source ${prefix}/user.cfg
+ if [ -n "${GRUB2_PASSWORD}" ]; then
+ set superusers="root"
+ export superusers
+ password_pbkdf2 root ${GRUB2_PASSWORD}
+ fi
+fi
+### END /etc/grub.d/01_users ###
+
+### BEGIN /etc/grub.d/10_linux_bls ###
+insmod part_msdos
+insmod xfs
+set root='hd0,msdos2'
+if [ x$feature_platform_search_hint = xy ]; then
+ search --no-floppy --fs-uuid --set=root --hint-ieee1275='ieee1275//pciex@600c3c0100000/pci@0/pci@0/pci@9/raid@0/disk@8,msdos2' --hint-bios=hd0,msdos2 --hint-efi=hd0,msdos2 --hint-baremetal=ahci0,msdos2 --hint='hd0,msdos2' 9a8ea027-4829-45b9-829b-18ed6cc1f33b
+else
+ search --no-floppy --fs-uuid --set=root 9a8ea027-4829-45b9-829b-18ed6cc1f33b
+fi
+insmod part_msdos
+insmod xfs
+set boot='hd0,msdos2'
+if [ x$feature_platform_search_hint = xy ]; then
+ search --no-floppy --fs-uuid --set=boot --hint-ieee1275='ieee1275//pciex@600c3c0100000/pci@0/pci@0/pci@9/raid@0/disk@8,msdos2' --hint-bios=hd0,msdos2 --hint-efi=hd0,msdos2 --hint-baremetal=ahci0,msdos2 --hint='hd0,msdos2' 9a8ea027-4829-45b9-829b-18ed6cc1f33b
+else
+ search --no-floppy --fs-uuid --set=boot 9a8ea027-4829-45b9-829b-18ed6cc1f33b
+fi
+
+# This section was generated by a script. Do not modify the generated file - all changes
+# will be lost the next time file is regenerated. Instead edit the BootLoaderSpec files.
+
+menuentry 'Red Hat Enterprise Linux (4.18.0-80.11.2.el8_0.ppc64le) 8.0 (Ootpa)' --class kernel --unrestricted --users $grub_users --id 8e15296c9cc14deb9e3f8548d49fd6fc-4.18.0-80.11.2.el8_0.ppc64le {
+ linux /vmlinuz-4.18.0-80.11.2.el8_0.ppc64le $kernelopts $tuned_params
+ initrd /initramfs-4.18.0-80.11.2.el8_0.ppc64le.img $tuned_initrd
+}
+
+menuentry 'Red Hat Enterprise Linux (4.18.0-80.el8.ppc64le) 8.0 (Ootpa)' --class kernel --unrestricted --users $grub_users --id 8e15296c9cc14deb9e3f8548d49fd6fc-4.18.0-80.el8.ppc64le {
+ linux /vmlinuz-4.18.0-80.el8.ppc64le $kernelopts $tuned_params
+ initrd /initramfs-4.18.0-80.el8.ppc64le.img $tuned_initrd
+}
+
+menuentry 'Red Hat Enterprise Linux (0-rescue-8e15296c9cc14deb9e3f8548d49fd6fc) 8.0 (Ootpa)' --class kernel --unrestricted --users $grub_users --id 8e15296c9cc14deb9e3f8548d49fd6fc-0-rescue {
+ linux /vmlinuz-0-rescue-8e15296c9cc14deb9e3f8548d49fd6fc $kernelopts
+ initrd /initramfs-0-rescue-8e15296c9cc14deb9e3f8548d49fd6fc.img
+}
+
+### END /etc/grub.d/10_linux_bls ###
+
+### BEGIN /etc/grub.d/20_linux_xen ###
+### END /etc/grub.d/20_linux_xen ###
+
+### BEGIN /etc/grub.d/20_ppc_terminfo ###
+ terminfo -g 80x24 ofconsole
+### END /etc/grub.d/20_ppc_terminfo ###
+
+### BEGIN /etc/grub.d/30_os-prober ###
+### END /etc/grub.d/30_os-prober ###
+
+### BEGIN /etc/grub.d/30_uefi-firmware ###
+### END /etc/grub.d/30_uefi-firmware ###
+
+### BEGIN /etc/grub.d/40_custom ###
+# This file provides an easy way to add custom menu entries. Simply type the
+# menu entries you want to add after this comment. Be careful not to change
+# the 'exec tail' line above.
+### END /etc/grub.d/40_custom ###
+
+### BEGIN /etc/grub.d/41_custom ###
+if [ -f ${config_directory}/custom.cfg ]; then
+ source ${config_directory}/custom.cfg
+elif [ -z "${config_directory}" -a -f $prefix/custom.cfg ]; then
+ source $prefix/custom.cfg;
+fi
+### END /etc/grub.d/41_custom ###
diff --git a/test/parser/test-grub2-default-id-space.c b/test/parser/test-grub2-default-id-space.c
new file mode 100644
index 0000000..df0eb2a
--- /dev/null
+++ b/test/parser/test-grub2-default-id-space.c
@@ -0,0 +1,34 @@
+
+#include "parser-test.h"
+
+#if 0 /* PARSER_EMBEDDED_CONFIG */
+set default=option1
+menuentry 'test-option-0' --id option0 {
+ linux /vmlinux.0
+}
+menuentry 'test-option-1' --id option1 {
+ linux /vmlinux.1
+}
+menuentry 'test-option-2' --id option2 {
+ linux /vmlinux.2
+}
+#endif
+
+void run_test(struct parser_test *test)
+{
+ struct discover_boot_option *opt;
+ struct discover_context *ctx;
+
+ test_read_conf_embedded(test, "/boot/grub2/grub.cfg");
+ test_run_parser(test, "grub2");
+
+ ctx = test->ctx;
+
+ check_boot_option_count(ctx, 3);
+ opt = get_boot_option(ctx, 1);
+
+ check_name(opt, "test-option-1");
+ check_resolved_local_resource(opt->boot_image, ctx->device,
+ "/vmlinux.1");
+ check_is_default(opt);
+}
diff --git a/test/parser/test-grub2-default-id.c b/test/parser/test-grub2-default-id.c
new file mode 100644
index 0000000..a41a4f9
--- /dev/null
+++ b/test/parser/test-grub2-default-id.c
@@ -0,0 +1,34 @@
+
+#include "parser-test.h"
+
+#if 0 /* PARSER_EMBEDDED_CONFIG */
+set default=option1
+menuentry 'test-option-0' --id=option0 {
+ linux /vmlinux.0
+}
+menuentry 'test-option-1' --id=option1 {
+ linux /vmlinux.1
+}
+menuentry 'test-option-2' --id=option2 {
+ linux /vmlinux.2
+}
+#endif
+
+void run_test(struct parser_test *test)
+{
+ struct discover_boot_option *opt;
+ struct discover_context *ctx;
+
+ test_read_conf_embedded(test, "/boot/grub2/grub.cfg");
+ test_run_parser(test, "grub2");
+
+ ctx = test->ctx;
+
+ check_boot_option_count(ctx, 3);
+ opt = get_boot_option(ctx, 1);
+
+ check_name(opt, "test-option-1");
+ check_resolved_local_resource(opt->boot_image, ctx->device,
+ "/vmlinux.1");
+ check_is_default(opt);
+}
diff --git a/test/parser/test-grub2-default-multiword.c b/test/parser/test-grub2-default-multiword.c
index 25d1cf1..d455d62 100644
--- a/test/parser/test-grub2-default-multiword.c
+++ b/test/parser/test-grub2-default-multiword.c
@@ -3,10 +3,10 @@
#if 0 /* PARSER_EMBEDDED_CONFIG */
set default="Multiple word option"
-menuentry 'Non-defalt option' {
+menuentry 'Non-defalt option' --id=option0 {
linux /vmlinux.non-default
}
-menuentry 'Multiple word option' {
+menuentry 'Multiple word option' --id=option1 {
linux /vmlinux
}
#endif
diff --git a/test/parser/test-grub2-devpath-scripting.c b/test/parser/test-grub2-devpath-scripting.c
new file mode 100644
index 0000000..9046ab6
--- /dev/null
+++ b/test/parser/test-grub2-devpath-scripting.c
@@ -0,0 +1,56 @@
+/* check grub2 device+path string parsing, as used in scripts */
+
+#include "parser-test.h"
+
+#if 0 /* PARSER_EMBEDDED_CONFIG */
+
+v=
+
+# local device, file present
+if [ -f "/1-present" ]; then v=${v}a; fi
+
+# local device, file absent
+if [ -f "/1-absent" ]; then v=${v}b; fi;
+
+# local device by UUID, file present
+if [ -f "(00000000-0000-0000-0000-000000000001)/1-present" ]; then v=${v}c; fi;
+
+# remote device by UUID, file present
+if [ -f "(00000000-0000-0000-0000-000000000002)/2-present" ]; then v=${v}d; fi;
+
+# non-existent device
+if [ -f "(00000000-0000-0000-0000-000000000003)/present" ]; then v=${v}e; fi;
+
+menuentry $v {
+ linux /vmlinux
+}
+
+#endif
+
+void run_test(struct parser_test *test)
+{
+ struct discover_device *dev1, *dev2;
+ struct discover_boot_option *opt;
+ struct discover_context *ctx;
+
+ ctx = test->ctx;
+
+ /* set local uuid */
+ dev1 = test->ctx->device;
+ dev1->uuid = "00000000-0000-0000-0000-000000000001";
+
+ dev2 = test_create_device(test, "extdev");
+ dev2->uuid = "00000000-0000-0000-0000-000000000002";
+ device_handler_add_device(ctx->handler, dev2);
+
+ test_add_file_data(test, dev1, "/1-present", "x", 1);
+ test_add_file_data(test, dev2, "/2-present", "x", 1);
+
+ test_read_conf_embedded(test, "/grub/grub.cfg");
+
+ test_run_parser(test, "grub2");
+
+ check_boot_option_count(ctx, 1);
+ opt = get_boot_option(ctx, 0);
+ check_name(opt, "acd");
+}
diff --git a/test/parser/test-grub2-devpath.c b/test/parser/test-grub2-devpath.c
new file mode 100644
index 0000000..d1d00f1
--- /dev/null
+++ b/test/parser/test-grub2-devpath.c
@@ -0,0 +1,88 @@
+/* check grub2 device+path string parsing */
+
+#include "parser-test.h"
+
+#if 0 /* PARSER_EMBEDDED_CONFIG */
+
+# local
+menuentry a {
+ linux /vmlinux
+}
+
+# local, specified by root env var
+root=00000000-0000-0000-0000-000000000001
+menuentry b {
+ linux /vmlinux
+}
+
+# remote, specified by root env var
+root=00000000-0000-0000-0000-000000000002
+menuentry c {
+ linux /vmlinux
+}
+
+# local, full dev+path spec
+menuentry d {
+ linux (00000000-0000-0000-0000-000000000001)/vmlinux
+}
+
+# remote, full dev+path spec
+menuentry e {
+ linux (00000000-0000-0000-0000-000000000002)/vmlinux
+}
+
+# invalid: incomplete dev+path spec
+menuentry f {
+ linux (00000000-0000-0000-0000-000000000001
+}
+
+# invalid: no path
+menuentry g {
+ linux (00000000-0000-0000-0000-000000000001)
+}
+
+
+#endif
+
+void run_test(struct parser_test *test)
+{
+ struct discover_device *dev1, *dev2;
+ struct discover_boot_option *opt;
+ struct discover_context *ctx;
+
+ ctx = test->ctx;
+
+ /* set local uuid */
+ dev1 = test->ctx->device;
+ dev1->uuid = "00000000-0000-0000-0000-000000000001";
+
+ dev2 = test_create_device(test, "extdev");
+ dev2->uuid = "00000000-0000-0000-0000-000000000002";
+ device_handler_add_device(ctx->handler, dev2);
+
+ test_read_conf_embedded(test, "/grub/grub.cfg");
+
+ test_run_parser(test, "grub2");
+
+ check_boot_option_count(ctx, 5);
+
+ opt = get_boot_option(ctx, 0);
+ check_name(opt, "a");
+ check_resolved_local_resource(opt->boot_image, dev1, "/vmlinux");
+
+ opt = get_boot_option(ctx, 1);
+ check_name(opt, "b");
+ check_resolved_local_resource(opt->boot_image, dev1, "/vmlinux");
+
+ opt = get_boot_option(ctx, 2);
+ check_name(opt, "c");
+ check_resolved_local_resource(opt->boot_image, dev2, "/vmlinux");
+
+ opt = get_boot_option(ctx, 3);
+ check_name(opt, "d");
+ check_resolved_local_resource(opt->boot_image, dev1, "/vmlinux");
+
+ opt = get_boot_option(ctx, 4);
+ check_name(opt, "e");
+ check_resolved_local_resource(opt->boot_image, dev2, "/vmlinux");
+}
diff --git a/test/parser/test-grub2-rhcos-ootpa.c b/test/parser/test-grub2-rhcos-ootpa.c
new file mode 100644
index 0000000..19299be
--- /dev/null
+++ b/test/parser/test-grub2-rhcos-ootpa.c
@@ -0,0 +1,38 @@
+
+#include "parser-test.h"
+
+void run_test(struct parser_test *test)
+{
+ struct discover_boot_option *opt;
+ struct discover_context *ctx;
+ struct discover_device *dev;
+
+ ctx = test->ctx;
+
+ dev = test_create_device(test, "bootdev");
+ dev->label = "boot";
+ device_handler_add_device(test->handler, dev);
+
+ test_read_conf_file(test, "grub2-rhcos-ootpa.conf",
+ "/grub/grub.cfg");
+
+ /* add the ignition.firstboot file on the boot-labelled partition,
+ * to check that we can source this correctly */
+ test_add_file_string(test, dev,
+ "/ignition.firstboot",
+ "ignition_extra_kcmdline=meep\n");
+
+ test_run_parser(test, "grub2");
+
+ check_boot_option_count(ctx, 1);
+
+ opt = get_boot_option(ctx, 0);
+ check_name(opt,
+ "Red Hat Enterprise Linux CoreOS 42.80.20191030.0 (Ootpa) (ostree)");
+ check_args(opt, "console=tty0 console=hvc0,115200n8 "
+ "rootflags=defaults,prjquota rw "
+ "ignition.firstboot rd.neednet=1 ip=dhcp meep "
+ "root=UUID=8d8a5c3b-97e6-4d7b-bb87-206af5a9d851 "
+ "ostree=/ostree/boot.0/rhcos/6264e4be818e20cf1021bd6e7aa8c76147ce07dec186468c7dfbbc9c5dfc7d8b/0 "
+ "ignition.platform.id=openstack");
+}
diff --git a/test/parser/test-grub2-rhel8.c b/test/parser/test-grub2-rhel8.c
new file mode 100644
index 0000000..3e208bc
--- /dev/null
+++ b/test/parser/test-grub2-rhel8.c
@@ -0,0 +1,21 @@
+
+#include "parser-test.h"
+
+void run_test(struct parser_test *test)
+{
+ struct discover_context *ctx;
+ struct discover_device *dev;
+
+ ctx = test->ctx;
+
+ dev = test_create_device(test, "boot");
+ dev->uuid = "9a8ea027-4829-45b9-829b-18ed6cc1f33b";
+ device_handler_add_device(test->handler, dev);
+
+ test_read_conf_file(test, "grub2-rhel8.conf",
+ "/grub/grub.cfg");
+
+ test_run_parser(test, "grub2");
+
+ check_boot_option_count(ctx, 3);
+}
diff --git a/test/parser/test-grub2-search-args.c b/test/parser/test-grub2-search-args.c
new file mode 100644
index 0000000..0eb5762
--- /dev/null
+++ b/test/parser/test-grub2-search-args.c
@@ -0,0 +1,35 @@
+
+/* check for multiple styles of option parsing for the 'search' command */
+
+#include "parser-test.h"
+
+#if 0 /* PARSER_EMBEDDED_CONFIG */
+
+# no --set arugment will set the 'root' var
+search a
+search --set=v1 b
+search --set v2 c
+search --set=v3 --no-floppy d
+search --no-floppy --set=v4 e
+
+menuentry $root$v1$v2$v3$v4 {
+ linux /vmlinux
+}
+
+#endif
+
+void run_test(struct parser_test *test)
+{
+ struct discover_boot_option *opt;
+ struct discover_context *ctx;
+
+ ctx = test->ctx;
+
+ test_read_conf_embedded(test, "/grub/grub.cfg");
+
+ test_run_parser(test, "grub2");
+
+ check_boot_option_count(ctx, 1);
+ opt = get_boot_option(ctx, 0);
+ check_name(opt, "abcde");
+}
diff --git a/test/parser/test-grub2-search-label.c b/test/parser/test-grub2-search-label.c
new file mode 100644
index 0000000..b9ee034
--- /dev/null
+++ b/test/parser/test-grub2-search-label.c
@@ -0,0 +1,47 @@
+/* check for grub2 search command, searching by partition label */
+
+#include "parser-test.h"
+
+#if 0 /* PARSER_EMBEDDED_CONFIG */
+
+# valid label
+search --set=v1 --label testlabel
+
+v2=prev
+# invalid label: does not alter v2
+search --set=v2 --label invalidlabel
+
+menuentry $v1 {
+ linux /vmlinux
+}
+
+menuentry $v2 {
+ linux /vmlinux
+}
+
+#endif
+
+void run_test(struct parser_test *test)
+{
+ struct discover_boot_option *opt;
+ struct discover_context *ctx;
+ struct discover_device *dev;
+
+ ctx = test->ctx;
+
+ dev = test_create_device(test, "testdev");
+ dev->label = "testlabel";
+ device_handler_add_device(test->handler, dev);
+
+ test_read_conf_embedded(test, "/grub/grub.cfg");
+
+ test_run_parser(test, "grub2");
+
+ check_boot_option_count(ctx, 2);
+
+ opt = get_boot_option(ctx, 0);
+ check_name(opt, "testdev");
+
+ opt = get_boot_option(ctx, 1);
+ check_name(opt, "prev");
+}
diff --git a/test/parser/test-grub2-search-uuid.c b/test/parser/test-grub2-search-uuid.c
new file mode 100644
index 0000000..7eacd1d
--- /dev/null
+++ b/test/parser/test-grub2-search-uuid.c
@@ -0,0 +1,55 @@
+/* check for grub2 search command, searching by FS UUID */
+
+#include "parser-test.h"
+
+#if 0 /* PARSER_EMBEDDED_CONFIG */
+
+# valid UUID
+search --set=v1 --fs-uuid ee0cc6fa-1dba-48f2-8f5b-19e4b8de8c37
+
+# invalid UUID: will fall back to passing the UUID through
+search --set=v2 --fs-uuid 92b0da57-6e04-4e54-960b-85e6bb060433
+
+# no 'type' argument defaults to UUID search
+search --set=v3 ee0cc6fa-1dba-48f2-8f5b-19e4b8de8c37
+
+menuentry $v1 {
+ linux /vmlinux
+}
+
+menuentry $v2 {
+ linux /vmlinux
+}
+
+menuentry $v3 {
+ linux /vmlinux
+}
+#endif
+
+void run_test(struct parser_test *test)
+{
+ struct discover_boot_option *opt;
+ struct discover_context *ctx;
+ struct discover_device *dev;
+
+ ctx = test->ctx;
+
+ dev = test_create_device(test, "testdev");
+ dev->uuid = "ee0cc6fa-1dba-48f2-8f5b-19e4b8de8c37";
+ device_handler_add_device(test->handler, dev);
+
+ test_read_conf_embedded(test, "/grub/grub.cfg");
+
+ test_run_parser(test, "grub2");
+
+ check_boot_option_count(ctx, 3);
+
+ opt = get_boot_option(ctx, 0);
+ check_name(opt, dev->device->id);
+
+ opt = get_boot_option(ctx, 1);
+ check_name(opt, "92b0da57-6e04-4e54-960b-85e6bb060433");
+
+ opt = get_boot_option(ctx, 2);
+ check_name(opt, dev->device->id);
+}
diff --git a/test/parser/test-grub2-source-functions.c b/test/parser/test-grub2-source-functions.c
new file mode 100644
index 0000000..a9da934
--- /dev/null
+++ b/test/parser/test-grub2-source-functions.c
@@ -0,0 +1,46 @@
+
+/* check that we can source other scripts, and functions can be defined
+ * and called across sourced scripts */
+
+#include "parser-test.h"
+
+#if 0 /* PARSER_EMBEDDED_CONFIG */
+
+function f1 {
+ menuentry "f1$1" { linux $2 }
+}
+
+source /grub/2.cfg
+
+f2 a /vmlinux
+
+#endif
+
+void run_test(struct parser_test *test)
+{
+ struct discover_boot_option *opt;
+ struct discover_context *ctx;
+ struct discover_device *dev;
+
+ ctx = test->ctx;
+ dev = ctx->device;
+
+ test_read_conf_embedded(test, "/grub/grub.cfg");
+
+ test_add_file_string(test, dev,
+ "/grub/2.cfg",
+ "function f2 { menuentry \"f2$1\" { linux $2 } }\n"
+ "f1 a /vmlinux\n");
+
+ test_run_parser(test, "grub2");
+
+ check_boot_option_count(ctx, 2);
+
+ opt = get_boot_option(ctx, 0);
+ check_name(opt, "f1a");
+ check_resolved_local_resource(opt->boot_image, dev, "/vmlinux");
+
+ opt = get_boot_option(ctx, 1);
+ check_name(opt, "f2a");
+ check_resolved_local_resource(opt->boot_image, dev, "/vmlinux");
+}
diff --git a/test/parser/test-grub2-source-recursion-infinite.c b/test/parser/test-grub2-source-recursion-infinite.c
new file mode 100644
index 0000000..fbcc5a3
--- /dev/null
+++ b/test/parser/test-grub2-source-recursion-infinite.c
@@ -0,0 +1,43 @@
+
+/* check that have a maximum source recursion limit */
+
+#include "parser-test.h"
+
+#if 0 /* PARSER_EMBEDDED_CONFIG */
+
+name=a$name
+
+menuentry $name {
+ linux /a
+}
+
+source /grub/grub.cfg
+
+#endif
+
+void run_test(struct parser_test *test)
+{
+ struct discover_boot_option *opt;
+ struct discover_context *ctx;
+ struct discover_device *dev;
+
+ ctx = test->ctx;
+ dev = ctx->device;
+
+ test_read_conf_embedded(test, "/grub/grub.cfg");
+
+ test_run_parser(test, "grub2");
+
+ /* we error out after 10 levels, but we should still have
+ * parse results up to that point
+ */
+ check_boot_option_count(ctx, 11);
+
+ opt = get_boot_option(ctx, 0);
+ check_name(opt, "a");
+ check_resolved_local_resource(opt->boot_image, dev, "/a");
+
+ opt = get_boot_option(ctx,10);
+ check_name(opt, "aaaaaaaaaaa");
+ check_resolved_local_resource(opt->boot_image, dev, "/a");
+}
diff --git a/test/parser/test-grub2-source-recursion.c b/test/parser/test-grub2-source-recursion.c
new file mode 100644
index 0000000..21b6bd2
--- /dev/null
+++ b/test/parser/test-grub2-source-recursion.c
@@ -0,0 +1,58 @@
+/* check that we can source other files recursively */
+
+#include "parser-test.h"
+
+#if 0 /* PARSER_EMBEDDED_CONFIG */
+
+menuentry a {
+ linux /a
+}
+
+source /grub/2.cfg
+
+menuentry c {
+ linux /c
+}
+
+#endif
+
+void run_test(struct parser_test *test)
+{
+ struct discover_boot_option *opt;
+ struct discover_context *ctx;
+ struct discover_device *dev;
+
+ ctx = test->ctx;
+ dev = ctx->device;
+
+ test_read_conf_embedded(test, "/grub/grub.cfg");
+
+ /* four levels of config files, the last defining a boot option */
+ test_add_file_string(test, dev,
+ "/grub/2.cfg",
+ "source /grub/3.cfg\n");
+
+ test_add_file_string(test, dev,
+ "/grub/3.cfg",
+ "source /grub/4.cfg\n");
+
+ test_add_file_string(test, dev,
+ "/grub/4.cfg",
+ "menuentry b { linux /b }\n");
+
+ test_run_parser(test, "grub2");
+
+ check_boot_option_count(ctx, 3);
+
+ opt = get_boot_option(ctx, 0);
+ check_name(opt, "a");
+ check_resolved_local_resource(opt->boot_image, dev, "/a");
+
+ opt = get_boot_option(ctx, 1);
+ check_name(opt, "b");
+ check_resolved_local_resource(opt->boot_image, dev, "/b");
+
+ opt = get_boot_option(ctx, 2);
+ check_name(opt, "c");
+ check_resolved_local_resource(opt->boot_image, dev, "/c");
+}
diff --git a/test/parser/test-grub2-source.c b/test/parser/test-grub2-source.c
new file mode 100644
index 0000000..a14bef7
--- /dev/null
+++ b/test/parser/test-grub2-source.c
@@ -0,0 +1,54 @@
+
+/* check that we can source other scripts, and variables get passed
+ * in to and out of sourced scripts */
+
+#include "parser-test.h"
+
+#if 0 /* PARSER_EMBEDDED_CONFIG */
+
+menuentry a {
+ linux /a
+}
+
+# var: outer -> inner -> outer
+v=b
+
+source /grub/2.cfg
+
+menuentry $v {
+ linux /c
+}
+
+#endif
+
+void run_test(struct parser_test *test)
+{
+ struct discover_boot_option *opt;
+ struct discover_context *ctx;
+ struct discover_device *dev;
+
+ ctx = test->ctx;
+ dev = ctx->device;
+
+ test_read_conf_embedded(test, "/grub/grub.cfg");
+
+ test_add_file_string(test, dev,
+ "/grub/2.cfg",
+ "menuentry $v { linux /b }\nv=c\n");
+
+ test_run_parser(test, "grub2");
+
+ check_boot_option_count(ctx, 3);
+
+ opt = get_boot_option(ctx, 0);
+ check_name(opt, "a");
+ check_resolved_local_resource(opt->boot_image, dev, "/a");
+
+ opt = get_boot_option(ctx, 1);
+ check_name(opt, "b");
+ check_resolved_local_resource(opt->boot_image, dev, "/b");
+
+ opt = get_boot_option(ctx, 2);
+ check_name(opt, "c");
+ check_resolved_local_resource(opt->boot_image, dev, "/c");
+}
diff --git a/ui/ncurses/nc-config-help.c b/ui/ncurses/nc-config-help.c
index 6b0d59f..7c3bf35 100644
--- a/ui/ncurses/nc-config-help.c
+++ b/ui/ncurses/nc-config-help.c
@@ -50,4 +50,10 @@ the pb-discover server will use these details.\n"
"\n"
"Disk R/W: Certain bootloader configurations may request write access to \
disks to save information or update parameters (eg. GRUB2). "
-"Use this option to control access to disks.\n");
+"Use this option to control access to disks.\n"
+"\n"
+"Pre-boot check: Petitboot is able to check a payload for compatibility before \
+booting it. If this check fails, petitboot will determine that the payload will \
+not properly boot on this platform, and abort the boot. This configuration \
+option allows skipping this check.\n"
+"");
diff --git a/ui/ncurses/nc-config.c b/ui/ncurses/nc-config.c
index 943ee8a..e231f4b 100644
--- a/ui/ncurses/nc-config.c
+++ b/ui/ncurses/nc-config.c
@@ -34,7 +34,7 @@
#include "nc-config.h"
#include "nc-widgets.h"
-#define N_FIELDS 51
+#define N_FIELDS 53
extern struct help_text config_help_text;
@@ -70,6 +70,8 @@ struct config_screen {
bool ipmi_mailbox;
bool net_override;
+ bool preboot_check_enabled;
+
struct {
struct nc_widget_label *autoboot_l;
struct nc_widget_select *autoboot_f;
@@ -124,6 +126,9 @@ struct config_screen {
struct nc_widget_button *update_password_l;
+ struct nc_widget_label *preboot_check_l;
+ struct nc_widget_select *preboot_check_f;
+
struct nc_widget_label *net_override_l;
struct nc_widget_label *safe_mode;
struct nc_widget_button *ok_b;
@@ -364,6 +369,9 @@ static int screen_process_form(struct config_screen *screen)
}
}
+ config->preboot_check_enabled = widget_select_get_value(
+ screen->widgets.preboot_check_f);
+
config->safe_mode = false;
rc = cui_send_config(screen->cui, config);
talloc_free(config);
@@ -735,6 +743,17 @@ static void config_screen_layout_widgets(struct config_screen *screen)
y += 1;
}
+ wl = widget_label_base(screen->widgets.preboot_check_l);
+ widget_set_visible(wl, true);
+ widget_move(wl, y, screen->label_x);
+
+ wf = widget_select_base(screen->widgets.preboot_check_f);
+ widget_set_visible(wf, true);
+ widget_move(wf, y, screen->field_x);
+ y += widget_height(wf);
+
+ y += 1;
+
widget_move(widget_button_base(screen->widgets.ok_b),
y, screen->field_x);
widget_move(widget_button_base(screen->widgets.help_b),
@@ -770,6 +789,15 @@ static void config_screen_autoboot_change(void *arg, int value)
widgetset_post(screen->widgetset);
}
+static void config_screen_preboot_check_change(void *arg, int value)
+{
+ struct config_screen *screen = arg;
+ screen->preboot_check_enabled = !!value;
+ widgetset_unpost(screen->widgetset);
+ config_screen_layout_widgets(screen);
+ widgetset_post(screen->widgetset);
+}
+
static void config_screen_add_device(void *arg)
{
struct config_screen *screen = arg;
@@ -1196,6 +1224,20 @@ static void config_screen_setup_widgets(struct config_screen *screen,
_("Update system password"), password_click, screen);
#endif
+ screen->preboot_check_enabled = config->preboot_check_enabled;
+ screen->widgets.preboot_check_l = widget_new_label(set, 0, 0,
+ _("Pre-boot check:"));
+ screen->widgets.preboot_check_f = widget_new_select(set, 0, 0,
+ COLS - screen->field_x - 1);
+
+ widget_select_add_option(screen->widgets.preboot_check_f, 0, _("Disabled"),
+ !screen->preboot_check_enabled);
+ widget_select_add_option(screen->widgets.preboot_check_f, 1, _("Enabled"),
+ screen->preboot_check_enabled);
+
+ widget_select_on_change(screen->widgets.preboot_check_f,
+ config_screen_preboot_check_change, screen);
+
screen->widgets.ok_b = widget_new_button(set, 0, 0, 10, _("OK"),
ok_click, screen);
screen->widgets.help_b = widget_new_button(set, 0, 0, 10, _("Help"),
diff --git a/ui/ncurses/nc-cui.c b/ui/ncurses/nc-cui.c
index bd2eb68..66f34b6 100644
--- a/ui/ncurses/nc-cui.c
+++ b/ui/ncurses/nc-cui.c
@@ -1052,7 +1052,9 @@ static int cui_boot_option_add(struct device *dev, struct boot_option *opt,
struct pmenu_item *item;
unsigned int j;
result = set_menu_items(cui->main->ncm, NULL);
- for (j = 0 ; j < cui->main->item_count; j++) {
+ if (result)
+ pb_log_fn("unset_menu_items failed: %d\n", result);
+ for (j = 0 ; j < cui->main->item_count && !result; j++) {
item = item_userptr(cui->main->items[j]);
if (item->on_execute != menu_plugin_execute)
continue;
diff --git a/ui/ncurses/nc-sysinfo.c b/ui/ncurses/nc-sysinfo.c
index 756f15d..9ca15ff 100644
--- a/ui/ncurses/nc-sysinfo.c
+++ b/ui/ncurses/nc-sysinfo.c
@@ -81,6 +81,15 @@ static void sysinfo_screen_populate(struct sysinfo_screen *screen,
}
}
+ line(NULL);
+ line("%s", _("Secure & trusted boot"));
+ line(" %-15s: %s", _("FW verification"), sysinfo->stb_fw_enforcing ?
+ _("enabled") : _("disabled"));
+ line(" %-15s: %s", _("FW measurement"), sysinfo->stb_fw_measurement ?
+ _("enabled") : _("disabled"));
+ line(" %-15s: %s", _("OS verification"), sysinfo->stb_os_enforcing ?
+ _("enforcing") : _("disabled"));
+
if (sysinfo->n_bmc_current) {
line(NULL);
line("%s", _("BMC current side:"));
diff --git a/utils/Makefile.am b/utils/Makefile.am
index a523430..8788150 100644
--- a/utils/Makefile.am
+++ b/utils/Makefile.am
@@ -18,7 +18,8 @@ dist_pkglibexec_SCRIPTS = utils/pb-console
sbin_PROGRAMS += utils/pb-event utils/pb-config
utils_pb_config_LDADD = $(top_builddir)/lib/libpbcore.la \
- $(top_builddir)/discover/platform.ro
+ $(top_builddir)/discover/platform.ro \
+ $(ELF_LIBS)
utils_hooks_30_dtb_updates_SOURCES = utils/hooks/30-dtb-updates.c
utils_hooks_30_dtb_updates_LDADD = $(top_builddir)/lib/libpbcore.la \
diff --git a/utils/hooks/30-dtb-updates.c b/utils/hooks/30-dtb-updates.c
index b8413fd..2d30c40 100644
--- a/utils/hooks/30-dtb-updates.c
+++ b/utils/hooks/30-dtb-updates.c
@@ -605,7 +605,7 @@ out:
int main(void)
{
struct offb_ctx *ctx;
- int rc;
+ int rc = 0;
ctx = talloc_zero(NULL, struct offb_ctx);
diff --git a/utils/pb-console b/utils/pb-console
index 5ba98cc..012d242 100644
--- a/utils/pb-console
+++ b/utils/pb-console
@@ -119,10 +119,8 @@ esac
# we may have been run from udev - ensure we have a sensible PATH
if [ -z "$PATH" ]
then
- PATH=/usr/bin:/usr/sbin:/bin:/sbin
+ export PATH=/usr/bin:/usr/sbin:/bin:/sbin
fi
-PATH=/var/lib/pb-plugins/bin:$PATH
-export PATH
verbose_opt=
if $pb_config debug | grep -q enabled
@@ -141,5 +139,5 @@ while :
do
$ui $verbose_opt
reset
- $shell
+ $shell -ml
done
diff --git a/utils/pb-exec b/utils/pb-exec
index bfe13f6..d672ec1 100755
--- a/utils/pb-exec
+++ b/utils/pb-exec
@@ -1,7 +1,18 @@
#!/bin/sh
+PREFIX=""
+
+# Check if root required
+if [[ "$(id -u)" != "0" ]]; then
+ read -n 1 -r -p "Running as user $(id -un), run as root? (y/N)" key
+ if [ "$key" == "y" ]; then
+ PREFIX="sudo"
+ fi
+ printf "\n"
+fi
+
# Run a program specified by Petitboot.
-$@
+$PREFIX $@
echo "$0 ran '$@'"
# Wait for the user to exit back to Petitboot.
diff --git a/utils/pb-plugin b/utils/pb-plugin
index a42d051..45bf19d 100755
--- a/utils/pb-plugin
+++ b/utils/pb-plugin
@@ -232,8 +232,8 @@ do_install()
done
pb-event plugin@local \
- name=$PLUGIN_NAME id=$PLUGIN_ID version=$PLUGIN_VERSION \
- vendor=$PLUGIN_VENDOR vendor_id=$PLUGIN_VENDOR_ID \
+ name="$PLUGIN_NAME" id=$PLUGIN_ID version=$PLUGIN_VERSION \
+ vendor="$PLUGIN_VENDOR" vendor_id=$PLUGIN_VENDOR_ID \
date=$PLUGIN_DATE executables="$PLUGIN_EXECUTABLES" \
source_file=$url installed="yes"
OpenPOWER on IntegriCloud