62741e79f5a3217a4e5f2ac1e9cb2dff8c5bd801
[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