aboutsummaryrefslogtreecommitdiff
path: root/libexec/flua
diff options
context:
space:
mode:
Diffstat (limited to 'libexec/flua')
-rw-r--r--libexec/flua/Makefile71
-rw-r--r--libexec/flua/Makefile.inc10
-rw-r--r--libexec/flua/bootstrap.h32
-rw-r--r--libexec/flua/flua.199
-rw-r--r--libexec/flua/lfbsd/Makefile5
-rw-r--r--libexec/flua/lfbsd/Makefile.inc2
-rw-r--r--libexec/flua/lfbsd/lfbsd.c289
-rw-r--r--libexec/flua/lfbsd/lfbsd.h32
-rw-r--r--libexec/flua/lfs/Makefile5
-rw-r--r--libexec/flua/lfs/Makefile.inc2
-rw-r--r--libexec/flua/lfs/lfs.c453
-rw-r--r--libexec/flua/lfs/lfs.h31
-rw-r--r--libexec/flua/libfreebsd/Makefile4
-rw-r--r--libexec/flua/libfreebsd/Makefile.inc3
-rw-r--r--libexec/flua/libfreebsd/kenv/Makefile5
-rw-r--r--libexec/flua/libfreebsd/kenv/Makefile.inc2
-rw-r--r--libexec/flua/libfreebsd/kenv/freebsd.kenv.3lua44
-rw-r--r--libexec/flua/libfreebsd/kenv/kenv.c100
-rw-r--r--libexec/flua/libfreebsd/sys/Makefile4
-rw-r--r--libexec/flua/libfreebsd/sys/Makefile.inc3
-rw-r--r--libexec/flua/libfreebsd/sys/linker/Makefile6
-rw-r--r--libexec/flua/libfreebsd/sys/linker/Makefile.inc2
-rw-r--r--libexec/flua/libfreebsd/sys/linker/freebsd.sys.linker.3lua46
-rw-r--r--libexec/flua/libfreebsd/sys/linker/linker.c86
-rw-r--r--libexec/flua/libhash/Makefile6
-rw-r--r--libexec/flua/libhash/Makefile.inc3
-rw-r--r--libexec/flua/libhash/hash.3lua54
-rw-r--r--libexec/flua/libhash/lhash.c183
-rw-r--r--libexec/flua/libhash/lhash.h11
-rw-r--r--libexec/flua/libjail/Makefile6
-rw-r--r--libexec/flua/libjail/Makefile.inc3
-rw-r--r--libexec/flua/libjail/jail.3lua277
-rw-r--r--libexec/flua/libjail/lua_jail.c722
-rw-r--r--libexec/flua/liblyaml/Makefile4
-rw-r--r--libexec/flua/liblyaml/Makefile.inc20
-rw-r--r--libexec/flua/libucl/Makefile4
-rw-r--r--libexec/flua/libucl/Makefile.inc12
-rw-r--r--libexec/flua/linit_flua.c95
-rw-r--r--libexec/flua/modules/lposix.c699
-rw-r--r--libexec/flua/modules/lposix.h10
40 files changed, 3445 insertions, 0 deletions
diff --git a/libexec/flua/Makefile b/libexec/flua/Makefile
new file mode 100644
index 000000000000..23de404710d0
--- /dev/null
+++ b/libexec/flua/Makefile
@@ -0,0 +1,71 @@
+.include <src.lua.mk>
+
+# New flua modules should be added here rather than to SUBDIR so that we can do
+# the right thing for both bootstrap flua and target flua. The former does not
+# do any shared libs, so we just build them all straight into flua itself rather
+# than mucking about with the infrastructure to make them linkable -- thus, why
+# these are all structured to have a Makefile that describes what we want
+# *installed*, and a Makefile.inc that describes what we need to *build*.
+FLUA_MODULES+= lfbsd
+FLUA_MODULES+= lfs
+FLUA_MODULES+= libhash
+.ifndef BOOTSTRAPPING
+# Bootstrap flua can't usefully do anything with libjail anyways, because it
+# can't assume it's being run on a system that even supports jails.
+FLUA_MODULES+= libjail
+.endif
+FLUA_MODULES+= libucl
+FLUA_MODULES+= liblyaml
+
+.ifdef BOOTSTRAPPING
+# libfreebsd is generally omitted from the bootstrap flua because its
+# functionality largely assumes a FreeBSD kernel/system headers, so it doesn't
+# really offer functionality that we can use in bootstrap.
+CFLAGS+= -I${.CURDIR} -DBOOTSTRAPPING
+
+SHAREDIR= ${WORLDTMP}/legacy/usr/share/flua
+FLUA_PATH= ${SHAREDIR}/?.lua;${SHAREDIR}/?/init.lua
+CFLAGS+= -DBOOTSTRAP_FLUA_PATH=\"${FLUA_PATH:Q}\"
+
+.for mod in ${FLUA_MODULES}
+.include "${mod}/Makefile.inc"
+.endfor
+
+.else
+
+FLUA_MODULES+= libfreebsd
+SUBDIR+= ${FLUA_MODULES}
+
+.endif
+
+LUASRC?= ${SRCTOP}/contrib/lua/src
+.PATH: ${LUASRC}
+
+PROG= flua
+WARNS?= 3
+
+CWARNFLAGS.gcc+= -Wno-format-nonliteral
+
+LIBADD+= lua
+
+# Entry point
+SRCS+= lua.c
+
+# FreeBSD Extensions
+.PATH: ${.CURDIR}/modules
+SRCS+= linit_flua.c
+SRCS+= lposix.c
+
+CFLAGS+= -I${SRCTOP}/lib/liblua -I${.CURDIR}/modules -I${LUASRC}
+CFLAGS+= -DLUA_PROGNAME="\"${PROG}\""
+
+# readline bits; these aren't needed if we're building a bootstrap flua, as we
+# don't expect that one to see any REPL usage.
+.if !defined(BOOTSTRAPPING)
+CFLAGS+= -DLUA_USE_READLINE
+CFLAGS+= -I${SRCTOP}/lib/libedit -I${SRCTOP}/contrib/libedit
+LIBADD+= edit
+LDFLAGS+= -Wl,-E
+.endif
+
+.include <bsd.prog.mk>
diff --git a/libexec/flua/Makefile.inc b/libexec/flua/Makefile.inc
new file mode 100644
index 000000000000..37a49e258ecb
--- /dev/null
+++ b/libexec/flua/Makefile.inc
@@ -0,0 +1,10 @@
+SHLIBDIR?= ${LIBDIR}/flua
+
+CFLAGS+= \
+ -I${SRCTOP}/contrib/lua/src \
+ -I${SRCTOP}/lib/liblua \
+ -I${SRCTOP}/libexec/flua
+
+.ifdef BOOTSTRAPPING
+CFLAGS+= -DBOOTSTRAPPING
+.endif
diff --git a/libexec/flua/bootstrap.h b/libexec/flua/bootstrap.h
new file mode 100644
index 000000000000..caf00518c1e0
--- /dev/null
+++ b/libexec/flua/bootstrap.h
@@ -0,0 +1,32 @@
+/*-
+ * Copyright (c) 2025 Kyle Evans <kevans@FreeBSD.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#ifndef FLUA_BOOTSTRAP_H
+#define FLUA_BOOTSTRAP_H
+
+#ifdef BOOTSTRAPPING
+#include <sys/linker_set.h>
+
+#include <lauxlib.h>
+
+#define FLUA_MODULE_SETNAME flua_modules
+
+SET_DECLARE(FLUA_MODULE_SETNAME, const luaL_Reg);
+#define FLUA_MODULE_DEF(ident, modname, openfn) \
+ static const luaL_Reg ident = { modname, openfn }; \
+ DATA_SET(FLUA_MODULE_SETNAME, ident)
+
+#define FLUA_MODULE_NAMED(mod, name) \
+ FLUA_MODULE_DEF(module_ ## mod, name, luaopen_ ## mod)
+#define FLUA_MODULE(mod) \
+ FLUA_MODULE_DEF(module_ ## mod, #mod, luaopen_ ## mod)
+#else /* !BOOTSTRAPPING */
+#define FLUA_MODULE_DEF(ident, modname, openfn)
+#define FLUA_MODULE_NAMED(mod, name)
+#define FLUA_MODULE(modname)
+#endif /* BOOTSTRAPPING */
+
+#endif /* FLUA_BOOTSTRAP_H */
diff --git a/libexec/flua/flua.1 b/libexec/flua/flua.1
new file mode 100644
index 000000000000..a315e4106065
--- /dev/null
+++ b/libexec/flua/flua.1
@@ -0,0 +1,99 @@
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.\" Copyright (c) 2025 The FreeBSD Foundation
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\"
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd April 28, 2025
+.Dt FLUA 1
+.Os
+.Sh NAME
+.Nm flua
+.Nd Lua interpreter for the FreeBSD base system
+.Sh SYNOPSIS
+.Nm flua
+.Op Fl EWiv
+.Op Fl e Ar string
+.Op Fl l Ar module
+.Op Fl l Ar g=module
+.Op Ar script Op Ar args
+.Op Fl -
+.Op Fl
+.Sh DESCRIPTION
+.Nm
+is a minimal Lua interpreter integrated into the FreeBSD base system.
+It is derived from Lua 5.4 with modifications to suit the needs of
+.Fx
+build infrastructure and system tooling.
+.Nm
+is intended for internal use within the base system and is
+.Em not
+designed for general-purpose scripting or use by third-party applications.
+.Pp
+Unlike full Lua installations provided by the Ports Collection,
+.Nm
+has a reduced feature set and is limited to meeting the requirements of
+base system environments such as the bootloader.
+.Sh USAGE
+.Nm
+is typically invoked internally by FreeBSD base system tools and build scripts.
+While it accepts Lua source files and arguments in a standard fashion, its
+limited environment and module support make it unsuitable for general scripting
+use.
+.Sh INCLUDED MODULES
+.Nm
+includes a subset of functionality from a small number of standard Lua modules
+as well as bespoke modules necessary for the base system:
+.Bl -bullet
+.It
+lfs (LuaFileSystem) – file attribute and directory manipulation
+.It
+lposix - basic POSIX system calls
+.It
+.Xr freebsd.kenv 3lua
+.It
+.Xr freebsd.sys.linker 3lua
+.It
+.Xr hash 3lua
+.It
+.Xr jail 3lua
+.El
+.Sh NOTES
+.Nm
+should not be used as a replacement for
+.Xr lua 1
+from the Ports Collection (e.g.,
+.Pa lang/lua54 )
+as it may be modified or updated to a newer Lua version in the future without
+retaining backwards compatibility.
+.Sh SEE ALSO
+.Xr freebsd.kenv 3lua ,
+.Xr freebsd.sys.linker 3lua ,
+.Xr hash 3lua ,
+.Xr jail 3lua ,
+.Xr style.lua 9
+.Sh HISTORY
+.Nm
+first appeared in
+.Fx 14.0 .
diff --git a/libexec/flua/lfbsd/Makefile b/libexec/flua/lfbsd/Makefile
new file mode 100644
index 000000000000..e2a4aae14bcd
--- /dev/null
+++ b/libexec/flua/lfbsd/Makefile
@@ -0,0 +1,5 @@
+SHLIB_NAME= fbsd.so
+WARNS?= 3
+
+.include "Makefile.inc"
+.include <bsd.lib.mk>
diff --git a/libexec/flua/lfbsd/Makefile.inc b/libexec/flua/lfbsd/Makefile.inc
new file mode 100644
index 000000000000..7a78ef82e0fc
--- /dev/null
+++ b/libexec/flua/lfbsd/Makefile.inc
@@ -0,0 +1,2 @@
+.PATH: ${.PARSEDIR}
+SRCS+= lfbsd.c
diff --git a/libexec/flua/lfbsd/lfbsd.c b/libexec/flua/lfbsd/lfbsd.c
new file mode 100644
index 000000000000..541b6c9611df
--- /dev/null
+++ b/libexec/flua/lfbsd/lfbsd.c
@@ -0,0 +1,289 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright 2023 Baptiste Daroussin <bapt@FreeBSD.org>
+ * Copyright (C) 2025 Kyle Evans <kevans@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted providing that the following conditions~
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <spawn.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <lua.h>
+#include "lauxlib.h"
+#include "lfbsd.h"
+
+#include "bootstrap.h"
+
+#define FBSD_PROCESSHANDLE "fbsd_process_t*"
+
+struct fbsd_process {
+ int pid;
+ int stdin_fileno;
+ int stdout_fileno;
+};
+
+extern char **environ;
+
+static const char**
+luaL_checkarraystrings(lua_State *L, int arg)
+{
+ const char **ret;
+ lua_Integer n, i;
+ int t;
+ int abs_arg = lua_absindex(L, arg);
+ luaL_checktype(L, abs_arg, LUA_TTABLE);
+ n = lua_rawlen(L, abs_arg);
+ ret = lua_newuserdata(L, (n+1)*sizeof(char*));
+ for (i=0; i<n; i++) {
+ t = lua_rawgeti(L, abs_arg, i+1);
+ if (t == LUA_TNIL)
+ break;
+ luaL_argcheck(L, t == LUA_TSTRING, arg, "expected array of strings");
+ ret[i] = lua_tostring(L, -1);
+ lua_pop(L, 1);
+ }
+ ret[i] = NULL;
+ return ret;
+}
+
+static void
+close_pipes(int pipes[2])
+{
+
+ if (pipes[0] != -1)
+ close(pipes[0]);
+ if (pipes[1] != -1)
+ close(pipes[1]);
+}
+
+static int
+lua_exec(lua_State *L)
+{
+ struct fbsd_process *proc;
+ int r;
+ posix_spawn_file_actions_t action;
+ int stdin_pipe[2] = {-1, -1};
+ int stdout_pipe[2] = {-1, -1};
+ pid_t pid;
+ const char **argv;
+ int n = lua_gettop(L);
+ bool capture_stdout;
+ luaL_argcheck(L, n > 0 && n <= 2, n >= 2 ? 2 : n,
+ "fbsd.exec takes exactly one or two arguments");
+
+ capture_stdout = lua_toboolean(L, 2);
+ if (pipe(stdin_pipe) < 0) {
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(errno));
+ lua_pushinteger(L, errno);
+ return (3);
+ }
+ if (capture_stdout && pipe(stdout_pipe) < 0) {
+ close_pipes(stdin_pipe);
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(errno));
+ lua_pushinteger(L, errno);
+ return (3);
+ }
+
+ proc = lua_newuserdata(L, sizeof(*proc));
+ proc->stdin_fileno = stdin_pipe[1];
+ proc->stdout_fileno = stdout_pipe[1];
+ posix_spawn_file_actions_init(&action);
+ posix_spawn_file_actions_adddup2(&action, stdin_pipe[0], STDIN_FILENO);
+ posix_spawn_file_actions_addclose(&action, stdin_pipe[1]);
+ if (stdin_pipe[0] != STDIN_FILENO)
+ posix_spawn_file_actions_addclose(&action, stdin_pipe[0]);
+
+ /*
+ * Setup stdout to be captured if requested. Otherwise, we just let it
+ * go to our own stdout.
+ */
+ if (stdout_pipe[0] != -1) {
+ posix_spawn_file_actions_adddup2(&action, stdout_pipe[0],
+ STDOUT_FILENO);
+ posix_spawn_file_actions_addclose(&action, stdout_pipe[1]);
+ if (stdout_pipe[0] != STDOUT_FILENO) {
+ posix_spawn_file_actions_addclose(&action,
+ stdout_pipe[0]);
+ }
+ }
+
+ argv = luaL_checkarraystrings(L, 1);
+ if (0 != (r = posix_spawnp(&pid, argv[0], &action, NULL,
+ (char*const*)argv, environ))) {
+ close_pipes(stdin_pipe);
+ close_pipes(stdout_pipe);
+ posix_spawn_file_actions_destroy(&action);
+ lua_pop(L, 2); /* Pop off the process handle and args. */
+
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(r));
+ lua_pushinteger(L, r);
+ return (3);
+ }
+
+ lua_pop(L, 1);
+
+ close(stdin_pipe[0]);
+ if (stdout_pipe[0] != -1)
+ close(stdout_pipe[0]);
+ posix_spawn_file_actions_destroy(&action);
+
+ proc->pid = pid;
+ luaL_setmetatable(L, FBSD_PROCESSHANDLE);
+
+ return (1);
+}
+
+static int
+lua_process_close(lua_State *L)
+{
+ struct fbsd_process *proc;
+ int pstat, r;
+
+ proc = luaL_checkudata(L, 1, FBSD_PROCESSHANDLE);
+ while (waitpid(proc->pid, &pstat, 0) == -1) {
+ if ((r = errno) != EINTR) {
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(r));
+ lua_pushinteger(L, r);
+ return (3);
+ }
+ }
+
+ if (!WIFEXITED(pstat) || WEXITSTATUS(pstat) != 0) {
+ lua_pushnil(L);
+ lua_pushstring(L, "Abnormal termination");
+ return (2);
+ }
+
+ if (proc->stdin_fileno >= 0) {
+ close(proc->stdin_fileno);
+ proc->stdin_fileno = -1;
+ }
+
+ if (proc->stdout_fileno >= 0) {
+ close(proc->stdout_fileno);
+ proc->stdout_fileno = -1;
+ }
+
+ lua_pushboolean(L, 1);
+ return (1);
+}
+
+static int
+lua_process_makestdio(lua_State *L, int fd, const char *mode)
+{
+ luaL_Stream *p;
+ FILE *fp;
+ int r;
+
+ if (fd == -1) {
+ lua_pushnil(L);
+ lua_pushstring(L, "Stream not captured");
+ return (2);
+ }
+
+ fp = fdopen(fd, mode);
+ if (fp == NULL) {
+ r = errno;
+
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(r));
+ lua_pushinteger(L, r);
+ return (3);
+ }
+
+ p = lua_newuserdata(L, sizeof(*p));
+ p->closef = &lua_process_close;
+ p->f = fp;
+ luaL_setmetatable(L, LUA_FILEHANDLE);
+ return (1);
+}
+
+static int
+lua_process_stdin(lua_State *L)
+{
+ struct fbsd_process *proc;
+
+ proc = luaL_checkudata(L, 1, FBSD_PROCESSHANDLE);
+ return (lua_process_makestdio(L, proc->stdin_fileno, "w"));
+}
+
+static int
+lua_process_stdout(lua_State *L)
+{
+ struct fbsd_process *proc;
+
+ proc = luaL_checkudata(L, 1, FBSD_PROCESSHANDLE);
+ return (lua_process_makestdio(L, proc->stdout_fileno, "r"));
+}
+
+#define PROCESS_SIMPLE(n) { #n, lua_process_ ## n }
+static const struct luaL_Reg fbsd_process[] = {
+ PROCESS_SIMPLE(close),
+ PROCESS_SIMPLE(stdin),
+ PROCESS_SIMPLE(stdout),
+ { NULL, NULL },
+};
+
+static const struct luaL_Reg fbsd_process_meta[] = {
+ { "__index", NULL },
+ { "__gc", lua_process_close },
+ { "__close", lua_process_close },
+ { NULL, NULL },
+};
+
+#define REG_SIMPLE(n) { #n, lua_ ## n }
+static const struct luaL_Reg fbsd_lib[] = {
+ REG_SIMPLE(exec),
+ { NULL, NULL },
+};
+#undef REG_SIMPLE
+
+int
+luaopen_fbsd(lua_State *L)
+{
+ luaL_newlib(L, fbsd_lib);
+
+ luaL_newmetatable(L, FBSD_PROCESSHANDLE);
+ luaL_setfuncs(L, fbsd_process_meta, 0);
+
+ luaL_newlibtable(L, fbsd_process);
+ luaL_setfuncs(L, fbsd_process, 0);
+ lua_setfield(L, -2, "__index");
+ lua_pop(L, 1);
+
+ return (1);
+}
+
+FLUA_MODULE(fbsd);
diff --git a/libexec/flua/lfbsd/lfbsd.h b/libexec/flua/lfbsd/lfbsd.h
new file mode 100644
index 000000000000..01034a3ad7cd
--- /dev/null
+++ b/libexec/flua/lfbsd/lfbsd.h
@@ -0,0 +1,32 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright 2023 Baptiste Daroussin <bapt@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted providing that the following conditions~
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <lua.h>
+
+int luaopen_fbsd(lua_State *L);
diff --git a/libexec/flua/lfs/Makefile b/libexec/flua/lfs/Makefile
new file mode 100644
index 000000000000..3df83d6d2fc1
--- /dev/null
+++ b/libexec/flua/lfs/Makefile
@@ -0,0 +1,5 @@
+SHLIB_NAME= lfs.so
+WARNS?= 3
+
+.include "Makefile.inc"
+.include <bsd.lib.mk>
diff --git a/libexec/flua/lfs/Makefile.inc b/libexec/flua/lfs/Makefile.inc
new file mode 100644
index 000000000000..9d40c42dc0e6
--- /dev/null
+++ b/libexec/flua/lfs/Makefile.inc
@@ -0,0 +1,2 @@
+.PATH: ${.PARSEDIR}
+SRCS+= lfs.c
diff --git a/libexec/flua/lfs/lfs.c b/libexec/flua/lfs/lfs.c
new file mode 100644
index 000000000000..517e16ae65c8
--- /dev/null
+++ b/libexec/flua/lfs/lfs.c
@@ -0,0 +1,453 @@
+/*-
+ * Copyright (c) 2018 Conrad Meyer <cem@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Portions derived from https://github.com/keplerproject/luafilesystem under
+ * the terms of the MIT license:
+ *
+ * Copyright (c) 2003-2014 Kepler Project.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef _STANDALONE
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#endif
+
+#include <lua.h>
+#include "lauxlib.h"
+#include "lfs.h"
+
+#ifdef _STANDALONE
+#include "lstd.h"
+#include "lutils.h"
+#endif
+
+#include "bootstrap.h"
+
+#ifndef nitems
+#define nitems(x) (sizeof((x)) / sizeof((x)[0]))
+#endif
+
+/*
+ * The goal is to emulate a subset of the upstream Lua FileSystem library, as
+ * faithfully as possible in the boot environment. Only APIs that seem useful
+ * need to emulated.
+ *
+ * Example usage:
+ *
+ * for file in lfs.dir("/boot") do
+ * print("\t"..file)
+ * end
+ *
+ * Prints:
+ * .
+ * ..
+ * (etc.)
+ *
+ * The other available API is lfs.attributes(), which functions somewhat like
+ * stat(2) and returns a table of values. Example code:
+ *
+ * attrs, errormsg, errorcode = lfs.attributes("/boot")
+ * if attrs == nil then
+ * print(errormsg)
+ * return errorcode
+ * end
+ *
+ * for k, v in pairs(attrs) do
+ * print(k .. ":\t" .. v)
+ * end
+ * return 0
+ *
+ * Prints (on success):
+ * gid: 0
+ * change: 140737488342640
+ * mode: directory
+ * rdev: 0
+ * ino: 4199275
+ * dev: 140737488342544
+ * modification: 140737488342576
+ * size: 512
+ * access: 140737488342560
+ * permissions: 755
+ * nlink: 58283552
+ * uid: 1001
+ */
+
+#define DIR_METATABLE "directory iterator metatable"
+
+static int
+lua_dir_iter_pushtype(lua_State *L __unused, const struct dirent *ent __unused)
+{
+
+ /*
+ * This is a non-standard extension to luafilesystem for loader's
+ * benefit. The extra stat() calls to determine the entry type can
+ * be quite expensive on some systems, so this speeds up enumeration of
+ * /boot greatly by providing the type up front.
+ *
+ * This extension is compatible enough with luafilesystem, in that we're
+ * just using an extra return value for the iterator.
+ */
+#ifdef _STANDALONE
+ lua_pushinteger(L, ent->d_type);
+ return 1;
+#else
+ return 0;
+#endif
+}
+
+static int
+lua_dir_iter_next(lua_State *L)
+{
+ struct dirent *entry;
+ DIR *dp, **dpp;
+
+ dpp = (DIR **)luaL_checkudata(L, 1, DIR_METATABLE);
+ dp = *dpp;
+ luaL_argcheck(L, dp != NULL, 1, "closed directory");
+
+#ifdef _STANDALONE
+ entry = readdirfd(dp->fd);
+#else
+ entry = readdir(dp);
+#endif
+ if (entry == NULL) {
+ closedir(dp);
+ *dpp = NULL;
+ return 0;
+ }
+
+ lua_pushstring(L, entry->d_name);
+ return 1 + lua_dir_iter_pushtype(L, entry);
+}
+
+static int
+lua_dir_iter_close(lua_State *L)
+{
+ DIR *dp, **dpp;
+
+ dpp = (DIR **)lua_touserdata(L, 1);
+ dp = *dpp;
+ if (dp == NULL)
+ return 0;
+
+ closedir(dp);
+ *dpp = NULL;
+ return 0;
+}
+
+static int
+lua_dir(lua_State *L)
+{
+ const char *path;
+ DIR *dp;
+
+ if (lua_gettop(L) != 1) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ path = luaL_checkstring(L, 1);
+ dp = opendir(path);
+ if (dp == NULL) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ lua_pushcfunction(L, lua_dir_iter_next);
+ *(DIR **)lua_newuserdata(L, sizeof(DIR **)) = dp;
+ luaL_getmetatable(L, DIR_METATABLE);
+ lua_setmetatable(L, -2);
+ return 2;
+}
+
+static void
+register_metatable(lua_State *L)
+{
+ /*
+ * Create so-called metatable for iterator object returned by
+ * lfs.dir().
+ */
+ luaL_newmetatable(L, DIR_METATABLE);
+
+ lua_newtable(L);
+ lua_pushcfunction(L, lua_dir_iter_next);
+ lua_setfield(L, -2, "next");
+ lua_pushcfunction(L, lua_dir_iter_close);
+ lua_setfield(L, -2, "close");
+
+ /* Magically associate anonymous method table with metatable. */
+ lua_setfield(L, -2, "__index");
+ /* Implement magic destructor method */
+ lua_pushcfunction(L, lua_dir_iter_close);
+ lua_setfield(L, -2, "__gc");
+
+ lua_pop(L, 1);
+}
+
+#define PUSH_INTEGER(lname, stname) \
+static void \
+push_st_ ## lname (lua_State *L, struct stat *sb) \
+{ \
+ lua_pushinteger(L, (lua_Integer)sb->st_ ## stname); \
+}
+PUSH_INTEGER(dev, dev)
+PUSH_INTEGER(ino, ino)
+PUSH_INTEGER(nlink, nlink)
+PUSH_INTEGER(uid, uid)
+PUSH_INTEGER(gid, gid)
+PUSH_INTEGER(rdev, rdev)
+PUSH_INTEGER(access, atime)
+PUSH_INTEGER(modification, mtime)
+PUSH_INTEGER(change, ctime)
+PUSH_INTEGER(size, size)
+#undef PUSH_INTEGER
+
+static void
+push_st_mode(lua_State *L, struct stat *sb)
+{
+ const char *mode_s;
+ mode_t mode;
+
+ mode = (sb->st_mode & S_IFMT);
+ if (S_ISREG(mode))
+ mode_s = "file";
+ else if (S_ISDIR(mode))
+ mode_s = "directory";
+ else if (S_ISLNK(mode))
+ mode_s = "link";
+ else if (S_ISSOCK(mode))
+ mode_s = "socket";
+ else if (S_ISFIFO(mode))
+ mode_s = "fifo";
+ else if (S_ISCHR(mode))
+ mode_s = "char device";
+ else if (S_ISBLK(mode))
+ mode_s = "block device";
+ else
+ mode_s = "other";
+
+ lua_pushstring(L, mode_s);
+}
+
+static void
+push_st_permissions(lua_State *L, struct stat *sb)
+{
+ char buf[20];
+
+ /*
+ * XXX
+ * Could actually format as "-rwxrwxrwx" -- do we care?
+ */
+ snprintf(buf, sizeof(buf), "%o", sb->st_mode & ~S_IFMT);
+ lua_pushstring(L, buf);
+}
+
+#define PUSH_ENTRY(n) { #n, push_st_ ## n }
+struct stat_members {
+ const char *name;
+ void (*push)(lua_State *, struct stat *);
+} members[] = {
+ PUSH_ENTRY(mode),
+ PUSH_ENTRY(dev),
+ PUSH_ENTRY(ino),
+ PUSH_ENTRY(nlink),
+ PUSH_ENTRY(uid),
+ PUSH_ENTRY(gid),
+ PUSH_ENTRY(rdev),
+ PUSH_ENTRY(access),
+ PUSH_ENTRY(modification),
+ PUSH_ENTRY(change),
+ PUSH_ENTRY(size),
+ PUSH_ENTRY(permissions),
+};
+#undef PUSH_ENTRY
+
+static int
+lua_attributes(lua_State *L)
+{
+ struct stat sb;
+ const char *path, *member;
+ size_t i;
+ int rc;
+
+ path = luaL_checkstring(L, 1);
+ if (path == NULL) {
+ lua_pushnil(L);
+ lua_pushfstring(L, "cannot convert first argument to string");
+ lua_pushinteger(L, EINVAL);
+ return 3;
+ }
+
+ rc = stat(path, &sb);
+ if (rc != 0) {
+ lua_pushnil(L);
+ lua_pushfstring(L,
+ "cannot obtain information from file '%s': %s", path,
+ strerror(errno));
+ lua_pushinteger(L, errno);
+ return 3;
+ }
+
+ if (lua_isstring(L, 2)) {
+ member = lua_tostring(L, 2);
+ for (i = 0; i < nitems(members); i++) {
+ if (strcmp(members[i].name, member) != 0)
+ continue;
+
+ members[i].push(L, &sb);
+ return 1;
+ }
+ return luaL_error(L, "invalid attribute name '%s'", member);
+ }
+
+ /* Create or reuse existing table */
+ lua_settop(L, 2);
+ if (!lua_istable(L, 2))
+ lua_newtable(L);
+
+ /* Export all stat data to caller */
+ for (i = 0; i < nitems(members); i++) {
+ lua_pushstring(L, members[i].name);
+ members[i].push(L, &sb);
+ lua_rawset(L, -3);
+ }
+ return 1;
+}
+
+#ifndef _STANDALONE
+#define lfs_mkdir_impl(path) (mkdir((path), \
+ S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | \
+ S_IROTH | S_IXOTH))
+
+static int
+lua_mkdir(lua_State *L)
+{
+ const char *path;
+ int error, serrno;
+
+ path = luaL_checkstring(L, 1);
+ if (path == NULL) {
+ lua_pushnil(L);
+ lua_pushfstring(L, "cannot convert first argument to string");
+ lua_pushinteger(L, EINVAL);
+ return 3;
+ }
+
+ error = lfs_mkdir_impl(path);
+ if (error == -1) {
+ /* Save it; unclear what other libc functions may be invoked */
+ serrno = errno;
+ lua_pushnil(L);
+ lua_pushfstring(L, strerror(serrno));
+ lua_pushinteger(L, serrno);
+ return 3;
+ }
+
+ lua_pushboolean(L, 1);
+ return 1;
+}
+
+static int
+lua_rmdir(lua_State *L)
+{
+ const char *path;
+ int error, serrno;
+
+ path = luaL_checkstring(L, 1);
+ if (path == NULL) {
+ lua_pushnil(L);
+ lua_pushfstring(L, "cannot convert first argument to string");
+ lua_pushinteger(L, EINVAL);
+ return 3;
+ }
+
+ error = rmdir(path);
+ if (error == -1) {
+ /* Save it; unclear what other libc functions may be invoked */
+ serrno = errno;
+ lua_pushnil(L);
+ lua_pushfstring(L, strerror(serrno));
+ lua_pushinteger(L, serrno);
+ return 3;
+ }
+
+ lua_pushboolean(L, 1);
+ return 1;
+}
+#endif
+
+#define REG_SIMPLE(n) { #n, lua_ ## n }
+static const struct luaL_Reg fslib[] = {
+ REG_SIMPLE(attributes),
+ REG_SIMPLE(dir),
+#ifndef _STANDALONE
+ REG_SIMPLE(mkdir),
+ REG_SIMPLE(rmdir),
+#endif
+ { NULL, NULL },
+};
+#undef REG_SIMPLE
+
+int
+luaopen_lfs(lua_State *L)
+{
+ register_metatable(L);
+ luaL_newlib(L, fslib);
+#ifdef _STANDALONE
+ /* Non-standard extension for loader, used with lfs.dir(). */
+ lua_pushinteger(L, DT_DIR);
+ lua_setfield(L, -2, "DT_DIR");
+#endif
+ return 1;
+}
+
+#ifndef _STANDALONE
+FLUA_MODULE(lfs);
+#endif
diff --git a/libexec/flua/lfs/lfs.h b/libexec/flua/lfs/lfs.h
new file mode 100644
index 000000000000..a99e66d7f601
--- /dev/null
+++ b/libexec/flua/lfs/lfs.h
@@ -0,0 +1,31 @@
+/*-
+ * Copyright (c) 2018 Conrad Meyer <cem@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <lua.h>
+
+int luaopen_lfs(lua_State *L);
diff --git a/libexec/flua/libfreebsd/Makefile b/libexec/flua/libfreebsd/Makefile
new file mode 100644
index 000000000000..36d39d6b0502
--- /dev/null
+++ b/libexec/flua/libfreebsd/Makefile
@@ -0,0 +1,4 @@
+SUBDIR+= kenv
+SUBDIR+= sys
+
+.include <bsd.subdir.mk>
diff --git a/libexec/flua/libfreebsd/Makefile.inc b/libexec/flua/libfreebsd/Makefile.inc
new file mode 100644
index 000000000000..26a1540482c7
--- /dev/null
+++ b/libexec/flua/libfreebsd/Makefile.inc
@@ -0,0 +1,3 @@
+SHLIBDIR?= ${LIBDIR}/flua/freebsd
+
+.include "../Makefile.inc"
diff --git a/libexec/flua/libfreebsd/kenv/Makefile b/libexec/flua/libfreebsd/kenv/Makefile
new file mode 100644
index 000000000000..a1b388bb3612
--- /dev/null
+++ b/libexec/flua/libfreebsd/kenv/Makefile
@@ -0,0 +1,5 @@
+SHLIB_NAME= kenv.so
+MAN= freebsd.kenv.3lua
+
+.include "Makefile.inc"
+.include <bsd.lib.mk>
diff --git a/libexec/flua/libfreebsd/kenv/Makefile.inc b/libexec/flua/libfreebsd/kenv/Makefile.inc
new file mode 100644
index 000000000000..05819c5280d9
--- /dev/null
+++ b/libexec/flua/libfreebsd/kenv/Makefile.inc
@@ -0,0 +1,2 @@
+.PATH: ${.PARSEDIR}
+SRCS+= kenv.c
diff --git a/libexec/flua/libfreebsd/kenv/freebsd.kenv.3lua b/libexec/flua/libfreebsd/kenv/freebsd.kenv.3lua
new file mode 100644
index 000000000000..d254dd22c91c
--- /dev/null
+++ b/libexec/flua/libfreebsd/kenv/freebsd.kenv.3lua
@@ -0,0 +1,44 @@
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.\" Copyright (c) 2024, Baptiste Daroussin <bapt@FreeBSD.org>
+.\"
+.Dd September 6, 2024
+.Dt FREEBSD.KENV 3lua
+.Os
+.Sh NAME
+.Nm freebsd.kenv
+.Nd Lua binding to
+.Fx 's
+Linker functions
+.Sh SYNOPSIS
+.Bd -literal
+local kenv = require('freebsd.kenv')
+.Ed
+.Pp
+.Bl -tag -width XXXX -compact
+.It Dv table = kenv.get()
+.It Dv value = kenv.get(key)
+.El
+.Sh DESCRIPTION
+The
+.Nm
+module is a binding to the
+.Xr kenv 2
+function.
+.Pp
+List of functions:
+.Bl -tag -width XXXX
+.It Dv table = freebsd.kenv.get()
+Dump the kernel environnement into a key/value
+.Fa table .
+.It Dv value = freebsd.kenv.get(key)
+Return the
+.Fa value
+associated to the
+.Fa key ,
+if it exists, or
+.Va nil
+otherwise.
+.Sh SEE ALSO
+.Xr kenv 2
diff --git a/libexec/flua/libfreebsd/kenv/kenv.c b/libexec/flua/libfreebsd/kenv/kenv.c
new file mode 100644
index 000000000000..56b24c72904a
--- /dev/null
+++ b/libexec/flua/libfreebsd/kenv/kenv.c
@@ -0,0 +1,100 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2024, Baptiste Daroussin <bapt@FreeBSD.org>
+ */
+
+#include <errno.h>
+#include <kenv.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include "bootstrap.h"
+
+int luaopen_freebsd_kenv(lua_State *L);
+
+static int
+lua_kenv_get(lua_State *L)
+{
+ const char *env;
+ int ret, n;
+ char value[1024];
+
+ n = lua_gettop(L);
+ if (n == 0) {
+ char *buf, *bp, *cp;
+ int size;
+
+ size = kenv(KENV_DUMP, NULL, NULL, 0);
+ if (size < 0) {
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(errno));
+ lua_pushinteger(L, errno);
+ return (3);
+ }
+ size += 1;
+ buf = malloc(size);
+ if (buf == NULL) {
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(errno));
+ lua_pushinteger(L, errno);
+ return (3);
+ }
+ if (kenv(KENV_DUMP, NULL, buf, size) < 0) {
+ free(buf);
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(errno));
+ lua_pushinteger(L, errno);
+ return (3);
+ }
+
+ lua_newtable(L);
+ for (bp = buf; *bp != '\0'; bp += strlen(bp) + 1) {
+ cp = strchr(bp, '=');
+ if (cp == NULL)
+ continue;
+ *cp++ = '\0';
+ lua_pushstring(L, cp);
+ lua_setfield(L, -2, bp);
+ bp = cp;
+ }
+ free(buf);
+ return (1);
+ }
+ env = luaL_checkstring(L, 1);
+ ret = kenv(KENV_GET, env, value, sizeof(value));
+ if (ret == -1) {
+ if (errno == ENOENT) {
+ lua_pushnil(L);
+ return (1);
+ }
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(errno));
+ lua_pushinteger(L, errno);
+ return (3);
+ }
+ lua_pushstring(L, value);
+ return (1);
+}
+
+#define REG_SIMPLE(n) { #n, lua_kenv_ ## n }
+static const struct luaL_Reg freebsd_kenv[] = {
+ REG_SIMPLE(get),
+ { NULL, NULL },
+};
+#undef REG_SIMPLE
+
+int
+luaopen_freebsd_kenv(lua_State *L)
+{
+ luaL_newlib(L, freebsd_kenv);
+
+ return (1);
+}
+
+FLUA_MODULE_NAMED(freebsd_kenv, "freebsd.kenv");
diff --git a/libexec/flua/libfreebsd/sys/Makefile b/libexec/flua/libfreebsd/sys/Makefile
new file mode 100644
index 000000000000..9f38294536f2
--- /dev/null
+++ b/libexec/flua/libfreebsd/sys/Makefile
@@ -0,0 +1,4 @@
+SUBDIR+= linker
+
+.include <bsd.subdir.mk>
+
diff --git a/libexec/flua/libfreebsd/sys/Makefile.inc b/libexec/flua/libfreebsd/sys/Makefile.inc
new file mode 100644
index 000000000000..ca2630721733
--- /dev/null
+++ b/libexec/flua/libfreebsd/sys/Makefile.inc
@@ -0,0 +1,3 @@
+SHLIBDIR?= ${LIBDIR}/flua/freebsd/sys
+
+.include "../Makefile.inc"
diff --git a/libexec/flua/libfreebsd/sys/linker/Makefile b/libexec/flua/libfreebsd/sys/linker/Makefile
new file mode 100644
index 000000000000..f1f65ad5f6c1
--- /dev/null
+++ b/libexec/flua/libfreebsd/sys/linker/Makefile
@@ -0,0 +1,6 @@
+SHLIB_NAME= linker.so
+
+MAN= freebsd.sys.linker.3lua
+
+.include "Makefile.inc"
+.include <bsd.lib.mk>
diff --git a/libexec/flua/libfreebsd/sys/linker/Makefile.inc b/libexec/flua/libfreebsd/sys/linker/Makefile.inc
new file mode 100644
index 000000000000..da65c0070170
--- /dev/null
+++ b/libexec/flua/libfreebsd/sys/linker/Makefile.inc
@@ -0,0 +1,2 @@
+.PATH: ${.PARSEDIR}
+SRCS+= linker.c
diff --git a/libexec/flua/libfreebsd/sys/linker/freebsd.sys.linker.3lua b/libexec/flua/libfreebsd/sys/linker/freebsd.sys.linker.3lua
new file mode 100644
index 000000000000..34198d20463e
--- /dev/null
+++ b/libexec/flua/libfreebsd/sys/linker/freebsd.sys.linker.3lua
@@ -0,0 +1,46 @@
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.\" Copyright (c) 2024, Baptiste Daroussin <bapt@FreeBSD.org>
+.\"
+.Dd September 6, 2024
+.Dt FREEBSD.SYS.LINKER 3lua
+.Os
+.Sh NAME
+.Nm freebsd.sys.linker
+.Nd Lua binding to
+.Fx 's
+Linker functions
+.Sh SYNOPSIS
+.Bd -literal
+local linker = require('freebsd.sys.linker')
+.Ed
+.Pp
+.Bl -tag -width XXXX -compact
+.It Dv fileid, err, errno = linker.kldload(name)
+.It Dv ok, err, errno = linker.kldunload(fileid|name)
+.El
+.Sh DESCRIPTION
+The
+.Nm
+module is a binding to the
+.Fx 's
+linker functions.
+List of functions:
+.Bl -tag -width XXXX
+.It Dv fileid, err = freebsd.sys.linker.kldload(name)
+Load the kernel module named
+.Fa name
+and return the identifier
+.Pq fileid
+as an interger.
+.It Dv ok, err, errno = freebsd.sys.linker.kldunload(fileid|name)
+Unload the kernel module identifier either by
+.Fa name
+as a string, or
+.Fa fileid
+as an integer.
+.El
+.Sh SEE ALSO
+.Xr kldload 2 ,
+.Xr kldunload 2
diff --git a/libexec/flua/libfreebsd/sys/linker/linker.c b/libexec/flua/libfreebsd/sys/linker/linker.c
new file mode 100644
index 000000000000..c78fbb2b39d2
--- /dev/null
+++ b/libexec/flua/libfreebsd/sys/linker/linker.c
@@ -0,0 +1,86 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2024, Baptiste Daroussin <bapt@FreeBSD.org>
+ */
+
+#include <sys/param.h>
+#include <sys/linker.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include "bootstrap.h"
+
+int luaopen_freebsd_sys_linker(lua_State *L);
+
+static int
+lua_kldload(lua_State *L)
+{
+ const char *name;
+ int ret;
+
+ name = luaL_checkstring(L, 1);
+ ret = kldload(name);
+ if (ret == -1) {
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(errno));
+ lua_pushinteger(L, errno);
+ return (3);
+ }
+ lua_pushinteger(L, ret);
+ return (1);
+}
+
+static int
+lua_kldunload(lua_State *L)
+{
+ const char *name;
+ int ret, fileid;
+
+ if (lua_isinteger(L, 1)) {
+ fileid = lua_tointeger(L, 1);
+ } else {
+ name = luaL_checkstring(L, 1);
+ fileid = kldfind(name);
+ }
+ if (fileid == -1) {
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(errno));
+ lua_pushinteger(L, errno);
+ return (3);
+ }
+ ret = kldunload(fileid);
+ lua_pushinteger(L, ret);
+ if (ret == -1) {
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(errno));
+ lua_pushinteger(L, errno);
+ return (3);
+ }
+ lua_pushinteger(L, 0);
+ return (1);
+}
+
+#define REG_SIMPLE(n) { #n, lua_ ## n }
+static const struct luaL_Reg freebsd_sys_linker[] = {
+ REG_SIMPLE(kldload),
+ REG_SIMPLE(kldunload),
+ { NULL, NULL },
+};
+#undef REG_SIMPLE
+
+int
+luaopen_freebsd_sys_linker(lua_State *L)
+{
+ luaL_newlib(L, freebsd_sys_linker);
+
+ return (1);
+}
+
+FLUA_MODULE_NAMED(freebsd_sys_linker, "freebsd.sys.linker");
diff --git a/libexec/flua/libhash/Makefile b/libexec/flua/libhash/Makefile
new file mode 100644
index 000000000000..9cbd6f15acae
--- /dev/null
+++ b/libexec/flua/libhash/Makefile
@@ -0,0 +1,6 @@
+SHLIB_NAME= hash.so
+
+MAN= hash.3lua
+
+.include "Makefile.inc"
+.include <bsd.lib.mk>
diff --git a/libexec/flua/libhash/Makefile.inc b/libexec/flua/libhash/Makefile.inc
new file mode 100644
index 000000000000..d112dfe7df33
--- /dev/null
+++ b/libexec/flua/libhash/Makefile.inc
@@ -0,0 +1,3 @@
+.PATH: ${.PARSEDIR}
+SRCS+= lhash.c
+LIBADD+= md
diff --git a/libexec/flua/libhash/hash.3lua b/libexec/flua/libhash/hash.3lua
new file mode 100644
index 000000000000..1662e87f7c68
--- /dev/null
+++ b/libexec/flua/libhash/hash.3lua
@@ -0,0 +1,54 @@
+.\"
+.\" Copyright (c) 2024 Netflix, Inc.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd February 6, 2024
+.Dt HASH 3lua
+.Os
+.Sh NAME
+.Nm new ,
+.Nm update ,
+.Nm digest ,
+.Nm hexdigest
+.Nd Lua Cryptographic hash module.
+.Sh DESCRIPTION
+The built-in cryptographic hashing Lua bindings for the are available via the
+.Ic hash
+table.
+.Ss Supported Hashing Schemes
+The following hashing schemes are supported by the hash module.
+.Bl -bullet -compact
+.It
+sha256
+.El
+.Ss APIs Supported
+.Bl -tag -width asdf -compact
+.It Fn new data
+Compute a digest based on the
+.Va data .
+.It Fn update Va data
+Using the current digest, process
+.Va data
+to compute a new digest as if all prior data had been concatenated together.
+.It Fn digest
+Return the hashed digest as a binary array.
+This resets the context.
+.It Fn hexdigest
+Take
+.Fn digest
+and convert it to an upper case hex string.
+This resets the context.
+.It Va digest_size
+Return the size of the digest, in bytes.
+.It Va block_size
+Return the block size used in bytes.
+.El
+.Sh EXAMPLES
+.Sh SEE ALSO
+.Xr sha256 3
+.Sh AUTHORS
+The
+.Nm
+man page was written by
+.An Warner Losh Aq Mt imp@FreeBSD.org .
diff --git a/libexec/flua/libhash/lhash.c b/libexec/flua/libhash/lhash.c
new file mode 100644
index 000000000000..f455f006bf27
--- /dev/null
+++ b/libexec/flua/libhash/lhash.c
@@ -0,0 +1,183 @@
+/*-
+ * Copyright (c) 2024 Netflix, Inc
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <lua.h>
+#include "lauxlib.h"
+#include "lhash.h"
+
+#include <sha256.h>
+#include <string.h>
+
+#include "bootstrap.h"
+
+#define SHA256_META "SHA256 meta table"
+#define SHA256_DIGEST_LEN 32
+
+/*
+ * Note C++ comments indicate the before -- after state of the stack, in with a
+ * similar convention to forth's ( ) comments. Lua indexes are from 1 and can be
+ * read left to right (leftmost is 1). Negative are relative to the end (-1 is
+ * rightmost). A '.' indicates a return value left on the stack (all values to
+ * its right). Trivial functions don't do this.
+ */
+
+/*
+ * Updates the digest with the new data passed in. Takes 1 argument, which
+ * is converted to a string.
+ */
+static int
+lua_sha256_update(lua_State *L)
+{
+ size_t len;
+ const unsigned char *data;
+ SHA256_CTX *ctx;
+
+ ctx = luaL_checkudata(L, 1, SHA256_META);
+ data = luaL_checklstring(L, 2, &len);
+ SHA256_Update(ctx, data, len);
+
+ lua_settop(L, 1);
+
+ return (1);
+}
+
+/*
+ * Finalizes the digest value and returns it as a 32-byte binary string. The ctx
+ * is zeroed.
+ */
+static int
+lua_sha256_digest(lua_State *L)
+{
+ SHA256_CTX *ctx;
+ unsigned char digest[SHA256_DIGEST_LEN];
+
+ ctx = luaL_checkudata(L, 1, SHA256_META);
+ SHA256_Final(digest, ctx);
+ lua_pushlstring(L, digest, sizeof(digest));
+
+ return (1);
+}
+
+/*
+ * Finalizes the digest value and returns it as a 64-byte ascii string of hex
+ * numbers. The ctx is zeroed.
+ */
+static int
+lua_sha256_hexdigest(lua_State *L)
+{
+ SHA256_CTX *ctx;
+ char buf[SHA256_DIGEST_LEN * 2 + 1];
+ unsigned char digest[SHA256_DIGEST_LEN];
+ static const char hex[]="0123456789abcdef";
+ int i;
+
+ ctx = luaL_checkudata(L, 1, SHA256_META);
+ SHA256_Final(digest, ctx);
+ for (i = 0; i < SHA256_DIGEST_LEN; i++) {
+ buf[i+i] = hex[digest[i] >> 4];
+ buf[i+i+1] = hex[digest[i] & 0x0f];
+ }
+ buf[i+i] = '\0';
+
+ lua_pushstring(L, buf);
+
+ return (1);
+}
+
+/*
+ * Zeros out the ctx before garbage collection. Normally this is done in
+ * obj:digest or obj:hexdigest, but if not, it will be wiped here. Lua
+ * manages freeing the ctx memory.
+ */
+static int
+lua_sha256_done(lua_State *L)
+{
+ SHA256_CTX *ctx;
+
+ ctx = luaL_checkudata(L, 1, SHA256_META);
+ memset(ctx, 0, sizeof(*ctx));
+
+ return (0);
+}
+
+/*
+ * Create object obj which accumulates the state of the sha256 digest
+ * for its contents and any subsequent obj:update call. It takes zero
+ * or 1 arguments.
+ */
+static int
+lua_sha256(lua_State *L)
+{
+ SHA256_CTX *ctx;
+ int top;
+
+ /* We take 0 or 1 args */
+ top = lua_gettop(L); // data -- data
+ if (top > 1) {
+ lua_pushnil(L);
+ return (1);
+ }
+
+ ctx = lua_newuserdata(L, sizeof(*ctx)); // data -- data ctx
+ SHA256_Init(ctx);
+ if (top == 1) {
+ size_t len;
+ const unsigned char *data;
+
+ data = luaL_checklstring(L, 1, &len);
+ SHA256_Update(ctx, data, len);
+ }
+ luaL_setmetatable(L, SHA256_META); // data ctx -- data ctx
+
+ return (1); // data . ctx
+}
+
+/*
+ * Setup the metatable to manage our userdata that we create in lua_sha256. We
+ * request a finalization call with __gc so we can zero out the ctx buffer so
+ * that we don't leak secrets if obj:digest or obj:hexdigest aren't called.
+ */
+static void
+register_metatable_sha256(lua_State *L)
+{
+ luaL_newmetatable(L, SHA256_META); // -- meta
+
+ lua_newtable(L); // meta -- meta tbl
+ lua_pushcfunction(L, lua_sha256_update); // meta tbl -- meta tbl fn
+ lua_setfield(L, -2, "update"); // meta tbl fn -- meta tbl
+ lua_pushcfunction(L, lua_sha256_digest); // meta tbl -- meta tbl fn
+ lua_setfield(L, -2, "digest"); // meta tbl fn -- meta tbl
+ lua_pushcfunction(L, lua_sha256_hexdigest); // meta tbl -- meta tbl fn
+ lua_setfield(L, -2, "hexdigest"); // meta tbl fn -- meta tbl
+
+ /* Associate tbl with metatable */
+ lua_setfield(L, -2, "__index"); // meta tbl -- meta
+ lua_pushcfunction(L, lua_sha256_done); // meta -- meta fn
+ lua_setfield(L, -2, "__gc"); // meta fn -- meta
+
+ lua_pop(L, 1); // meta --
+}
+
+#define REG_SIMPLE(n) { #n, lua_ ## n }
+static const struct luaL_Reg hashlib[] = {
+ REG_SIMPLE(sha256),
+ { NULL, NULL },
+};
+#undef REG_SIMPLE
+
+int
+luaopen_hash(lua_State *L)
+{
+ register_metatable_sha256(L);
+
+ luaL_newlib(L, hashlib);
+
+ return 1;
+}
+
+#ifndef _STANDALONE
+FLUA_MODULE(hash);
+#endif
diff --git a/libexec/flua/libhash/lhash.h b/libexec/flua/libhash/lhash.h
new file mode 100644
index 000000000000..c1e9788a55a3
--- /dev/null
+++ b/libexec/flua/libhash/lhash.h
@@ -0,0 +1,11 @@
+/*-
+ * Copyright (c) 2024 Netflix, Inc
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <lua.h>
+
+int luaopen_hash(lua_State *L);
diff --git a/libexec/flua/libjail/Makefile b/libexec/flua/libjail/Makefile
new file mode 100644
index 000000000000..b9c8bdc39095
--- /dev/null
+++ b/libexec/flua/libjail/Makefile
@@ -0,0 +1,6 @@
+SHLIB_NAME= jail.so
+
+MAN= jail.3lua
+
+.include "Makefile.inc"
+.include <bsd.lib.mk>
diff --git a/libexec/flua/libjail/Makefile.inc b/libexec/flua/libjail/Makefile.inc
new file mode 100644
index 000000000000..a896bf38c65b
--- /dev/null
+++ b/libexec/flua/libjail/Makefile.inc
@@ -0,0 +1,3 @@
+.PATH: ${.PARSEDIR}
+SRCS+= lua_jail.c
+LIBADD+= jail
diff --git a/libexec/flua/libjail/jail.3lua b/libexec/flua/libjail/jail.3lua
new file mode 100644
index 000000000000..59cbd2dc228c
--- /dev/null
+++ b/libexec/flua/libjail/jail.3lua
@@ -0,0 +1,277 @@
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.\" Copyright (c) 2020, Ryan Moeller <freqlabs@FreeBSD.org>
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd October 24, 2020
+.Dt JAIL 3lua
+.Os
+.Sh NAME
+.Nm attach ,
+.Nm getid ,
+.Nm getname ,
+.Nm list ,
+.Nm allparams ,
+.Nm getparams ,
+.Nm remove ,
+.Nm setparams ,
+.Nm CREATE ,
+.Nm UPDATE ,
+.Nm ATTACH ,
+.Nm DYING
+.Nd Lua binding to
+.Xr jail 3
+.Sh SYNOPSIS
+.Bd -literal
+local jail = require('jail')
+.Ed
+.Pp
+.Bl -tag -width XXXX -compact
+.It Dv ok, err = jail.attach(jid|name)
+.It Dv jid, err = jail.getid(name)
+.It Dv name, err = jail.getname(jid)
+.It Dv params, err = jail.allparams()
+.It Dv iter, jail_obj = jail.list([params])
+.It Dv jid, res = jail.getparams(jid|name, params [, flags ] )
+.It Dv ok, err = jail.remove(jid|name)
+.It Dv jid, err = jail.setparams(jid|name, params, flags )
+.It Dv jail.CREATE
+.It Dv jail.UPDATE
+.It Dv jail.ATTACH
+.It Dv jail.DYING
+.El
+.Sh DESCRIPTION
+The
+.Nm jail
+module is a binding to the
+.Xr jail 3
+library.
+It provides a string-oriented interface for the
+.Xr jail_get 2
+and
+.Xr jail_set 2
+system calls.
+.Bl -tag -width XXXX
+.It Dv ok, err = jail.attach(jid|name)
+Attach to the given jail, identified by an integer
+.Fa jid
+or the
+.Fa name .
+.It Dv jid, err = jail.getid(name)
+Get the jail identifier
+.Pq jid
+as an integer.
+.Fa name
+is the name of a jail or a jid in the form of a string.
+.It Dv name, err = jail.getname(jid)
+Get the name of a jail as a string for the given
+.Fa jid
+.Pq an integer .
+.It Dv iter, jail_obj = jail.list([params])
+Returns an iterator over running jails on the system.
+.Dv params
+is a list of parameters to fetch for each jail as we iterate.
+.Dv jid
+and
+.Dv name
+will always be returned, and may be omitted from
+.Dv params .
+Additionally,
+.Dv params
+may be omitted or an empty table, but not nil.
+.Pp
+See
+.Sx EXAMPLES .
+.It Dv params, err = jail.allparams()
+Get a list of all supported parameter names
+.Pq as strings .
+See
+.Xr jail 8
+for descriptions of the core jail parameters.
+.It Dv jid, res = jail.getparams(jid|name, params [, flags ] )
+Get a table of the requested parameters for the given jail.
+.Nm jid|name
+can either be the jid as an integer or the jid or name as a string.
+.Nm params
+is a list of parameter names.
+.Nm flags
+is an optional integer representing the flag bits to apply for the operation.
+See the list of flags below.
+Only the
+.Dv DYING
+flag is valid to set.
+.It Dv ok, err = jail.remove(jid|name)
+Remove the given jail, identified by an integer
+.Fa jid
+or the
+.Fa name .
+.It Dv jid, err = jail.setparams(jid|name, params [, flags ] )
+Set parameters for a given jail.
+This is used to create, update, attach to, or destroy a jail.
+.Nm jid|name
+can either be the jid as an integer or the jid or name as a string.
+.Nm params
+is a table of parameters to apply to the jail, where each key in the table
+is a parameter name as a string and each value is a string that will be
+converted to the internal value type by
+.Xr jailparam_import 3 .
+.Nm flags
+is an optional integer representing the flag bits to apply for the operation.
+See the list of flags below.
+.El
+.Pp
+The
+.Nm flags
+arguments are an integer bitwise-or combination of one or more of the following
+flags:
+.Bl -tag -width XXXX
+.It Dv jail.CREATE
+Used with
+.Fn setparams
+to create a new jail.
+The jail must not already exist, unless combined with
+.Dv UPDATE .
+.It Dv jail.UPDATE
+Used with
+.Fn setparams
+to modify an existing jail.
+The jail must already exist, unless combined with
+.Dv CREATE .
+.It Dv jail.ATTACH
+Used with
+.Fn setparams
+in combination with
+.Dv CREATE
+or
+.Dv UPDATE
+to attach the current process to a jail.
+.It Dv jail.DYING
+Allow operating on a jail that is in the process of being removed.
+.El
+.Sh RETURN VALUES
+The
+.Fn getid
+and
+.Fn setparams
+functions return a jail identifier integer on success, or
+.Dv nil
+and an error message string if an error occurred.
+.Pp
+The
+.Fn getname
+function returns a jail name string on success, or
+.Dv nil
+and an error message string if an error occurred.
+.Pp
+The
+.Fn allparams
+function returns a list of parameter name strings on success, or
+.Dv nil
+and an error message string if an error occurred.
+.Pp
+The
+.Fn getparams
+function returns a jail identifier integer and a table of jail parameters
+with parameter name strings as keys and strings for values on success, or
+.Dv nil
+and an error message string if an error occurred.
+.Pp
+The
+.Fn list
+function returns an iterator over the list of running jails.
+.Pp
+The
+.Fn attach
+and
+.Fn remove
+functions return true on success, or
+.Dv nil
+and an error message string if an error occurred.
+.Sh EXAMPLES
+Set the hostname of jail
+.Dq foo
+to
+.Dq foo.bar :
+.Bd -literal -offset indent
+local jail = require('jail')
+
+jid, err = jail.setparams("foo", {["host.hostname"]="foo.bar"},
+ jail.UPDATE)
+if not jid then
+ error(err)
+end
+.Ed
+.Pp
+Retrieve the hostname of jail
+.Dq foo :
+.Bd -literal -offset indent
+local jail = require('jail')
+
+jid, res = jail.getparams("foo", {"host.hostname"})
+if not jid then
+ error(res)
+end
+print(res["host.hostname"])
+.Ed
+.Pp
+Iterate over jails on the system:
+.Bd -literal -offset indent
+local jail = require('jail')
+
+-- Recommended: just loop over it
+for jparams in jail.list() do
+ print(jparams["jid"] .. " = " .. jparams["name"])
+end
+
+-- Request path and hostname, too
+for jparams in jail.list({"path", "host.hostname"}) do
+ print(jparams["host.hostname"] .. " mounted at " .. jparams["path"])
+end
+
+-- Raw iteration protocol
+local iter, jail_obj = jail.list()
+
+-- Request the first params
+local jparams = jail_obj:next()
+while jparams do
+ print(jparams["jid"] .. " = " .. jparams["name"])
+ -- Subsequent calls may return nil
+ jparams = jail_obj:next()
+end
+.Ed
+.Sh SEE ALSO
+.Xr jail 2 ,
+.Xr jail 3 ,
+.Xr jail 8
+.Sh HISTORY
+The
+.Nm jail
+Lua module for flua first appeared in
+.Fx 13.0 .
+.Sh AUTHORS
+.An Ryan Moeller ,
+with inspiration from
+.Nx
+gpio(3lua), by
+.An Mark Balmer .
diff --git a/libexec/flua/libjail/lua_jail.c b/libexec/flua/libjail/lua_jail.c
new file mode 100644
index 000000000000..8c3ec6c1d500
--- /dev/null
+++ b/libexec/flua/libjail/lua_jail.c
@@ -0,0 +1,722 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2020, Ryan Moeller <freqlabs@FreeBSD.org>
+ * Copyright (c) 2020, Kyle Evans <kevans@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/jail.h>
+#include <errno.h>
+#include <jail.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <lua.h>
+#include <lauxlib.h>
+#include <lualib.h>
+
+#include "bootstrap.h"
+
+#define JAIL_METATABLE "jail iterator metatable"
+
+/*
+ * Taken from RhodiumToad's lspawn implementation, let static analyzers make
+ * better decisions about the behavior after we raise an error.
+ */
+#if defined(LUA_VERSION_NUM) && defined(LUA_API)
+LUA_API int (lua_error) (lua_State *L) __dead2;
+#endif
+#if defined(LUA_ERRFILE) && defined(LUALIB_API)
+LUALIB_API int (luaL_argerror) (lua_State *L, int arg, const char *extramsg) __dead2;
+LUALIB_API int (luaL_typeerror) (lua_State *L, int arg, const char *tname) __dead2;
+LUALIB_API int (luaL_error) (lua_State *L, const char *fmt, ...) __dead2;
+#endif
+
+int luaopen_jail(lua_State *);
+
+typedef bool (*getparam_filter)(const char *, void *);
+
+static void getparam_table(lua_State *L, int paramindex,
+ struct jailparam *params, size_t paramoff, size_t *params_countp,
+ getparam_filter keyfilt, void *udata);
+
+struct l_jail_iter {
+ struct jailparam *params;
+ size_t params_count;
+ int jid;
+};
+
+static bool
+l_jail_filter(const char *param_name, void *data __unused)
+{
+
+ /*
+ * Allowing lastjid will mess up our iteration over all jails on the
+ * system, as this is a special parameter that indicates where the search
+ * starts from. We'll always add jid and name, so just silently remove
+ * these.
+ */
+ return (strcmp(param_name, "lastjid") != 0 &&
+ strcmp(param_name, "jid") != 0 &&
+ strcmp(param_name, "name") != 0);
+}
+
+static int
+l_jail_iter_next(lua_State *L)
+{
+ struct l_jail_iter *iter, **iterp;
+ struct jailparam *jp;
+ int serrno;
+
+ iterp = (struct l_jail_iter **)luaL_checkudata(L, 1, JAIL_METATABLE);
+ iter = *iterp;
+ luaL_argcheck(L, iter != NULL, 1, "closed jail iterator");
+
+ jp = iter->params;
+ /* Populate lastjid; we must keep it in params[0] for our sake. */
+ if (jailparam_import_raw(&jp[0], &iter->jid, sizeof(iter->jid))) {
+ jailparam_free(jp, iter->params_count);
+ free(jp);
+ free(iter);
+ *iterp = NULL;
+ return (luaL_error(L, "jailparam_import_raw: %s", jail_errmsg));
+ }
+
+ /* The list of requested params was populated back in l_list(). */
+ iter->jid = jailparam_get(jp, iter->params_count, 0);
+ if (iter->jid == -1) {
+ /*
+ * We probably got an ENOENT to signify the end of the jail
+ * listing, but just in case we didn't; stash it off and start
+ * cleaning up. We'll handle non-ENOENT errors later.
+ */
+ serrno = errno;
+ jailparam_free(jp, iter->params_count);
+ free(iter->params);
+ free(iter);
+ *iterp = NULL;
+ if (serrno != ENOENT)
+ return (luaL_error(L, "jailparam_get: %s",
+ strerror(serrno)));
+ return (0);
+ }
+
+ /*
+ * Finally, we'll fill in the return table with whatever parameters the
+ * user requested, in addition to the ones we forced with exception to
+ * lastjid.
+ */
+ lua_newtable(L);
+ for (size_t i = 0; i < iter->params_count; ++i) {
+ char *value;
+
+ jp = &iter->params[i];
+ if (strcmp(jp->jp_name, "lastjid") == 0)
+ continue;
+ value = jailparam_export(jp);
+ lua_pushstring(L, value);
+ lua_setfield(L, -2, jp->jp_name);
+ free(value);
+ }
+
+ return (1);
+}
+
+static int
+l_jail_iter_close(lua_State *L)
+{
+ struct l_jail_iter *iter, **iterp;
+
+ /*
+ * Since we're using this as the __gc method as well, there's a good
+ * chance that it's already been cleaned up by iterating to the end of
+ * the list.
+ */
+ iterp = (struct l_jail_iter **)lua_touserdata(L, 1);
+ iter = *iterp;
+ if (iter == NULL)
+ return (0);
+
+ jailparam_free(iter->params, iter->params_count);
+ free(iter->params);
+ free(iter);
+ *iterp = NULL;
+ return (0);
+}
+
+static int
+l_list(lua_State *L)
+{
+ struct l_jail_iter *iter;
+ int nargs;
+
+ nargs = lua_gettop(L);
+ if (nargs >= 1)
+ luaL_checktype(L, 1, LUA_TTABLE);
+
+ iter = malloc(sizeof(*iter));
+ if (iter == NULL)
+ return (luaL_error(L, "malloc: %s", strerror(errno)));
+
+ /*
+ * lastjid, jid, name + length of the table. This may be too much if
+ * we have duplicated one of those fixed parameters.
+ */
+ iter->params_count = 3 + (nargs != 0 ? lua_rawlen(L, 1) : 0);
+ iter->params = malloc(iter->params_count * sizeof(*iter->params));
+ if (iter->params == NULL) {
+ free(iter);
+ return (luaL_error(L, "malloc params: %s", strerror(errno)));
+ }
+
+ /* The :next() method will populate lastjid before jail_getparam(). */
+ if (jailparam_init(&iter->params[0], "lastjid") == -1) {
+ free(iter->params);
+ free(iter);
+ return (luaL_error(L, "jailparam_init: %s", jail_errmsg));
+ }
+ /* These two will get populated by jail_getparam(). */
+ if (jailparam_init(&iter->params[1], "jid") == -1) {
+ jailparam_free(iter->params, 1);
+ free(iter->params);
+ free(iter);
+ return (luaL_error(L, "jailparam_init: %s",
+ jail_errmsg));
+ }
+ if (jailparam_init(&iter->params[2], "name") == -1) {
+ jailparam_free(iter->params, 2);
+ free(iter->params);
+ free(iter);
+ return (luaL_error(L, "jailparam_init: %s",
+ jail_errmsg));
+ }
+
+ /*
+ * We only need to process additional arguments if we were given any.
+ * That is, we don't descend into getparam_table if we're passed nothing
+ * or an empty table.
+ */
+ iter->jid = 0;
+ if (iter->params_count != 3)
+ getparam_table(L, 1, iter->params, 2, &iter->params_count,
+ l_jail_filter, NULL);
+
+ /*
+ * Part of the iterator magic. We give it an iterator function with a
+ * metatable defining next() and close() that can be used for manual
+ * iteration. iter->jid is how we track which jail we last iterated, to
+ * be supplied as "lastjid".
+ */
+ lua_pushcfunction(L, l_jail_iter_next);
+ *(struct l_jail_iter **)lua_newuserdata(L,
+ sizeof(struct l_jail_iter **)) = iter;
+ luaL_getmetatable(L, JAIL_METATABLE);
+ lua_setmetatable(L, -2);
+ return (2);
+}
+
+static void
+register_jail_metatable(lua_State *L)
+{
+ luaL_newmetatable(L, JAIL_METATABLE);
+ lua_newtable(L);
+ lua_pushcfunction(L, l_jail_iter_next);
+ lua_setfield(L, -2, "next");
+ lua_pushcfunction(L, l_jail_iter_close);
+ lua_setfield(L, -2, "close");
+
+ lua_setfield(L, -2, "__index");
+
+ lua_pushcfunction(L, l_jail_iter_close);
+ lua_setfield(L, -2, "__gc");
+
+ lua_pop(L, 1);
+}
+
+static int
+l_getid(lua_State *L)
+{
+ const char *name;
+ int jid;
+
+ name = luaL_checkstring(L, 1);
+ jid = jail_getid(name);
+ if (jid == -1) {
+ lua_pushnil(L);
+ lua_pushstring(L, jail_errmsg);
+ return (2);
+ }
+ lua_pushinteger(L, jid);
+ return (1);
+}
+
+static int
+l_getname(lua_State *L)
+{
+ char *name;
+ int jid;
+
+ jid = luaL_checkinteger(L, 1);
+ name = jail_getname(jid);
+ if (name == NULL) {
+ lua_pushnil(L);
+ lua_pushstring(L, jail_errmsg);
+ return (2);
+ }
+ lua_pushstring(L, name);
+ free(name);
+ return (1);
+}
+
+static int
+l_allparams(lua_State *L)
+{
+ struct jailparam *params;
+ int params_count;
+
+ params_count = jailparam_all(&params);
+ if (params_count == -1) {
+ lua_pushnil(L);
+ lua_pushstring(L, jail_errmsg);
+ return (2);
+ }
+ lua_newtable(L);
+ for (int i = 0; i < params_count; ++i) {
+ lua_pushstring(L, params[i].jp_name);
+ lua_rawseti(L, -2, i + 1);
+ }
+ jailparam_free(params, params_count);
+ free(params);
+ return (1);
+}
+
+static void
+getparam_table(lua_State *L, int paramindex, struct jailparam *params,
+ size_t params_off, size_t *params_countp, getparam_filter keyfilt,
+ void *udata)
+{
+ size_t params_count;
+ int skipped;
+
+ params_count = *params_countp;
+ skipped = 0;
+ for (size_t i = 1 + params_off; i < params_count; ++i) {
+ const char *param_name;
+
+ lua_rawgeti(L, -1, i - params_off);
+ param_name = lua_tostring(L, -1);
+ if (param_name == NULL) {
+ jailparam_free(params, i - skipped);
+ free(params);
+ luaL_argerror(L, paramindex,
+ "param names must be strings");
+ }
+ lua_pop(L, 1);
+ if (keyfilt != NULL && !keyfilt(param_name, udata)) {
+ ++skipped;
+ continue;
+ }
+ if (jailparam_init(&params[i - skipped], param_name) == -1) {
+ jailparam_free(params, i - skipped);
+ free(params);
+ luaL_error(L, "jailparam_init: %s", jail_errmsg);
+ }
+ }
+ *params_countp -= skipped;
+}
+
+struct getparams_filter_args {
+ int filter_type;
+};
+
+static bool
+l_getparams_filter(const char *param_name, void *udata)
+{
+ struct getparams_filter_args *gpa;
+
+ gpa = udata;
+
+ /* Skip name or jid, whichever was given. */
+ if (gpa->filter_type == LUA_TSTRING) {
+ if (strcmp(param_name, "name") == 0)
+ return (false);
+ } else /* type == LUA_TNUMBER */ {
+ if (strcmp(param_name, "jid") == 0)
+ return (false);
+ }
+
+ return (true);
+}
+
+static int
+l_getparams(lua_State *L)
+{
+ const char *name;
+ struct jailparam *params;
+ size_t params_count;
+ struct getparams_filter_args gpa;
+ int flags, jid, type;
+
+ type = lua_type(L, 1);
+ luaL_argcheck(L, type == LUA_TSTRING || type == LUA_TNUMBER, 1,
+ "expected a jail name (string) or id (integer)");
+ luaL_checktype(L, 2, LUA_TTABLE);
+ params_count = 1 + lua_rawlen(L, 2);
+ flags = luaL_optinteger(L, 3, 0);
+
+ params = malloc(params_count * sizeof(struct jailparam));
+ if (params == NULL)
+ return (luaL_error(L, "malloc: %s", strerror(errno)));
+
+ /*
+ * Set the jail name or id param as determined by the first arg.
+ */
+
+ if (type == LUA_TSTRING) {
+ if (jailparam_init(&params[0], "name") == -1) {
+ free(params);
+ return (luaL_error(L, "jailparam_init: %s",
+ jail_errmsg));
+ }
+ name = lua_tostring(L, 1);
+ if (jailparam_import(&params[0], name) == -1) {
+ jailparam_free(params, 1);
+ free(params);
+ return (luaL_error(L, "jailparam_import: %s",
+ jail_errmsg));
+ }
+ } else /* type == LUA_TNUMBER */ {
+ if (jailparam_init(&params[0], "jid") == -1) {
+ free(params);
+ return (luaL_error(L, "jailparam_init: %s",
+ jail_errmsg));
+ }
+ jid = lua_tointeger(L, 1);
+ if (jailparam_import_raw(&params[0], &jid, sizeof(jid)) == -1) {
+ jailparam_free(params, 1);
+ free(params);
+ return (luaL_error(L, "jailparam_import_raw: %s",
+ jail_errmsg));
+ }
+ }
+
+ /*
+ * Set the remaining param names being requested.
+ */
+ gpa.filter_type = type;
+ getparam_table(L, 2, params, 0, &params_count, l_getparams_filter, &gpa);
+
+ /*
+ * Get the values and convert to a table.
+ */
+
+ jid = jailparam_get(params, params_count, flags);
+ if (jid == -1) {
+ jailparam_free(params, params_count);
+ free(params);
+ lua_pushnil(L);
+ lua_pushstring(L, jail_errmsg);
+ return (2);
+ }
+ lua_pushinteger(L, jid);
+
+ lua_newtable(L);
+ for (size_t i = 0; i < params_count; ++i) {
+ char *value;
+
+ if (params[i].jp_flags & JP_KEYVALUE &&
+ params[i].jp_valuelen == 0) {
+ /* Communicate back a missing key. */
+ lua_pushnil(L);
+ } else {
+ value = jailparam_export(&params[i]);
+ lua_pushstring(L, value);
+ free(value);
+ }
+
+ lua_setfield(L, -2, params[i].jp_name);
+ }
+
+ jailparam_free(params, params_count);
+ free(params);
+
+ return (2);
+}
+
+static int
+l_setparams(lua_State *L)
+{
+ const char *name;
+ struct jailparam *params;
+ size_t params_count;
+ int flags, jid, type;
+
+ type = lua_type(L, 1);
+ luaL_argcheck(L, type == LUA_TSTRING || type == LUA_TNUMBER, 1,
+ "expected a jail name (string) or id (integer)");
+ luaL_checktype(L, 2, LUA_TTABLE);
+
+ lua_pushnil(L);
+ for (params_count = 1; lua_next(L, 2) != 0; ++params_count)
+ lua_pop(L, 1);
+
+ flags = luaL_optinteger(L, 3, 0);
+
+ params = malloc(params_count * sizeof(struct jailparam));
+ if (params == NULL)
+ return (luaL_error(L, "malloc: %s", strerror(errno)));
+
+ /*
+ * Set the jail name or id param as determined by the first arg.
+ */
+
+ if (type == LUA_TSTRING) {
+ if (jailparam_init(&params[0], "name") == -1) {
+ free(params);
+ return (luaL_error(L, "jailparam_init: %s",
+ jail_errmsg));
+ }
+ name = lua_tostring(L, 1);
+ if (jailparam_import(&params[0], name) == -1) {
+ jailparam_free(params, 1);
+ free(params);
+ return (luaL_error(L, "jailparam_import: %s",
+ jail_errmsg));
+ }
+ } else /* type == LUA_TNUMBER */ {
+ if (jailparam_init(&params[0], "jid") == -1) {
+ free(params);
+ return (luaL_error(L, "jailparam_init: %s",
+ jail_errmsg));
+ }
+ jid = lua_tointeger(L, 1);
+ if (jailparam_import_raw(&params[0], &jid, sizeof(jid)) == -1) {
+ jailparam_free(params, 1);
+ free(params);
+ return (luaL_error(L, "jailparam_import_raw: %s",
+ jail_errmsg));
+ }
+ }
+
+ /*
+ * Set the rest of the provided params.
+ */
+
+ lua_pushnil(L);
+ for (size_t i = 1; i < params_count && lua_next(L, 2) != 0; ++i) {
+ const char *value;
+
+ name = lua_tostring(L, -2);
+ if (name == NULL) {
+ jailparam_free(params, i);
+ free(params);
+ return (luaL_argerror(L, 2,
+ "param names must be strings"));
+ }
+ if (jailparam_init(&params[i], name) == -1) {
+ jailparam_free(params, i);
+ free(params);
+ return (luaL_error(L, "jailparam_init: %s",
+ jail_errmsg));
+ }
+
+ value = lua_tostring(L, -1);
+ /* Allow passing NULL for key removal. */
+ if (value == NULL && !(params[i].jp_flags & JP_KEYVALUE)) {
+ jailparam_free(params, i + 1);
+ free(params);
+ return (luaL_argerror(L, 2,
+ "param values must be strings"));
+ }
+ if (jailparam_import(&params[i], value) == -1) {
+ jailparam_free(params, i + 1);
+ free(params);
+ return (luaL_error(L, "jailparam_import: %s",
+ jail_errmsg));
+ }
+
+ lua_pop(L, 1);
+ }
+
+ /*
+ * Attempt to set the params.
+ */
+
+ jid = jailparam_set(params, params_count, flags);
+ if (jid == -1) {
+ jailparam_free(params, params_count);
+ free(params);
+ lua_pushnil(L);
+ lua_pushstring(L, jail_errmsg);
+ return (2);
+ }
+ lua_pushinteger(L, jid);
+
+ jailparam_free(params, params_count);
+ free(params);
+ return (1);
+}
+
+static int
+l_attach(lua_State *L)
+{
+ int jid, type;
+
+ type = lua_type(L, 1);
+ luaL_argcheck(L, type == LUA_TSTRING || type == LUA_TNUMBER, 1,
+ "expected a jail name (string) or id (integer)");
+
+ if (lua_isstring(L, 1)) {
+ /* Resolve it to a jid. */
+ jid = jail_getid(lua_tostring(L, 1));
+ if (jid == -1) {
+ lua_pushnil(L);
+ lua_pushstring(L, jail_errmsg);
+ return (2);
+ }
+ } else {
+ jid = lua_tointeger(L, 1);
+ }
+
+ if (jail_attach(jid) == -1) {
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(errno));
+ return (2);
+ }
+
+ lua_pushboolean(L, 1);
+ return (1);
+}
+
+static int
+l_remove(lua_State *L)
+{
+ int jid, type;
+
+ type = lua_type(L, 1);
+ luaL_argcheck(L, type == LUA_TSTRING || type == LUA_TNUMBER, 1,
+ "expected a jail name (string) or id (integer)");
+
+ if (lua_isstring(L, 1)) {
+ /* Resolve it to a jid. */
+ jid = jail_getid(lua_tostring(L, 1));
+ if (jid == -1) {
+ lua_pushnil(L);
+ lua_pushstring(L, jail_errmsg);
+ return (2);
+ }
+ } else {
+ jid = lua_tointeger(L, 1);
+ }
+
+ if (jail_remove(jid) == -1) {
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(errno));
+ return (2);
+ }
+
+ lua_pushboolean(L, 1);
+ return (1);
+}
+
+static const struct luaL_Reg l_jail[] = {
+ /** Get id of a jail by name.
+ * @param name jail name (string)
+ * @return jail id (integer)
+ * or nil, error (string) on error
+ */
+ {"getid", l_getid},
+ /** Get name of a jail by id.
+ * @param jid jail id (integer)
+ * @return jail name (string)
+ * or nil, error (string) on error
+ */
+ {"getname", l_getname},
+ /** Get a list of all known jail parameters.
+ * @return list of jail parameter names (table of strings)
+ * or nil, error (string) on error
+ */
+ {"allparams", l_allparams},
+ /** Get the listed params for a given jail.
+ * @param jail jail name (string) or id (integer)
+ * @param params list of parameter names (table of strings)
+ * @param flags optional flags (integer)
+ * @return jid (integer), params (table of [string] = string)
+ * or nil, error (string) on error
+ */
+ {"getparams", l_getparams},
+ /** Set params for a given jail.
+ * @param jail jail name (string) or id (integer)
+ * @param params params and values (table of [string] = string)
+ * @param flags optional flags (integer)
+ * @return jid (integer)
+ * or nil, error (string) on error
+ */
+ {"setparams", l_setparams},
+ /** Get a list of jail parameters for running jails on the system.
+ * @param params optional list of parameter names (table of
+ * strings)
+ * @return iterator (function), jail_obj (object) with next and
+ * close methods
+ */
+ {"list", l_list},
+ /** Attach to a running jail.
+ * @param jail jail name (string) or id (integer)
+ * @return true (boolean)
+ * or nil, error (string) on error
+ */
+ {"attach", l_attach},
+ /** Remove a running jail.
+ * @param jail jail name (string) or id (integer)
+ * @return true (boolean)
+ * or nil, error (string) on error
+ */
+ {"remove", l_remove},
+ {NULL, NULL}
+};
+
+int
+luaopen_jail(lua_State *L)
+{
+ lua_newtable(L);
+
+ luaL_setfuncs(L, l_jail, 0);
+
+ lua_pushinteger(L, JAIL_CREATE);
+ lua_setfield(L, -2, "CREATE");
+ lua_pushinteger(L, JAIL_UPDATE);
+ lua_setfield(L, -2, "UPDATE");
+ lua_pushinteger(L, JAIL_ATTACH);
+ lua_setfield(L, -2, "ATTACH");
+ lua_pushinteger(L, JAIL_DYING);
+ lua_setfield(L, -2, "DYING");
+
+ register_jail_metatable(L);
+
+ return (1);
+}
+
+FLUA_MODULE(jail);
diff --git a/libexec/flua/liblyaml/Makefile b/libexec/flua/liblyaml/Makefile
new file mode 100644
index 000000000000..8d1432acd325
--- /dev/null
+++ b/libexec/flua/liblyaml/Makefile
@@ -0,0 +1,4 @@
+SHLIB_NAME= yaml.so
+
+.include "Makefile.inc"
+.include <bsd.lib.mk>
diff --git a/libexec/flua/liblyaml/Makefile.inc b/libexec/flua/liblyaml/Makefile.inc
new file mode 100644
index 000000000000..caa1f37b57eb
--- /dev/null
+++ b/libexec/flua/liblyaml/Makefile.inc
@@ -0,0 +1,20 @@
+WARNS= 1
+
+LYAMLSRC?= ${SRCTOP}/contrib/lyaml
+.PATH: ${LYAMLSRC}/ext/yaml ${LYAMLSRC}/lib/lyaml
+SRCS+= emitter.c \
+ parser.c \
+ scanner.c \
+ yaml.c
+CFLAGS+= \
+ -I${LYAMLSRC}/ext/yaml \
+ -I${SRCTOP}/contrib/libyaml/include \
+ -DVERSION=\"6.2.8\"
+LIBADD+= yaml
+
+FILESGROUPS+= YAML
+YAML= explicit.lua \
+ functional.lua \
+ implicit.lua \
+ init.lua
+YAMLDIR= ${SHAREDIR}/flua/lyaml
diff --git a/libexec/flua/libucl/Makefile b/libexec/flua/libucl/Makefile
new file mode 100644
index 000000000000..32d76d1ea1ad
--- /dev/null
+++ b/libexec/flua/libucl/Makefile
@@ -0,0 +1,4 @@
+SHLIB_NAME= ucl.so
+
+.include "Makefile.inc"
+.include <bsd.lib.mk>
diff --git a/libexec/flua/libucl/Makefile.inc b/libexec/flua/libucl/Makefile.inc
new file mode 100644
index 000000000000..70fb0f265635
--- /dev/null
+++ b/libexec/flua/libucl/Makefile.inc
@@ -0,0 +1,12 @@
+.if ${WARNS:U6} > 2
+WARNS= 2
+.endif
+
+UCLSRC?= ${SRCTOP}/contrib/libucl
+.PATH: ${UCLSRC}/lua
+SRCS+= lua_ucl.c
+CFLAGS+= \
+ -I${UCLSRC}/include \
+ -I${UCLSRC}/src \
+ -I${UCLSRC}/uthash
+LIBADD+= ucl
diff --git a/libexec/flua/linit_flua.c b/libexec/flua/linit_flua.c
new file mode 100644
index 000000000000..65356c938671
--- /dev/null
+++ b/libexec/flua/linit_flua.c
@@ -0,0 +1,95 @@
+/*
+** $Id: linit.c,v 1.39.1.1 2017/04/19 17:20:42 roberto Exp $
+** Initialization of libraries for lua.c and other clients
+** See Copyright Notice in lua.h
+*/
+
+
+#define linit_c
+#define LUA_LIB
+
+/*
+** If you embed Lua in your program and need to open the standard
+** libraries, call luaL_openlibs in your program. If you need a
+** different set of libraries, copy this file to your project and edit
+** it to suit your needs.
+**
+** You can also *preload* libraries, so that a later 'require' can
+** open the library, which is already linked to the application.
+** For that, do the following code:
+**
+** luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE);
+** lua_pushcfunction(L, luaopen_modname);
+** lua_setfield(L, -2, modname);
+** lua_pop(L, 1); // remove PRELOAD table
+*/
+
+#include "lprefix.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+
+#include "lua.h"
+
+#include "lualib.h"
+#include "lauxlib.h"
+#include "lposix.h"
+
+#include "bootstrap.h"
+
+/*
+** these libs are loaded by lua.c and are readily available to any Lua
+** program
+*/
+static const luaL_Reg loadedlibs[] = {
+ {"_G", luaopen_base},
+ {LUA_LOADLIBNAME, luaopen_package},
+ {LUA_COLIBNAME, luaopen_coroutine},
+ {LUA_TABLIBNAME, luaopen_table},
+ {LUA_IOLIBNAME, luaopen_io},
+ {LUA_OSLIBNAME, luaopen_os},
+ {LUA_STRLIBNAME, luaopen_string},
+ {LUA_MATHLIBNAME, luaopen_math},
+ {LUA_UTF8LIBNAME, luaopen_utf8},
+ {LUA_DBLIBNAME, luaopen_debug},
+#if defined(LUA_COMPAT_BITLIB)
+ {LUA_BITLIBNAME, luaopen_bit32},
+#endif
+ /* FreeBSD Extensions */
+ {"posix", luaopen_posix},
+ {NULL, NULL}
+};
+
+#ifdef BOOTSTRAPPING
+static void __attribute__((constructor)) flua_init_env(void) {
+ /*
+ * This happens in the middle of luaopen_package(). We could move it into
+ * flua_setup_mods(), but it seems better to avoid its timing being so
+ * important that it would break some of our bootstrap modules if someone
+ * were to reorder things.
+ */
+ if (getenv("LUA_PATH") == NULL)
+ setenv("LUA_PATH", BOOTSTRAP_FLUA_PATH, 1);
+}
+
+static void flua_setup_mods (lua_State *L) {
+ const luaL_Reg **flib;
+
+ SET_FOREACH(flib, FLUA_MODULE_SETNAME) {
+ luaL_requiref(L, (*flib)->name, (*flib)->func, 1);
+ lua_pop(L, 1); /* remove lib */
+ }
+};
+#endif
+
+LUALIB_API void luaL_openlibs (lua_State *L) {
+ const luaL_Reg *lib;
+ /* "require" functions from 'loadedlibs' and set results to global table */
+ for (lib = loadedlibs; lib->func; lib++) {
+ luaL_requiref(L, lib->name, lib->func, 1);
+ lua_pop(L, 1); /* remove lib */
+ }
+#ifdef BOOTSTRAPPING
+ flua_setup_mods(L);
+#endif
+}
diff --git a/libexec/flua/modules/lposix.c b/libexec/flua/modules/lposix.c
new file mode 100644
index 000000000000..75cdd345aeaa
--- /dev/null
+++ b/libexec/flua/modules/lposix.c
@@ -0,0 +1,699 @@
+/*-
+ * Copyright (c) 2019, 2023 Kyle Evans <kevans@FreeBSD.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/stat.h>
+#include <sys/utsname.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <fnmatch.h>
+#include <grp.h>
+#include <libgen.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <lua.h>
+#include "lauxlib.h"
+#include "lposix.h"
+
+static void
+enforce_max_args(lua_State *L, int max)
+{
+ int narg;
+
+ narg = lua_gettop(L);
+ luaL_argcheck(L, narg <= max, max + 1, "too many arguments");
+}
+
+/*
+ * Minimal implementation of luaposix needed for internal FreeBSD bits.
+ */
+static int
+lua__exit(lua_State *L)
+{
+ int code;
+
+ enforce_max_args(L, 1);
+ code = luaL_checkinteger(L, 1);
+
+ _exit(code);
+}
+
+static int
+lua_basename(lua_State *L)
+{
+ char *inpath, *outpath;
+
+ enforce_max_args(L, 1);
+ inpath = strdup(luaL_checkstring(L, 1));
+ if (inpath == NULL) {
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(ENOMEM));
+ lua_pushinteger(L, ENOMEM);
+ return (3);
+ }
+
+ outpath = basename(inpath);
+ lua_pushstring(L, outpath);
+ free(inpath);
+ return (1);
+}
+
+static int
+lua_chmod(lua_State *L)
+{
+ const char *path;
+ mode_t mode;
+
+ enforce_max_args(L, 2);
+ path = luaL_checkstring(L, 1);
+ mode = (mode_t)luaL_checkinteger(L, 2);
+
+ if (chmod(path, mode) == -1) {
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(errno));
+ lua_pushinteger(L, errno);
+ return (3);
+ }
+ lua_pushinteger(L, 0);
+ return (1);
+}
+
+static int
+lua_chown(lua_State *L)
+{
+ const char *path;
+ uid_t owner = (uid_t)-1;
+ gid_t group = (gid_t)-1;
+ int error;
+
+ enforce_max_args(L, 3);
+
+ path = luaL_checkstring(L, 1);
+ if (lua_isinteger(L, 2))
+ owner = (uid_t)lua_tointeger(L, 2);
+ else if (lua_isstring(L, 2)) {
+ char buf[4096];
+ struct passwd passwd, *pwd;
+
+ error = getpwnam_r(lua_tostring(L, 2), &passwd,
+ buf, sizeof(buf), &pwd);
+ if (error == 0)
+ owner = pwd->pw_uid;
+ else
+ return (luaL_argerror(L, 2,
+ lua_pushfstring(L, "unknown user %s",
+ lua_tostring(L, 2))));
+ } else if (!lua_isnoneornil(L, 2)) {
+ const char *type = luaL_typename(L, 2);
+ return (luaL_argerror(L, 2,
+ lua_pushfstring(L, "integer or string expected, got %s",
+ type)));
+ }
+
+ if (lua_isinteger(L, 3))
+ group = (gid_t)lua_tointeger(L, 3);
+ else if (lua_isstring(L, 3)) {
+ char buf[4096];
+ struct group gr, *grp;
+
+ error = getgrnam_r(lua_tostring(L, 3), &gr, buf, sizeof(buf),
+ &grp);
+ if (error == 0)
+ group = grp->gr_gid;
+ else
+ return (luaL_argerror(L, 3,
+ lua_pushfstring(L, "unknown group %s",
+ lua_tostring(L, 3))));
+ } else if (!lua_isnoneornil(L, 3)) {
+ const char *type = luaL_typename(L, 3);
+ return (luaL_argerror(L, 3,
+ lua_pushfstring(L, "integer or string expected, got %s",
+ type)));
+ }
+
+ if (chown(path, owner, group) == -1) {
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(errno));
+ lua_pushinteger(L, errno);
+ return (3);
+ }
+ lua_pushinteger(L, 0);
+ return (1);
+}
+
+static int
+lua_pclose(lua_State *L)
+{
+ int error, fd;
+
+ enforce_max_args(L, 1);
+
+ fd = luaL_checkinteger(L, 1);
+ if (fd < 0) {
+ error = EBADF;
+ goto err;
+ }
+
+ if (close(fd) == 0) {
+ lua_pushinteger(L, 0);
+ return (1);
+ }
+
+ error = errno;
+err:
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(error));
+ lua_pushinteger(L, error);
+ return (3);
+
+}
+
+static int
+lua_dup2(lua_State *L)
+{
+ int error, oldd, newd;
+
+ enforce_max_args(L, 2);
+
+ oldd = luaL_checkinteger(L, 1);
+ if (oldd < 0) {
+ error = EBADF;
+ goto err;
+ }
+
+ newd = luaL_checkinteger(L, 2);
+ if (newd < 0) {
+ error = EBADF;
+ goto err;
+ }
+
+ error = dup2(oldd, newd);
+ if (error >= 0) {
+ lua_pushinteger(L, error);
+ return (1);
+ }
+
+ error = errno;
+err:
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(error));
+ lua_pushinteger(L, error);
+ return (3);
+}
+
+static int
+lua_execp(lua_State *L)
+{
+ int argc, error;
+ const char *file;
+ const char **argv;
+
+ enforce_max_args(L, 2);
+
+ file = luaL_checkstring(L, 1);
+ luaL_checktype(L, 2, LUA_TTABLE);
+
+ lua_len(L, 2);
+ argc = lua_tointeger(L, -1);
+
+ /*
+ * Use lua_newuserdatauv() to allocate a scratch buffer that is tracked
+ * and freed by lua's GC. This avoid any chance of a leak if a lua error
+ * is raised later in this function (e.g. by luaL_argerror()).
+ * The (argc + 2) size gives enough space in the buffer for argv[0] and
+ * the terminating NULL.
+ */
+ argv = lua_newuserdatauv(L, (argc + 2) * sizeof(char *), 0);
+
+ /*
+ * Sequential tables in lua start at index 1 by convention.
+ * If there happens to be a string at index 0, use that to
+ * override the default argv[0]. This matches the lposix API.
+ */
+ lua_pushinteger(L, 0);
+ lua_gettable(L, 2);
+ argv[0] = lua_tostring(L, -1);
+ if (argv[0] == NULL) {
+ argv[0] = file;
+ }
+
+ for (int i = 1; i <= argc; i++) {
+ lua_pushinteger(L, i);
+ lua_gettable(L, 2);
+ argv[i] = lua_tostring(L, -1);
+ if (argv[i] == NULL) {
+ luaL_argerror(L, 2,
+ "argv table must contain only strings");
+ }
+ }
+ argv[argc + 1] = NULL;
+
+ execvp(file, (char **)argv);
+ error = errno;
+
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(error));
+ lua_pushinteger(L, error);
+ return (3);
+}
+
+static int
+lua_fnmatch(lua_State *L)
+{
+ const char *pattern, *string;
+ int flags;
+
+ enforce_max_args(L, 3);
+ pattern = luaL_checkstring(L, 1);
+ string = luaL_checkstring(L, 2);
+ flags = luaL_optinteger(L, 3, 0);
+
+ lua_pushinteger(L, fnmatch(pattern, string, flags));
+
+ return (1);
+}
+
+static int
+lua_uname(lua_State *L)
+{
+ struct utsname name;
+ int error;
+
+ enforce_max_args(L, 0);
+
+ error = uname(&name);
+ if (error != 0) {
+ error = errno;
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(error));
+ lua_pushinteger(L, error);
+ return (3);
+ }
+
+ lua_newtable(L);
+#define setkv(f) do { \
+ lua_pushstring(L, name.f); \
+ lua_setfield(L, -2, #f); \
+} while (0)
+ setkv(sysname);
+ setkv(nodename);
+ setkv(release);
+ setkv(version);
+ setkv(machine);
+#undef setkv
+
+ return (1);
+}
+
+static int
+lua_dirname(lua_State *L)
+{
+ char *inpath, *outpath;
+
+ enforce_max_args(L, 1);
+
+ inpath = strdup(luaL_checkstring(L, 1));
+ if (inpath == NULL) {
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(ENOMEM));
+ lua_pushinteger(L, ENOMEM);
+ return (3);
+ }
+
+ outpath = dirname(inpath);
+ lua_pushstring(L, outpath);
+ free(inpath);
+ return (1);
+}
+
+static int
+lua_fork(lua_State *L)
+{
+ pid_t pid;
+
+ enforce_max_args(L, 0);
+
+ pid = fork();
+ if (pid < 0) {
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(errno));
+ lua_pushinteger(L, errno);
+ return (3);
+ }
+
+ lua_pushinteger(L, pid);
+ return (1);
+}
+
+static int
+lua_getpid(lua_State *L)
+{
+ enforce_max_args(L, 0);
+
+ lua_pushinteger(L, getpid());
+ return (1);
+}
+
+static int
+lua_pipe(lua_State *L)
+{
+ int error, fd[2];
+
+ enforce_max_args(L, 0);
+
+ error = pipe(fd);
+ if (error != 0) {
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(errno));
+ lua_pushinteger(L, errno);
+ return (1);
+ }
+
+ lua_pushinteger(L, fd[0]);
+ lua_pushinteger(L, fd[1]);
+ return (2);
+}
+
+static int
+lua_read(lua_State *L)
+{
+ char *buf;
+ ssize_t ret;
+ size_t sz;
+ int error, fd;
+
+ enforce_max_args(L, 2);
+ fd = luaL_checkinteger(L, 1);
+ sz = luaL_checkinteger(L, 2);
+
+ if (fd < 0) {
+ error = EBADF;
+ goto err;
+ }
+
+ buf = malloc(sz);
+ if (buf == NULL)
+ goto err;
+
+ /*
+ * For 0-byte reads, we'll still push the empty string and let the
+ * caller deal with EOF to match lposix semantics.
+ */
+ ret = read(fd, buf, sz);
+ if (ret >= 0)
+ lua_pushlstring(L, buf, ret);
+ else if (ret < 0)
+ error = errno; /* Save to avoid clobber by free() */
+
+ free(buf);
+ if (error != 0)
+ goto err;
+
+ /* Just the string pushed. */
+ return (1);
+err:
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(error));
+ lua_pushinteger(L, error);
+ return (3);
+}
+
+static int
+lua_realpath(lua_State *L)
+{
+ const char *inpath;
+ char *outpath;
+
+ enforce_max_args(L, 1);
+ inpath = luaL_checkstring(L, 1);
+
+ outpath = realpath(inpath, NULL);
+ if (outpath == NULL) {
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(errno));
+ lua_pushinteger(L, errno);
+ return (3);
+ }
+
+ lua_pushstring(L, outpath);
+ free(outpath);
+ return (1);
+}
+
+static int
+lua_wait(lua_State *L)
+{
+ pid_t pid;
+ int options, status;
+
+ enforce_max_args(L, 2);
+ pid = luaL_optinteger(L, 1, -1);
+ options = luaL_optinteger(L, 2, 0);
+
+ status = 0;
+ pid = waitpid(pid, &status, options);
+ if (pid < 0) {
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(errno));
+ lua_pushinteger(L, errno);
+ return (3);
+ }
+
+ lua_pushinteger(L, pid);
+ if (pid == 0) {
+ lua_pushliteral(L, "running");
+ return (2);
+ }
+
+ if (WIFCONTINUED(status)) {
+ lua_pushliteral(L, "continued");
+ return (2);
+ } else if(WIFSTOPPED(status)) {
+ lua_pushliteral(L, "stopped");
+ lua_pushinteger(L, WSTOPSIG(status));
+ return (3);
+ } else if (WIFEXITED(status)) {
+ lua_pushliteral(L, "exited");
+ lua_pushinteger(L, WEXITSTATUS(status));
+ return (3);
+ } else if (WIFSIGNALED(status)) {
+ lua_pushliteral(L, "killed");
+ lua_pushinteger(L, WTERMSIG(status));
+ return (3);
+ }
+
+ return (1);
+}
+
+static int
+lua_write(lua_State *L)
+{
+ const char *buf;
+ size_t bufsz, sz;
+ ssize_t ret;
+ off_t offset;
+ int error, fd;
+
+ enforce_max_args(L, 4);
+
+ fd = luaL_checkinteger(L, 1);
+ if (fd < 0) {
+ error = EBADF;
+ goto err;
+ }
+
+ buf = luaL_checkstring(L, 2);
+
+ bufsz = lua_rawlen(L, 2);
+ sz = luaL_optinteger(L, 3, bufsz);
+
+ offset = luaL_optinteger(L, 4, 0);
+
+
+ if ((size_t)offset > bufsz || offset + sz > bufsz) {
+ lua_pushnil(L);
+ lua_pushfstring(L,
+ "write: invalid access offset %zu, size %zu in a buffer size %zu",
+ offset, sz, bufsz);
+ lua_pushinteger(L, EINVAL);
+ return (3);
+ }
+
+ ret = write(fd, buf + offset, sz);
+ if (ret < 0) {
+ error = errno;
+ goto err;
+ }
+
+ lua_pushinteger(L, ret);
+ return (1);
+err:
+ lua_pushnil(L);
+ lua_pushstring(L, strerror(error));
+ lua_pushinteger(L, error);
+ return (3);
+}
+
+#define REG_DEF(n, func) { #n, func }
+#define REG_SIMPLE(n) REG_DEF(n, lua_ ## n)
+static const struct luaL_Reg libgenlib[] = {
+ REG_SIMPLE(basename),
+ REG_SIMPLE(dirname),
+ { NULL, NULL },
+};
+
+static const struct luaL_Reg stdliblib[] = {
+ REG_SIMPLE(realpath),
+ { NULL, NULL },
+};
+
+static const struct luaL_Reg fnmatchlib[] = {
+ REG_SIMPLE(fnmatch),
+ { NULL, NULL },
+};
+
+static const struct luaL_Reg sys_statlib[] = {
+ REG_SIMPLE(chmod),
+ { NULL, NULL },
+};
+
+static const struct luaL_Reg sys_utsnamelib[] = {
+ REG_SIMPLE(uname),
+ { NULL, NULL },
+};
+
+static const struct luaL_Reg sys_waitlib[] = {
+ REG_SIMPLE(wait),
+ {NULL, NULL},
+};
+
+static const struct luaL_Reg unistdlib[] = {
+ REG_SIMPLE(_exit),
+ REG_SIMPLE(chown),
+ REG_DEF(close, lua_pclose),
+ REG_SIMPLE(dup2),
+ REG_SIMPLE(execp),
+ REG_SIMPLE(fork),
+ REG_SIMPLE(getpid),
+ REG_SIMPLE(pipe),
+ REG_SIMPLE(read),
+ REG_SIMPLE(write),
+ { NULL, NULL },
+};
+
+#undef REG_SIMPLE
+#undef REG_DEF
+
+static int
+luaopen_posix_libgen(lua_State *L)
+{
+ luaL_newlib(L, libgenlib);
+ return (1);
+}
+
+static int
+luaopen_posix_stdlib(lua_State *L)
+{
+ luaL_newlib(L, stdliblib);
+ return (1);
+}
+
+static int
+luaopen_posix_fnmatch(lua_State *L)
+{
+ luaL_newlib(L, fnmatchlib);
+
+#define setkv(f) do { \
+ lua_pushinteger(L, f); \
+ lua_setfield(L, -2, #f); \
+} while (0)
+ setkv(FNM_PATHNAME);
+ setkv(FNM_NOESCAPE);
+ setkv(FNM_NOMATCH);
+ setkv(FNM_PERIOD);
+#undef setkv
+
+ return 1;
+}
+
+static int
+luaopen_posix_sys_stat(lua_State *L)
+{
+ luaL_newlib(L, sys_statlib);
+ return (1);
+}
+
+static int
+luaopen_posix_sys_utsname(lua_State *L)
+{
+ luaL_newlib(L, sys_utsnamelib);
+ return 1;
+}
+
+static int
+luaopen_posix_sys_wait(lua_State *L)
+{
+ luaL_newlib(L, sys_waitlib);
+
+#define lua_pushflag(L, flag) do { \
+ lua_pushinteger(L, flag); \
+ lua_setfield(L, -2, #flag); \
+} while(0)
+
+ /* Only these two exported by lposix */
+ lua_pushflag(L, WNOHANG);
+ lua_pushflag(L, WUNTRACED);
+
+ lua_pushflag(L, WCONTINUED);
+ lua_pushflag(L, WSTOPPED);
+#ifdef WTRAPPED
+ lua_pushflag(L, WTRAPPED);
+#endif
+ lua_pushflag(L, WEXITED);
+ lua_pushflag(L, WNOWAIT);
+#undef lua_pushflag
+
+ return (1);
+}
+
+static int
+luaopen_posix_unistd(lua_State *L)
+{
+ luaL_newlib(L, unistdlib);
+ return (1);
+}
+
+int
+luaopen_posix(lua_State *L)
+{
+ lua_newtable(L); /* posix */
+
+ luaL_requiref(L, "posix.fnmatch", luaopen_posix_fnmatch, 0);
+ lua_setfield(L, -2, "fnmatch");
+
+ luaL_requiref(L, "posix.libgen", luaopen_posix_libgen, 0);
+ lua_setfield(L, -2, "libgen");
+
+ luaL_requiref(L, "posix.stdlib", luaopen_posix_stdlib, 0);
+ lua_setfield(L, -2, "stdlib");
+
+ lua_newtable(L); /* posix.sys */
+ luaL_requiref(L, "posix.sys.stat", luaopen_posix_sys_stat, 0);
+ lua_setfield(L, -2, "stat");
+ luaL_requiref(L, "posix.sys.utsname", luaopen_posix_sys_utsname, 0);
+ lua_setfield(L, -2, "utsname");
+ luaL_requiref(L, "posix.sys.wait", luaopen_posix_sys_wait, 0);
+ lua_setfield(L, -2, "wait");
+ lua_setfield(L, -2, "sys");
+
+ luaL_requiref(L, "posix.unistd", luaopen_posix_unistd, 0);
+ lua_setfield(L, -2, "unistd");
+
+ return (1);
+}
diff --git a/libexec/flua/modules/lposix.h b/libexec/flua/modules/lposix.h
new file mode 100644
index 000000000000..1aa33f042571
--- /dev/null
+++ b/libexec/flua/modules/lposix.h
@@ -0,0 +1,10 @@
+/*-
+ *
+ * This file is in the public domain.
+ */
+
+#pragma once
+
+#include <lua.h>
+
+int luaopen_posix(lua_State *L);