Initial commit

This commit is contained in:
2022-10-08 17:16:13 -04:00
commit 385638c5e1
1925 changed files with 872504 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
set(database_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/database.cpp
${CMAKE_CURRENT_SOURCE_DIR}/database-dummy.cpp
${CMAKE_CURRENT_SOURCE_DIR}/database-files.cpp
${CMAKE_CURRENT_SOURCE_DIR}/database-leveldb.cpp
${CMAKE_CURRENT_SOURCE_DIR}/database-postgresql.cpp
${CMAKE_CURRENT_SOURCE_DIR}/database-redis.cpp
${CMAKE_CURRENT_SOURCE_DIR}/database-sqlite3.cpp
PARENT_SCOPE
)

View File

@@ -0,0 +1,120 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser 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.
*/
/*
Dummy database class
*/
#include "database-dummy.h"
#include "remoteplayer.h"
bool Database_Dummy::saveBlock(const v3s16 &pos, const std::string &data)
{
m_database[getBlockAsInteger(pos)] = data;
return true;
}
void Database_Dummy::loadBlock(const v3s16 &pos, std::string *block)
{
s64 i = getBlockAsInteger(pos);
auto it = m_database.find(i);
if (it == m_database.end()) {
*block = "";
return;
}
*block = it->second;
}
bool Database_Dummy::deleteBlock(const v3s16 &pos)
{
m_database.erase(getBlockAsInteger(pos));
return true;
}
void Database_Dummy::listAllLoadableBlocks(std::vector<v3s16> &dst)
{
dst.reserve(m_database.size());
for (std::map<s64, std::string>::const_iterator x = m_database.begin();
x != m_database.end(); ++x) {
dst.push_back(getIntegerAsBlock(x->first));
}
}
void Database_Dummy::savePlayer(RemotePlayer *player)
{
m_player_database.insert(player->getName());
}
bool Database_Dummy::loadPlayer(RemotePlayer *player, PlayerSAO *sao)
{
return m_player_database.find(player->getName()) != m_player_database.end();
}
bool Database_Dummy::removePlayer(const std::string &name)
{
m_player_database.erase(name);
return true;
}
void Database_Dummy::listPlayers(std::vector<std::string> &res)
{
for (const auto &player : m_player_database) {
res.emplace_back(player);
}
}
bool Database_Dummy::getModEntries(const std::string &modname, StringMap *storage)
{
const auto mod_pair = m_mod_meta_database.find(modname);
if (mod_pair != m_mod_meta_database.cend()) {
for (const auto &pair : mod_pair->second) {
(*storage)[pair.first] = pair.second;
}
}
return true;
}
bool Database_Dummy::setModEntry(const std::string &modname,
const std::string &key, const std::string &value)
{
auto mod_pair = m_mod_meta_database.find(modname);
if (mod_pair == m_mod_meta_database.end()) {
m_mod_meta_database[modname] = StringMap({{key, value}});
} else {
mod_pair->second[key] = value;
}
return true;
}
bool Database_Dummy::removeModEntry(const std::string &modname, const std::string &key)
{
auto mod_pair = m_mod_meta_database.find(modname);
if (mod_pair != m_mod_meta_database.end())
return mod_pair->second.erase(key) > 0;
return false;
}
void Database_Dummy::listMods(std::vector<std::string> *res)
{
for (const auto &pair : m_mod_meta_database) {
res->push_back(pair.first);
}
}

View File

@@ -0,0 +1,53 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser 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.
*/
#pragma once
#include <map>
#include <string>
#include "database.h"
#include "irrlichttypes.h"
class Database_Dummy : public MapDatabase, public PlayerDatabase, public ModMetadataDatabase
{
public:
bool saveBlock(const v3s16 &pos, const std::string &data);
void loadBlock(const v3s16 &pos, std::string *block);
bool deleteBlock(const v3s16 &pos);
void listAllLoadableBlocks(std::vector<v3s16> &dst);
void savePlayer(RemotePlayer *player);
bool loadPlayer(RemotePlayer *player, PlayerSAO *sao);
bool removePlayer(const std::string &name);
void listPlayers(std::vector<std::string> &res);
bool getModEntries(const std::string &modname, StringMap *storage);
bool setModEntry(const std::string &modname,
const std::string &key, const std::string &value);
bool removeModEntry(const std::string &modname, const std::string &key);
void listMods(std::vector<std::string> *res);
void beginSave() {}
void endSave() {}
private:
std::map<s64, std::string> m_database;
std::set<std::string> m_player_database;
std::unordered_map<std::string, StringMap> m_mod_meta_database;
};

View File

@@ -0,0 +1,501 @@
/*
Minetest
Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser 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.
*/
#include <cassert>
#include "convert_json.h"
#include "database-files.h"
#include "remoteplayer.h"
#include "settings.h"
#include "porting.h"
#include "filesys.h"
#include "server/player_sao.h"
#include "util/string.h"
// !!! WARNING !!!
// This backend is intended to be used on Minetest 0.4.16 only for the transition backend
// for player files
PlayerDatabaseFiles::PlayerDatabaseFiles(const std::string &savedir) : m_savedir(savedir)
{
fs::CreateDir(m_savedir);
}
void PlayerDatabaseFiles::deSerialize(RemotePlayer *p, std::istream &is,
const std::string &playername, PlayerSAO *sao)
{
Settings args("PlayerArgsEnd");
if (!args.parseConfigLines(is)) {
throw SerializationError("PlayerArgsEnd of player " + playername + " not found!");
}
p->m_dirty = true;
//args.getS32("version"); // Version field value not used
const std::string &name = args.get("name");
strlcpy(p->m_name, name.c_str(), PLAYERNAME_SIZE);
if (sao) {
try {
sao->setHPRaw(args.getU16("hp"));
} catch(SettingNotFoundException &e) {
sao->setHPRaw(PLAYER_MAX_HP_DEFAULT);
}
try {
sao->setBasePosition(args.getV3F("position"));
} catch (SettingNotFoundException &e) {}
try {
sao->setLookPitch(args.getFloat("pitch"));
} catch (SettingNotFoundException &e) {}
try {
sao->setPlayerYaw(args.getFloat("yaw"));
} catch (SettingNotFoundException &e) {}
try {
sao->setBreath(args.getU16("breath"), false);
} catch (SettingNotFoundException &e) {}
try {
const std::string &extended_attributes = args.get("extended_attributes");
std::istringstream iss(extended_attributes);
Json::CharReaderBuilder builder;
builder.settings_["collectComments"] = false;
std::string errs;
Json::Value attr_root;
Json::parseFromStream(builder, iss, &attr_root, &errs);
const Json::Value::Members attr_list = attr_root.getMemberNames();
for (const auto &it : attr_list) {
Json::Value attr_value = attr_root[it];
sao->getMeta().setString(it, attr_value.asString());
}
sao->getMeta().setModified(false);
} catch (SettingNotFoundException &e) {}
}
try {
p->inventory.deSerialize(is);
} catch (SerializationError &e) {
errorstream << "Failed to deserialize player inventory. player_name="
<< name << " " << e.what() << std::endl;
}
if (!p->inventory.getList("craftpreview") && p->inventory.getList("craftresult")) {
// Convert players without craftpreview
p->inventory.addList("craftpreview", 1);
bool craftresult_is_preview = true;
if(args.exists("craftresult_is_preview"))
craftresult_is_preview = args.getBool("craftresult_is_preview");
if(craftresult_is_preview)
{
// Clear craftresult
p->inventory.getList("craftresult")->changeItem(0, ItemStack());
}
}
}
void PlayerDatabaseFiles::serialize(RemotePlayer *p, std::ostream &os)
{
// Utilize a Settings object for storing values
Settings args("PlayerArgsEnd");
args.setS32("version", 1);
args.set("name", p->m_name);
PlayerSAO *sao = p->getPlayerSAO();
// This should not happen
sanity_check(sao);
args.setU16("hp", sao->getHP());
args.setV3F("position", sao->getBasePosition());
args.setFloat("pitch", sao->getLookPitch());
args.setFloat("yaw", sao->getRotation().Y);
args.setU16("breath", sao->getBreath());
std::string extended_attrs;
{
// serializeExtraAttributes
Json::Value json_root;
const StringMap &attrs = sao->getMeta().getStrings();
for (const auto &attr : attrs) {
json_root[attr.first] = attr.second;
}
extended_attrs = fastWriteJson(json_root);
}
args.set("extended_attributes", extended_attrs);
args.writeLines(os);
p->inventory.serialize(os);
}
void PlayerDatabaseFiles::savePlayer(RemotePlayer *player)
{
fs::CreateDir(m_savedir);
std::string savedir = m_savedir + DIR_DELIM;
std::string path = savedir + player->getName();
bool path_found = false;
RemotePlayer testplayer("", NULL);
for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES && !path_found; i++) {
if (!fs::PathExists(path)) {
path_found = true;
continue;
}
// Open and deserialize file to check player name
std::ifstream is(path.c_str(), std::ios_base::binary);
if (!is.good()) {
errorstream << "Failed to open " << path << std::endl;
return;
}
deSerialize(&testplayer, is, path, NULL);
is.close();
if (strcmp(testplayer.getName(), player->getName()) == 0) {
path_found = true;
continue;
}
path = savedir + player->getName() + itos(i);
}
if (!path_found) {
errorstream << "Didn't find free file for player " << player->getName()
<< std::endl;
return;
}
// Open and serialize file
std::ostringstream ss(std::ios_base::binary);
serialize(player, ss);
if (!fs::safeWriteToFile(path, ss.str())) {
infostream << "Failed to write " << path << std::endl;
}
player->onSuccessfulSave();
}
bool PlayerDatabaseFiles::removePlayer(const std::string &name)
{
std::string players_path = m_savedir + DIR_DELIM;
std::string path = players_path + name;
RemotePlayer temp_player("", NULL);
for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) {
// Open file and deserialize
std::ifstream is(path.c_str(), std::ios_base::binary);
if (!is.good())
continue;
deSerialize(&temp_player, is, path, NULL);
is.close();
if (temp_player.getName() == name) {
fs::DeleteSingleFileOrEmptyDirectory(path);
return true;
}
path = players_path + name + itos(i);
}
return false;
}
bool PlayerDatabaseFiles::loadPlayer(RemotePlayer *player, PlayerSAO *sao)
{
std::string players_path = m_savedir + DIR_DELIM;
std::string path = players_path + player->getName();
const std::string player_to_load = player->getName();
for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) {
// Open file and deserialize
std::ifstream is(path.c_str(), std::ios_base::binary);
if (!is.good())
continue;
deSerialize(player, is, path, sao);
is.close();
if (player->getName() == player_to_load)
return true;
path = players_path + player_to_load + itos(i);
}
infostream << "Player file for player " << player_to_load << " not found" << std::endl;
return false;
}
void PlayerDatabaseFiles::listPlayers(std::vector<std::string> &res)
{
std::vector<fs::DirListNode> files = fs::GetDirListing(m_savedir);
// list files into players directory
for (std::vector<fs::DirListNode>::const_iterator it = files.begin(); it !=
files.end(); ++it) {
// Ignore directories
if (it->dir)
continue;
const std::string &filename = it->name;
std::string full_path = m_savedir + DIR_DELIM + filename;
std::ifstream is(full_path.c_str(), std::ios_base::binary);
if (!is.good())
continue;
RemotePlayer player(filename.c_str(), NULL);
// Null env & dummy peer_id
PlayerSAO playerSAO(NULL, &player, 15789, false);
deSerialize(&player, is, "", &playerSAO);
is.close();
res.emplace_back(player.getName());
}
}
AuthDatabaseFiles::AuthDatabaseFiles(const std::string &savedir) : m_savedir(savedir)
{
readAuthFile();
}
bool AuthDatabaseFiles::getAuth(const std::string &name, AuthEntry &res)
{
const auto res_i = m_auth_list.find(name);
if (res_i == m_auth_list.end()) {
return false;
}
res = res_i->second;
return true;
}
bool AuthDatabaseFiles::saveAuth(const AuthEntry &authEntry)
{
m_auth_list[authEntry.name] = authEntry;
// save entire file
return writeAuthFile();
}
bool AuthDatabaseFiles::createAuth(AuthEntry &authEntry)
{
m_auth_list[authEntry.name] = authEntry;
// save entire file
return writeAuthFile();
}
bool AuthDatabaseFiles::deleteAuth(const std::string &name)
{
if (!m_auth_list.erase(name)) {
// did not delete anything -> hadn't existed
return false;
}
return writeAuthFile();
}
void AuthDatabaseFiles::listNames(std::vector<std::string> &res)
{
res.clear();
res.reserve(m_auth_list.size());
for (const auto &res_pair : m_auth_list) {
res.push_back(res_pair.first);
}
}
void AuthDatabaseFiles::reload()
{
readAuthFile();
}
bool AuthDatabaseFiles::readAuthFile()
{
std::string path = m_savedir + DIR_DELIM + "auth.txt";
std::ifstream file(path, std::ios::binary);
if (!file.good()) {
return false;
}
m_auth_list.clear();
while (file.good()) {
std::string line;
std::getline(file, line);
std::vector<std::string> parts = str_split(line, ':');
if (parts.size() < 3) // also: empty line at end
continue;
const std::string &name = parts[0];
const std::string &password = parts[1];
std::vector<std::string> privileges = str_split(parts[2], ',');
s64 last_login = parts.size() > 3 ? atol(parts[3].c_str()) : 0;
m_auth_list[name] = {
1,
name,
password,
privileges,
last_login,
};
}
return true;
}
bool AuthDatabaseFiles::writeAuthFile()
{
std::string path = m_savedir + DIR_DELIM + "auth.txt";
std::ostringstream output(std::ios_base::binary);
for (const auto &auth_i : m_auth_list) {
const AuthEntry &authEntry = auth_i.second;
output << authEntry.name << ":" << authEntry.password << ":";
output << str_join(authEntry.privileges, ",");
output << ":" << authEntry.last_login;
output << std::endl;
}
if (!fs::safeWriteToFile(path, output.str())) {
infostream << "Failed to write " << path << std::endl;
return false;
}
return true;
}
ModMetadataDatabaseFiles::ModMetadataDatabaseFiles(const std::string &savedir):
m_storage_dir(savedir + DIR_DELIM + "mod_storage")
{
}
bool ModMetadataDatabaseFiles::getModEntries(const std::string &modname, StringMap *storage)
{
Json::Value *meta = getOrCreateJson(modname);
if (!meta)
return false;
const Json::Value::Members attr_list = meta->getMemberNames();
for (const auto &it : attr_list) {
Json::Value attr_value = (*meta)[it];
(*storage)[it] = attr_value.asString();
}
return true;
}
bool ModMetadataDatabaseFiles::setModEntry(const std::string &modname,
const std::string &key, const std::string &value)
{
Json::Value *meta = getOrCreateJson(modname);
if (!meta)
return false;
(*meta)[key] = Json::Value(value);
m_modified.insert(modname);
return true;
}
bool ModMetadataDatabaseFiles::removeModEntry(const std::string &modname,
const std::string &key)
{
Json::Value *meta = getOrCreateJson(modname);
if (!meta)
return false;
Json::Value removed;
if (meta->removeMember(key, &removed)) {
m_modified.insert(modname);
return true;
}
return false;
}
void ModMetadataDatabaseFiles::beginSave()
{
}
void ModMetadataDatabaseFiles::endSave()
{
if (m_modified.empty())
return;
if (!fs::CreateAllDirs(m_storage_dir)) {
errorstream << "ModMetadataDatabaseFiles: Unable to save. '"
<< m_storage_dir << "' cannot be created." << std::endl;
return;
}
if (!fs::IsDir(m_storage_dir)) {
errorstream << "ModMetadataDatabaseFiles: Unable to save. '"
<< m_storage_dir << "' is not a directory." << std::endl;
return;
}
for (auto it = m_modified.begin(); it != m_modified.end();) {
const std::string &modname = *it;
const Json::Value &json = m_mod_meta[modname];
if (!fs::safeWriteToFile(m_storage_dir + DIR_DELIM + modname, fastWriteJson(json))) {
errorstream << "ModMetadataDatabaseFiles[" << modname
<< "]: failed to write file." << std::endl;
++it;
continue;
}
it = m_modified.erase(it);
}
}
void ModMetadataDatabaseFiles::listMods(std::vector<std::string> *res)
{
// List in-memory metadata first.
for (const auto &pair : m_mod_meta) {
res->push_back(pair.first);
}
// List other metadata present in the filesystem.
for (const auto &entry : fs::GetDirListing(m_storage_dir)) {
if (!entry.dir && m_mod_meta.count(entry.name) == 0)
res->push_back(entry.name);
}
}
Json::Value *ModMetadataDatabaseFiles::getOrCreateJson(const std::string &modname)
{
auto found = m_mod_meta.find(modname);
if (found != m_mod_meta.end())
return &found->second;
Json::Value meta(Json::objectValue);
std::string path = m_storage_dir + DIR_DELIM + modname;
if (fs::PathExists(path)) {
std::ifstream is(path.c_str(), std::ios_base::binary);
Json::CharReaderBuilder builder;
builder.settings_["collectComments"] = false;
std::string errs;
if (!Json::parseFromStream(builder, is, &meta, &errs)) {
errorstream << "ModMetadataDatabaseFiles[" << modname
<< "]: failed to decode data: " << errs << std::endl;
return nullptr;
}
}
return &(m_mod_meta[modname] = meta);
}

