anope

- supernets anope source code & configuration
git clone git://git.acid.vegas/anope.git
Log | Files | Refs | Archive | README

db_flatfile.cpp (9787B)

      1 /*
      2  *
      3  * (C) 2003-2022 Anope Team
      4  * Contact us at team@anope.org
      5  *
      6  * Please read COPYING and README for further details.
      7  *
      8  * Based on the original code of Epona by Lara.
      9  * Based on the original code of Services by Andy Church.
     10  */
     11 
     12 #include "module.h"
     13 
     14 #ifndef _WIN32
     15 #include <sys/wait.h>
     16 #endif
     17 
     18 class SaveData : public Serialize::Data
     19 {
     20  public:
     21 	Anope::string last;
     22 	std::fstream *fs;
     23 
     24 	SaveData() : fs(NULL) { }
     25 
     26 	std::iostream& operator[](const Anope::string &key) anope_override
     27 	{
     28 		if (key != last)
     29 		{
     30 			*fs << "\nDATA " << key << " ";
     31 			last = key;
     32 		}
     33 
     34 		return *fs;
     35 	}
     36 };
     37 
     38 class LoadData : public Serialize::Data
     39 {
     40  public:
     41 	std::fstream *fs;
     42 	unsigned int id;
     43 	std::map<Anope::string, Anope::string> data;
     44 	std::stringstream ss;
     45 	bool read;
     46 
     47 	LoadData() : fs(NULL), id(0), read(false) { }
     48 
     49 	std::iostream& operator[](const Anope::string &key) anope_override
     50 	{
     51 		if (!read)
     52 		{
     53 			for (Anope::string token; std::getline(*this->fs, token.str());)
     54 			{
     55 				if (token.find("ID ") == 0)
     56 				{
     57 					try
     58 					{
     59 						this->id = convertTo<unsigned int>(token.substr(3));
     60 					}
     61 					catch (const ConvertException &) { }
     62 
     63 					continue;
     64 				}
     65 				else if (token.find("DATA ") != 0)
     66 					break;
     67 
     68 				size_t sp = token.find(' ', 5); // Skip DATA
     69 				if (sp != Anope::string::npos)
     70 					data[token.substr(5, sp - 5)] = token.substr(sp + 1);
     71 			}
     72 
     73 			read = true;
     74 		}
     75 
     76 		ss.clear();
     77 		this->ss << this->data[key];
     78 		return this->ss;
     79 	}
     80 
     81 	std::set<Anope::string> KeySet() const anope_override
     82 	{
     83 		std::set<Anope::string> keys;
     84 		for (std::map<Anope::string, Anope::string>::const_iterator it = this->data.begin(), it_end = this->data.end(); it != it_end; ++it)
     85 			keys.insert(it->first);
     86 		return keys;
     87 	}
     88 
     89 	size_t Hash() const anope_override
     90 	{
     91 		size_t hash = 0;
     92 		for (std::map<Anope::string, Anope::string>::const_iterator it = this->data.begin(), it_end = this->data.end(); it != it_end; ++it)
     93 			if (!it->second.empty())
     94 				hash ^= Anope::hash_cs()(it->second);
     95 		return hash;
     96 	}
     97 
     98 	void Reset()
     99 	{
    100 		id = 0;
    101 		read = false;
    102 		data.clear();
    103 	}
    104 };
    105 
    106 class DBFlatFile : public Module, public Pipe
    107 {
    108 	/* Day the last backup was on */
    109 	int last_day;
    110 	/* Backup file names */
    111 	std::map<Anope::string, std::list<Anope::string> > backups;
    112 	bool loaded;
    113 
    114 	int child_pid;
    115 
    116 	void BackupDatabase()
    117 	{
    118 		tm *tm = localtime(&Anope::CurTime);
    119 
    120 		if (tm->tm_mday != last_day)
    121 		{
    122 			last_day = tm->tm_mday;
    123 
    124 			const std::vector<Anope::string> &type_order = Serialize::Type::GetTypeOrder();
    125 
    126 			std::set<Anope::string> dbs;
    127 			dbs.insert(Config->GetModule(this)->Get<const Anope::string>("database", "anope.db"));
    128 
    129 			for (unsigned i = 0; i < type_order.size(); ++i)
    130 			{
    131 				Serialize::Type *stype = Serialize::Type::Find(type_order[i]);
    132 
    133 				if (stype && stype->GetOwner())
    134 					dbs.insert("module_" + stype->GetOwner()->name + ".db");
    135 			}
    136 
    137 
    138 			for (std::set<Anope::string>::const_iterator it = dbs.begin(), it_end = dbs.end(); it != it_end; ++it)
    139 			{
    140 				const Anope::string &oldname = Anope::DataDir + "/" + *it;
    141 				Anope::string newname = Anope::DataDir + "/backups/" + *it + "-" + stringify(tm->tm_year + 1900) + Anope::printf("-%02i-", tm->tm_mon + 1) + Anope::printf("%02i", tm->tm_mday);
    142 
    143 				/* Backup already exists or no database to backup */
    144 				if (Anope::IsFile(newname) || !Anope::IsFile(oldname))
    145 					continue;
    146 
    147 				Log(LOG_DEBUG) << "db_flatfile: Attempting to rename " << *it << " to " << newname;
    148 				if (rename(oldname.c_str(), newname.c_str()))
    149 				{
    150 					Anope::string err = Anope::LastError();
    151 					Log(this) << "Unable to back up database " << *it << " (" << err << ")!";
    152 
    153 					if (!Config->GetModule(this)->Get<bool>("nobackupokay"))
    154 					{
    155 						Anope::Quitting = true;
    156 						Anope::QuitReason = "Unable to back up database " + *it + " (" + err + ")";
    157 					}
    158 
    159 					continue;
    160 				}
    161 
    162 				backups[*it].push_back(newname);
    163 
    164 				unsigned keepbackups = Config->GetModule(this)->Get<unsigned>("keepbackups");
    165 				if (keepbackups > 0 && backups[*it].size() > keepbackups)
    166 				{
    167 					unlink(backups[*it].front().c_str());
    168 					backups[*it].pop_front();
    169 				}
    170 			}
    171 		}
    172 	}
    173 
    174  public:
    175 	DBFlatFile(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, DATABASE | VENDOR), last_day(0), loaded(false), child_pid(-1)
    176 	{
    177 
    178 	}
    179 
    180 #ifndef _WIN32
    181 	void OnRestart() anope_override
    182 	{
    183 		OnShutdown();
    184 	}
    185 
    186 	void OnShutdown() anope_override
    187 	{
    188 		if (child_pid > -1)
    189 		{
    190 			Log(this) << "Waiting for child to exit...";
    191 
    192 			int status;
    193 			waitpid(child_pid, &status, 0);
    194 
    195 			Log(this) << "Done";
    196 		}
    197 	}
    198 #endif
    199 
    200 	void OnNotify() anope_override
    201 	{
    202 		char buf[512];
    203 		int i = this->Read(buf, sizeof(buf) - 1);
    204 		if (i <= 0)
    205 			return;
    206 		buf[i] = 0;
    207 
    208 		child_pid = -1;
    209 
    210 		if (!*buf)
    211 		{
    212 			Log(this) << "Finished saving databases";
    213 			return;
    214 		}
    215 
    216 		Log(this) << "Error saving databases: " << buf;
    217 
    218 		if (!Config->GetModule(this)->Get<bool>("nobackupokay"))
    219 			Anope::Quitting = true;
    220 	}
    221 
    222 	EventReturn OnLoadDatabase() anope_override
    223 	{
    224 		const std::vector<Anope::string> &type_order = Serialize::Type::GetTypeOrder();
    225 		std::set<Anope::string> tried_dbs;
    226 
    227 		const Anope::string &db_name = Anope::DataDir + "/" + Config->GetModule(this)->Get<const Anope::string>("database", "anope.db");
    228 
    229 		std::fstream fd(db_name.c_str(), std::ios_base::in | std::ios_base::binary);
    230 		if (!fd.is_open())
    231 		{
    232 			Log(this) << "Unable to open " << db_name << " for reading!";
    233 			return EVENT_STOP;
    234 		}
    235 
    236 		std::map<Anope::string, std::vector<std::streampos> > positions;
    237 
    238 		for (Anope::string buf; std::getline(fd, buf.str());)
    239 			if (buf.find("OBJECT ") == 0)
    240 				positions[buf.substr(7)].push_back(fd.tellg());
    241 
    242 		LoadData ld;
    243 		ld.fs = &fd;
    244 
    245 		for (unsigned i = 0; i < type_order.size(); ++i)
    246 		{
    247 			Serialize::Type *stype = Serialize::Type::Find(type_order[i]);
    248 			if (!stype || stype->GetOwner())
    249 				continue;
    250 
    251 			std::vector<std::streampos> &pos = positions[stype->GetName()];
    252 
    253 			for (unsigned j = 0; j < pos.size(); ++j)
    254 			{
    255 				fd.clear();
    256 				fd.seekg(pos[j]);
    257 
    258 				Serializable *obj = stype->Unserialize(NULL, ld);
    259 				if (obj != NULL)
    260 					obj->id = ld.id;
    261 				ld.Reset();
    262 			}
    263 		}
    264 
    265 		fd.close();
    266 
    267 		loaded = true;
    268 		return EVENT_STOP;
    269 	}
    270 
    271 
    272 	void OnSaveDatabase() anope_override
    273 	{
    274 		if (child_pid > -1)
    275 		{
    276 			Log(this) << "Database save is already in progress!";
    277 			return;
    278 		}
    279 
    280 		BackupDatabase();
    281 
    282 		int i = -1;
    283 #ifndef _WIN32
    284 		if (!Anope::Quitting && Config->GetModule(this)->Get<bool>("fork"))
    285 		{
    286 			i = fork();
    287 			if (i > 0)
    288 			{
    289 				child_pid = i;
    290 				return;
    291 			}
    292 			else if (i < 0)
    293 				Log(this) << "Unable to fork for database save";
    294 		}
    295 #endif
    296 
    297 		try
    298 		{
    299 			std::map<Module *, std::fstream *> databases;
    300 
    301 			/* 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 */
    302 			for (std::map<Anope::string, Serialize::Type *>::const_iterator it = Serialize::Type::GetTypes().begin(), it_end = Serialize::Type::GetTypes().end(); it != it_end; ++it)
    303 			{
    304 				Serialize::Type *s_type = it->second;
    305 
    306 				if (databases[s_type->GetOwner()])
    307 					continue;
    308 
    309 				Anope::string db_name;
    310 				if (s_type->GetOwner())
    311 					db_name = Anope::DataDir + "/module_" + s_type->GetOwner()->name + ".db";
    312 				else
    313 					db_name = Anope::DataDir + "/" + Config->GetModule(this)->Get<const Anope::string>("database", "anope.db");
    314 
    315 				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);
    316 
    317 				if (!fs->is_open())
    318 					Log(this) << "Unable to open " << db_name << " for writing";
    319 			}
    320 
    321 			SaveData data;
    322 			const std::list<Serializable *> &items = Serializable::GetItems();
    323 			for (std::list<Serializable *>::const_iterator it = items.begin(), it_end = items.end(); it != it_end; ++it)
    324 			{
    325 				Serializable *base = *it;
    326 				Serialize::Type *s_type = base->GetSerializableType();
    327 
    328 				data.fs = databases[s_type->GetOwner()];
    329 				if (!data.fs || !data.fs->is_open())
    330 					continue;
    331 
    332 				*data.fs << "OBJECT " << s_type->GetName();
    333 				if (base->id)
    334 					*data.fs << "\nID " << base->id;
    335 				base->Serialize(data);
    336 				*data.fs << "\nEND\n";
    337 			}
    338 
    339 			for (std::map<Module *, std::fstream *>::iterator it = databases.begin(), it_end = databases.end(); it != it_end; ++it)
    340 			{
    341 				std::fstream *f = it->second;
    342 				const Anope::string &db_name = Anope::DataDir + "/" + (it->first ? (it->first->name + ".db") : Config->GetModule(this)->Get<const Anope::string>("database", "anope.db"));
    343 
    344 				if (!f->is_open() || !f->good())
    345 				{
    346 					this->Write("Unable to write database " + db_name);
    347 
    348 					f->close();
    349 				}
    350 				else
    351 				{
    352 					f->close();
    353 #ifdef _WIN32
    354 					/* Windows rename() fails if the file already exists. */
    355 					remove(db_name.c_str());
    356 #endif
    357 					rename((db_name + ".tmp").c_str(), db_name.c_str());
    358 				}
    359 
    360 				delete f;
    361 			}
    362 		}
    363 		catch (...)
    364 		{
    365 			if (i)
    366 				throw;
    367 		}
    368 
    369 		if (!i)
    370 		{
    371 			this->Notify();
    372 			exit(0);
    373 		}
    374 	}
    375 
    376 	/* Load just one type. Done if a module is reloaded during runtime */
    377 	void OnSerializeTypeCreate(Serialize::Type *stype) anope_override
    378 	{
    379 		if (!loaded)
    380 			return;
    381 
    382 		Anope::string db_name;
    383 		if (stype->GetOwner())
    384 			db_name = Anope::DataDir + "/module_" + stype->GetOwner()->name + ".db";
    385 		else
    386 			db_name = Anope::DataDir + "/" + Config->GetModule(this)->Get<const Anope::string>("database", "anope.db");
    387 
    388 		std::fstream fd(db_name.c_str(), std::ios_base::in | std::ios_base::binary);
    389 		if (!fd.is_open())
    390 		{
    391 			Log(this) << "Unable to open " << db_name << " for reading!";
    392 			return;
    393 		}
    394 
    395 		LoadData ld;
    396 		ld.fs = &fd;
    397 
    398 		for (Anope::string buf; std::getline(fd, buf.str());)
    399 		{
    400 			if (buf == "OBJECT " + stype->GetName())
    401 			{
    402 				stype->Unserialize(NULL, ld);
    403 				ld.Reset();
    404 			}
    405 		}
    406 
    407 		fd.close();
    408 	}
    409 };
    410 
    411 MODULE_INIT(DBFlatFile)