/* * Lua polling library * Copyright (C) 2017-2018 Kim Alvefur * * This project is MIT licensed. Please see the * COPYING file in the source package for more information. * */ #include #include #include #if defined(__linux__) #define USE_EPOLL #define POLL_BACKEND "epoll" #else #define USE_SELECT #define POLL_BACKEND "select" #endif #ifdef USE_EPOLL #include #ifndef MAX_EVENTS #define MAX_EVENTS 64 #endif #endif #ifdef USE_SELECT #include #endif #include #include #define STATE_MT "util.poll<" POLL_BACKEND ">" #if (LUA_VERSION_NUM == 501) #define luaL_setmetatable(L, tname) luaL_getmetatable(L, tname); lua_setmetatable(L, -2) #endif #if (LUA_VERSION_NUM < 504) #define luaL_pushfail lua_pushnil #endif /* * Structure to keep state for each type of API */ typedef struct Lpoll_state { int processed; #ifdef USE_EPOLL int epoll_fd; struct epoll_event events[MAX_EVENTS]; #endif #ifdef USE_SELECT fd_set wantread; fd_set wantwrite; fd_set readable; fd_set writable; fd_set all; fd_set err; #endif } Lpoll_state; /* * Add an FD to be watched */ static int Ladd(lua_State *L) { struct Lpoll_state *state = luaL_checkudata(L, 1, STATE_MT); int fd = luaL_checkinteger(L, 2); int wantread = lua_toboolean(L, 3); int wantwrite = lua_toboolean(L, 4); if(fd < 0) { luaL_pushfail(L); lua_pushstring(L, strerror(EBADF)); lua_pushinteger(L, EBADF); return 3; } #ifdef USE_EPOLL struct epoll_event event; event.data.fd = fd; event.events = (wantread ? EPOLLIN : 0) | (wantwrite ? EPOLLOUT : 0); event.events |= EPOLLERR | EPOLLHUP | EPOLLRDHUP; int ret = epoll_ctl(state->epoll_fd, EPOLL_CTL_ADD, fd, &event); if(ret < 0) { ret = errno; luaL_pushfail(L); lua_pushstring(L, strerror(ret)); lua_pushinteger(L, ret); return 3; } lua_pushboolean(L, 1); return 1; #endif #ifdef USE_SELECT if(fd > FD_SETSIZE) { luaL_pushfail(L); lua_pushstring(L, strerror(EBADF)); lua_pushinteger(L, EBADF); return 3; } if(FD_ISSET(fd, &state->all)) { luaL_pushfail(L); lua_pushstring(L, strerror(EEXIST)); lua_pushinteger(L, EEXIST); return 3; } FD_CLR(fd, &state->readable); FD_CLR(fd, &state->writable); FD_CLR(fd, &state->err); FD_SET(fd, &state->all); if(wantread) { FD_SET(fd, &state->wantread); } else { FD_CLR(fd, &state->wantread); } if(wantwrite) { FD_SET(fd, &state->wantwrite); } else { FD_CLR(fd, &state->wantwrite); } lua_pushboolean(L, 1); return 1; #endif } /* * Set events to watch for, readable and/or writable */ static int Lset(lua_State *L) { struct Lpoll_state *state = luaL_checkudata(L, 1, STATE_MT); int fd = luaL_checkinteger(L, 2); #ifdef USE_EPOLL int wantread = lua_toboolean(L, 3); int wantwrite = lua_toboolean(L, 4); struct epoll_event event; event.data.fd = fd; event.events = (wantread ? EPOLLIN : 0) | (wantwrite ? EPOLLOUT : 0); event.events |= EPOLLERR | EPOLLHUP | EPOLLRDHUP; int ret = epoll_ctl(state->epoll_fd, EPOLL_CTL_MOD, fd, &event); if(ret == 0) { lua_pushboolean(L, 1); return 1; } else { ret = errno; luaL_pushfail(L); lua_pushstring(L, strerror(ret)); lua_pushinteger(L, ret); return 3; } #endif #ifdef USE_SELECT if(!FD_ISSET(fd, &state->all)) { luaL_pushfail(L); lua_pushstring(L, strerror(ENOENT)); lua_pushinteger(L, ENOENT); return 3; } if(!lua_isnoneornil(L, 3)) { if(lua_toboolean(L, 3)) { FD_SET(fd, &state->wantread); } else { FD_CLR(fd, &state->wantread); } } if(!lua_isnoneornil(L, 4)) { if(lua_toboolean(L, 4)) { FD_SET(fd, &state->wantwrite); } else { FD_CLR(fd, &state->wantwrite); } } lua_pushboolean(L, 1); return 1; #endif } /* * Remove FDs */ static int Ldel(lua_State *L) { struct Lpoll_state *state = luaL_checkudata(L, 1, STATE_MT); int fd = luaL_checkinteger(L, 2); #ifdef USE_EPOLL struct epoll_event event; event.data.fd = fd; int ret = epoll_ctl(state->epoll_fd, EPOLL_CTL_DEL, fd, &event); if(ret == 0) { lua_pushboolean(L, 1); return 1; } else { ret = errno; luaL_pushfail(L); lua_pushstring(L, strerror(ret)); lua_pushinteger(L, ret); return 3; } #endif #ifdef USE_SELECT if(!FD_ISSET(fd, &state->all)) { luaL_pushfail(L); lua_pushstring(L, strerror(ENOENT)); lua_pushinteger(L, ENOENT); return 3; } FD_CLR(fd, &state->wantread); FD_CLR(fd, &state->wantwrite); FD_CLR(fd, &state->readable); FD_CLR(fd, &state->writable); FD_CLR(fd, &state->all); FD_CLR(fd, &state->err); lua_pushboolean(L, 1); return 1; #endif } /* * Check previously manipulated event state for FDs ready for reading or writing */ static int Lpushevent(lua_State *L, struct Lpoll_state *state) { #ifdef USE_EPOLL if(state->processed > 0) { state->processed--; struct epoll_event event = state->events[state->processed]; lua_pushinteger(L, event.data.fd); lua_pushboolean(L, event.events & (EPOLLIN | EPOLLHUP | EPOLLRDHUP | EPOLLERR)); lua_pushboolean(L, event.events & EPOLLOUT); return 3; } #endif #ifdef USE_SELECT for(int fd = state->processed + 1; fd < FD_SETSIZE; fd++) { if(FD_ISSET(fd, &state->readable) || FD_ISSET(fd, &state->writable) || FD_ISSET(fd, &state->err)) { lua_pushinteger(L, fd); lua_pushboolean(L, FD_ISSET(fd, &state->readable) | FD_ISSET(fd, &state->err)); lua_pushboolean(L, FD_ISSET(fd, &state->writable)); FD_CLR(fd, &state->readable); FD_CLR(fd, &state->writable); FD_CLR(fd, &state->err); state->processed = fd; return 3; } } #endif return 0; } /* * Wait for event */ static int Lwait(lua_State *L) { struct Lpoll_state *state = luaL_checkudata(L, 1, STATE_MT); int ret = Lpushevent(L, state); if(ret != 0) { return ret; } lua_Number timeout = luaL_checknumber(L, 2); luaL_argcheck(L, timeout >= 0, 1, "positive number expected"); #ifdef USE_EPOLL ret = epoll_wait(state->epoll_fd, state->events, MAX_EVENTS, timeout * 1000); #endif #ifdef USE_SELECT /* * select(2) mutates the fd_sets passed to it so in order to not * have to recreate it manually every time a copy is made. */ memcpy(&state->readable, &state->wantread, sizeof(fd_set)); memcpy(&state->writable, &state->wantwrite, sizeof(fd_set)); memcpy(&state->err, &state->all, sizeof(fd_set)); struct timeval tv; tv.tv_sec = (time_t)timeout; tv.tv_usec = ((suseconds_t)(timeout * 1000000)) % 1000000; ret = select(FD_SETSIZE, &state->readable, &state->writable, &state->err, &tv); #endif if(ret == 0) { /* Is this an error? */ lua_pushnil(L); lua_pushstring(L, "timeout"); return 2; } else if(ret < 0 && errno == EINTR) { /* Is this an error? */ lua_pushnil(L); lua_pushstring(L, "signal"); return 2; } else if(ret < 0) { ret = errno; luaL_pushfail(L); lua_pushstring(L, strerror(ret)); lua_pushinteger(L, ret); return 3; } /* * Search for the first ready FD and return it */ #ifdef USE_EPOLL state->processed = ret; #endif #ifdef USE_SELECT state->processed = -1; #endif return Lpushevent(L, state); } #ifdef USE_EPOLL /* * Return Epoll FD */ static int Lgetfd(lua_State *L) { struct Lpoll_state *state = luaL_checkudata(L, 1, STATE_MT); lua_pushinteger(L, state->epoll_fd); return 1; } /* * Close epoll FD */ static int Lgc(lua_State *L) { struct Lpoll_state *state = luaL_checkudata(L, 1, STATE_MT); if(state->epoll_fd == -1) { return 0; } if(close(state->epoll_fd) == 0) { state->epoll_fd = -1; } else { lua_pushstring(L, strerror(errno)); lua_error(L); } return 0; } #endif /* * String representation */ static int Ltos(lua_State *L) { struct Lpoll_state *state = luaL_checkudata(L, 1, STATE_MT); lua_pushfstring(L, "%s: %p", STATE_MT, state); return 1; } /* * Create a new context */ static int Lnew(lua_State *L) { /* Allocate state */ Lpoll_state *state = lua_newuserdata(L, sizeof(Lpoll_state)); luaL_setmetatable(L, STATE_MT); /* Initialize state */ #ifdef USE_EPOLL state->epoll_fd = -1; state->processed = 0; int epoll_fd = epoll_create1(EPOLL_CLOEXEC); if(epoll_fd <= 0) { luaL_pushfail(L); lua_pushstring(L, strerror(errno)); lua_pushinteger(L, errno); return 3; } state->epoll_fd = epoll_fd; #endif #ifdef USE_SELECT FD_ZERO(&state->wantread); FD_ZERO(&state->wantwrite); FD_ZERO(&state->readable); FD_ZERO(&state->writable); FD_ZERO(&state->all); FD_ZERO(&state->err); state->processed = FD_SETSIZE; #endif return 1; } /* * Open library */ int luaopen_util_poll(lua_State *L) { #if (LUA_VERSION_NUM > 501) luaL_checkversion(L); #endif luaL_newmetatable(L, STATE_MT); { lua_pushliteral(L, STATE_MT); lua_setfield(L, -2, "__name"); lua_pushcfunction(L, Ltos); lua_setfield(L, -2, "__tostring"); lua_createtable(L, 0, 2); { lua_pushcfunction(L, Ladd); lua_setfield(L, -2, "add"); lua_pushcfunction(L, Lset); lua_setfield(L, -2, "set"); lua_pushcfunction(L, Ldel); lua_setfield(L, -2, "del"); lua_pushcfunction(L, Lwait); lua_setfield(L, -2, "wait"); #ifdef USE_EPOLL lua_pushcfunction(L, Lgetfd); lua_setfield(L, -2, "getfd"); #endif } lua_setfield(L, -2, "__index"); #ifdef USE_EPOLL lua_pushcfunction(L, Lgc); lua_setfield(L, -2, "__gc"); #endif } lua_createtable(L, 0, 3); { lua_pushcfunction(L, Lnew); lua_setfield(L, -2, "new"); #define push_errno(named_error) lua_pushinteger(L, named_error);\ lua_setfield(L, -2, #named_error); push_errno(EEXIST); push_errno(ENOENT); lua_pushliteral(L, POLL_BACKEND); lua_setfield(L, -2, "api"); } return 1; }