View File

@@ -0,0 +1,97 @@
/*
Minetest
Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser 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.
*/
#pragma once
// !!! WARNING !!!
// This backend is intended to be used on Minetest 0.4.16 only for the transition backend
// for player files
#include "database.h"
#include <unordered_map>
#include <unordered_set>
#include <json/json.h>
class PlayerDatabaseFiles : public PlayerDatabase
{
public:
PlayerDatabaseFiles(const std::string &savedir);
virtual ~PlayerDatabaseFiles() = default;
void savePlayer(RemotePlayer *player);
bool loadPlayer(RemotePlayer *player, PlayerSAO *sao);
bool removePlayer(const std::string &name);
void listPlayers(std::vector<std::string> &res);
private:
void deSerialize(RemotePlayer *p, std::istream &is, const std::string &playername,
PlayerSAO *sao);
/*
serialize() writes a bunch of text that can contain
any characters except a '\0', and such an ending that
deSerialize stops reading exactly at the right point.
*/
void serialize(RemotePlayer *p, std::ostream &os);
std::string m_savedir;
};
class AuthDatabaseFiles : public AuthDatabase
{
public:
AuthDatabaseFiles(const std::string &savedir);
virtual ~AuthDatabaseFiles() = default;
virtual bool getAuth(const std::string &name, AuthEntry &res);
virtual bool saveAuth(const AuthEntry &authEntry);
virtual bool createAuth(AuthEntry &authEntry);
virtual bool deleteAuth(const std::string &name);
virtual void listNames(std::vector<std::string> &res);
virtual void reload();
private:
std::unordered_map<std::string, AuthEntry> m_auth_list;
std::string m_savedir;
bool readAuthFile();
bool writeAuthFile();
};
class ModMetadataDatabaseFiles : public ModMetadataDatabase
{
public:
ModMetadataDatabaseFiles(const std::string &savedir);
virtual ~ModMetadataDatabaseFiles() = default;
virtual bool getModEntries(const std::string &modname, StringMap *storage);
virtual bool setModEntry(const std::string &modname,
const std::string &key, const std::string &value);
virtual bool removeModEntry(const std::string &modname, const std::string &key);
virtual void listMods(std::vector<std::string> *res);
virtual void beginSave();
virtual void endSave();
private:
Json::Value *getOrCreateJson(const std::string &modname);
bool writeJson(const std::string &modname, const Json::Value &json);
std::string m_storage_dir;
std::unordered_map<std::string, Json::Value> m_mod_meta;
std::unordered_set<std::string> m_modified;
};

View File

@@ -0,0 +1,309 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser 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.
*/
#include "config.h"
#if USE_LEVELDB
#include "database-leveldb.h"
#include "log.h"
#include "filesys.h"
#include "exceptions.h"
#include "remoteplayer.h"
#include "server/player_sao.h"
#include "util/serialize.h"
#include "util/string.h"
#include "leveldb/db.h"
#define ENSURE_STATUS_OK(s) \
if (!(s).ok()) { \
throw DatabaseException(std::string("LevelDB error: ") + \
(s).ToString()); \
}
Database_LevelDB::Database_LevelDB(const std::string &savedir)
{
leveldb::Options options;
options.create_if_missing = true;
leveldb::Status status = leveldb::DB::Open(options,
savedir + DIR_DELIM + "map.db", &m_database);
ENSURE_STATUS_OK(status);
}
Database_LevelDB::~Database_LevelDB()
{
delete m_database;
}
bool Database_LevelDB::saveBlock(const v3s16 &pos, const std::string &data)
{
leveldb::Status status = m_database->Put(leveldb::WriteOptions(),
i64tos(getBlockAsInteger(pos)), data);
if (!status.ok()) {
warningstream << "saveBlock: LevelDB error saving block "
<< PP(pos) << ": " << status.ToString() << std::endl;
return false;
}
return true;
}
void Database_LevelDB::loadBlock(const v3s16 &pos, std::string *block)
{
leveldb::Status status = m_database->Get(leveldb::ReadOptions(),
i64tos(getBlockAsInteger(pos)), block);
if (!status.ok())
block->clear();
}
bool Database_LevelDB::deleteBlock(const v3s16 &pos)
{
leveldb::Status status = m_database->Delete(leveldb::WriteOptions(),
i64tos(getBlockAsInteger(pos)));
if (!status.ok()) {
warningstream << "deleteBlock: LevelDB error deleting block "
<< PP(pos) << ": " << status.ToString() << std::endl;
return false;
}
return true;
}
void Database_LevelDB::listAllLoadableBlocks(std::vector<v3s16> &dst)
{
leveldb::Iterator* it = m_database->NewIterator(leveldb::ReadOptions());
for (it->SeekToFirst(); it->Valid(); it->Next()) {
dst.push_back(getIntegerAsBlock(stoi64(it->key().ToString())));
}
ENSURE_STATUS_OK(it->status()); // Check for any errors found during the scan
delete it;
}
PlayerDatabaseLevelDB::PlayerDatabaseLevelDB(const std::string &savedir)
{
leveldb::Options options;
options.create_if_missing = true;
leveldb::Status status = leveldb::DB::Open(options,
savedir + DIR_DELIM + "players.db", &m_database);
ENSURE_STATUS_OK(status);
}
PlayerDatabaseLevelDB::~PlayerDatabaseLevelDB()
{
delete m_database;
}
void PlayerDatabaseLevelDB::savePlayer(RemotePlayer *player)
{
/*
u8 version = 1
u16 hp
v3f position
f32 pitch
f32 yaw
u16 breath
u32 attribute_count
for each attribute {
std::string name
std::string (long) value
}
std::string (long) serialized_inventory
*/
std::ostringstream os(std::ios_base::binary);
writeU8(os, 1);
PlayerSAO *sao = player->getPlayerSAO();
sanity_check(sao);
writeU16(os, sao->getHP());
writeV3F32(os, sao->getBasePosition());
writeF32(os, sao->getLookPitch());
writeF32(os, sao->getRotation().Y);
writeU16(os, sao->getBreath());
const auto &stringvars = sao->getMeta().getStrings();
writeU32(os, stringvars.size());
for (const auto &it : stringvars) {
os << serializeString16(it.first);
os << serializeString32(it.second);
}
player->inventory.serialize(os);
leveldb::Status status = m_database->Put(leveldb::WriteOptions(),
player->getName(), os.str());
ENSURE_STATUS_OK(status);
player->onSuccessfulSave();
}
bool PlayerDatabaseLevelDB::removePlayer(const std::string &name)
{
leveldb::Status s = m_database->Delete(leveldb::WriteOptions(), name);
return s.ok();
}
bool PlayerDatabaseLevelDB::loadPlayer(RemotePlayer *player, PlayerSAO *sao)
{
std::string raw;
leveldb::Status s = m_database->Get(leveldb::ReadOptions(),
player->getName(), &raw);
if (!s.ok())
return false;
std::istringstream is(raw, std::ios_base::binary);
if (readU8(is) > 1)
return false;
sao->setHPRaw(readU16(is));
sao->setBasePosition(readV3F32(is));
sao->setLookPitch(readF32(is));
sao->setPlayerYaw(readF32(is));
sao->setBreath(readU16(is), false);
u32 attribute_count = readU32(is);
for (u32 i = 0; i < attribute_count; i++) {
std::string name = deSerializeString16(is);
std::string value = deSerializeString32(is);
sao->getMeta().setString(name, value);
}
sao->getMeta().setModified(false);
// This should always be last.
try {
player->inventory.deSerialize(is);
} catch (SerializationError &e) {
errorstream << "Failed to deserialize player inventory. player_name="
<< player->getName() << " " << e.what() << std::endl;
}
return true;
}
void PlayerDatabaseLevelDB::listPlayers(std::vector<std::string> &res)
{
leveldb::Iterator* it = m_database->NewIterator(leveldb::ReadOptions());
res.clear();
for (it->SeekToFirst(); it->Valid(); it->Next()) {
res.push_back(it->key().ToString());
}
delete it;
}
AuthDatabaseLevelDB::AuthDatabaseLevelDB(const std::string &savedir)
{
leveldb::Options options;
options.create_if_missing = true;
leveldb::Status status = leveldb::DB::Open(options,
savedir + DIR_DELIM + "auth.db", &m_database);
ENSURE_STATUS_OK(status);
}
AuthDatabaseLevelDB::~AuthDatabaseLevelDB()
{
delete m_database;
}
bool AuthDatabaseLevelDB::getAuth(const std::string &name, AuthEntry &res)
{
std::string raw;
leveldb::Status s = m_database->Get(leveldb::ReadOptions(), name, &raw);
if (!s.ok())
return false;
std::istringstream is(raw, std::ios_base::binary);
/*
u8 version = 1
std::string password
u16 number of privileges
for each privilege {
std::string privilege
}
s64 last_login
*/
if (readU8(is) > 1)
return false;
res.id = 1;
res.name = name;
res.password = deSerializeString16(is);
u16 privilege_count = readU16(is);
res.privileges.clear();
res.privileges.reserve(privilege_count);
for (u16 i = 0; i < privilege_count; i++) {
res.privileges.push_back(deSerializeString16(is));
}
res.last_login = readS64(is);
return true;
}
bool AuthDatabaseLevelDB::saveAuth(const AuthEntry &authEntry)
{
std::ostringstream os(std::ios_base::binary);
writeU8(os, 1);
os << serializeString16(authEntry.password);
size_t privilege_count = authEntry.privileges.size();
FATAL_ERROR_IF(privilege_count > U16_MAX,
"Unsupported number of privileges");
writeU16(os, privilege_count);
for (const std::string &privilege : authEntry.privileges) {
os << serializeString16(privilege);
}
writeS64(os, authEntry.last_login);
leveldb::Status s = m_database->Put(leveldb::WriteOptions(),
authEntry.name, os.str());
return s.ok();
}
bool AuthDatabaseLevelDB::createAuth(AuthEntry &authEntry)
{
return saveAuth(authEntry);
}
bool AuthDatabaseLevelDB::deleteAuth(const std::string &name)
{
leveldb::Status s = m_database->Delete(leveldb::WriteOptions(), name);
return s.ok();
}
void AuthDatabaseLevelDB::listNames(std::vector<std::string> &res)
{
leveldb::Iterator* it = m_database->NewIterator(leveldb::ReadOptions());
res.clear();
for (it->SeekToFirst(); it->Valid(); it->Next()) {
res.emplace_back(it->key().ToString());
}
delete it;
}
void AuthDatabaseLevelDB::reload()
{
// No-op for LevelDB.
}
#endif // USE_LEVELDB

