1
0
mirror of https://github.com/ShadowKatStudios/OC-Minitel.git synced 2024-11-23 10:38:05 +11:00

replaced the FRequest server (again) with something that threads better

This commit is contained in:
Izaya 2018-09-01 17:47:58 +10:00
parent 66f844ec65
commit 6d1d972e9b

View File

@ -1,24 +1,30 @@
local net = require "minitel" local net = require "minitel"
local event = require "event"
local syslog = require "syslog" local syslog = require "syslog"
local fs = require "filesystem" local fs = require "filesystem"
local computer = require "computer" local event = require "event"
local serial = require "serialization"
local sockets = {} local coro = {} -- table of coroutines, one per socket
local cfg = {} local cfg = {}
cfg.path = "/srv" local timer, listener
cfg.looptime = 0.5 local isRunning = false -- simple lock
local timer = false
cfg.path = "/srv" -- default config
cfg.port = 70
cfg.looptimer = 0.5
local function log(msg,level) local function log(msg,level)
syslog(msg,level,"frequestd") syslog(msg,level,"frequestd")
end end
local function loadConfig() local function loadConfig() -- load config from file
local fobj = io.open("/etc/fserv.cfg","rb") local fobj = io.open("/etc/fserv.cfg","rb")
if fobj then if fobj then
cfg = serial.unserialize(fobj:read("*a")) or cfg local ncfg = serial.unserialize(fobj:read("*a"))
if ncfg then
for k,v in pairs(ncfg) do
cfg[k] = v
end
end
fobj:close() fobj:close()
end end
end end
@ -31,107 +37,102 @@ local function writeConfig()
end end
end end
--[[ local function handleSocket(sock) -- create a coroutine for a new socket
overview of how this works: coro[#coro+1] = coroutine.create(function()
socketHandler listens for connections on port 70, places them into the sockets table local line
socketLoop runs quite regularly - every cfg.looptime or so. repeat
It iterates over each socket in the sockets table, and if any of them contain a valid request, it attempts to handle it: coroutine.yield()
- calls getFile to get a status code and file contents if applicable line = sock:read()
- writes either a failure code and message, the file contents, or the file size until line
Previously this was based on a listener but that caused a race condition local ttype, path = line:match("([ts])(.+)")
if not ttype or not path then
note to self: consider using the FS library for handling stat requests, and returning an iterator instead of an entire file for transfer requests sock:write("fIncomplete request")
]]-- sock:close()
return false
local function getFile(path)
path = cfg.path .. path
if not fs.exists(path) then
log(path.." not found",syslog.notice)
return "n",path.." not found"
end
if fs.isDirectory(path) then
local dlist = ""
for file in fs.list(path) do
dlist = dlist .. file .. "\n"
end end
return "d", dlist path = fs.canonical(cfg.path.."/"..fs.canonical(path))
end sock.cname = sock.addr..":"..tostring(sock.port)
local f = io.open(path,"rb") log("["..sock.cname.."] "..ttype.." "..path,6)
if not f then if ttype == "t" then -- transfer request
log("unable to open "..path,syslog.notice) if not fs.exists(path) then
return "f","unable to open "..path sock:write("nFile not found.")
end sock:close()
local content = f:read("*a") log("["..sock.cname.."] Not found.",7)
f:close() return
return "y", content
end
local function socketLoop()
for sn,socket in pairs(sockets) do
local op, path = socket.rbuffer:match("([ts])(.+)\n")
if op and path then
path = fs.canonical(path)
if path:sub(1,1) ~= "/" then
path = "/" .. path
end end
log("client "..tostring(socket.addr)..":"..tostring(socket.port).." requested "..path, syslog.debug) if fs.isDirectory(path) then
if op == "t" and path then local rs = "d"
local wf, file = getFile(path) for file in fs.list(path) do
socket:write(wf .. file) rs = rs..file.."\n"
socket:close()
sockets[sn] = nil
elseif op == "s" and path then
local wf, file = getFile(path)
if wf == "y" then
socket:write(wf..file:len())
else
socket:write(wf..file)
end end
socket:close() sock:write(rs)
sockets[sn] = nil sock:close()
log("["..sock.cname.."] Directory.",7)
return
end
local f = io.open(path,"rb")
if not f then
sock:write("fUnable to open file for reading",7)
sock:close()
return
end
sock:write("y")
log("["..sock.cname.."] Transferring file.",7)
local chunk = f:read(net.mtu)
repeat
sock:write(chunk)
coroutine.yield()
chunk = f:read(net.mtu)
until not chunk
sock:close()
f:close()
log("["..sock.cname.."] file transferred.",7)
elseif ttype == "s" then -- stat request
if fs.exists(path) then
local ftype = "f"
if fs.isDirectory(path) then
ftype = "d"
end
sock:write("y"..ftype..tostring(fs.size(path)))
sock:close()
log("["..sock.cname.."] stat request returned.",7)
else
sock:write("nFile not found.",7)
sock:close()
log("["..sock.cname.."] Not found.",7)
end end
end end
if computer.uptime() > socket.opened + 60 then end)
socket:close() log("New connection: "..sock.addr..":"..tostring(sock.port),7)
sockets[sn] = nil
log("dropped client "..tostring(socket.addr)..":"..tostring(socket.port).." for inactivity",syslog.debug)
elseif socket.state ~= "open" then
sockets[sn] = nil
log("client "..tostring(socket.addr)..":"..tostring(socket.port).." closed socket",syslog.debug)
end
end
end end
local function socketHandler(socket) local function sched() -- run coroutines
log(tostring(socket.addr)..":"..tostring(socket.port).." opened",syslog.debug) if isRunning then return end
socket.opened = computer.uptime() isRunning = true
sockets[tostring(socket.addr)..":"..tostring(socket.port)] = socket for i = #coro, 1, -1 do -- prune dead coroutines
if coroutine.status(coro[i]) == "dead" then
table.remove(coro,i)
end
end
for k,v in pairs(coro) do
coroutine.resume(v)
end
isRunning = false
end end
function start() function start()
if timer or listener then return end
loadConfig() loadConfig()
writeConfig() writeConfig()
net.flisten(70, socketHandler) timer = event.timer(cfg.looptimer, sched, math.huge)
timer = event.timer(cfg.looptime, socketLoop, math.huge) listener = net.flisten(cfg.port, handleSocket)
end end
function stop() function stop()
event.ignore("net_msg", socketLoop) if not timer or not listener then return end
event.cancel(timer) event.cancel(timer)
event.ignore("net_msg",listener)
end end
function restart()
function set(k,v) stop()
if type(cfg[k]) == "string" then start()
cfg[k] = v
elseif type(cfg[k]) == "number" then
cfg[k] = tonumber(v)
elseif type(cfg[k]) == "boolean" then
if v:lower():sub(1,1) == "t" then
cfg[k] = true
else
cfg[k] = false
end
end
print("cfg."..k.." = "..tostring(cfg[k]))
writeConfig()
end end