GSoC Commit #1: LuCId + HTTP-Server
[project/luci.git] / libs / lucid / luasrc / lucid.lua
1 --[[
2 LuCI - Lua Development Framework
3
4 Copyright 2009 Steven Barth <steven@midlink.org>
5
6 Licensed under the Apache License, Version 2.0 (the "License");
7 you may not use this file except in compliance with the License.
8 You may obtain a copy of the License at
9
10 http://www.apache.org/licenses/LICENSE-2.0
11
12 $Id$
13 ]]
14
15 local nixio = require "nixio"
16 local table = require "table"
17 local uci = require "luci.model.uci"
18 local os = require "os"
19 local io = require "io"
20
21 local pairs, require, pcall, assert, type = pairs, require, pcall, assert, type
22 local ipairs, tonumber, collectgarbage = ipairs, tonumber, collectgarbage
23
24
25 module "luci.lucid"
26
27 local slaves = {}
28 local pollt  = {}
29 local tickt  = {}
30 local tpids  = {}
31 local tcount = 0
32 local ifaddrs = nixio.getifaddrs()
33
34 cursor = uci.cursor()
35 state  = uci.cursor_state()
36 UCINAME = "lucid"
37
38 local cursor = cursor
39 local state = state
40 local UCINAME = UCINAME
41 local SSTATE = "/tmp/.lucid_store"
42
43
44
45 function start()
46         prepare()
47
48         local detach = cursor:get(UCINAME, "main", "daemonize")
49         if detach == "1" then
50                 local stat, code, msg = daemonize()
51                 if not stat then
52                         nixio.syslog("crit", "Unable to detach process: " .. msg .. "\n")
53                         ox.exit(2)
54                 end
55         end
56
57         run()
58 end
59
60 function prepare()
61         local debug = tonumber((cursor:get(UCINAME, "main", "debug")))
62         
63         nixio.openlog("lucid", "pid", "perror")
64         if debug ~= 1 then
65                 nixio.setlogmask("warning")
66         end
67         
68         cursor:foreach(UCINAME, "daemon", function(config)
69                 if config.enabled ~= "1" then
70                         return
71                 end
72         
73                 local key = config[".name"]
74                 if not config.slave then
75                         nixio.syslog("crit", "Daemon "..key.." is missing a slave\n")
76                         os.exit(1)
77                 else
78                         nixio.syslog("info", "Initializing daemon " .. key)
79                 end
80                 
81                 state:revert(UCINAME, key)
82                 
83                 local daemon, code, err = prepare_daemon(config)
84                 if daemon then
85                         state:set(UCINAME, key, "status", "started")
86                         nixio.syslog("info", "Prepared daemon " .. key)
87                 else
88                         state:set(UCINAME, key, "status", "error")
89                         state:set(UCINAME, key, "error", err)
90                         nixio.syslog("err", "Failed to initialize daemon "..key..": "..
91                         err .. "\n")
92                 end
93         end)
94 end
95         
96 function run()
97         local pollint = tonumber((cursor:get(UCINAME, "main", "pollinterval")))
98
99         while true do
100                 local stat, code = nixio.poll(pollt, pollint)
101                 
102                 if stat and stat > 0 then
103                         for _, polle in ipairs(pollt) do
104                                 if polle.revents ~= 0 and polle.handler then
105                                         polle.handler(polle)
106                                 end
107                         end
108                 elseif stat == 0 then
109                         ifaddrs = nixio.getifaddrs()
110                         collectgarbage("collect")
111                 end
112                 
113                 for _, cb in ipairs(tickt) do
114                         cb()
115                 end
116                 
117                 local pid, stat, code = nixio.wait(-1, "nohang")
118                 while pid and pid > 0 do
119                         tcount = tcount - 1
120                         if tpids[pid] and tpids[pid] ~= true then
121                                 tpids[pid](pid, stat, code)
122                         end
123                         pid, stat, code = nixio.wait(-1, "nohang")
124                 end
125         end
126 end
127
128 function register_pollfd(polle)
129         pollt[#pollt+1] = polle
130         return true 
131 end
132
133 function unregister_pollfd(polle)
134         for k, v in ipairs(pollt) do
135                 if v == polle then
136                         table.remove(pollt, k)
137                         return true
138                 end
139         end
140         return false
141 end
142
143 function close_pollfds()
144         for k, v in ipairs(pollt) do
145                 if v.fd and v.fd.close then
146                         v.fd:close()
147                 end
148         end
149 end
150
151 function register_tick(cb)
152         tickt[#tickt+1] = cb
153         return true
154 end
155
156 function unregister_tick(cb)
157         for k, v in ipairs(tickt) do
158                 if v == cb then
159                         table.remove(tickt, k)
160                         return true
161                 end
162         end
163         return false
164 end
165
166 function create_process(threadcb, waitcb)
167         local threadlimit = tonumber(cursor:get(UCINAME, "main", "threadlimit"))
168         if threadlimit and #tpids >= tcount then
169                 nixio.syslog("warning", "Unable to create thread: process limit reached")
170                 return nil
171         end
172         local pid, code, err = nixio.fork()
173         if pid and pid ~= 0 then
174                 tpids[pid] = waitcb
175                 tcount = tcount + 1
176         elseif pid == 0 then
177                 local code = threadcb()
178                 os.exit(code)
179         else
180                 nixio.syslog("err", "Unable to fork(): " .. err)
181         end
182         return pid, code, err
183 end
184
185 function prepare_daemon(config)
186         nixio.syslog("info", "Preparing daemon " .. config[".name"])
187         local modname = cursor:get(UCINAME, config.slave)
188         if not modname then
189                 return nil, -1, "invalid slave"
190         end
191
192         local stat, module = pcall(require, _NAME .. "." .. modname)
193         if not stat or not module.prepare_daemon then
194                 return nil, -2, "slave type not supported"
195         end
196         
197         config.slave = prepare_slave(config.slave)
198
199         return module.prepare_daemon(config, _M)
200 end
201
202 function prepare_slave(name)
203         local slave = slaves[name]
204         if not slave then
205                 local config = cursor:get_all(UCINAME, name)
206                 
207                 local stat, module = pcall(require, config and config.entrypoint)
208                 if stat then
209                         slave = {module = module, config = config}
210                 end
211         end
212         
213         if slave then
214                 return slave
215         else
216                 return nil, module
217         end
218 end
219
220 function get_interfaces()
221         return ifaddrs
222 end
223
224 function revoke_privileges(user, group)
225         if nixio.getuid() == 0 then
226                 return nixio.setgid(group) and nixio.setuid(user)
227         end
228 end
229
230 function securestate()
231         local stat = nixio.fs.stat(SSTATE) or {}
232         local uid = nixio.getuid()
233         if stat.type ~= "dir" or (stat.modedec % 100) ~= 0 or stat.uid ~= uid then
234                 nixio.fs.remover(SSTATE)
235                 if not nixio.fs.mkdir(SSTATE, 700) then
236                         local errno = nixio.errno()
237                         nixio.syslog("err", "Integrity check on secure state failed!")
238                         return nil, errno, nixio.perror(errno)
239                 end
240         end
241         
242         return uci.cursor(nil, SSTATE)
243 end
244
245 function daemonize()
246         if nixio.getppid() == 1 then
247                 return
248         end
249         
250         local pid, code, msg = nixio.fork()
251         if not pid then
252                 return nil, code, msg
253         elseif pid > 0 then
254                 os.exit(0)
255         end
256         
257         nixio.setsid()
258         nixio.chdir("/")
259         
260         local devnull = nixio.open("/dev/null", nixio.open_flags("rdwr"))
261         nixio.dup(devnull, nixio.stdin)
262         nixio.dup(devnull, nixio.stdout)
263         nixio.dup(devnull, nixio.stderr)
264         
265         return true
266 end