View File

@@ -0,0 +1,80 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser 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.
*/
#pragma once
#include "config.h"
#if USE_LEVELDB
#include <string>
#include "database.h"
#include "leveldb/db.h"
class Database_LevelDB : public MapDatabase
{
public:
Database_LevelDB(const std::string &savedir);
~Database_LevelDB();
bool saveBlock(const v3s16 &pos, const std::string &data);
void loadBlock(const v3s16 &pos, std::string *block);
bool deleteBlock(const v3s16 &pos);
void listAllLoadableBlocks(std::vector<v3s16> &dst);
void beginSave() {}
void endSave() {}
private:
leveldb::DB *m_database;
};
class PlayerDatabaseLevelDB : public PlayerDatabase
{
public:
PlayerDatabaseLevelDB(const std::string &savedir);
~PlayerDatabaseLevelDB();
void savePlayer(RemotePlayer *player);
bool loadPlayer(RemotePlayer *player, PlayerSAO *sao);
bool removePlayer(const std::string &name);
void listPlayers(std::vector<std::string> &res);
private:
leveldb::DB *m_database;
};
class AuthDatabaseLevelDB : public AuthDatabase
{
public:
AuthDatabaseLevelDB(const std::string &savedir);
virtual ~AuthDatabaseLevelDB();
virtual bool getAuth(const std::string &name, AuthEntry &res);
virtual bool saveAuth(const AuthEntry &authEntry);
virtual bool createAuth(AuthEntry &authEntry);
virtual bool deleteAuth(const std::string &name);
virtual void listNames(std::vector<std::string> &res);
virtual void reload();
private:
leveldb::DB *m_database;
};
#endif // USE_LEVELDB

View File

