diff options
Diffstat (limited to 'libexec/flua')
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(¶ms); + 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(¶ms[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(¶ms[0], "name") == -1) { + free(params); + return (luaL_error(L, "jailparam_init: %s", + jail_errmsg)); + } + name = lua_tostring(L, 1); + if (jailparam_import(¶ms[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(¶ms[0], "jid") == -1) { + free(params); + return (luaL_error(L, "jailparam_init: %s", + jail_errmsg)); + } + jid = lua_tointeger(L, 1); + if (jailparam_import_raw(¶ms[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, ¶ms_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(¶ms[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(¶ms[0], "name") == -1) { + free(params); + return (luaL_error(L, "jailparam_init: %s", + jail_errmsg)); + } + name = lua_tostring(L, 1); + if (jailparam_import(¶ms[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(¶ms[0], "jid") == -1) { + free(params); + return (luaL_error(L, "jailparam_init: %s", + jail_errmsg)); + } + jid = lua_tointeger(L, 1); + if (jailparam_import_raw(¶ms[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(¶ms[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(¶ms[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); |