root/luci/trunk/libs/lucid/luasrc/lucid.lua @ 5502

Revision 5502, 8.5 KB (checked in by Cyrus, 4 years ago)

LuCId: Watch threads in debug mode

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            collectgarbage("collect")
140        end
141       
142        for _, cb in ipairs(tickt) do
143            cb()
144        end
145       
146        local pid, stat, code = nixio.wait(-1, "nohang")
147        while pid and pid > 0 do
148            nixio.syslog("info", "Buried thread: " .. pid)
149            if tpids[pid] then
150                tcount = tcount - 1
151                if tpids[pid] ~= true then
152                    tpids[pid](pid, stat, code)
153                end
154            end
155            pid, stat, code = nixio.wait(-1, "nohang")
156        end
157    end
158end
159
160--- Add a file descriptor for the main loop and associate handler functions.
161-- @param polle Table containing: {fd = FILE DESCRIPTOR, events = POLL EVENTS,
162-- handler = EVENT HANDLER CALLBACK}
163-- @see unregister_pollfd
164-- @return boolean status
165function register_pollfd(polle)
166    pollt[#pollt+1] = polle
167    return true 
168end
169
170--- Unregister a file desciptor and associate handler from the main loop.
171-- @param polle Poll descriptor
172-- @see register_pollfd
173-- @return boolean status
174function unregister_pollfd(polle)
175    for k, v in ipairs(pollt) do
176        if v == polle then
177            table.remove(pollt, k)
178            return true
179        end
180    end
181    return false
182end
183
184--- Close all registered file descriptors from main loop.
185-- This is useful for forked child processes.
186function close_pollfds()
187    for k, v in ipairs(pollt) do
188        if v.fd and v.fd.close then
189            v.fd:close()
190        end
191    end
192end
193
194--- Register a tick function that will be called at each cycle of the main loop.
195-- @param cb Callback
196-- @see unregister_tick
197-- @return boolean status
198function register_tick(cb)
199    tickt[#tickt+1] = cb
200    return true
201end
202
203--- Unregister a tick function from the main loop.
204-- @param cb Callback
205-- @see register_tick
206-- @return boolean status
207function unregister_tick(cb)
208    for k, v in ipairs(tickt) do
209        if v == cb then
210            table.remove(tickt, k)
211            return true
212        end
213    end
214    return false
215end
216
217--- Tests whether a given number of processes can be created.
218-- @oaram num Processes to be created
219-- @return boolean status
220function try_process(num)
221    local threadlimit = tonumber((cursor:get(UCINAME, "main", "threadlimit")))
222    return not threadlimit or (threadlimit - tcount) >= (num or 1)
223end
224
225--- Create a new child process from a Lua function and assign a destructor.
226-- @param threadcb main function of the new process
227-- @param waitcb destructor callback
228-- @return process identifier or nil, error code, error message
229function create_process(threadcb, waitcb)
230    local threadlimit = tonumber(cursor:get(UCINAME, "main", "threadlimit"))
231    if threadlimit and tcount >= threadlimit then
232        nixio.syslog("warning", "Cannot create thread: process limit reached")
233        return nil
234    end
235    local pid, code, err = nixio.fork()
236    if pid and pid ~= 0 then
237        nixio.syslog("info", "Created thread: " .. pid)
238        tpids[pid] = waitcb or true
239        tcount = tcount + 1
240    elseif pid == 0 then
241        local code = threadcb()
242        os.exit(code)
243    else
244        nixio.syslog("err", "Unable to fork(): " .. err)
245    end
246    return pid, code, err
247end
248
249--- Prepare a daemon from a given configuration table.
250-- @param config Configuration data.
251-- @return boolean status or nil, error code, error message
252function prepare_daemon(config)
253    nixio.syslog("info", "Preparing daemon " .. config[".name"])
254    local modname = cursor:get(UCINAME, config.slave)
255    if not modname then
256        return nil, -1, "invalid slave"
257    end
258
259    local stat, module = pcall(require, _NAME .. "." .. modname)
260    if not stat or not module.prepare_daemon then
261        return nil, -2, "slave type not supported"
262    end
263   
264    config.slave = prepare_slave(config.slave)
265
266    return module.prepare_daemon(config, _M)
267end
268
269--- Prepare a slave.
270-- @param name slave name
271-- @return table containing slave module and configuration or nil, error message
272function prepare_slave(name)
273    local slave = slaves[name]
274    if not slave then
275        local config = cursor:get_all(UCINAME, name)
276       
277        local stat, module = pcall(require, config and config.entrypoint)
278        if stat then
279            slave = {module = module, config = config}
280        end
281    end
282   
283    if slave then
284        return slave
285    else
286        return nil, module
287    end
288end
289
290--- Return a list of available network interfaces on the host.
291-- @return table returned by nixio.getifaddrs()
292function get_interfaces()
293    return ifaddrs
294end
295
296--- Revoke process privileges.
297-- @param user new user name or uid
298-- @param group new group name or gid
299-- @return boolean status or nil, error code, error message
300function revoke_privileges(user, group)
301    if nixio.getuid() == 0 then
302        return nixio.setgid(group) and nixio.setuid(user)
303    end
304end
305
306--- Return a secure UCI cursor.
307-- @return UCI cursor
308function securestate()
309    local stat = nixio.fs.stat(SSTATE) or {}
310    local uid = nixio.getuid()
311    if stat.type ~= "dir" or (stat.modedec % 100) ~= 0 or stat.uid ~= uid then
312        nixio.fs.remover(SSTATE)
313        if not nixio.fs.mkdir(SSTATE, 700) then
314            local errno = nixio.errno()
315            nixio.syslog("err", "Integrity check on secure state failed!")
316            return nil, errno, nixio.perror(errno)
317        end
318    end
319   
320    return uci.cursor(nil, SSTATE)
321end
322
323--- Daemonize the process.
324-- @return boolean status or nil, error code, error message
325function daemonize()
326    if nixio.getppid() == 1 then
327        return
328    end
329   
330    local pid, code, msg = nixio.fork()
331    if not pid then
332        return nil, code, msg
333    elseif pid > 0 then
334        os.exit(0)
335    end
336   
337    nixio.setsid()
338    nixio.chdir("/")
339   
340    local devnull = nixio.open("/dev/null", nixio.open_flags("rdwr"))
341    nixio.dup(devnull, nixio.stdin)
342    nixio.dup(devnull, nixio.stdout)
343    nixio.dup(devnull, nixio.stderr)
344   
345    return true
346end
Note: See TracBrowser for help on using the browser.