1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
|
-- 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 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
-- you need to give it an identifier (this can be anything, and it isn't the id
-- that it uses internally). things pushed to the rx_queue will look like
-- { IDENTIFIER, "line", "meow" }
-- this is to make multiplexing easier
-- you must also give it a queue to send to (the rx_queue)
function mom.create(m, command, rx_id, rx_queue, directory)
local id = {}
m.clients[id] = {
command = assert(command),
directory = assert(directory),
rx_id = assert(rx_id),
tx_queue = fifo.new(),
rx_queue = assert(rx_queue),
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
local function error_wait(err)
print("error: " .. err)
print("say something to try again")
io.read("*l")
os.exit(100)
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.dup2(proc_out_tx, std.STDERR_FILENO)
local ok, err = std.chdir(m.clients[id].directory)
if not ok then
error_wait(err)
end
local _, err = std.execp(m.clients[id].command[1], { select(2, table.unpack(m.clients[id].command)) }) -- meh
error_wait(err)
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({ client.rx_id, "line", line })
end
inp:close()
client.rx_queue:put({ client.rx_id, "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
local client_id = m.pids[pid]
m.pids[pid] = nil
m.clients[client_id].rx_queue:put({ "quit" })
end
end
end
return mom
|