@@ -0,0 +1,816 @@
/*
Copyright (C) 2016 Loic Blot <loic.blot@unix-experience.fr>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser 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.
*/
#include "config.h"
#if USE_POSTGRESQL
#include "database-postgresql.h"
#ifdef _WIN32
// Without this some of the network functions are not found on mingw
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501
#endif
#include <windows.h>
#include <winsock2.h>
#else
#include <netinet/in.h>
#endif
#include "debug.h"
#include "exceptions.h"
#include "settings.h"
#include "remoteplayer.h"
#include "server/player_sao.h"
Database_PostgreSQL::Database_PostgreSQL(const std::string &connect_string,
const char *type) :
m_connect_string(connect_string)
{
if (m_connect_string.empty()) {
// Use given type to reference the exact setting in the error message
std::string s = type;
std::string msg =
"Set pgsql" + s + "_connection string in world.mt to "
"use the postgresql backend\n"
"Notes:\n"
"pgsql" + s + "_connection has the following form: \n"
"\tpgsql" + s + "_connection = host=127.0.0.1 port=5432 "
"user=mt_user password=mt_password dbname=minetest" + s + "\n"
"mt_user should have CREATE TABLE, INSERT, SELECT, UPDATE and "
"DELETE rights on the database. "
"Don't create mt_user as a SUPERUSER!";
throw SettingNotFoundException(msg);
}
}
Database_PostgreSQL::~Database_PostgreSQL()
{
PQfinish(m_conn);
}
void Database_PostgreSQL::connectToDatabase()
{
m_conn = PQconnectdb(m_connect_string.c_str());
if (PQstatus(m_conn) != CONNECTION_OK) {
throw DatabaseException(std::string(
"PostgreSQL database error: ") +
PQerrorMessage(m_conn));
}
m_pgversion = PQserverVersion(m_conn);
/*
* We are using UPSERT feature from PostgreSQL 9.5
* to have the better performance where possible.
*/
if (m_pgversion < 90500) {
warningstream << "Your PostgreSQL server lacks UPSERT "
<< "support. Use version 9.5 or better if possible."
<< std::endl;
}
infostream << "PostgreSQL Database: Version " << m_pgversion
<< " Connection made." << std::endl;
createDatabase();
initStatements();
}
void Database_PostgreSQL::verifyDatabase()
{
if (PQstatus(m_conn) == CONNECTION_OK)
return;
PQreset(m_conn);
ping();
}
void Database_PostgreSQL::ping()
{
if (PQping(m_connect_string.c_str()) != PQPING_OK) {
throw DatabaseException(std::string(
"PostgreSQL database error: ") +
PQerrorMessage(m_conn));
}
}
bool Database_PostgreSQL::initialized() const
{
return (PQstatus(m_conn) == CONNECTION_OK);
}
PGresult *Database_PostgreSQL::checkResults(PGresult *result, bool clear)
{
ExecStatusType statusType = PQresultStatus(result);
switch (statusType) {
case PGRES_COMMAND_OK:
case PGRES_TUPLES_OK:
break;
case PGRES_FATAL_ERROR:
default:
throw DatabaseException(
std::string("PostgreSQL database error: ") +
PQresultErrorMessage(result));
}
if (clear)
PQclear(result);
return result;
}
void Database_PostgreSQL::createTableIfNotExists(const std::string &table_name,
const std::string &definition)
{
std::string sql_check_table = "SELECT relname FROM pg_class WHERE relname='" +
table_name + "';";
PGresult *result = checkResults(PQexec(m_conn, sql_check_table.c_str()), false);
// If table doesn't exist, create it
if (!PQntuples(result)) {
checkResults(PQexec(m_conn, definition.c_str()));
}
PQclear(result);
}
void Database_PostgreSQL::beginSave()
{
verifyDatabase();
checkResults(PQexec(m_conn, "BEGIN;"));
}
void Database_PostgreSQL::endSave()
{
checkResults(PQexec(m_conn, "COMMIT;"));
}
void Database_PostgreSQL::rollback()
{
checkResults(PQexec(m_conn, "ROLLBACK;"));
}
MapDatabasePostgreSQL::MapDatabasePostgreSQL(const std::string &connect_string):
Database_PostgreSQL(connect_string, ""),
MapDatabase()
{
connectToDatabase();
}
void MapDatabasePostgreSQL::createDatabase()
{
createTableIfNotExists("blocks",
"CREATE TABLE blocks ("
"posX INT NOT NULL,"
"posY INT NOT NULL,"
"posZ INT NOT NULL,"
"data BYTEA,"
"PRIMARY KEY (posX,posY,posZ)"
");"
);
infostream << "PostgreSQL: Map Database was initialized." << std::endl;
}
void MapDatabasePostgreSQL::initStatements()
{
prepareStatement("read_block",
"SELECT data FROM blocks "
"WHERE posX = $1::int4 AND posY = $2::int4 AND "
"posZ = $3::int4");
if (getPGVersion() < 90500) {
prepareStatement("write_block_insert",
"INSERT INTO blocks (posX, posY, posZ, data) SELECT "
"$1::int4, $2::int4, $3::int4, $4::bytea "
"WHERE NOT EXISTS (SELECT true FROM blocks "
"WHERE posX = $1::int4 AND posY = $2::int4 AND "
"posZ = $3::int4)");
prepareStatement("write_block_update",
"UPDATE blocks SET data = $4::bytea "
"WHERE posX = $1::int4 AND posY = $2::int4 AND "
"posZ = $3::int4");
} else {
prepareStatement("write_block",
"INSERT INTO blocks (posX, posY, posZ, data) VALUES "
"($1::int4, $2::int4, $3::int4, $4::bytea) "
"ON CONFLICT ON CONSTRAINT blocks_pkey DO "
"UPDATE SET data = $4::bytea");
}
prepareStatement("delete_block", "DELETE FROM blocks WHERE "
"posX = $1::int4 AND posY = $2::int4 AND posZ = $3::int4");
prepareStatement("list_all_loadable_blocks",
"SELECT posX, posY, posZ FROM blocks");
}
bool MapDatabasePostgreSQL::saveBlock(const v3s16 &pos, const std::string &data)
{
// Verify if we don't overflow the platform integer with the mapblock size
if (data.size() > INT_MAX) {
errorstream << "Database_PostgreSQL::saveBlock: Data truncation! "
<< "data.size() over 0xFFFFFFFF (== " << data.size()
<< ")" << std::endl;
return false;
}
verifyDatabase();
s32 x, y, z;
x = htonl(pos.X);
y = htonl(pos.Y);
z = htonl(pos.Z);
const void *args[] = { &x, &y, &z, data.c_str() };
const int argLen[] = {
sizeof(x), sizeof(y), sizeof(z), (int)data.size()
};
const int argFmt[] = { 1, 1, 1, 1 };
if (getPGVersion() < 90500) {
execPrepared("write_block_update", ARRLEN(args), args, argLen, argFmt);
execPrepared("write_block_insert", ARRLEN(args), args, argLen, argFmt);
} else {
execPrepared("write_block", ARRLEN(args), args, argLen, argFmt);
}
return true;
}
void MapDatabasePostgreSQL::loadBlock(const v3s16 &pos, std::string *block)
{
verifyDatabase();
s32 x, y, z;
x = htonl(pos.X);
y = htonl(pos.Y);
z = htonl(pos.Z);
const void *args[] = { &x, &y, &z };
const int argLen[] = { sizeof(x), sizeof(y), sizeof(z) };
const int argFmt[] = { 1, 1, 1 };
PGresult *results = execPrepared("read_block", ARRLEN(args), args,
argLen, argFmt, false);
if (PQntuples(results))
block->assign(PQgetvalue(results, 0, 0), PQgetlength(results, 0, 0));
else
block->clear();
PQclear(results);
}
bool MapDatabasePostgreSQL::deleteBlock(const v3s16 &pos)
{
verifyDatabase();
s32 x, y, z;
x = htonl(pos.X);
y = htonl(pos.Y);
z = htonl(pos.Z);
const void *args[] = { &x, &y, &z };
const int argLen[] = { sizeof(x), sizeof(y), sizeof(z) };
const int argFmt[] = { 1, 1, 1 };
execPrepared("delete_block", ARRLEN(args), args, argLen, argFmt);
return true;
}
void MapDatabasePostgreSQL::listAllLoadableBlocks(std::vector<v3s16> &dst)
{
verifyDatabase();
PGresult *results = execPrepared("list_all_loadable_blocks", 0,
NULL, NULL, NULL, false, false);
int numrows = PQntuples(results);
for (int row = 0; row < numrows; ++row)
dst.push_back(pg_to_v3s16(results, row, 0));
PQclear(results);
}
/*
* Player Database
*/
PlayerDatabasePostgreSQL::PlayerDatabasePostgreSQL(const std::string &connect_string):
Database_PostgreSQL(connect_string, "_player"),
PlayerDatabase()
{
connectToDatabase();
}
void PlayerDatabasePostgreSQL::createDatabase()
{
createTableIfNotExists("player",
"CREATE TABLE player ("
"name VARCHAR(60) NOT NULL,"
"pitch NUMERIC(15, 7) NOT NULL,"
"yaw NUMERIC(15, 7) NOT NULL,"
"posX NUMERIC(15, 7) NOT NULL,"
"posY NUMERIC(15, 7) NOT NULL,"
"posZ NUMERIC(15, 7) NOT NULL,"
"hp INT NOT NULL,"
"breath INT NOT NULL,"
"creation_date TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),"
"modification_date TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),"
"PRIMARY KEY (name)"
");"
);
createTableIfNotExists("player_inventories",
"CREATE TABLE player_inventories ("
"player VARCHAR(60) NOT NULL,"
"inv_id INT NOT NULL,"
"inv_width INT NOT NULL,"
"inv_name TEXT NOT NULL DEFAULT '',"
"inv_size INT NOT NULL,"
"PRIMARY KEY(player, inv_id),"
"CONSTRAINT player_inventories_fkey FOREIGN KEY (player) REFERENCES "
"player (name) ON DELETE CASCADE"
");"
);
createTableIfNotExists("player_inventory_items",
"CREATE TABLE player_inventory_items ("
"player VARCHAR(60) NOT NULL,"
"inv_id INT NOT NULL,"
"slot_id INT NOT NULL,"
"item TEXT NOT NULL DEFAULT '',"
"PRIMARY KEY(player, inv_id, slot_id),"
"CONSTRAINT player_inventory_items_fkey FOREIGN KEY (player) REFERENCES "
"player (name) ON DELETE CASCADE"
");"
);
createTableIfNotExists("player_metadata",
"CREATE TABLE player_metadata ("
"player VARCHAR(60) NOT NULL,"
"attr VARCHAR(256) NOT NULL,"
"value TEXT,"
"PRIMARY KEY(player, attr),"
"CONSTRAINT player_metadata_fkey FOREIGN KEY (player) REFERENCES "
"player (name) ON DELETE CASCADE"
");"
);
infostream << "PostgreSQL: Player Database was inited." << std::endl;
}
void PlayerDatabasePostgreSQL::initStatements()
{
if (getPGVersion() < 90500) {
prepareStatement("create_player",
"INSERT INTO player(name, pitch, yaw, posX, posY, posZ, hp, breath) VALUES "
"($1, $2, $3, $4, $5, $6, $7::int, $8::int)");
prepareStatement("update_player",
"UPDATE SET pitch = $2, yaw = $3, posX = $4, posY = $5, posZ = $6, hp = $7::int, "
"breath = $8::int, modification_date = NOW() WHERE name = $1");
} else {
prepareStatement("save_player",
"INSERT INTO player(name, pitch, yaw, posX, posY, posZ, hp, breath) VALUES "
"($1, $2, $3, $4, $5, $6, $7::int, $8::int)"
"ON CONFLICT ON CONSTRAINT player_pkey DO UPDATE SET pitch = $2, yaw = $3, "
"posX = $4, posY = $5, posZ = $6, hp = $7::int, breath = $8::int, "
"modification_date = NOW()");
}
prepareStatement("remove_player", "DELETE FROM player WHERE name = $1");
prepareStatement("load_player_list", "SELECT name FROM player");
prepareStatement("remove_player_inventories",
"DELETE FROM player_inventories WHERE player = $1");
prepareStatement("remove_player_inventory_items",
"DELETE FROM player_inventory_items WHERE player = $1");
prepareStatement("add_player_inventory",
"INSERT INTO player_inventories (player, inv_id, inv_width, inv_name, inv_size) VALUES "
"($1, $2::int, $3::int, $4, $5::int)");
prepareStatement("add_player_inventory_item",
"INSERT INTO player_inventory_items (player, inv_id, slot_id, item) VALUES "
"($1, $2::int, $3::int, $4)");
prepareStatement("load_player_inventories",
"SELECT inv_id, inv_width, inv_name, inv_size FROM player_inventories "
"WHERE player = $1 ORDER BY inv_id");
prepareStatement("load_player_inventory_items",
"SELECT slot_id, item FROM player_inventory_items WHERE "
"player = $1 AND inv_id = $2::int");
prepareStatement("load_player",
"SELECT pitch, yaw, posX, posY, posZ, hp, breath FROM player WHERE name = $1");
prepareStatement("remove_player_metadata",
"DELETE FROM player_metadata WHERE player = $1");
prepareStatement("save_player_metadata",
"INSERT INTO player_metadata (player, attr, value) VALUES ($1, $2, $3)");
prepareStatement("load_player_metadata",
"SELECT attr, value FROM player_metadata WHERE player = $1");
}
bool PlayerDatabasePostgreSQL::playerDataExists(const std::string &playername)
{
verifyDatabase();
const char *values[] = { playername.c_str() };
PGresult *results = execPrepared("load_player", 1, values, false);
bool res = (PQntuples(results) > 0);
PQclear(results);
return res;
}
void PlayerDatabasePostgreSQL::savePlayer(RemotePlayer *player)
{
PlayerSAO* sao = player->getPlayerSAO();
if (!sao)
return;
verifyDatabase();
v3f pos = sao->getBasePosition();
std::string pitch = ftos(sao->getLookPitch());
std::string yaw = ftos(sao->getRotation().Y);
std::string posx = ftos(pos.X);
std::string posy = ftos(pos.Y);
std::string posz = ftos(pos.Z);
std::string hp = itos(sao->getHP());
std::string breath = itos(sao->getBreath());
const char *values[] = {
player->getName(),
pitch.c_str(),
yaw.c_str(),
posx.c_str(), posy.c_str(), posz.c_str(),
hp.c_str(),
breath.c_str()
};
const char* rmvalues[] = { player->getName() };
beginSave();
if (getPGVersion() < 90500) {
if (!playerDataExists(player->getName()))
execPrepared("create_player", 8, values, true, false);
else
execPrepared("update_player", 8, values, true, false);
}
else
execPrepared("save_player", 8, values, true, false);
// Write player inventories
execPrepared("remove_player_inventories", 1, rmvalues);
execPrepared("remove_player_inventory_items", 1, rmvalues);
const auto &inventory_lists = sao->getInventory()->getLists();
std::ostringstream oss;
for (u16 i = 0; i < inventory_lists.size(); i++) {
const InventoryList* list = inventory_lists[i];
const std::string &name = list->getName();
std::string width = itos(list->getWidth()),
inv_id = itos(i), lsize = itos(list->getSize());
const char* inv_values[] = {
player->getName(),
inv_id.c_str(),
width.c_str(),
name.c_str(),
lsize.c_str()
};
execPrepared("add_player_inventory", 5, inv_values);
for (u32 j = 0; j < list->getSize(); j++) {
oss.str("");
oss.clear();
list->getItem(j).serialize(oss);
std::string itemStr = oss.str(), slotId = itos(j);
const char* invitem_values[] = {
player->getName(),
inv_id.c_str(),
slotId.c_str(),
itemStr.c_str()
};
execPrepared("add_player_inventory_item", 4, invitem_values);
}
}
execPrepared("remove_player_metadata", 1, rmvalues);
const StringMap &attrs = sao->getMeta().getStrings();
for (const auto &attr : attrs) {
const char *meta_values[] = {
player->getName(),
attr.first.c_str(),
attr.second.c_str()
};
execPrepared("save_player_metadata", 3, meta_values);
}
endSave();
player->onSuccessfulSave();
}
bool PlayerDatabasePostgreSQL::loadPlayer(RemotePlayer *player, PlayerSAO *sao)
{
sanity_check(sao);
verifyDatabase();
const char *values[] = { player->getName() };
PGresult *results = execPrepared("load_player", 1, values, false, false);
// Player not found, return not found
if (!PQntuples(results)) {
PQclear(results);
return false;
}
sao->setLookPitch(pg_to_float(results, 0, 0));
sao->setRotation(v3f(0, pg_to_float(results, 0, 1), 0));
sao->setBasePosition(v3f(
pg_to_float(results, 0, 2),
pg_to_float(results, 0, 3),
pg_to_float(results, 0, 4))
);
sao->setHPRaw((u16) pg_to_int(results, 0, 5));
sao->setBreath((u16) pg_to_int(results, 0, 6), false);
PQclear(results);
// Load inventory
results = execPrepared("load_player_inventories", 1, values, false, false);
int resultCount = PQntuples(results);
for (int row = 0; row < resultCount; ++row) {
InventoryList* invList = player->inventory.
addList(PQgetvalue(results, row, 2), pg_to_uint(results, row, 3));
invList->setWidth(pg_to_uint(results, row, 1));
u32 invId = pg_to_uint(results, row, 0);
std::string invIdStr = itos(invId);
const char* values2[] = {
player->getName(),
invIdStr.c_str()
};
PGresult *results2 = execPrepared("load_player_inventory_items", 2,
values2, false, false);
int resultCount2 = PQntuples(results2);
for (int row2 = 0; row2 < resultCount2; row2++) {
const std::string itemStr = PQgetvalue(results2, row2, 1);
if (itemStr.length() > 0) {
ItemStack stack;
stack.deSerialize(itemStr);
invList->changeItem(pg_to_uint(results2, row2, 0), stack);
}
}
PQclear(results2);
}
PQclear(results);
results = execPrepared("load_player_metadata", 1, values, false);
int numrows = PQntuples(results);
for (int row = 0; row < numrows; row++) {
sao->getMeta().setString(PQgetvalue(results, row, 0), PQgetvalue(results, row, 1));
}
sao->getMeta().setModified(false);
PQclear(results);
return true;
}
bool PlayerDatabasePostgreSQL::removePlayer(const std::string &name)
{
if (!playerDataExists(name))
return false;
verifyDatabase();
const char *values[] = { name.c_str() };
execPrepared("remove_player", 1, values);
return true;
}
void PlayerDatabasePostgreSQL::listPlayers(std::vector<std::string> &res)
{
verifyDatabase();
PGresult *results = execPrepared("load_player_list", 0, NULL, false);
int numrows = PQntuples(results);
for (int row = 0; row < numrows; row++)
res.emplace_back(PQgetvalue(results, row, 0));
PQclear(results);
}
AuthDatabasePostgreSQL::AuthDatabasePostgreSQL(const std::string &connect_string) :
Database_PostgreSQL(connect_string, "_auth"),
AuthDatabase()
{
connectToDatabase();
}
void AuthDatabasePostgreSQL::createDatabase()
{
createTableIfNotExists("auth",
"CREATE TABLE auth ("
"id SERIAL,"
"name TEXT UNIQUE,"
"password TEXT,"
"last_login INT NOT NULL DEFAULT 0,"
"PRIMARY KEY (id)"
");");
createTableIfNotExists("user_privileges",
"CREATE TABLE user_privileges ("
"id INT,"
"privilege TEXT,"
"PRIMARY KEY (id, privilege),"
"CONSTRAINT fk_id FOREIGN KEY (id) REFERENCES auth (id) ON DELETE CASCADE"
");");
}
void AuthDatabasePostgreSQL::initStatements()
{
prepareStatement("auth_read", "SELECT id, name, password, last_login FROM auth WHERE name = $1");
prepareStatement("auth_write", "UPDATE auth SET name = $1, password = $2, last_login = $3 WHERE id = $4");
prepareStatement("auth_create", "INSERT INTO auth (name, password, last_login) VALUES ($1, $2, $3) RETURNING id");
prepareStatement("auth_delete", "DELETE FROM auth WHERE name = $1");
prepareStatement("auth_list_names", "SELECT name FROM auth ORDER BY name DESC");
prepareStatement("auth_read_privs", "SELECT privilege FROM user_privileges WHERE id = $1");
prepareStatement("auth_write_privs", "INSERT INTO user_privileges (id, privilege) VALUES ($1, $2)");
prepareStatement("auth_delete_privs", "DELETE FROM user_privileges WHERE id = $1");
}
bool AuthDatabasePostgreSQL::getAuth(const std::string &name, AuthEntry &res)
{
verifyDatabase();
const char *values[] = { name.c_str() };
PGresult *result = execPrepared("auth_read", 1, values, false, false);
int numrows = PQntuples(result);
if (numrows == 0) {
PQclear(result);
return false;
}
res.id = pg_to_uint(result, 0, 0);
res.name = std::string(PQgetvalue(result, 0, 1), PQgetlength(result, 0, 1));
res.password = std::string(PQgetvalue(result, 0, 2), PQgetlength(result, 0, 2));
res.last_login = pg_to_int(result, 0, 3);
PQclear(result);
std::string playerIdStr = itos(res.id);
const char *privsValues[] = { playerIdStr.c_str() };
PGresult *results = execPrepared("auth_read_privs", 1, privsValues, false);
numrows = PQntuples(results);
for (int row = 0; row < numrows; row++)
res.privileges.emplace_back(PQgetvalue(results, row, 0));
PQclear(results);
return true;
}
bool AuthDatabasePostgreSQL::saveAuth(const AuthEntry &authEntry)
{
verifyDatabase();
beginSave();
std::string lastLoginStr = itos(authEntry.last_login);
std::string idStr = itos(authEntry.id);
const char *values[] = {
authEntry.name.c_str() ,
authEntry.password.c_str(),
lastLoginStr.c_str(),
idStr.c_str(),
};
execPrepared("auth_write", 4, values);
writePrivileges(authEntry);
endSave();
return true;
}
bool AuthDatabasePostgreSQL::createAuth(AuthEntry &authEntry)
{
verifyDatabase();
std::string lastLoginStr = itos(authEntry.last_login);
const char *values[] = {
authEntry.name.c_str() ,
authEntry.password.c_str(),
lastLoginStr.c_str()
};
beginSave();
PGresult *result = execPrepared("auth_create", 3, values, false, false);
int numrows = PQntuples(result);
if (numrows == 0) {
errorstream << "Strange behaviour on auth creation, no ID returned." << std::endl;
PQclear(result);
rollback();
return false;
}
authEntry.id = pg_to_uint(result, 0, 0);
PQclear(result);
writePrivileges(authEntry);
endSave();
return true;
}
bool AuthDatabasePostgreSQL::deleteAuth(const std::string &name)
{
verifyDatabase();
const char *values[] = { name.c_str() };
execPrepared("auth_delete", 1, values);
// privileges deleted by foreign key on delete cascade
return true;
}
void AuthDatabasePostgreSQL::listNames(std::vector<std::string> &res)
{
verifyDatabase();
PGresult *results = execPrepared("auth_list_names", 0,
NULL, NULL, NULL, false, false);
int numrows = PQntuples(results);
for (int row = 0; row < numrows; ++row)
res.emplace_back(PQgetvalue(results, row, 0));
PQclear(results);
}
void AuthDatabasePostgreSQL::reload()
{
// noop for PgSQL
}
void AuthDatabasePostgreSQL::writePrivileges(const AuthEntry &authEntry)
{
std::string authIdStr = itos(authEntry.id);
const char *values[] = { authIdStr.c_str() };
execPrepared("auth_delete_privs", 1, values);
for (const std::string &privilege : authEntry.privileges) {
const char *values[] = { authIdStr.c_str(), privilege.c_str() };
execPrepared("auth_write_privs", 2, values);
}
}
#endif // USE_POSTGRESQL

View File

