Lunatik is a framework for scripting the Linux kernel with Lua. It is composed by the Lua interpreter modified to run in the kernel; a device driver (written in Lua =)) and a command line tool to load and run scripts and manage runtime environments from the user space; a C API to load and run scripts and manage runtime environments from the kernel; and Lua APIs for binding kernel facilities to Lua scripts. Lunatik also offers a shell script as a helper for managing its kernel modules.
Here is an example of a character device driver written in Lua using Lunatik to generate random ASCII printable characters:
-- /lib/modules/lua/passwd.lua
--
-- implements /dev/passwd for generate passwords
-- usage: $ sudo sbin/lunatik --run passwd
-- $ head -c <width> /dev/passwd
local device = require("device")
local linux = require("linux")
local function nop() end -- do nothing
local s = linux.stat
local driver = {name = "passwd", open = nop, release = nop, mode = s.IRUGO}
function driver:read() -- read(2) callback
-- generate random ASCII printable characters
return string.char(linux.random(32, 126))
end
-- creates a new character device
device.new(driver)make
sudo sbin/lunatik.sh install # copy lunatik.lua to /lib/modules/lua
sudo sbin/lunatik.sh start # load Lunatik kernel modules
sudo sbin/lunatik.sh run # execute sbin/lunatik REPL
Lunatik 3.0 Copyright (C) 2023 ring-0 Ltda.
> return 42 -- execute this line in the kernel
42
usage: sbin/lunatik [[--run] <script>] [--stop <script>]--run: create a new runtime environment to run the script/lib/modules/lua/<script>.lua--stop: stop the runtime environment created to run the script<script>default: start a REPL (Read–Eval–Print Loop)
usage: sbin/lunatik.sh {start|stop|restart|status|install|run}
start: load Lunatik kernel modulesstop: remove Lunatik kernel modulesrestart: reload Lunatik kernel modulesstatus: show what Lunatik kernel modules are currently loadedinstall: copylunatik.luadevice driver to/lib/modules/luarun: executesbin/lunatikREPL
Lunatik 3.0 is based on Lua 5.4 adapted to run in the kernel.
Lunatik does not support floating-point arithmetic,
thus it does not support __div nor __pow
metamethods
and the type number has only the subtype integer.
Lunatik does not support both io and os libraries, and the given identifiers from the following libraries:
- debug.debug, math.acos, math.asin, math.atan, math.ceil, math.cos, math.deg, math.exp, math.floor, math.fmod, math.huge. math.log, math.modf, math.pi, math.rad, math.random, math.randomseed, math.sin, math.sqrt, math.tan, math.type, package.cpath.
Lunatik modifies the following identifiers:
- _VERSION: is defined as
"Lua 5.4-kernel". - collectgarbage("count"): returns the total memory in use by Lua in bytes, instead of Kbytes.
- package.path: is defined as
"/lib/modules/lua/?.lua;/lib/modules/lua/?/init.lua". - require: only supports built-in or already linked C modules, that is, Lunatik cannot load kernel modules dynamically.
Lunatik does not support luaL_Stream, luaL_execresult, luaL_fileresult, luaopen_io and luaopen_os.
Lunatik modifies luaL_openlibs to remove luaopen_io and luaopen_os.
#include <lunatik.h>int lunatik_runtime(lunatik_runtime_t **pruntime, const char *script, bool sleep);lunatik_runtime() creates a new runtime environment then loads and runs the script
/lib/modules/lua/<script>.lua as the entry point for this environment.
It must only be called from process context.
The runtime environment is composed by
a Lua state,
a lock type and
a reference counter.
If sleep is true, it will use a mutex
for locking the runtime environment and the
GFP_KERNEL
flag for allocating new memory later on on
lunatik_run() calls.
Otherwise, it will use a spinlock and GFP_ATOMIC.
lunatik_runtime() opens the Lua standard libraries
present on Lunatik
and, if sleep is true, it also opens the
lunatik
library to make them available for the script.
If successful, lunatik_runtime() sets the address pointed by pruntime and
Lua's extra space
with a pointer for the new created runtime environment,
sets the reference counter to 1 and then returns 0.
Otherwise, it returns -ENOMEM, if insufficient memory is available;
or -EINVAL, if it fails to load or run the script.
-- /lib/modules/lua/mydevice.lua
function myread(len, off)
return "42"
endstatic lunatik_runtime_t *runtime;
static int __init mydevice_init(void)
{
return lunatik_runtime(&runtime, "mydevice", true);
}int lunatik_stop(lunatik_runtime_t *runtime);lunatik_stop()
closes
the
Lua state
created for this runtime environment and decrements the
reference counter.
Once the reference counter is decremented to zero, the
lock type
and the memory allocated for the runtime environment are released.
If the runtime environment has been released, it returns 1;
otherwise, it returns 0.
void lunatik_run(lunatik_runtime_t *runtime, <inttype> (*handler)(...), <inttype> &ret, ...);lunatik_run() locks the runtime environment and calls the handler
passing the associated Lua state as the first argument followed by the variadic arguments.
If the Lua state has been closed, ret is set with -ENXIO;
otherwise, ret is set with the result of handler(L, ...) call.
Then, it restores the Lua stack and unlocks the runtime environment.
It is defined as a macro.
static int l_read(lua_State *L, char *buf, size_t len, loff_t *off)
{
size_t llen;
const char *lbuf;
lua_getglobal(L, "myread");
lua_pushinteger(L, len);
lua_pushinteger(L, *off);
if (lua_pcall(L, 2, 2, 0) != LUA_OK) { /* calls myread(len, off) */
pr_err("%s\n", lua_tostring(L, -1));
return -ECANCELED;
}
lbuf = lua_tolstring(L, -2, &llen);
llen = min(len, llen);
if (copy_to_user(buf, lbuf, llen) != 0)
return -EFAULT;
*off = (loff_t)luaL_optinteger(L, -1, *off + llen);
return (ssize_t)llen;
}
static ssize_t mydevice_read(struct file *f, char *buf, size_t len, loff_t *off)
{
ssize_t ret;
lunatik_runtime_t *runtime = (lunatik_runtime_t *)f->private_data;
lunatik_run(runtime, l_read, ret, buf, len, off);
return ret;
}void lunatik_get(lunatik_runtime_t *runtime);lunatik_get() increments the
reference counter
of this runtime environment.
int lunatik_put(lunatik_runtime_t *runtime);lunatik_put() decrements the
reference counter
of this runtime environment.
If the runtime environment has been released, it returns 1;
otherwise, it returns 0.
lunatik_runtime_t *lunatik_toruntime(lua_State *L);lunatik_toruntime() returns the runtime environment referenced by the L's
extra space.
The lunatik library provides support to load and run scripts and manage runtime environments from Lua.
lunatik.runtime() creates a new
runtime environment
then loads and runs the script
/lib/modules/lua/<script>.lua as the entry point for this environment.
It returns a
userdata
representing the runtime environment.
If sleep is true or omitted, it will use a mutex
and
GFP_KERNEL;
otherwise, it will use a spinlock and GFP_ATOMIC.
lunatik.runtime() opens the Lua standard libraries
present on Lunatik,
but differently from
lunatik_runtime(),
it does not open the lunatik library.
lunatik.stop()
stops
the runtime environment and clear its reference from the runtime
userdata.
The device library provides support for writting
character device drivers
in Lua.
device.new() returns a new device
userdata
and installs its driver in the system.
The driver must be defined as a table containing the following field:
name: string defining the device name; it is used for creating the device file (e.g.,/dev/<name>).
The driver table might optionally contain the following fields:
read: callback function to handle the read operation on the device file. It receives thedrivertable as the first argument followed by two integers, thelengthto be read and the fileoffset. It should return a string and, optionally, theupdated offset. If the length of the returned string is greater than the requestedlength, the string will be corrected to thatlength. If theupdated offsetis not returned, theoffsetwill be updated withoffset + length.write: callback function to handle the write operation on the device file. It receives thedrivertable as the first argument followed by the string to be written and an integer as the fileoffset. It might return optionally the writtenlengthfollowed by theupdated offset. If the returned length is greater than the requestedlength, the returned length will be corrected. If theupdated offsetis not returned, theoffsetwill be updated withoffset + length.open: callback function to handle the open operation on the device file. It receives thedrivertable and it is expected to return nothing.release: callback function to handle the release operation on the device file. It receives thedrivertable and it is expected to return nothing.mode: an integer specifying the device file mode.
If an operation callback is not defined, the device returns -ENXIO to VFS on its access.
device.delete() removes a device driver specified by the dev
userdata
from the system.
The linux library provides support for some Linux kernel facilities.
linux.random() mimics the behavior of math.random, but binding <linux/random.h>'s get_random_u32() and get_random_u64() APIs.
When called without arguments,
produces an integer with all bits (pseudo)random.
When called with two integers m and n,
linux.random() returns a pseudo-random integer with uniform distribution in the range [m, n].
The call math.random(n), for a positive n, is equivalent to math.random(1, n).
linux.stat is a table that exports <linux/stat.h> integer flags to Lua.
"IRWXUGO": permission to read, write and execute for user, group and other."IRUGO": permission only to read for user, group and other."IWUGO": permission only to write for user, group and other."IXUGO": permission only to execute for user, group and other.