\n", label); - for ( ; *envp != NULL; envp++) - { - FCGI_printf("%s\n", *envp); - } - FCGI_printf("
\n");
-}
-
-static void printEnv(char **envp)
-{
- for ( ; *envp != NULL; envp++)
- {
- D("%s", *envp);
- }
-}
-
-
-my_ulonglong dbCount(MYSQL *db, char *table, char *where)
-{
- my_ulonglong ret = 0;
-
- memset(toybuf, 0, sizeof(toybuf));
- if (NULL == where)
- snprintf(toybuf, sizeof(toybuf), "SELECT Count(*) FROM %s", table);
- else
- snprintf(toybuf, sizeof(toybuf), "SELECT Count(*) FROM %s WHERE %s", table, where);
-
- if (mysql_query(db, toybuf))
- E("Query failed: %s", mysql_error(db));
- else
- {
- MYSQL_RES *result = mysql_store_result(db);
-
- if (!result)
- E("Couldn't get results set from %s\n: %s", toybuf, mysql_error(db));
- else
- {
- MYSQL_ROW row = mysql_fetch_row(result);
- if (!row)
- E("Couldn't get row from %s\n: %s", toybuf, mysql_error(db));
- else
- ret = atoll(row[0]);
- mysql_free_result(result);
- }
- }
-
- return ret;
-}
-
-my_ulonglong dbCountJoin(MYSQL *db, char *table, char *select, char *join, char *where, char *order)
-{
- my_ulonglong ret = 0;
-
- if (NULL == select)
- select = "*";
-
- memset(toybuf, 0, sizeof(toybuf));
- if (NULL == where)
- snprintf(toybuf, sizeof(toybuf), "SELECT %s FROM %s", select, table, join);
- else
- snprintf(toybuf, sizeof(toybuf), "SELECT %s FROM %s %s WHERE %s", select, table, join, where);
-
- if (mysql_query(db, toybuf))
- E("Query failed: %s", mysql_error(db));
- else
- {
- MYSQL_RES *result = mysql_store_result(db);
-
- if (!result)
- E("Couldn't get results set from %s\n: %s", toybuf, mysql_error(db));
- else
- ret = mysql_num_rows(result);
- mysql_free_result(result);
- }
-
- return ret;
-}
-
-qlist_t *dbSelect(MYSQL *db, char *table, char *select, char *join, char *where, char *order)
-{
- qlist_t *ret = qlist(0);
- if (NULL == select)
- select = "*";
-
- memset(toybuf, 0, sizeof(toybuf));
- if (NULL == where)
- snprintf(toybuf, sizeof(toybuf), "SELECT %s FROM %s", select, table, join);
- else
- snprintf(toybuf, sizeof(toybuf), "SELECT %s FROM %s %s WHERE %s", select, table, join, where);
-
- if (mysql_query(db, toybuf))
- E("Query failed: %s", mysql_error(db));
- else
- {
- MYSQL_RES *result = mysql_store_result(db);
-
- if (!result)
- E("Couldn't get results set from %s\n: %s", toybuf, mysql_error(db));
- else
- {
- MYSQL_FIELD *fields;
-
- fields = mysql_fetch_fields(result);
-
- if (!fields)
- E("Faild fetching fields: %s", mysql_error(db));
- else
- {
- unsigned int i, num_fields = mysql_num_fields(result);
-
- MYSQL_ROW row;
-
- while ((row = mysql_fetch_row(result)))
- {
- qhashtbl_t *flds = qhashtbl(0, 0);
-
- for (i = 0; i < num_fields; i++)
- {
- flds->putstr(flds, fields[i].name, row[i]);
- }
- ret->addlast(ret, flds, sizeof(*flds));
- }
- }
- }
- mysql_free_result(result);
- }
-
- return ret;
-}
-
-void replaceStr(qhashtbl_t *ssi, char *key, char *value)
-{
-// char *tmp;
-
-// I think this is taken care of already.
-// if ((tmp = ssi->getstr(ssi, key, false)) != NULL)
-// free(tmp);
- ssi->putstr(ssi, key, value);
-}
-
-void replaceLong(qhashtbl_t *ssi, char *key, my_ulonglong value)
-{
- char *tmp = xmprintf("%lu", value);
-
- replaceStr(ssi, key, tmp);
- free(tmp);
-}
-
-
-float timeDiff(struct timeval *now, struct timeval *then)
-{
- if (0 == gettimeofday(now, NULL))
- {
- struct timeval thisTime = { 0, 0 };
- double result = 0.0;
-
- thisTime.tv_sec = now->tv_sec;
- thisTime.tv_usec = now->tv_usec;
- if (thisTime.tv_usec < then->tv_usec)
- {
- thisTime.tv_sec--;
- thisTime.tv_usec += 1000000;
- }
- thisTime.tv_usec -= then->tv_usec;
- thisTime.tv_sec -= then->tv_sec;
- result = ((double) thisTime.tv_usec) / ((double) 1000000.0);
- result += thisTime.tv_sec;
- return result;
- }
- else
- return 0.0;
-}
-
-typedef struct _gridStats gridStats;
-struct _gridStats
-{
- float next;
- struct timeval last;
- qhashtbl_t *stats;
-};
-
-gridStats *getStats(MYSQL *db, gridStats *stats)
-{
- if (NULL == stats)
- {
- stats = xmalloc(sizeof(gridStats));
- stats->next = 300;
- gettimeofday(&(stats->last), NULL);
- stats->stats = qhashtbl(0, 0);
- stats->stats->putstr(stats->stats, "version", "SledjChisl FCGI Dev 0.1");
- stats->stats->putstr(stats->stats, "grid", "my grid");
- stats->stats->putstr(stats->stats, "uri", "http://localhost:8002/");
-
- stats->stats->putstr(stats->stats, "gridOnline", "??");
- }
- else
- {
- static struct timeval thisTime;
- if (stats->next > timeDiff(&thisTime, &(stats->last)))
- return stats;
- }
-
- if (db)
- {
- I("Getting fresh grid stats.");
- char *tmp;
- my_ulonglong locIn = dbCount(db, "Presence", "RegionID != '00000000-0000-0000-0000-000000000000'"); // Locals online but not HGing, and HGers in world.
- my_ulonglong HGin = dbCount(db, "Presence", "UserID NOT IN (SELECT PrincipalID FROM UserAccounts)"); // HGers in world.
-
- // Collect stats about members.
- replaceLong(stats->stats, "hgers", HGin);
- replaceLong(stats->stats, "inworld", locIn - HGin);
- tmp = xmprintf("GridExternalName != '%s'", stats->stats->getstr(stats->stats, "uri", false));
- replaceLong(stats->stats, "outworld", dbCount(db, "hg_traveling_data", tmp));
- free(tmp);
- replaceLong(stats->stats, "members", dbCount(db, "UserAccounts", NULL));
-
- // Count local and HG visitors for the last 30 and 60 days.
- locIn = dbCountJoin(db, "GridUser", "GridUser.UserID", "INNER JOIN UserAccounts ON GridUser.UserID = UserAccounts.PrincipalID",
- "Login > UNIX_TIMESTAMP(FROM_UNIXTIME(UNIX_TIMESTAMP(now()) - 2419200))", "");
- HGin = dbCount(db, "GridUser", "Login > UNIX_TIMESTAMP(FROM_UNIXTIME(UNIX_TIMESTAMP(now()) - 2419200))");
- replaceLong(stats->stats, "locDay30", locIn);
- replaceLong(stats->stats, "day30", HGin);
- replaceLong(stats->stats, "HGday30", HGin - locIn);
-
- locIn = dbCountJoin(db, "GridUser", "GridUser.UserID", "INNER JOIN UserAccounts ON GridUser.UserID = UserAccounts.PrincipalID",
- "Login > UNIX_TIMESTAMP(FROM_UNIXTIME(UNIX_TIMESTAMP(now()) - 4838400))", "");
- HGin = dbCount(db, "GridUser", "Login > UNIX_TIMESTAMP(FROM_UNIXTIME(UNIX_TIMESTAMP(now()) - 4838400))");
- replaceLong(stats->stats, "locDay60", locIn);
- replaceLong(stats->stats, "day60", HGin);
- replaceLong(stats->stats, "HGday60", HGin - locIn);
-
- // Collect stats about sims.
- replaceLong(stats->stats, "sims", dbCount(db, "regions", NULL));
- replaceLong(stats->stats, "onlineSims", dbCount(db, "regions", "sizeX != 0"));
- replaceLong(stats->stats, "varRegions", dbCount(db, "regions", "sizeX > 256 or sizeY > 256"));
- replaceLong(stats->stats, "singleSims", dbCount(db, "regions", "sizeX = 256 and sizeY = 256"));
- replaceLong(stats->stats, "offlineSims", dbCount(db, "regions", "sizeX = 0"));
-
- // Calculate total size of all regions.
- qlist_t *regions = dbSelect(db, "regions", "sizeX,sizeY", "", "sizeX != 0", "");
- qlist_obj_t obj;
- my_ulonglong simSize = 0;
-
- memset((void *) &obj, 0, sizeof(obj)); // must be cleared before call
- regions->lock(regions);
- while (regions->getnext(regions, &obj, false) == true)
- {
- qhashtbl_t *row = (qhashtbl_t *) obj.data;
- my_ulonglong x = 0, y = 0;
-
- tmp = row->getstr(row, "sizeX", false);
- if (NULL == tmp)
- E("No regions.sizeX!");
- else
- x = atoll(tmp);
- tmp = row->getstr(row, "sizeY", false);
- if (NULL == tmp)
- E("No regions.sizeY!");
- else
- y = atoll(tmp);
- simSize += x * y;
- }
- regions->unlock(regions);
- tmp = xmprintf("%lu", simSize);
- stats->stats->putstr(stats->stats, "simsSize", tmp);
- free(tmp);
- gettimeofday(&(stats->last), NULL);
- }
- return stats;
-}
-
-
-enum fragmentType
-{
- FT_TEXT,
- FT_PARAM,
- FT_LUA
-};
-
-typedef struct _fragment fragment;
-struct _fragment
-{
- enum fragmentType type;
- int length;
- char *text;
-};
-
-typedef struct _HTMLfile HTMLfile;
-struct _HTMLfile
-{
- struct timespec last;
- qlist_t *fragments;
-};
-
-qhashtbl_t *HTMLfileCache = NULL;
-
-fragment *newFragment(enum fragmentType type, char *text, int len)
-{
- fragment *frg = xmalloc(sizeof(fragment));
- frg->type = type;
- frg->length = len;
- frg->text = xmalloc(len + 1);
- memcpy(frg->text, text, len);
- frg->text[len] = '\0';
- return frg;
-}
-
-HTMLfile *checkHTMLcache(char *file)
-{
- if (NULL == HTMLfileCache)
- HTMLfileCache = qhashtbl(0, 0);
-
- HTMLfile *ret = (HTMLfile *) HTMLfileCache->get(HTMLfileCache, file, NULL, false);
- int fd = open(file, O_RDONLY);
- size_t length = 0;
- fragment *frg0, *frg1;
-
- if (-1 == fd)
- E("Failed to open %s", file);
- else
- {
- struct stat sb;
- if (fstat(fd, &sb) == -1)
- E("Failed to stat %s", file);
- else
- {
- if ((NULL != ret) && (ret->last.tv_sec < sb.st_mtim.tv_sec))
- {
- HTMLfileCache->remove(HTMLfileCache, file);
- ret = NULL;
- }
-
- if (NULL == ret)
- {
- char *mm = MAP_FAILED;
-
- ret = xmalloc(sizeof(HTMLfile));
- ret->fragments = qlist(QLIST_THREADSAFE);
- length = sb.st_size;
- ret->last.tv_sec = sb.st_mtim.tv_sec;
- ret->last.tv_nsec = sb.st_mtim.tv_nsec;
-
- I("Loading web template %s", file);
- D("Web template %s is %d bytes long.", file, length);
-
- mm = mmap(NULL, length, PROT_READ, MAP_SHARED | MAP_POPULATE, fd, 0);
- if (mm == MAP_FAILED)
- E("Failed to mmap %s", file);
- else
- {
- char *h;
- int i, j = 0, k = 0, l, m;
-
- // Scan for server side includes style markings.
- for (i = 0; i < length; i++)
- {
- if (i + 5 < length)
- {
- if (('<' == mm[i]) && ('!' == mm[i + 1]) && ('-' == mm[i + 2]) && ('-' == mm[i + 3]) && ('#' == mm[i + 4])) // ''
- i += 4;
- }
- frg0 = newFragment(FT_TEXT, &mm[k], m - k);
- ret->fragments->addlast(ret->fragments, frg0, sizeof(*frg0));
- ret->fragments->addlast(ret->fragments, frg1, sizeof(*frg1));
- k = i;
- break;
- }
- }
- }
- }
- }
- }
- }
- }
- }
- frg0 = newFragment(FT_TEXT, &mm[k], length - k);
- ret->fragments->addlast(ret->fragments, frg0, sizeof(*frg0));
-
- if (-1 == munmap(mm, length))
- FCGI_fprintf(FCGI_stderr, "Failed to munmap %s\n", file);
-
- HTMLfileCache->put(HTMLfileCache, file, ret, sizeof(*ret));
- }
- }
- close(fd);
- }
- }
-
- return ret;
-}
-
-
-int main(int argc, char *argv[], char **env)
-{
- // don't segfault if our environment is crazy
- if (!*argv) return 127;
-
- char *cmd = *argv;
- char *tmp;
- qhashtbl_t *configs = qhashtbl(0, 0);
- lua_State *L = luaL_newstate();
- MYSQL *database = NULL, *dbconn = NULL;
- gridStats *stats = NULL;
- int status, result, i;
- void *vd;
-
- pwd = getcwd(0, 0);
-
- if (isatty(1))
- {
- I("Outputting to a terminal, not a web server.");
- // Check if we are already running inside the proper tmux server.
- char *eTMUX = getenv("TMUX");
- memset(toybuf, 0, sizeof(toybuf));
- snprintf(toybuf, sizeof(toybuf), "%s/%s", scRoot, Tsocket);
- if (((eTMUX) && (0 == strncmp(toybuf, eTMUX, strlen(toybuf)))))
- {
- I("Running inside the proper tmux server.");
- isTmux = 1;
- }
- else
- I("Not running inside the proper tmux server, starting it.");
- I("libfcgi version: %s", FCGI_VERSION);
- I("Lua version: %s", LUA_RELEASE);
- I("LuaJIT version: %s", LUAJIT_VERSION);
- I("MariaDB / MySQL client version: %s", mysql_get_client_info());
- }
- else
- isWeb = 1;
-
-
-/* From http://luajit.org/install.html -
-To change or extend the list of standard libraries to load, copy
-src/lib_init.c to your project and modify it accordingly. Make sure the
-jit library is loaded or the JIT compiler will not be activated.
-*/
- luaL_openlibs(L); // Load Lua libraries.
-
- // Load the config scripts.
- char *cPaths[] =
- {
- "/etc/sledjChisl.conf.lua",
-// "/etc/sledjChisl.d/*.lua",
- "~/.sledjChisl.conf.lua",
-// "~/.config/sledjChisl/*.lua",
- ".sledjChisl.conf.lua",
- NULL
- };
- struct stat st;
-
-
- for (i = 0; cPaths[i]; i++)
- {
- memset(toybuf, 0, sizeof(toybuf));
- if (('/' == cPaths[i][0]) || ('~' == cPaths[i][0]))
- snprintf(toybuf, sizeof(toybuf), "%s", cPaths[i]);
- else
- snprintf(toybuf, sizeof(toybuf), "%s/%s", pwd, cPaths[i]);
- if (0 != lstat(toybuf, &st))
- continue;
- if (!isWeb) I("Loading configuration file - %s", toybuf);
- status = luaL_loadfile(L, toybuf);
- if (status) // If something went wrong, error message is at the top of the stack.
- E("Couldn't load file: %s", lua_tostring(L, -1));
- else
- {
- result = lua_pcall(L, 0, LUA_MULTRET, 0);
- if (result)
- E("Failed to run script: %s", lua_tostring(L, -1));
- else
- {
- lua_getglobal(L, "config");
- lua_pushnil(L);
- while(lua_next(L, -2) != 0)
- {
- char *n = (char *) lua_tostring(L, -2);
-
- // Numbers can convert to strings, so check for numbers before checking for strings.
- // On the other hand, strings that can be converted to numbers also pass lua_isnumber(). sigh
- if (lua_isnumber(L, -1))
- {
- float v = lua_tonumber(L, -1);
- configs->put(configs, n, &v, sizeof(float));
- }
- else if (lua_isstring(L, -1))
- configs->putstr(configs, n, (char *) lua_tostring(L, -1));
- else
- {
- char *v = (char *) lua_tostring(L, -1);
- E("Unknown config variable type for %s = %s", n, v);
- }
- lua_pop(L, 1);
- }
- }
- }
- }
- if ((vd = configs->get (configs, "loadAverageInc", NULL, false)) != NULL) {loadAverageInc = *((float *) vd); D("Setting loadAverageInc = %f", loadAverageInc);}
- if ((vd = configs->get (configs, "simTimeOut", NULL, false)) != NULL) {simTimeOut = (int) *((float *) vd); D("Setting simTimeOut = %d", simTimeOut);}
- if ((tmp = configs->getstr(configs, "scRoot", false)) != NULL) {scRoot = tmp; D("Setting scRoot = %s", scRoot);}
- if ((tmp = configs->getstr(configs, "scUser", false)) != NULL) {scUser = tmp; D("Setting scUser = %s", scUser);}
- if ((tmp = configs->getstr(configs, "Tconsole", false)) != NULL) {Tconsole = tmp; D("Setting Tconsole = %s", Tconsole);}
- if ((tmp = configs->getstr(configs, "Tsocket", false)) != NULL) {Tsocket = tmp; D("Setting Tsocket = %s", Tsocket);}
- if ((tmp = configs->getstr(configs, "Ttab", false)) != NULL) {Ttab = tmp; D("Setting Ttab = %s", Ttab);}
- if ((tmp = configs->getstr(configs, "webRoot", false)) != NULL) {webRoot = tmp; D("Setting webRoot = %s", webRoot);}
-
-
- if (isTmux || isWeb)
- {
- char *d;
- memset(toybuf, 0, sizeof(toybuf));
-// TODO - the problem here is that web server isn't in the opensimmc group, so can't read this file ,with the database credentials.
-// Other web server things have access to database credentials, so not like this is a big problem.
-// For now I've just opened up the perms on it on my desktop.
- snprintf(toybuf, sizeof(toybuf), "%s/config/config.ini", scRoot);
-
- qlisttbl_t *qconfig = qconfig_parse_file(NULL, toybuf, '=');
- d = qstrunchar(qconfig->getstr(qconfig, "Const.ConnectionString", false), '"', '"');
-
- if (NULL == d)
- {
- E("No database credentials in %s!", toybuf);
- goto finished;
- }
- else
- {
- char *p0, *p1, *p2;
- if (NULL == (d = strdup(d)))
- {
- E("Out of memory!");
- goto finished;
- }
- // Data Source=MYSQL_HOST;Database=MYSQL_DB;User ID=MYSQL_USER;Password=MYSQL_PASSWORD;Old Guids=true;
- p0 = d;
- while (NULL != p0)
- {
- p1 = strchr(p0, '=');
- if (NULL == p1) break;
- *p1 = '\0';
- p2 = strchr(p1 + 1, ';');
- if (NULL == p2) break;
- *p2 = '\0';
- configs->putstr(configs, p0, p1 + 1); // NOTE - this allocs memory for it's key and it's data.
- p0 = p2 + 1;
- if ('\0' == *p0)
- p0 = NULL;
- };
- free(d);
- }
- if (mysql_library_init(argc, argv, NULL))
- {
- E("mysql_library_init() failed!");
- goto finished;
- }
-
- database = mysql_init(NULL);
- if (NULL == database)
- {
- E("mysql_init() failed - %s", mysql_error(database));
- goto finished;
- }
- else
- {
- // I have no idea what evil magic toybox is doing, but this ALWAYS CRASHES, no matter what I do.
- dbconn = mysql_real_connect(database,
- configs->getstr(configs, "Data Source", true),
- configs->getstr(configs, "User ID", true),
- configs->getstr(configs, "Password", true),
- configs->getstr(configs, "Database", true),
-// 3036, "/var/run/mysqld/mysqld.sock",
- 0, NULL,
- CLIENT_FOUND_ROWS | CLIENT_LOCAL_FILES | CLIENT_MULTI_STATEMENTS | CLIENT_MULTI_RESULTS);
- if (NULL == dbconn)
- {
- E("mysql_real_connect() failed - %s", mysql_error(database));
- goto finished;
- }
-
- // Need to kick this off.
- stats = getStats(database, stats);
- char *h = qstrunchar(qconfig->getstr(qconfig, "Const.HostName", false), '"', '"');
- char *p = qstrunchar(qconfig->getstr(qconfig, "Const.PublicPort", false), '"', '"');
- stats->stats->putstr(stats->stats, "grid", qstrunchar(qconfig->getstr(qconfig, "Const.GridName", false), '"', '"'));
- stats->stats->putstr(stats->stats, "HostName", h);
- stats->stats->putstr(stats->stats, "PublicPort", p);
- snprintf(toybuf, sizeof(toybuf), "http://%s:%s/", h, p);
-
- stats->stats->putstr(stats->stats, "uri", toybuf);
- }
- }
-
-
- if (isWeb)
- {
- char **initialEnv = env;
- int count = 0, entries, bytes;
-
- // FCGI_LISTENSOCK_FILENO is the socket to the web server.
- // STDOUT and STDERR go to the web servers error log, or at least it does in Apache 2.
-// fprintf(stdout, "STDOUT Started SledjChisl web server.\n");
-// fprintf(stderr, "STDERR Started SledjChisl web server.\n");
- I("Running SledjChisl inside a web server.");
-
- if (1 == argc)
- D("no args");
- else
- {
- for (i = 0; argv[i] != NULL; i++)
- D("ARG %s", argv[i]);
- }
- printEnv(env);
-
- struct stat statbuf;
- if (-1 == fstat(FCGI_LISTENSOCK_FILENO, &statbuf))
- tb_error_msg("fstat() failed");
- else
- {
- if (S_ISREG (statbuf.st_mode)) D("regular file");
- else if (S_ISDIR (statbuf.st_mode)) D("directory");
- else if (S_ISCHR (statbuf.st_mode)) D("character device");
- else if (S_ISBLK (statbuf.st_mode)) D("block device");
- else if (S_ISFIFO(statbuf.st_mode)) D("FIFO (named pipe)");
- else if (S_ISLNK (statbuf.st_mode)) D("symbolic link");
- else if (S_ISSOCK(statbuf.st_mode)) D("socket");
- else D("unknown file descriptor type");
- }
-
-
- while (FCGI_Accept() != -1)
- {
- if (NULL == getenv("PATH_INFO")) {msleep(1000); continue;}
-
- char *contentLength = getenv("CONTENT_LENGTH");
- int len;
-
- if (contentLength != NULL)
- len = strtol(contentLength, NULL, 10);
- else
- len = 0;
-
-
- if (FCGX_IsCGI())
- D("Started SledjChisl CGI web request ROLE = %s!", getenv("FCGI_ROLE"));
- else
- D("Started SledjChisl FCGI web request ROLE = %s.", getenv("FCGI_ROLE"));
-
- memset(toybuf, 0, sizeof(toybuf));
- snprintf(toybuf, sizeof(toybuf), "%s/html%s", webRoot, getenv("PATH_INFO"));
-
- HTMLfile *thisFile = checkHTMLcache(toybuf);
-
- getStats(database, stats);
-
-// This is dynamic content, it's always gonna be modified. I think.
-// char *since = getenv("If-Modified-Since");
-// if (NULL != since)
-// {
-// time_t snc = qtime_parse_gmtstr(since);
-// if (thisFile->last.tv_sec < snc)
-// {
-// D("Status: 304 Not Modified - %s", toybuf);
-// FCGI_printf("Status: 304 Not Modified\r\n");
-// goto fcgiDone;
-// }
-// }
-
- FCGI_printf("Status: 200 OK\r\n"
- "Content-type: text/html\r\n"
- "\r\n");
- if (len <= 0)
- D("No data from standard input.");
- else
- {
- int i, ch;
-
- FCGI_printf("Standard input:
\n
\n"); - for (i = 0; i < len; i++) - { - if ((ch = FCGI_getchar()) < 0) - { - E("Error: Not enough bytes received on standard input"); - break; - } - FCGI_putchar(ch); - } - FCGI_printf("\n
\n"); - } - - - qlist_obj_t obj; - memset((void *) &obj, 0, sizeof(obj)); // must be cleared before call - thisFile->fragments->lock(thisFile->fragments); - while (thisFile->fragments->getnext(thisFile->fragments, &obj, false) == true) - { - fragment *frg = (fragment *) obj.data; - if (NULL == frg->text) - { - E("NULL fragment!"); - continue; - } - switch (frg->type) - { - case FT_TEXT: - { - // Silly thing triggers a "mod_fcgid: ap_pass_brigade failed in handle_request_ipc function" in Apache 2.4, sometimes. - // Haven't seen one for some time. - // https://www.tablix.org/~avian/blog/archives/2016/05/on_ap_pass_brigade_failed/ - int l = FCGI_fwrite(frg->text, 1, frg->length, FCGI_stdout); - if (l != frg->length) - E("Failed to write %d != %d", l, frg->length); - break; - } - - case FT_PARAM: - { - if (strcmp("DEBUG", frg->text) == 0) - { - FCGI_printf("
\n", ++count, getpid()); - FCGI_printf("
libfcgi version: %s
\n", FCGI_VERSION); - FCGI_printf("Lua version: %s
\n", LUA_RELEASE); - FCGI_printf("LuaJIT version: %s
\n", LUAJIT_VERSION); - FCGI_printf("MySQL client version: %s
\n", mysql_get_client_info()); - PrintEnv("Initial environment", initialEnv); - PrintEnv("Request environment", environ); - } - else if (strcmp("URL", frg->text) == 0) - FCGI_printf("%s://%s%s", getenv("REQUEST_SCHEME"), getenv("HTTP_HOST"), getenv("SCRIPT_NAME")); - else - { - if ((tmp = stats->stats->getstr(stats->stats, frg->text, false)) != NULL) - FCGI_printf("%s", tmp); - else - FCGI_printf("%s", frg->text); - } - break; - } - - case FT_LUA: - break; - } - } - thisFile->fragments->unlock(thisFile->fragments); - -fcgiDone: - D("Finishing SledjChisl web request."); - FCGI_Finish(); - } - - FCGI_fprintf(FCGI_stdout, "Stopped SledjChisl web server.\n"); - D("Stopped SledjChisl web server."); - - goto finished; - } - - - if (!isTmux) - { // Let's see if the proper tmux server is even running. - memset(toybuf, 0, sizeof(toybuf)); - snprintf(toybuf, sizeof(toybuf), "%s %s/%s -q list-sessions 2>/dev/null | grep -q %s:", Tcmd, scRoot, Tsocket, Tconsole); - i = system(toybuf); - if (WIFEXITED(i)) - { - if (0 != WEXITSTATUS(i)) // No such sesion, create it. - { - memset(toybuf, 0, sizeof(toybuf)); - // 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. - // 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. - snprintf(toybuf, sizeof(toybuf), - "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'", - scUser, Tcmd, scRoot, Tsocket, Tconsole, Ttab, Tconsole, scRoot); - i = system(toybuf); - if (!WIFEXITED(i)) - E("tmux new-session command failed!"); - } - // Join the session. - memset(toybuf, 0, sizeof(toybuf)); - snprintf(toybuf, sizeof(toybuf), "%s %s/%s select-window -t '%s' \\; attach-session -t '%s'", Tcmd, scRoot, Tsocket, Tconsole, Tconsole); - i = system(toybuf); - if (!WIFEXITED(i)) - E("tmux attach-session command failed!"); - goto finished; - } - else - E("tmux list-sessions command failed!"); - } - - - - simList *sims = getSims(); - if (1) - { - struct sysinfo info; - float la; - - sysinfo(&info); - la = info.loads[0]/65536.0; - - if (!checkSimIsRunning("ROBUST")) - { - char *d = xmprintf("%s.{right}", Ttab); - char *c = xmprintf("cd %s/current/bin", scRoot); - - I("ROBUST is starting up."); - sendTmuxCmd(d, c); - free(c); - c = xmprintf("mono Robust.exe -inidirectory=%s/config/ROBUST", scRoot); - sendTmuxCmd(d, c); - free(c); - waitTmuxText(d, "INITIALIZATION COMPLETE FOR ROBUST"); - I("ROBUST is done starting up."); - la = waitLoadAverage(la, loadAverageInc / 3.0, simTimeOut / 3); - free(d); - } - - for (i = 0; i < sims->num; i++) - { - char *sim = sims->sims[i], *name = getSimName(sims->sims[i]); - - if (!checkSimIsRunning(sim)) - { - I("%s is starting up.", name); - memset(toybuf, 0, sizeof(toybuf)); - 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'", - Tcmd, scRoot, Tsocket, name, Tconsole, i + 1, scRoot, scRoot, sim); - int r = system(toybuf); - if (!WIFEXITED(r)) - E("tmux new-window command failed!"); - else - { - memset(toybuf, 0, sizeof(toybuf)); - snprintf(toybuf, sizeof(toybuf), "INITIALIZATION COMPLETE FOR %s", name); - waitTmuxText(name, toybuf); - I("%s is done starting up.", name); - la = waitLoadAverage(la, loadAverageInc, simTimeOut); - } - } - } - - } - else if (!strcmp(cmd, "create")) // "create name x,y size" - { - } - else if (!strcmp(cmd, "start")) // "start sim01" "start Welcome" "start" start everything - { - } - else if (!strcmp(cmd, "backup")) // "backup onefang rejected" "backup sim01" "backup Welcome" "backup" backup everything - { // If it's not a sim code, and not a sim name, it's an account inventory. - } - else if (!strcmp(cmd, "gitAR")) // "gitAR i name" - { - } - else if (!strcmp(cmd, "stop")) // "stop sim01" "stop Welcome" "stop" stop everything - { - } - - - double sum; - - // Load the file containing the script we are going to run - status = luaL_loadfile(L, "script.lua"); - if (status) - { - // If something went wrong, error message is at the top of the stack - E("Couldn't load file: %s", lua_tostring(L, -1)); - goto finished; - } - - /* - * Ok, now here we go: We pass data to the lua script on the stack. - * That is, we first have to prepare Lua's virtual stack the way we - * want the script to receive it, then ask Lua to run it. - */ - lua_newtable(L); /* We will pass a table */ - - /* - * To put values into the table, we first push the index, then the - * value, and then call lua_rawset() with the index of the table in the - * stack. Let's see why it's -3: In Lua, the value -1 always refers to - * the top of the stack. When you create the table with lua_newtable(), - * the table gets pushed into the top of the stack. When you push the - * index and then the cell value, the stack looks like: - * - * <- [stack bottom] -- table, index, value [top] - * - * So the -1 will refer to the cell value, thus -3 is used to refer to - * the table itself. Note that lua_rawset() pops the two last elements - * of the stack, so that after it has been called, the table is at the - * top of the stack. - */ - for (i = 1; i <= 5; i++) - { - lua_pushnumber(L, i); // Push the table index - lua_pushnumber(L, i*2); // Push the cell value - lua_rawset(L, -3); // Stores the pair in the table - } - - // By what name is the script going to reference our table? - lua_setglobal(L, "foo"); - - // Ask Lua to run our little script - result = lua_pcall(L, 0, LUA_MULTRET, 0); - if (result) - { - E("Failed to run script: %s", lua_tostring(L, -1)); - goto finished; - } - - // Get the returned value at the top of the stack (index -1) - sum = lua_tonumber(L, -1); - - I("Script returned: %.0f", sum); - - lua_pop(L, 1); // Take the returned value out of the stack - - puts(""); - fflush(stdout); - -finished: - if (database) mysql_close(database); - mysql_library_end(); - lua_close(L); - if (stats) - { - if (stats->stats) stats->stats->free(stats->stats); - free(stats); - } - if (configs) configs->free(configs); - return EXIT_SUCCESS; -} diff --git a/src/toybox.c b/src/toybox.c deleted file mode 100644 index cd1071b..0000000 --- a/src/toybox.c +++ /dev/null @@ -1,520 +0,0 @@ -#include "toybox.h" - - -char libbuf[4096]; - - -// From lib.c - -void tb_verror_msg(char *msg, int err, va_list va) -{ - char *s = ": %s"; - -// fprintf(stderr, "%s: ", toys.which->name); - if (msg) vfprintf(stderr, msg, va); - else s+=2; - if (err>0) fprintf(stderr, s, strerror(err)); -// if (err<0 && CFG_TOYBOX_HELP) -// fprintf(stderr, " (see \"%s --help\")", toys.which->name); - if (msg || err) putc('\n', stderr); -// if (!toys.exitval) toys.exitval++; -} - -// These functions don't collapse together because of the va_stuff. - -void tb_error_msg(char *msg, ...) -{ - va_list va; - - va_start(va, msg); - tb_verror_msg(msg, 0, va); - va_end(va); -} - -void tb_perror_msg(char *msg, ...) -{ - va_list va; - - va_start(va, msg); - tb_verror_msg(msg, errno, va); - va_end(va); -} - -// Die with an error message. -void tb_error_exit(char *msg, ...) -{ - va_list va; - - va_start(va, msg); - tb_verror_msg(msg, 0, va); - va_end(va); - - xexit(); -} - -// Die with an error message and strerror(errno) -void tb_perror_exit(char *msg, ...) -{ - // Die silently if our pipeline exited. - if (errno != EPIPE) { - va_list va; - - va_start(va, msg); - tb_verror_msg(msg, errno, va); - va_end(va); - } - - xexit(); -} - -// If you want to explicitly disable the printf() behavior (because you're -// printing user-supplied data, or because android's static checker produces -// false positives for 'char *s = x ? "blah1" : "blah2"; printf(s);' and it's -// -Werror there for policy reasons). -void tb_error_msg_raw(char *msg) -{ - tb_error_msg("%s", msg); -} - -void tb_perror_msg_raw(char *msg) -{ - tb_perror_msg("%s", msg); -} - -void tb_error_exit_raw(char *msg) -{ - tb_error_exit("%s", msg); -} - -void tb_perror_exit_raw(char *msg) -{ - tb_perror_exit("%s", msg); -} - -// Sleep for this many thousandths of a second -void msleep(long milliseconds) -{ - struct timespec ts; - - ts.tv_sec = milliseconds/1000; - ts.tv_nsec = (milliseconds%1000)*1000000; - nanosleep(&ts, &ts); -} - -// Slow, but small. -char *get_line(int fd) -{ - char c, *buf = NULL; - long len = 0; - - for (;;) { - if (1>read(fd, &c, 1)) break; - if (!(len & 63)) buf=xrealloc(buf, len+65); - if ((buf[len++]=c) == '\n') break; - } - if (buf) { - buf[len]=0; - if (buf[--len]=='\n') buf[len]=0; - } - - return buf; -} - -// The qsort man page says you can use alphasort, the posix committee -// disagreed, and doubled down: http://austingroupbugs.net/view.php?id=142 -// So just do our own. (The const is entirely to humor the stupid compiler.) -int qstrcmp(const void *a, const void *b) -{ - return strcmp(*(char **)a, *(char **)b); -} - - - -// From xwrap.c - -// We replaced exit(), _exit(), and atexit() with xexit(), _xexit(), and -// sigatexit(). This gives _xexit() the option to siglongjmp(toys.rebound, 1) -// instead of exiting, lets xexit() report stdout flush failures to stderr -// and change the exit code to indicate error, lets our toys.exit function -// change happen for signal exit paths and lets us remove the functions -// after we've called them. - -void _xexit(void) -{ -// if (toys.rebound) siglongjmp(*toys.rebound, 1); - -// _exit(toys.exitval); - _exit(0); -} - -void xexit(void) -{ - // Call toys.xexit functions in reverse order added. -// while (toys.xexit) { -// struct arg_list *al = tb_llist_pop(&toys.xexit); - - // typecast xexit->arg to a function pointer, then call it using invalid - // signal 0 to let signal handlers tell actual signal from regular exit. -// ((void (*)(int))(al->arg))(0); - -// free(al); -// } - xflush(1); - _xexit(); -} - -// Die unless we can allocate memory. -void *xmalloc(size_t size) -{ - void *ret = malloc(size); - if (!ret) tb_error_exit("xmalloc(%ld)", (long)size); - - return ret; -} - -// Die unless we can allocate prezeroed memory. -void *xzalloc(size_t size) -{ - void *ret = xmalloc(size); - memset(ret, 0, size); - return ret; -} - -// Die unless we can change the size of an existing allocation, possibly -// moving it. (Notice different arguments from libc function.) -void *xrealloc(void *ptr, size_t size) -{ - ptr = realloc(ptr, size); - if (!ptr) tb_error_exit("xrealloc"); - - return ptr; -} - -// Die unless we can allocate a copy of this many bytes of string. -char *xstrndup(char *s, size_t n) -{ - char *ret = strndup(s, n); - - if (!ret) tb_error_exit("xstrndup"); - - return ret; -} - -// Die unless we can allocate a copy of this string. -char *xstrdup(char *s) -{ - return xstrndup(s, strlen(s)); -} - -void *xmemdup(void *s, long len) -{ - void *ret = xmalloc(len); - memcpy(ret, s, len); - - return ret; -} - -// Die unless we can allocate enough space to sprintf() into. -char *xmprintf(char *format, ...) -{ - va_list va, va2; - int len; - char *ret; - - va_start(va, format); - va_copy(va2, va); - - // How long is it? - len = vsnprintf(0, 0, format, va); - len++; - va_end(va); - - // Allocate and do the sprintf() - ret = xmalloc(len); - vsnprintf(ret, len, format, va2); - va_end(va2); - - return ret; -} - -// if !flush just check for error on stdout without flushing -void xflush(int flush) -{ - if ((flush && fflush(0)) || ferror(stdout)) -;// if (!toys.exitval) tb_perror_msg("write"); -} - -// Die unless we can open/create a file, returning file descriptor. -// The meaning of O_CLOEXEC is reversed (it defaults on, pass it to disable) -// and WARN_ONLY tells us not to exit. -int xcreate_stdio(char *path, int flags, int mode) -{ - int fd = open(path, (flags^O_CLOEXEC)&~WARN_ONLY, mode); - - if (fd == -1) ((mode&WARN_ONLY) ? tb_perror_msg_raw : tb_perror_exit_raw)(path); - return fd; -} - -// Die unless we can open a file, returning file descriptor. -int xopen_stdio(char *path, int flags) -{ - return xcreate_stdio(path, flags, 0); -} - -void xclose(int fd) -{ - if (close(fd)) tb_perror_exit("xclose"); -} - -int xdup(int fd) -{ - if (fd != -1) { - fd = dup(fd); - if (fd == -1) tb_perror_exit("xdup"); - } - return fd; -} - -// Move file descriptor above stdin/stdout/stderr, using /dev/null to consume -// old one. (We should never be called with stdin/stdout/stderr closed, but...) -int notstdio(int fd) -{ - if (fd<0) return fd; - - while (fd<3) { - int fd2 = xdup(fd); - - close(fd); - xopen_stdio("/dev/null", O_RDWR); - fd = fd2; - } - - return fd; -} - -// Open a file descriptor NOT in stdin/stdout/stderr -int xopen(char *path, int flags) -{ - return notstdio(xopen_stdio(path, flags)); -} - -// Open read only, treating "-" as a synonym for stdin, defaulting to warn only -int openro(char *path, int flags) -{ - if (!strcmp(path, "-")) return 0; - - return xopen(path, flags^WARN_ONLY); -} - -// Open read only, treating "-" as a synonym for stdin. -int xopenro(char *path) -{ - return openro(path, O_RDONLY|WARN_ONLY); -} - -// Compile a regular expression into a regex_t -void xregcomp(regex_t *preg, char *regex, int cflags) -{ - int rc; - - // BSD regex implementations don't support the empty regex (which isn't - // allowed in the POSIX grammar), but glibc does. Fake it for BSD. - if (!*regex) { - regex = "()"; - cflags |= REG_EXTENDED; - } - - if ((rc = regcomp(preg, regex, cflags))) { - regerror(rc, preg, libbuf, sizeof(libbuf)); - tb_error_exit("bad regex: %s", libbuf); - } -} - - - -// From dirtree.c - -int tb_isdotdot(char *name) -{ - if (name[0]=='.' && (!name[1] || (name[1]=='.' && !name[2]))) return 1; - - return 0; -} - -// Create a tb_dirtree node from a path, with stat and symlink info. -// (This doesn't open directory filehandles yet so as not to exhaust the -// filehandle space on large trees, tb_dirtree_handle_callback() does that.) - -struct tb_dirtree *tb_dirtree_add_node(struct tb_dirtree *parent, char *name, int flags) -{ - struct tb_dirtree *dt = 0; - struct stat st; - int len = 0, linklen = 0, statless = 0; - - if (name) { - // open code fd = because haven't got node to call tb_dirtree_parentfd() on yet - int fd = parent ? parent->dirfd : AT_FDCWD, - sym = AT_SYMLINK_NOFOLLOW*!(flags&TB_DIRTREE_SYMFOLLOW); - - // stat dangling symlinks - if (fstatat(fd, name, &st, sym)) { - // If we got ENOENT without NOFOLLOW, try again with NOFOLLOW. - if (errno!=ENOENT || sym || fstatat(fd, name, &st, AT_SYMLINK_NOFOLLOW)) { - if (flags&TB_DIRTREE_STATLESS) statless++; - else goto error; - } - } - if (!statless && S_ISLNK(st.st_mode)) { - if (0>(linklen = readlinkat(fd, name, libbuf, 4095))) goto error; - libbuf[linklen++]=0; - } - len = strlen(name); - } - - // Allocate/populate return structure - dt = xmalloc((len = sizeof(struct tb_dirtree)+len+1)+linklen); - memset(dt, 0, statless ? offsetof(struct tb_dirtree, again) - : offsetof(struct tb_dirtree, st)); - dt->parent = parent; - dt->again = statless ? 2 : 0; - if (!statless) memcpy(&dt->st, &st, sizeof(struct stat)); - strcpy(dt->name, name ? name : ""); - if (linklen) dt->symlink = memcpy(len+(char *)dt, libbuf, linklen); - - return dt; - -error: - if (!(flags&TB_DIRTREE_SHUTUP) && !tb_isdotdot(name)) { - char *path = parent ? tb_dirtree_path(parent, 0) : ""; - - tb_perror_msg("%s%s%s", path, parent ? "/" : "", name); - if (parent) free(path); - } - if (parent) parent->symlink = (char *)1; - free(dt); - return 0; -} - -// Return path to this node, assembled recursively. - -// Initial call can pass in NULL to plen, or point to an int initialized to 0 -// to return the length of the path, or a value greater than 0 to allocate -// extra space if you want to append your own text to the string. - -char *tb_dirtree_path(struct tb_dirtree *node, int *plen) -{ - char *path; - int len; - - if (!node) { - path = xmalloc(*plen); - *plen = 0; - return path; - } - - len = (plen ? *plen : 0)+strlen(node->name)+1; - path = tb_dirtree_path(node->parent, &len); - if (len && path[len-1] != '/') path[len++]='/'; - len = stpcpy(path+len, node->name) - path; - if (plen) *plen = len; - - return path; -} - -int tb_dirtree_parentfd(struct tb_dirtree *node) -{ - return node->parent ? node->parent->dirfd : AT_FDCWD; -} - -// Handle callback for a node in the tree. Returns saved node(s) if -// callback returns TB_DIRTREE_SAVE, otherwise frees consumed nodes and -// returns NULL. If !callback return top node unchanged. -// If !new return TB_DIRTREE_ABORTVAL - -struct tb_dirtree *tb_dirtree_handle_callback(struct tb_dirtree *new, - int (*callback)(struct tb_dirtree *node)) -{ - int flags; - - if (!new) return TB_DIRTREE_ABORTVAL; - if (!callback) return new; - flags = callback(new); - - if (S_ISDIR(new->st.st_mode) && (flags & (TB_DIRTREE_RECURSE|TB_DIRTREE_COMEAGAIN))) - flags = tb_dirtree_recurse(new, callback, - openat(tb_dirtree_parentfd(new), new->name, O_CLOEXEC), flags); - - // If this had children, it was callback's job to free them already. - if (!(flags & TB_DIRTREE_SAVE)) { - free(new); - new = 0; - } - - return (flags & TB_DIRTREE_ABORT)==TB_DIRTREE_ABORT ? TB_DIRTREE_ABORTVAL : new; -} - -// Recursively read/process children of directory node, filtering through -// callback(). Uses and closes supplied ->dirfd. - -int tb_dirtree_recurse(struct tb_dirtree *node, - int (*callback)(struct tb_dirtree *node), int dirfd, int flags) -{ - struct tb_dirtree *new, **ddt = &(node->child); - struct dirent *entry; - DIR *dir; - - node->dirfd = dirfd; - if (node->dirfd == -1 || !(dir = fdopendir(node->dirfd))) { - if (!(flags & TB_DIRTREE_SHUTUP)) { - char *path = tb_dirtree_path(node, 0); - tb_perror_msg_raw(path); - free(path); - } - close(node->dirfd); - - return flags; - } - - // according to the fddir() man page, the filehandle in the DIR * can still - // be externally used by things that don't lseek() it. - - // The extra parentheses are to shut the stupid compiler up. - while ((entry = readdir(dir))) { - if ((flags&TB_DIRTREE_PROC) && !isdigit(*entry->d_name)) continue; - if (!(new = tb_dirtree_add_node(node, entry->d_name, flags))) continue; - if (!new->st.st_blksize && !new->st.st_mode) - new->st.st_mode = entry->d_type<<12; - new = tb_dirtree_handle_callback(new, callback); - if (new == TB_DIRTREE_ABORTVAL) break; - if (new) { - *ddt = new; - ddt = &((*ddt)->next); - } - } - - if (flags & TB_DIRTREE_COMEAGAIN) { - node->again |= 1; - flags = callback(node); - } - - // This closes filehandle as well, so note it - closedir(dir); - node->dirfd = -1; - - return flags; -} - -void tb_dirtree_free(struct tb_dirtree *new) -{ - struct tb_dirtree *dt; - - if ((dt = new)) { - new = 0; - while (dt->child) { - new = dt->child->next; - free(dt->child); - dt->child = new; - } - free(dt); - } -} diff --git a/src/toybox.h b/src/toybox.h deleted file mode 100644 index 1f64310..0000000 --- a/src/toybox.h +++ /dev/null @@ -1,278 +0,0 @@ - -#ifdef __GNUC__ -#define printf_format __attribute__((format(printf, 1, 2))) -#else -#define printf_format -#endif - -// General posix-2008 headers -#include- - diff --git a/web/debugStyle.css b/web/debugStyle.css deleted file mode 100644 index 98645cc..0000000 --- a/web/debugStyle.css +++ /dev/null @@ -1,36 +0,0 @@ -.hoverWrapper0:hover #hoverShow0 -{ - display: block; - border-style: solid; - border-color: fuchsia; -} -.hoverWrapper0 #hoverShow0 -{ - display: none; - background-color: #222222; - text-align: left; - position: absolute; - width: 100%; - border-style: solid; - border-color: fuchsia; -} -.hoverWrapper1:hover #hoverShow1 -{ - display: block; - border-style: solid; - border-color: fuchsia; -} -.hoverWrapper1 #hoverShow1 -{ - display: none; - background-color: #222222; - text-align: left; - position: absolute; - width: 100%; - border-style: solid; - border-color: fuchsia; -} -.hoverItem -{ - border: 1px solid fuchsia; -} diff --git a/web/help.html b/web/help.html deleted file mode 100644 index 9a75aa6..0000000 --- a/web/help.html +++ /dev/null @@ -1,11 +0,0 @@ - -
- - diff --git a/web/loginpage.html b/web/loginpage.html deleted file mode 100644 index bceeee5..0000000 --- a/web/loginpage.html +++ /dev/null @@ -1,108 +0,0 @@ - -
-
DEBUG
--
-
-
Perhaps describe here.
--
There are members of this grid.
-There are locals and hypergrid visitors in world.
-There are locals out on the hypergrid.
-There have been locals and visitors on this grid in the last month.
-There are regions, though some might not be online right now.
-Maybe add some news or events here, or something.
-CSS by Taylor Temper, photo by onefang rejected.
-is running , - part of the SledjHamr project.
-- - diff --git a/web/register.html b/web/register.html deleted file mode 100644 index 46317e6..0000000 --- a/web/register.html +++ /dev/null @@ -1,15 +0,0 @@ - -
-
If you want to register an account on this grid, ask the person that runs it to do that for you.
--
If you want to register an account on this grid, click here (when it's written).
-- - diff --git a/web/stats.html b/web/stats.html deleted file mode 100644 index b9647c0..0000000 --- a/web/stats.html +++ /dev/null @@ -1,25 +0,0 @@ - -
Login URI -
-Login page - /loginpage.html">/loginpage.html
-There are members of this grid.
-There are locals and hypergrid visitors in world right now.
-There are locals out on the hypergrid right now.
-There have been locals and hypergrid visitors on this grid in the last 30 days ( total).
-There have been locals and hypergrid visitors on this grid in the last 60 days ( total).
-There are regions, though might not be online right now.
-There may be regions online, with a total area of roughly square metres.
-There are varregions that might be online now.
-There are normal regions that might be online now.
-is running , - part of the SledjHamr project.
-These statistics will update every two minutes.
- - -- cgit v1.1