Compare commits

...

4 Commits

Author SHA1 Message Date
dan 9b7832a44d WIP rewrite in C
$ ls -l result/bin/luad src/luad
-r-xr-xr-x 1 root root  415032 Jan  1  1970 result/bin/luad
-rwxr-xr-x 1 dan  users  21872 May 14 23:18 src/luad

it's difficult to argue against that size saving, and we weren't
doing much except calling libraries written in C anyway

this is WIP as in it's functional mostly but it's a first pass, may
contain bugs, needs lint, hardcodes the socket path
2026-05-14 23:20:11 +01:00
dan 713d30f9a2 fix most compiler warnings 2026-05-13 19:06:38 +01:00
dan 0a5d7f83f6 remove fork and nix crates, use libc directly
doesn't seem to make any difference to binary size?
2026-05-13 19:02:48 +01:00
dan 4370b6ea99 luad allow preloading lua in parent process
if you have libaries that you require in every lua script, you can use
this to load them when luad starts so that they are already there (and
maybe sharing ram?) when clients connect.
2026-05-13 17:58:46 +01:00
4 changed files with 312 additions and 45 deletions
Generated
-29
View File
@@ -40,12 +40,6 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "either"
version = "1.15.0"
@@ -58,15 +52,6 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "fork"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bcc4b4161e53d499e41af904acb23950adf85682c772921ef3957cf1ecc98b3"
dependencies = [
"libc",
]
[[package]]
name = "libc"
version = "0.2.186"
@@ -86,10 +71,8 @@ dependencies = [
name = "luad"
version = "0.1.0"
dependencies = [
"fork",
"libc",
"mlua",
"nix",
]
[[package]]
@@ -126,18 +109,6 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "nix"
version = "0.31.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3"
dependencies = [
"bitflags",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]]
name = "num-traits"
version = "0.2.19"
-2
View File
@@ -4,7 +4,5 @@ version = "0.1.0"
edition = "2024"
[dependencies]
fork = "0.7.0"
libc = "0.2.186"
mlua = { version = "0.11.6", features = ["lua53"] }
nix = { version = "0.31.2", features = ["process"] }
+242
View File
@@ -0,0 +1,242 @@
#include <unistd.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/un.h>
#include <signal.h>
#include <sys/wait.h>
#include <poll.h>
#include <arpa/inet.h>
static int sigchild_fd = -1;
static void handle_sigchld(int num) {
if(sigchild_fd >= 0) {
write(sigchild_fd, "\n", 1);
}
}
int open_socket(char * pathname) {
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, pathname, sizeof(addr.sun_path) - 1);
int fd = socket(AF_LOCAL, SOCK_DGRAM, PF_LOCAL);
if(fd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}
if((bind(fd, (struct sockaddr *) &addr, sizeof addr)) < 0) {
perror("bind");
exit(EXIT_FAILURE);
}
return fd;
}
int * fds_from_msg(struct msghdr *msg, size_t peer_fds_count) {
int *peer_fds = (int *) calloc(peer_fds_count, sizeof(int));
for (struct cmsghdr* cmsg = CMSG_FIRSTHDR(msg);
cmsg != NULL;
cmsg = CMSG_NXTHDR(msg, cmsg)) {
if(cmsg->cmsg_level == SOL_SOCKET &&
cmsg->cmsg_type == SCM_RIGHTS) {
memcpy(peer_fds, CMSG_DATA(cmsg), peer_fds_count * sizeof(int));
}
}
return peer_fds;
}
int start_lua_child(lua_State *L,
char * command_line, int command_line_length,
int * fds, int fds_count) {
char * word = command_line;
char * command_line_end = command_line + command_line_length;
char * pathname = NULL;
int i = 0;
fflush(stdout); fflush(stderr);
for(int i=0; i < fds_count; i++) {
if(fds[i] > 0) dup2(fds[i], i);
}
lua_newtable(L); /* arg */
while(word < command_line_end) {
char * word_end = memchr(word, '\0', command_line_end - word);
if(!word_end) break;
if(!pathname) {
pathname = strndup(word, (word_end - word));
}
lua_pushlstring(L, word, (word_end - word));
lua_seti(L, -2, i++);
word = word_end + 1;
}
lua_setglobal(L, "arg");
int status = luaL_dofile(L, pathname);
if (status) {
fprintf(stderr, "Error: %s\n", lua_tostring(L, -1));
return 1;
} else {
return 0;
}
}
pid_t fork_lua(lua_State *L,
char * command_line, int command_line_length,
int * fds, int fds_count) {
pid_t pid = fork();
if(pid == 0) {
exit(start_lua_child(L, command_line, command_line_length,
fds, fds_count));
} else if(pid > 0) {
free(command_line);
return pid;
} else {
perror("fork");
return -1;
}
}
struct peer_entry {
pid_t pid;
int addrlen;
struct sockaddr_un addr;
};
int register_peer(struct peer_entry *table, pid_t pid, struct sockaddr_un *addr, int addrlen) {
if(addrlen > sizeof (struct sockaddr_un))
return -1;
struct peer_entry *p = table;
while(p->pid != 0 && p->pid != -1)
p++;
if(p->pid == -1)
return -1;
p->pid = pid;
p->addrlen = addrlen;
memcpy(&(p->addr), addr, addrlen);
}
struct peer_entry* find_peer(struct peer_entry *table, pid_t pid) {
while(table->pid != -1) {
if(table->pid == pid)
return table;
table++;
}
return NULL;
}
int main(int argc, char **argv)
{
int status;
lua_State *L = luaL_newstate();
luaL_openlibs(L);
int sigpipe_fds[2];
struct peer_entry * peer_table = calloc(501, sizeof (struct peer_entry));
peer_table[500].pid = -1;
if(pipe(sigpipe_fds) < 0) {
perror("pipe");
exit(EXIT_FAILURE);
}
sigchild_fd = sigpipe_fds[1];
signal(SIGCHLD, handle_sigchld);
if (argc >= 2) {
if (!access(argv[1], F_OK) == 0) {
fprintf(stderr, "File %s does not exist!\n", argv[1]);
exit(1);
}
status = luaL_dofile(L, argv[1]);
if (status) {
fprintf(stderr, "Error: %s\n", lua_tostring(L, -1));
return 1;
}
}
int socket = open_socket("/tmp/mysock"); /* FIXME hardcoded */
struct sockaddr_un peer_addr;
socklen_t peer_addr_len;
char buf[1025];
struct iovec iov[1];
iov[0].iov_base = buf;
iov[0].iov_len = sizeof buf;
struct cmsghdr* cmsg = (struct cmsghdr *) calloc(1000, 1);
struct msghdr msg = {
.msg_name = &peer_addr,
.msg_namelen = sizeof peer_addr,
.msg_iov = iov,
.msg_iovlen = 1,
.msg_control = cmsg,
.msg_controllen = 1000
};
struct pollfd pollfds[2] = {
{
.fd = socket,
.events = POLLIN,
.revents = 0
},
{
.fd = sigpipe_fds[0],
.events = POLLIN,
.revents = 0
},
};
while(1) {
if(poll(pollfds, 2, 10000) == 0)
continue;
if(pollfds[0].revents) {
int len = recvmsg(socket, &msg , 0);
int *peer_fds = fds_from_msg(&msg, 5);
char *argline = malloc(len);
memmove(argline, buf, len);
pid_t child_pid = fork_lua(L, argline, len, peer_fds, 5);
if(child_pid >= 0) {
register_peer(peer_table, child_pid, &peer_addr, msg.msg_namelen);
}
}
if(pollfds[1].revents) {
pid_t pid;
int wstatus;
read(pollfds[1].fd, buf, sizeof buf);
while((pid=waitpid(0, &wstatus, WNOHANG)) > 0) {
struct peer_entry *peer = find_peer(peer_table, pid);
if(peer) {
uint32_t ret = 0;
if(WIFEXITED(wstatus)) {
ret = htonl(WEXITSTATUS(wstatus));
} else if(WIFSIGNALED(wstatus)) {
ret = htonl(128 + WTERMSIG(wstatus));
}
sendto(socket, (void *) &ret, 4, 0,
(const struct sockaddr *) &(peer->addr),
peer->addrlen);
peer->pid = 0;
}
}
}
}
return 0;
}
+70 -14
View File
@@ -23,11 +23,56 @@ use libc::{
pollfd,
poll,
POLLIN,
c_void
};
use nix::sys::wait::{wait,WaitStatus::{Exited,Signaled}};
c_void,
use fork::{Fork, fork};
// fork,
pid_t,
// wait,
c_int,
WIFEXITED,
WIFSIGNALED,
WEXITSTATUS,
WTERMSIG
};
enum WaitStatus {
Exited(pid_t, c_int),
Signaled(pid_t, c_int),
}
use crate::WaitStatus::*;
enum Forked {
Parent(pid_t),
Child,
Err(std::io::Error)
}
fn fork() -> Forked {
let child_pid = unsafe { libc::fork() };
if child_pid > 0 {
Forked::Parent(child_pid)
} else if child_pid == 0 {
Forked::Child
} else {
Forked::Err(std::io::Error::last_os_error())
}
}
fn wait() -> Result<WaitStatus, std::io::Error> {
let mut wstatus : c_int = 0;
let pid = unsafe { libc::wait(&mut wstatus) };
if pid >= 0 {
if WIFEXITED(wstatus) {
return Ok(WaitStatus::Exited(pid, WEXITSTATUS(wstatus)))
} else if WIFSIGNALED(wstatus) {
return Ok(WaitStatus::Signaled(pid, WTERMSIG(wstatus)))
}
}
Err(std::io::Error::last_os_error())
}
static mut sigchild_fd : i32 = -1;
@@ -47,17 +92,17 @@ fn fork_lua(lua : &Lua, args: Vec<String>, fds: Vec<i32>) -> Option<i32> {
let path = Path::new(pathname);
match fork() {
Ok(Fork::Parent(pid)) => Some(pid),
Ok(Fork::Child) => {
Forked::Parent(pid) => Some(pid),
Forked::Child => {
for (i, fd) in fds.iter().enumerate() {
unsafe { libc::dup2(*fd, i.try_into().unwrap()); }
};
let globals = lua.globals();
if let Ok(arg_table) = lua.create_table() {
for (i, arg) in args.iter().enumerate() {
arg_table.set(i, arg.clone());
arg_table.set(i, arg.clone()).expect("arg set failed");
}
globals.set("arg".to_string(), arg_table);
globals.set("arg".to_string(), arg_table).expect("arg set failed");
}
let chunk = lua.load(path);
@@ -69,7 +114,7 @@ fn fork_lua(lua : &Lua, args: Vec<String>, fds: Vec<i32>) -> Option<i32> {
},
};
},
Err(e) => {
Forked::Err(e) => {
println!("fork error {:?}", e);
None
}
@@ -111,10 +156,22 @@ fn main() -> std::io::Result<()> {
unsafe {
sigchild_fd = sigchild_writer.as_raw_fd();
libc::signal(libc::SIGCHLD, handle_sigchld as libc::sighandler_t);
libc::signal(libc::SIGCHLD, handle_sigchld as *const () as libc::sighandler_t);
};
let lua = unsafe { Lua::unsafe_new() };
let mut argv = env::args();
argv.next();
if let Some(prelude) = argv.next() {
let path = Path::new(&prelude);
println!("preloading {:?}", path);
lua.load(path).exec().expect("load prelude failed");
} else {
println!("no preload");
}
let sock = UnixDatagram::bind(sock_path)?;
let mut peers : HashMap<i32, SocketAddr> = HashMap::new();
@@ -165,11 +222,10 @@ fn main() -> std::io::Result<()> {
if let Some((pid, ret)) =
match wstatus {
Exited(pid, code) => Some((pid, code as u32)),
Signaled(pid, sig, _) => Some((pid, (sig as u32) + 128)),
_ => None
Signaled(pid, sig) => Some((pid, (sig as u32) + 128))
} {
if let Some(addr) = peers.get(&pid.as_raw()) {
sock.send_to_addr(&ret.to_be_bytes(), addr);
if let Some(addr) = peers.get(&pid) {
let _ = sock.send_to_addr(&ret.to_be_bytes(), addr);
}
}
};