major refactor to the process API

- process.new() is replaced by the Process class
- Process() accepts redirection options and environment variables
- Process:read() does not accept `tries`
- Process:read_stdout() and process:read_stderr() is introduced
- Process:read() now accepts stream as it's first parameter
- Process:running() actually detects if process is running
- Process.returncode() returns the exit code (or exit SIGNAL on POSIX when available)
- constants are moved to process.constants
- process_t.L is removed
This commit is contained in:
takase1121 2021-07-06 17:00:41 +08:00
parent 022f92bcd4
commit ccbf676f50
1 changed files with 234 additions and 241 deletions

View File

@ -12,26 +12,133 @@
#define READ_BUF_SIZE 4096 #define READ_BUF_SIZE 4096
#define L_GETTABLE(L, idx, key, conv, def) ( \
lua_getfield(L, idx, key), \
conv(L, -1, def) \
)
#define L_GETNUM(L, idx, key, def) L_GETTABLE(L, idx, key, luaL_optnumber, def)
#define L_GETSTR(L, idx, key, def) L_GETTABLE(L, idx, key, luaL_optstring, def)
#define L_SETNUM(L, idx, key, n) (lua_pushnumber(L, n), lua_setfield(L, idx - 1, key))
#define L_RETURN_REPROC_ERROR(L, code) { \
lua_pushnil(L); \
lua_pushstring(L, reproc_strerror(code)); \
lua_pushnumber(L, code); \
return 3; \
}
#define ASSERT_MALLOC(ptr) \
if (ptr == NULL) \
L_RETURN_REPROC_ERROR(L, REPROC_ENOMEM)
#define ASSERT_REPROC_ERRNO(L, code) { \
if (code < 0) \
L_RETURN_REPROC_ERROR(L, code) \
}
typedef struct { typedef struct {
reproc_t * process; reproc_t * process;
lua_State* L; bool running;
int returncode;
} process_t; } process_t;
// this function should be called instead of reproc_wait
static int poll_process(process_t* proc, int timeout)
{
int ret = reproc_wait(proc->process, timeout);
if (ret != REPROC_ETIMEDOUT) {
proc->running = false;
proc->returncode = ret;
}
return ret;
}
static int process_new(lua_State* L) static int process_new(lua_State* L)
{ {
process_t* self = (process_t*) lua_newuserdata( int cmd_len = lua_rawlen(L, 1) + 1;
L, sizeof(process_t) const char** cmd = malloc(sizeof(char *) * cmd_len);
ASSERT_MALLOC(cmd);
cmd[cmd_len] = NULL;
for(int i = 0; i < cmd_len; i++) {
lua_rawgeti(L, 1, i + 1);
cmd[i] = luaL_checkstring(L, -1);
lua_pop(L, 1);
}
int deadline = L_GETNUM(L, 2, "timeout", 0);
const char* cwd =L_GETSTR(L, 2, "cwd", NULL);
int redirect_in = L_GETNUM(L, 2, "stdin", REPROC_REDIRECT_DEFAULT);
int redirect_out = L_GETNUM(L, 2, "stdout", REPROC_REDIRECT_DEFAULT);
int redirect_err = L_GETNUM(L, 2, "stderr", REPROC_REDIRECT_DEFAULT);
lua_pop(L, 5); // remove args we just read
if (
redirect_in > REPROC_REDIRECT_STDOUT
|| redirect_out > REPROC_REDIRECT_STDOUT
|| redirect_err > REPROC_REDIRECT_STDOUT)
{
lua_pushnil(L);
lua_pushliteral(L, "redirect to handles, FILE* and paths are not supported");
return 2;
}
// env
luaL_getsubtable(L, 2, "env");
lua_pushnil(L);
int env_len = 1;
while (lua_next(L, -2) != 0) {
env_len++;
lua_pop(L, 1);
}
const char** env = malloc(sizeof(char *) * env_len);
ASSERT_MALLOC(env);
env[env_len] = NULL;
int i = 0;
lua_pushnil(L);
while (lua_next(L, -2) != 0) {
lua_pushliteral(L, "=");
lua_pushvalue(L, -3); // push the key to the top
lua_concat(L, 3); // key=value
env[i++] = luaL_checkstring(L, -1);
lua_pop(L, 1);
}
reproc_t* proc = reproc_new();
int out = reproc_start(
proc,
(const char* const*) cmd,
(reproc_options) {
.working_directory = cwd,
.deadline = deadline,
.nonblocking = true,
.env = {
.behavior = REPROC_ENV_EXTEND,
.extra = env
},
.redirect = {
.in.type = redirect_in,
.out.type = redirect_out,
.err.type = redirect_err
}
}
); );
memset(self, 0, sizeof(process_t)); ASSERT_REPROC_ERRNO(L, out);
self->process = NULL; process_t* self = lua_newuserdata(L, sizeof(process_t));
self->L = L; self->process = proc;
self->running = true;
luaL_getmetatable(L, API_TYPE_PROCESS);
lua_setmetatable(L, -2);
// this is equivalent to using lua_setmetatable()
luaL_setmetatable(L, API_TYPE_PROCESS);
return 1; return 1;
} }
@ -39,24 +146,24 @@ static int process_strerror(lua_State* L)
{ {
int error_code = luaL_checknumber(L, 1); int error_code = luaL_checknumber(L, 1);
if(error_code){ if (error_code < 0)
lua_pushstring( lua_pushstring(L, reproc_strerror(error_code));
L, else
reproc_strerror(error_code)
);
} else {
lua_pushnil(L); lua_pushnil(L);
}
return 1; return 1;
} }
static int process_gc(lua_State* L) static int f_gc(lua_State* L)
{ {
process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS); process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
if(self->process){ if(self->process) {
reproc_kill(self->process); reproc_stop(self->process, (reproc_stop_actions) {
{ REPROC_STOP_KILL, 0 },
{ REPROC_STOP_KILL, 0 },
{ REPROC_STOP_TERMINATE, 0 }
});
reproc_destroy(self->process); reproc_destroy(self->process);
self->process = NULL; self->process = NULL;
} }
@ -64,78 +171,23 @@ static int process_gc(lua_State* L)
return 0; return 0;
} }
static int process_start(lua_State* L) static int f_pid(lua_State* L)
{ {
process_t* self = (process_t*) lua_touserdata(L, 1); process_t* self = (process_t*) lua_touserdata(L, 1);
luaL_checktype(L, 2, LUA_TTABLE); lua_pushnumber(L, reproc_pid(self->process));
char* path = NULL;
size_t path_len = 0;
if(lua_type(L, 3) == LUA_TSTRING){
path = (char*) lua_tolstring(L, 3, &path_len);
}
size_t deadline = 0;
if(lua_type(L, 4) == LUA_TNUMBER){
deadline = lua_tonumber(L, 4);
}
size_t table_len = luaL_len(L, 2);
char* command[table_len+1];
command[table_len] = NULL;
int i;
for(i=1; i<=table_len; i++){
lua_pushnumber(L, i);
lua_gettable(L, 2);
command[i-1] = (char*) lua_tostring(L, -1);
lua_remove(L, -1);
}
if(self->process){
reproc_kill(self->process);
reproc_destroy(self->process);
}
self->process = reproc_new();
int out = reproc_start(
self->process,
(const char* const*) command,
(reproc_options){
.working_directory = path,
.deadline = deadline,
.nonblocking=true,
.redirect.err.type=REPROC_REDIRECT_PIPE
}
);
if(out > 0) {
lua_pushboolean(L, 1);
}
else {
reproc_destroy(self->process);
self->process = NULL;
lua_pushnumber(L, out);
}
return 1; return 1;
} }
static int process_pid(lua_State* L) static int f_returncode(lua_State *L)
{ {
process_t* self = (process_t*) lua_touserdata(L, 1); process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS);
int ret = poll_process(self, 0);
if(self->process){ if (self->running)
lua_pushnumber(L, reproc_pid(self->process)); lua_pushnil(L);
} else { else
lua_pushnumber(L, 0); lua_pushnumber(L, ret);
}
return 1; return 1;
} }
@ -143,239 +195,180 @@ static int process_pid(lua_State* L)
static int g_read(lua_State* L, int stream) static int g_read(lua_State* L, int stream)
{ {
process_t* self = (process_t*) lua_touserdata(L, 1); process_t* self = (process_t*) lua_touserdata(L, 1);
unsigned long read_size = luaL_optunsigned(L, 2, READ_BUF_SIZE);
if(self->process){ luaL_Buffer b;
int read_size = READ_BUF_SIZE; uint8_t* buffer = (uint8_t*) luaL_buffinitsize(L, &b, read_size);
if (lua_type(L, 2) == LUA_TNUMBER){
read_size = (int) lua_tonumber(L, 2);
}
int tries = 1; int out = reproc_read(
if (lua_type(L, 3) == LUA_TNUMBER){ self->process,
tries = (int) lua_tonumber(L, 3); stream,
} buffer,
read_size
);
int out = 0; if (out >= 0)
uint8_t buffer[read_size]; luaL_addsize(&b, out);
luaL_pushresult(&b);
int runs; if (out == REPROC_EPIPE)
for (runs=0; runs<tries; runs++){ ASSERT_REPROC_ERRNO(L, out);
out = reproc_read(
self->process,
REPROC_STREAM_OUT,
buffer,
read_size
);
if (out >= 0)
break;
}
if(out == REPROC_EPIPE){
reproc_kill(self->process);
reproc_destroy(self->process);
self->process = NULL;
lua_pushnil(L);
} else if(out > 0) {
lua_pushlstring(L, (const char*) buffer, out);
} else {
lua_pushnil(L);
}
} else {
lua_pushnil(L);
}
return 1; return 1;
} }
static int process_read(lua_State* L) static int f_read_stdout(lua_State* L)
{ {
return g_read(L, REPROC_STREAM_OUT); return g_read(L, REPROC_STREAM_OUT);
} }
static int process_read_errors(lua_State* L) static int f_read_stderr(lua_State* L)
{ {
return g_read(L, REPROC_STREAM_ERR); return g_read(L, REPROC_STREAM_ERR);
} }
static int process_write(lua_State* L) static int f_read(lua_State* L)
{
int stream = luaL_checknumber(L, 1);
lua_remove(L, 1); // remove the number we just read
return g_read(L, stream);
}
static int f_write(lua_State* L)
{ {
process_t* self = (process_t*) lua_touserdata(L, 1); process_t* self = (process_t*) lua_touserdata(L, 1);
if(self->process){ size_t data_size = 0;
size_t data_size = 0; const char* data = luaL_checklstring(L, 2, &data_size);
const char* data = luaL_checklstring(L, 2, &data_size);
int out = 0; int out = reproc_write(
self->process,
out = reproc_write( (uint8_t*) data,
self->process, data_size
(uint8_t*) data, );
data_size if (out == REPROC_EPIPE)
); L_RETURN_REPROC_ERROR(L, out);
if(out == REPROC_EPIPE){
reproc_kill(self->process);
reproc_destroy(self->process);
self->process = NULL;
}
lua_pushnumber(L, out);
} else {
lua_pushnumber(L, REPROC_EPIPE);
}
lua_pushnumber(L, out);
return 1; return 1;
} }
static int process_close_stream(lua_State* L) static int f_close_stream(lua_State* L)
{ {
process_t* self = (process_t*) lua_touserdata(L, 1); process_t* self = (process_t*) lua_touserdata(L, 1);
if(self->process){ int stream = luaL_checknumber(L, 2);
size_t stream = luaL_checknumber(L, 2); int out = reproc_close(self->process, stream);
ASSERT_REPROC_ERRNO(L, out);
int out = reproc_close(self->process, stream);
lua_pushnumber(L, out);
} else {
lua_pushnumber(L, REPROC_EINVAL);
}
lua_pushboolean(L, 1);
return 1; return 1;
} }
static int process_wait(lua_State* L) static int f_wait(lua_State* L)
{ {
process_t* self = (process_t*) lua_touserdata(L, 1); process_t* self = (process_t*) lua_touserdata(L, 1);
int timeout = luaL_optnumber(L, 2, 0);
int ret = poll_process(self, timeout);
// negative returncode is also used for signals on POSIX
if (ret == REPROC_ETIMEDOUT)
ASSERT_REPROC_ERRNO(L, ret);
if(self->process){ lua_pushnumber(L, ret);
size_t timeout = luaL_checknumber(L, 2);
int out = reproc_wait(self->process, timeout);
if(out >= 0){
reproc_destroy(self->process);
self->process = NULL;
}
lua_pushnumber(L, out);
} else {
lua_pushnumber(L, REPROC_EINVAL);
}
return 1; return 1;
} }
static int process_terminate(lua_State* L) static int f_terminate(lua_State* L)
{ {
process_t* self = (process_t*) lua_touserdata(L, 1); process_t* self = (process_t*) lua_touserdata(L, 1);
if(self->process){ int out = reproc_terminate(self->process);
int out = reproc_terminate(self->process); ASSERT_REPROC_ERRNO(L, out);
if(out < 0){ poll_process(self, 0);
lua_pushnumber(L, out);
} else {
reproc_destroy(self->process);
self->process = NULL;
lua_pushboolean(L, 1);
}
} else {
lua_pushnumber(L, REPROC_EINVAL);
}
lua_pushboolean(L, 1);
return 1; return 1;
} }
static int process_kill(lua_State* L) static int f_kill(lua_State* L)
{ {
process_t* self = (process_t*) lua_touserdata(L, 1); process_t* self = (process_t*) lua_touserdata(L, 1);
if(self->process){ int out = reproc_kill(self->process);
int out = reproc_kill(self->process); ASSERT_REPROC_ERRNO(L, out);
if(out < 0){ poll_process(self, 0);
lua_pushnumber(L, out);
} else {
reproc_destroy(self->process);
self->process = NULL;
lua_pushboolean(L, 1);
}
} else {
lua_pushnumber(L, REPROC_EINVAL);
}
lua_pushboolean(L, 1);
return 1; return 1;
} }
static int process_running(lua_State* L) static int f_running(lua_State* L)
{ {
process_t* self = (process_t*) lua_touserdata(L, 1); process_t* self = (process_t*) lua_touserdata(L, 1);
if(self->process){ poll_process(self, 0);
lua_pushboolean(L, 1); lua_pushboolean(L, self->running);
} else {
lua_pushboolean(L, 0);
}
return 1; return 1;
} }
static const struct luaL_Reg process_methods[] = { static const struct luaL_Reg process_methods[] = {
{ "__gc", process_gc}, { "__call", process_new },
{"start", process_start}, { "__gc", f_gc},
{"pid", process_pid}, {"pid", f_pid},
{"read", process_read}, {"returncode", f_returncode},
{"read_errors", process_read_errors}, {"read", f_read},
{"write", process_write}, {"read_stdout", f_read_stdout},
{"close_stream", process_close_stream}, {"read_stderr", f_read_stderr},
{"wait", process_wait}, {"write", f_write},
{"terminate", process_terminate}, {"close_stream", f_close_stream},
{"kill", process_kill}, {"wait", f_wait},
{"running", process_running}, {"terminate", f_terminate},
{"kill", f_kill},
{"running", f_running},
{NULL, NULL} {NULL, NULL}
}; };
static const struct luaL_Reg process[] = { static const struct luaL_Reg lib[] = {
{"new", process_new},
{"strerror", process_strerror}, {"strerror", process_strerror},
{"ERROR_PIPE", NULL}, {"Process", NULL},
{"ERROR_WOULDBLOCK", NULL}, {"CONSTANTS", NULL}
{"ERROR_TIMEDOUT", NULL},
{"STREAM_STDIN", NULL},
{"STREAM_STDOUT", NULL},
{"STREAM_STDERR", NULL},
{NULL, NULL}
}; };
int luaopen_process(lua_State *L) int luaopen_process(lua_State *L)
{ {
luaL_newlib(L, lib);
luaL_newmetatable(L, API_TYPE_PROCESS); luaL_newmetatable(L, API_TYPE_PROCESS);
luaL_setfuncs(L, process_methods, 0); luaL_setfuncs(L, process_methods, 0);
lua_pushvalue(L, -1); lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index"); lua_setfield(L, -2, "__index");
lua_setfield(L, -2, "Process"); // process.Process
luaL_newlib(L, process); lua_newtable(L);
L_SETNUM(L, -1, "ERROR_INVAL", REPROC_EINVAL);
L_SETNUM(L, -1, "ERROR_TIMEDOUT", REPROC_ETIMEDOUT);
L_SETNUM(L, -1, "ERROR_PIPE", REPROC_EPIPE);
L_SETNUM(L, -1, "ERROR_NOMEM", REPROC_ENOMEM);
L_SETNUM(L, -1, "ERROR_WOULDBLOCK", REPROC_EWOULDBLOCK);
lua_pushnumber(L, REPROC_EPIPE); L_SETNUM(L, -1, "WAIT_INFINITE", REPROC_INFINITE);
lua_setfield(L, -2, "ERROR_PIPE"); L_SETNUM(L, -1, "WAIT_DEADLINE", REPROC_DEADLINE);
lua_pushnumber(L, REPROC_EWOULDBLOCK); L_SETNUM(L, -1, "STREAM_STDIN", REPROC_STREAM_IN);
lua_setfield(L, -2, "ERROR_WOULDBLOCK"); L_SETNUM(L, -1, "STREAM_STDOUT", REPROC_STREAM_OUT);
L_SETNUM(L, -1, "STREAM_STDERR", REPROC_STREAM_ERR);
lua_pushnumber(L, REPROC_ETIMEDOUT); L_SETNUM(L, -1, "REDIRECT_DEFAULT", REPROC_REDIRECT_DEFAULT);
lua_setfield(L, -2, "ERROR_TIMEDOUT"); L_SETNUM(L, -1, "REDIRECT_PIPE", REPROC_REDIRECT_PIPE);
L_SETNUM(L, -1, "REDIRECT_PARENT", REPROC_REDIRECT_PARENT);
lua_pushnumber(L, REPROC_STREAM_IN); L_SETNUM(L, -1, "REDIRECT_DISCARD", REPROC_REDIRECT_DISCARD);
lua_setfield(L, -2, "STREAM_STDIN"); L_SETNUM(L, -1, "REDIRECT_STDOUT", REPROC_REDIRECT_STDOUT);
lua_setfield(L, -2, "CONSTANTS"); // process.CONSTANTS
lua_pushnumber(L, REPROC_STREAM_OUT);
lua_setfield(L, -2, "STREAM_STDOUT");
lua_pushnumber(L, REPROC_STREAM_ERR);
lua_setfield(L, -2, "STREAM_STDERR");
return 1; return 1;
} }