\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 new file mode 100644 index 0000000..cd1071b --- /dev/null +++ b/src/toybox.c @@ -0,0 +1,520 @@ +#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 new file mode 100644 index 0000000..1f64310 --- /dev/null +++ b/src/toybox.h @@ -0,0 +1,278 @@ + +#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 new file mode 100644 index 0000000..98645cc --- /dev/null +++ b/web/debugStyle.css @@ -0,0 +1,36 @@ +.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 new file mode 100644 index 0000000..9a75aa6 --- /dev/null +++ b/web/help.html @@ -0,0 +1,11 @@ + +
+ + diff --git a/web/loginpage.html b/web/loginpage.html new file mode 100644 index 0000000..bceeee5 --- /dev/null +++ b/web/loginpage.html @@ -0,0 +1,108 @@ + +
+
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 new file mode 100644 index 0000000..46317e6 --- /dev/null +++ b/web/register.html @@ -0,0 +1,15 @@ + +
+
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 new file mode 100644 index 0000000..b9647c0 --- /dev/null +++ b/web/stats.html @@ -0,0 +1,25 @@ + +
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