diff options
Diffstat (limited to 'mom.lua')
-rw-r--r-- | mom.lua | 133 |
1 files changed, 133 insertions, 0 deletions
diff --git a/mom.lua b/mom.lua new file mode 100644 index 0000000..86bfacc --- /dev/null +++ b/mom.lua @@ -0,0 +1,133 @@ +-- a cqueues utility for handling a bunch of editor processes +-- it runs in the same process as the main program to make it easier +-- to pass around lua data. as a result of this, it greedily blocks +-- SIGCHILD; since this is the only time we make subprocesses in the scope +-- of this program, this is fine. + +local cqueues = require("cqueues") +local signal = require("cqueues.signal") + +local wait = require("posix.sys.wait") + +local std = require("posix.unistd") + +local fifo = require("fifo") +local fs = require("fs") + +local mom = {} + +-- initialize a new mom +-- this starts the sigblocking handler; only one will work at a time +-- otherwise the blocking system is not portable +function mom.new(loop) + local m = setmetatable({}, { __index = mom }) + m.loop = loop + m.pids = {} + -- actual clients. each of the keys is a unique table that gets returned to + -- the caller when they make a new ed process + -- you could argue this is unnecessary, and that the caller should be in charge of + -- making its own process. i don't really care either way; this is easier and does + -- what we want + m.clients = {} + + loop:wrap(function () mom.tend(m) end) + + return m +end + +-- create a new process in it +-- returns process (unique id object), tx_queue, rx_queue +function mom.create(m, command) + local id = {} + + m.clients[id] = { + command = command, + tx_queue = fifo.new(), + rx_queue = fifo.new(), + tx_fds = fifo.new(), + rx_fds = fifo.new(), + } + + mom.start_process(m, id) + m.loop:wrap(function() mom.handle_from_ed(m, id) end) + m.loop:wrap(function() mom.handle_to_ed(m, id) end) + return id, m.clients[id].tx_queue, m.clients[id].rx_queue +end + +function mom.start_process(m, id) + local proc_out_rx, proc_out_tx = std.pipe() + if not (proc_out_rx and proc_out_tx) then return nil, "can't create pipe" end + local proc_in_rx, proc_in_tx = std.pipe() + if not (proc_in_rx and proc_in_tx) then return nil, "can't create pipe" end + + local pid, err = std.fork() + if not pid then return nil, "couldn't create process: " .. err end + + if pid == 0 then + std.close(proc_out_rx) + std.close(proc_in_tx) + std.dup2(proc_in_rx, std.STDIN_FILENO) + std.dup2(proc_out_tx, std.STDOUT_FILENO) + std.execp(m.clients[id].command[1], { select(2, table.unpack(m.clients[id].command)) }) -- meh + os.exit(100) -- TODO + else + m.pids[pid] = id + std.close(proc_in_rx) + std.close(proc_out_tx) + + m.clients[id].tx_fds:put(fs.new(proc_in_tx, "w")) + m.clients[id].rx_fds:put(fs.new(proc_out_rx, "r")) + end + + return m +end + +function mom.handle_from_ed(m, id) + local client = m.clients[id] + + for inp in client.rx_fds:iter() do + for line in inp:lines() do + client.rx_queue:put(line) + end + + inp:close() + client.rx_queue:put({ "quit" }) + end +end + +function mom.handle_to_ed(m, id) + local client = m.clients[id] + + local out = client.tx_fds:get() + while true do + local line = client.tx_queue:get() + while true do + local good, err = out:write(line .. "\n") + + if good then break end + + out:close() + out = client.tx_fds:get() + end + end +end + +function mom.tend(m) + signal.block(signal.SIGCHLD) + local l = signal.listen(signal.SIGCHLD) + + while true do + l:wait() + local pid, status = wait.wait(-1, wait.WNOHANG | wait.WUNTRACED) + + if pid and status ~= "running" then + client_id = m.pids[pid] + m.pids[pid] = nil + -- TODO: only restart sometimes + mom.start_process(m, client_id) + m.clients[client_id].rx_queue:put({ "quit" }) + end + end +end + +return mom |