From 2c6f4d7c2727772ba8631b58dc1faef5d94c54bd Mon Sep 17 00:00:00 2001 From: Sadie Powell Date: Thu, 31 Jul 2025 17:26:32 +0100 Subject: [PATCH] Make db_flatfile import-only. --- data/anope.example.conf | 49 +----- modules/database/db_flatfile.cpp | 254 +------------------------------ 2 files changed, 11 insertions(+), 292 deletions(-) diff --git a/data/anope.example.conf b/data/anope.example.conf index 7281c59b2..43245f783 100644 --- a/data/anope.example.conf +++ b/data/anope.example.conf @@ -1067,15 +1067,16 @@ mail /* * db_atheme * - * This allows importing databases from Atheme. You should load another database module as - * well as this as it can only read Atheme databases not write them. + * This allows importing databases from Atheme. You should load another database + * module like db_json as well as this as it can only read Atheme databases not + * write them. */ #module { name = "db_atheme" /* - * The database name db_atheme should use. + * The file that db_atheme will import your main database from. */ database = "atheme.db" } @@ -1083,52 +1084,18 @@ mail /* * [DEPRECATED] db_flatfile * - * Stores your database in a custom flatfile format. - * - * This was the recommended database module in 2.0 but it is now recommended - * that you use db_json instead. You can migrate your db_flatfile database to - * db_json by loading db_flatfile BEFORE db_json, sending SIGUSR1 to force a - * database write, and then unloading db_flatfile. This module will become - * import-only in a future release. + * This allows importing databases from the custom flat file format used between + * Anope 1.9.6 and 2.1.17. You should load another database module like db_json + * as well as this as it can only read db_flatfile databases not write them. */ #module { name = "db_flatfile" /* - * The database name db_flatfile should use + * The file that db_flatfile will import your main database from. */ database = "anope.db" - - /* - * Sets the number of days backups of databases are kept. If you don't give it, - * or if you set it to 0, Anope won't backup the databases. - * - * This directive is optional, but recommended. - */ - keepbackups = 7 - - /* - * Allows Anope to continue file write operations (i.e. database saving) - * even if the original file cannot be backed up. Enabling this option may - * allow Anope to continue operation under conditions where it might - * otherwise fail, such as a nearly-full disk. - * - * NOTE: Enabling this option can cause irrecoverable data loss under some - * conditions, so make CERTAIN you know what you're doing when you enable it! - * - * This directive is optional, and you are discouraged against enabling it. - */ - #nobackupokay = yes - - /* - * If enabled, services will fork a child process to save databases. - * - * This is only useful with very large databases, with hundreds - * of thousands of objects, that have a noticeable delay from - * writing databases. - */ - fork = no } /* diff --git a/modules/database/db_flatfile.cpp b/modules/database/db_flatfile.cpp index a68d80633..ef09a94c7 100644 --- a/modules/database/db_flatfile.cpp +++ b/modules/database/db_flatfile.cpp @@ -11,31 +11,6 @@ #include "module.h" -#ifndef _WIN32 -#include -#endif - -#include - -class SaveData final - : public Serialize::Data -{ -public: - Anope::string last; - std::fstream *fs = nullptr; - - std::iostream &operator[](const Anope::string &key) override - { - if (key != last) - { - *fs << "\nDATA " << key << " "; - last = key; - } - - return *fs; - } -}; - class LoadData final : public Serialize::Data { @@ -97,133 +72,14 @@ public: class DBFlatFile final : public Module - , public Pipe { - /* Day the last backup was on */ - int last_day = 0; +private: bool loaded = false; - int child_pid = -1; - - void BackupDatabase() - { - tm *tm = localtime(&Anope::CurTime); - - if (tm->tm_mday != last_day) - { - last_day = tm->tm_mday; - - std::set dbs; - dbs.insert(Config->GetModule(this).Get("database", "anope.db")); - - for (const auto &type_order : Serialize::Type::GetTypeOrder()) - { - Serialize::Type *stype = Serialize::Type::Find(type_order); - - if (stype && stype->GetOwner()) - dbs.insert("module_" + stype->GetOwner()->name + ".db"); - } - - const auto backupdir = Anope::ExpandData("backups"); - for (const auto &db : dbs) - { - const auto oldname = Anope::ExpandData(db); - const auto basename = Anope::Expand(backupdir, db + "-"); - const auto newname = Anope::printf("%s%04i-%02i-%02i", basename.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); - - /* Backup already exists or no database to backup */ - if (Anope::IsFile(newname) || !Anope::IsFile(oldname)) - continue; - - Log(LOG_DEBUG) << "db_flatfile: Attempting to rename " << db << " to " << newname; - if (rename(oldname.c_str(), newname.c_str())) - { - Anope::string err = Anope::LastError(); - Log(this) << "Unable to back up database " << db << " (" << err << ")!"; - - if (!Config->GetModule(this).Get("nobackupokay")) - { - Anope::Quitting = true; - Anope::QuitReason = "Unable to back up database " + db + " (" + err + ")"; - } - - continue; - } - - const auto keepbackups = Config->GetModule(this).Get("keepbackups", "7"); - if (!keepbackups) - continue; - - std::error_code ec; - std::set old_backups; - for (const auto &entry : std::filesystem::directory_iterator(backupdir.str(), ec)) - { - Anope::string entryname = entry.path().string(); - if (entryname.compare(0, basename.length(), basename) != 0) - continue; - - old_backups.insert(entryname); - if (old_backups.size() <= keepbackups) - continue; - - Log(LOG_DEBUG) << "Deleting expired backup " << *old_backups.begin(); - if (!std::filesystem::remove(old_backups.begin()->str(), ec)) - { - Log(this) << "Failed to delete expired backup " << *old_backups.begin() << ": " << ec.message(); - continue; - } - old_backups.erase(old_backups.begin()); - } - } - } - } - public: - DBFlatFile(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, DATABASE | VENDOR) + DBFlatFile(const Anope::string &modname, const Anope::string &creator) + : Module(modname, creator, DATABASE | VENDOR) { - - } - -#ifndef _WIN32 - void OnRestart() override - { - OnShutdown(); - } - - void OnShutdown() override - { - if (child_pid > -1) - { - Log(this) << "Waiting for child to exit..."; - - int status; - waitpid(child_pid, &status, 0); - - Log(this) << "Done"; - } - } -#endif - - void OnNotify() override - { - char buf[512]; - int i = this->Read(buf, sizeof(buf) - 1); - if (i <= 0) - return; - buf[i] = 0; - - child_pid = -1; - - if (!*buf) - { - Log(this) << "Finished saving databases"; - return; - } - - Log(this) << "Error saving databases: " << buf; - - if (!Config->GetModule(this).Get("nobackupokay")) - Anope::Quitting = true; } EventReturn OnLoadDatabase() override @@ -270,110 +126,6 @@ public: return EVENT_STOP; } - - void OnSaveDatabase() override - { - if (child_pid > -1) - { - Log(this) << "Database save is already in progress!"; - return; - } - - BackupDatabase(); - - int i = -1; -#ifndef _WIN32 - if (!Anope::Quitting && Config->GetModule(this).Get("fork")) - { - i = fork(); - if (i > 0) - { - child_pid = i; - return; - } - else if (i < 0) - Log(this) << "Unable to fork for database save"; - } -#endif - - try - { - std::map databases; - - /* First open the databases of all of the registered types. This way, if we have a type with 0 objects, that database will be properly cleared */ - for (const auto &[_, s_type] : Serialize::Type::GetTypes()) - { - if (databases[s_type->GetOwner()]) - continue; - - Anope::string db_name; - if (s_type->GetOwner()) - db_name = Anope::ExpandData("module_" + s_type->GetOwner()->name + ".db"); - else - db_name = Anope::ExpandData(Config->GetModule(this).Get("database", "anope.db")); - - std::fstream *fs = databases[s_type->GetOwner()] = new std::fstream((db_name + ".tmp").c_str(), std::ios_base::out | std::ios_base::trunc | std::ios_base::binary); - - if (!fs->is_open()) - Log(this) << "Unable to open " << db_name << " for writing"; - } - - SaveData data; - const std::list &items = Serializable::GetItems(); - for (auto *base : items) - { - Serialize::Type *s_type = base->GetSerializableType(); - if (!s_type) - continue; - - data.fs = databases[s_type->GetOwner()]; - if (!data.fs || !data.fs->is_open()) - continue; - - *data.fs << "OBJECT " << s_type->GetName(); - if (base->id) - *data.fs << "\nID " << base->id; - s_type->Serialize(base, data); - *data.fs << "\nEND\n"; - } - - for (auto &[mod, f] : databases) - { - const auto db_name = Anope::ExpandData((mod ? (mod->name + ".db") : Config->GetModule(this).Get("database", "anope.db"))); - - if (!f->is_open() || !f->good()) - { - this->Write("Unable to write database " + db_name); - - f->close(); - } - else - { - f->close(); -#ifdef _WIN32 - /* Windows rename() fails if the file already exists. */ - remove(db_name.c_str()); -#endif - rename((db_name + ".tmp").c_str(), db_name.c_str()); - } - - delete f; - } - } - catch (...) - { - if (i) - throw; - } - - if (!i) - { - this->Notify(); - exit(0); - } - } - - /* Load just one type. Done if a module is reloaded during runtime */ void OnSerializeTypeCreate(Serialize::Type *stype) override { if (!loaded)