cyphesis/tools/cydb.cpp
2018-05-20 19:22:20 +02:00

669 lines
20 KiB
C++

// Cyphesis Online RPG Server and AI Engine
// Copyright (C) 2001 Alistair Riddoch
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software Foundation,
// Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
/// \page cydb_index
///
/// \section Introduction
///
/// cydb is a commandline tool to administrate the server database. For
/// information on the commands available, please see the unix manual page.
/// The manual page is generated from docbook sources, so can
/// also be converted into other formats.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "common/log.h"
#include "common/globals.h"
#include "common/system.h"
#include "common/Storage.h"
#include <varconf/config.h>
#include "common/compose.hpp"
#ifdef HAVE_GETOPT_H
#endif // HAVE_GETOPT_H
#define USE_VARARGS
#define PREFER_STDARG
#include <readline/readline.h>
#include <cstring>
#include <common/DatabaseSQLite.h>
typedef int (*dbcmd_function)(Storage & ab, struct dbsys * system,
int argc, char ** argv);
/// \brief Entry in the global command table for cycmd
struct dbsys {
const char * sys_name;
const char * sys_description;
dbcmd_function sys_function;
struct dbsys * sys_subsys;
};
int dbs_help(Storage & ab, struct dbsys * system, int argc, char ** argv)
{
size_t max_length = 0;
for (struct dbsys * I = system->sys_subsys; I->sys_name != nullptr; ++I) {
max_length = std::max(max_length, strlen(I->sys_name));
}
max_length += 2;
std::cout << "Cyphesis database tool." << std::endl
<< system->sys_name << " commands:"
<< std::endl << std::endl;
for (struct dbsys * I = system->sys_subsys; I->sys_name != nullptr; ++I) {
std::cout << " " << I->sys_name
<< std::string(max_length - strlen(I->sys_name), ' ')
<< I->sys_description << std::endl;
}
std::cout << std::endl << std::flush;
return 0;
}
static int world_purge(Storage & ab, struct dbsys * system,
int argc, char ** argv)
{
std::string cmd = "DELETE FROM entities WHERE loc IS NOT null";
if (Database::instance().runCommandQuery(cmd) != 0) {
std::cout << "Entity purge fail" << std::endl << std::flush;
return 1;
}
cmd = "DELETE FROM properties";
if (Database::instance().runCommandQuery(cmd) != 0) {
std::cout << "Property purge fail" << std::endl << std::flush;
return 1;
}
return 0;
}
static int users_purge(Storage & ab, struct dbsys * system,
int argc, char ** argv)
{
std::string cmd = "DELETE FROM accounts WHERE username != 'admin'";
if (Database::instance().runCommandQuery(cmd) != 0) {
std::cout << "User purge fail" << std::endl << std::flush;
return 1;
}
return 0;
}
static int users_list(Storage & ab, struct dbsys * system,
int argc, char ** argv)
{
std::string cmd = "SELECT username, type FROM accounts";
DatabaseResult res = Database::instance().runSimpleSelectQuery(cmd);
DatabaseResult::const_iterator I = res.begin();
DatabaseResult::const_iterator Iend = res.end();
for (; I != Iend; ++I) {
std::string name = I.column("username");
std::string type = I.column("type");
std::cout << (type == "admin" ? "*" : " ") << name
<< std::endl << std::flush;
}
return 0;
}
static int users_del(Storage & ab, struct dbsys * system,
int argc, char ** argv)
{
if (argc != 2) {
std::cout << "usage: " << system->sys_name
<< " <username>" << std::endl << std::flush;
return 1;
}
std::string id = argv[1];
std::string cmd = String::compose("SELECT username FROM accounts "
"WHERE username='%1'", id);
DatabaseResult res = Database::instance().runSimpleSelectQuery(cmd);
if (res.size() == 0) {
std::cout << "User account " << id << " not found"
<< std::endl << std::flush;
return 1;
}
if (res.size() != 1) {
std::cout << "ERROR: Multiple accounts match " << id
<< std::endl << std::flush;
return 1;
}
cmd = String::compose("DELETE FROM accounts WHERE "
"username = '%1'", id);
if (Database::instance().runCommandQuery(cmd) != 0) {
std::cout << "User delete fail" << std::endl << std::flush;
return 1;
}
return 0;
}
static int users_mod(Storage & ab, struct dbsys * system,
int argc, char ** argv)
{
int opt;
char * type = 0, * password = 0;
while ((opt = getopt(argc, argv, "t:p:")) != -1) {
switch (opt) {
case 't':
type = optarg;
break;
case 'p':
password = optarg;
break;
default:
std::cout << "usage: " << system->sys_name
<< " [-t TYPE|-p PASSWORD] USERNAME"
<< std::endl << std::flush;
return 1;
}
}
if (argc - optind != 1 || (type == 0 && password == 0)) {
std::cout << "usage: " << system->sys_name
<< " [-t TYPE] [-p PASSWORD] USERNAME"
<< std::endl << std::flush;
return 1;
}
std::string id = argv[optind];
std::string cmd = String::compose("SELECT username, type FROM accounts "
"WHERE username='%1'", id);
DatabaseResult res = Database::instance().runSimpleSelectQuery(cmd);
if (res.size() == 0) {
std::cout << "User account " << id << " not found"
<< std::endl << std::flush;
return 1;
}
if (res.size() != 1) {
std::cout << "ERROR: Multiple accounts match " << id
<< std::endl << std::flush;
return 1;
}
if (type != 0) {
std::string new_type = type;
if (new_type != "admin" && new_type != "player" &&
new_type != "disabled") {
std::cout << "ERROR: Account type must be one of "
"\"player\", \"admin\" or \"disabled\""
<< std::endl << std::flush;
}
// FIXME Verify the account exists.
cmd = String::compose("UPDATE accounts SET type = '%1' WHERE "
"username = '%2'", new_type, id);
if (Database::instance().runCommandQuery(cmd) != 0) {
std::cout << "User mod type fail" << std::endl << std::flush;
return 1;
}
std::cout << "Account type updated."
<< std::endl << std::flush;
}
if (password != 0) {
std::string new_pass;
encrypt_password(password, new_pass);
cmd = String::compose("UPDATE accounts SET password = '%1' WHERE "
"username = '%2'", new_pass, id);
if (Database::instance().runCommandQuery(cmd) != 0) {
std::cout << "User mod password fail" << std::endl << std::flush;
return 1;
}
std::cout << "Account password updated."
<< std::endl << std::flush;
}
return 0;
}
int dbs_generic(Storage & ab, struct dbsys * system,
int argc, char ** argv)
{
struct dbsys * subsyss = system->sys_subsys;
if (subsyss == 0) {
std::cout << "INTERNAL ERROR" << std::endl << std::flush;
return 1;
}
int nargc = argc - 1;
if (nargc < 1) {
dbs_help(ab, system, argc, argv);
return 1;
}
char ** nargv = &argv[1];
for (struct dbsys * I = subsyss; I->sys_name != nullptr; ++I) {
if (strcmp(nargv[0], I->sys_name) == 0) {
return I->sys_function(ab, I, nargc, nargv);
}
}
std::cout << "ERROR: No such command: "
<< system->sys_name << " " << nargv[0]
<< std::endl << std::endl << std::flush;
dbs_help(ab, system, argc, argv);
return 0;
}
struct dbsys world_cmds[] = {
{ "purge", "Purge world data", &world_purge, 0 },
{ "help", "Show world help", &dbs_help, &world_cmds[0] },
{ nullptr, "Guard", }
};
struct dbsys users_cmds[] = {
{ "purge", "Purge users data", &users_purge, 0 },
{ "list", "List user accounts", &users_list, 0 },
{ "del", "Delete a user account", &users_del, 0 },
{ "mod", "Modify a user account", &users_mod, 0 },
{ "help", "Show users help", &dbs_help, &users_cmds[0] },
{ nullptr, "Guard", }
};
struct dbsys systems[] = {
{ "user", "Modify the user account table", &dbs_generic, &users_cmds[0] },
{ "world", "Modify the world storage tables", &dbs_generic, &world_cmds[0] },
{ "help", "Show command help", &dbs_help, &systems[0] },
{ nullptr, "Guard", }
};
struct dbsys tool = {
"cydb", "Work with cyphesis databases", &dbs_help, &systems[0]
};
int completion_iterator = 0;
char * completion_generator(const char * text, int state)
{
if (state == 0) {
completion_iterator = 0;
}
for (int i = completion_iterator; systems[i].sys_name != 0; ++i) {
if (strncmp(text, systems[i].sys_name, strlen(text)) == 0) {
completion_iterator = i + 1;
return strdup(systems[i].sys_name);
}
}
return 0;
}
void gotCommand(char * cmd)
{
}
// FIXME
void setup_completion()
{
std::string prompt="foobar>";
rl_callback_handler_install(prompt.c_str(), &gotCommand);
rl_completion_entry_function = &completion_generator;
std::cout << std::endl << std::flush;
rl_callback_handler_remove();
}
#if 0
// FIXME
void updatePrompt()
{
std::string prompt;
std::string designation(">");
if (!username.empty()) {
prompt = username + "@";
if (accountType == "admin") {
designation = "#";
} else {
designation = "$";
}
} else {
prompt = "";
}
prompt += serverName;
prompt += " ";
prompt += systemType;
prompt += designation;
prompt += " ";
rl_set_prompt(prompt.c_str());
}
void exec(const std::string & cmd, const std::string & arg)
{
bool reply_expected = true;
reply_flag = false;
error_flag = false;
if (cmd == "stat") {
Get g;
encoder->streamObjectsMessage(g);
} else if (cmd == "install") {
size_t space = arg.find(' ');
if (space == std::string::npos || space >= (arg.size() - 1)) {
std::cout << "usage: install <type id> <parent id>"
<< std::endl << std::flush;
} else {
Create c;
c->setFrom(accountId);
Anonymous ent;
ent->setId(std::string(arg, 0, space));
ent->setObjtype("class");
ent->setParent(std::string(arg, space + 1));
c->setArgs1(ent);
encoder->streamObjectsMessage(c);
}
reply_expected = false;
} else if (cmd == "look") {
Look l;
l->setFrom(accountId);
encoder->streamObjectsMessage(l);
} else if (cmd == "logout") {
Logout l;
l->setFrom(accountId);
if (!arg.empty()) {
Anonymous lmap;
lmap->setId(arg);
l->setArgs1(lmap);
reply_expected = false;
}
encoder->streamObjectsMessage(l);
} else if (cmd == "say") {
Talk t;
Anonymous ent;
ent->setAttr("say", arg);
t->setArgs1(ent);
t->setFrom(accountId);
encoder->streamObjectsMessage(t);
} else if (cmd == "help" || cmd == "?") {
reply_expected = false;
help();
} else if (cmd == "query") {
Get g;
Anonymous cmap;
cmap->setObjtype("obj");
if (!arg.empty()) {
cmap->setId(arg);
}
g->setArgs1(cmap);
g->setFrom(accountId);
encoder->streamObjectsMessage(g);
} else if (cmd == "reload") {
if (arg.empty()) {
reply_expected = false;
std::cout << "reload: Argument required" << std::endl << std::flush;
} else {
Set s;
Anonymous tmap;
tmap->setObjtype("class");
tmap->setId(arg);
s->setArgs1(tmap);
s->setFrom(accountId);
encoder->streamObjectsMessage(s);
}
} else if (cmd == "get") {
Get g;
Anonymous cmap;
cmap->setObjtype("class");
if (!arg.empty()) {
cmap->setId(arg);
}
g->setArgs1(cmap);
g->setFrom(accountId);
encoder->streamObjectsMessage(g);
} else if (cmd == "monitor") {
AdminTask * task = new OperationMonitor;
if (runTask(task, arg) == 0) {
Monitor m;
m->setArgs1(Anonymous());
m->setFrom(accountId);
encoder->streamObjectsMessage(m);
}
reply_expected = false;
} else if (cmd == "unmonitor") {
OperationMonitor * om = dynamic_cast<OperationMonitor *>(currentTask);
if (om != 0) {
Monitor m;
m->setFrom(accountId);
encoder->streamObjectsMessage(m);
reply_expected = false;
struct timeval tv;
gettimeofday(&tv, nullptr);
int monitor_time = tv.tv_sec - om->startTime();
std::cout << om->count() << " operations monitored in "
<< monitor_time << " seconds = "
<< om->count() / monitor_time
<< " operations per second"
<< std::endl << std::flush;
endTask();
}
} else if (cmd == "connect") {
reply_expected = false;
Connect m;
Anonymous cmap;
cmap->setAttr("hostname", arg);
m->setArgs1(cmap);
m->setFrom(accountId);
encoder->streamObjectsMessage(m);
} else if (cmd == "add_agent") {
std::string agent_type("creator");
if (!arg.empty()) {
agent_type = arg;
}
Create c;
Anonymous cmap;
cmap->setParent(agent_type);
cmap->setName("cycmd agent");
cmap->setObjtype("obj");
c->setArgs1(cmap);
c->setFrom(accountId);
avatar_flag = true;
encoder->streamObjectsMessage(c);
} else if (cmd == "delete") {
if (agentId.empty()) {
std::cout << "Use add_agent to add an in-game agent first" << std::endl << std::flush;
reply_expected = false;
} else if (arg.empty()) {
std::cout << "Please specify the entity to delete" << std::endl << std::flush;
reply_expected = false;
} else {
Delete del;
Anonymous del_arg;
del_arg->setId(arg);
del->setArgs1(del_arg);
del->setFrom(agentId);
del->setTo(arg);
encoder->streamObjectsMessage(del);
reply_expected = false;
}
} else if (cmd == "find_by_name") {
if (agentId.empty()) {
std::cout << "Use add_agent to add an in-game agent first" << std::endl << std::flush;
reply_expected = false;
} else if (arg.empty()) {
std::cout << "Please specify the name to search for" << std::endl << std::flush;
reply_expected = false;
} else {
Look l;
Anonymous lmap;
lmap->setName(arg);
l->setArgs1(lmap);
l->setFrom(agentId);
encoder->streamObjectsMessage(l);
reply_expected = false;
}
} else if (cmd == "find_by_type") {
if (agentId.empty()) {
std::cout << "Use add_agent to add an in-game agent first" << std::endl << std::flush;
reply_expected = false;
} else if (arg.empty()) {
std::cout << "Please specify the type to search for" << std::endl << std::flush;
reply_expected = false;
} else {
Look l;
Anonymous lmap;
lmap->setParent(arg);
l->setArgs1(lmap);
l->setFrom(agentId);
encoder->streamObjectsMessage(l);
reply_expected = false;
}
} else if (cmd == "flush") {
if (agentId.empty()) {
std::cout << "Use add_agent to add an in-game agent first" << std::endl << std::flush;
reply_expected = false;
} else if (arg.empty()) {
std::cout << "Please specify the type to flush" << std::endl << std::flush;
reply_expected = false;
} else {
AdminTask * task = new Flusher(agentId);
runTask(task, arg);
reply_expected = false;
}
} else if (cmd == "creator_create") {
if (agentId.empty()) {
std::cout << "Use add_agent to add an in-game agent first" << std::endl << std::flush;
reply_expected = false;
} else if (arg.empty()) {
std::cout << "Use add_agent to add an in-game agent first" << std::endl << std::flush;
reply_expected = false;
} else {
Create c;
Anonymous thing;
thing->setParent(arg);
c->setArgs1(thing);
c->setFrom(agentId);
encoder->streamObjectsMessage(c);
reply_expected = false;
}
} else if (cmd == "cancel") {
if (endTask() != 0) {
std::cout << "No task currently running" << std::endl << std::flush;
}
} else {
reply_expected = false;
std::cout << cmd << ": Command not known" << std::endl << std::flush;
}
ios << std::flush;
if (!reply_expected) { return; }
// Wait for reply
time_t wait_start_time = time(nullptr);
while (!reply_flag) {
if (time(nullptr) - wait_start_time > 5) {
std::cout << cmd << ": No reply from server" << std::endl << std::flush;
return;
}
poll(false);
}
}
#endif
static int run_command(Storage & ab, int argc, char ** argv)
{
for (struct dbsys * I = &systems[0]; I->sys_name != nullptr; ++I) {
if (strcmp(argv[0], I->sys_name) == 0) {
return I->sys_function(ab, I, argc, argv);
}
}
std::cout << "ERROR: No such command: " << argv[0]
<< std::endl << std::endl << std::flush;
dbs_help(ab, &tool, argc, argv);
return 1;
}
int main(int argc, char ** argv)
{
int config_status = loadConfig(argc, argv, USAGE_DBASE);
if (config_status < 0) {
if (config_status == CONFIG_VERSION) {
reportVersion(argv[0]);
return 0;
} else if (config_status == CONFIG_HELP) {
showUsage(argv[0], USAGE_DBASE, "[ cmd ]");
return 0;
} else if (config_status != CONFIG_ERROR) {
log(ERROR, "Unknown error reading configuration.");
}
// Fatal error loading config file
return 1;
}
int optind = config_status;
bool interactive = true;
int ret = 0;
std::string cmd;
if (optind < argc) {
cmd = argv[optind];
interactive = false;
}
DatabaseSQLite database;
database.initConnection();
if (database.initConnection() != 0) {
log(ERROR, "Database setup failed.");
return 1;
}
Storage ab(database);
if (!interactive) {
ret = run_command(ab, argc - optind, &argv[optind]);
} else {
dbs_help(ab, &tool, argc, argv);
}
delete global_conf;
return ret;
}