89ada3c2e8b6dc02edf7e21ce3d08bcbbfef4ad9
[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         local threadlimit = tonumber(cursor:get(UCINAME, "main", "threadlimit"))
110
111         while true do
112                 if not threadlimit or tcount < threadlimit then
113                         local stat, code = nixio.poll(pollt, pollint)
114                 
115                         if stat and stat > 0 then
116                                 for _, polle in ipairs(pollt) do
117                                         if polle.revents ~= 0 and polle.handler then
118                                                 polle.handler(polle)
119                                         end
120                                 end
121                         elseif stat == 0 then
122                                 ifaddrs = nixio.getifaddrs()
123                                 collectgarbage("collect")
124                         end
125                 end
126                 
127                 for _, cb in ipairs(tickt) do
128                         cb()
129                 end
130                 
131                 local pid, stat, code = nixio.wait(-1, "nohang")
132                 while pid and pid > 0 do
133                         tcount = tcount - 1
134                         if tpids[pid] and tpids[pid] ~= true then
135                                 tpids[pid](pid, stat, code)
136                         end
137                         pid, stat, code = nixio.wait(-1, "nohang")
138                 end
139         end
140 end
141
142 function register_pollfd(polle)
143         pollt[#pollt+1] = polle
144         return true 
145 end
146
147 function unregister_pollfd(polle)
148         for k, v in ipairs(pollt) do
149                 if v == polle then
150                         table.remove(pollt, k)
151                         return true
152                 end
153         end
154         return false
155 end
156
157 function close_pollfds()
158         for k, v in ipairs(pollt) do
159                 if v.fd and v.fd.close then
160                         v.fd:close()
161                 end
162         end
163 end
164
165 function register_tick(cb)
166         tickt[#tickt+1] = cb
167         return true
168 end
169
170 function unregister_tick(cb)
171         for k, v in ipairs(tickt) do
172                 if v == cb then
173                         table.remove(tickt, k)
174                         return true
175                 end
176         end
177         return false
178 end
179
180 function create_process(threadcb, waitcb)
181         local threadlimit = tonumber(cursor:get(UCINAME, "main", "threadlimit"))
182         if threadlimit and tcount >= threadlimit then
183                 nixio.syslog("warning", "Unable to create thread: process limit reached")
184                 return nil
185         end
186         local pid, code, err = nixio.fork()
187         if pid and pid ~= 0 then
188                 tpids[pid] = waitcb
189                 tcount = tcount + 1
190         elseif pid == 0 then
191                 local code = threadcb()
192                 os.exit(code)
193         else
194                 nixio.syslog("err", "Unable to fork(): " .. err)
195         end
196         return pid, code, err
197 end
198
199 function prepare_daemon(config)
200         nixio.syslog("info", "Preparing daemon " .. config[".name"])
201         local modname = cursor:get(UCINAME, config.slave)
202         if not modname then
203                 return nil, -1, "invalid slave"
204         end
205
206         local stat, module = pcall(require, _NAME .. "." .. modname)
207         if not stat or not module.prepare_daemon then
208                 return nil, -2, "slave type not supported"
209         end
210         
211         config.slave = prepare_slave(config.slave)
212
213         return module.prepare_daemon(config, _M)
214 end
215
216 function prepare_slave(name)
217         local slave = slaves[name]
218         if not slave then
219                 local config = cursor:get_all(UCINAME, name)
220                 
221                 local stat, module = pcall(require, config and config.entrypoint)
222                 if stat then
223                         slave = {module = module, config = config}
224                 end
225         end
226         
227         if slave then
228                 return slave
229         else
230                 return nil, module
231         end
232 end
233
234 function get_interfaces()
235         return ifaddrs
236 end
237
238 function revoke_privileges(user, group)
239         if nixio.getuid() == 0 then
240                 return nixio.setgid(group) and nixio.setuid(user)
241         end
242 end
243
244 function securestate()
245         local stat = nixio.fs.stat(SSTATE) or {}
246         local uid = nixio.getuid()
247         if stat.type ~= "dir" or (stat.modedec % 100) ~= 0 or stat.uid ~= uid then
248                 nixio.fs.remover(SSTATE)
249                 if not nixio.fs.mkdir(SSTATE, 700) then
250                         local errno = nixio.errno()
251                         nixio.syslog("err", "Integrity check on secure state failed!")
252                         return nil, errno, nixio.perror(errno)
253                 end
254         end
255         
256         return uci.cursor(nil, SSTATE)
257 end
258
259 function daemonize()
260         if nixio.getppid() == 1 then
261                 return
262         end
263         
264         local pid, code, msg = nixio.fork()
265         if not pid then
266                 return nil, code, msg
267         elseif pid > 0 then
268                 os.exit(0)
269         end
270         
271         nixio.setsid()
272         nixio.chdir("/")
273         
274         local devnull = nixio.open("/dev/null", nixio.open_flags("rdwr"))
275         nixio.dup(devnull, nixio.stdin)
276         nixio.dup(devnull, nixio.stdout)
277         nixio.dup(devnull, nixio.stderr)
278         
279         return true
280 end