aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src/sledjchisl.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sledjchisl.c')
-rw-r--r--src/sledjchisl.c1297
1 files changed, 0 insertions, 1297 deletions
diff --git a/src/sledjchisl.c b/src/sledjchisl.c
deleted file mode 100644
index df24ad7..0000000
--- a/src/sledjchisl.c
+++ /dev/null
@@ -1,1297 +0,0 @@
1/* sledjchisl.c - opensim-SC management system.
2 *
3 * Copyright 2020 David Seikel <sledjchisl@sledjhamr.org>
4*/
5
6#include <fcgi_config.h>
7#ifdef _WIN32
8#include <process.h>
9#else
10extern char **environ;
11#endif
12#define NO_FCGI_DEFINES
13#include <fcgi_stdio.h>
14#undef NO_FCGI_DEFINES
15//#include "fcgiapp.h"
16
17#include <lua.h>
18#include <lualib.h>
19#include <lauxlib.h>
20#include <luajit.h>
21
22#include "fcgi_SC.h"
23#include "handlekeys.h"
24
25// Both my_config.h and fcgi_config.h define the same PACKAGE* variables, which we don't use anyway, soooo -
26//#undef PACKAGE
27//#undef PACKAGE_NAME
28//#undef PACKAGE_STRING
29//#undef PACKAGE_TARNAME
30//#undef PACKAGE_VERSION
31//#undef VERSION
32
33// https://mariadb.com/kb/en/about-mariadb-connector-c/ Official docs.
34// http://dev.mysql.com/doc/refman/5.5/en/c-api-function-overview.html MySQL docs.
35// http://zetcode.com/db/mysqlc/ MySQL tutorial.
36#include <my_global.h>
37#include <mysql.h>
38
39#include <qlibc.h>
40#include <extensions/qconfig.h>
41
42// Toybox's library has something really odd in it that causes MariaDB library to crash, no idea what.
43// I've copied the stuff I'm using, and that works.
44#include "toybox.h"
45
46
47//struct toy_context toys;
48char toybuf[4096];
49int isTmux = 0;
50int isWeb = 0;
51char *pwd = "";
52char *scRoot = "/opt/opensim_SC";
53char *scUser = "opensimsc";
54char *Tconsole = "SledjChisl";
55char *Tsocket = "caches/opensim-tmux.socket";
56char *Ttab = "SC";
57char *Tcmd = "tmux -S";
58char *webRoot = "/opt/opensim_SC/web";
59float loadAverageInc = 0.5;
60int simTimeOut = 45;
61
62char *logTypes[] =
63{
64 "CRITICAL",
65 "ERROR",
66 "WARNING",
67 "TIMEOUT",
68 "INFO",
69 "DEBUG",
70};
71
72#define DATE_TIME_LEN 42
73void logMe(int v, char *format, ...)
74{
75 va_list va, va2;
76 int len;
77 char *ret;
78 struct timeval tv;
79 time_t curtime;
80 char date[DATE_TIME_LEN];
81
82 va_start(va, format);
83 va_copy(va2, va);
84 // How long is it?
85 len = vsnprintf(0, 0, format, va);
86 len++;
87 va_end(va);
88 // Allocate and do the sprintf()
89 ret = xmalloc(len);
90 vsnprintf(ret, len, format, va2);
91 va_end(va2);
92
93 gettimeofday(&tv, NULL);
94 curtime = tv.tv_sec;
95 strftime(date, DATE_TIME_LEN, "(%Z %z) %F %R", localtime(&curtime));
96
97 fprintf(stderr, "%s.%.6ld %s: %s\n", date, tv.tv_usec, logTypes[v], ret);
98 free(ret);
99}
100#define C(...) logMe(0, __VA_ARGS__)
101#define E(...) logMe(1, __VA_ARGS__)
102#define W(...) logMe(2, __VA_ARGS__)
103#define T(...) logMe(3, __VA_ARGS__)
104#define I(...) logMe(4, __VA_ARGS__)
105#define D(...) logMe(5, __VA_ARGS__)
106
107
108
109// In Lua 5.0 reference manual is a table traversal example at page 29.
110void PrintTable(lua_State *L)
111{
112 lua_pushnil(L);
113
114 while (lua_next(L, -2) != 0)
115 {
116 // Numbers can convert to strings, so check for numbers before checking for strings.
117 if (lua_isnumber(L, -1))
118 printf("%s = %f\n", lua_tostring(L, -2), lua_tonumber(L, -1));
119 else if (lua_isstring(L, -1))
120 printf("%s = '%s'\n", lua_tostring(L, -2), lua_tostring(L, -1));
121 else if (lua_istable(L, -1))
122 PrintTable(L);
123 lua_pop(L, 1);
124 }
125}
126
127
128int sendTmuxKeys(char *dest, char *keys)
129{
130 int ret = 0, i;
131 char *c = xmprintf("%s %s/%s send-keys -t %s:%s '%s'", Tcmd, scRoot, Tsocket, Tconsole, dest, keys);
132
133 i = system(c);
134 if (!WIFEXITED(i))
135 E("tmux send-keys command failed!");
136 free(c);
137 return ret;
138}
139
140int sendTmuxCmd(char *dest, char *cmd)
141{
142 int ret = 0, i;
143 char *c = xmprintf("%s %s/%s send-keys -t %s:'%s' '%s' Enter", Tcmd, scRoot, Tsocket, Tconsole, dest, cmd);
144
145 i = system(c);
146 if (!WIFEXITED(i))
147 E("tmux send-keys command failed!");
148 free(c);
149 return ret;
150}
151
152void waitTmuxText(char *dest, char *text)
153{
154 int i;
155 char *c = xmprintf("sleep 5; %s %s/%s capture-pane -t %s:'%s' -p | grep -E '%s' 2>&1 > /dev/null", Tcmd, scRoot, Tsocket, Tconsole, dest, text);
156
157 D("Waiting for '%s'.", text);
158 do
159 {
160 i = system(c);
161 if (!WIFEXITED(i))
162 {
163 E("tmux capture-pane command failed!");
164 break;
165 }
166 else if (0 == WEXITSTATUS(i))
167 break;
168 } while (1);
169
170 free(c);
171}
172
173float waitLoadAverage(float la, float extra, int timeout)
174{
175 struct sysinfo info;
176 struct timespec timeOut;
177 float l;
178 int to = timeout;
179
180 I("Sleeping until load average is below %.02f (%.02f + %.02f) or for %d seconds.", la + extra, la, extra, timeout);
181 clock_gettime(CLOCK_MONOTONIC, &timeOut);
182 to += timeOut.tv_sec;
183
184 do
185 {
186 msleep(5000);
187 sysinfo(&info);
188 l = info.loads[0]/65536.0;
189 clock_gettime(CLOCK_MONOTONIC, &timeOut);
190 timeout -= 5;
191 I("Tick, load average is %.02f, countdown %d seconds.", l, timeout);
192 } while (((la + extra) < l) && (timeOut.tv_sec < to));
193
194 return l;
195}
196
197
198typedef struct _simList simList;
199struct _simList
200{
201 int len, num;
202 char **sims;
203};
204
205static int filterSims(struct tb_dirtree *node)
206{
207 if (!node->parent) return TB_DIRTREE_RECURSE | TB_DIRTREE_SHUTUP;
208 if ((strncmp(node->name, "sim", 3) == 0) && ((strcmp(node->name, "sim_skeleton") != 0)))
209 {
210 simList *list = (simList *) node->parent->extra;
211
212 if ((list->num + 1) > list->len)
213 {
214 list->len = list->len + 1;
215 list->sims = xrealloc(list->sims, list->len * sizeof(char *));
216 }
217 list->sims[list->num] = xstrdup(node->name);
218 list->num++;
219 }
220 return 0;
221}
222
223simList *getSims()
224{
225 simList *sims = xmalloc(sizeof(simList));
226 memset(sims, 0, sizeof(simList));
227 memset(toybuf, 0, sizeof(toybuf));
228 snprintf(toybuf, sizeof(toybuf), "%s/config", scRoot);
229 struct tb_dirtree *new = tb_dirtree_add_node(0, toybuf, 0);
230 new->extra = (long) sims;
231 tb_dirtree_handle_callback(new, filterSims);
232 qsort(sims->sims, sims->num, sizeof(char *), qstrcmp);
233 tb_dirtree_free(new);
234 return sims;
235}
236
237
238static int filterInis(struct tb_dirtree *node)
239{
240 if (!node->parent) return TB_DIRTREE_RECURSE | TB_DIRTREE_SHUTUP | TB_DIRTREE_SAVE;
241 int l = strlen(node->name);
242 if (strncmp(&(node->name[l - 4]), ".ini", 4) == 0)
243 {
244 node->parent->extra = (long) node->name;
245 return TB_DIRTREE_ABORT;
246 }
247 return 0;
248}
249
250char *getSimName(char *sim)
251{
252 char *ret = NULL;
253 char *c = xmprintf("%s/config/%s", scRoot, sim);
254 struct tb_dirtree *new = tb_dirtree_add_node(0, c, 0);
255
256 tb_dirtree_handle_callback(new, filterInis);
257 if (new->extra)
258 {
259 char *temp = NULL;
260 regex_t pat;
261 regmatch_t m[2];
262 long len;
263 int fd;
264
265 c = xmprintf("%s/config/%s/%s", scRoot, sim, new->extra);
266 fd = xopenro(c);
267 xregcomp(&pat, "RegionName = \"(.+)\"", REG_EXTENDED);
268 do
269 {
270 // TODO - get_line() is slow, and wont help much with DOS and Mac line endings.
271 temp = get_line(fd);
272 if (temp)
273 {
274 if (!regexec(&pat, temp, 2, m, 0))
275 {
276 // Return first parenthesized subexpression as string.
277 if (pat.re_nsub > 0)
278 {
279 ret = xmprintf("%.*s", (int) (m[1].rm_eo - m[1].rm_so), temp + m[1].rm_so);
280 break;
281 }
282 }
283 }
284 } while (temp);
285 xclose(fd);
286 }
287 tb_dirtree_free(new);
288 return ret;
289}
290
291
292// Expects either "simXX" or "ROBUST".
293int checkSimIsRunning(char *sim)
294{
295 int ret = 0;
296 struct stat st;
297
298 // Check if it's running.
299 memset(toybuf, 0, sizeof(toybuf));
300 snprintf(toybuf, sizeof(toybuf), "%s/caches/%s.pid", scRoot, sim);
301 if (0 == stat(toybuf, &st))
302 {
303 int fd, i;
304 char *pid = NULL;
305
306 // Double check if it's REALLY running.
307 if ((fd = xopenro(toybuf)) == -1)
308 tb_perror_msg("xopenro(%s)", toybuf);
309 else
310 {
311 pid = get_line(fd);
312 if (NULL == pid)
313 tb_perror_msg("get_line(%s)", toybuf);
314 else
315 {
316 xclose(fd);
317
318 memset(toybuf, 0, sizeof(toybuf));
319 snprintf(toybuf, sizeof(toybuf), "ps -p %s --no-headers -o comm", pid);
320 i = system(toybuf);
321 if (WIFEXITED(i))
322 {
323 if (0 != WEXITSTATUS(i)) // No such pid.
324 {
325 memset(toybuf, 0, sizeof(toybuf));
326 snprintf(toybuf, sizeof(toybuf), "rm -f %s/caches/%s.pid", scRoot, sim);
327 D("%s", toybuf);
328 i = system(toybuf);
329 }
330 }
331 }
332 }
333 }
334
335 // Now check if it's really really running. lol
336 memset(toybuf, 0, sizeof(toybuf));
337 snprintf(toybuf, sizeof(toybuf), "%s/caches/%s.pid", scRoot, sim);
338 if (0 == stat(toybuf, &st))
339 ret = 1;
340
341 return ret;
342}
343
344static void PrintEnv(char *label, char **envp)
345{
346 FCGI_printf("%s:<br>\n<pre>\n", label);
347 for ( ; *envp != NULL; envp++)
348 {
349 FCGI_printf("%s\n", *envp);
350 }
351 FCGI_printf("</pre><p>\n");
352}
353
354static void printEnv(char **envp)
355{
356 for ( ; *envp != NULL; envp++)
357 {
358 D("%s", *envp);
359 }
360}
361
362
363my_ulonglong dbCount(MYSQL *db, char *table, char *where)
364{
365 my_ulonglong ret = 0;
366
367 memset(toybuf, 0, sizeof(toybuf));
368 if (NULL == where)
369 snprintf(toybuf, sizeof(toybuf), "SELECT Count(*) FROM %s", table);
370 else
371 snprintf(toybuf, sizeof(toybuf), "SELECT Count(*) FROM %s WHERE %s", table, where);
372
373 if (mysql_query(db, toybuf))
374 E("Query failed: %s", mysql_error(db));
375 else
376 {
377 MYSQL_RES *result = mysql_store_result(db);
378
379 if (!result)
380 E("Couldn't get results set from %s\n: %s", toybuf, mysql_error(db));
381 else
382 {
383 MYSQL_ROW row = mysql_fetch_row(result);
384 if (!row)
385 E("Couldn't get row from %s\n: %s", toybuf, mysql_error(db));
386 else
387 ret = atoll(row[0]);
388 mysql_free_result(result);
389 }
390 }
391
392 return ret;
393}
394
395my_ulonglong dbCountJoin(MYSQL *db, char *table, char *select, char *join, char *where, char *order)
396{
397 my_ulonglong ret = 0;
398
399 if (NULL == select)
400 select = "*";
401
402 memset(toybuf, 0, sizeof(toybuf));
403 if (NULL == where)
404 snprintf(toybuf, sizeof(toybuf), "SELECT %s FROM %s", select, table, join);
405 else
406 snprintf(toybuf, sizeof(toybuf), "SELECT %s FROM %s %s WHERE %s", select, table, join, where);
407
408 if (mysql_query(db, toybuf))
409 E("Query failed: %s", mysql_error(db));
410 else
411 {
412 MYSQL_RES *result = mysql_store_result(db);
413
414 if (!result)
415 E("Couldn't get results set from %s\n: %s", toybuf, mysql_error(db));
416 else
417 ret = mysql_num_rows(result);
418 mysql_free_result(result);
419 }
420
421 return ret;
422}
423
424qlist_t *dbSelect(MYSQL *db, char *table, char *select, char *join, char *where, char *order)
425{
426 qlist_t *ret = qlist(0);
427 if (NULL == select)
428 select = "*";
429
430 memset(toybuf, 0, sizeof(toybuf));
431 if (NULL == where)
432 snprintf(toybuf, sizeof(toybuf), "SELECT %s FROM %s", select, table, join);
433 else
434 snprintf(toybuf, sizeof(toybuf), "SELECT %s FROM %s %s WHERE %s", select, table, join, where);
435
436 if (mysql_query(db, toybuf))
437 E("Query failed: %s", mysql_error(db));
438 else
439 {
440 MYSQL_RES *result = mysql_store_result(db);
441
442 if (!result)
443 E("Couldn't get results set from %s\n: %s", toybuf, mysql_error(db));
444 else
445 {
446 MYSQL_FIELD *fields;
447
448 fields = mysql_fetch_fields(result);
449
450 if (!fields)
451 E("Faild fetching fields: %s", mysql_error(db));
452 else
453 {
454 unsigned int i, num_fields = mysql_num_fields(result);
455
456 MYSQL_ROW row;
457
458 while ((row = mysql_fetch_row(result)))
459 {
460 qhashtbl_t *flds = qhashtbl(0, 0);
461
462 for (i = 0; i < num_fields; i++)
463 {
464 flds->putstr(flds, fields[i].name, row[i]);
465 }
466 ret->addlast(ret, flds, sizeof(*flds));
467 }
468 }
469 }
470 mysql_free_result(result);
471 }
472
473 return ret;
474}
475
476void replaceStr(qhashtbl_t *ssi, char *key, char *value)
477{
478// char *tmp;
479
480// I think this is taken care of already.
481// if ((tmp = ssi->getstr(ssi, key, false)) != NULL)
482// free(tmp);
483 ssi->putstr(ssi, key, value);
484}
485
486void replaceLong(qhashtbl_t *ssi, char *key, my_ulonglong value)
487{
488 char *tmp = xmprintf("%lu", value);
489
490 replaceStr(ssi, key, tmp);
491 free(tmp);
492}
493
494
495float timeDiff(struct timeval *now, struct timeval *then)
496{
497 if (0 == gettimeofday(now, NULL))
498 {
499 struct timeval thisTime = { 0, 0 };
500 double result = 0.0;
501
502 thisTime.tv_sec = now->tv_sec;
503 thisTime.tv_usec = now->tv_usec;
504 if (thisTime.tv_usec < then->tv_usec)
505 {
506 thisTime.tv_sec--;
507 thisTime.tv_usec += 1000000;
508 }
509 thisTime.tv_usec -= then->tv_usec;
510 thisTime.tv_sec -= then->tv_sec;
511 result = ((double) thisTime.tv_usec) / ((double) 1000000.0);
512 result += thisTime.tv_sec;
513 return result;
514 }
515 else
516 return 0.0;
517}
518
519typedef struct _gridStats gridStats;
520struct _gridStats
521{
522 float next;
523 struct timeval last;
524 qhashtbl_t *stats;
525};
526
527gridStats *getStats(MYSQL *db, gridStats *stats)
528{
529 if (NULL == stats)
530 {
531 stats = xmalloc(sizeof(gridStats));
532 stats->next = 300;
533 gettimeofday(&(stats->last), NULL);
534 stats->stats = qhashtbl(0, 0);
535 stats->stats->putstr(stats->stats, "version", "SledjChisl FCGI Dev 0.1");
536 stats->stats->putstr(stats->stats, "grid", "my grid");
537 stats->stats->putstr(stats->stats, "uri", "http://localhost:8002/");
538
539 stats->stats->putstr(stats->stats, "gridOnline", "??");
540 }
541 else
542 {
543 static struct timeval thisTime;
544 if (stats->next > timeDiff(&thisTime, &(stats->last)))
545 return stats;
546 }
547
548 if (db)
549 {
550 I("Getting fresh grid stats.");
551 char *tmp;
552 my_ulonglong locIn = dbCount(db, "Presence", "RegionID != '00000000-0000-0000-0000-000000000000'"); // Locals online but not HGing, and HGers in world.
553 my_ulonglong HGin = dbCount(db, "Presence", "UserID NOT IN (SELECT PrincipalID FROM UserAccounts)"); // HGers in world.
554
555 // Collect stats about members.
556 replaceLong(stats->stats, "hgers", HGin);
557 replaceLong(stats->stats, "inworld", locIn - HGin);
558 tmp = xmprintf("GridExternalName != '%s'", stats->stats->getstr(stats->stats, "uri", false));
559 replaceLong(stats->stats, "outworld", dbCount(db, "hg_traveling_data", tmp));
560 free(tmp);
561 replaceLong(stats->stats, "members", dbCount(db, "UserAccounts", NULL));
562
563 // Count local and HG visitors for the last 30 and 60 days.
564 locIn = dbCountJoin(db, "GridUser", "GridUser.UserID", "INNER JOIN UserAccounts ON GridUser.UserID = UserAccounts.PrincipalID",
565 "Login > UNIX_TIMESTAMP(FROM_UNIXTIME(UNIX_TIMESTAMP(now()) - 2419200))", "");
566 HGin = dbCount(db, "GridUser", "Login > UNIX_TIMESTAMP(FROM_UNIXTIME(UNIX_TIMESTAMP(now()) - 2419200))");
567 replaceLong(stats->stats, "locDay30", locIn);
568 replaceLong(stats->stats, "day30", HGin);
569 replaceLong(stats->stats, "HGday30", HGin - locIn);
570
571 locIn = dbCountJoin(db, "GridUser", "GridUser.UserID", "INNER JOIN UserAccounts ON GridUser.UserID = UserAccounts.PrincipalID",
572 "Login > UNIX_TIMESTAMP(FROM_UNIXTIME(UNIX_TIMESTAMP(now()) - 4838400))", "");
573 HGin = dbCount(db, "GridUser", "Login > UNIX_TIMESTAMP(FROM_UNIXTIME(UNIX_TIMESTAMP(now()) - 4838400))");
574 replaceLong(stats->stats, "locDay60", locIn);
575 replaceLong(stats->stats, "day60", HGin);
576 replaceLong(stats->stats, "HGday60", HGin - locIn);
577
578 // Collect stats about sims.
579 replaceLong(stats->stats, "sims", dbCount(db, "regions", NULL));
580 replaceLong(stats->stats, "onlineSims", dbCount(db, "regions", "sizeX != 0"));
581 replaceLong(stats->stats, "varRegions", dbCount(db, "regions", "sizeX > 256 or sizeY > 256"));
582 replaceLong(stats->stats, "singleSims", dbCount(db, "regions", "sizeX = 256 and sizeY = 256"));
583 replaceLong(stats->stats, "offlineSims", dbCount(db, "regions", "sizeX = 0"));
584
585 // Calculate total size of all regions.
586 qlist_t *regions = dbSelect(db, "regions", "sizeX,sizeY", "", "sizeX != 0", "");
587 qlist_obj_t obj;
588 my_ulonglong simSize = 0;
589
590 memset((void *) &obj, 0, sizeof(obj)); // must be cleared before call
591 regions->lock(regions);
592 while (regions->getnext(regions, &obj, false) == true)
593 {
594 qhashtbl_t *row = (qhashtbl_t *) obj.data;
595 my_ulonglong x = 0, y = 0;
596
597 tmp = row->getstr(row, "sizeX", false);
598 if (NULL == tmp)
599 E("No regions.sizeX!");
600 else
601 x = atoll(tmp);
602 tmp = row->getstr(row, "sizeY", false);
603 if (NULL == tmp)
604 E("No regions.sizeY!");
605 else
606 y = atoll(tmp);
607 simSize += x * y;
608 }
609 regions->unlock(regions);
610 tmp = xmprintf("%lu", simSize);
611 stats->stats->putstr(stats->stats, "simsSize", tmp);
612 free(tmp);
613 gettimeofday(&(stats->last), NULL);
614 }
615 return stats;
616}
617
618
619enum fragmentType
620{
621 FT_TEXT,
622 FT_PARAM,
623 FT_LUA
624};
625
626typedef struct _fragment fragment;
627struct _fragment
628{
629 enum fragmentType type;
630 int length;
631 char *text;
632};
633
634typedef struct _HTMLfile HTMLfile;
635struct _HTMLfile
636{
637 struct timespec last;
638 qlist_t *fragments;
639};
640
641qhashtbl_t *HTMLfileCache = NULL;
642
643fragment *newFragment(enum fragmentType type, char *text, int len)
644{
645 fragment *frg = xmalloc(sizeof(fragment));
646 frg->type = type;
647 frg->length = len;
648 frg->text = xmalloc(len + 1);
649 memcpy(frg->text, text, len);
650 frg->text[len] = '\0';
651 return frg;
652}
653
654HTMLfile *checkHTMLcache(char *file)
655{
656 if (NULL == HTMLfileCache)
657 HTMLfileCache = qhashtbl(0, 0);
658
659 HTMLfile *ret = (HTMLfile *) HTMLfileCache->get(HTMLfileCache, file, NULL, false);
660 int fd = open(file, O_RDONLY);
661 size_t length = 0;
662 fragment *frg0, *frg1;
663
664 if (-1 == fd)
665 E("Failed to open %s", file);
666 else
667 {
668 struct stat sb;
669 if (fstat(fd, &sb) == -1)
670 E("Failed to stat %s", file);
671 else
672 {
673 if ((NULL != ret) && (ret->last.tv_sec < sb.st_mtim.tv_sec))
674 {
675 HTMLfileCache->remove(HTMLfileCache, file);
676 ret = NULL;
677 }
678
679 if (NULL == ret)
680 {
681 char *mm = MAP_FAILED;
682
683 ret = xmalloc(sizeof(HTMLfile));
684 ret->fragments = qlist(QLIST_THREADSAFE);
685 length = sb.st_size;
686 ret->last.tv_sec = sb.st_mtim.tv_sec;
687 ret->last.tv_nsec = sb.st_mtim.tv_nsec;
688
689 I("Loading web template %s", file);
690 D("Web template %s is %d bytes long.", file, length);
691
692 mm = mmap(NULL, length, PROT_READ, MAP_SHARED | MAP_POPULATE, fd, 0);
693 if (mm == MAP_FAILED)
694 E("Failed to mmap %s", file);
695 else
696 {
697 char *h;
698 int i, j = 0, k = 0, l, m;
699
700 // Scan for server side includes style markings.
701 for (i = 0; i < length; i++)
702 {
703 if (i + 5 < length)
704 {
705 if (('<' == mm[i]) && ('!' == mm[i + 1]) && ('-' == mm[i + 2]) && ('-' == mm[i + 3]) && ('#' == mm[i + 4])) // '<!--#'
706 {
707 m = i;
708 i += 5;
709 if (i < length)
710 {
711 if (('e' == mm[i]) && ('c' == mm[i + 1]) && ('h' == mm[i + 2]) && ('o' == mm[i + 3]) && (' ' == mm[i + 4])) // 'echo '
712 {
713 i += 5;
714 if (i + 5 < length)
715 {
716 if (('v' == mm[i]) && ('a' == mm[i + 1]) && ('r' == mm[i + 2]) && ('=' == mm[i + 3]) && ('"' == mm[i + 4])) // 'var="'
717 {
718 i += 5;
719 for (j = i; j < length; j++)
720 {
721 if ('"' == mm[j]) // '"'
722 {
723 frg1 = newFragment(FT_PARAM, &mm[i], j - i);
724 i = j + 1;
725 if (i + 4 < length)
726 {
727 if ((' ' == mm[i]) && ('-' == mm[i + 1]) && ('-' == mm[i + 2]) && ('>' == mm[i + 3])) // ' -->'
728 i += 4;
729 }
730 frg0 = newFragment(FT_TEXT, &mm[k], m - k);
731 ret->fragments->addlast(ret->fragments, frg0, sizeof(*frg0));
732 ret->fragments->addlast(ret->fragments, frg1, sizeof(*frg1));
733 k = i;
734 break;
735 }
736 }
737 }
738 }
739 }
740 }
741 }
742 }
743 }
744 frg0 = newFragment(FT_TEXT, &mm[k], length - k);
745 ret->fragments->addlast(ret->fragments, frg0, sizeof(*frg0));
746
747 if (-1 == munmap(mm, length))
748 FCGI_fprintf(FCGI_stderr, "Failed to munmap %s\n", file);
749
750 HTMLfileCache->put(HTMLfileCache, file, ret, sizeof(*ret));
751 }
752 }
753 close(fd);
754 }
755 }
756
757 return ret;
758}
759
760
761int main(int argc, char *argv[], char **env)
762{
763 // don't segfault if our environment is crazy
764 if (!*argv) return 127;
765
766 char *cmd = *argv;
767 char *tmp;
768 qhashtbl_t *configs = qhashtbl(0, 0);
769 lua_State *L = luaL_newstate();
770 MYSQL *database = NULL, *dbconn = NULL;
771 gridStats *stats = NULL;
772 int status, result, i;
773 void *vd;
774
775 pwd = getcwd(0, 0);
776
777 if (isatty(1))
778 {
779 I("Outputting to a terminal, not a web server.");
780 // Check if we are already running inside the proper tmux server.
781 char *eTMUX = getenv("TMUX");
782 memset(toybuf, 0, sizeof(toybuf));
783 snprintf(toybuf, sizeof(toybuf), "%s/%s", scRoot, Tsocket);
784 if (((eTMUX) && (0 == strncmp(toybuf, eTMUX, strlen(toybuf)))))
785 {
786 I("Running inside the proper tmux server.");
787 isTmux = 1;
788 }
789 else
790 I("Not running inside the proper tmux server, starting it.");
791 I("libfcgi version: %s", FCGI_VERSION);
792 I("Lua version: %s", LUA_RELEASE);
793 I("LuaJIT version: %s", LUAJIT_VERSION);
794 I("MariaDB / MySQL client version: %s", mysql_get_client_info());
795 }
796 else
797 isWeb = 1;
798
799
800/* From http://luajit.org/install.html -
801To change or extend the list of standard libraries to load, copy
802src/lib_init.c to your project and modify it accordingly. Make sure the
803jit library is loaded or the JIT compiler will not be activated.
804*/
805 luaL_openlibs(L); // Load Lua libraries.
806
807 // Load the config scripts.
808 char *cPaths[] =
809 {
810 "/etc/sledjChisl.conf.lua",
811// "/etc/sledjChisl.d/*.lua",
812 "~/.sledjChisl.conf.lua",
813// "~/.config/sledjChisl/*.lua",
814 ".sledjChisl.conf.lua",
815 NULL
816 };
817 struct stat st;
818
819
820 for (i = 0; cPaths[i]; i++)
821 {
822 memset(toybuf, 0, sizeof(toybuf));
823 if (('/' == cPaths[i][0]) || ('~' == cPaths[i][0]))
824 snprintf(toybuf, sizeof(toybuf), "%s", cPaths[i]);
825 else
826 snprintf(toybuf, sizeof(toybuf), "%s/%s", pwd, cPaths[i]);
827 if (0 != lstat(toybuf, &st))
828 continue;
829 if (!isWeb) I("Loading configuration file - %s", toybuf);
830 status = luaL_loadfile(L, toybuf);
831 if (status) // If something went wrong, error message is at the top of the stack.
832 E("Couldn't load file: %s", lua_tostring(L, -1));
833 else
834 {
835 result = lua_pcall(L, 0, LUA_MULTRET, 0);
836 if (result)
837 E("Failed to run script: %s", lua_tostring(L, -1));
838 else
839 {
840 lua_getglobal(L, "config");
841 lua_pushnil(L);
842 while(lua_next(L, -2) != 0)
843 {
844 char *n = (char *) lua_tostring(L, -2);
845
846 // Numbers can convert to strings, so check for numbers before checking for strings.
847 // On the other hand, strings that can be converted to numbers also pass lua_isnumber(). sigh
848 if (lua_isnumber(L, -1))
849 {
850 float v = lua_tonumber(L, -1);
851 configs->put(configs, n, &v, sizeof(float));
852 }
853 else if (lua_isstring(L, -1))
854 configs->putstr(configs, n, (char *) lua_tostring(L, -1));
855 else
856 {
857 char *v = (char *) lua_tostring(L, -1);
858 E("Unknown config variable type for %s = %s", n, v);
859 }
860 lua_pop(L, 1);
861 }
862 }
863 }
864 }
865 if ((vd = configs->get (configs, "loadAverageInc", NULL, false)) != NULL) {loadAverageInc = *((float *) vd); D("Setting loadAverageInc = %f", loadAverageInc);}
866 if ((vd = configs->get (configs, "simTimeOut", NULL, false)) != NULL) {simTimeOut = (int) *((float *) vd); D("Setting simTimeOut = %d", simTimeOut);}
867 if ((tmp = configs->getstr(configs, "scRoot", false)) != NULL) {scRoot = tmp; D("Setting scRoot = %s", scRoot);}
868 if ((tmp = configs->getstr(configs, "scUser", false)) != NULL) {scUser = tmp; D("Setting scUser = %s", scUser);}
869 if ((tmp = configs->getstr(configs, "Tconsole", false)) != NULL) {Tconsole = tmp; D("Setting Tconsole = %s", Tconsole);}
870 if ((tmp = configs->getstr(configs, "Tsocket", false)) != NULL) {Tsocket = tmp; D("Setting Tsocket = %s", Tsocket);}
871 if ((tmp = configs->getstr(configs, "Ttab", false)) != NULL) {Ttab = tmp; D("Setting Ttab = %s", Ttab);}
872 if ((tmp = configs->getstr(configs, "webRoot", false)) != NULL) {webRoot = tmp; D("Setting webRoot = %s", webRoot);}
873
874
875 if (isTmux || isWeb)
876 {
877 char *d;
878 memset(toybuf, 0, sizeof(toybuf));
879// TODO - the problem here is that web server isn't in the opensimmc group, so can't read this file ,with the database credentials.
880// Other web server things have access to database credentials, so not like this is a big problem.
881// For now I've just opened up the perms on it on my desktop.
882 snprintf(toybuf, sizeof(toybuf), "%s/config/config.ini", scRoot);
883
884 qlisttbl_t *qconfig = qconfig_parse_file(NULL, toybuf, '=');
885 d = qstrunchar(qconfig->getstr(qconfig, "Const.ConnectionString", false), '"', '"');
886
887 if (NULL == d)
888 {
889 E("No database credentials in %s!", toybuf);
890 goto finished;
891 }
892 else
893 {
894 char *p0, *p1, *p2;
895 if (NULL == (d = strdup(d)))
896 {
897 E("Out of memory!");
898 goto finished;
899 }
900 // Data Source=MYSQL_HOST;Database=MYSQL_DB;User ID=MYSQL_USER;Password=MYSQL_PASSWORD;Old Guids=true;
901 p0 = d;
902 while (NULL != p0)
903 {
904 p1 = strchr(p0, '=');
905 if (NULL == p1) break;
906 *p1 = '\0';
907 p2 = strchr(p1 + 1, ';');
908 if (NULL == p2) break;
909 *p2 = '\0';
910 configs->putstr(configs, p0, p1 + 1); // NOTE - this allocs memory for it's key and it's data.
911 p0 = p2 + 1;
912 if ('\0' == *p0)
913 p0 = NULL;
914 };
915 free(d);
916 }
917 if (mysql_library_init(argc, argv, NULL))
918 {
919 E("mysql_library_init() failed!");
920 goto finished;
921 }
922
923 database = mysql_init(NULL);
924 if (NULL == database)
925 {
926 E("mysql_init() failed - %s", mysql_error(database));
927 goto finished;
928 }
929 else
930 {
931 // I have no idea what evil magic toybox is doing, but this ALWAYS CRASHES, no matter what I do.
932 dbconn = mysql_real_connect(database,
933 configs->getstr(configs, "Data Source", true),
934 configs->getstr(configs, "User ID", true),
935 configs->getstr(configs, "Password", true),
936 configs->getstr(configs, "Database", true),
937// 3036, "/var/run/mysqld/mysqld.sock",
938 0, NULL,
939 CLIENT_FOUND_ROWS | CLIENT_LOCAL_FILES | CLIENT_MULTI_STATEMENTS | CLIENT_MULTI_RESULTS);
940 if (NULL == dbconn)
941 {
942 E("mysql_real_connect() failed - %s", mysql_error(database));
943 goto finished;
944 }
945
946 // Need to kick this off.
947 stats = getStats(database, stats);
948 char *h = qstrunchar(qconfig->getstr(qconfig, "Const.HostName", false), '"', '"');
949 char *p = qstrunchar(qconfig->getstr(qconfig, "Const.PublicPort", false), '"', '"');
950 stats->stats->putstr(stats->stats, "grid", qstrunchar(qconfig->getstr(qconfig, "Const.GridName", false), '"', '"'));
951 stats->stats->putstr(stats->stats, "HostName", h);
952 stats->stats->putstr(stats->stats, "PublicPort", p);
953 snprintf(toybuf, sizeof(toybuf), "http://%s:%s/", h, p);
954
955 stats->stats->putstr(stats->stats, "uri", toybuf);
956 }
957 }
958
959
960 if (isWeb)
961 {
962 char **initialEnv = env;
963 int count = 0, entries, bytes;
964
965 // FCGI_LISTENSOCK_FILENO is the socket to the web server.
966 // STDOUT and STDERR go to the web servers error log, or at least it does in Apache 2.
967// fprintf(stdout, "STDOUT Started SledjChisl web server.\n");
968// fprintf(stderr, "STDERR Started SledjChisl web server.\n");
969 I("Running SledjChisl inside a web server.");
970
971 if (1 == argc)
972 D("no args");
973 else
974 {
975 for (i = 0; argv[i] != NULL; i++)
976 D("ARG %s", argv[i]);
977 }
978 printEnv(env);
979
980 struct stat statbuf;
981 if (-1 == fstat(FCGI_LISTENSOCK_FILENO, &statbuf))
982 tb_error_msg("fstat() failed");
983 else
984 {
985 if (S_ISREG (statbuf.st_mode)) D("regular file");
986 else if (S_ISDIR (statbuf.st_mode)) D("directory");
987 else if (S_ISCHR (statbuf.st_mode)) D("character device");
988 else if (S_ISBLK (statbuf.st_mode)) D("block device");
989 else if (S_ISFIFO(statbuf.st_mode)) D("FIFO (named pipe)");
990 else if (S_ISLNK (statbuf.st_mode)) D("symbolic link");
991 else if (S_ISSOCK(statbuf.st_mode)) D("socket");
992 else D("unknown file descriptor type");
993 }
994
995
996 while (FCGI_Accept() != -1)
997 {
998 if (NULL == getenv("PATH_INFO")) {msleep(1000); continue;}
999
1000 char *contentLength = getenv("CONTENT_LENGTH");
1001 int len;
1002
1003 if (contentLength != NULL)
1004 len = strtol(contentLength, NULL, 10);
1005 else
1006 len = 0;
1007
1008
1009 if (FCGX_IsCGI())
1010 D("Started SledjChisl CGI web request ROLE = %s!", getenv("FCGI_ROLE"));
1011 else
1012 D("Started SledjChisl FCGI web request ROLE = %s.", getenv("FCGI_ROLE"));
1013
1014 memset(toybuf, 0, sizeof(toybuf));
1015 snprintf(toybuf, sizeof(toybuf), "%s/html%s", webRoot, getenv("PATH_INFO"));
1016
1017 HTMLfile *thisFile = checkHTMLcache(toybuf);
1018
1019 getStats(database, stats);
1020
1021// This is dynamic content, it's always gonna be modified. I think.
1022// char *since = getenv("If-Modified-Since");
1023// if (NULL != since)
1024// {
1025// time_t snc = qtime_parse_gmtstr(since);
1026// if (thisFile->last.tv_sec < snc)
1027// {
1028// D("Status: 304 Not Modified - %s", toybuf);
1029// FCGI_printf("Status: 304 Not Modified\r\n");
1030// goto fcgiDone;
1031// }
1032// }
1033
1034 FCGI_printf("Status: 200 OK\r\n"
1035 "Content-type: text/html\r\n"
1036 "\r\n");
1037 if (len <= 0)
1038 D("No data from standard input.");
1039 else
1040 {
1041 int i, ch;
1042
1043 FCGI_printf("Standard input:<br>\n<pre>\n");
1044 for (i = 0; i < len; i++)
1045 {
1046 if ((ch = FCGI_getchar()) < 0)
1047 {
1048 E("Error: Not enough bytes received on standard input<p>");
1049 break;
1050 }
1051 FCGI_putchar(ch);
1052 }
1053 FCGI_printf("\n</pre><p>\n");
1054 }
1055
1056
1057 qlist_obj_t obj;
1058 memset((void *) &obj, 0, sizeof(obj)); // must be cleared before call
1059 thisFile->fragments->lock(thisFile->fragments);
1060 while (thisFile->fragments->getnext(thisFile->fragments, &obj, false) == true)
1061 {
1062 fragment *frg = (fragment *) obj.data;
1063 if (NULL == frg->text)
1064 {
1065 E("NULL fragment!");
1066 continue;
1067 }
1068 switch (frg->type)
1069 {
1070 case FT_TEXT:
1071 {
1072 // Silly thing triggers a "mod_fcgid: ap_pass_brigade failed in handle_request_ipc function" in Apache 2.4, sometimes.
1073 // Haven't seen one for some time.
1074 // https://www.tablix.org/~avian/blog/archives/2016/05/on_ap_pass_brigade_failed/
1075 int l = FCGI_fwrite(frg->text, 1, frg->length, FCGI_stdout);
1076 if (l != frg->length)
1077 E("Failed to write %d != %d", l, frg->length);
1078 break;
1079 }
1080
1081 case FT_PARAM:
1082 {
1083 if (strcmp("DEBUG", frg->text) == 0)
1084 {
1085 FCGI_printf("<h1>FastCGI SledjChisl</h1>\n"
1086 "Request number %d, Process ID: %d<p>\n", ++count, getpid());
1087 FCGI_printf("<p>libfcgi version: %s</p>\n", FCGI_VERSION);
1088 FCGI_printf("<p>Lua version: %s</p>\n", LUA_RELEASE);
1089 FCGI_printf("<p>LuaJIT version: %s</p>\n", LUAJIT_VERSION);
1090 FCGI_printf("<p>MySQL client version: %s</p>\n", mysql_get_client_info());
1091 PrintEnv("Initial environment", initialEnv);
1092 PrintEnv("Request environment", environ);
1093 }
1094 else if (strcmp("URL", frg->text) == 0)
1095 FCGI_printf("%s://%s%s", getenv("REQUEST_SCHEME"), getenv("HTTP_HOST"), getenv("SCRIPT_NAME"));
1096 else
1097 {
1098 if ((tmp = stats->stats->getstr(stats->stats, frg->text, false)) != NULL)
1099 FCGI_printf("%s", tmp);
1100 else
1101 FCGI_printf("<b>%s</b>", frg->text);
1102 }
1103 break;
1104 }
1105
1106 case FT_LUA:
1107 break;
1108 }
1109 }
1110 thisFile->fragments->unlock(thisFile->fragments);
1111
1112fcgiDone:
1113 D("Finishing SledjChisl web request.");
1114 FCGI_Finish();
1115 }
1116
1117 FCGI_fprintf(FCGI_stdout, "Stopped SledjChisl web server.\n");
1118 D("Stopped SledjChisl web server.");
1119
1120 goto finished;
1121 }
1122
1123
1124 if (!isTmux)
1125 { // Let's see if the proper tmux server is even running.
1126 memset(toybuf, 0, sizeof(toybuf));
1127 snprintf(toybuf, sizeof(toybuf), "%s %s/%s -q list-sessions 2>/dev/null | grep -q %s:", Tcmd, scRoot, Tsocket, Tconsole);
1128 i = system(toybuf);
1129 if (WIFEXITED(i))
1130 {
1131 if (0 != WEXITSTATUS(i)) // No such sesion, create it.
1132 {
1133 memset(toybuf, 0, sizeof(toybuf));
1134 // The sudo is only so that the session is owned by opensim, otherwise it's owned by whoever ran this script, which is a likely security hole.
1135 // After the session is created, we rely on the caches directory to be group sticky, so that anyone in the opensim group can attach to the tmux socket.
1136 snprintf(toybuf, sizeof(toybuf),
1137 "sudo -Hu %s %s %s/%s new-session -d -s %s -n '%s' \\; split-window -bhp 50 -t '%s:' bash -c './sledjchisl; cd %s; bash'",
1138 scUser, Tcmd, scRoot, Tsocket, Tconsole, Ttab, Tconsole, scRoot);
1139 i = system(toybuf);
1140 if (!WIFEXITED(i))
1141 E("tmux new-session command failed!");
1142 }
1143 // Join the session.
1144 memset(toybuf, 0, sizeof(toybuf));
1145 snprintf(toybuf, sizeof(toybuf), "%s %s/%s select-window -t '%s' \\; attach-session -t '%s'", Tcmd, scRoot, Tsocket, Tconsole, Tconsole);
1146 i = system(toybuf);
1147 if (!WIFEXITED(i))
1148 E("tmux attach-session command failed!");
1149 goto finished;
1150 }
1151 else
1152 E("tmux list-sessions command failed!");
1153 }
1154
1155
1156
1157 simList *sims = getSims();
1158 if (1)
1159 {
1160 struct sysinfo info;
1161 float la;
1162
1163 sysinfo(&info);
1164 la = info.loads[0]/65536.0;
1165
1166 if (!checkSimIsRunning("ROBUST"))
1167 {
1168 char *d = xmprintf("%s.{right}", Ttab);
1169 char *c = xmprintf("cd %s/current/bin", scRoot);
1170
1171 I("ROBUST is starting up.");
1172 sendTmuxCmd(d, c);
1173 free(c);
1174 c = xmprintf("mono Robust.exe -inidirectory=%s/config/ROBUST", scRoot);
1175 sendTmuxCmd(d, c);
1176 free(c);
1177 waitTmuxText(d, "INITIALIZATION COMPLETE FOR ROBUST");
1178 I("ROBUST is done starting up.");
1179 la = waitLoadAverage(la, loadAverageInc / 3.0, simTimeOut / 3);
1180 free(d);
1181 }
1182
1183 for (i = 0; i < sims->num; i++)
1184 {
1185 char *sim = sims->sims[i], *name = getSimName(sims->sims[i]);
1186
1187 if (!checkSimIsRunning(sim))
1188 {
1189 I("%s is starting up.", name);
1190 memset(toybuf, 0, sizeof(toybuf));
1191 snprintf(toybuf, sizeof(toybuf), "%s %s/%s new-window -dn '%s' -t '%s:%d' 'cd %s/current/bin; mono OpenSim.exe -inidirectory=%s/config/%s'",
1192 Tcmd, scRoot, Tsocket, name, Tconsole, i + 1, scRoot, scRoot, sim);
1193 int r = system(toybuf);
1194 if (!WIFEXITED(r))
1195 E("tmux new-window command failed!");
1196 else
1197 {
1198 memset(toybuf, 0, sizeof(toybuf));
1199 snprintf(toybuf, sizeof(toybuf), "INITIALIZATION COMPLETE FOR %s", name);
1200 waitTmuxText(name, toybuf);
1201 I("%s is done starting up.", name);
1202 la = waitLoadAverage(la, loadAverageInc, simTimeOut);
1203 }
1204 }
1205 }
1206
1207 }
1208 else if (!strcmp(cmd, "create")) // "create name x,y size"
1209 {
1210 }
1211 else if (!strcmp(cmd, "start")) // "start sim01" "start Welcome" "start" start everything
1212 {
1213 }
1214 else if (!strcmp(cmd, "backup")) // "backup onefang rejected" "backup sim01" "backup Welcome" "backup" backup everything
1215 { // If it's not a sim code, and not a sim name, it's an account inventory.
1216 }
1217 else if (!strcmp(cmd, "gitAR")) // "gitAR i name"
1218 {
1219 }
1220 else if (!strcmp(cmd, "stop")) // "stop sim01" "stop Welcome" "stop" stop everything
1221 {
1222 }
1223
1224
1225 double sum;
1226
1227 // Load the file containing the script we are going to run
1228 status = luaL_loadfile(L, "script.lua");
1229 if (status)
1230 {
1231 // If something went wrong, error message is at the top of the stack
1232 E("Couldn't load file: %s", lua_tostring(L, -1));
1233 goto finished;
1234 }
1235
1236 /*
1237 * Ok, now here we go: We pass data to the lua script on the stack.
1238 * That is, we first have to prepare Lua's virtual stack the way we
1239 * want the script to receive it, then ask Lua to run it.
1240 */
1241 lua_newtable(L); /* We will pass a table */
1242
1243 /*
1244 * To put values into the table, we first push the index, then the
1245 * value, and then call lua_rawset() with the index of the table in the
1246 * stack. Let's see why it's -3: In Lua, the value -1 always refers to
1247 * the top of the stack. When you create the table with lua_newtable(),
1248 * the table gets pushed into the top of the stack. When you push the
1249 * index and then the cell value, the stack looks like:
1250 *
1251 * <- [stack bottom] -- table, index, value [top]
1252 *
1253 * So the -1 will refer to the cell value, thus -3 is used to refer to
1254 * the table itself. Note that lua_rawset() pops the two last elements
1255 * of the stack, so that after it has been called, the table is at the
1256 * top of the stack.
1257 */
1258 for (i = 1; i <= 5; i++)
1259 {
1260 lua_pushnumber(L, i); // Push the table index
1261 lua_pushnumber(L, i*2); // Push the cell value
1262 lua_rawset(L, -3); // Stores the pair in the table
1263 }
1264
1265 // By what name is the script going to reference our table?
1266 lua_setglobal(L, "foo");
1267
1268 // Ask Lua to run our little script
1269 result = lua_pcall(L, 0, LUA_MULTRET, 0);
1270 if (result)
1271 {
1272 E("Failed to run script: %s", lua_tostring(L, -1));
1273 goto finished;
1274 }
1275
1276 // Get the returned value at the top of the stack (index -1)
1277 sum = lua_tonumber(L, -1);
1278
1279 I("Script returned: %.0f", sum);
1280
1281 lua_pop(L, 1); // Take the returned value out of the stack
1282
1283 puts("");
1284 fflush(stdout);
1285
1286finished:
1287 if (database) mysql_close(database);
1288 mysql_library_end();
1289 lua_close(L);
1290 if (stats)
1291 {
1292 if (stats->stats) stats->stats->free(stats->stats);
1293 free(stats);
1294 }
1295 if (configs) configs->free(configs);
1296 return EXIT_SUCCESS;
1297}