Compare commits

...

3 Commits

Author SHA1 Message Date
dan 15aad21535 add README 2026-05-08 23:27:00 +01:00
dan 9d91c35cae set up arg array in lua 2026-05-08 23:04:02 +01:00
dan 56c7d48e65 remove hard tabs 2026-05-08 22:33:15 +01:00
2 changed files with 134 additions and 96 deletions
+35
View File
@@ -0,0 +1,35 @@
# luad
A server that provides pre-warmed Lua VMs to clients connecting over
unix sockets
* no waiting for the interpeter to load
* share read-only pages between Lua processes
Lua is small enough that this is probably meaningless on any
reasonably powerful computer, but conversely: Lua is useful on
computers that are unreasonably feeble. This utility was written for
Liminix, but it may be useful elsewhere too.
## what it does
* start Lua
* accept unix datagram socket commands
* fork a child for each peer socket and run the requested lua script
## small print
* WIP. Not finished. Use at own risk, etc
* it only works on Linux (or if there are other OSes with SCM_RIGHTS
to send unix file descriptors across processes, then maybe it works
on them as well)
## TO DO
[.] run a script in the Lua vm before forking (e.g. to preload
libraries that you want available in every process)
[.] packaging
+99 -96
View File
@@ -33,50 +33,58 @@ static mut sigchild_fd : i32 = -1;
extern "C" fn handle_sigchld(_: libc::c_int) {
unsafe {
if sigchild_fd >= 0 {
let _ = libc::write(
sigchild_fd,
"\n".as_ptr() as * const c_void,
1);
}
if sigchild_fd >= 0 {
let _ = libc::write(
sigchild_fd,
"\n".as_ptr() as * const c_void,
1);
}
}
}
fn fork_lua(lua : &Lua, args: Vec<String>, fds: Vec<i32>) -> Option<i32> {
let pathname = &args[0];
let path = Path::new(pathname);
match fork() {
Ok(Fork::Parent(pid)) => Some(pid),
Ok(Fork::Child) => {
for (i, fd) in fds.iter().enumerate() {
unsafe { libc::dup2(*fd, i.try_into().unwrap()); }
};
let chunk = lua.load(path);
match chunk.exec() {
Ok(()) => { exit(0); },
Err(e) => {
println!("lua error {:?}", e);
exit(1);
},
};
},
Err(e) => {
println!("fork error {:?}", e);
None
}
Ok(Fork::Parent(pid)) => Some(pid),
Ok(Fork::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());
}
globals.set("arg".to_string(), arg_table);
}
let chunk = lua.load(path);
match chunk.exec() {
Ok(()) => { exit(0); },
Err(e) => {
println!("lua error {:?}", e);
exit(1);
},
};
},
Err(e) => {
println!("fork error {:?}", e);
None
}
}
}
fn read_ancillary_data(ancillary : SocketAncillary<'_>) -> Vec<i32> {
let mut fds : Vec<i32> = Vec::new();
for ancillary_result in ancillary.messages() {
if let AncillaryData::ScmRights(scm_rights) = ancillary_result.unwrap() {
for fd in scm_rights {
println!("receive file descriptor: {fd}");
fds.push(fd);
}
}
if let AncillaryData::ScmRights(scm_rights) = ancillary_result.unwrap() {
for fd in scm_rights {
println!("receive file descriptor: {fd}");
fds.push(fd);
}
}
}
fds
}
@@ -84,9 +92,9 @@ fn read_ancillary_data(ancillary : SocketAncillary<'_>) -> Vec<i32> {
fn split_command(buf : &[u8]) -> Vec<String> {
let mut words : Vec<String> = Vec::new();
for w in buf.split(|e| *e == 0) {
if let Ok(s) = str::from_utf8(w) {
words.push(s.to_string());
}
if let Ok(s) = str::from_utf8(w) {
words.push(s.to_string());
}
}
words
}
@@ -94,16 +102,16 @@ fn split_command(buf : &[u8]) -> Vec<String> {
fn main() -> std::io::Result<()> {
let Ok(sock_path) = env::var("LUAD_SOCKET_PATH") else {
panic!("need to set LUAD_SOCKET_PATH env var");
panic!("need to set LUAD_SOCKET_PATH env var");
};
let Ok((mut sigchild_reader, sigchild_writer)) = std::io::pipe() else {
panic!("failed to open pipe for sigchild")
panic!("failed to open pipe for sigchild")
};
unsafe {
sigchild_fd = sigchild_writer.as_raw_fd();
libc::signal(libc::SIGCHLD, handle_sigchld as libc::sighandler_t);
sigchild_fd = sigchild_writer.as_raw_fd();
libc::signal(libc::SIGCHLD, handle_sigchld as libc::sighandler_t);
};
let lua = unsafe { Lua::unsafe_new() };
@@ -112,65 +120,60 @@ fn main() -> std::io::Result<()> {
let mut peers : HashMap<i32, SocketAddr> = HashMap::new();
loop {
let mut buf = [0u8; 1024];
let mut bufs = [
IoSliceMut::new(&mut buf),
];
let mut ancillary_buffer = [0; 128];
let mut ancillary = SocketAncillary::new(&mut ancillary_buffer[..]);
let mut buf = [0u8; 1024];
let mut bufs = [
IoSliceMut::new(&mut buf),
];
let mut ancillary_buffer = [0; 128];
let mut ancillary = SocketAncillary::new(&mut ancillary_buffer[..]);
let mut pollfds = [
pollfd {
fd: sock.as_raw_fd(),
events: POLLIN,
revents: 0,
},
pollfd {
fd: sigchild_reader.as_raw_fd(),
events: POLLIN,
revents: 0,
}
];
let n_ready = unsafe { poll(pollfds.as_mut_ptr(), 2, 10000) };
if n_ready > 0 {
if pollfds[0].revents > 0 {
match sock.recv_vectored_with_ancillary_from(&mut bufs, &mut ancillary) {
Ok((size, _truncated, peer)) => {
let fds = read_ancillary_data(ancillary);
let args = split_command(&buf[..size-1]);
println!("args {:?} from {:?}", args, peer);
if let Some(child) = fork_lua(&lua, args, fds) {
println!("forked {child}");
peers.insert(child, peer);
}
let mut pollfds = [
pollfd {
fd: sock.as_raw_fd(),
events: POLLIN,
revents: 0,
},
pollfd {
fd: sigchild_reader.as_raw_fd(),
events: POLLIN,
revents: 0,
}
];
let n_ready = unsafe { poll(pollfds.as_mut_ptr(), 2, 10000) };
if n_ready > 0 {
if pollfds[0].revents > 0 {
match sock.recv_vectored_with_ancillary_from(&mut bufs, &mut ancillary) {
Ok((size, _truncated, peer)) => {
let fds = read_ancillary_data(ancillary);
let args = split_command(&buf[..size-1]);
println!("args {:?} from {:?}", args, peer);
if let Some(child) = fork_lua(&lua, args, fds) {
println!("forked {child}");
peers.insert(child, peer);
}
},
Err(e) => {
println!("error reading: {:?}", e);
}
}
};
if pollfds[1].revents > 0 {
let mut buf = [0u8;1];
let _ = sigchild_reader.read(&mut buf);
if let Ok(wstatus) = wait() {
match wstatus {
Exited(pid, code) => {
if let Some(addr) = peers.get(&pid.as_raw()) {
sock.send_to_addr(&code.to_be_bytes(), addr);
}
},
Signaled(pid, sig, _) => {
if let Some(addr) = peers.get(&pid.as_raw()) {
let ret : u32 = sig as u32 + 128;
sock.send_to_addr(&ret.to_be_bytes(), addr);
}
},
_ => {}
}
};
}
}
},
Err(e) => {
println!("error reading: {:?}", e);
}
}
};
if pollfds[1].revents > 0 {
let mut buf = [0u8;1];
let _ = sigchild_reader.read(&mut buf);
if let Ok(wstatus) = wait() {
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
} {
if let Some(addr) = peers.get(&pid.as_raw()) {
sock.send_to_addr(&ret.to_be_bytes(), addr);
}
}
};
}
}
}
}