Skip to content

Instantly share code, notes, and snippets.

@neomantra
Last active December 22, 2017 09:38

Revisions

  1. neomantra revised this gist Dec 11, 2013. 1 changed file with 5 additions and 3 deletions.
    8 changes: 5 additions & 3 deletions spawn.lua
    Original file line number Diff line number Diff line change
    @@ -28,7 +28,9 @@ int execvp(const char *file, char *const argv[]);

    local bor = bit.bor

    local char_arr_t = ffi.typeof('const char * [?]')
    local ffi_cast = ffi.cast
    local k_char_p_arr_t = ffi.typeof('const char * [?]')
    local char_p_k_p_t = ffi.typeof('char * const *')

    local octal = function(n) return tonumber(n, 8) end
    local O_WRONLY = octal('0001')
    @@ -107,12 +109,12 @@ local function spawn(cmd_line, stdout_redirect, stderr_redirect)
    redirect(stdout_redirect, FD_STDOUT)
    redirect(stderr_redirect, FD_STDERR)

    local argv = char_arr_t(#args + 1) -- automatically NULL terminated
    local argv = k_char_p_arr_t(#args + 1) -- automatically NULL terminated
    for i = 1, #args do
    argv[i-1] = args[i] -- args is 1-based Lua table, argv is 0-based C array
    end

    local res = C.execvp(args[1], ffi.cast('char *const *', argv))
    local res = C.execvp(args[1], ffi_cast(char_p_k_p_t, argv))
    if res == -1 then error("execvp failed with " .. ffi.errno()) end
    -- HERE SHOULD BE UNREACHABLE!!
    end
  2. neomantra created this gist Dec 11, 2013.
    122 changes: 122 additions & 0 deletions spawn.lua
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,122 @@
    -- Spawn a command in the background, optionally redirecting stderr and stdout
    --
    -- requiring this file returns a function(cmd_line, stdout_redirect, stderr_redirect)
    --
    -- `cmd_line` is the command with possible arguments
    -- optional `stdout_redirect` is io.stdout, io.stderr, or a filename. default/nil is io.stdout
    -- optional `stderr_redirect` is io.stdout, io.stderr, or a filename. default/nil is io.stderr
    --
    -- Example:
    -- luajit -e 'require("spawn")("cat /etc/network/interfaces", "foo1", io.stdout)'
    --

    local ffi = require 'ffi'
    local C = ffi.C

    ffi.cdef([[
    typedef int32_t pid_t;
    pid_t fork(void);
    int open(const char *pathname, int flags, int mode);
    int close(int fd);
    int dup2(int oldfd, int newfd);
    int execvp(const char *file, char *const argv[]);
    ]])

    local bor = bit.bor

    local char_arr_t = ffi.typeof('const char * [?]')

    local octal = function(n) return tonumber(n, 8) end
    local O_WRONLY = octal('0001')
    local O_CREAT = octal('0100')
    local S_IRUSR = octal('00400') -- user has read permission
    local S_IWUSR = octal('00200') -- user has write permission

    local FD_STDOUT = 1
    local FD_STDERR = 2


    -- split a string by spaces, except that single-quoted items are kept as a single token
    local function tokenize_args( s )
    local t = {}
    local i, prev = 1, 1
    local in_q = nil

    local function capture_token()
    local w = s:sub(prev, i-1)
    if #w ~= 0 then t[#t+1] = w end
    prev = i + 1
    end

    while i <= #s do
    local c = s:sub(i, i)
    if in_q then -- close quote?
    if c == in_q then
    capture_token()
    in_q = nil
    end
    elseif c == ' ' then
    capture_token()
    elseif c == '\'' then
    in_q = '\''
    capture_token()
    end

    i = i + 1
    end

    -- final cleanup
    capture_token()
    return t
    end


    -- dest should be either 0 or 1 (FD_STDOUT or FD_STDERR)
    local function redirect(io_or_filename, dest_fd)
    if io_or_filename == nil then return end

    -- first check for regular
    if (io_or_filename == io.stdout or io_or_filename == FD_STDOUT) and dest_fd ~= FD_STDOUT then
    C.dup2(FD_STDERR, FD_STDOUT)
    elseif (io_or_filename == io.stderr or io_or_filename == FD_STDERR) and dest_fd ~= FD_STDERR then
    C.dup2(FD_STDOUT, FD_STDERR)

    -- otherwise handle file-based redirection
    else
    local fd = C.open(io_or_filename, bor(O_WRONLY, O_CREAT), bor(S_IRUSR, S_IWUSR))
    if fd < 0 then error("couldn't open file '" .. fname .. "': " .. ffi.errno()) end
    C.dup2(fd, dest_fd)
    C.close(fd)
    end
    end


    local function spawn(cmd_line, stdout_redirect, stderr_redirect)
    local args = tokenize_args(cmd_line)
    if not args or #args == 0 then error("couldn't tokenize cmd_line") end

    local pid = C.fork()
    if pid < 0 then
    error("fork failed " .. ffi.errno())
    elseif pid == 0 then -- child process

    redirect(stdout_redirect, FD_STDOUT)
    redirect(stderr_redirect, FD_STDERR)

    local argv = char_arr_t(#args + 1) -- automatically NULL terminated
    for i = 1, #args do
    argv[i-1] = args[i] -- args is 1-based Lua table, argv is 0-based C array
    end

    local res = C.execvp(args[1], ffi.cast('char *const *', argv))
    if res == -1 then error("execvp failed with " .. ffi.errno()) end
    -- HERE SHOULD BE UNREACHABLE!!
    end
    end


    return spawn