loader: provide a features table for binary compatibility advertisement

liblua now provides a loader.has_feature() function to probe the loader
binary for features advertised.  name => desc mappings are provided in
loader.features to get a list of all of the features loader *can*
support.  core.hasFeature is provided as a shim to loader.has_feature
so that individual consumers don't need to think about the logic of the
loader module not providing has_feature; we know that means the feature
isn't enabled.

The first consumer of this will be EARLY_ACPI to advertise that the
loader binary probes for ACPI presence before the interpreter has
started, so that we know whether we can trust the presence of acpi.rsdp
as relatively authoritative.  In general, it's intended to be used to
avoid breaking new scripts on older loaders within reason.

This will be used in lua as `core.hasFeature("EARLY_ACPI")`, while the
C bits of loader will `feature_enable(FEATURE_EARLY_ACPI)`.

Reviewed by:	imp
Differential Revision:	https://reviews.freebsd.org/D42695
This commit is contained in:
Kyle Evans 2023-12-08 15:36:06 -06:00
parent f959fcafb5
commit 1631382cf2
7 changed files with 197 additions and 2 deletions

View File

@ -75,6 +75,25 @@ lua_has_command(lua_State *L)
return 1;
}
static int
lua_has_feature(lua_State *L)
{
const char *feature;
char *msg;
feature = luaL_checkstring(L, 1);
if (feature_name_is_enabled(feature)) {
lua_pushboolean(L, 1);
return 1;
}
lua_pushnil(L);
lua_pushstring(L, "Feature not enabled");
return 2;
}
static int
lua_perform(lua_State *L)
{
@ -552,6 +571,7 @@ static const struct luaL_Reg loaderlib[] = {
REG_SIMPLE(parse),
REG_SIMPLE(getenv),
REG_SIMPLE(has_command),
REG_SIMPLE(has_feature),
REG_SIMPLE(perform),
REG_SIMPLE(printc), /* Also registered as the global 'printc' */
REG_SIMPLE(setenv),
@ -579,6 +599,33 @@ static const struct luaL_Reg iolib[] = {
};
#undef REG_SIMPLE
static void
lua_add_feature(void *cookie, const char *name, const char *desc, bool enabled)
{
lua_State *L = cookie;
/*
* The feature table consists solely of features that are enabled, and
* their associated descriptions for debugging purposes.
*/
lua_pushstring(L, desc);
lua_setfield(L, -2, name);
}
static void
lua_add_features(lua_State *L)
{
lua_newtable(L);
feature_iter(&lua_add_feature, L);
/*
* We should still have just the table on the stack after we're done
* iterating.
*/
lua_setfield(L, -2, "features");
}
int
luaopen_loader(lua_State *L)
{
@ -592,6 +639,7 @@ luaopen_loader(lua_State *L)
lua_setfield(L, -2, "lua_path");
lua_pushinteger(L, bootprog_rev);
lua_setfield(L, -2, "version");
lua_add_features(L);
/* Set global printc to loader.printc */
lua_register(L, "printc", lua_printc);
return 1;

View File

@ -13,7 +13,7 @@ LIB?= sa
# standalone components and stuff we have modified locally
SRCS+= gzguts.h zutil.h __main.c abort.c assert.c bcd.c environment.c \
getopt.c gets.c globals.c \
features.c getopt.c gets.c globals.c \
hexdump.c nvstore.c pager.c panic.c printf.c strdup.c strerror.c \
random.c sbrk.c tslog.c twiddle.c zalloc.c zalloc_malloc.c

56
stand/libsa/features.c Normal file
View File

@ -0,0 +1,56 @@
/*-
* Copyright (c) 2023 Kyle Evans <kevans@FreeBSD.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*
*/
#include <sys/param.h>
#include "stand.h"
static uint32_t loader_features;
#define FEATURE_ENTRY(name, desc) { FEATURE_##name, #name, desc }
static const struct feature_entry {
uint32_t value;
const char *name;
const char *desc;
} feature_map[] = {
FEATURE_ENTRY(EARLY_ACPI, "Loader probes ACPI in early startup"),
};
void
feature_enable(uint32_t mask)
{
loader_features |= mask;
}
bool
feature_name_is_enabled(const char *name)
{
const struct feature_entry *entry;
for (size_t i = 0; i < nitems(feature_map); i++) {
entry = &feature_map[i];
if (strcmp(entry->name, name) == 0)
return ((loader_features & entry->value) != 0);
}
return (false);
}
void
feature_iter(feature_iter_fn *iter_fn, void *cookie)
{
const struct feature_entry *entry;
for (size_t i = 0; i < nitems(feature_map); i++) {
entry = &feature_map[i];
(*iter_fn)(cookie, entry->name, entry->desc,
(loader_features & entry->value) != 0);
}
}

View File

@ -497,6 +497,66 @@ Attempts to open and display the file
.Fa fname .
Returns -1 on error, 0 at EOF, or 1 if the user elects to quit while reading.
.El
.Sh FEATURE SUPPORT
A set of functions are provided to communicate support of features from the
loader binary to the interpreter.
These are used to do something sensible if we are still operating with a loader
binary that behaves differently than expected.
.Bl -hang -width 10n
.It Xo
.Ft void
.Fn feature_enable "uint32_t mask"
.Xc
.Pp
Enable the referenced
.Fa mask
feature, which should be one of the
.Li FEATURE_*
macros defined in
.In stand.h .
.It Xo
.Ft bool
.Fn feature_name_is_enabled "const char *name"
.Xc
.Pp
Check if the referenced
.Fa name
feature is enabled.
The
.Fa name
is usually the same name as the
.Li FEATURE_*
macro, but with the FEATURE_ prefix stripped off.
The authoritative source of feature names is the mapping table near the top in
.Pa stand/libsa/features.c .
.It Xo
.Ft void
.Fn "(feature_iter_fn)" "void *cookie" "const char *name" "const char *desc" "bool enabled"
.Xc
.Pp
The
.Fa cookie
argument is passed as-is from the argument of the same name to
.Fn feature_iter .
The
.Fa name
and
.Fa desc
arguments are defined in the mapping table in
.Pa stand/libsa/features.c .
The
.Fa enabled
argument indicates the current status of the feature, though one could
theoretically turn a feature off in later execution.
As such, this should likely not be trusted if it is needed after the iteration
has finished.
.It Xo
.Ft void
.Fn "feature_iter" "feature_iter_fn *iter_fn" "void *cookie"
.Xc
.Pp
Iterate over the current set of features.
.El
.Sh MISC
.Bl -hang -width 10n
.It Xo

View File

@ -496,6 +496,21 @@ extern void *reallocf(void *, size_t);
*/
caddr_t ptov(uintptr_t);
/* features.c */
typedef void (feature_iter_fn)(void *, const char *, const char *, bool);
extern void feature_enable(uint32_t);
extern bool feature_name_is_enabled(const char *);
extern void feature_iter(feature_iter_fn *, void *);
/*
* Note that these should also be added to the mapping table in features.c,
* which the interpreter may query to provide details from. The name with
* FEATURE_ removed is assumed to be the name we'll provide in the loader
* features table, just to simplify reasoning about these.
*/
#define FEATURE_EARLY_ACPI 0x0001
/* hexdump.c */
void hexdump(caddr_t region, size_t len);

View File

@ -378,6 +378,15 @@ function core.boot(argstr)
loader.perform(composeLoaderCmd("boot", argstr))
end
function core.hasFeature(name)
if not loader.has_feature then
-- Loader too old, no feature support
return nil, "No feature support in loaded loader"
end
return loader.has_feature(name)
end
function core.isSingleUserBoot()
local single_user = loader.getenv("boot_single")
return single_user ~= nil and single_user:lower() == "yes"

View File

@ -24,7 +24,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.Dd November 20, 2023
.Dd December 8, 2023
.Dt CORE.LUA 8
.Os
.Sh NAME
@ -142,6 +142,13 @@ due to a boot environment change or a potential change in either
.Ic kernel
or
.Ic kernels .
.It Fn core.hasFeature featureName
Checks whether the named
.Fa featureName
is enabled in the current loader.
This is specifically used for detecting capabilities of the loaded loader
binary, so that one can reasonably implement backwards compatibility shims if
needed.
.It Fn core.kernelList
Returns a table of kernels to display on the boot menu.
This will combine