about summary refs log tree commit diff
diff options
context:
space:
mode:
authorequa <equaa@protonmail.com>2022-03-08 22:04:55 +0000
committerequa <equaa@protonmail.com>2022-03-08 22:12:23 +0000
commit55e859929479050e2a3b71a614a388f3b6c2a197 (patch)
tree95ebc1638c229dd8409ba56054a8f48b2fa4f533
parent224e8f43c50a513bae78af2152c12c0a5f9564f9 (diff)
basic editor watcher, soon to be several editors
-rw-r--r--bot.lua39
-rw-r--r--fs.lua19
-rw-r--r--mom.lua133
3 files changed, 177 insertions, 14 deletions
diff --git a/bot.lua b/bot.lua
index f957fd3..ac0a10c 100644
--- a/bot.lua
+++ b/bot.lua
@@ -8,6 +8,7 @@ local dkjson = require("dkjson")
 local fs = require("fs")
 
 local fifo = require("fifo")
+local mom = require("mom")
 
 function irc_dump(fifo, socket)
 	for data in fifo:iter() do
@@ -128,6 +129,7 @@ function irc_connect(loop, host, config)
 	local state = {
 		queue = fifo.new(),
 		nick = config.nick or "ed1bot",
+		command = config.command,
 	}
 
 	-- populate channels
@@ -169,19 +171,38 @@ function irc_connect(loop, host, config)
 		end
 	end)
 
-	state.to_ed, state.from_ed = fifo.new(), fifo.new()
-	handle_ed(loop, state.to_ed, state.from_ed, { "red" })
+	-- state.to_ed, state.from_ed = fifo.new(), fifo.new()
+	-- handle_ed(loop, state.to_ed, state.from_ed, { "red" })
+	state.mom = mom.new(loop)
+	state.to_ed, state.from_ed = select(2, state.mom:create(state.command))
 
 	loop:wrap(function()
 		while true do
 			local line = fifo.get(state.from_ed)
-			fifo.put(state.queue, {
-				command = "PRIVMSG",
-				params = {
-					state.channel,
-					line
-				}
-			})
+			if type(line) == "string" then
+				fifo.put(state.queue, {
+					command = "PRIVMSG",
+					params = {
+						state.channel,
+						line
+					}
+				})
+			else -- TODO other possible types
+				fifo.put(state.queue, {
+					command = "PART",
+					params = {
+						state.channel
+					},
+				})
+				-- be annoying
+				cqueues.poll(10.0)
+				fifo.put(state.queue, {
+					command = "JOIN",
+					params = {
+						state.channel
+					},
+				})
+			end
 		end
 	end)
 
diff --git a/fs.lua b/fs.lua
index 2e7638f..153e611 100644
--- a/fs.lua
+++ b/fs.lua
@@ -1,5 +1,7 @@
 -- cqueue filesystem with luaposix
 -- TODO: closing files
+-- TODO: error handling. right now it *always* returns errno instead of
+-- throwing it, which may be what we want. code depends on this
 local unistd = require("posix.unistd")
 local cqueues = require("cqueues")
 local dkjson = require("dkjson")
@@ -85,8 +87,9 @@ function try_read(buffer, fd, max)
 	local data, _, errno = unistd.read(fd, max)
 
 	if not data then return nil, errno end
+	if #data == 0 then return nil end
 
-	table.insert(buffer, { data = data, index = 1})
+	table.insert(buffer, { data = data, index = 1 })
 
 	return #data
 end
@@ -96,7 +99,7 @@ function file.read(f, what)
 
 	if (type(what) == "number" and what > 0) then
 		while buffer_length(f.rbuf) < what do
-			if try_read(f.rbuf, f.pollfd, what - buffer_length(f.rbuf)) == 0 then
+			if not try_read(f.rbuf, f.pollfd, what - buffer_length(f.rbuf)) then
 				break
 			end
 		end
@@ -104,13 +107,14 @@ function file.read(f, what)
 		return buffer_get(f.rbuf, math.min(what, buffer_length(f.rbuf)))
 	elseif (type(what) == "number" and what < 0) then
 		while buffer_length(f.rbuf) == 0 do
-			if try_read(f.rbuf, f.pollfd, 0 - what) == 0 then return nil end
+			if not try_read(f.rbuf, f.pollfd, 0 - what) then return nil end
 		end
 		return buffer_get(f.rbuf, math.min(0 - what, buffer_length(f.rbuf)))
 	elseif what == "*l" or what == "*L" then
 		while not buffer_char_index(f.rbuf, "\n") do
 			-- TODO: constantize this
-			if try_read(f.rbuf, f.pollfd, 1024) == 0 then
+			local x = try_read(f.rbuf, f.pollfd, 1024)
+			if not x then
 				break
 			end
 		end
@@ -125,6 +129,7 @@ function file.read(f, what)
 			"\n",
 			(what == "*l" and "" or "\n")
 		)
+
 		return ret
 	else
 		-- TODO
@@ -140,7 +145,7 @@ function file.write(f, data)
 	while index <= #data do
 		cqueues.poll({ pollfd = f.pollfd, events = function () return "w" end })
 		local nb, _, errno = unistd.write(f.pollfd, string.sub(data, index))
-		if nb >= 0 then
+		if nb then
 			index = index + nb
 		else
 			return nil, errno -- TODO?
@@ -150,4 +155,8 @@ function file.write(f, data)
 	return f
 end
 
+function file.close(f)
+	cqueues.cancel(f.pollfd)
+end
+
 return file
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