@@ -0,0 +1,171 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser 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.
*/
#pragma once
#include <string>
#include <libpq-fe.h>
#include "database.h"
#include "util/basic_macros.h"
class Settings;
class Database_PostgreSQL: public Database
{
public:
Database_PostgreSQL(const std::string &connect_string, const char *type);
~Database_PostgreSQL();
void beginSave();
void endSave();
void rollback();
bool initialized() const;
protected:
// Conversion helpers
inline int pg_to_int(PGresult *res, int row, int col)
{
return atoi(PQgetvalue(res, row, col));
}
inline u32 pg_to_uint(PGresult *res, int row, int col)
{
return (u32) atoi(PQgetvalue(res, row, col));
}
inline float pg_to_float(PGresult *res, int row, int col)
{
return (float) atof(PQgetvalue(res, row, col));
}
inline v3s16 pg_to_v3s16(PGresult *res, int row, int col)
{
return v3s16(
pg_to_int(res, row, col),
pg_to_int(res, row, col + 1),
pg_to_int(res, row, col + 2)
);
}
inline PGresult *execPrepared(const char *stmtName, const int paramsNumber,
const void **params,
const int *paramsLengths = NULL, const int *paramsFormats = NULL,
bool clear = true, bool nobinary = true)
{
return checkResults(PQexecPrepared(m_conn, stmtName, paramsNumber,
(const char* const*) params, paramsLengths, paramsFormats,
nobinary ? 1 : 0), clear);
}
inline PGresult *execPrepared(const char *stmtName, const int paramsNumber,
const char **params, bool clear = true, bool nobinary = true)
{
return execPrepared(stmtName, paramsNumber,
(const void **)params, NULL, NULL, clear, nobinary);
}
void createTableIfNotExists(const std::string &table_name, const std::string &definition);
void verifyDatabase();
// Database initialization
void connectToDatabase();
virtual void createDatabase() = 0;
virtual void initStatements() = 0;
inline void prepareStatement(const std::string &name, const std::string &sql)
{
checkResults(PQprepare(m_conn, name.c_str(), sql.c_str(), 0, NULL));
}
int getPGVersion() const { return m_pgversion; }
private:
// Database connectivity checks
void ping();
// Database usage
PGresult *checkResults(PGresult *res, bool clear = true);
// Attributes
std::string m_connect_string;
PGconn *m_conn = nullptr;
int m_pgversion = 0;
};
class MapDatabasePostgreSQL : private Database_PostgreSQL, public MapDatabase
{
public:
MapDatabasePostgreSQL(const std::string &connect_string);
virtual ~MapDatabasePostgreSQL() = default;
bool saveBlock(const v3s16 &pos, const std::string &data);
void loadBlock(const v3s16 &pos, std::string *block);
bool deleteBlock(const v3s16 &pos);
void listAllLoadableBlocks(std::vector<v3s16> &dst);
void beginSave() { Database_PostgreSQL::beginSave(); }
void endSave() { Database_PostgreSQL::endSave(); }
protected:
virtual void createDatabase();
virtual void initStatements();
};
class PlayerDatabasePostgreSQL : private Database_PostgreSQL, public PlayerDatabase
{
public:
PlayerDatabasePostgreSQL(const std::string &connect_string);
virtual ~PlayerDatabasePostgreSQL() = default;
void savePlayer(RemotePlayer *player);
bool loadPlayer(RemotePlayer *player, PlayerSAO *sao);
bool removePlayer(const std::string &name);
void listPlayers(std::vector<std::string> &res);
protected:
virtual void createDatabase();
virtual void initStatements();
private:
bool playerDataExists(const std::string &playername);
};
class AuthDatabasePostgreSQL : private Database_PostgreSQL, public AuthDatabase
{
public:
AuthDatabasePostgreSQL(const std::string &connect_string);
virtual ~AuthDatabasePostgreSQL() = default;
virtual void verifyDatabase() { Database_PostgreSQL::verifyDatabase(); }
virtual bool getAuth(const std::string &name, AuthEntry &res);
virtual bool saveAuth(const AuthEntry &authEntry);
virtual bool createAuth(AuthEntry &authEntry);
virtual bool deleteAuth(const std::string &name);
virtual void listNames(std::vector<std::string> &res);
virtual void reload();
protected:
virtual void createDatabase();
virtual void initStatements();
private:
virtual void writePrivileges(const AuthEntry &authEntry);
};

View File

@@ -0,0 +1,201 @@
/*
Minetest
Copyright (C) 2014 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser 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.
*/
#include "config.h"
#if USE_REDIS
#include "database-redis.h"
#include "settings.h"
#include "log.h"
#include "exceptions.h"
#include "util/string.h"
#include <hiredis.h>
#include <cassert>
Database_Redis::Database_Redis(Settings &conf)
{
std::string tmp;
try {
tmp = conf.get("redis_address");
hash = conf.get("redis_hash");
} catch (SettingNotFoundException &) {
throw SettingNotFoundException("Set redis_address and "
"redis_hash in world.mt to use the redis backend");
}
const char *addr = tmp.c_str();
int port = conf.exists("redis_port") ? conf.getU16("redis_port") : 6379;
// if redis_address contains '/' assume unix socket, else hostname/ip
ctx = tmp.find('/') != std::string::npos ? redisConnectUnix(addr) : redisConnect(addr, port);
if (!ctx) {
throw DatabaseException("Cannot allocate redis context");
} else if (ctx->err) {
std::string err = std::string("Connection error: ") + ctx->errstr;
redisFree(ctx);
throw DatabaseException(err);
}
if (conf.exists("redis_password")) {
tmp = conf.get("redis_password");
redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "AUTH %s", tmp.c_str()));
if (!reply)
throw DatabaseException("Redis authentication failed");
if (reply->type == REDIS_REPLY_ERROR) {
std::string err = "Redis authentication failed: " + std::string(reply->str, reply->len);
freeReplyObject(reply);
throw DatabaseException(err);
}
freeReplyObject(reply);
}
}
Database_Redis::~Database_Redis()
{
redisFree(ctx);
}
void Database_Redis::beginSave() {
redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "MULTI"));
if (!reply) {
throw DatabaseException(std::string(
"Redis command 'MULTI' failed: ") + ctx->errstr);
}
freeReplyObject(reply);
}
void Database_Redis::endSave() {
redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "EXEC"));
if (!reply) {
throw DatabaseException(std::string(
"Redis command 'EXEC' failed: ") + ctx->errstr);
}
freeReplyObject(reply);
}
bool Database_Redis::saveBlock(const v3s16 &pos, const std::string &data)
{
std::string tmp = i64tos(getBlockAsInteger(pos));
redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "HSET %s %s %b",
hash.c_str(), tmp.c_str(), data.c_str(), data.size()));
if (!reply) {
warningstream << "saveBlock: redis command 'HSET' failed on "
"block " << PP(pos) << ": " << ctx->errstr << std::endl;
freeReplyObject(reply);
return false;
}
if (reply->type == REDIS_REPLY_ERROR) {
warningstream << "saveBlock: saving block " << PP(pos)
<< " failed: " << std::string(reply->str, reply->len) << std::endl;
freeReplyObject(reply);
return false;
}
freeReplyObject(reply);
return true;
}
void Database_Redis::loadBlock(const v3s16 &pos, std::string *block)
{
std::string tmp = i64tos(getBlockAsInteger(pos));
redisReply *reply = static_cast<redisReply *>(redisCommand(ctx,
"HGET %s %s", hash.c_str(), tmp.c_str()));
if (!reply) {
throw DatabaseException(std::string(
"Redis command 'HGET %s %s' failed: ") + ctx->errstr);
}
switch (reply->type) {
case REDIS_REPLY_STRING: {
block->assign(reply->str, reply->len);
freeReplyObject(reply);
return;
}
case REDIS_REPLY_ERROR: {
std::string errstr(reply->str, reply->len);
freeReplyObject(reply);
errorstream << "loadBlock: loading block " << PP(pos)
<< " failed: " << errstr << std::endl;
throw DatabaseException(std::string(
"Redis command 'HGET %s %s' errored: ") + errstr);
}
case REDIS_REPLY_NIL: {
block->clear();
freeReplyObject(reply);
return;
}
}
errorstream << "loadBlock: loading block " << PP(pos)
<< " returned invalid reply type " << reply->type
<< ": " << std::string(reply->str, reply->len) << std::endl;
freeReplyObject(reply);
throw DatabaseException(std::string(
"Redis command 'HGET %s %s' gave invalid reply."));
}
bool Database_Redis::deleteBlock(const v3s16 &pos)
{
std::string tmp = i64tos(getBlockAsInteger(pos));
redisReply *reply = static_cast<redisReply *>(redisCommand(ctx,
"HDEL %s %s", hash.c_str(), tmp.c_str()));
if (!reply) {
throw DatabaseException(std::string(
"Redis command 'HDEL %s %s' failed: ") + ctx->errstr);
} else if (reply->type == REDIS_REPLY_ERROR) {
warningstream << "deleteBlock: deleting block " << PP(pos)
<< " failed: " << std::string(reply->str, reply->len) << std::endl;
freeReplyObject(reply);
return false;
}
freeReplyObject(reply);
return true;
}
void Database_Redis::listAllLoadableBlocks(std::vector<v3s16> &dst)
{
redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "HKEYS %s", hash.c_str()));
if (!reply) {
throw DatabaseException(std::string(
"Redis command 'HKEYS %s' failed: ") + ctx->errstr);
}
switch (reply->type) {
case REDIS_REPLY_ARRAY:
dst.reserve(reply->elements);
for (size_t i = 0; i < reply->elements; i++) {
assert(reply->element[i]->type == REDIS_REPLY_STRING);
dst.push_back(getIntegerAsBlock(stoi64(reply->element[i]->str)));
}
break;
case REDIS_REPLY_ERROR:
throw DatabaseException(std::string(
"Failed to get keys from database: ") +
std::string(reply->str, reply->len));
}
freeReplyObject(reply);
}
#endif // USE_REDIS

View File

@@ -0,0 +1,51 @@
/*
Minetest
Copyright (C) 2014 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser 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.
*/
#pragma once
#include "config.h"
#if USE_REDIS
#include <hiredis.h>
#include <string>
#include "database.h"
class Settings;
class Database_Redis : public MapDatabase
{
public:
Database_Redis(Settings &conf);
~Database_Redis();
void beginSave();
void endSave();
bool saveBlock(const v3s16 &pos, const std::string &data);
void loadBlock(const v3s16 &pos, std::string *block);
bool deleteBlock(const v3s16 &pos);
void listAllLoadableBlocks(std::vector<v3s16> &dst);
private:
redisContext *ctx = nullptr;
std::string hash = "";
};
#endif // USE_REDIS

View File

