d72bf25ef9c6007f94c8067fe6adee506a2e6045
[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