root/luci/branches/luci-0.10/libs/lucid/luasrc/lucid.lua @ 7333

Revision 7333, 8.5 KB (checked in by jow, 2 years ago)

luci-0Ã.10: merge r7332

Line 
1--[[
2LuCI - Lua Development Framework
3
4Copyright 2009 Steven Barth <steven@midlink.org>
5
6Licensed under the Apache License, Version 2.0 (the "License");
7you may not use this file except in compliance with the License.
8You may obtain a copy of the License at
9
10http://www.apache.org/licenses/LICENSE-2.0
11
12$Id$
13]]
14
15local nixio = require "nixio"
16local table = require "table"
17local uci = require "luci.model.uci"
18local os = require "os"
19local io = require "io"
20
21local pairs, require, pcall, assert, type = pairs, require, pcall, assert, type
22local ipairs, tonumber, collectgarbage = ipairs, tonumber, collectgarbage
23
24
25module "luci.lucid"
26
27local slaves = {}
28local pollt  = {}
29local tickt  = {}
30local tpids  = {}
31local tcount = 0
32local ifaddrs = nixio.getifaddrs()
33
34cursor = uci.cursor()
35state  = uci.cursor_state()
36UCINAME = "lucid"
37
38local cursor = cursor
39local state = state
40local UCINAME = UCINAME
41local SSTATE = "/tmp/.lucid_store"
42
43
44--- Starts a new LuCId superprocess.
45function start()
46    state:revert(UCINAME, "main")
47
48    prepare()
49
50    local detach = cursor:get(UCINAME, "main", "daemonize")
51    if detach == "1" then
52        local stat, code, msg = daemonize()
53        if not stat then
54            nixio.syslog("crit", "Unable to detach process: " .. msg .. "\n")
55            ox.exit(2)
56        end
57    end
58
59    state:set(UCINAME, "main", "pid", nixio.getpid())
60    state:save(UCINAME)
61
62    run()
63end
64
65--- Returns the PID of the currently active LuCId process.
66function running()
67    local pid = tonumber(state:get(UCINAME, "main", "pid"))
68    return pid and nixio.kill(pid, 0) and pid
69end
70
71--- Stops any running LuCId superprocess.
72function stop()
73    local pid = tonumber(state:get(UCINAME, "main", "pid"))
74    if pid then
75        return nixio.kill(pid, nixio.const.SIGTERM)
76    end
77    return false
78end
79
80--- Prepares the slaves, daemons and publishers, allocate resources.
81function prepare()
82    local debug = tonumber((cursor:get(UCINAME, "main", "debug")))
83   
84    nixio.openlog("lucid", "pid", "perror")
85    if debug ~= 1 then
86        nixio.setlogmask("warning")
87    end
88   
89    cursor:foreach(UCINAME, "daemon", function(config)
90        if config.enabled ~= "1" then
91            return
92        end
93   
94        local key = config[".name"]
95        if not config.slave then
96            nixio.syslog("crit", "Daemon "..key.." is missing a slave\n")
97            os.exit(1)
98        else
99            nixio.syslog("info", "Initializing daemon " .. key)
100        end
101       
102        state:revert(UCINAME, key)
103       
104        local daemon, code, err = prepare_daemon(config)
105        if daemon then
106            state:set(UCINAME, key, "status", "started")
107            nixio.syslog("info", "Prepared daemon " .. key)
108        else
109            state:set(UCINAME, key, "status", "error")
110            state:set(UCINAME, key, "error", err)
111            nixio.syslog("err", "Failed to initialize daemon "..key..": "..
112            err .. "\n")
113        end
114    end)
115end
116   
117--- Run the superprocess if prepared before.
118-- This main function of LuCId will wait for events on given file descriptors.
119function run()
120    local pollint = tonumber((cursor:get(UCINAME, "main", "pollinterval")))
121    local threadlimit = tonumber((cursor:get(UCINAME, "main", "threadlimit")))
122
123    while true do
124        local stat, code = nixio.poll(pollt, pollint)
125       
126        if stat and stat > 0 then
127            local ok = false
128            for _, polle in ipairs(pollt) do
129                if polle.revents ~= 0 and polle.handler then
130                    ok = ok or polle.handler(polle)
131                end
132            end
133            if not ok then
134                -- Avoid high CPU usage if thread limit is reached
135                nixio.nanosleep(0, 100000000)
136            end
137        elseif stat == 0 then
138            ifaddrs = nixio.getifaddrs()
139        end
140       
141        for _, cb in ipairs(tickt) do
142            cb()
143        end
144       
145        local pid, stat, code = nixio.wait(-1, "nohang")
146        while pid and pid > 0 do
147            nixio.syslog("info", "Buried thread: " .. pid)
148            if tpids[pid] then
149                tcount = tcount - 1
150                if tpids[pid] ~= true then
151                    tpids[pid](pid, stat, code)
152                end
153            end
154            pid, stat, code = nixio.wait(-1, "nohang")
155        end
156    end
157end
158
159--- Add a file descriptor for the main loop and associate handler functions.
160-- @param polle Table containing: {fd = FILE DESCRIPTOR, events = POLL EVENTS,
161-- handler = EVENT HANDLER CALLBACK}
162-- @see unregister_pollfd
163-- @return boolean status
164function register_pollfd(polle)
165    pollt[#pollt+1] = polle
166    return true 
167end
168
169--- Unregister a file desciptor and associate handler from the main loop.
170-- @param polle Poll descriptor
171-- @see register_pollfd
172-- @return boolean status
173function unregister_pollfd(polle)
174    for k, v in ipairs(pollt) do
175        if v == polle then
176            table.remove(pollt, k)
177            return true
178        end
179    end
180    return false
181end
182
183--- Close all registered file descriptors from main loop.
184-- This is useful for forked child processes.
185function close_pollfds()
186    for k, v in ipairs(pollt) do
187        if v.fd and v.fd.close then
188            v.fd:close()
189        end
190    end
191end
192
193--- Register a tick function that will be called at each cycle of the main loop.
194-- @param cb Callback
195-- @see unregister_tick
196-- @return boolean status
197function register_tick(cb)
198    tickt[#tickt+1] = cb
199    return true
200end
201
202--- Unregister a tick function from the main loop.
203-- @param cb Callback
204-- @see register_tick
205-- @return boolean status
206function unregister_tick(cb)
207    for k, v in ipairs(tickt) do
208        if v == cb then
209            table.remove(tickt, k)
210            return true
211        end
212    end
213    return false
214end
215
216--- Tests whether a given number of processes can be created.
217-- @oaram num Processes to be created
218-- @return boolean status
219function try_process(num)
220    local threadlimit = tonumber((cursor:get(UCINAME, "main", "threadlimit")))
221    return not threadlimit or (threadlimit - tcount) >= (num or 1)
222end
223
224--- Create a new child process from a Lua function and assign a destructor.
225-- @param threadcb main function of the new process
226-- @param waitcb destructor callback
227-- @return process identifier or nil, error code, error message
228function create_process(threadcb, waitcb)
229    local threadlimit = tonumber(cursor:get(UCINAME, "main", "threadlimit"))
230    if threadlimit and tcount >= threadlimit then
231        nixio.syslog("warning", "Cannot create thread: process limit reached")
232        return nil
233    else
234        collectgarbage("collect")
235    end
236    local pid, code, err = nixio.fork()
237    if pid and pid ~= 0 then
238        nixio.syslog("info", "Created thread: " .. pid)
239        tpids[pid] = waitcb or true
240        tcount = tcount + 1
241    elseif pid == 0 then
242        local code = threadcb()
243        os.exit(code)
244    else
245        nixio.syslog("err", "Unable to fork(): " .. err)
246    end
247    return pid, code, err
248end
249
250--- Prepare a daemon from a given configuration table.
251-- @param config Configuration data.
252-- @return boolean status or nil, error code, error message
253function prepare_daemon(config)
254    nixio.syslog("info", "Preparing daemon " .. config[".name"])
255    local modname = cursor:get(UCINAME, config.slave)
256    if not modname then
257        return nil, -1, "invalid slave"
258    end
259
260    local stat, module = pcall(require, _NAME .. "." .. modname)
261    if not stat or not module.prepare_daemon then
262        return nil, -2, "slave type not supported"
263    end
264   
265    config.slave = prepare_slave(config.slave)
266
267    return module.prepare_daemon(config, _M)
268end
269
270--- Prepare a slave.
271-- @param name slave name
272-- @return table containing slave module and configuration or nil, error message
273function prepare_slave(name)
274    local slave = slaves[name]
275    if not slave then
276        local config = cursor:get_all(UCINAME, name)
277       
278        local stat, module = pcall(require, config and config.entrypoint)
279        if stat then
280            slave = {module = module, config = config}
281        end
282    end
283   
284    if slave then
285        return slave
286    else
287        return nil, module
288    end
289end
290
291--- Return a list of available network interfaces on the host.
292-- @return table returned by nixio.getifaddrs()
293function get_interfaces()
294    return ifaddrs
295end
296
297--- Revoke process privileges.
298-- @param user new user name or uid
299-- @param group new group name or gid
300-- @return boolean status or nil, error code, error message
301function revoke_privileges(user, group)
302    if nixio.getuid() == 0 then
303        return nixio.setgid(group) and nixio.setuid(user)
304    end
305end
306
307--- Return a secure UCI cursor.
308-- @return UCI cursor
309function securestate()
310    local stat = nixio.fs.stat(SSTATE) or {}
311    local uid = nixio.getuid()
312    if stat.type ~= "dir" or (stat.modedec % 100) ~= 0 or stat.uid ~= uid then
313        nixio.fs.remover(SSTATE)
314        if not nixio.fs.mkdir(SSTATE, 700) then
315            local errno = nixio.errno()
316            nixio.syslog("err", "Integrity check on secure state failed!")
317            return nil, errno, nixio.perror(errno)
318        end
319    end
320   
321    return uci.cursor(nil, SSTATE)
322end
323
324--- Daemonize the process.
325-- @return boolean status or nil, error code, error message
326function daemonize()
327    if nixio.getppid() == 1 then
328        return
329    end
330   
331    local pid, code, msg = nixio.fork()
332    if not pid then
333        return nil, code, msg
334    elseif pid > 0 then
335        os.exit(0)
336    end
337   
338    nixio.setsid()
339    nixio.chdir("/")
340   
341    local devnull = nixio.open("/dev/null", nixio.open_flags("rdwr"))
342    nixio.dup(devnull, nixio.stdin)
343    nixio.dup(devnull, nixio.stdout)
344    nixio.dup(devnull, nixio.stderr)
345   
346    return true
347end
Note: See TracBrowser for help on using the browser.