@@ -0,0 +1,869 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser 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.
*/
/*
SQLite format specification:
blocks:
(PK) INT id
BLOB data
*/
#include "database-sqlite3.h"
#include "log.h"
#include "filesys.h"
#include "exceptions.h"
#include "settings.h"
#include "porting.h"
#include "util/string.h"
#include "remoteplayer.h"
#include "server/player_sao.h"
#include <cassert>
// When to print messages when the database is being held locked by another process
// Note: I've seen occasional delays of over 250ms while running minetestmapper.
#define BUSY_INFO_TRESHOLD 100 // Print first informational message after 100ms.
#define BUSY_WARNING_TRESHOLD 250 // Print warning message after 250ms. Lag is increased.
#define BUSY_ERROR_TRESHOLD 1000 // Print error message after 1000ms. Significant lag.
#define BUSY_FATAL_TRESHOLD 3000 // Allow SQLITE_BUSY to be returned, which will cause a minetest crash.
#define BUSY_ERROR_INTERVAL 10000 // Safety net: report again every 10 seconds
#define SQLRES(s, r, m) \
if ((s) != (r)) { \
throw DatabaseException(std::string(m) + ": " +\
sqlite3_errmsg(m_database)); \
}
#define SQLOK(s, m) SQLRES(s, SQLITE_OK, m)
#define PREPARE_STATEMENT(name, query) \
SQLOK(sqlite3_prepare_v2(m_database, query, -1, &m_stmt_##name, NULL),\
"Failed to prepare query '" query "'")
#define SQLOK_ERRSTREAM(s, m) \
if ((s) != SQLITE_OK) { \
errorstream << (m) << ": " \
<< sqlite3_errmsg(m_database) << std::endl; \
}
#define FINALIZE_STATEMENT(statement) SQLOK_ERRSTREAM(sqlite3_finalize(statement), \
"Failed to finalize " #statement)
int Database_SQLite3::busyHandler(void *data, int count)
{
s64 &first_time = reinterpret_cast<s64 *>(data)[0];
s64 &prev_time = reinterpret_cast<s64 *>(data)[1];
s64 cur_time = porting::getTimeMs();
if (count == 0) {
first_time = cur_time;
prev_time = first_time;
} else {
while (cur_time < prev_time)
cur_time += s64(1)<<32;
}
if (cur_time - first_time < BUSY_INFO_TRESHOLD) {
; // do nothing
} else if (cur_time - first_time >= BUSY_INFO_TRESHOLD &&
prev_time - first_time < BUSY_INFO_TRESHOLD) {
infostream << "SQLite3 database has been locked for "
<< cur_time - first_time << " ms." << std::endl;
} else if (cur_time - first_time >= BUSY_WARNING_TRESHOLD &&
prev_time - first_time < BUSY_WARNING_TRESHOLD) {
warningstream << "SQLite3 database has been locked for "
<< cur_time - first_time << " ms." << std::endl;
} else if (cur_time - first_time >= BUSY_ERROR_TRESHOLD &&
prev_time - first_time < BUSY_ERROR_TRESHOLD) {
errorstream << "SQLite3 database has been locked for "
<< cur_time - first_time << " ms; this causes lag." << std::endl;
} else if (cur_time - first_time >= BUSY_FATAL_TRESHOLD &&
prev_time - first_time < BUSY_FATAL_TRESHOLD) {
errorstream << "SQLite3 database has been locked for "
<< cur_time - first_time << " ms - giving up!" << std::endl;
} else if ((cur_time - first_time) / BUSY_ERROR_INTERVAL !=
(prev_time - first_time) / BUSY_ERROR_INTERVAL) {
// Safety net: keep reporting every BUSY_ERROR_INTERVAL
errorstream << "SQLite3 database has been locked for "
<< (cur_time - first_time) / 1000 << " seconds!" << std::endl;
}
prev_time = cur_time;
// Make sqlite transaction fail if delay exceeds BUSY_FATAL_TRESHOLD
return cur_time - first_time < BUSY_FATAL_TRESHOLD;
}
Database_SQLite3::Database_SQLite3(const std::string &savedir, const std::string &dbname) :
m_savedir(savedir),
m_dbname(dbname)
{
}
void Database_SQLite3::beginSave()
{
verifyDatabase();
SQLRES(sqlite3_step(m_stmt_begin), SQLITE_DONE,
"Failed to start SQLite3 transaction");
sqlite3_reset(m_stmt_begin);
}
void Database_SQLite3::endSave()
{
verifyDatabase();
SQLRES(sqlite3_step(m_stmt_end), SQLITE_DONE,
"Failed to commit SQLite3 transaction");
sqlite3_reset(m_stmt_end);
}
void Database_SQLite3::openDatabase()
{
if (m_database) return;
std::string dbp = m_savedir + DIR_DELIM + m_dbname + ".sqlite";
// Open the database connection
if (!fs::CreateAllDirs(m_savedir)) {
infostream << "Database_SQLite3: Failed to create directory \""
<< m_savedir << "\"" << std::endl;
throw FileNotGoodException("Failed to create database "
"save directory");
}
bool needs_create = !fs::PathExists(dbp);
SQLOK(sqlite3_open_v2(dbp.c_str(), &m_database,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL),
std::string("Failed to open SQLite3 database file ") + dbp);
SQLOK(sqlite3_busy_handler(m_database, Database_SQLite3::busyHandler,
m_busy_handler_data), "Failed to set SQLite3 busy handler");
if (needs_create) {
createDatabase();
}
std::string query_str = std::string("PRAGMA synchronous = ")
+ itos(g_settings->getU16("sqlite_synchronous"));
SQLOK(sqlite3_exec(m_database, query_str.c_str(), NULL, NULL, NULL),
"Failed to modify sqlite3 synchronous mode");
SQLOK(sqlite3_exec(m_database, "PRAGMA foreign_keys = ON", NULL, NULL, NULL),
"Failed to enable sqlite3 foreign key support");
}
void Database_SQLite3::verifyDatabase()
{
if (m_initialized) return;
openDatabase();
PREPARE_STATEMENT(begin, "BEGIN;");
PREPARE_STATEMENT(end, "COMMIT;");
initStatements();
m_initialized = true;
}
Database_SQLite3::~Database_SQLite3()
{
FINALIZE_STATEMENT(m_stmt_begin)
FINALIZE_STATEMENT(m_stmt_end)
SQLOK_ERRSTREAM(sqlite3_close(m_database), "Failed to close database");
}
/*
* Map database
*/
MapDatabaseSQLite3::MapDatabaseSQLite3(const std::string &savedir):
Database_SQLite3(savedir, "map"),
MapDatabase()
{
}
MapDatabaseSQLite3::~MapDatabaseSQLite3()
{
FINALIZE_STATEMENT(m_stmt_read)
FINALIZE_STATEMENT(m_stmt_write)
FINALIZE_STATEMENT(m_stmt_list)
FINALIZE_STATEMENT(m_stmt_delete)
}
void MapDatabaseSQLite3::createDatabase()
{
assert(m_database); // Pre-condition
SQLOK(sqlite3_exec(m_database,
"CREATE TABLE IF NOT EXISTS `blocks` (\n"
" `pos` INT PRIMARY KEY,\n"
" `data` BLOB\n"
");\n",
NULL, NULL, NULL),
"Failed to create database table");
}
void MapDatabaseSQLite3::initStatements()
{
PREPARE_STATEMENT(read, "SELECT `data` FROM `blocks` WHERE `pos` = ? LIMIT 1");
PREPARE_STATEMENT(write, "REPLACE INTO `blocks` (`pos`, `data`) VALUES (?, ?)");
PREPARE_STATEMENT(delete, "DELETE FROM `blocks` WHERE `pos` = ?");
PREPARE_STATEMENT(list, "SELECT `pos` FROM `blocks`");
verbosestream << "ServerMap: SQLite3 database opened." << std::endl;
}
inline void MapDatabaseSQLite3::bindPos(sqlite3_stmt *stmt, const v3s16 &pos, int index)
{
SQLOK(sqlite3_bind_int64(stmt, index, getBlockAsInteger(pos)),
"Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__));
}
bool MapDatabaseSQLite3::deleteBlock(const v3s16 &pos)
{
verifyDatabase();
bindPos(m_stmt_delete, pos);
bool good = sqlite3_step(m_stmt_delete) == SQLITE_DONE;
sqlite3_reset(m_stmt_delete);
if (!good) {
warningstream << "deleteBlock: Block failed to delete "
<< PP(pos) << ": " << sqlite3_errmsg(m_database) << std::endl;
}
return good;
}
bool MapDatabaseSQLite3::saveBlock(const v3s16 &pos, const std::string &data)
{
verifyDatabase();
bindPos(m_stmt_write, pos);
SQLOK(sqlite3_bind_blob(m_stmt_write, 2, data.data(), data.size(), NULL),
"Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__));
SQLRES(sqlite3_step(m_stmt_write), SQLITE_DONE, "Failed to save block")
sqlite3_reset(m_stmt_write);
return true;
}
void MapDatabaseSQLite3::loadBlock(const v3s16 &pos, std::string *block)
{
verifyDatabase();
bindPos(m_stmt_read, pos);
if (sqlite3_step(m_stmt_read) != SQLITE_ROW) {
sqlite3_reset(m_stmt_read);
return;
}
const char *data = (const char *) sqlite3_column_blob(m_stmt_read, 0);
size_t len = sqlite3_column_bytes(m_stmt_read, 0);
if (data)
block->assign(data, len);
else
block->clear();
sqlite3_step(m_stmt_read);
// We should never get more than 1 row, so ok to reset
sqlite3_reset(m_stmt_read);
}
void MapDatabaseSQLite3::listAllLoadableBlocks(std::vector<v3s16> &dst)
{
verifyDatabase();
while (sqlite3_step(m_stmt_list) == SQLITE_ROW)
dst.push_back(getIntegerAsBlock(sqlite3_column_int64(m_stmt_list, 0)));
sqlite3_reset(m_stmt_list);
}
/*
* Player Database
*/
PlayerDatabaseSQLite3::PlayerDatabaseSQLite3(const std::string &savedir):
Database_SQLite3(savedir, "players"),
PlayerDatabase()
{
}
PlayerDatabaseSQLite3::~PlayerDatabaseSQLite3()
{
FINALIZE_STATEMENT(m_stmt_player_load)
FINALIZE_STATEMENT(m_stmt_player_add)
FINALIZE_STATEMENT(m_stmt_player_update)
FINALIZE_STATEMENT(m_stmt_player_remove)
FINALIZE_STATEMENT(m_stmt_player_list)
FINALIZE_STATEMENT(m_stmt_player_add_inventory)
FINALIZE_STATEMENT(m_stmt_player_add_inventory_items)
FINALIZE_STATEMENT(m_stmt_player_remove_inventory)
FINALIZE_STATEMENT(m_stmt_player_remove_inventory_items)
FINALIZE_STATEMENT(m_stmt_player_load_inventory)
FINALIZE_STATEMENT(m_stmt_player_load_inventory_items)
FINALIZE_STATEMENT(m_stmt_player_metadata_load)
FINALIZE_STATEMENT(m_stmt_player_metadata_add)
FINALIZE_STATEMENT(m_stmt_player_metadata_remove)
};
void PlayerDatabaseSQLite3::createDatabase()
{
assert(m_database); // Pre-condition
SQLOK(sqlite3_exec(m_database,
"CREATE TABLE IF NOT EXISTS `player` ("
"`name` VARCHAR(50) NOT NULL,"
"`pitch` NUMERIC(11, 4) NOT NULL,"
"`yaw` NUMERIC(11, 4) NOT NULL,"
"`posX` NUMERIC(11, 4) NOT NULL,"
"`posY` NUMERIC(11, 4) NOT NULL,"
"`posZ` NUMERIC(11, 4) NOT NULL,"
"`hp` INT NOT NULL,"
"`breath` INT NOT NULL,"
"`creation_date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,"
"`modification_date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,"
"PRIMARY KEY (`name`));",
NULL, NULL, NULL),
"Failed to create player table");
SQLOK(sqlite3_exec(m_database,
"CREATE TABLE IF NOT EXISTS `player_metadata` ("
" `player` VARCHAR(50) NOT NULL,"
" `metadata` VARCHAR(256) NOT NULL,"
" `value` TEXT,"
" PRIMARY KEY(`player`, `metadata`),"
" FOREIGN KEY (`player`) REFERENCES player (`name`) ON DELETE CASCADE );",
NULL, NULL, NULL),
"Failed to create player metadata table");
SQLOK(sqlite3_exec(m_database,
"CREATE TABLE IF NOT EXISTS `player_inventories` ("
" `player` VARCHAR(50) NOT NULL,"
" `inv_id` INT NOT NULL,"
" `inv_width` INT NOT NULL,"
" `inv_name` TEXT NOT NULL DEFAULT '',"
" `inv_size` INT NOT NULL,"
" PRIMARY KEY(player, inv_id),"
" FOREIGN KEY (`player`) REFERENCES player (`name`) ON DELETE CASCADE );",
NULL, NULL, NULL),
"Failed to create player inventory table");
SQLOK(sqlite3_exec(m_database,
"CREATE TABLE `player_inventory_items` ("
" `player` VARCHAR(50) NOT NULL,"
" `inv_id` INT NOT NULL,"
" `slot_id` INT NOT NULL,"
" `item` TEXT NOT NULL DEFAULT '',"
" PRIMARY KEY(player, inv_id, slot_id),"
" FOREIGN KEY (`player`) REFERENCES player (`name`) ON DELETE CASCADE );",
NULL, NULL, NULL),
"Failed to create player inventory items table");
}
void PlayerDatabaseSQLite3::initStatements()
{
PREPARE_STATEMENT(player_load, "SELECT `pitch`, `yaw`, `posX`, `posY`, `posZ`, `hp`, "
"`breath`"
"FROM `player` WHERE `name` = ?")
PREPARE_STATEMENT(player_add, "INSERT INTO `player` (`name`, `pitch`, `yaw`, `posX`, "
"`posY`, `posZ`, `hp`, `breath`) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")
PREPARE_STATEMENT(player_update, "UPDATE `player` SET `pitch` = ?, `yaw` = ?, "
"`posX` = ?, `posY` = ?, `posZ` = ?, `hp` = ?, `breath` = ?, "
"`modification_date` = CURRENT_TIMESTAMP WHERE `name` = ?")
PREPARE_STATEMENT(player_remove, "DELETE FROM `player` WHERE `name` = ?")
PREPARE_STATEMENT(player_list, "SELECT `name` FROM `player`")
PREPARE_STATEMENT(player_add_inventory, "INSERT INTO `player_inventories` "
"(`player`, `inv_id`, `inv_width`, `inv_name`, `inv_size`) VALUES (?, ?, ?, ?, ?)")
PREPARE_STATEMENT(player_add_inventory_items, "INSERT INTO `player_inventory_items` "
"(`player`, `inv_id`, `slot_id`, `item`) VALUES (?, ?, ?, ?)")
PREPARE_STATEMENT(player_remove_inventory, "DELETE FROM `player_inventories` "
"WHERE `player` = ?")
PREPARE_STATEMENT(player_remove_inventory_items, "DELETE FROM `player_inventory_items` "
"WHERE `player` = ?")
PREPARE_STATEMENT(player_load_inventory, "SELECT `inv_id`, `inv_width`, `inv_name`, "
"`inv_size` FROM `player_inventories` WHERE `player` = ? ORDER BY inv_id")
PREPARE_STATEMENT(player_load_inventory_items, "SELECT `slot_id`, `item` "
"FROM `player_inventory_items` WHERE `player` = ? AND `inv_id` = ?")
PREPARE_STATEMENT(player_metadata_load, "SELECT `metadata`, `value` FROM "
"`player_metadata` WHERE `player` = ?")
PREPARE_STATEMENT(player_metadata_add, "INSERT INTO `player_metadata` "
"(`player`, `metadata`, `value`) VALUES (?, ?, ?)")
PREPARE_STATEMENT(player_metadata_remove, "DELETE FROM `player_metadata` "
"WHERE `player` = ?")
verbosestream << "ServerEnvironment: SQLite3 database opened (players)." << std::endl;
}
bool PlayerDatabaseSQLite3::playerDataExists(const std::string &name)
{
verifyDatabase();
str_to_sqlite(m_stmt_player_load, 1, name);
bool res = (sqlite3_step(m_stmt_player_load) == SQLITE_ROW);
sqlite3_reset(m_stmt_player_load);
return res;
}
void PlayerDatabaseSQLite3::savePlayer(RemotePlayer *player)
{
PlayerSAO* sao = player->getPlayerSAO();
sanity_check(sao);
const v3f &pos = sao->getBasePosition();
// Begin save in brace is mandatory
if (!playerDataExists(player->getName())) {
beginSave();
str_to_sqlite(m_stmt_player_add, 1, player->getName());
double_to_sqlite(m_stmt_player_add, 2, sao->getLookPitch());
double_to_sqlite(m_stmt_player_add, 3, sao->getRotation().Y);
double_to_sqlite(m_stmt_player_add, 4, pos.X);
double_to_sqlite(m_stmt_player_add, 5, pos.Y);
double_to_sqlite(m_stmt_player_add, 6, pos.Z);
int64_to_sqlite(m_stmt_player_add, 7, sao->getHP());
int64_to_sqlite(m_stmt_player_add, 8, sao->getBreath());
sqlite3_vrfy(sqlite3_step(m_stmt_player_add), SQLITE_DONE);
sqlite3_reset(m_stmt_player_add);
} else {
beginSave();
double_to_sqlite(m_stmt_player_update, 1, sao->getLookPitch());
double_to_sqlite(m_stmt_player_update, 2, sao->getRotation().Y);
double_to_sqlite(m_stmt_player_update, 3, pos.X);
double_to_sqlite(m_stmt_player_update, 4, pos.Y);
double_to_sqlite(m_stmt_player_update, 5, pos.Z);
int64_to_sqlite(m_stmt_player_update, 6, sao->getHP());
int64_to_sqlite(m_stmt_player_update, 7, sao->getBreath());
str_to_sqlite(m_stmt_player_update, 8, player->getName());
sqlite3_vrfy(sqlite3_step(m_stmt_player_update), SQLITE_DONE);
sqlite3_reset(m_stmt_player_update);
}
// Write player inventories
str_to_sqlite(m_stmt_player_remove_inventory, 1, player->getName());
sqlite3_vrfy(sqlite3_step(m_stmt_player_remove_inventory), SQLITE_DONE);
sqlite3_reset(m_stmt_player_remove_inventory);
str_to_sqlite(m_stmt_player_remove_inventory_items, 1, player->getName());
sqlite3_vrfy(sqlite3_step(m_stmt_player_remove_inventory_items), SQLITE_DONE);
sqlite3_reset(m_stmt_player_remove_inventory_items);
const auto &inventory_lists = sao->getInventory()->getLists();
std::ostringstream oss;
for (u16 i = 0; i < inventory_lists.size(); i++) {
const InventoryList *list = inventory_lists[i];
str_to_sqlite(m_stmt_player_add_inventory, 1, player->getName());
int_to_sqlite(m_stmt_player_add_inventory, 2, i);
int_to_sqlite(m_stmt_player_add_inventory, 3, list->getWidth());
str_to_sqlite(m_stmt_player_add_inventory, 4, list->getName());
int_to_sqlite(m_stmt_player_add_inventory, 5, list->getSize());
sqlite3_vrfy(sqlite3_step(m_stmt_player_add_inventory), SQLITE_DONE);
sqlite3_reset(m_stmt_player_add_inventory);
for (u32 j = 0; j < list->getSize(); j++) {
oss.str("");
oss.clear();
list->getItem(j).serialize(oss);
std::string itemStr = oss.str();
str_to_sqlite(m_stmt_player_add_inventory_items, 1, player->getName());
int_to_sqlite(m_stmt_player_add_inventory_items, 2, i);
int_to_sqlite(m_stmt_player_add_inventory_items, 3, j);
str_to_sqlite(m_stmt_player_add_inventory_items, 4, itemStr);
sqlite3_vrfy(sqlite3_step(m_stmt_player_add_inventory_items), SQLITE_DONE);
sqlite3_reset(m_stmt_player_add_inventory_items);
}
}
str_to_sqlite(m_stmt_player_metadata_remove, 1, player->getName());
sqlite3_vrfy(sqlite3_step(m_stmt_player_metadata_remove), SQLITE_DONE);
sqlite3_reset(m_stmt_player_metadata_remove);
const StringMap &attrs = sao->getMeta().getStrings();
for (const auto &attr : attrs) {
str_to_sqlite(m_stmt_player_metadata_add, 1, player->getName());
str_to_sqlite(m_stmt_player_metadata_add, 2, attr.first);
str_to_sqlite(m_stmt_player_metadata_add, 3, attr.second);
sqlite3_vrfy(sqlite3_step(m_stmt_player_metadata_add), SQLITE_DONE);
sqlite3_reset(m_stmt_player_metadata_add);
}
endSave();
player->onSuccessfulSave();
}
bool PlayerDatabaseSQLite3::loadPlayer(RemotePlayer *player, PlayerSAO *sao)
{
verifyDatabase();
str_to_sqlite(m_stmt_player_load, 1, player->getName());
if (sqlite3_step(m_stmt_player_load) != SQLITE_ROW) {
sqlite3_reset(m_stmt_player_load);
return false;
}
sao->setLookPitch(sqlite_to_float(m_stmt_player_load, 0));
sao->setPlayerYaw(sqlite_to_float(m_stmt_player_load, 1));
sao->setBasePosition(sqlite_to_v3f(m_stmt_player_load, 2));
sao->setHPRaw((u16) MYMIN(sqlite_to_int(m_stmt_player_load, 5), U16_MAX));
sao->setBreath((u16) MYMIN(sqlite_to_int(m_stmt_player_load, 6), U16_MAX), false);
sqlite3_reset(m_stmt_player_load);
// Load inventory
str_to_sqlite(m_stmt_player_load_inventory, 1, player->getName());
while (sqlite3_step(m_stmt_player_load_inventory) == SQLITE_ROW) {
InventoryList *invList = player->inventory.addList(
sqlite_to_string(m_stmt_player_load_inventory, 2),
sqlite_to_uint(m_stmt_player_load_inventory, 3));
invList->setWidth(sqlite_to_uint(m_stmt_player_load_inventory, 1));
u32 invId = sqlite_to_uint(m_stmt_player_load_inventory, 0);
str_to_sqlite(m_stmt_player_load_inventory_items, 1, player->getName());
int_to_sqlite(m_stmt_player_load_inventory_items, 2, invId);
while (sqlite3_step(m_stmt_player_load_inventory_items) == SQLITE_ROW) {
const std::string itemStr = sqlite_to_string(m_stmt_player_load_inventory_items, 1);
if (itemStr.length() > 0) {
ItemStack stack;
stack.deSerialize(itemStr);
invList->changeItem(sqlite_to_uint(m_stmt_player_load_inventory_items, 0), stack);
}
}
sqlite3_reset(m_stmt_player_load_inventory_items);
}
sqlite3_reset(m_stmt_player_load_inventory);
str_to_sqlite(m_stmt_player_metadata_load, 1, sao->getPlayer()->getName());
while (sqlite3_step(m_stmt_player_metadata_load) == SQLITE_ROW) {
std::string attr = sqlite_to_string(m_stmt_player_metadata_load, 0);
std::string value = sqlite_to_string(m_stmt_player_metadata_load, 1);
sao->getMeta().setString(attr, value);
}
sao->getMeta().setModified(false);
sqlite3_reset(m_stmt_player_metadata_load);
return true;
}
bool PlayerDatabaseSQLite3::removePlayer(const std::string &name)
{
if (!playerDataExists(name))
return false;
str_to_sqlite(m_stmt_player_remove, 1, name);
sqlite3_vrfy(sqlite3_step(m_stmt_player_remove), SQLITE_DONE);
sqlite3_reset(m_stmt_player_remove);
return true;
}
void PlayerDatabaseSQLite3::listPlayers(std::vector<std::string> &res)
{
verifyDatabase();
while (sqlite3_step(m_stmt_player_list) == SQLITE_ROW)
res.push_back(sqlite_to_string(m_stmt_player_list, 0));
sqlite3_reset(m_stmt_player_list);
}
/*
* Auth database
*/
AuthDatabaseSQLite3::AuthDatabaseSQLite3(const std::string &savedir) :
Database_SQLite3(savedir, "auth"), AuthDatabase()
{
}
AuthDatabaseSQLite3::~AuthDatabaseSQLite3()
{
FINALIZE_STATEMENT(m_stmt_read)
FINALIZE_STATEMENT(m_stmt_write)
FINALIZE_STATEMENT(m_stmt_create)
FINALIZE_STATEMENT(m_stmt_delete)
FINALIZE_STATEMENT(m_stmt_list_names)
FINALIZE_STATEMENT(m_stmt_read_privs)
FINALIZE_STATEMENT(m_stmt_write_privs)
FINALIZE_STATEMENT(m_stmt_delete_privs)
FINALIZE_STATEMENT(m_stmt_last_insert_rowid)
}
void AuthDatabaseSQLite3::createDatabase()
{
assert(m_database); // Pre-condition
SQLOK(sqlite3_exec(m_database,
"CREATE TABLE IF NOT EXISTS `auth` ("
"`id` INTEGER PRIMARY KEY AUTOINCREMENT,"
"`name` VARCHAR(32) UNIQUE,"
"`password` VARCHAR(512),"
"`last_login` INTEGER"
");",
NULL, NULL, NULL),
"Failed to create auth table");
SQLOK(sqlite3_exec(m_database,
"CREATE TABLE IF NOT EXISTS `user_privileges` ("
"`id` INTEGER,"
"`privilege` VARCHAR(32),"
"PRIMARY KEY (id, privilege)"
"CONSTRAINT fk_id FOREIGN KEY (id) REFERENCES auth (id) ON DELETE CASCADE"
");",
NULL, NULL, NULL),
"Failed to create auth privileges table");
}
void AuthDatabaseSQLite3::initStatements()
{
PREPARE_STATEMENT(read, "SELECT id, name, password, last_login FROM auth WHERE name = ?");
PREPARE_STATEMENT(write, "UPDATE auth set name = ?, password = ?, last_login = ? WHERE id = ?");
PREPARE_STATEMENT(create, "INSERT INTO auth (name, password, last_login) VALUES (?, ?, ?)");
PREPARE_STATEMENT(delete, "DELETE FROM auth WHERE name = ?");
PREPARE_STATEMENT(list_names, "SELECT name FROM auth ORDER BY name DESC");
PREPARE_STATEMENT(read_privs, "SELECT privilege FROM user_privileges WHERE id = ?");
PREPARE_STATEMENT(write_privs, "INSERT OR IGNORE INTO user_privileges (id, privilege) VALUES (?, ?)");
PREPARE_STATEMENT(delete_privs, "DELETE FROM user_privileges WHERE id = ?");
PREPARE_STATEMENT(last_insert_rowid, "SELECT last_insert_rowid()");
}
bool AuthDatabaseSQLite3::getAuth(const std::string &name, AuthEntry &res)
{
verifyDatabase();
str_to_sqlite(m_stmt_read, 1, name);
if (sqlite3_step(m_stmt_read) != SQLITE_ROW) {
sqlite3_reset(m_stmt_read);
return false;
}
res.id = sqlite_to_uint(m_stmt_read, 0);
res.name = sqlite_to_string(m_stmt_read, 1);
res.password = sqlite_to_string(m_stmt_read, 2);
res.last_login = sqlite_to_int64(m_stmt_read, 3);
sqlite3_reset(m_stmt_read);
int64_to_sqlite(m_stmt_read_privs, 1, res.id);
while (sqlite3_step(m_stmt_read_privs) == SQLITE_ROW) {
res.privileges.emplace_back(sqlite_to_string(m_stmt_read_privs, 0));
}
sqlite3_reset(m_stmt_read_privs);
return true;
}
bool AuthDatabaseSQLite3::saveAuth(const AuthEntry &authEntry)
{
beginSave();
str_to_sqlite(m_stmt_write, 1, authEntry.name);
str_to_sqlite(m_stmt_write, 2, authEntry.password);
int64_to_sqlite(m_stmt_write, 3, authEntry.last_login);
int64_to_sqlite(m_stmt_write, 4, authEntry.id);
sqlite3_vrfy(sqlite3_step(m_stmt_write), SQLITE_DONE);
sqlite3_reset(m_stmt_write);
writePrivileges(authEntry);
endSave();
return true;
}
bool AuthDatabaseSQLite3::createAuth(AuthEntry &authEntry)
{
beginSave();
// id autoincrements
str_to_sqlite(m_stmt_create, 1, authEntry.name);
str_to_sqlite(m_stmt_create, 2, authEntry.password);
int64_to_sqlite(m_stmt_create, 3, authEntry.last_login);
sqlite3_vrfy(sqlite3_step(m_stmt_create), SQLITE_DONE);
sqlite3_reset(m_stmt_create);
// obtain id and write back to original authEntry
sqlite3_step(m_stmt_last_insert_rowid);
authEntry.id = sqlite_to_uint(m_stmt_last_insert_rowid, 0);
sqlite3_reset(m_stmt_last_insert_rowid);
writePrivileges(authEntry);
endSave();
return true;
}
bool AuthDatabaseSQLite3::deleteAuth(const std::string &name)
{
verifyDatabase();
str_to_sqlite(m_stmt_delete, 1, name);
sqlite3_vrfy(sqlite3_step(m_stmt_delete), SQLITE_DONE);
int changes = sqlite3_changes(m_database);
sqlite3_reset(m_stmt_delete);
// privileges deleted by foreign key on delete cascade
return changes > 0;
}
void AuthDatabaseSQLite3::listNames(std::vector<std::string> &res)
{
verifyDatabase();
while (sqlite3_step(m_stmt_list_names) == SQLITE_ROW) {
res.push_back(sqlite_to_string(m_stmt_list_names, 0));
}
sqlite3_reset(m_stmt_list_names);
}
void AuthDatabaseSQLite3::reload()
{
// noop for SQLite
}
void AuthDatabaseSQLite3::writePrivileges(const AuthEntry &authEntry)
{
int64_to_sqlite(m_stmt_delete_privs, 1, authEntry.id);
sqlite3_vrfy(sqlite3_step(m_stmt_delete_privs), SQLITE_DONE);
sqlite3_reset(m_stmt_delete_privs);
for (const std::string &privilege : authEntry.privileges) {
int64_to_sqlite(m_stmt_write_privs, 1, authEntry.id);
str_to_sqlite(m_stmt_write_privs, 2, privilege);
sqlite3_vrfy(sqlite3_step(m_stmt_write_privs), SQLITE_DONE);
sqlite3_reset(m_stmt_write_privs);
}
}
ModMetadataDatabaseSQLite3::ModMetadataDatabaseSQLite3(const std::string &savedir):
Database_SQLite3(savedir, "mod_storage"), ModMetadataDatabase()
{
}
ModMetadataDatabaseSQLite3::~ModMetadataDatabaseSQLite3()
{
FINALIZE_STATEMENT(m_stmt_remove)
FINALIZE_STATEMENT(m_stmt_set)
FINALIZE_STATEMENT(m_stmt_get)
}
void ModMetadataDatabaseSQLite3::createDatabase()
{
assert(m_database); // Pre-condition
SQLOK(sqlite3_exec(m_database,
"CREATE TABLE IF NOT EXISTS `entries` (\n"
" `modname` TEXT NOT NULL,\n"
" `key` BLOB NOT NULL,\n"
" `value` BLOB NOT NULL,\n"
" PRIMARY KEY (`modname`, `key`)\n"
");\n",
NULL, NULL, NULL),
"Failed to create database table");
}
void ModMetadataDatabaseSQLite3::initStatements()
{
PREPARE_STATEMENT(get, "SELECT `key`, `value` FROM `entries` WHERE `modname` = ?");
PREPARE_STATEMENT(set,
"REPLACE INTO `entries` (`modname`, `key`, `value`) VALUES (?, ?, ?)");
PREPARE_STATEMENT(remove, "DELETE FROM `entries` WHERE `modname` = ? AND `key` = ?");
}
bool ModMetadataDatabaseSQLite3::getModEntries(const std::string &modname, StringMap *storage)
{
verifyDatabase();
str_to_sqlite(m_stmt_get, 1, modname);
while (sqlite3_step(m_stmt_get) == SQLITE_ROW) {
const char *key_data = (const char *) sqlite3_column_blob(m_stmt_get, 0);
size_t key_len = sqlite3_column_bytes(m_stmt_get, 0);
const char *value_data = (const char *) sqlite3_column_blob(m_stmt_get, 1);
size_t value_len = sqlite3_column_bytes(m_stmt_get, 1);
(*storage)[std::string(key_data, key_len)] = std::string(value_data, value_len);
}
sqlite3_vrfy(sqlite3_errcode(m_database), SQLITE_DONE);
sqlite3_reset(m_stmt_get);
return true;
}
bool ModMetadataDatabaseSQLite3::setModEntry(const std::string &modname,
const std::string &key, const std::string &value)
{
verifyDatabase();
str_to_sqlite(m_stmt_set, 1, modname);
SQLOK(sqlite3_bind_blob(m_stmt_set, 2, key.data(), key.size(), NULL),
"Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__));
SQLOK(sqlite3_bind_blob(m_stmt_set, 3, value.data(), value.size(), NULL),
"Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__));
SQLRES(sqlite3_step(m_stmt_set), SQLITE_DONE, "Failed to set mod entry")
sqlite3_reset(m_stmt_set);
return true;
}
bool ModMetadataDatabaseSQLite3::removeModEntry(const std::string &modname,
const std::string &key)
{
verifyDatabase();
str_to_sqlite(m_stmt_remove, 1, modname);
SQLOK(sqlite3_bind_blob(m_stmt_remove, 2, key.data(), key.size(), NULL),
"Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__));
sqlite3_vrfy(sqlite3_step(m_stmt_remove), SQLITE_DONE);
int changes = sqlite3_changes(m_database);
sqlite3_reset(m_stmt_remove);
return changes > 0;
}
void ModMetadataDatabaseSQLite3::listMods(std::vector<std::string> *res)
{
verifyDatabase();
char *errmsg;
int status = sqlite3_exec(m_database,
"SELECT `modname` FROM `entries` GROUP BY `modname`;",
[](void *res_vp, int n_col, char **cols, char **col_names) -> int {
((decltype(res)) res_vp)->emplace_back(cols[0]);
return 0;
}, (void *) res, &errmsg);
if (status != SQLITE_OK) {
DatabaseException e(std::string("Error trying to list mods with metadata: ") + errmsg);
sqlite3_free(errmsg);
throw e;
}
}

View File

@@ -0,0 +1,259 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser 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.
*/
#pragma once
#include <cstring>
#include <string>
#include "database.h"
#include "exceptions.h"
extern "C" {
#include "sqlite3.h"
}
class Database_SQLite3 : public Database
{
public:
virtual ~Database_SQLite3();
void beginSave();
void endSave();
bool initialized() const { return m_initialized; }
protected:
Database_SQLite3(const std::string &savedir, const std::string &dbname);
// Open and initialize the database if needed
void verifyDatabase();
// Convertors
inline void str_to_sqlite(sqlite3_stmt *s, int iCol, const std::string &str) const
{
sqlite3_vrfy(sqlite3_bind_text(s, iCol, str.c_str(), str.size(), NULL));
}
inline void str_to_sqlite(sqlite3_stmt *s, int iCol, const char *str) const
{
sqlite3_vrfy(sqlite3_bind_text(s, iCol, str, strlen(str), NULL));
}
inline void int_to_sqlite(sqlite3_stmt *s, int iCol, int val) const
{
sqlite3_vrfy(sqlite3_bind_int(s, iCol, val));
}
inline void int64_to_sqlite(sqlite3_stmt *s, int iCol, s64 val) const
{
sqlite3_vrfy(sqlite3_bind_int64(s, iCol, (sqlite3_int64) val));
}
inline void double_to_sqlite(sqlite3_stmt *s, int iCol, double val) const
{
sqlite3_vrfy(sqlite3_bind_double(s, iCol, val));
}
inline std::string sqlite_to_string(sqlite3_stmt *s, int iCol)
{
const char* text = reinterpret_cast<const char*>(sqlite3_column_text(s, iCol));
return std::string(text ? text : "");
}
inline s32 sqlite_to_int(sqlite3_stmt *s, int iCol)
{
return sqlite3_column_int(s, iCol);
}
inline u32 sqlite_to_uint(sqlite3_stmt *s, int iCol)
{
return (u32) sqlite3_column_int(s, iCol);
}
inline s64 sqlite_to_int64(sqlite3_stmt *s, int iCol)
{
return (s64) sqlite3_column_int64(s, iCol);
}
inline u64 sqlite_to_uint64(sqlite3_stmt *s, int iCol)
{
return (u64) sqlite3_column_int64(s, iCol);
}
inline float sqlite_to_float(sqlite3_stmt *s, int iCol)
{
return (float) sqlite3_column_double(s, iCol);
}
inline const v3f sqlite_to_v3f(sqlite3_stmt *s, int iCol)
{
return v3f(sqlite_to_float(s, iCol), sqlite_to_float(s, iCol + 1),
sqlite_to_float(s, iCol + 2));
}
// Query verifiers helpers
inline void sqlite3_vrfy(int s, const std::string &m = "", int r = SQLITE_OK) const
{
if (s != r)
throw DatabaseException(m + ": " + sqlite3_errmsg(m_database));
}
inline void sqlite3_vrfy(const int s, const int r, const std::string &m = "") const
{
sqlite3_vrfy(s, m, r);
}
// Create the database structure
virtual void createDatabase() = 0;
virtual void initStatements() = 0;
sqlite3 *m_database = nullptr;
private:
// Open the database
void openDatabase();
bool m_initialized = false;
std::string m_savedir = "";
std::string m_dbname = "";
sqlite3_stmt *m_stmt_begin = nullptr;
sqlite3_stmt *m_stmt_end = nullptr;
s64 m_busy_handler_data[2];
static int busyHandler(void *data, int count);
};
class MapDatabaseSQLite3 : private Database_SQLite3, public MapDatabase
{
public:
MapDatabaseSQLite3(const std::string &savedir);
virtual ~MapDatabaseSQLite3();
bool saveBlock(const v3s16 &pos, const std::string &data);
void loadBlock(const v3s16 &pos, std::string *block);
bool deleteBlock(const v3s16 &pos);
void listAllLoadableBlocks(std::vector<v3s16> &dst);
void beginSave() { Database_SQLite3::beginSave(); }
void endSave() { Database_SQLite3::endSave(); }
protected:
virtual void createDatabase();
virtual void initStatements();
private:
void bindPos(sqlite3_stmt *stmt, const v3s16 &pos, int index = 1);
// Map
sqlite3_stmt *m_stmt_read = nullptr;
sqlite3_stmt *m_stmt_write = nullptr;
sqlite3_stmt *m_stmt_list = nullptr;
sqlite3_stmt *m_stmt_delete = nullptr;
};
class PlayerDatabaseSQLite3 : private Database_SQLite3, public PlayerDatabase
{
public:
PlayerDatabaseSQLite3(const std::string &savedir);
virtual ~PlayerDatabaseSQLite3();
void savePlayer(RemotePlayer *player);
bool loadPlayer(RemotePlayer *player, PlayerSAO *sao);
bool removePlayer(const std::string &name);
void listPlayers(std::vector<std::string> &res);
protected:
virtual void createDatabase();
virtual void initStatements();
private:
bool playerDataExists(const std::string &name);
// Players
sqlite3_stmt *m_stmt_player_load = nullptr;
sqlite3_stmt *m_stmt_player_add = nullptr;
sqlite3_stmt *m_stmt_player_update = nullptr;
sqlite3_stmt *m_stmt_player_remove = nullptr;
sqlite3_stmt *m_stmt_player_list = nullptr;
sqlite3_stmt *m_stmt_player_load_inventory = nullptr;
sqlite3_stmt *m_stmt_player_load_inventory_items = nullptr;
sqlite3_stmt *m_stmt_player_add_inventory = nullptr;
sqlite3_stmt *m_stmt_player_add_inventory_items = nullptr;
sqlite3_stmt *m_stmt_player_remove_inventory = nullptr;
sqlite3_stmt *m_stmt_player_remove_inventory_items = nullptr;
sqlite3_stmt *m_stmt_player_metadata_load = nullptr;
sqlite3_stmt *m_stmt_player_metadata_remove = nullptr;
sqlite3_stmt *m_stmt_player_metadata_add = nullptr;
};
class AuthDatabaseSQLite3 : private Database_SQLite3, public AuthDatabase
{
public:
AuthDatabaseSQLite3(const std::string &savedir);
virtual ~AuthDatabaseSQLite3();
virtual bool getAuth(const std::string &name, AuthEntry &res);
virtual bool saveAuth(const AuthEntry &authEntry);
virtual bool createAuth(AuthEntry &authEntry);
virtual bool deleteAuth(const std::string &name);
virtual void listNames(std::vector<std::string> &res);
virtual void reload();
protected:
virtual void createDatabase();
virtual void initStatements();
private:
virtual void writePrivileges(const AuthEntry &authEntry);
sqlite3_stmt *m_stmt_read = nullptr;
sqlite3_stmt *m_stmt_write = nullptr;
sqlite3_stmt *m_stmt_create = nullptr;
sqlite3_stmt *m_stmt_delete = nullptr;
sqlite3_stmt *m_stmt_list_names = nullptr;
sqlite3_stmt *m_stmt_read_privs = nullptr;
sqlite3_stmt *m_stmt_write_privs = nullptr;
sqlite3_stmt *m_stmt_delete_privs = nullptr;
sqlite3_stmt *m_stmt_last_insert_rowid = nullptr;
};
class ModMetadataDatabaseSQLite3 : private Database_SQLite3, public ModMetadataDatabase
{
public:
ModMetadataDatabaseSQLite3(const std::string &savedir);
virtual ~ModMetadataDatabaseSQLite3();
virtual bool getModEntries(const std::string &modname, StringMap *storage);
virtual bool setModEntry(const std::string &modname,
const std::string &key, const std::string &value);
virtual bool removeModEntry(const std::string &modname, const std::string &key);
virtual void listMods(std::vector<std::string> *res);
virtual void beginSave() { Database_SQLite3::beginSave(); }
virtual void endSave() { Database_SQLite3::endSave(); }
protected:
virtual void createDatabase();
virtual void initStatements();
private:
sqlite3_stmt *m_stmt_get = nullptr;
sqlite3_stmt *m_stmt_set = nullptr;
sqlite3_stmt *m_stmt_remove = nullptr;
};

69
src/database/database.cpp Normal file
View File

@@ -0,0 +1,69 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser 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.
*/
#include "database.h"
#include "irrlichttypes.h"
/****************
* Black magic! *
****************
* The position hashing is very messed up.
* It's a lot more complicated than it looks.
*/
static inline s16 unsigned_to_signed(u16 i, u16 max_positive)
{
if (i < max_positive) {
return i;
}
return i - (max_positive * 2);
}
// Modulo of a negative number does not work consistently in C
static inline s64 pythonmodulo(s64 i, s16 mod)
{
if (i >= 0) {
return i % mod;
}
return mod - ((-i) % mod);
}
s64 MapDatabase::getBlockAsInteger(const v3s16 &pos)
{
return (u64) pos.Z * 0x1000000 +
(u64) pos.Y * 0x1000 +
(u64) pos.X;
}
v3s16 MapDatabase::getIntegerAsBlock(s64 i)
{
v3s16 pos;
pos.X = unsigned_to_signed(pythonmodulo(i, 4096), 2048);
i = (i - pos.X) / 4096;
pos.Y = unsigned_to_signed(pythonmodulo(i, 4096), 2048);
i = (i - pos.Y) / 4096;
pos.Z = unsigned_to_signed(pythonmodulo(i, 4096), 2048);
return pos;
}

99
src/database/database.h Normal file
View File

@@ -0,0 +1,99 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser 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.
*/
#pragma once
#include <set>
#include <string>
#include <vector>
#include "irr_v3d.h"
#include "irrlichttypes.h"
#include "util/basic_macros.h"
#include "util/string.h"
class Database
{
public:
virtual void beginSave() = 0;
virtual void endSave() = 0;
virtual bool initialized() const { return true; }
};
class MapDatabase : public Database
{
public:
virtual ~MapDatabase() = default;
virtual bool saveBlock(const v3s16 &pos, const std::string &data) = 0;
virtual void loadBlock(const v3s16 &pos, std::string *block) = 0;
virtual bool deleteBlock(const v3s16 &pos) = 0;
static s64 getBlockAsInteger(const v3s16 &pos);
static v3s16 getIntegerAsBlock(s64 i);
virtual void listAllLoadableBlocks(std::vector<v3s16> &dst) = 0;
};
class PlayerSAO;
class RemotePlayer;
class PlayerDatabase
{
public:
virtual ~PlayerDatabase() = default;
virtual void savePlayer(RemotePlayer *player) = 0;
virtual bool loadPlayer(RemotePlayer *player, PlayerSAO *sao) = 0;
virtual bool removePlayer(const std::string &name) = 0;
virtual void listPlayers(std::vector<std::string> &res) = 0;
};
struct AuthEntry
{
u64 id;
std::string name;
std::string password;
std::vector<std::string> privileges;
s64 last_login;
};
class AuthDatabase
{
public:
virtual ~AuthDatabase() = default;
virtual bool getAuth(const std::string &name, AuthEntry &res) = 0;
virtual bool saveAuth(const AuthEntry &authEntry) = 0;
virtual bool createAuth(AuthEntry &authEntry) = 0;
virtual bool deleteAuth(const std::string &name) = 0;
virtual void listNames(std::vector<std::string> &res) = 0;
virtual void reload() = 0;
};
class ModMetadataDatabase : public Database
{
public:
virtual ~ModMetadataDatabase() = default;
virtual bool getModEntries(const std::string &modname, StringMap *storage) = 0;
virtual bool setModEntry(const std::string &modname,
const std::string &key, const std::string &value) = 0;
virtual bool removeModEntry(const std::string &modname, const std::string &key) = 0;
virtual void listMods(std::vector<std::string> *res) = 0;
};