mooa

Lua + lubev + sandboxing
git clone https://code.literati.org/mooa.git
Log | Files | Refs | README | LICENSE

commit ecbc00a83069ad4b05d8a556e8153e884ad3c592
parent c54003cf33288b817e9c05c13e7cdc03512f1be6
Author: Sean Lynch <seanl@literati.org>
Date:   Wed, 18 Jun 2014 22:04:48 -0700

Latest version as of 2014-06-18

Diffstat:
MMakefile | 9++++++---
Adns.c | 236+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adns.h | 8++++++++
Ahttp.c | 143+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ahttp.h | 8++++++++
Mmain.lua | 240+++++++++----------------------------------------------------------------------
Mmooa.c | 24+++++++++++++++---------
Msocket.c | 79++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Msocket.h | 4++--
Mtask.c | 141++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Mtask.h | 9+++++++--
Mutils.c | 97+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mutils.h | 7++++---
13 files changed, 668 insertions(+), 337 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,13 +1,16 @@ CC=gcc -CFLAGS=-ggdb3 $(shell pkg-config --cflags lua) +CFLAGS=-ggdb3 -Wall -Werror -pedantic -Wno-strict-aliasing -O2 $(shell pkg-config --cflags lua) +LINT=splint LIBS=$(shell pkg-config --libs lua) -lev all: mooa -mooa: mooa.o socket.o task.o utils.o - ${CC} -o $@ ${LIBS} $^ +mooa: mooa.o dns.o http.o socket.o task.o utils.o http_parser.o + ${CC} -o $@ ${LIBS} $^ -ludns .c.o: ${CC} ${CFLAGS} -c $< +lint: mooa.c http.c socket.c task.c utils.c + splint -checks -posix-lib -mustfreeonly $^ diff --git a/dns.c b/dns.c @@ -0,0 +1,236 @@ +#include <arpa/inet.h> +#include <stdlib.h> + +#include <lua.h> +#include <lualib.h> +#include <lauxlib.h> + +#include <ev.h> +#include <udns.h> + +#include "task.h" + +typedef struct mooa_dns_context { + struct mooa_dns_context *next; + struct dns_ctx *ctx; + lua_State *coro; + ev_timer timer; + ev_io io; +} mooa_dns_context_t; + + +typedef struct mooa_dns_state { + struct mooa_dns_context *free_contexts; +} mooa_dns_state_t; + + + +static void mooa_dns_timer_cb(struct ev_loop *loop, ev_timer *timer) { + mooa_dns_context_t *context = timer->data; + dns_timeouts(context->ctx, -1, + (time_t)ev_now(mooa_task_get_loop(context->coro))); +} + + +static void mooa_dns_io_cb(struct ev_loop *loop, ev_io *io) { + mooa_dns_context_t *context = io->data; + + dns_ioevent(context->ctx, (time_t)ev_now(loop)); +} + + +static void mooa_dns_utm_cb(struct dns_ctx *ctx, int timeout, + void *data) { + struct ev_loop *loop; + mooa_dns_context_t *context; + if (ctx == NULL) { + return; + } + + context = data; + loop = mooa_task_get_loop(context->coro); + ev_timer_stop(loop, &context->timer); + if (timeout >= 0.0) { + ev_timer_set(&context->timer, timeout, 0.0); + ev_timer_start(loop, &context->timer); + } +} + + +static mooa_dns_context_t *mooa_dns_context_new() { + mooa_dns_context_t *context = malloc(sizeof(mooa_dns_context_t)); + context->next = NULL; + context->ctx = dns_new(NULL); + dns_open(context->ctx); + ev_init(&context->timer, mooa_dns_timer_cb); + context->timer.data = context; + ev_init(&context->io, mooa_dns_io_cb); + context->io.data = context; + dns_set_tmcbck(context->ctx, mooa_dns_utm_cb, context); + return context; +} + + +static mooa_dns_state_t *mooa_dns_get_state(lua_State *L) { + mooa_dns_state_t *state; + lua_getfield(L, LUA_REGISTRYINDEX, "mooa_dns"); + state = lua_touserdata(L, -1); + lua_pop(L, 1); + return state; +} + + +static void mooa_dns_context_destroy(mooa_dns_context_t *context, + struct ev_loop *loop) { + ev_timer_stop(loop, &context->timer); + dns_free(context->ctx); + free(context); +} + + +static void mooa_dns_context_free(mooa_dns_context_t *context) { + mooa_dns_state_t *state = mooa_dns_get_state(context->coro); + context->next = state->free_contexts; + state->free_contexts = context; +} + + +static mooa_dns_context_t *mooa_dns_get_context(lua_State *L) { + mooa_dns_state_t *state = mooa_dns_get_state(L); + mooa_dns_context_t *context; + if (state->free_contexts == NULL) { + context = mooa_dns_context_new(); + } else { + context = state->free_contexts; + state->free_contexts = context->next; + context->next = NULL; + } + + context->coro = L; + return context; +} + + +static int mooa_dns_state_finalize(lua_State *L) { + mooa_dns_state_t *state = lua_touserdata(L, 1); + struct ev_loop *loop = mooa_task_get_loop(L); + mooa_dns_context_t *context; + + for (context = state->free_contexts; + context != NULL; + context = context->next) { + mooa_dns_context_destroy(context, loop); + } + return 0; +} + + +static void mooa_dns_init_state(lua_State *L) { + luaL_Reg state_funcs[] = { + {"__gc", mooa_dns_state_finalize}, + {NULL, NULL} + }; + + dns_init(NULL, 0); + luaL_newmetatable(L, "mooa_dns_state"); + luaL_setfuncs(L, state_funcs, 0); + lua_pop(L, 1); + lua_newuserdata(L, sizeof(mooa_dns_state_t)); + luaL_setmetatable(L, "mooa_dns_state"); + lua_setfield(L, LUA_REGISTRYINDEX, "mooa_dns"); +} + + +static void mooa_dns_gethostbyname_cb(struct dns_ctx *ctx, + struct dns_rr_a4 *result, + void *data) { + int i; + mooa_dns_context_t *context = data; + lua_State *L = context->coro; + struct ev_loop *loop = mooa_task_get_loop(L); + char addr[INET_ADDRSTRLEN]; + char *error; + + ev_io_stop(loop, &context->io); + + if (result == NULL) { + switch(dns_status(ctx)) { + case 0: + error = "ok"; + break; + case DNS_E_TEMPFAIL: + error = "temporary failure"; + break; + case DNS_E_PROTOCOL: + error = "protocol error"; + break; + case DNS_E_NXDOMAIN: + error = "nonexistent domain"; + break; + case DNS_E_NODATA: + error = "no data of requested type"; + break; + case DNS_E_NOMEM: + error = "out of memory"; + break; + case DNS_E_BADQUERY: + error = "bad query"; + break; + default: + error = "unknown"; + } + + luaL_error(L, "Error doing dns request: %s", error); + } + + lua_createtable(L, 0, 4); + + lua_pushstring(L, result->dnsa4_qname); + lua_setfield(L, -2, "qname"); + + lua_pushstring(L, result->dnsa4_cname); + lua_setfield(L, -2, "cname"); + + lua_pushinteger(L, result->dnsa4_ttl); + lua_setfield(L, -2, "ttl"); + + lua_createtable(L, result->dnsa4_nrr, 0); + for (i = 0; i < result->dnsa4_nrr; i++) { + inet_ntop(AF_INET, &result->dnsa4_addr[i], addr, sizeof(addr)); + lua_pushstring(L, addr); + lua_rawseti(L, -2, i + 1); + } + + lua_setfield(L, -2, "rr"); + + mooa_dns_context_free(context); + mooa_task_wake(L, 1); +} + + +static int mooa_dns_gethostbyname(lua_State *L) { + mooa_dns_context_t *context; + const char *name; + + luaL_checkstring(L, 1); + context = mooa_dns_get_context(L); + name = lua_tostring(L, 1); + dns_submit_a4(context->ctx, name, 0, + mooa_dns_gethostbyname_cb, context); + ev_io_set(&context->io, dns_sock(context->ctx), EV_READ); + ev_io_start(mooa_task_get_loop(L), &context->io); + return lua_yield(L, 0); +} + + +int mooa_dns_open(lua_State *L) { + luaL_Reg module_functions[] = { + {"gethostbyname", mooa_dns_gethostbyname}, + {NULL, NULL} + }; + + mooa_dns_init_state(L); + + luaL_newlib(L, module_functions); + return 1; +} diff --git a/dns.h b/dns.h @@ -0,0 +1,8 @@ +#ifndef MOOA_DNS_H +#define MOOA_DNS_H 1 + +struct lua_State; + +extern int mooa_dns_open(lua_State *L); + +#endif diff --git a/http.c b/http.c @@ -0,0 +1,143 @@ +#include <lua.h> +#include <lualib.h> +#include <lauxlib.h> + +#include "http_parser.h" + +#include "utils.h" + + +static int mooa_http_notification_cb(http_parser *parser, const char *name) { + lua_State *L = (lua_State *)parser->data; + lua_getfield(L, -1, name); + if (!lua_isfunction(L, -1)) { + lua_pop(L, 1); + return 0; + } + lua_call(L, 0, 0); + return 0; +} + + +static int mooa_http_data_cb(http_parser *parser, const char *name, + const char *data, int length) { + lua_State *L = (lua_State *)parser->data; + lua_getfield(L, -1, name); + if (!lua_isfunction(L, -1)) { + lua_pop(L, 1); + return 0; + } + lua_pushlstring(L, data, length); + lua_call(L, 1, 0); + return 0; +} + + +static int mooa_http_on_message_begin(http_parser *parser) { + return mooa_http_notification_cb(parser, "on_message_begin"); +} + + +static int mooa_http_on_headers_complete(http_parser *parser) { + return mooa_http_notification_cb(parser, "on_headers_complete"); +} + + +static int mooa_http_on_message_complete(http_parser *parser) { + return mooa_http_notification_cb(parser, "on_message_complete"); +} + + +static int mooa_http_on_url(http_parser *parser, const char *data, + size_t length) { + return mooa_http_data_cb(parser, "on_url", data, length); +} + + +static int mooa_http_on_header_field(http_parser *parser, const char *data, + size_t length) { + return mooa_http_data_cb(parser, "on_header_field", data, length); +} + + +static int mooa_http_on_header_value(http_parser *parser, const char *data, + size_t length) { + return mooa_http_data_cb(parser, "on_header_value", data, length); +} + + +static int mooa_http_on_body(http_parser *parser, const char *data, + size_t length) { + return mooa_http_data_cb(parser, "on_body", data, length); +} + + +static http_parser_settings mooa_http_parser_settings = { + .on_message_begin = mooa_http_on_message_begin, + .on_headers_complete = mooa_http_on_headers_complete, + .on_message_complete = mooa_http_on_message_complete, + .on_url = mooa_http_on_url, + .on_header_field = mooa_http_on_header_field, + .on_header_value = mooa_http_on_header_value, + .on_body = mooa_http_on_body +}; + + +static int mooa_http_request_parser(lua_State *L) { + http_parser *parser = lua_newuserdata(L, sizeof(http_parser)); + http_parser_init(parser, HTTP_REQUEST); + luaL_setmetatable(L, "mooa_http_parser"); + return 1; +} + + +static int mooa_http_response_parser(lua_State *L) { + http_parser *parser = lua_newuserdata(L, sizeof(http_parser)); + http_parser_init(parser, HTTP_RESPONSE); + luaL_setmetatable(L, "mooa_http_parser"); + return 1; +} + + +static int mooa_http_parser_execute(lua_State *L) { + size_t nparsed; + luaL_checkudata(L, 1, "mooa_http_parser"); + luaL_checkstring(L, 2); + http_parser *parser = lua_touserdata(L, 1); + size_t length; + const char *data = lua_tolstring(L, 2, &length); + parser->data = L; + nparsed = http_parser_execute(parser, &mooa_http_parser_settings, data, + length); + if (nparsed != length) { + int error = HTTP_PARSER_ERRNO(parser); + return luaL_error(L, "HTTP parsing failed (error %d): %s", + error, http_errno_name(error)); + } + + return 0; +} + + +int mooa_http_open(lua_State *L) { + static luaL_Reg module_functions[] = { + {"request_parser", mooa_http_request_parser}, + {"response_parser", mooa_http_response_parser}, + {NULL, NULL} + }; + + static luaL_Reg parser_functions[] = { + {"execute", mooa_http_parser_execute}, + {NULL, NULL} + }; + + luaL_newmetatable(L, "mooa_http_parser"); + lua_newtable(L); /* Parser methods */ + luaL_setfuncs(L, parser_functions, 0); + lua_setfield(L, -2, "__index"); + lua_pop(L, 1); + + lua_newtable(L); /* The module */ + luaL_setfuncs(L, module_functions, 0); + return 1; +} diff --git a/http.h b/http.h @@ -0,0 +1,8 @@ +#ifndef MOOA_HTTP_H +#define MOOA_HTTP_H 1 + +typedef struct lua_State lua_State; + +extern int mooa_http_open(lua_State *L); + +#endif diff --git a/main.lua b/main.lua @@ -1,232 +1,46 @@ -local dumplua_closure = [[ -local closures = {} -local function closure(t) - closures[#closures+1] = t - t[1] = assert(loadstring(t[1])) - return t[1] -end - -for _,t in pairs(closures) do - for i = 2,#t do - debug.setupvalue(t[1], i-1, t[i]) - end -end -]] - -local lua_reserved_keywords = { - 'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', - 'function', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat', - 'return', 'then', 'true', 'until', 'while' } - -local function keys(t) - local res = {} - local oktypes = { stringstring = true, numbernumber = true } - local function cmpfct(a,b) - if oktypes[type(a)..type(b)] then - return a < b - else - return type(a) < type(b) - end - end - for k in pairs(t) do - res[#res+1] = k - end - table.sort(res, cmpfct) - return res -end - -local c_functions = {} -for _,lib in pairs{'_G', 'string', 'table', 'math', - 'io', 'os', 'coroutine', 'package', 'debug'} do - local t = _G[lib] or {} - lib = lib .. "." - if lib == "_G." then lib = "" end - for k,v in pairs(t) do - if type(v) == 'function' and not pcall(string.dump, v) then - c_functions[v] = lib..k - end - end -end - -function DataDumper(value, varname, fastmode, ident) - local defined, dumplua = {} - -- Local variables for speed optimization - local string_format, type, string_dump, string_rep = - string.format, type, string.dump, string.rep - local tostring, pairs, table_concat = - tostring, pairs, table.concat - local keycache, strvalcache, out, closure_cnt = {}, {}, {}, 0 - setmetatable(strvalcache, {__index = function(t,value) - local res = string_format('%q', value) - t[value] = res - return res - end}) - local fcts = { - string = function(value) return strvalcache[value] end, - number = function(value) return value end, - boolean = function(value) return tostring(value) end, - ['nil'] = function(value) return 'nil' end, - ['function'] = function(value) - return string_format("loadstring(%q)", string_dump(value)) - end, - userdata = function() error("Cannot dump userdata") end, - thread = function() error("Cannot dump threads") end, - } - local function test_defined(value, path) - if defined[value] then - if path:match("^getmetatable.*%)$") then - out[#out+1] = string_format("s%s, %s)\n", path:sub(2,-2), defined[value]) - else - out[#out+1] = path .. " = " .. defined[value] .. "\n" - end - return true - end - defined[value] = path - end - local function make_key(t, key) - local s - if type(key) == 'string' and key:match('^[_%a][_%w]*$') then - s = key .. "=" - else - s = "[" .. dumplua(key, 0) .. "]=" - end - t[key] = s - return s - end - for _,k in ipairs(lua_reserved_keywords) do - keycache[k] = '["'..k..'"] = ' - end - if fastmode then - fcts.table = function (value) - -- Table value - local numidx = 1 - out[#out+1] = "{" - for key,val in pairs(value) do - if key == numidx then - numidx = numidx + 1 - else - out[#out+1] = keycache[key] - end - local str = dumplua(val) - out[#out+1] = str.."," - end - if string.sub(out[#out], -1) == "," then - out[#out] = string.sub(out[#out], 1, -2); - end - out[#out+1] = "}" - return "" - end - else - fcts.table = function (value, ident, path) - if test_defined(value, path) then return "nil" end - -- Table value - local sep, str, numidx, totallen = " ", {}, 1, 0 - local meta, metastr = (debug or getfenv()).getmetatable(value) - if meta then - ident = ident + 1 - metastr = dumplua(meta, ident, "getmetatable("..path..")") - totallen = totallen + #metastr + 16 - end - for _,key in pairs(keys(value)) do - local val = value[key] - local s = "" - local subpath = path - if key == numidx then - subpath = subpath .. "[" .. numidx .. "]" - numidx = numidx + 1 - else - s = keycache[key] - if not s:match "^%[" then subpath = subpath .. "." end - subpath = subpath .. s:gsub("%s*=%s*$","") - end - s = s .. dumplua(val, ident+1, subpath) - str[#str+1] = s - totallen = totallen + #s + 2 - end - if totallen > 80 then - sep = "\n" .. string_rep(" ", ident+1) - end - str = "{"..sep..table_concat(str, ","..sep).." "..sep:sub(1,-3).."}" - if meta then - sep = sep:sub(1,-3) - return "setmetatable("..sep..str..","..sep..metastr..sep:sub(1,-3)..")" - end - return str - end - fcts['function'] = function (value, ident, path) - if test_defined(value, path) then return "nil" end - if c_functions[value] then - return c_functions[value] - elseif debug == nil or debug.getupvalue(value, 1) == nil then - return string_format("loadstring(%q)", string_dump(value)) - end - closure_cnt = closure_cnt + 1 - local res = {string.dump(value)} - for i = 1,math.huge do - local name, v = debug.getupvalue(value,i) - if name == nil then break end - res[i+1] = v - end - return "closure " .. dumplua(res, ident, "closures["..closure_cnt.."]") - end - end - function dumplua(value, ident, path) - return fcts[type(value)](value, ident, path) - end - if varname == nil then - varname = "return " - elseif varname:match("^[%a_][%w_]*$") then - varname = varname .. " = " - end - if fastmode then - setmetatable(keycache, {__index = make_key }) - out[1] = varname - table.insert(out,dumplua(value, 0)) - return table.concat(out) - else - setmetatable(keycache, {__index = make_key }) - local items = {} - for i=1,10 do items[i] = '' end - items[3] = dumplua(value, ident or 0, "t") - if closure_cnt > 0 then - items[1], items[6] = dumplua_closure:match("(.*\n)\n(.*)") - out[#out+1] = "" - end - if #out > 0 then - items[2], items[4] = "local t = ", "\n" - items[5] = table.concat(out) - items[7] = varname .. "t" - else - items[2] = varname - end - return table.concat(items) - end -end - --- Define a shortcut function for testing -function dump(...) - print(DataDumper(...), "\n---") -end - --- dump(_G) - function foo(x) print("Hello world!", x) task.yield() print("Hello again, world!") end +function get_http_callbacks() + local headers = {} + local header_field + return { + on_header_field = function(field) + header_field = field + end, + on_header_value = function(value) + headers[header_field] = value + end, + get_headers = function() + return headers + end + } +end + function test_connect() + parser = http.response_parser() s = socket.socket() - print(s) + utils.dump(s) s:connect("66.181.143.21", 80) s:write("GET / HTTP/1.0\nHost: www.literati.org\n\n") data = s:read(1024) print(data) + callbacks = get_http_callbacks() + parser:execute(data, callbacks) + utils.dump(callbacks.get_headers()) +end + +function test_dns() + result = dns.gethostbyname("setsuna.literati.org") + utils.dump(result) end task.spawn(foo, 4) task.spawn(test_connect) print("Goodbye world!") +task.spawn(test_dns) task.sleep(1) print("Goodbye again, world!") diff --git a/mooa.c b/mooa.c @@ -5,6 +5,8 @@ #include <lualib.h> #include <lauxlib.h> +#include "dns.h" +#include "http.h" #include "socket.h" #include "task.h" #include "utils.h" @@ -15,8 +17,8 @@ typedef struct { } mooa_alloc_state_t; -static void *mooa_alloc(void *ud, void *ptr, size_t osize, - size_t nsize) { +static /*@only@*/ /*@null@*/ void *mooa_alloc(void *ud, /*@only@*/ void *ptr, + size_t osize, size_t nsize) { mooa_alloc_state_t *state = (mooa_alloc_state_t *)ud; if (nsize == 0) @@ -28,15 +30,16 @@ static void *mooa_alloc(void *ud, void *ptr, size_t osize, if (state->used + (nsize - osize) > state->limit) return NULL; ptr = realloc(ptr, nsize); - if (ptr) + if (ptr != NULL) state->used += (nsize - osize); return ptr; } } -static void *mooa_state_makesafe(lua_State *L) { - const static char *whitelist[] = { +static void mooa_state_makesafe(lua_State *L) { + static const char *whitelist[] = { + "getmetatable", "next", "pairs", "pcall", @@ -46,6 +49,7 @@ static void *mooa_state_makesafe(lua_State *L) { "rawset", "select", "setfenv", + "setmetatable", "tonumber", "tostring", "type", @@ -62,7 +66,7 @@ static void *mooa_state_makesafe(lua_State *L) { lua_pushvalue(L, -1); lua_setfield(L, -2, "_G"); - for (i = 0; key = whitelist[i]; i++) { + for (i = 0; (key = whitelist[i]) != NULL; i++) { lua_getfield(L, -2, key); lua_setfield(L, -2, key); } @@ -88,19 +92,23 @@ static lua_State *mooa_state_new() { mooa_alloc_state_t *state = (mooa_alloc_state_t *)calloc( sizeof(mooa_alloc_state_t), 1); - state->limit = 8 * 1024 * 1024; // 8 megs + state->limit = 8 * 1024 * 1024; /* 8 megs */ L = lua_newstate(mooa_alloc, (void *)state); lua_atpanic(L, mooa_panic); luaL_requiref(L, "base", luaopen_base, 0); + luaL_requiref(L, "package", luaopen_package, 1); mooa_state_makesafe(L); luaL_requiref(L, LUA_BITLIBNAME, luaopen_bit32, 1); luaL_requiref(L, LUA_MATHLIBNAME, luaopen_math, 1); luaL_requiref(L, LUA_STRLIBNAME, luaopen_string, 1); luaL_requiref(L, LUA_TABLIBNAME, luaopen_table, 1); + luaL_requiref(L, "dns", mooa_dns_open, 1); + luaL_requiref(L, "http", mooa_http_open, 1); luaL_requiref(L, "task", mooa_task_open, 1); luaL_requiref(L, "socket", mooa_socket_open, 1); + luaL_requiref(L, "utils", mooa_utils_open, 1); lua_settop(L, 0); return L; @@ -108,8 +116,6 @@ static lua_State *mooa_state_new() { int main(void) { - int status, result, i; - double sum; lua_State *L; L = mooa_state_new(); diff --git a/socket.c b/socket.c @@ -1,13 +1,14 @@ +#include <arpa/inet.h> #include <errno.h> #include <fcntl.h> #include <string.h> #include <sys/socket.h> +#include <unistd.h> #include <lua.h> #include <lualib.h> #include <lauxlib.h> -#include <ares.h> #include <ev.h> #include "task.h" @@ -35,11 +36,20 @@ static int mooa_socket_new(lua_State *L) { } +static int mooa_socket_finalize(lua_State *L) { + /* TODO: sharing of socket among workers */ + mooa_socket_t *sock = lua_touserdata(L, 1); + close(sock->socket); + return 0; +} + + static void mooa_makeaddr(lua_State *L, struct sockaddr_in *sa, const char *address, int port) { memset(sa, 0, sizeof(*sa)); /* TODO: ipv6 */ + /* TODO: dns */ sa->sin_family = AF_INET; sa->sin_port = htons(port); if (!inet_pton(AF_INET, address, &sa->sin_addr)) { @@ -66,20 +76,24 @@ static int mooa_socket_accept_cb(lua_State *L) { static int mooa_socket_accept(lua_State *L) { + mooa_socket_t *sock; + luaL_checkudata(L, 1, "mooa_socket"); - mooa_socket_t *sock = lua_touserdata(L, 1); + sock = lua_touserdata(L, 1); mooa_task_wait_for_read(L, sock->socket); return lua_yieldk(L, 0, 0, mooa_socket_accept_cb); } static int mooa_socket_bind(lua_State *L) { + mooa_socket_t *sock; + struct sockaddr_in sa; + luaL_checkudata(L, 1, "mooa_socket"); luaL_checkstring(L, 2); luaL_checknumber(L, 3); - mooa_socket_t *sock = lua_touserdata(L, 1); - struct sockaddr_in sa; + sock = lua_touserdata(L, 1); mooa_makeaddr(L, &sa, lua_tostring(L, 2), lua_tointeger(L, 3)); if (bind(sock->socket, (struct sockaddr *)&sa, sizeof(sa)) != 0) { return luaL_error(L, "Unable to bind (errno %d)", errno); @@ -89,13 +103,28 @@ static int mooa_socket_bind(lua_State *L) { } +static int mooa_socket_close(lua_State *L) { + mooa_socket_t *sock; + + luaL_checkudata(L, 1, "mooa_socket"); + sock = lua_touserdata(L, 1); + if (close(sock->socket) != 0) { + return luaL_error(L, "Unable to close socket (errno %d)", errno); + } + + return 0; +} + + static int mooa_socket_connect(lua_State *L) { + mooa_socket_t *sock; + struct sockaddr_in sa; + luaL_checkudata(L, 1, "mooa_socket"); luaL_checkstring(L, 2); luaL_checknumber(L, 3); - mooa_socket_t *sock = lua_touserdata(L, 1); - struct sockaddr_in sa; + sock = lua_touserdata(L, 1); mooa_makeaddr(L, &sa, lua_tostring(L, 2), lua_tointeger(L, 3)); if (connect(sock->socket, (struct sockaddr *)&sa, sizeof(sa)) != 0 && errno != EINPROGRESS) { @@ -108,11 +137,14 @@ static int mooa_socket_connect(lua_State *L) { static int mooa_socket_listen(lua_State *L) { + mooa_socket_t *sock; + int backlog; + luaL_checkudata(L, 1, "mooa_socket"); luaL_checknumber(L, 2); - mooa_socket_t *sock = lua_touserdata(L, 1); - int backlog = lua_tointeger(L, 2); + sock = lua_touserdata(L, 1); + backlog = lua_tointeger(L, 2); if (listen(sock->socket, backlog) != 0) { return luaL_error(L, "Unable to listen (errno %d)", errno); @@ -126,8 +158,8 @@ static int mooa_socket_read_cb(lua_State *L) { luaL_Buffer buf; mooa_socket_t *sock = lua_touserdata(L, 1); int size = lua_tointeger(L, 2); - luaL_buffinitsize(L, &buf, size); - ssize_t nread = read(sock->socket, buf.b, size); + char *data = luaL_buffinitsize(L, &buf, size); + ssize_t nread = read(sock->socket, data, size); if (nread < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { return lua_yieldk(L, 0, 0, mooa_socket_read_cb); @@ -141,10 +173,12 @@ static int mooa_socket_read_cb(lua_State *L) { static int mooa_socket_read(lua_State *L) { + mooa_socket_t *sock = lua_touserdata(L, 1); + luaL_checkudata(L, 1, "mooa_socket"); luaL_checknumber(L, 2); - mooa_socket_t *sock = lua_touserdata(L, 1); + sock = lua_touserdata(L, 1); mooa_task_wait_for_read(L, sock->socket); return lua_yieldk(L, 0, 0, mooa_socket_read_cb); } @@ -152,11 +186,14 @@ static int mooa_socket_read(lua_State *L) { static int mooa_socket_write_cb(lua_State *L) { size_t len; + ssize_t nsent; int offset; + const char *data; mooa_socket_t *sock = lua_touserdata(L, 1); lua_getctx(L, &offset); - const char *data = lua_tolstring(L, 2, &len); - ssize_t nsent = write(sock->socket, &data[offset], len - offset); + + data = lua_tolstring(L, 2, &len); + nsent = write(sock->socket, &data[offset], len - offset); if (nsent < 0) { /* error */ return luaL_error(L, "Error writing to socket"); @@ -165,14 +202,18 @@ static int mooa_socket_write_cb(lua_State *L) { if (offset < len) { return lua_yieldk(L, 0, offset, mooa_socket_write_cb); } + + return 0; } static int mooa_socket_write(lua_State *L) { + mooa_socket_t *sock; + luaL_checkudata(L, 1, "mooa_socket"); luaL_checkstring(L, 2); - mooa_socket_t *sock = lua_touserdata(L, 1); + sock = lua_touserdata(L, 1); mooa_task_wait_for_write(L, sock->socket); return lua_yieldk(L, 0, 0, mooa_socket_write_cb); } @@ -187,6 +228,7 @@ int mooa_socket_open(lua_State *L) { static luaL_Reg socket_functions[] = { {"accept", mooa_socket_accept}, {"bind", mooa_socket_bind}, + {"close", mooa_socket_close}, {"connect", mooa_socket_connect}, {"listen", mooa_socket_listen}, {"read", mooa_socket_read}, @@ -195,13 +237,12 @@ int mooa_socket_open(lua_State *L) { }; luaL_newmetatable(L, "mooa_socket"); - lua_newtable(L); /* Socket methods */ - luaL_setfuncs(L, socket_functions, 0); + luaL_newlib(L, socket_functions); lua_setfield(L, -2, "__index"); + lua_pushcfunction(L, mooa_socket_finalize); + lua_setfield(L, -2, "__gc"); lua_pop(L, 1); - lua_newtable(L); /* The module */ - luaL_setfuncs(L, module_functions, 0); - + luaL_newlib(L, module_functions); return 1; } diff --git a/socket.h b/socket.h @@ -1,5 +1,5 @@ -#ifndef _MOOA_SOCKET_H -#define _MOOA_SOCKET_H 1 +#ifndef MOOA_SOCKET_H +#define MOOA_SOCKET_H 1 #include <lua.h> diff --git a/task.c b/task.c @@ -1,3 +1,4 @@ +#include <assert.h> #include <stdlib.h> /* lua */ @@ -12,58 +13,91 @@ typedef struct mooa_task { + struct mooa_task *next; + lua_State *coro; ev_timer timer; - ev_idle idle; ev_io io; - lua_State *coro; - struct ev_loop *loop; int nargs; } mooa_task_t; +typedef struct mooa_task_state { + struct ev_loop *loop; + mooa_task_t *runnable_tasks; + mooa_task_t *last_runnable_task; +} mooa_task_state_t; + + +void mooa_task_init_state(lua_State *L) { + mooa_task_state_t *state = lua_newuserdata(L, sizeof(mooa_task_state_t)); + state->runnable_tasks = NULL; + state->last_runnable_task = NULL; + lua_setfield(L, LUA_REGISTRYINDEX, "mooa_task_state"); +} + + +mooa_task_state_t *mooa_task_get_state(lua_State *L) { + mooa_task_state_t *state; + lua_getfield(L, LUA_REGISTRYINDEX, "mooa_task_state"); + state = lua_touserdata(L, -1); + lua_pop(L, 1); + return state; +} + + /* Get the task corresponding to the Lua thread. */ mooa_task_t *mooa_task_get(lua_State *L) { + mooa_task_t *task; lua_rawgetp(L, LUA_REGISTRYINDEX, L); - mooa_task_t *task = lua_touserdata(L, -1); + task = lua_touserdata(L, -1); lua_pop(L, 1); return task; } -/* Pause the task when waiting for some other event. */ -static void mooa_task_pause(mooa_task_t *task) { - ev_idle_stop(task->loop, &task->idle); +/* Get the libev loop */ +struct ev_loop *mooa_task_get_loop(lua_State *L) { + struct ev_loop *loop; + lua_getfield(L, LUA_REGISTRYINDEX, "mooa_task_loop"); + loop = lua_touserdata(L, -1); + lua_pop(L, 1); + return loop; +} + + +static void mooa_task_schedule(mooa_task_t *task, int nargs) { + mooa_task_state_t *state = mooa_task_get_state(task->coro); + task->nargs = nargs; + task->next = NULL; + if (state->last_runnable_task != NULL) { + state->last_runnable_task->next = task; + state->last_runnable_task = task; + } else { + assert(state->runnable_tasks == NULL); + state->runnable_tasks = task; + state->last_runnable_task = task; + } } -/* Start the task back up and step it. */ -static void mooa_task_wake(mooa_task_t *task) { - ev_idle_start(task->loop, &task->idle); +void mooa_task_wake(lua_State *L, int nargs) { + mooa_task_schedule(mooa_task_get(L), nargs); } +/* Wait for a file descriptor to become readable */ void mooa_task_wait_for_read(lua_State *L, int fd) { mooa_task_t *task = mooa_task_get(L); - mooa_task_pause(task); ev_io_set(&task->io, fd, EV_READ); - ev_io_start(task->loop, &task->io); + ev_io_start(mooa_task_get_loop(L), &task->io); } +/* Wait for a file descriptor to become writable */ void mooa_task_wait_for_write(lua_State *L, int fd) { mooa_task_t *task = mooa_task_get(L); - mooa_task_pause(task); ev_io_set(&task->io, fd, EV_WRITE); - ev_io_start(task->loop, &task->io); -} - - -/* Get the libev loop */ -struct ev_loop *mooa_task_get_loop(lua_State *L) { - lua_getfield(L, LUA_REGISTRYINDEX, "mooa_task_loop"); - struct ev_loop *loop = lua_touserdata(L, -1); - lua_pop(L, 1); - return loop; + ev_io_start(mooa_task_get_loop(L), &task->io); } @@ -74,14 +108,11 @@ static void mooa_task_delete(mooa_task_t *task) { } -static void mooa_task_idle_cb(struct ev_loop *loop, ev_idle *idle) { - mooa_task_t *task = (mooa_task_t *)idle->data; +static void mooa_task_step(mooa_task_t *task) { int result = lua_resume(task->coro, NULL, task->nargs); - task->nargs = 0; switch (result) { case LUA_OK: /* Task completed. */ - ev_idle_stop(loop, &task->idle); mooa_task_delete(task); break; case LUA_YIELD: @@ -113,35 +144,29 @@ static void mooa_task_idle_cb(struct ev_loop *loop, ev_idle *idle) { static void mooa_task_timer_cb(struct ev_loop *loop, ev_timer *timer, int revents) { - mooa_task_wake((mooa_task_t *)timer->data); + mooa_task_schedule((mooa_task_t *)timer->data, 0); } -static int mooa_task_io_cb(struct ev_loop *loop, ev_io *io, int revents) { - struct mooa_task *task = (struct mooa_task *)io->data; - /* Always one-shot */ +static void mooa_task_io_cb(struct ev_loop *loop, ev_io *io, int revents) { ev_io_stop(loop, io); - mooa_task_wake(task); + mooa_task_schedule((mooa_task_t *)io->data, 0); } static mooa_task_t *mooa_task_new(lua_State *L) { mooa_task_t *task = lua_newuserdata(L, sizeof(mooa_task_t)); - task->loop = mooa_task_get_loop(L); + ev_timer *timer = &task->timer; + ev_io *io = &task->io; /* Initialize the timer watcher. */ - ev_init(&task->timer, mooa_task_timer_cb); - task->timer.data = task; - - /* Initialize the idle watcher. */ - ev_idle_init(&task->idle, mooa_task_idle_cb); - task->idle.data = task; - ev_set_priority(&task->idle, EV_MAXPRI); + ev_init(timer, mooa_task_timer_cb); + timer->data = task; /* Initialize the IO watcher. */ - ev_init(&task->io, mooa_task_io_cb); - task->io.data = task; + ev_init(io, mooa_task_io_cb); + io->data = task; luaL_setmetatable(L, "mooa_task"); lua_newtable(L); @@ -154,10 +179,19 @@ static mooa_task_t *mooa_task_new(lua_State *L) { void mooa_task_loop(lua_State *L) { - struct ev_loop *loop = mooa_task_get_loop(L); - ev_run(loop, 0); + mooa_task_state_t *state = mooa_task_get_state(L); + mooa_task_t *task; + bool running; + + do { + running = ev_run(state->loop, state->runnable_tasks ? EVRUN_NOWAIT : 0); + for (task = state->runnable_tasks; task != NULL; task = task->next) { + mooa_task_step(task); + } + } while (running || state->runnable_tasks); } + /* Lua functions */ /* Get the currently running task */ @@ -170,39 +204,42 @@ static int mooa_task_current(lua_State *L) { /* Spawn a new task. Can be called from C. */ int mooa_task_spawn(lua_State *L) { mooa_task_t *task; - int nargs, r; + int nargs; luaL_checktype(L, 1, LUA_TFUNCTION); nargs = lua_gettop(L); task = mooa_task_new(L); lua_insert(L, 1); /* Move task to the bottom of the stack */ /* Move arguments and function into new thread's stack */ lua_xmove(L, task->coro, nargs); - task->nargs = nargs - 1; /* Don't count the function */ /* Store the task userdata indexed by its thread */ lua_rawsetp(L, LUA_REGISTRYINDEX, task->coro); - mooa_task_wake(task); + mooa_task_schedule(task, nargs - 1); return 1; } static int mooa_task_sleep(lua_State *L) { + double secs; + mooa_task_t *task; + luaL_checknumber(L, 1); - double secs = lua_tonumber(L, 1); - mooa_task_t *task = mooa_task_get(L); - ev_idle_stop(task->loop, &task->idle); + secs = lua_tonumber(L, 1); + task = mooa_task_get(L); ev_timer_set(&task->timer, secs, 0.0); - ev_timer_start(task->loop, &task->timer); + ev_timer_start(mooa_task_get_loop(L), &task->timer); return lua_yield(L, 0); } static int mooa_task_yield(lua_State *L) { + mooa_task_wake(L, 0); /* Keep the task runnable */ return lua_yield(L, 0); } int mooa_task_open(lua_State *L) { + struct ev_loop *loop; static luaL_Reg functions[] = { {"current", mooa_task_current}, {"sleep", mooa_task_sleep}, @@ -214,7 +251,7 @@ int mooa_task_open(lua_State *L) { luaL_newmetatable(L, "mooa_task"); lua_pop(L, 1); - struct ev_loop *loop = ev_loop_new(EVFLAG_AUTO); + loop = ev_loop_new(EVFLAG_AUTO); lua_pushlightuserdata(L, loop); lua_setfield(L, LUA_REGISTRYINDEX, "mooa_task_loop"); diff --git a/task.h b/task.h @@ -1,11 +1,16 @@ -#ifndef _MOOA_TASK_H -#define _MOOA_TASK_H 1 +#ifndef MOOA_TASK_H +#define MOOA_TASK_H 1 #include <lua.h> +struct ev_loop; + +extern struct ev_loop *mooa_task_get_loop(lua_State *L); +extern void mooa_task_loop(lua_State *L); extern int mooa_task_open(lua_State *L); extern int mooa_task_spawn(lua_State *L); extern void mooa_task_wait_for_read(lua_State *L, int fd); extern void mooa_task_wait_for_write(lua_State *L, int fd); +extern void mooa_task_wake(lua_State *L, int nargs); #endif diff --git a/utils.c b/utils.c @@ -5,6 +5,14 @@ #include <lauxlib.h> +static void mooa_indent(int indent) { + int i; + for (i = 0; i < indent; i++) { + fputc(' ', stderr); + } +} + + void mooa_print_string(lua_State *L, int index) { char buf[1024]; const char *str; @@ -25,38 +33,72 @@ void mooa_print_string(lua_State *L, int index) { } buf[j] = 0; - fprintf(stderr, "\"%s\"\n", buf); + fprintf(stderr, "\"%s\"", buf); } void mooa_print_pointer(lua_State *L, int index) { - fprintf(stderr, "%s(%p)\n", luaL_typename(L, index), + fprintf(stderr, "%s(%p)", luaL_typename(L, index), lua_topointer(L, index)); } -void mooa_print_value(lua_State *L, int index) { +void mooa_dump_stack(lua_State *L) { + int i, top; + top = lua_gettop(L); + fprintf(stderr, "-- Stack (%d items) --\n", top); + for (i = 1; i <= top; i++) { + fprintf(stderr, "%d: %s(%p)\n", i, luaL_typename(L, i), lua_topointer(L, i)); + } + fprintf(stderr, "-- End of stack --\n"); +} + + +void mooa_print_value(lua_State *, int, int); + + +void mooa_print_table(lua_State *L, int index, int indent) { + index = lua_absindex(L, index); + lua_pushnil(L); + fprintf(stderr, "{\n"); + while (lua_next(L, index) != 0) { + mooa_indent(indent + 2); + mooa_print_value(L, -2, indent + 2); + fprintf(stderr, " = "); + mooa_print_value(L, -1, indent + 2); + fprintf(stderr, ",\n"); + /* removes 'value'; keeps 'key' for next iteration */ + lua_pop(L, 1); + } + mooa_indent(indent); + fprintf(stderr, "}"); +} + + +void mooa_print_value(lua_State *L, int index, int indent) { switch (lua_type(L, index)) { case LUA_TNONE: - fprintf(stderr, "NONE\n"); + fprintf(stderr, "NONE"); break; case LUA_TNIL: - fprintf(stderr, "nil\n"); + fprintf(stderr, "nil"); break; case LUA_TNUMBER: - fprintf(stderr, "%f\n", lua_tonumber(L, index)); + fprintf(stderr, "%f", lua_tonumber(L, index)); break; case LUA_TBOOLEAN: if (lua_toboolean(L, index)) { - fprintf(stderr, "true\n"); + fprintf(stderr, "true"); } else { - fprintf(stderr, "false\n"); + fprintf(stderr, "false"); } break; case LUA_TSTRING: mooa_print_string(L, index); break; case LUA_TTABLE: + mooa_print_table(L, index, indent); + break; case LUA_TFUNCTION: case LUA_TUSERDATA: case LUA_TTHREAD: @@ -67,33 +109,20 @@ void mooa_print_value(lua_State *L, int index) { } -void mooa_dump_stack(lua_State *L) { - int i, top; - top = lua_gettop(L); - fprintf(stderr, "-- Stack (%d items) --\n", top); - for (i = 1; i <= top; i++) { - fprintf(stderr, "%d: ", i); - mooa_print_value(L, i); - } - fprintf(stderr, "-- End of stack --\n"); +int mooa_utils_dump(lua_State *L) { + mooa_print_value(L, 1, 0); + fprintf(stderr, "\n"); + return 0; } -/* Dump the contents of the table at the top of the stack */ -void mooa_dump_table(lua_State *L) { - lua_pushnil(L); - while (lua_next(L, -2) != 0) { - const char *key; - if (lua_isstring(L, -2)) { - key = lua_tostring(L, -2); - } else { - key = lua_typename(L, lua_type(L, -2)); - } - printf("%s - %s\n", - key, - lua_typename(L, lua_type(L, -1))); - /* removes 'value'; keeps 'key' for next iteration */ - lua_pop(L, 1); - } - lua_pop(L, 1); +int mooa_utils_open(lua_State *L) { + luaL_Reg module_functions[] = { + {"dump", mooa_utils_dump}, + {NULL, NULL} + }; + + lua_newtable(L); + luaL_setfuncs(L, module_functions, 0); + return 1; } diff --git a/utils.h b/utils.h @@ -1,7 +1,8 @@ -#ifndef _MOOA_UTILS_H -#define _MOOA_UTILS_H 1 +#ifndef MOOA_UTILS_H +#define MOOA_UTILS_H 1 extern void mooa_dump_stack(lua_State *L); -extern void mooa_print_value(lua_State *L, int index); +extern void mooa_print_value(lua_State *L, int index, int indent); +extern int mooa_utils_open(lua_State *L); #endif /* _MOOA_UTILS_H */