Fix redirector
[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         state:set(UCINAME, "main", "pid", nixio.getpid())
58         state:save(UCINAME)
59
60         run()
61 end
62
63 function stop()
64         local pid = tonumber(state:get(UCINAME, "main", "pid"))
65         if pid then
66                 return nixio.kill(pid, nixio.const.SIGTERM)
67         end
68         return false
69 end
70
71 function prepare()
72         local debug = tonumber((cursor:get(UCINAME, "main", "debug")))
73         
74         nixio.openlog("lucid", "pid", "perror")
75         if debug ~= 1 then
76                 nixio.setlogmask("warning")
77         end
78         
79         cursor:foreach(UCINAME, "daemon", function(config)
80                 if config.enabled ~= "1" then
81                         return
82                 end
83         
84                 local key = config[".name"]
85                 if not config.slave then
86                         nixio.syslog("crit", "Daemon "..key.." is missing a slave\n")
87                         os.exit(1)
88                 else
89                         nixio.syslog("info", "Initializing daemon " .. key)
90                 end
91                 
92                 state:revert(UCINAME, key)
93                 
94                 local daemon, code, err = prepare_daemon(config)
95                 if daemon then
96                         state:set(UCINAME, key, "status", "started")
97                         nixio.syslog("info", "Prepared daemon " .. key)
98                 else
99                         state:set(UCINAME, key, "status", "error")
100                         state:set(UCINAME, key, "error", err)
101                         nixio.syslog("err", "Failed to initialize daemon "..key..": "..
102                         err .. "\n")
103                 end
104         end)
105 end
106         
107 function run()
108         local pollint = tonumber((cursor:get(UCINAME, "main", "pollinterval")))
109
110         while true do
111                 local stat, code = nixio.poll(pollt, pollint)
112                 
113                 if stat and stat > 0 then
114                         for _, polle in ipairs(pollt) do
115                                 if polle.revents ~= 0 and polle.handler then
116                                         polle.handler(polle)
117                                 end
118                         end
119                 elseif stat == 0 then
120                         ifaddrs = nixio.getifaddrs()
121                         collectgarbage("collect")
122                 end
123                 
124                 for _, cb in ipairs(tickt) do
125                         cb()
126                 end
127                 
128                 local pid, stat, code = nixio.wait(-1, "nohang")
129                 while pid and pid > 0 do
130                         tcount = tcount - 1
131                         if tpids[pid] and tpids[pid] ~= true then
132                                 tpids[pid](pid, stat, code)
133                         end
134                         pid, stat, code = nixio.wait(-1, "nohang")
135                 end
136         end
137 end
138
139 function register_pollfd(polle)
140         pollt[#pollt+1] = polle
141         return true 
142 end
143
144 function unregister_pollfd(polle)
145         for k, v in ipairs(pollt) do
146                 if v == polle then
147                         table.remove(pollt, k)
148                         return true
149                 end
150         end
151         return false
152 end
153
154 function close_pollfds()
155         for k, v in ipairs(pollt) do
156                 if v.fd and v.fd.close then
157                         v.fd:close()
158                 end
159         end
160 end
161
162 function register_tick(cb)
163         tickt[#tickt+1] = cb
164         return true
165 end
166
167 function unregister_tick(cb)
168         for k, v in ipairs(tickt) do
169                 if v == cb then
170                         table.remove(tickt, k)
171                         return true
172                 end
173         end
174         return false
175 end
176
177 function create_process(threadcb, waitcb)
178         local threadlimit = tonumber(cursor:get(UCINAME, "main", "threadlimit"))
179         if threadlimit and #tpids >= tcount then
180                 nixio.syslog("warning", "Unable to create thread: process limit reached")
181                 return nil
182         end
183         local pid, code, err = nixio.fork()
184         if pid and pid ~= 0 then
185                 tpids[pid] = waitcb
186                 tcount = tcount + 1
187         elseif pid == 0 then
188                 local code = threadcb()
189                 os.exit(code)
190         else
191                 nixio.syslog("err", "Unable to fork(): " .. err)
192         end
193         return pid, code, err
194 end
195
196 function prepare_daemon(config)
197         nixio.syslog("info", "Preparing daemon " .. config[".name"])
198         local modname = cursor:get(UCINAME, config.slave)
199         if not modname then
200                 return nil, -1, "invalid slave"
201         end
202
203         local stat, module = pcall(require, _NAME .. "." .. modname)
204         if not stat or not module.prepare_daemon then
205                 return nil, -2, "slave type not supported"
206         end
207         
208         config.slave = prepare_slave(config.slave)
209
210         return module.prepare_daemon(config, _M)
211 end
212
213 function prepare_slave(name)
214         local slave = slaves[name]
215         if not slave then
216                 local config = cursor:get_all(UCINAME, name)
217                 
218                 local stat, module = pcall(require, config and config.entrypoint)
219                 if stat then
220                         slave = {module = module, config = config}
221                 end
222         end
223         
224         if slave then
225                 return slave
226         else
227                 return nil, module
228         end
229 end
230
231 function get_interfaces()
232         return ifaddrs
233 end
234
235 function revoke_privileges(user, group)
236         if nixio.getuid() == 0 then
237                 return nixio.setgid(group) and nixio.setuid(user)
238         end
239 end
240
241 function securestate()
242         local stat = nixio.fs.stat(SSTATE) or {}
243         local uid = nixio.getuid()
244         if stat.type ~= "dir" or (stat.modedec % 100) ~= 0 or stat.uid ~= uid then
245                 nixio.fs.remover(SSTATE)
246                 if not nixio.fs.mkdir(SSTATE, 700) then
247                         local errno = nixio.errno()
248                         nixio.syslog("err", "Integrity check on secure state failed!")
249                         return nil, errno, nixio.perror(errno)
250                 end
251         end
252         
253         return uci.cursor(nil, SSTATE)
254 end
255
256 function daemonize()
257         if nixio.getppid() == 1 then
258                 return
259         end
260         
261         local pid, code, msg = nixio.fork()
262         if not pid then
263                 return nil, code, msg
264         elseif pid > 0 then
265                 os.exit(0)
266         end
267         
268         nixio.setsid()
269         nixio.chdir("/")
270         
271         local devnull = nixio.open("/dev/null", nixio.open_flags("rdwr"))
272         nixio.dup(devnull, nixio.stdin)
273         nixio.dup(devnull, nixio.stdout)
274         nixio.dup(devnull, nixio.stderr)
275         
276         return true
277 end