summaryrefslogtreecommitdiffhomepage
path: root/libs/lucid/luasrc
diff options
context:
space:
mode:
authorSteven Barth <steven@midlink.org>2009-05-23 17:21:36 +0000
committerSteven Barth <steven@midlink.org>2009-05-23 17:21:36 +0000
commit8c4f847ea5b95aaf0e716beaf736b4e2b67655ae (patch)
tree5b931064a2df45c3903b66b425f49b621e9c31df /libs/lucid/luasrc
parent0ad58e38b42dca2f46bc492b7f889b5031a9c6a1 (diff)
GSoC Commit #1: LuCId + HTTP-Server
Diffstat (limited to 'libs/lucid/luasrc')
-rw-r--r--libs/lucid/luasrc/lucid.lua266
-rw-r--r--libs/lucid/luasrc/lucid/tcpserver.lua192
2 files changed, 458 insertions, 0 deletions
diff --git a/libs/lucid/luasrc/lucid.lua b/libs/lucid/luasrc/lucid.lua
new file mode 100644
index 000000000..62741e79f
--- /dev/null
+++ b/libs/lucid/luasrc/lucid.lua
@@ -0,0 +1,266 @@
+--[[
+LuCI - Lua Development Framework
+
+Copyright 2009 Steven Barth <steven@midlink.org>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+$Id$
+]]
+
+local nixio = require "nixio"
+local table = require "table"
+local uci = require "luci.model.uci"
+local os = require "os"
+local io = require "io"
+
+local pairs, require, pcall, assert, type = pairs, require, pcall, assert, type
+local ipairs, tonumber, collectgarbage = ipairs, tonumber, collectgarbage
+
+
+module "luci.lucid"
+
+local slaves = {}
+local pollt = {}
+local tickt = {}
+local tpids = {}
+local tcount = 0
+local ifaddrs = nixio.getifaddrs()
+
+cursor = uci.cursor()
+state = uci.cursor_state()
+UCINAME = "lucid"
+
+local cursor = cursor
+local state = state
+local UCINAME = UCINAME
+local SSTATE = "/tmp/.lucid_store"
+
+
+
+function start()
+ prepare()
+
+ local detach = cursor:get(UCINAME, "main", "daemonize")
+ if detach == "1" then
+ local stat, code, msg = daemonize()
+ if not stat then
+ nixio.syslog("crit", "Unable to detach process: " .. msg .. "\n")
+ ox.exit(2)
+ end
+ end
+
+ run()
+end
+
+function prepare()
+ local debug = tonumber((cursor:get(UCINAME, "main", "debug")))
+
+ nixio.openlog("lucid", "pid", "perror")
+ if debug ~= 1 then
+ nixio.setlogmask("warning")
+ end
+
+ cursor:foreach(UCINAME, "daemon", function(config)
+ if config.enabled ~= "1" then
+ return
+ end
+
+ local key = config[".name"]
+ if not config.slave then
+ nixio.syslog("crit", "Daemon "..key.." is missing a slave\n")
+ os.exit(1)
+ else
+ nixio.syslog("info", "Initializing daemon " .. key)
+ end
+
+ state:revert(UCINAME, key)
+
+ local daemon, code, err = prepare_daemon(config)
+ if daemon then
+ state:set(UCINAME, key, "status", "started")
+ nixio.syslog("info", "Prepared daemon " .. key)
+ else
+ state:set(UCINAME, key, "status", "error")
+ state:set(UCINAME, key, "error", err)
+ nixio.syslog("err", "Failed to initialize daemon "..key..": "..
+ err .. "\n")
+ end
+ end)
+end
+
+function run()
+ local pollint = tonumber((cursor:get(UCINAME, "main", "pollinterval")))
+
+ while true do
+ local stat, code = nixio.poll(pollt, pollint)
+
+ if stat and stat > 0 then
+ for _, polle in ipairs(pollt) do
+ if polle.revents ~= 0 and polle.handler then
+ polle.handler(polle)
+ end
+ end
+ elseif stat == 0 then
+ ifaddrs = nixio.getifaddrs()
+ collectgarbage("collect")
+ end
+
+ for _, cb in ipairs(tickt) do
+ cb()
+ end
+
+ local pid, stat, code = nixio.wait(-1, "nohang")
+ while pid and pid > 0 do
+ tcount = tcount - 1
+ if tpids[pid] and tpids[pid] ~= true then
+ tpids[pid](pid, stat, code)
+ end
+ pid, stat, code = nixio.wait(-1, "nohang")
+ end
+ end
+end
+
+function register_pollfd(polle)
+ pollt[#pollt+1] = polle
+ return true
+end
+
+function unregister_pollfd(polle)
+ for k, v in ipairs(pollt) do
+ if v == polle then
+ table.remove(pollt, k)
+ return true
+ end
+ end
+ return false
+end
+
+function close_pollfds()
+ for k, v in ipairs(pollt) do
+ if v.fd and v.fd.close then
+ v.fd:close()
+ end
+ end
+end
+
+function register_tick(cb)
+ tickt[#tickt+1] = cb
+ return true
+end
+
+function unregister_tick(cb)
+ for k, v in ipairs(tickt) do
+ if v == cb then
+ table.remove(tickt, k)
+ return true
+ end
+ end
+ return false
+end
+
+function create_process(threadcb, waitcb)
+ local threadlimit = tonumber(cursor:get(UCINAME, "main", "threadlimit"))
+ if threadlimit and #tpids >= tcount then
+ nixio.syslog("warning", "Unable to create thread: process limit reached")
+ return nil
+ end
+ local pid, code, err = nixio.fork()
+ if pid and pid ~= 0 then
+ tpids[pid] = waitcb
+ tcount = tcount + 1
+ elseif pid == 0 then
+ local code = threadcb()
+ os.exit(code)
+ else
+ nixio.syslog("err", "Unable to fork(): " .. err)
+ end
+ return pid, code, err
+end
+
+function prepare_daemon(config)
+ nixio.syslog("info", "Preparing daemon " .. config[".name"])
+ local modname = cursor:get(UCINAME, config.slave)
+ if not modname then
+ return nil, -1, "invalid slave"
+ end
+
+ local stat, module = pcall(require, _NAME .. "." .. modname)
+ if not stat or not module.prepare_daemon then
+ return nil, -2, "slave type not supported"
+ end
+
+ config.slave = prepare_slave(config.slave)
+
+ return module.prepare_daemon(config, _M)
+end
+
+function prepare_slave(name)
+ local slave = slaves[name]
+ if not slave then
+ local config = cursor:get_all(UCINAME, name)
+
+ local stat, module = pcall(require, config and config.entrypoint)
+ if stat then
+ slave = {module = module, config = config}
+ end
+ end
+
+ if slave then
+ return slave
+ else
+ return nil, module
+ end
+end
+
+function get_interfaces()
+ return ifaddrs
+end
+
+function revoke_privileges(user, group)
+ if nixio.getuid() == 0 then
+ return nixio.setgid(group) and nixio.setuid(user)
+ end
+end
+
+function securestate()
+ local stat = nixio.fs.stat(SSTATE) or {}
+ local uid = nixio.getuid()
+ if stat.type ~= "dir" or (stat.modedec % 100) ~= 0 or stat.uid ~= uid then
+ nixio.fs.remover(SSTATE)
+ if not nixio.fs.mkdir(SSTATE, 700) then
+ local errno = nixio.errno()
+ nixio.syslog("err", "Integrity check on secure state failed!")
+ return nil, errno, nixio.perror(errno)
+ end
+ end
+
+ return uci.cursor(nil, SSTATE)
+end
+
+function daemonize()
+ if nixio.getppid() == 1 then
+ return
+ end
+
+ local pid, code, msg = nixio.fork()
+ if not pid then
+ return nil, code, msg
+ elseif pid > 0 then
+ os.exit(0)
+ end
+
+ nixio.setsid()
+ nixio.chdir("/")
+
+ local devnull = nixio.open("/dev/null", nixio.open_flags("rdwr"))
+ nixio.dup(devnull, nixio.stdin)
+ nixio.dup(devnull, nixio.stdout)
+ nixio.dup(devnull, nixio.stderr)
+
+ return true
+end \ No newline at end of file
diff --git a/libs/lucid/luasrc/lucid/tcpserver.lua b/libs/lucid/luasrc/lucid/tcpserver.lua
new file mode 100644
index 000000000..22f094529
--- /dev/null
+++ b/libs/lucid/luasrc/lucid/tcpserver.lua
@@ -0,0 +1,192 @@
+--[[
+LuCI - Lua Development Framework
+
+Copyright 2009 Steven Barth <steven@midlink.org>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+$Id$
+]]
+
+local os = require "os"
+local nixio = require "nixio"
+local lucid = require "luci.lucid"
+
+local ipairs, type, require, setmetatable = ipairs, type, require, setmetatable
+local pairs, print, tostring, unpack = pairs, print, tostring, unpack
+
+module "luci.lucid.tcpserver"
+
+local cursor = lucid.cursor
+local UCINAME = lucid.UCINAME
+
+local tcpsockets = {}
+
+
+function prepare_daemon(config, server)
+ nixio.syslog("info", "Preparing TCP-Daemon " .. config[".name"])
+ if type(config.address) ~= "table" then
+ config.address = {config.address}
+ end
+
+ local sockets, socket, code, err = {}
+ local sopts = {reuseaddr = 1}
+ for _, addr in ipairs(config.address) do
+ local host, port = addr:match("(.-):?([^:]*)")
+ if not host then
+ nixio.syslog("err", "Invalid address: " .. addr)
+ return nil, -5, "invalid address format"
+ elseif #host == 0 then
+ host = nil
+ end
+ socket, code, err = prepare_socket(config.family, host, port, sopts)
+ if socket then
+ sockets[#sockets+1] = socket
+ end
+ end
+
+ nixio.syslog("info", "Sockets bound for " .. config[".name"])
+
+ if #sockets < 1 then
+ return nil, -6, "no sockets bound"
+ end
+
+ nixio.syslog("info", "Preparing publishers for " .. config[".name"])
+
+ local publisher = {}
+ for k, pname in ipairs(config.publisher) do
+ local pdata = cursor:get_all(UCINAME, pname)
+ if pdata then
+ publisher[#publisher+1] = pdata
+ else
+ nixio.syslog("err", "Publisher " .. pname .. " not found")
+ end
+ end
+
+ nixio.syslog("info", "Preparing TLS for " .. config[".name"])
+
+ local tls = prepare_tls(config.tls)
+ if not tls and config.encryption == "enable" then
+ for _, s in ipairs(sockets) do
+ s:close()
+ end
+ return nil, -4, "Encryption requested, but no TLS context given"
+ end
+
+ nixio.syslog("info", "Invoking daemon factory for " .. config[".name"])
+ local handler, err = config.slave.module.factory(publisher, config)
+ if not handler then
+ for _, s in ipairs(sockets) do
+ s:close()
+ end
+ return nil, -3, err
+ else
+ local pollin = nixio.poll_flags("in")
+ for _, s in ipairs(sockets) do
+ server.register_pollfd({
+ fd = s,
+ events = pollin,
+ revents = 0,
+ handler = accept,
+ accept = handler,
+ config = config,
+ publisher = publisher,
+ tls = tls
+ })
+ end
+ return true
+ end
+end
+
+function accept(polle)
+ local socket, host, port = polle.fd:accept()
+ if not socket then
+ return nixio.syslog("warn", "accept() failed: " .. port)
+ end
+
+ socket:setblocking(true)
+
+ local function thread()
+ lucid.close_pollfds()
+ local inst = setmetatable({
+ host = host, port = port, interfaces = lucid.get_interfaces()
+ }, {__index = polle})
+ if polle.config.encryption then
+ socket = polle.tls:create(socket)
+ if not socket:accept() then
+ socket:close()
+ return nixio.syslog("warning", "TLS handshake failed: " .. host)
+ end
+ end
+
+ return polle.accept(socket, inst)
+ end
+
+ local stat = {lucid.create_process(thread)}
+ socket:close()
+ return unpack(stat)
+end
+
+function prepare_socket(family, host, port, opts, backlog)
+ nixio.syslog("info", "Preparing socket for port " .. port)
+ backlog = backlog or 1024
+ family = family or "inetany"
+ opts = opts or {}
+
+ local inetany = family == "inetany"
+ family = inetany and "inet6" or family
+
+ local socket, code, err = nixio.socket(family, "stream")
+ if not socket and inetany then
+ family = "inet"
+ socket, code, err = nixio.socket(family, "stream")
+ end
+
+ if not socket then
+ return nil, code, err
+ end
+
+ for k, v in pairs(opts) do
+ socket:setsockopt("socket", k, v)
+ end
+
+ local stat, code, err = socket:bind(host, port)
+ if not stat then
+ return nil, code, err
+ end
+
+ stat, code, err = socket:listen(backlog)
+ if not stat then
+ return nil, code, err
+ end
+
+ socket:setblocking(false)
+
+ return socket, family
+end
+
+function prepare_tls(tlskey)
+ local tls = nixio.tls()
+ if tlskey and cursor:get(UCINAME, tlskey) then
+ local cert = cursor:get(UCINAME, tlskey, "cert")
+ if cert then
+ tls:set_cert(cert)
+ end
+ local key = cursor:get(UCINAME, tlskey, "key")
+ if key then
+ tls:set_key(key)
+ end
+ local ciphers = cursor:get(UCINAME, tlskey, "ciphers")
+ if ciphers then
+ if type(ciphers) == "table" then
+ ciphers = table.concat(ciphers, ":")
+ end
+ tls:set_ciphers(ciphers)
+ end
+ end
+ return tls
+end \ No newline at end of file