diff options
author | Daniel Silverstone <dsilvers@digital-scurf.org> | 2012-03-07 19:41:37 +0000 |
---|---|---|
committer | Daniel Silverstone <dsilvers@digital-scurf.org> | 2012-03-07 19:41:37 +0000 |
commit | ff5a761214f9d918cb6ca402e5d5b6e181ab899a (patch) | |
tree | ff8170fb43daa21ceab464662aa491d5e3e01c05 /luxio | |
parent | a4ee52ea92648b714b766834df6c96e4be3807c5 (diff) | |
download | luxio-ff5a761214f9d918cb6ca402e5d5b6e181ab899a.tar.gz |
SUBPROCESS: Simple subprocess runner/reaper
Diffstat (limited to 'luxio')
-rw-r--r-- | luxio/subprocess.lua | 275 |
1 files changed, 275 insertions, 0 deletions
diff --git a/luxio/subprocess.lua b/luxio/subprocess.lua new file mode 100644 index 0000000..f064593 --- /dev/null +++ b/luxio/subprocess.lua @@ -0,0 +1,275 @@ +-- Light Unix I/O for Lua +-- Copyright 2012 Daniel Silverstone <dsilvers@digital-scurf.org> +-- +-- Distributed under the same terms as Lua itself (MIT). +-- +-- A wrapper for managing sub processes +------------------------------------------------------------------------------- +-- +-- sp = require "luxio.subprocess" +-- +-- +-- proc = sp.spawn { +-- "sleep", "10", +-- exe = "/usr/bin/sleep" +-- } +-- +-- how, why = proc:wait(noblock) +-- noblock, if true, won't block until proc exits. +-- +-- You can redirect stdin/stdout/stderr for subprocesses +-- by passing FDs or simple file handles as std{in,out,err} in +-- the arguments to run or spawn. +-- +-- If you provide sp.PIPE then a pipe will be created for you +-- and the proc's std{in,out,err} will be the other end of it. +-- + +local l = require 'luxio' +local sio = require 'luxio.simple' + +local fork = l.fork +local waitpid = l.waitpid +local exec = l.exec +local execp = l.execp +local pipe = l.pipe +local close = l.close +local dup = l.dup +local dup2 = l.dup2 +local fcntl = l.fcntl +local bclear = l.bit.bclear +local wrap_fd = sio.wrap_fd + +local F_SETFD = l.F_SETFD +local F_GETFD = l.F_GETFD + +local WIFEXITED = l.WIFEXITED +local WEXITSTATUS = l.WEXITSTATUS +local WIFSIGNALLED = l.WIFSIGNALLED +local WTERMSIG = l.WTERMSIG +local WNOHANG = l.WNOHANG + +local assert = assert +local unpack = unpack +local pairs = pairs +local error = error + +l = nil +sio = nil + +local _PIPE = {} + +local function _wait(proc, nohang) + local pid, status = waitpid(proc.pid, nohang and WNOHANG or 0) + if pid == -1 then + return "error", status + end + if pid == 0 then + return "running", 0 + end + + if WIFEXITED(status) ~= 0 then + return "exit", WEXITSTATUS(status) + end + + if WIFSIGNALLED(status) ~= 0 then + return "signal", WTERMSIG(status) + end + + return "unknown", status +end + +local function _spawn(t) + -- Stage one, normalise our process + local proc = {args={}} + for k, v in pairs(t) do + proc.args[k] = v + end + + if proc.args[0] == nil then + if proc.args.exe then + proc.args[0] = proc.args.exe + else + proc.args[0] = proc.args[1] + if proc.args[0]:match("^/") then + proc.args.exe = proc.args[0] + end + end + end + + -- Choose which exec function to use, based on whether or not + -- we have an explicit exe. + local exec_fn = proc.args.exe and exec or execp + -- And prepare a local copy of the exe based on the args. + local exe = proc.args.exe or proc.args[0] + + assert(exe, "Unable to continue, nothing to run") + + -- Simple verification of the FDs. + local function check(v) + if proc.args[v] then + -- Something to do for handle + if proc.args[v] == _PIPE then + -- For now, nothing, later we'll handle this + elseif type(proc.args[v]) == "table" then + -- Simple handle, extract the FD + proc.args[v] = proc.args[v].fd + elseif type(proc.args[v]) == "number" then + -- FD, nothing to do for now + else + assert(false, v .. " isn't a PIPE, Simple Handle or FD") + end + end + end + + check "stdin" + check "stdout" + check "stderr" + + + + -- Beyond here, if we get errors, we *MUST* clean up FDs + local parent_close = {} + local child_close = {} + + local function _assert(m,v) + if not m then + for fd in pairs(parent_close) do + close(fd) + end + for fd in pairs(child_close) do + close(fd) + end + error(v) + end + end + + local function do_pipe(v) + if proc.args[v] == _PIPE then + local fds = {} + local res, errno = pipe(fds) + _assert(res == 0, "Unable to pipe()") + proc.args[v] = fds[2] + proc[v] = fds[1] + parent_close[proc.args[v]] = true + child_close[proc[v]] = true + end + end + do_pipe "stdin" + do_pipe "stdout" + do_pipe "stderr" + + if proc.stdin then + -- Swap stdin over. + proc.stdin, proc.args.stdin = proc.args.stdin, proc.stdin + parent_close[proc.stdin] = nil + parent_close[proc.args.stdin] = true + child_close[proc.stdin] = true + child_close[proc.args.stdin] = nil + end + + proc.args.stdin = proc.args.stdin or 0 + proc.args.stdout = proc.args.stdout or 1 + proc.args.stderr = proc.args.stderr or 2 + + -- Finally, let's fork + + local pid, errno = fork() + + _assert(pid > -1, "Unable to fork()") + + if pid == 0 then + local function check(m) + if m == -1 then + _exit(1) + end + return m + end + + -- Set up the FDs. + if proc.args.stdin ~= 0 then + check(dup2(proc.args.stdin, 0)) + end + if proc.args.stdout ~= 1 then + if proc.args.stderr == 1 then + proc.args.old_stdout = check(dup(1)) + end + check(dup2(proc.args.stdout, 1)) + end + if proc.args.stderr ~= 2 then + if proc.args.stderr == 1 and proc.args.old_stdout then + check(dup2(proc.args.old_stdout, 2)) + else + check(dup2(proc.args.stderr, 2)) + end + end + if proc.args.old_stdout then + close(proc.args.old_stdout) + end + -- FDs 0, 1, 2 initialised, ensure they won't close on exec + fcntl(0, F_SETFD, bclear(fcntl(0, F_GETFD), 1)) + fcntl(0, F_SETFD, bclear(fcntl(1, F_GETFD), 1)) + fcntl(0, F_SETFD, bclear(fcntl(2, F_GETFD), 1)) + + -- Finally, close the parent halves of any pipes + for fd in pairs(child_close) do + close(fd) + end + + -- Run the child process + exec_fn(exe, unpack(proc.args)) + + -- Exec failed, exit without doing lua cleanups + _exit(1) + end + + -- The parent + for fd in pairs(parent_close) do + close(fd) + end + + proc.pid = pid + + proc.wait = _wait + + return proc +end + +local function _spawn_simple(t) + local t_copy = {} + for k, v in pairs(t) do + t_copy[k] = v + end + + local piping = nil + + if type(t_copy.stdin) == "string" then + piping = t_copy.stdin + t_copy.stdin = _PIPE + end + + local proc = _spawn(t_copy) + + local function doit(v) + if proc[v] then + proc[v] = wrap_fd(proc[v]) + end + end + doit "stdin" + doit "stdout" + doit "stderr" + + if piping then + proc.stdin:write(piping) + proc.stdin:close() + proc.stdin = nil + end + + return proc +end + +return { + spawn = _spawn, + spawn_simple = _spawn_simple, + PIPE = _PIPE, +} |