db_flatfile.cpp

Go to the documentation of this file.
00001 /*
00002  * (C) 2003-2013 Anope Team
00003  * Contact us at team@anope.org
00004  *
00005  * Please read COPYING and README for further details.
00006  *
00007  * Based on the original code of Epona by Lara.
00008  * Based on the original code of Services by Andy Church.
00009  */
00010 
00011 /*************************************************************************/
00012 
00013 #include "module.h"
00014 
00015 class SaveData : public Serialize::Data
00016 {
00017  public:
00018         Anope::string last;
00019         std::fstream *fs;
00020 
00021         SaveData() : fs(NULL) { }
00022 
00023         std::iostream& operator[](const Anope::string &key) anope_override
00024         {
00025                 if (key != last)
00026                 {
00027                         *fs << "\nDATA " << key << " ";
00028                         last = key;
00029                 }
00030 
00031                 return *fs;
00032         }
00033 };
00034 
00035 class LoadData : public Serialize::Data
00036 {
00037  public:
00038         std::fstream *fs;
00039         unsigned int id;
00040         std::map<Anope::string, Anope::string> data;
00041         std::stringstream ss;
00042         bool read;
00043 
00044         LoadData() : fs(NULL), id(0), read(false) { }
00045 
00046         std::iostream& operator[](const Anope::string &key) anope_override
00047         {
00048                 if (!read)
00049                 {
00050                         for (Anope::string token; std::getline(*this->fs, token.str());)
00051                         {
00052                                 if (token.find("ID ") == 0)
00053                                 {
00054                                         try
00055                                         {
00056                                                 this->id = convertTo<unsigned int>(token.substr(3));
00057                                         }
00058                                         catch (const ConvertException &) { }
00059 
00060                                         continue;
00061                                 }
00062                                 else if (token.find("DATA ") != 0)
00063                                         break;
00064 
00065                                 size_t sp = token.find(' ', 5); // Skip DATA
00066                                 if (sp != Anope::string::npos)
00067                                         data[token.substr(5, sp - 5)] = token.substr(sp + 1);
00068                         }
00069 
00070                         read = true;
00071                 }
00072 
00073                 ss.clear();
00074                 this->ss << this->data[key];
00075                 return this->ss;
00076         }
00077 
00078         std::set<Anope::string> KeySet() const anope_override
00079         {
00080                 std::set<Anope::string> keys;
00081                 for (std::map<Anope::string, Anope::string>::const_iterator it = this->data.begin(), it_end = this->data.end(); it != it_end; ++it)
00082                         keys.insert(it->first);
00083                 return keys;
00084         }
00085 
00086         size_t Hash() const anope_override
00087         {
00088                 size_t hash = 0;
00089                 for (std::map<Anope::string, Anope::string>::const_iterator it = this->data.begin(), it_end = this->data.end(); it != it_end; ++it)
00090                         if (!it->second.empty())
00091                                 hash ^= Anope::hash_cs()(it->second);
00092                 return hash;
00093         }
00094         
00095         void Reset()
00096         {
00097                 id = 0;
00098                 read = false;
00099                 data.clear();
00100         }
00101 };
00102 
00103 class DBFlatFile : public Module, public Pipe
00104 {
00105         Anope::string database_file;
00106         /* Day the last backup was on */
00107         int last_day;
00108         /* Backup file names */
00109         std::map<Anope::string, std::list<Anope::string> > backups;
00110         bool use_fork;
00111         bool loaded;
00112 
00113         void BackupDatabase()
00114         {
00115                 tm *tm = localtime(&Anope::CurTime);
00116 
00117                 if (tm->tm_mday != last_day)
00118                 {
00119                         last_day = tm->tm_mday;
00120 
00121                         const std::vector<Anope::string> &type_order = Serialize::Type::GetTypeOrder();
00122 
00123                         std::set<Anope::string> dbs;
00124                         dbs.insert(database_file);
00125 
00126                         for (unsigned i = 0; i < type_order.size(); ++i)
00127                         {
00128                                 Serialize::Type *stype = Serialize::Type::Find(type_order[i]);
00129 
00130                                 if (stype && stype->GetOwner())
00131                                         dbs.insert("module_" + stype->GetOwner()->name + ".db");
00132                         }
00133 
00134 
00135                         for (std::set<Anope::string>::const_iterator it = dbs.begin(), it_end = dbs.end(); it != it_end; ++it)
00136                         {
00137                                 const Anope::string &oldname = Anope::DataDir + "/" + *it;
00138                                 Anope::string newname = Anope::DataDir + "/backups/" + *it + "." + stringify(tm->tm_year) + "." + stringify(tm->tm_mon) + "." + stringify(tm->tm_mday);
00139 
00140                                 /* Backup already exists or no database to backup */
00141                                 if (Anope::IsFile(newname) || !Anope::IsFile(oldname))
00142                                         continue;
00143 
00144                                 Log(LOG_DEBUG) << "db_flatfile: Attemping to rename " << *it << " to " << newname;
00145                                 if (rename(oldname.c_str(), newname.c_str()))
00146                                 {
00147                                         Log(this) << "Unable to back up database " << *it << "!";
00148 
00149                                         if (!Config->NoBackupOkay)
00150                                                 Anope::Quitting = true;
00151 
00152                                         continue;
00153                                 }
00154 
00155                                 backups[*it].push_back(newname);
00156 
00157                                 if (Config->KeepBackups > 0 && backups[*it].size() > static_cast<unsigned>(Config->KeepBackups))
00158                                 {
00159                                         unlink(backups[*it].front().c_str());
00160                                         backups[*it].pop_front();
00161                                 }
00162                         }
00163                 }
00164         }
00165 
00166  public:
00167         DBFlatFile(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, DATABASE), last_day(0), use_fork(false), loaded(false)
00168         {
00169                 this->SetAuthor("Anope");
00170 
00171                 Implementation i[] = { I_OnReload, I_OnLoadDatabase, I_OnSaveDatabase, I_OnSerializeTypeCreate };
00172                 ModuleManager::Attach(i, this, sizeof(i) / sizeof(Implementation));
00173 
00174                 OnReload();
00175         }
00176 
00177         void OnNotify() anope_override
00178         {
00179                 char buf[512];
00180                 int i = this->Read(buf, sizeof(buf) - 1);
00181                 if (i <= 0)
00182                         return;
00183                 buf[i] = 0;
00184 
00185                 if (!*buf)
00186                 {
00187                         Log(this) << "Finished saving databases";
00188                         return;
00189                 }
00190 
00191                 Log(this) << "Error saving databases: " << buf;
00192 
00193                 if (!Config->NoBackupOkay)
00194                         Anope::Quitting = true;
00195         }
00196 
00197         void OnReload() anope_override
00198         {
00199                 ConfigReader config;
00200                 database_file = config.ReadValue("db_flatfile", "database", "anope.db", 0);
00201                 use_fork = config.ReadFlag("db_flatfile", "fork", "no", 0);
00202         }
00203 
00204         EventReturn OnLoadDatabase() anope_override
00205         {
00206                 const std::vector<Anope::string> &type_order = Serialize::Type::GetTypeOrder();
00207                 std::set<Anope::string> tried_dbs;
00208 
00209                 const Anope::string &db_name = Anope::DataDir + "/" + database_file;
00210 
00211                 std::fstream fd(db_name.c_str(), std::ios_base::in);
00212                 if (!fd.is_open())
00213                 {
00214                         Log(this) << "Unable to open " << db_name << " for reading!";
00215                         return EVENT_STOP;
00216                 }
00217 
00218                 std::map<Anope::string, std::vector<std::streampos> > positions;
00219 
00220                 for (Anope::string buf; std::getline(fd, buf.str());)
00221                         if (buf.find("OBJECT ") == 0)
00222                                 positions[buf.substr(7)].push_back(fd.tellg());
00223 
00224                 LoadData ld;
00225                 ld.fs = &fd;
00226 
00227                 for (unsigned i = 0; i < type_order.size(); ++i)
00228                 {
00229                         Serialize::Type *stype = Serialize::Type::Find(type_order[i]);
00230                         if (!stype || stype->GetOwner())
00231                                 continue;
00232                                 
00233                         std::vector<std::streampos> &pos = positions[stype->GetName()];
00234 
00235                         for (unsigned j = 0; j < pos.size(); ++j)
00236                         {
00237                                 fd.clear();
00238                                 fd.seekg(pos[j]);
00239 
00240                                 Serializable *obj = stype->Unserialize(NULL, ld);
00241                                 if (obj != NULL)
00242                                         obj->id = ld.id;
00243                                 ld.Reset();
00244                         }
00245                 }
00246 
00247                 fd.close();
00248 
00249                 loaded = true;
00250                 return EVENT_STOP;
00251         }
00252 
00253 
00254         EventReturn OnSaveDatabase() anope_override
00255         {
00256                 BackupDatabase();
00257 
00258                 int i = -1;
00259 #ifndef _WIN32
00260                 if (use_fork)
00261                 {
00262                         i = fork();
00263                         if (i > 0)
00264                                 return EVENT_CONTINUE;
00265                         else if (i < 0)
00266                                 Log(this) << "Unable to fork for database save";
00267                 }
00268 #endif
00269 
00270                 try
00271                 {
00272                         std::map<Module *, std::fstream *> databases;
00273 
00274                         /* 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 */
00275                         for (std::map<Anope::string, Serialize::Type *>::const_iterator it = Serialize::Type::GetTypes().begin(), it_end = Serialize::Type::GetTypes().end(); it != it_end; ++it)
00276                         {
00277                                 Serialize::Type *s_type = it->second;
00278 
00279                                 if (databases[s_type->GetOwner()])
00280                                         continue;
00281 
00282                                 Anope::string db_name;
00283                                 if (s_type->GetOwner())
00284                                         db_name = Anope::DataDir + "/module_" + s_type->GetOwner()->name + ".db";
00285                                 else
00286                                         db_name = Anope::DataDir + "/" + database_file;
00287 
00288                                 if (Anope::IsFile(db_name))
00289                                         rename(db_name.c_str(), (db_name + ".tmp").c_str());
00290 
00291                                 std::fstream *fs = databases[s_type->GetOwner()] = new std::fstream(db_name.c_str(), std::ios_base::out | std::ios_base::trunc);
00292 
00293                                 if (!fs->is_open())
00294                                         Log(this) << "Unable to open " << db_name << " for writing";
00295                         }
00296 
00297                         SaveData data;
00298                         const std::list<Serializable *> &items = Serializable::GetItems();
00299                         for (std::list<Serializable *>::const_iterator it = items.begin(), it_end = items.end(); it != it_end; ++it)
00300                         {
00301                                 Serializable *base = *it;
00302                                 Serialize::Type *s_type = base->GetSerializableType();
00303 
00304                                 data.fs = databases[s_type->GetOwner()];
00305                                 if (!data.fs || !data.fs->is_open())
00306                                         continue;
00307 
00308                                 *data.fs << "OBJECT " << s_type->GetName();
00309                                 if (base->id)
00310                                         *data.fs << "\nID " << base->id;
00311                                 base->Serialize(data);
00312                                 *data.fs << "\nEND\n";
00313                         }
00314 
00315                         for (std::map<Module *, std::fstream *>::iterator it = databases.begin(), it_end = databases.end(); it != it_end; ++it)
00316                         {
00317                                 std::fstream *f = it->second;
00318                                 const Anope::string &db_name = Anope::DataDir + "/" + (it->first ? (it->first->name + ".db") : database_file);
00319 
00320                                 if (!f->is_open() || !f->good())
00321                                 {
00322                                         this->Write("Unable to write database " + db_name);
00323 
00324                                         f->close();
00325 
00326                                         if (Anope::IsFile((db_name + ".tmp").c_str()))
00327                                                 rename((db_name + ".tmp").c_str(), db_name.c_str());
00328                                 }
00329                                 else
00330                                 {
00331                                         f->close();
00332                                         unlink((db_name + ".tmp").c_str());
00333                                 }
00334 
00335                                 delete f;
00336                         }
00337                 }
00338                 catch (...)
00339                 {
00340                         if (i)
00341                                 throw;
00342                 }
00343 
00344                 if (!i)
00345                 {
00346                         this->Notify();
00347                         exit(0);
00348                 }
00349 
00350                 return EVENT_CONTINUE;
00351         }
00352 
00353         /* Load just one type. Done if a module is reloaded during runtime */
00354         void OnSerializeTypeCreate(Serialize::Type *stype) anope_override
00355         {
00356                 if (!loaded)
00357                         return;
00358 
00359                 Anope::string db_name;
00360                 if (stype->GetOwner())
00361                         db_name = Anope::DataDir + "/module_" + stype->GetOwner()->name + ".db";
00362                 else
00363                         db_name = Anope::DataDir + "/" + database_file;
00364 
00365                 std::fstream fd(db_name.c_str(), std::ios_base::in);
00366                 if (!fd.is_open())
00367                 {
00368                         Log(this) << "Unable to open " << db_name << " for reading!";
00369                         return;
00370                 }
00371 
00372                 LoadData ld;
00373                 ld.fs = &fd;
00374 
00375                 for (Anope::string buf; std::getline(fd, buf.str());)
00376                 {
00377                         if (buf == "OBJECT " + stype->GetName())
00378                         {
00379                                 stype->Unserialize(NULL, ld);
00380                                 ld.Reset();
00381                         }
00382                 }
00383 
00384                 fd.close();
00385         }
00386 };
00387 
00388 MODULE_INIT(DBFlatFile)
00389 
00390