Commit 0ae9c06a authored by Karel Koci's avatar Karel Koci 🤘

Drop environment setting and add callback instead

We want to do sometimes more than just edit environment for subprocess.
Defining full fledge callback that is called in subprocess is better
alternative. It allows even more changes to be done before execution of
command.
parent 8ebc0ab6
......@@ -454,44 +454,53 @@ static int lua_cleanup_unregister_handle(lua_State *L) {
return 0;
}
struct subprocess_callback_data {
lua_State *L;
char *callback;
};
static void subprocess_callback(void *vdata) {
struct subprocess_callback_data *dt = (struct subprocess_callback_data*)vdata;
if (!dt->callback)
return;
lua_State *L = dt->L;
// This may be called from C code with a dirty stack
luaL_checkstack(L, 4, "Not enough stack space to call subprocess callback");
int handler = push_err_handler(L);
extract_registry_value(L, dt->callback);
int result = lua_pcall(L, 0, 0, handler);
ASSERT_MSG(!result, "%s", interpreter_error_result(L));
}
static int lua_subprocess(lua_State *L) {
enum log_subproc_type type = (enum log_subproc_type)luaL_checkinteger(L, 1);
// TODO verify type?
const char *message = luaL_checkstring(L, 2);
int timeout = luaL_checkinteger(L, 3);
const char *command = luaL_checkstring(L, 5);
// Environment
luaL_checktype(L, 4, LUA_TTABLE);
int env_size = 1; // For terminating NULL
lua_pushnil(L);
while (lua_next(L, 4) != 0) {
env_size++;
lua_pop(L, 1); // pop pushed key
struct subprocess_callback_data callback_data = {
.L = L,
.callback = NULL
}; // This can be on stack as we won't return while using this
int cmd_index = 4;
if (lua_isfunction(L, 4)) {
callback_data.callback = register_value(L, 4);
cmd_index++;
}
struct env_change env[env_size];
lua_pushnil(L);
int i = 0;
while (lua_next(L, 4) != 0) {
env[i].name = strdup(luaL_checkstring(L, -2));
env[i].value = strdup(luaL_checkstring(L, -1));
lua_pop(L, 1); // pop pushed key
i++;
const char *command = luaL_checkstring(L, cmd_index);
const char *args[lua_gettop(L) - cmd_index - 1];
for (int i = cmd_index + 1; i <= lua_gettop(L); i++) {
args[i - cmd_index - 1] = luaL_checkstring(L, i);
}
env[env_size - 1] = (struct env_change){NULL};
args[lua_gettop(L) - cmd_index] = NULL;
int ec;
char *output;
if (lua_gettop(L) > 5) {
const char *args[lua_gettop(L) - 4];
for (int i = 6; i <= lua_gettop(L); i++) {
args[i - 6] = luaL_checkstring(L, i);
}
args[lua_gettop(L) - 5] = NULL;
ec = lsubprocle(type, message, &output, timeout, env, command, args);
} else {
ec = lsubprocve(type, message, &output, timeout, env, command, NULL);
}
int ec = lsubproclc(type, message, &output, timeout, subprocess_callback, &callback_data, command, args);
// Free callback data callback name
if (callback_data.callback)
free(callback_data.callback);
lua_pushinteger(L, ec);
lua_pushstring(L, output);
......
......@@ -93,7 +93,7 @@ Family of functions ``subproc*`` defined in ``subprocess.h`` are exported to lua
in form of functions `subprocess`.
Function `subprocess` is defined as follows:
`subprocess(type, message, timeout, environment, command ...)`
`subprocess(type, message, timeout, callback, command ...)`
`type` is identification used to specify what type of subprocess it's. Allowed
predefined constants are as follows:
......@@ -106,9 +106,10 @@ Function `subprocess` is defined as follows:
`timeout` is time in seconds after which subprocess will be automatically
killed.
`environment` is (hash) table containing changes to be done to environment
variables. Key has to be only strings but values can be anything that can be
later converted to string using `tostring` function.
`callback` is optional function that would be called in subprocess just before
it executes given command. If you don't want to specify it then you can pass nil
or you can just drop it out (in that case command is expeted on this argument
place). This functions should has no arguments and shouldn't return anything.
`command` is any arbitrary number of string arguments that are passed as command
and its additional arguments.
......@@ -117,6 +118,10 @@ This function returns exit code of executed subprocess as first argument. And
output of this process as second argument. (Output includes both stdout and
stderr).
Note::
There is some problem with printing from lua to stdout in callback on some
platforms.
Asynchronous events
-------------------
......
......@@ -36,32 +36,31 @@ void subproc_kill_t(int timeout) {
kill_timeout = timeout;
}
static void run_child(const char *cmd, const char *args[], struct env_change env[], int p_out[2], int p_err[2]) {
static void run_child(const char *cmd, const char *args[], subproc_callback callback, void *data, int p_out[2], int p_err[2]) {
// Close unneded FDs
ASSERT(close(0) != -1);
ASSERT(close(p_out[0]) != -1);
ASSERT(dup2(p_out[1], 1) != -1 && close(p_out[1]) != -1);
ASSERT(close(p_err[0]) != -1);
ASSERT(dup2(p_err[1], 2) != -1 && close(p_err[1]) != -1);
// Set environment
for (int i = 0; env[i].name != NULL; i++) {
if (env[i].value != NULL)
setenv(env[i].name, env[i].value, true);
else
unsetenv(env[i].name);
}
// Callback
if (callback)
callback(data);
// Exec
size_t arg_c = 2; // cmd and NULL terminator
for (const char **p = args; *p; p++)
arg_c++;
char *argv[arg_c];
size_t i = 1;
for (const char **p = args; *p; p++)
argv[i++] = strdup(*p);
argv[i] = NULL;
argv[0] = strdup(cmd);
execvp(cmd, argv);
DIE("Failed to exec %s: %s", cmd, strerror(errno));
if (cmd) {
size_t arg_c = 2; // cmd and NULL terminator
for (const char **p = args; *p; p++)
arg_c++;
char *argv[arg_c];
size_t i = 1;
for (const char **p = args; *p; p++)
argv[i++] = strdup(*p);
argv[i] = NULL;
argv[0] = strdup(cmd);
execvp(cmd, argv);
DIE("Failed to exec %s: %s", cmd, strerror(errno));
} else
exit(0); // We just exit child
}
int subprocv(int timeout, const char *cmd, ...) {
......@@ -80,10 +79,10 @@ int subprocvo(int timeout, FILE *fd[2], const char *cmd, ...) {
return res;
}
int subprocveo(int timeout, FILE *fd[2], struct env_change env[], const char *cmd, ...) {
int subprocvoc(int timeout, FILE *fd[2], subproc_callback callback, void *data, const char *cmd, ...) {
va_list va_args;
va_start(va_args, cmd);
int res = vsubprocveo(timeout, fd, env, cmd, va_args);
int res = vsubprocvoc(timeout, fd, callback, data, cmd, va_args);
va_end(va_args);
return res;
}
......@@ -94,11 +93,10 @@ int subprocl(int timeout, const char *cmd, const char *args[]) {
}
int subproclo(int timeout, FILE *fd[2], const char *cmd, const char *args[]) {
struct env_change env[] = {{NULL}};
return subprocleo(timeout, fd, env, cmd, args);
return subprocloc(timeout, fd, NULL, NULL, cmd, args);
}
int subprocleo(int timeout, FILE *fd[2], struct env_change env[], const char *cmd, const char *args[]) {
int subprocloc(int timeout, FILE *fd[2], subproc_callback callback, void *data, const char *cmd, const char *args[]) {
struct log_buffer log;
log_buffer_init(&log, LL_TRACE);
if (log.f) {
......@@ -119,7 +117,7 @@ int subprocleo(int timeout, FILE *fd[2], struct env_change env[], const char *cm
if (pid == -1)
DIE("Failed to fork command %s: %s", cmd, strerror(errno));
else if (pid == 0)
run_child(cmd, args, env, p_out, p_err);
run_child(cmd, args, callback, data, p_out, p_err);
ASSERT(close(p_out[1]) != -1);
ASSERT(close(p_err[1]) != -1);
......@@ -130,12 +128,14 @@ int subprocleo(int timeout, FILE *fd[2], struct env_change env[], const char *cm
};
time_t t_start = time(NULL);
bool term_sent = false;
int dead = 0, i;
while (dead < 2) {
while (true) {
time_t rem_t = timeout - time(NULL) + t_start;
ASSERT(poll(pfds, 2, rem_t < 0 ? 0 : rem_t) != -1);
dead = 0;
for (i = 0; i < 2; i++) {
// We ignore interrupt errors as those are really not an errors
// TODO what if timeout is negative?
// TODO also this timeout is in ms not in s!!!
ASSERT_MSG(poll(pfds, 2, rem_t < 0 ? 0 : rem_t) != -1 || errno == EINTR, "Subprocess poll failed with error: %s", strerror(errno));
int dead = 0;
for (int i = 0; i < 2; i++) {
if (pfds[i].revents & POLLIN) {
char *buff[64];
ssize_t loaded;
......@@ -174,11 +174,10 @@ int vsubprocv(int timeout, const char *cmd, va_list args) {
}
int vsubprocvo(int timeout, FILE *fd[2], const char *cmd, va_list args) {
struct env_change env[] = {{NULL}};
return vsubprocveo(timeout, fd, env, cmd, args);
return vsubprocvoc(timeout, fd, NULL, NULL, cmd, args);
}
int vsubprocveo(int timeout, FILE *fd[2], struct env_change env[], const char *cmd, va_list args) {
int vsubprocvoc(int timeout, FILE *fd[2], subproc_callback callback, void *data, const char *cmd, va_list args) {
size_t argc = 1; // For NULL terminator
// Count (use copy for that)
va_list va_copy;
......@@ -191,7 +190,7 @@ int vsubprocveo(int timeout, FILE *fd[2], struct env_change env[], const char *c
size_t i = 0;
while((argv[i++] = va_arg(args, const char *)) != NULL);
argv[argc - 1] = NULL;
return subprocleo(timeout, fd, env, cmd, argv);
return subprocloc(timeout, fd, callback, data, cmd, argv);
}
int lsubprocv(enum log_subproc_type type, const char *message, char **output, int timeout, const char *cmd, ...) {
......@@ -202,38 +201,36 @@ int lsubprocv(enum log_subproc_type type, const char *message, char **output, in
return ec;
}
int lsubprocve(enum log_subproc_type type, const char *message, char **output, int timeout, struct env_change env[], const char *cmd, ...) {
int lsubprocvc(enum log_subproc_type type, const char *message, char **output, int timeout, subproc_callback callback, void *data, const char *cmd, ...) {
va_list va_args;
va_start(va_args, cmd);
int ec = lvsubprocve(type, message, output, timeout, env, cmd, va_args);
int ec = lvsubprocvc(type, message, output, timeout, callback, data, cmd, va_args);
va_end(va_args);
return ec;
}
int lsubprocl(enum log_subproc_type type, const char *message, char **output, int timeout, const char *cmd, const char *args[]) {
struct env_change env[] = {{NULL}};
return lsubprocle(type, message, output, timeout, env, cmd, args);
return lsubproclc(type, message, output, timeout, NULL, NULL, cmd, args);
}
int lsubprocle(enum log_subproc_type type, const char *message, char **output, int timeout, struct env_change env[], const char *cmd, const char *args[]) {
int lsubproclc(enum log_subproc_type type, const char *message, char **output, int timeout, subproc_callback callback, void *data, const char *cmd, const char *args[]) {
struct log_subproc lsp;
log_subproc_open(&lsp, type, message);
FILE *fds[] = {lsp.out, lsp.err};
int ec = subprocleo(timeout, fds, env, cmd, args);
int ec = subprocloc(timeout, fds, callback, data, cmd, args);
log_subproc_close(&lsp, ec, output);
return ec;
}
int lvsubprocv(enum log_subproc_type type, const char *message, char **output, int timeout, const char *cmd, va_list args) {
struct env_change env[] = {{NULL}};
return lsubprocve(type, message, output, timeout, env, cmd, args);
return lsubprocvc(type, message, output, timeout, NULL, NULL, cmd, args);
}
int lvsubprocve(enum log_subproc_type type, const char *message, char **output, int timeout, struct env_change env[], const char *cmd, va_list args) {
int lvsubprocvc(enum log_subproc_type type, const char *message, char **output, int timeout, subproc_callback callback, void *data, const char *cmd, va_list args) {
struct log_subproc lsp;
log_subproc_open(&lsp, type, message);
FILE *fds[] = {lsp.out, lsp.err};
int ec = vsubprocveo(timeout, fds, env, cmd, args);
int ec = vsubprocvoc(timeout, fds, callback, data, cmd, args);
log_subproc_close(&lsp, ec, output);
return ec;
}
......@@ -24,15 +24,12 @@
#include <stdio.h>
#include "logging.h"
// Structure used for passing changes to environment
struct env_change {
const char *name, *value;
};
// Set subproc kill timeout. This is timeout used when primary timeout runs out
// and SIGTERM is send but process still doesn't dies.
void subproc_kill_t(int timeout);
typedef void (*subproc_callback)(void *data);
/*
This runs non-interactive programs as subprocess. It closes stdin and pipes stdout
and stderr trough logging system.
......@@ -50,20 +47,20 @@ Returned status field from wait call. See manual for wait on how to decode it.
*/
int subprocv(int timeout, const char *command, ...); // (char *) NULL
int subprocvo(int timeout, FILE *fd[2], const char *command, ...); // (char *) NULL
int subprocveo(int timeout, FILE *fd[2], struct env_change env[], const char *command, ...); // (char *) NULL
int subprocvoc(int timeout, FILE *fd[2], subproc_callback callback, void *data, const char *command, ...); // (char *) NULL
int subprocl(int timeout, const char *command, const char *args[]);
int subproclo(int timeout, FILE *fd[2], const char *command, const char *args[]);
int subprocleo(int timeout, FILE *fd[2], struct env_change env[], const char *command, const char *args[]);
int subprocloc(int timeout, FILE *fd[2], subproc_callback callback, void *data, const char *command, const char *args[]);
int vsubprocv(int timeout, const char *command, va_list args);
int vsubprocvo(int timeout, FILE *fd[2], const char *command, va_list args);
int vsubprocveo(int timeout, FILE *fd[2], struct env_change env[], const char *command, va_list args);
int vsubprocvoc(int timeout, FILE *fd[2], subproc_callback callback, void *data, const char *command, va_list args);
// Following functions integrate log_subproc with subproc to enable logging of subprocess output.
int lsubprocv(enum log_subproc_type type, const char *message, char **output, int timeout, const char *command, ...);
int lsubprocve(enum log_subproc_type type, const char *message, char **output, int timeout, struct env_change env[], const char *command, ...);
int lsubprocvc(enum log_subproc_type type, const char *message, char **output, int timeout, subproc_callback callback, void *data, const char *command, ...);
int lsubprocl(enum log_subproc_type type, const char *message, char **output, int timeout, const char *command, const char *args[]);
int lsubprocle(enum log_subproc_type type, const char *message, char **output, int timeout, struct env_change env[], const char *command, const char *args[]);
int lsubproclc(enum log_subproc_type type, const char *message, char **output, int timeout, subproc_callback callback, void *data, const char *command, const char *args[]);
int lvsubprocv(enum log_subproc_type type, const char *message, char **output, int timeout, const char *command, va_list args);
int lvsubprocve(enum log_subproc_type type, const char *message, char **output, int timeout, struct env_change env[], const char *command, va_list args);
int lvsubprocvc(enum log_subproc_type type, const char *message, char **output, int timeout, subproc_callback callback, void *data, const char *command, va_list args);
#endif
......@@ -49,61 +49,76 @@ START_TEST(timeout) {
}
END_TEST
struct buffs {
FILE *fds[2];
char *b_out, *b_err;
size_t s_out, s_err;
};
static struct buffs *buffs_init() {
struct buffs *bfs = malloc(sizeof *bfs);
bfs->fds[0] = open_memstream(&bfs->b_out, &bfs->s_out);
bfs->fds[1] = open_memstream(&bfs->b_err, &bfs->s_err);
return bfs;
}
static void buffs_assert(struct buffs *bfs, const char *out, const char *err) {
fflush(bfs->fds[0]);
fflush(bfs->fds[1]);
ck_assert(strcmp(out, bfs->b_out) == 0);
ck_assert(strcmp(err, bfs->b_err) == 0);
rewind(bfs->fds[0]);
rewind(bfs->fds[1]);
bfs->b_out[0] = '\0';
bfs->b_err[0] = '\0';
}
static void buffs_free(struct buffs *bfs) {
fclose(bfs->fds[0]);
fclose(bfs->fds[1]);
free(bfs->b_out);
free(bfs->b_err);
free(bfs);
}
START_TEST(output) {
subproc_kill_t(0);
char *buff_out, *buff_err;
size_t size_out, size_err;
FILE *ff_out = open_memstream(&buff_out, &size_out);
FILE *ff_err = open_memstream(&buff_err, &size_err);
FILE *fds[] = {ff_out, ff_err};
#define BUFF_ASSERT(STDOUT, STDERR) do { \
fflush(ff_out); \
fflush(ff_err); \
ck_assert(strcmp(STDOUT, buff_out) == 0); \
ck_assert(strcmp(STDERR, buff_err) == 0); \
rewind(ff_out); \
rewind(ff_err); \
buff_out[0] = '\0'; \
buff_err[0] = '\0'; \
} while(0)
struct buffs *bfs = buffs_init();
// Echo to stdout
ck_assert(subprocvo(1, fds, "echo", "hello", NULL) == 0);
BUFF_ASSERT("hello\n", "");
ck_assert(subprocvo(1, bfs->fds, "echo", "hello", NULL) == 0);
buffs_assert(bfs, "hello\n", "");
// Echo to stderr
ck_assert(subprocvo(1, fds, "sh", "-c", "echo hello >&2", NULL) == 0);
BUFF_ASSERT("", "hello\n");
#undef BUFF_ASSERT
ck_assert(subprocvo(1, bfs->fds, "sh", "-c", "echo hello >&2", NULL) == 0);
buffs_assert(bfs, "", "hello\n");
fclose(ff_out);
fclose(ff_err);
free(buff_out);
free(buff_err);
buffs_free(bfs);
}
END_TEST
START_TEST(environment) {
static void callback_test(void *data) {
if (data)
printf("%s", (const char *)data);
else
printf("hello");
}
START_TEST(callback) {
subproc_kill_t(0);
char *buff;
size_t size;
FILE *ff = open_memstream(&buff, &size);
FILE *devnull = fopen("/dev/null", "w");
FILE *fds[] = {ff, devnull};
struct env_change env[] = {
{.name = "TESTME", .value = "Hello" },
{NULL}
};
ck_assert(subprocveo(1, fds, env, "sh", "-c", "echo $TESTME", NULL) == 0);
fflush(ff);
ck_assert(strcmp("Hello\n", buff) == 0);
fclose(ff);
fclose(devnull);
free(buff);
struct buffs *bfs = buffs_init();
// Without data
ck_assert(subprocloc(1, bfs->fds, callback_test, NULL, NULL, NULL) == 0);
buffs_assert(bfs, "hello", "");
// With data
ck_assert(subprocvoc(1, bfs->fds, callback_test, "Hello again", NULL, NULL) == 0);
buffs_assert(bfs, "Hello again", "");
buffs_free(bfs);
}
END_TEST
......@@ -114,7 +129,8 @@ Suite *gen_test_suite(void) {
tcase_add_test(subproc, exit_code);
tcase_add_test(subproc, timeout);
tcase_add_test(subproc, output);
tcase_add_test(subproc, environment);
suite_add_tcase(result, subproc);
tcase_add_test(subproc, callback);
suite_add_tcase(
result, subproc);
return result;
}
......@@ -22,33 +22,46 @@ require 'lunit'
module("subproc", package.seeall, lunit.testcase)
function test_exit_code()
local ok, out = subprocess(LST_HOOK, "Test: true", 1, {}, "true")
local ok, out = subprocess(LST_HOOK, "Test: true", 1, "true")
assert_equal(0, ok)
assert_equal("", out)
local ok, out = subprocess(LST_HOOK, "Test: false", 1, {}, "false")
local ok, out = subprocess(LST_HOOK, "Test: false", 1, "false")
assert_not_equal(0, ok)
assert_equal("", out)
end
function test_output()
local ok, out = subprocess(LST_HOOK, "Test: echo", 1, {}, "echo", "hello")
local ok, out = subprocess(LST_HOOK, "Test: echo", 1, "echo", "hello")
assert_equal(0, ok)
assert_equal("hello\n", out)
local ok, out = subprocess(LST_HOOK, "Test: echo stderr", 1, {}, "sh", "-c", "echo hello >&2")
local ok, out = subprocess(LST_HOOK, "Test: echo stderr", 1, "sh", "-c", "echo hello >&2")
assert_equal(0, ok)
assert_equal("hello\n", out)
end
function test_timeout()
local ok, out = subprocess(LST_HOOK, "Test: sleep", 1, {}, "sleep", "2")
local ok, out = subprocess(LST_HOOK, "Test: sleep", 1, "sleep", "2")
assert_not_equal(0, ok)
assert_equal("", out)
end
function test_env()
local ok, out = subprocess(LST_HOOK, "Test: env", 1, {['TESTMSG'] = 'hello'}, "sh", "-c", "echo $TESTMSG")
function test_callback()
subprocess_kill_timeout(0)
local ok, out = subprocess(LST_HOOK, "Test: env", 1, function () io.stderr:write("Hello callback\n") end, "true")
assert_equal(0, ok)
assert_equal("hello\n", out)
assert_equal("Hello callback\n", out)
--[[
We are testing here with stderr for a reason. There seems to be some problem
with lua's stdout and dup. No lua stdout works after dup2 on f.d. 1 on some
testing platforms. We are not planning to print messages from lua from
subprocess on daily base so let's not care.
]]
local ok, out = subprocess(LST_HOOK, "Test: env", 1, function () setenv("TESTENV", "Hello env") end, "sh", "-c", "echo $TESTENV")
assert_equal(0, ok)
assert_equal("Hello env\n", out)
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment