1/* Copyright (c) 2018 CivetWeb developers
2 *
3 * Permission is hereby granted, free of charge, to any person obtaining a copy
4 * of this software and associated documentation files (the "Software"), to deal
5 * in the Software without restriction, including without limitation the rights
6 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 * copies of the Software, and to permit persons to whom the Software is
8 * furnished to do so, subject to the following conditions:
9 *
10 * The above copyright notice and this permission notice shall be included in
11 * all copies or substantial portions of the Software.
12 *
13 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 * THE SOFTWARE.
20 */
21
22
23/* Interface functions */
24LUA_SHARED_INTERFACE void lua_shared_init(void);
25
26LUA_SHARED_INTERFACE void lua_shared_exit(void);
27
28LUA_SHARED_INTERFACE void lua_shared_register(struct lua_State *L);
29
30
31/* Shared data for all Lua states */
32static lua_State *L_shared;
33static pthread_mutex_t lua_shared_lock;
34
35
36/* Library init.
37 * This function must be called before all other functions. Not thread-safe. */
38LUA_SHARED_INTERFACE void
39lua_shared_init(void)
40{
41	/* Create a new Lua state to store all shared data.
42	 * In fact, this is used as a hashmap. */
43	L_shared = lua_newstate(lua_allocator, NULL);
44
45	lua_newtable(L_shared);
46	lua_setglobal(L_shared, "shared");
47
48	/* Mutex for locking access to the shared state from different threads. */
49	pthread_mutex_init(&lua_shared_lock, &pthread_mutex_attr);
50}
51
52
53/* Library exit.
54 * This function should be called for cleanup. Not thread-safe. */
55LUA_SHARED_INTERFACE void
56lua_shared_exit(void)
57{
58	/* Destroy Lua state */
59	lua_close(L_shared);
60	L_shared = 0;
61
62	/* Destroy mutex. */
63	pthread_mutex_destroy(&lua_shared_lock);
64}
65
66#if defined(MG_EXPERIMENTAL_INTERFACES)
67static double
68shared_locked_add(const char *name, size_t namlen, double value, int op)
69{
70	double ret;
71
72	pthread_mutex_lock(&lua_shared_lock);
73
74	lua_getglobal(L_shared, "shared");
75	lua_pushlstring(L_shared, name, namlen);
76	lua_rawget(L_shared, -2);
77	ret = lua_tonumber(L_shared, -1);
78
79	if (op > 0) {
80		ret += value;
81	} else {
82		ret = value;
83	}
84
85	lua_getglobal(L_shared, "shared");
86	lua_pushlstring(L_shared, name, namlen);
87	lua_pushnumber(L_shared, ret);
88	lua_rawset(L_shared, -3);
89
90	lua_pop(L_shared, 3);
91
92	pthread_mutex_unlock(&lua_shared_lock);
93
94	return ret;
95}
96
97
98static int
99lua_shared_add(struct lua_State *L)
100{
101	size_t symlen = 0;
102	const char *sym = lua_tolstring(L, 1, &symlen);
103	double num = lua_tonumber(L, 2);
104
105	double ret = shared_locked_add(sym, symlen, num, 1);
106	lua_pushnumber(L, ret);
107	return 1;
108}
109
110
111static int
112lua_shared_inc(struct lua_State *L)
113{
114	size_t symlen = 0;
115	const char *sym = lua_tolstring(L, 1, &symlen);
116
117	double ret = shared_locked_add(sym, symlen, +1.0, 1);
118	lua_pushnumber(L, ret);
119	return 1;
120}
121
122
123static int
124lua_shared_dec(struct lua_State *L)
125{
126	size_t symlen = 0;
127	const char *sym = lua_tolstring(L, 1, &symlen);
128
129	double ret = shared_locked_add(sym, symlen, -1.0, 1);
130	lua_pushnumber(L, ret);
131	return 1;
132}
133
134
135static int
136lua_shared_exchange(struct lua_State *L)
137{
138	size_t namlen = 0;
139	const char *name = lua_tolstring(L, 1, &namlen);
140	double num = lua_tonumber(L, 2);
141	double ret;
142
143	pthread_mutex_lock(&lua_shared_lock);
144
145	lua_getglobal(L_shared, "shared");
146	lua_pushlstring(L_shared, name, namlen);
147	lua_rawget(L_shared, -2);
148	ret = lua_tonumber(L_shared, -1);
149
150	lua_getglobal(L_shared, "shared");
151	lua_pushlstring(L_shared, name, namlen);
152	lua_pushnumber(L_shared, num);
153	lua_rawset(L_shared, -3);
154
155	lua_pop(L_shared, 3);
156
157	pthread_mutex_unlock(&lua_shared_lock);
158
159	lua_pushnumber(L, ret);
160	return 1;
161}
162
163/*
164static int
165lua_shared_push(struct lua_State *L)
166{
167    int val_type = lua_type(L, 1);
168
169    if ((val_type != LUA_TNUMBER) && (val_type != LUA_TSTRING)
170        && (val_type != LUA_TBOOLEAN)) {
171        return luaL_error(L, "shared value must be string, number or boolean");
172    }
173
174    pthread_mutex_lock(&lua_shared_lock);
175
176    lua_getglobal(L_shared, "shared");
177    lua_pushnumber(L_shared, num);
178
179    if (val_type == LUA_TNUMBER) {
180        double num = lua_tonumber(L, 3);
181        lua_pushnumber(L_shared, num);
182
183    } else if (val_type == LUA_TBOOLEAN) {
184        int i = lua_toboolean(L, 3);
185        lua_pushboolean(L_shared, i);
186
187    } else {
188        size_t len = 0;
189        const char *str = lua_tolstring(L, 3, &len);
190        lua_pushlstring(L_shared, str, len);
191    }
192
193    lua_rawset(L_shared, -3);
194    lua_pop(L_shared, 1);
195
196    pthread_mutex_unlock(&lua_shared_lock);
197
198    return 0;
199}
200*/
201#endif
202
203
204/* Read access to shared element (x = shared.element) */
205static int
206lua_shared_index(struct lua_State *L)
207{
208	int key_type = lua_type(L, 2);
209	int val_type;
210
211	if ((key_type != LUA_TNUMBER) && (key_type != LUA_TSTRING)
212	    && (key_type != LUA_TBOOLEAN)) {
213		return luaL_error(L, "shared index must be string, number or boolean");
214	}
215
216	if (key_type == LUA_TNUMBER) {
217		double num = lua_tonumber(L, 2);
218
219		pthread_mutex_lock(&lua_shared_lock);
220		lua_getglobal(L_shared, "shared");
221		lua_pushnumber(L_shared, num);
222
223	} else if (key_type == LUA_TBOOLEAN) {
224		int i = lua_toboolean(L, 2);
225
226		pthread_mutex_lock(&lua_shared_lock);
227		lua_getglobal(L_shared, "shared");
228		lua_pushboolean(L_shared, i);
229
230	} else {
231		size_t len = 0;
232		const char *str = lua_tolstring(L, 2, &len);
233
234		if ((len > 1) && (0 == memcmp(str, "__", 2))) {
235#if defined(MG_EXPERIMENTAL_INTERFACES)
236			/* Return functions */
237			if (0 == strcmp(str, "__add")) {
238				lua_pushcclosure(L, lua_shared_add, 0);
239			} else if (0 == strcmp(str, "__inc")) {
240				lua_pushcclosure(L, lua_shared_inc, 0);
241			} else if (0 == strcmp(str, "__dec")) {
242				lua_pushcclosure(L, lua_shared_dec, 0);
243			} else if (0 == strcmp(str, "__exchange")) {
244				lua_pushcclosure(L, lua_shared_exchange, 0);
245				/*
246			} else if (0 == strcmp(str, "__push")) {
247				lua_pushcclosure(L, lua_shared_push, 0);
248			} else if (0 == strcmp(str, "__pop")) {
249				lua_pushcclosure(L, lua_shared_pop, 0);
250				*/
251			} else
252#endif
253			{
254				/* Unknown reserved index */
255				lua_pushnil(L);
256			}
257			return 1;
258		}
259
260		pthread_mutex_lock(&lua_shared_lock);
261		lua_getglobal(L_shared, "shared");
262		lua_pushlstring(L_shared, str, len);
263	}
264
265	lua_rawget(L_shared, -2);
266
267	val_type = lua_type(L_shared, -1);
268
269	if (val_type == LUA_TNUMBER) {
270		double num = lua_tonumber(L_shared, -1);
271		lua_pushnumber(L, num);
272
273	} else if (val_type == LUA_TBOOLEAN) {
274		int i = lua_toboolean(L_shared, -1);
275		lua_pushboolean(L, i);
276
277	} else if (val_type == LUA_TNIL) {
278		lua_pushnil(L);
279
280	} else {
281		size_t len = 0;
282		const char *str = lua_tolstring(L_shared, -1, &len);
283		lua_pushlstring(L, str, len);
284	}
285
286	lua_pop(L_shared, 2);
287
288	pthread_mutex_unlock(&lua_shared_lock);
289
290	return 1;
291}
292
293
294/* Write access to shared element (shared.element = x) */
295static int
296lua_shared_newindex(struct lua_State *L)
297{
298	int key_type = lua_type(L, 2);
299	int val_type = lua_type(L, 3);
300
301	if ((key_type != LUA_TNUMBER) && (key_type != LUA_TSTRING)
302	    && (key_type != LUA_TBOOLEAN)) {
303		return luaL_error(L, "shared index must be string, number or boolean");
304	}
305	if ((val_type != LUA_TNUMBER) && (val_type != LUA_TSTRING)
306	    && (val_type != LUA_TBOOLEAN) && (val_type != LUA_TNIL)) {
307		return luaL_error(L, "shared value must be string, number or boolean");
308	}
309
310	if (key_type == LUA_TNUMBER) {
311		double num = lua_tonumber(L, 2);
312
313		pthread_mutex_lock(&lua_shared_lock);
314		lua_getglobal(L_shared, "shared");
315		lua_pushnumber(L_shared, num);
316
317	} else if (key_type == LUA_TBOOLEAN) {
318		int i = lua_toboolean(L, 2);
319
320		pthread_mutex_lock(&lua_shared_lock);
321		lua_getglobal(L_shared, "shared");
322		lua_pushboolean(L_shared, i);
323
324	} else {
325		size_t len = 0;
326		const char *str = lua_tolstring(L, 2, &len);
327
328		if ((len > 1) && (0 == memcmp(str, "__", 2))) {
329			return luaL_error(L, "shared index is reserved");
330		}
331
332		pthread_mutex_lock(&lua_shared_lock);
333		lua_getglobal(L_shared, "shared");
334		lua_pushlstring(L_shared, str, len);
335	}
336
337	if (val_type == LUA_TNUMBER) {
338		double num = lua_tonumber(L, 3);
339		lua_pushnumber(L_shared, num);
340
341	} else if (val_type == LUA_TBOOLEAN) {
342		int i = lua_toboolean(L, 3);
343		lua_pushboolean(L_shared, i);
344
345	} else if (val_type == LUA_TNIL) {
346		lua_pushnil(L_shared);
347
348	} else {
349		size_t len = 0;
350		const char *str = lua_tolstring(L, 3, &len);
351		lua_pushlstring(L_shared, str, len);
352	}
353
354	lua_rawset(L_shared, -3);
355	lua_pop(L_shared, 1);
356
357	pthread_mutex_unlock(&lua_shared_lock);
358
359	return 0;
360}
361
362
363/* Register the "shared" library in a new Lua state.
364 * Call it once for every Lua state accessing "shared" elements. */
365LUA_SHARED_INTERFACE void
366lua_shared_register(struct lua_State *L)
367{
368	lua_newuserdata(L, 0);
369	lua_newtable(L);
370
371	lua_pushliteral(L, "__index");
372	lua_pushcclosure(L, lua_shared_index, 0);
373	lua_rawset(L, -3);
374
375	lua_pushliteral(L, "__newindex");
376	lua_pushcclosure(L, lua_shared_newindex, 0);
377	lua_rawset(L, -3);
378
379	lua_setmetatable(L, -2);
380	lua_setglobal(L, "shared");
381}
382