-
-
Save msva/3d278d06c796f16e693245cd7e3bc989 to your computer and use it in GitHub Desktop.
Using LuaJIT FFI, spawn a Linux command in the background.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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 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') | |
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 = 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_p_k_p_t, argv)) | |
if res == -1 then error("execvp failed with " .. ffi.errno()) end | |
-- HERE SHOULD BE UNREACHABLE!! | |
end | |
end | |
return spawn |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment