1 #include <assert.h>
2 #include <string.h>
3 
4 #include <lua.h>
5 #include <lualib.h>
6 #include <lauxlib.h>
7 
8 #if LUA_VERSION_NUM != 503
9 #error only Lua 5.3 supported
10 #endif
11 
12 #define TIMEOUT_PUBLIC static
13 #include "timeout.h"
14 #include "timeout.c"
15 
16 #define TIMEOUT_METANAME "struct timeout"
17 #define TIMEOUTS_METANAME "struct timeouts*"
18 
19 static struct timeout *
to_checkudata(lua_State * L,int index)20 to_checkudata(lua_State *L, int index)
21 {
22 	return luaL_checkudata(L, index, TIMEOUT_METANAME);
23 }
24 
25 static struct timeouts *
tos_checkudata(lua_State * L,int index)26 tos_checkudata(lua_State *L, int index)
27 {
28 	return *(struct timeouts **)luaL_checkudata(L, index, TIMEOUTS_METANAME);
29 }
30 
31 static void
tos_bind(lua_State * L,int tos_index,int to_index)32 tos_bind(lua_State *L, int tos_index, int to_index)
33 {
34 	lua_getuservalue(L, tos_index);
35 	lua_pushlightuserdata(L, to_checkudata(L, to_index));
36 	lua_pushvalue(L, to_index);
37 	lua_rawset(L, -3);
38 	lua_pop(L, 1);
39 }
40 
41 static void
tos_unbind(lua_State * L,int tos_index,int to_index)42 tos_unbind(lua_State *L, int tos_index, int to_index)
43 {
44 	lua_getuservalue(L, tos_index);
45 	lua_pushlightuserdata(L, to_checkudata(L, to_index));
46 	lua_pushnil(L);
47 	lua_rawset(L, -3);
48 	lua_pop(L, 1);
49 }
50 
51 static int
to__index(lua_State * L)52 to__index(lua_State *L)
53 {
54 	struct timeout *to = to_checkudata(L, 1);
55 
56 	if (lua_type(L, 2 == LUA_TSTRING)) {
57 		const char *key = lua_tostring(L, 2);
58 
59 		if (!strcmp(key, "flags")) {
60 			lua_pushinteger(L, to->flags);
61 
62 			return 1;
63 		} else if (!strcmp(key, "expires")) {
64 			lua_pushinteger(L, to->expires);
65 
66 			return 1;
67 		}
68 	}
69 
70 	if (LUA_TNIL != lua_getuservalue(L, 1)) {
71 		lua_pushvalue(L, 2);
72 		if (LUA_TNIL != lua_rawget(L, -2))
73 			return 1;
74 	}
75 
76 	lua_pushvalue(L, 2);
77 	if (LUA_TNIL != lua_rawget(L, lua_upvalueindex(1)))
78 		return 1;
79 
80 	return 0;
81 }
82 
83 static int
to__newindex(lua_State * L)84 to__newindex(lua_State *L)
85 {
86 	if (LUA_TNIL == lua_getuservalue(L, 1)) {
87 		lua_newtable(L);
88 		lua_pushvalue(L, -1);
89 		lua_setuservalue(L, 1);
90 	}
91 
92 	lua_pushvalue(L, 2);
93 	lua_pushvalue(L, 3);
94 	lua_rawset(L, -3);
95 
96 	return 0;
97 }
98 
99 static int
to__gc(lua_State * L)100 to__gc(lua_State *L)
101 {
102 	struct timeout *to = to_checkudata(L, 1);
103 
104 	/*
105 	 * NB: On script exit it's possible for a timeout to still be
106 	 * associated with a timeouts object, particularly when the timeouts
107 	 * object was created first.
108 	 */
109 	timeout_del(to);
110 
111 	return 0;
112 }
113 
114 static int
to_new(lua_State * L)115 to_new(lua_State *L)
116 {
117 	int flags = luaL_optinteger(L, 1, 0);
118 	struct timeout *to;
119 
120 	to = lua_newuserdata(L, sizeof *to);
121 	timeout_init(to, flags);
122 	luaL_setmetatable(L, TIMEOUT_METANAME);
123 
124 	return 1;
125 }
126 
127 static const luaL_Reg to_methods[] = {
128 	{ NULL,  NULL },
129 };
130 
131 static const luaL_Reg to_metatable[] = {
132 	{ "__index",    &to__index },
133 	{ "__newindex", &to__newindex },
134 	{ "__gc",       &to__gc },
135 	{ NULL,         NULL },
136 };
137 
138 static const luaL_Reg to_globals[] = {
139 	{ "new", &to_new },
140 	{ NULL,  NULL },
141 };
142 
143 static void
to_newmetatable(lua_State * L)144 to_newmetatable(lua_State *L)
145 {
146 	if (luaL_newmetatable(L, TIMEOUT_METANAME)) {
147 		/*
148 		 * fill metamethod table, capturing the methods table as an
149 		 * upvalue for use by __index metamethod
150 		 */
151 		luaL_newlib(L, to_methods);
152 		luaL_setfuncs(L, to_metatable, 1);
153 	}
154 }
155 
156 int
luaopen_timeout(lua_State * L)157 luaopen_timeout(lua_State *L)
158 {
159 	to_newmetatable(L);
160 
161 	luaL_newlib(L, to_globals);
162 	lua_pushinteger(L, TIMEOUT_INT);
163 	lua_setfield(L, -2, "INT");
164 	lua_pushinteger(L, TIMEOUT_ABS);
165 	lua_setfield(L, -2, "ABS");
166 
167 	return 1;
168 }
169 
170 static int
tos_update(lua_State * L)171 tos_update(lua_State *L)
172 {
173 	struct timeouts *T = tos_checkudata(L, 1);
174 	lua_Number n = luaL_checknumber(L, 2);
175 
176 	timeouts_update(T, timeouts_f2i(T, n));
177 
178 	lua_pushvalue(L, 1);
179 
180 	return 1;
181 }
182 
183 static int
tos_step(lua_State * L)184 tos_step(lua_State *L)
185 {
186 	struct timeouts *T = tos_checkudata(L, 1);
187 	lua_Number n = luaL_checknumber(L, 2);
188 
189 	timeouts_step(T, timeouts_f2i(T, n));
190 
191 	lua_pushvalue(L, 1);
192 
193 	return 1;
194 }
195 
196 static int
tos_timeout(lua_State * L)197 tos_timeout(lua_State *L)
198 {
199 	struct timeouts *T = tos_checkudata(L, 1);
200 
201 	lua_pushnumber(L, timeouts_i2f(T, timeouts_timeout(T)));
202 
203 	return 1;
204 }
205 
206 static int
tos_add(lua_State * L)207 tos_add(lua_State *L)
208 {
209 	struct timeouts *T = tos_checkudata(L, 1);
210 	struct timeout *to = to_checkudata(L, 2);
211 	lua_Number timeout = luaL_checknumber(L, 3);
212 
213 	tos_bind(L, 1, 2);
214 	timeouts_addf(T, to, timeout);
215 
216 	return lua_pushvalue(L, 1), 1;
217 }
218 
219 static int
tos_del(lua_State * L)220 tos_del(lua_State *L)
221 {
222 	struct timeouts *T = tos_checkudata(L, 1);
223 	struct timeout *to = to_checkudata(L, 2);
224 
225 	timeouts_del(T, to);
226 	tos_unbind(L, 1, 2);
227 
228 	return lua_pushvalue(L, 1), 1;
229 }
230 
231 static int
tos_get(lua_State * L)232 tos_get(lua_State *L)
233 {
234 	struct timeouts *T = tos_checkudata(L, 1);
235 	struct timeout *to;
236 
237 	if (!(to = timeouts_get(T)))
238 		return 0;
239 
240 	lua_getuservalue(L, 1);
241 	lua_rawgetp(L, -1, to);
242 
243 	if (!timeout_pending(to))
244 		tos_unbind(L, 1, lua_absindex(L, -1));
245 
246 	return 1;
247 }
248 
249 static int
tos_pending(lua_State * L)250 tos_pending(lua_State *L)
251 {
252 	struct timeouts *T = tos_checkudata(L, 1);
253 
254 	lua_pushboolean(L, timeouts_pending(T));
255 
256 	return 1;
257 }
258 
259 static int
tos_expired(lua_State * L)260 tos_expired(lua_State *L)
261 {
262 	struct timeouts *T = tos_checkudata(L, 1);
263 
264 	lua_pushboolean(L, timeouts_expired(T));
265 
266 	return 1;
267 }
268 
269 static int
tos_check(lua_State * L)270 tos_check(lua_State *L)
271 {
272 	struct timeouts *T = tos_checkudata(L, 1);
273 
274 	lua_pushboolean(L, timeouts_check(T, NULL));
275 
276 	return 1;
277 }
278 
279 static int
tos__next(lua_State * L)280 tos__next(lua_State *L)
281 {
282 	struct timeouts *T = tos_checkudata(L, lua_upvalueindex(1));
283 	struct timeouts_it *it = lua_touserdata(L, lua_upvalueindex(2));
284 	struct timeout *to;
285 
286 	if (!(to = timeouts_next(T, it)))
287 		return 0;
288 
289 	lua_getuservalue(L, lua_upvalueindex(1));
290 	lua_rawgetp(L, -1, to);
291 
292 	return 1;
293 }
294 
295 static int
tos_timeouts(lua_State * L)296 tos_timeouts(lua_State *L)
297 {
298 	int flags = luaL_checkinteger(L, 2);
299 	struct timeouts_it *it;
300 
301 	tos_checkudata(L, 1);
302 	lua_pushvalue(L, 1);
303 	it = lua_newuserdata(L, sizeof *it);
304 	TIMEOUTS_IT_INIT(it, flags);
305 	lua_pushcclosure(L, &tos__next, 2);
306 
307 	return 1;
308 }
309 
310 static int
tos__gc(lua_State * L)311 tos__gc(lua_State *L)
312 {
313 	struct timeouts **tos = luaL_checkudata(L, 1, TIMEOUTS_METANAME);
314 	struct timeout *to;
315 
316 	TIMEOUTS_FOREACH(to, *tos, TIMEOUTS_ALL) {
317 		timeouts_del(*tos, to);
318 	}
319 
320 	timeouts_close(*tos);
321 	*tos = NULL;
322 
323 	return 0;
324 }
325 
326 static int
tos_new(lua_State * L)327 tos_new(lua_State *L)
328 {
329 	timeout_t hz = luaL_optinteger(L, 1, 0);
330 	struct timeouts **T;
331 	int error;
332 
333 	T = lua_newuserdata(L, sizeof *T);
334 	luaL_setmetatable(L, TIMEOUTS_METANAME);
335 
336 	lua_newtable(L);
337 	lua_setuservalue(L, -2);
338 
339 	if (!(*T = timeouts_open(hz, &error)))
340 		return luaL_error(L, "%s", strerror(error));
341 
342 	return 1;
343 }
344 
345 static const luaL_Reg tos_methods[] = {
346 	{ "update",   &tos_update },
347 	{ "step",     &tos_step },
348 	{ "timeout",  &tos_timeout },
349 	{ "add",      &tos_add },
350 	{ "del",      &tos_del },
351 	{ "get",      &tos_get },
352 	{ "pending",  &tos_pending },
353 	{ "expired",  &tos_expired },
354 	{ "check",    &tos_check },
355 	{ "timeouts", &tos_timeouts },
356 	{ NULL,       NULL },
357 };
358 
359 static const luaL_Reg tos_metatable[] = {
360 	{ "__gc", &tos__gc },
361 	{ NULL,   NULL },
362 };
363 
364 static const luaL_Reg tos_globals[] = {
365 	{ "new", &tos_new },
366 	{ NULL,  NULL },
367 };
368 
369 static void
tos_newmetatable(lua_State * L)370 tos_newmetatable(lua_State *L)
371 {
372 	if (luaL_newmetatable(L, TIMEOUTS_METANAME)) {
373 		luaL_setfuncs(L, tos_metatable, 0);
374 		luaL_newlib(L, tos_methods);
375 		lua_setfield(L, -2, "__index");
376 	}
377 }
378 
379 int
luaopen_timeouts(lua_State * L)380 luaopen_timeouts(lua_State *L)
381 {
382 	to_newmetatable(L);
383 	tos_newmetatable(L);
384 
385 	luaL_newlib(L, tos_globals);
386 	lua_pushinteger(L, TIMEOUTS_PENDING);
387 	lua_setfield(L, -2, "PENDING");
388 	lua_pushinteger(L, TIMEOUTS_EXPIRED);
389 	lua_setfield(L, -2, "EXPIRED");
390 	lua_pushinteger(L, TIMEOUTS_ALL);
391 	lua_setfield(L, -2, "ALL");
392 	lua_pushinteger(L, TIMEOUTS_CLEAR);
393 	lua_setfield(L, -2, "CLEAR");
394 
395 	return 1;
396 }
397