summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2018-11-12 10:48:08 +0100
committerJo-Philipp Wich <jo@mein.io>2018-11-14 20:46:04 +0100
commit6469b653544b0ed842a7af50cccb738baaceb4d9 (patch)
tree39f5ab590b6bc414c52d19e20b7bbb2d781565e6
parentcf3621522815544d42d1cc5cdf65b4fd1a6b4412 (diff)
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 <jo@mein.io>
-rw-r--r--modules/luci-base/luasrc/sys.lua94
-rw-r--r--modules/luci-base/luasrc/sys.luadoc36
2 files changed, 128 insertions, 2 deletions
diff --git a/modules/luci-base/luasrc/sys.lua b/modules/luci-base/luasrc/sys.lua
index 1436a3a235..7e4a9d63cf 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 3c7f69c6e9..162650e7ac 100644
--- a/modules/luci-base/luasrc/sys.luadoc
+++ b/modules/luci-base/luasrc/sys.luadoc
@@ -272,6 +272,42 @@ Send a signal to a process identified by given pid.
]]
---[[
+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.
@class module