luci-base: add luci.sys.process.exec()
authorJo-Philipp Wich <jo@mein.io>
Mon, 12 Nov 2018 09:48:08 +0000 (10:48 +0100)
committerJo-Philipp Wich <jo@mein.io>
Wed, 14 Nov 2018 19:46:04 +0000 (20:46 +0100)
The new process.exec() function simplifies spawning external processes
and capturing their stdio.

Signed-off-by: Jo-Philipp Wich <jo@mein.io>
modules/luci-base/luasrc/sys.lua
modules/luci-base/luasrc/sys.luadoc

index 1436a3a235f3b556d68d8b1baf4fb4580485974e..7e4a9d63cf9c19a14a31a91079ddfa4180d66327 100644 (file)
@@ -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 = {}
 
index 3c7f69c6e9ecf40af11ddef78e6c8b59faed025a..162650e7ac025adfc53976405e54a760e1243930 100644 (file)
@@ -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.