diff options
| author | 2024-07-17 00:06:50 +0100 | |
|---|---|---|
| committer | 2024-07-17 00:06:50 +0100 | |
| commit | 889d521e05f2e922683d48c74eb00290e7a2f548 (patch) | |
| tree | b4c56bd4cdf0881601c7e4d6ff571e491af10bf4 /src/modules/extra/m_pgsql.cpp | |
| parent | Use std::endian from C++20 in the sha1 module. (diff) | |
| download | inspircd++-889d521e05f2e922683d48c74eb00290e7a2f548.tar.gz inspircd++-889d521e05f2e922683d48c74eb00290e7a2f548.tar.bz2 inspircd++-889d521e05f2e922683d48c74eb00290e7a2f548.zip | |
Shuffle the modules about a bit.
Diffstat (limited to 'src/modules/extra/m_pgsql.cpp')
| -rw-r--r-- | src/modules/extra/m_pgsql.cpp | 694 |
1 files changed, 0 insertions, 694 deletions
diff --git a/src/modules/extra/m_pgsql.cpp b/src/modules/extra/m_pgsql.cpp deleted file mode 100644 index 5ff50c765..000000000 --- a/src/modules/extra/m_pgsql.cpp +++ /dev/null @@ -1,694 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2015 Daniel Vassdal <shutter@canternet.org> - * Copyright (C) 2013, 2016-2017, 2019-2024 Sadie Powell <sadie@witchery.services> - * Copyright (C) 2012-2015 Attila Molnar <attilamolnar@hush.com> - * Copyright (C) 2012 Robby <robby@chatbelgie.be> - * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2008 Thomas Stagner <aquanight@gmail.com> - * Copyright (C) 2007, 2009 Dennis Friis <peavey@inspircd.org> - * Copyright (C) 2007, 2009 Craig Edwards <brain@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * Copyright (C) 2006 Oliver Lupton <om@inspircd.org> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -/// $CompilerFlags: find_compiler_flags("libpq") -/// $LinkerFlags: find_linker_flags("libpq") - -/// $PackageInfo: require_system("alpine") pcre2-dev pkgconf -/// $PackageInfo: require_system("arch") pkgconf postgresql-libs -/// $PackageInfo: require_system("darwin") libpq pkg-config -/// $PackageInfo: require_system("debian~") libpq-dev pkg-config -/// $PackageInfo: require_system("rhel~") pkg-config postgresql-devel -/// $PackageInfo: require_system("ubuntu") libpq-dev pkg-config - - -#include <libpq-fe.h> -#include <pg_config.h> - -#include "inspircd.h" -#include "modules/sql.h" -#include "utility/string.h" - -/* SQLConn rewritten by peavey to - * use EventHandler instead of - * BufferedSocket. This is much neater - * and gives total control of destroy - * and delete of resources. - */ - -/* Forward declare, so we can have the typedef neatly at the top */ -class SQLConn; -class ModulePgSQL; - -typedef insp::flat_map<std::string, SQLConn*> ConnMap; - -enum SQLstatus -{ - // The connection has died. - DEAD, - - // Connecting and wants read event. - CREAD, - - // Connecting and wants write event. - CWRITE, - - // Connected/working and wants read event. - WREAD, - - // Connected/working and wants write event. - WWRITE -}; - -class ReconnectTimer final - : public Timer -{ -private: - ModulePgSQL* mod; -public: - ReconnectTimer(ModulePgSQL* m) - : Timer(5, false) - , mod(m) - { - } - bool Tick() override; -}; - -struct QueueItem final -{ - SQL::Query* c; - std::string q; - QueueItem(SQL::Query* C, const std::string& Q) - : c(C) - , q(Q) - { - } -}; - -/** PgSQLresult is a subclass of the mostly-pure-virtual class SQLresult. - * All SQL providers must create their own subclass and define it's methods using that - * database library's data retrieval functions. The aim is to avoid a slow and inefficient process - * of converting all data to a common format before it reaches the result structure. This way - * data is passes to the module nearly as directly as if it was using the API directly itself. - */ - -class PgSQLresult final - : public SQL::Result -{ - PGresult* res; - int currentrow = 0; - int rows = 0; - std::vector<std::string> colnames; - - void getColNames() - { - colnames.resize(PQnfields(res)); - for(unsigned int i=0; i < colnames.size(); i++) - { - colnames[i] = PQfname(res, i); - } - } -public: - PgSQLresult(PGresult* result) - : res(result) - { - rows = PQntuples(res); - if (!rows) - rows = ConvToNum<int>(PQcmdTuples(res)); - } - - ~PgSQLresult() override - { - PQclear(res); - } - - int Rows() override - { - return rows; - } - - void GetCols(std::vector<std::string>& result) override - { - if (colnames.empty()) - getColNames(); - result = colnames; - } - - bool HasColumn(const std::string& column, size_t& index) override - { - if (colnames.empty()) - getColNames(); - - for (size_t i = 0; i < colnames.size(); ++i) - { - if (colnames[i] == column) - { - index = i; - return true; - } - } - return false; - } - - SQL::Field GetValue(int row, int column) - { - char* v = PQgetvalue(res, row, column); - if (!v || PQgetisnull(res, row, column)) - return std::nullopt; - - return std::string(v, PQgetlength(res, row, column)); - } - - bool GetRow(SQL::Row& result) override - { - if (currentrow >= PQntuples(res)) - return false; - int ncols = PQnfields(res); - - for(int i = 0; i < ncols; i++) - { - result.push_back(GetValue(currentrow, i)); - } - currentrow++; - - return true; - } -}; - -/** SQLConn represents one SQL session. - */ -class SQLConn final - : public SQL::Provider - , public EventHandler -{ -public: - std::shared_ptr<ConfigTag> conf; /* The <database> entry */ - std::deque<QueueItem> queue; - PGconn* sql = nullptr; /* PgSQL database connection handle */ - SQLstatus status = CWRITE; /* PgSQL database connection status */ - QueueItem qinprog; /* If there is currently a query in progress */ - - SQLConn(Module* Creator, const std::shared_ptr<ConfigTag>& tag) - : SQL::Provider(Creator, tag->getString("id")) - , conf(tag) - , qinprog(nullptr, "") - { - if (!DoConnect()) - DelayReconnect(); - } - - Cullable::Result Cull() override - { - this->SQL::Provider::Cull(); - ServerInstance->Modules.DelService(*this); - return this->EventHandler::Cull(); - } - - ~SQLConn() override - { - SQL::Error err(SQL::BAD_DBID); - if (qinprog.c) - { - qinprog.c->OnError(err); - delete qinprog.c; - } - for (const auto& item : queue) - { - SQL::Query* q = item.c; - q->OnError(err); - delete q; - } - Close(); - } - - void OnEventHandlerRead() override - { - DoEvent(); - } - - void OnEventHandlerWrite() override - { - DoEvent(); - } - - void OnEventHandlerError(int errornum) override - { - DelayReconnect(); - } - - std::string EscapeDSN(const std::string& str) - { - std::string out; - out.reserve(str.size()); - - for (std::string::const_iterator it = str.begin(); it != str.end(); ++it) - { - char chr = *it; - switch (chr) - { - case '\\': - out.append("\\\\"); - break; - - case '\'': - out.append("\\'"); - break; - - default: - out.push_back(chr); - break; - } - } - - return out; - } - - std::string GetDSN() - { - std::ostringstream conninfo("connect_timeout = '5'"); - std::string item; - - if (conf->readString("host", item)) - conninfo << " host = '" << EscapeDSN(item) << "'"; - - if (conf->readString("port", item)) - conninfo << " port = '" << EscapeDSN(item) << "'"; - - if (conf->readString("name", item)) - conninfo << " dbname = '" << EscapeDSN(item) << "'"; - - if (conf->readString("user", item)) - conninfo << " user = '" << EscapeDSN(item) << "'"; - - if (conf->readString("pass", item)) - conninfo << " password = '" << EscapeDSN(item) << "'"; - - if (conf->getBool("tls", conf->getBool("ssl", true))) - conninfo << " sslmode = 'require'"; - else - conninfo << " sslmode = 'disable'"; - - return conninfo.str(); - } - - bool HandleConnectError(const char* reason) - { - ServerInstance->Logs.Critical(MODNAME, "Could not connect to the \"{}\" database: {}", - GetId(), reason); - return false; - } - - bool DoConnect() - { - sql = PQconnectStart(GetDSN().c_str()); - if (!sql) - return HandleConnectError("PQconnectStart returned NULL"); - - if(PQstatus(sql) == CONNECTION_BAD) - return HandleConnectError("connection status is bad"); - - if(PQsetnonblocking(sql, 1) == -1) - return HandleConnectError("unable to mark fd as non-blocking"); - - /* OK, we've initialised the connection, now to get it hooked into the socket engine - * and then start polling it. - */ - SetFd(PQsocket(sql)); - if(!HasFd()) - return HandleConnectError("PQsocket returned an invalid fd"); - - if (!SocketEngine::AddFd(this, FD_WANT_NO_WRITE | FD_WANT_NO_READ)) - return HandleConnectError("could not add the pgsql socket to the socket engine"); - - /* Socket all hooked into the engine, now to tell PgSQL to start connecting */ - if (!DoPoll()) - return HandleConnectError("could not poll the connection state"); - - return true; - } - - bool DoPoll() - { - switch(PQconnectPoll(sql)) - { - case PGRES_POLLING_WRITING: - SocketEngine::ChangeEventMask(this, FD_WANT_POLL_WRITE | FD_WANT_NO_READ); - status = CWRITE; - return true; - case PGRES_POLLING_READING: - SocketEngine::ChangeEventMask(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE); - status = CREAD; - return true; - case PGRES_POLLING_FAILED: - SocketEngine::ChangeEventMask(this, FD_WANT_NO_READ | FD_WANT_NO_WRITE); - status = DEAD; - return false; - case PGRES_POLLING_OK: - SocketEngine::ChangeEventMask(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE); - status = WWRITE; - DoConnectedPoll(); - return true; - default: - return true; - } - } - - void DoConnectedPoll() - { -restart: - while (qinprog.q.empty() && !queue.empty()) - { - /* There's no query currently in progress, and there's queries in the queue. */ - DoQuery(queue.front()); - queue.pop_front(); - } - - if (PQconsumeInput(sql)) - { - if (PQisBusy(sql)) - { - /* Nothing happens here */ - } - else if (qinprog.c) - { - /* Fetch the result.. */ - PGresult* result = PQgetResult(sql); - - /* PgSQL would allow a query string to be sent which has multiple - * queries in it, this isn't portable across database backends and - * we don't want modules doing it. But just in case we make sure we - * drain any results there are and just use the last one. - * If the module devs are behaving there will only be one result. - */ - while (PGresult* temp = PQgetResult(sql)) - { - PQclear(result); - result = temp; - } - - /* ..and the result */ - PgSQLresult reply(result); - switch(PQresultStatus(result)) - { - case PGRES_EMPTY_QUERY: - case PGRES_BAD_RESPONSE: - case PGRES_FATAL_ERROR: - { - SQL::Error err(SQL::QREPLY_FAIL, PQresultErrorMessage(result)); - qinprog.c->OnError(err); - break; - } - default: - /* Other values are not errors */ - qinprog.c->OnResult(reply); - } - - delete qinprog.c; - qinprog = QueueItem(nullptr, ""); - goto restart; - } - else - { - qinprog.q.clear(); - } - } - else - { - /* I think we'll assume this means the server died...it might not, - * but I think that any error serious enough we actually get here - * deserves to reconnect [/excuse] - * Returning true so the core doesn't try and close the connection. - */ - DelayReconnect(); - } - } - - void DelayReconnect(); - - void DoEvent() - { - if((status == CREAD) || (status == CWRITE)) - { - DoPoll(); - } - else if (status == WREAD || status == WWRITE) - { - DoConnectedPoll(); - } - } - - void Submit(SQL::Query* req, const std::string& q) override - { - ServerInstance->Logs.Debug(MODNAME, "Executing PostgreSQL query: " + q); - if (qinprog.q.empty()) - { - DoQuery(QueueItem(req, q)); - } - else - { - // wait your turn. - queue.emplace_back(req, q); - } - } - - void Submit(SQL::Query* req, const std::string& q, const SQL::ParamList& p) override - { - std::string res; - unsigned int param = 0; - for (const auto chr : q) - { - if (chr != '?') - res.push_back(chr); - else - { - if (param < p.size()) - { - std::string parm = p[param++]; - std::vector<char> buffer(parm.length() * 2 + 1); - int error; - size_t escapedsize = PQescapeStringConn(sql, buffer.data(), parm.data(), parm.length(), &error); - if (error) - ServerInstance->Logs.Debug(MODNAME, "BUG: Apparently PQescapeStringConn() failed"); - res.append(buffer.data(), escapedsize); - } - } - } - Submit(req, res); - } - - void Submit(SQL::Query* req, const std::string& q, const SQL::ParamMap& p) override - { - std::string res; - for(std::string::size_type i = 0; i < q.length(); i++) - { - if (q[i] != '$') - res.push_back(q[i]); - else - { - std::string field; - i++; - while (i < q.length() && isalnum(q[i])) - field.push_back(q[i++]); - i--; - - SQL::ParamMap::const_iterator it = p.find(field); - if (it != p.end()) - { - std::string parm = it->second; - std::vector<char> buffer(parm.length() * 2 + 1); - int error; - size_t escapedsize = PQescapeStringConn(sql, buffer.data(), parm.data(), parm.length(), &error); - if (error) - ServerInstance->Logs.Debug(MODNAME, "BUG: Apparently PQescapeStringConn() failed"); - res.append(buffer.data(), escapedsize); - } - } - } - Submit(req, res); - } - - void DoQuery(const QueueItem& req) - { - if (status != WREAD && status != WWRITE) - { - // whoops, not connected... - SQL::Error err(SQL::BAD_CONN); - req.c->OnError(err); - delete req.c; - return; - } - - if(PQsendQuery(sql, req.q.c_str())) - { - qinprog = req; - } - else - { - SQL::Error err(SQL::QSEND_FAIL, PQerrorMessage(sql)); - req.c->OnError(err); - delete req.c; - } - } - - void Close() - { - status = DEAD; - - if (HasFd() && SocketEngine::HasFd(GetFd())) - SocketEngine::DelFd(this); - - if(sql) - { - PQfinish(sql); - sql = nullptr; - } - } -}; - -class ModulePgSQL final - : public Module -{ -public: - ConnMap connections; - ReconnectTimer* retimer = nullptr; - - ModulePgSQL() - : Module(VF_VENDOR, "Provides the ability for SQL modules to query a PostgreSQL database.") - { - } - - ~ModulePgSQL() override - { - delete retimer; - ClearAllConnections(); - } - - void init() override - { - int version = PQlibVersion(); - int minor = version / 100 % 100; - int revision = version % 100; - if (version >= 10'00'00) - { - // Ref: https://www.postgresql.org/docs/current/libpq-misc.html#LIBPQ-PQLIBVERSION - minor = revision; - revision = 0; - } - ServerInstance->Logs.Normal(MODNAME, "Module was compiled against libpq version {} and is running against version {}.{}.{}", - PG_VERSION, version / 10000, minor, revision); - } - - void ReadConfig(ConfigStatus& status) override - { - ReadConf(); - } - - void ReadConf() - { - ConnMap conns; - - for (const auto& [_, tag] : ServerInstance->Config->ConfTags("database")) - { - if (!insp::equalsci(tag->getString("module"), "pgsql")) - continue; - - std::string id = tag->getString("id"); - ConnMap::iterator curr = connections.find(id); - if (curr == connections.end()) - { - auto* conn = new SQLConn(this, tag); - if (conn->status != DEAD) - { - conns.emplace(id, conn); - ServerInstance->Modules.AddService(*conn); - } - // If the connection is dead it has already been queued for culling - // at the end of the main loop so we don't need to delete it here. - } - else - { - conns.insert(*curr); - connections.erase(curr); - } - } - ClearAllConnections(); - conns.swap(connections); - } - - void ClearAllConnections() - { - for (const auto& [_, conn] : connections) - { - conn->Cull(); - delete conn; - } - connections.clear(); - } - - void OnUnloadModule(Module* mod) override - { - SQL::Error err(SQL::BAD_DBID); - for (const auto& [_, conn] : connections) - { - if (conn->qinprog.c && conn->qinprog.c->creator == mod) - { - conn->qinprog.c->OnError(err); - delete conn->qinprog.c; - conn->qinprog.c = nullptr; - } - std::deque<QueueItem>::iterator j = conn->queue.begin(); - while (j != conn->queue.end()) - { - SQL::Query* q = j->c; - if (q->creator == mod) - { - q->OnError(err); - delete q; - j = conn->queue.erase(j); - } - else - j++; - } - } - } -}; - -bool ReconnectTimer::Tick() -{ - mod->retimer = nullptr; - mod->ReadConf(); - delete this; - return false; -} - -void SQLConn::DelayReconnect() -{ - status = DEAD; - ModulePgSQL* mod = (ModulePgSQL*)(Module*)creator; - - ConnMap::iterator it = mod->connections.find(conf->getString("id")); - if (it != mod->connections.end()) - mod->connections.erase(it); - ServerInstance->GlobalCulls.AddItem((EventHandler*)this); - if (!mod->retimer) - { - mod->retimer = new ReconnectTimer(mod); - ServerInstance->Timers.AddTimer(mod->retimer); - } -} - -MODULE_INIT(ModulePgSQL) |
