From: Jo-Philipp Wich Date: Mon, 12 Nov 2018 09:48:08 +0000 (+0100) Subject: luci-base: add luci.sys.process.exec() X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=6469b653544b0ed842a7af50cccb738baaceb4d9;p=oweals%2Fluci.git luci-base: add luci.sys.process.exec() The new process.exec() function simplifies spawning external processes and capturing their stdio. Signed-off-by: Jo-Philipp Wich --- diff --git a/modules/luci-base/luasrc/sys.lua b/modules/luci-base/luasrc/sys.lua index 1436a3a23..7e4a9d63c 100644 --- a/modules/luci-base/luasrc/sys.lua +++ b/modules/luci-base/luasrc/sys.lua @@ -13,8 +13,8 @@ local luci = {} luci.util = require "luci.util" luci.ip = require "luci.ip" -local tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select = - tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select +local tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select, unpack = + tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select, unpack module "luci.sys" @@ -436,6 +436,96 @@ end process.signal = nixio.kill +local function xclose(fd) + if fd and fd:fileno() > 2 then + fd:close() + end +end + +function process.exec(command, stdout, stderr, nowait) + local out_r, out_w, err_r, err_w + if stdout then out_r, out_w = nixio.pipe() end + if stderr then err_r, err_w = nixio.pipe() end + + local pid = nixio.fork() + if pid == 0 then + nixio.chdir("/") + + local null = nixio.open("/dev/null", "w+") + if null then + nixio.dup(out_w or null, nixio.stdout) + nixio.dup(err_w or null, nixio.stderr) + nixio.dup(null, nixio.stdin) + xclose(out_w) + xclose(out_r) + xclose(err_w) + xclose(err_r) + xclose(null) + end + + nixio.exec(unpack(command)) + os.exit(-1) + end + + local _, pfds, rv = nil, {}, { code = -1, pid = pid } + + xclose(out_w) + xclose(err_w) + + if out_r then + pfds[#pfds+1] = { + fd = out_r, + cb = type(stdout) == "function" and stdout, + name = "stdout", + events = nixio.poll_flags("in", "err", "hup") + } + end + + if err_r then + pfds[#pfds+1] = { + fd = err_r, + cb = type(stderr) == "function" and stderr, + name = "stderr", + events = nixio.poll_flags("in", "err", "hup") + } + end + + while #pfds > 0 do + local nfds, err = nixio.poll(pfds, -1) + if not nfds and err ~= nixio.const.EINTR then + break + end + + local i + for i = #pfds, 1, -1 do + local rfd = pfds[i] + if rfd.revents > 0 then + local chunk, err = rfd.fd:read(4096) + if chunk and #chunk > 0 then + if rfd.cb then + rfd.cb(chunk) + else + rfd.buf = rfd.buf or {} + rfd.buf[#rfd.buf + 1] = chunk + end + else + table.remove(pfds, i) + if rfd.buf then + rv[rfd.name] = table.concat(rfd.buf, "") + end + rfd.fd:close() + end + end + end + end + + if not nowait then + _, _, rv.code = nixio.waitpid(pid) + end + + return rv +end + user = {} diff --git a/modules/luci-base/luasrc/sys.luadoc b/modules/luci-base/luasrc/sys.luadoc index 3c7f69c6e..162650e7a 100644 --- a/modules/luci-base/luasrc/sys.luadoc +++ b/modules/luci-base/luasrc/sys.luadoc @@ -271,6 +271,42 @@ Send a signal to a process identified by given pid. @return Number containing the error code if failed ]] +---[[ +Execute a process, optionally capturing stdio. + +Executes the process specified by the given argv vector, e.g. +`{ "/bin/sh", "-c", "echo 1" }` and waits for it to terminate unless a true +value has been passed for the "nowait" parameter. + +When a function value is passed for the stdout or stderr arguments, the passed +function is repeatedly called for each chunk read from the corresponding stdio +stream. The read data is passed as string containing at most 4096 bytes at a +time. + +When a true, non-function value is passed for the stdout or stderr arguments, +the data of the corresponding stdio stream is read into an internal string +buffer and returned as "stdout" or "stderr" field respectively in the result +table. + +When a true value is passed to the nowait parameter, the function does not +await process termination but returns as soon as all captured stdio streams +have been closed or - if no streams are captured - immediately after launching +the process. + +@class function +@name process.exec +@param commend Table containing the argv vector to execute +@param stdout Callback function or boolean to indicate capturing (optional) +@param stderr Callback function or boolean to indicate capturing (optional) +@param nowait Don't wait for process termination when true (optional) +@return Table containing at least the fields "code" which holds the exit + status of the invoked process or "-1" on error and "pid", which + contains the process id assigned to the spawned process. When + stdout and/or stderr capturing has been requested, it additionally + contains "stdout" and "stderr" fields respectively, holding the + captured stdio data as string. +]] + ---[[ LuCI system utilities / user related functions.