2 LuCI - Lua Development Framework
4 Copyright 2009 Steven Barth <steven@midlink.org>
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
10 http://www.apache.org/licenses/LICENSE-2.0
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"
21 local pairs, require, pcall, assert, type = pairs, require, pcall, assert, type
22 local ipairs, tonumber, collectgarbage = ipairs, tonumber, collectgarbage
32 local ifaddrs = nixio.getifaddrs()
35 state = uci.cursor_state()
40 local UCINAME = UCINAME
41 local SSTATE = "/tmp/.lucid_store"
44 --- Starts a new LuCId superprocess.
46 state:revert(UCINAME, "main")
50 local detach = cursor:get(UCINAME, "main", "daemonize")
52 local stat, code, msg = daemonize()
54 nixio.syslog("crit", "Unable to detach process: " .. msg .. "\n")
59 state:set(UCINAME, "main", "pid", nixio.getpid())
65 --- Returns the PID of the currently active LuCId process.
67 local pid = tonumber(state:get(UCINAME, "main", "pid"))
68 return pid and nixio.kill(pid, 0) and pid
71 --- Stops any running LuCId superprocess.
73 local pid = tonumber(state:get(UCINAME, "main", "pid"))
75 return nixio.kill(pid, nixio.const.SIGTERM)
80 --- Prepares the slaves, daemons and publishers, allocate resources.
82 local debug = tonumber((cursor:get(UCINAME, "main", "debug")))
84 nixio.openlog("lucid", "pid", "perror")
86 nixio.setlogmask("warning")
89 cursor:foreach(UCINAME, "daemon", function(config)
90 if config.enabled ~= "1" then
94 local key = config[".name"]
95 if not config.slave then
96 nixio.syslog("crit", "Daemon "..key.." is missing a slave\n")
99 nixio.syslog("info", "Initializing daemon " .. key)
102 state:revert(UCINAME, key)
104 local daemon, code, err = prepare_daemon(config)
106 state:set(UCINAME, key, "status", "started")
107 nixio.syslog("info", "Prepared daemon " .. key)
109 state:set(UCINAME, key, "status", "error")
110 state:set(UCINAME, key, "error", err)
111 nixio.syslog("err", "Failed to initialize daemon "..key..": "..
117 --- Run the superprocess if prepared before.
118 -- This main function of LuCId will wait for events on given file descriptors.
120 local pollint = tonumber((cursor:get(UCINAME, "main", "pollinterval")))
121 local threadlimit = tonumber((cursor:get(UCINAME, "main", "threadlimit")))
124 local stat, code = nixio.poll(pollt, pollint)
126 if stat and stat > 0 then
128 for _, polle in ipairs(pollt) do
129 if polle.revents ~= 0 and polle.handler then
130 ok = ok or polle.handler(polle)
134 -- Avoid high CPU usage if thread limit is reached
135 nixio.nanosleep(0, 100000000)
137 elseif stat == 0 then
138 ifaddrs = nixio.getifaddrs()
141 for _, cb in ipairs(tickt) do
145 local pid, stat, code = nixio.wait(-1, "nohang")
146 while pid and pid > 0 do
147 nixio.syslog("info", "Buried thread: " .. pid)
150 if tpids[pid] ~= true then
151 tpids[pid](pid, stat, code)
154 pid, stat, code = nixio.wait(-1, "nohang")
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
164 function register_pollfd(polle)
165 pollt[#pollt+1] = polle
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
173 function unregister_pollfd(polle)
174 for k, v in ipairs(pollt) do
176 table.remove(pollt, k)
183 --- Close all registered file descriptors from main loop.
184 -- This is useful for forked child processes.
185 function close_pollfds()
186 for k, v in ipairs(pollt) do
187 if v.fd and v.fd.close then
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
197 function register_tick(cb)
202 --- Unregister a tick function from the main loop.
203 -- @param cb Callback
204 -- @see register_tick
205 -- @return boolean status
206 function unregister_tick(cb)
207 for k, v in ipairs(tickt) do
209 table.remove(tickt, k)
216 --- Tests whether a given number of processes can be created.
217 -- @oaram num Processes to be created
218 -- @return boolean status
219 function try_process(num)
220 local threadlimit = tonumber((cursor:get(UCINAME, "main", "threadlimit")))
221 return not threadlimit or (threadlimit - tcount) >= (num or 1)
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
228 function 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")
234 collectgarbage("collect")
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
242 local code = threadcb()
245 nixio.syslog("err", "Unable to fork(): " .. err)
247 return pid, code, err
250 --- Prepare a daemon from a given configuration table.
251 -- @param config Configuration data.
252 -- @return boolean status or nil, error code, error message
253 function prepare_daemon(config)
254 nixio.syslog("info", "Preparing daemon " .. config[".name"])
255 local modname = cursor:get(UCINAME, config.slave)
257 return nil, -1, "invalid slave"
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"
265 config.slave = prepare_slave(config.slave)
267 return module.prepare_daemon(config, _M)
271 -- @param name slave name
272 -- @return table containing slave module and configuration or nil, error message
273 function prepare_slave(name)
274 local slave = slaves[name]
276 local config = cursor:get_all(UCINAME, name)
278 local stat, module = pcall(require, config and config.entrypoint)
280 slave = {module = module, config = config}
291 --- Return a list of available network interfaces on the host.
292 -- @return table returned by nixio.getifaddrs()
293 function get_interfaces()
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
301 function revoke_privileges(user, group)
302 if nixio.getuid() == 0 then
303 return nixio.setgid(group) and nixio.setuid(user)
307 --- Return a secure UCI cursor.
308 -- @return UCI cursor
309 function 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)
321 return uci.cursor(nil, SSTATE)
324 --- Daemonize the process.
325 -- @return boolean status or nil, error code, error message
327 if nixio.getppid() == 1 then
331 local pid, code, msg = nixio.fork()
333 return nil, code, msg
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)