]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_xline_db.cpp
Use iostream instead of C-style file operations.
[user/henk/code/inspircd.git] / src / modules / m_xline_db.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org>
5  *   Copyright (C) 2008 Robin Burchell <robin+git@viroteck.net>
6  *
7  * This file is part of InspIRCd.  InspIRCd is free software: you can
8  * redistribute it and/or modify it under the terms of the GNU General Public
9  * License as published by the Free Software Foundation, version 2.
10  *
11  * This program is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
14  * details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20
21 #include "inspircd.h"
22 #include "xline.h"
23 #include <fstream>
24
25 /* $ModConfig: <xlinedb filename="data/xline.db">
26  *  Specify the filename for the xline database here*/
27 /* $ModDesc: Keeps a dynamic log of all XLines created, and stores them in a seperate conf file (xline.db). */
28
29 class ModuleXLineDB : public Module
30 {
31         bool dirty;
32         std::string xlinedbpath;
33  public:
34         void init() CXX11_OVERRIDE
35         {
36                 /* Load the configuration
37                  * Note:
38                  *              this is on purpose not in the OnRehash() method. It would be non-trivial to change the database on-the-fly.
39                  *              Imagine a scenario where the new file already exists. Merging the current XLines with the existing database is likely a bad idea
40                  *              ...and so is discarding all current in-memory XLines for the ones in the database.
41                  */
42                 ConfigTag* Conf = ServerInstance->Config->ConfValue("xlinedb");
43                 xlinedbpath = Conf->getString("filename", DATA_PATH "/xline.db");
44
45                 // Read xlines before attaching to events
46                 ReadDatabase();
47
48                 Implementation eventlist[] = { I_OnAddLine, I_OnDelLine, I_OnExpireLine, I_OnBackgroundTimer };
49                 ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation));
50                 dirty = false;
51         }
52
53         /** Called whenever an xline is added by a local user.
54          * This method is triggered after the line is added.
55          * @param source The sender of the line or NULL for local server
56          * @param line The xline being added
57          */
58         void OnAddLine(User* source, XLine* line) CXX11_OVERRIDE
59         {
60                 dirty = true;
61         }
62
63         /** Called whenever an xline is deleted.
64          * This method is triggered after the line is deleted.
65          * @param source The user removing the line or NULL for local server
66          * @param line the line being deleted
67          */
68         void OnDelLine(User* source, XLine* line) CXX11_OVERRIDE
69         {
70                 dirty = true;
71         }
72
73         void OnExpireLine(XLine *line) CXX11_OVERRIDE
74         {
75                 dirty = true;
76         }
77
78         void OnBackgroundTimer(time_t now) CXX11_OVERRIDE
79         {
80                 if (dirty)
81                 {
82                         if (WriteDatabase())
83                                 dirty = false;
84                 }
85         }
86
87         bool WriteDatabase()
88         {
89                 /*
90                  * We need to perform an atomic write so as not to fuck things up.
91                  * So, let's write to a temporary file, flush it, then rename the file..
92                  * Technically, that means that this can block, but I have *never* seen that.
93                  *     -- w00t
94                  */
95                 ServerInstance->Logs->Log("m_xline_db", LOG_DEBUG, "xlinedb: Opening temporary database");
96                 std::string xlinenewdbpath = xlinedbpath + ".new";
97                 std::ofstream stream(xlinenewdbpath.c_str());
98                 if (!stream.is_open())
99                 {
100                         ServerInstance->Logs->Log("m_xline_db", LOG_DEBUG, "xlinedb: Cannot create database! %s (%d)", strerror(errno), errno);
101                         ServerInstance->SNO->WriteToSnoMask('a', "database: cannot create new db: %s (%d)", strerror(errno), errno);
102                         return false;
103                 }
104
105                 ServerInstance->Logs->Log("m_xline_db", LOG_DEBUG, "xlinedb: Opened. Writing..");
106
107                 /*
108                  * Now, much as I hate writing semi-unportable formats, additional
109                  * xline types may not have a conf tag, so let's just write them.
110                  * In addition, let's use a file version, so we can maintain some
111                  * semblance of backwards compatibility for reading on startup..
112                  *              -- w00t
113                  */
114                 stream << "VERSION 1" << std::endl;
115
116                 // Now, let's write.
117                 std::vector<std::string> types = ServerInstance->XLines->GetAllTypes();
118                 for (std::vector<std::string>::const_iterator it = types.begin(); it != types.end(); ++it)
119                 {
120                         XLineLookup* lookup = ServerInstance->XLines->GetAll(*it);
121                         if (!lookup)
122                                 continue; // Not possible as we just obtained the list from XLineManager
123
124                         for (LookupIter i = lookup->begin(); i != lookup->end(); ++i)
125                         {
126                                 XLine* line = i->second;
127                                 stream << "LINE " << line->type << " " << line->Displayable() << " "
128                                         << ServerInstance->Config->ServerName << " " << line->set_time << " "
129                                         << line->duration << " " << line->reason << std::endl;
130                         }
131                 }
132
133                 ServerInstance->Logs->Log("m_xline_db", LOG_DEBUG, "xlinedb: Finished writing XLines. Checking for error..");
134
135                 if (stream.fail())
136                 {
137                         ServerInstance->Logs->Log("m_xline_db", LOG_DEBUG, "xlinedb: Cannot write to new database! %s (%d)", strerror(errno), errno);
138                         ServerInstance->SNO->WriteToSnoMask('a', "database: cannot write to new db: %s (%d)", strerror(errno), errno);
139                         return false;
140                 }
141                 stream.close();
142
143 #ifdef _WIN32
144                 if (remove(xlinedbpath.c_str()))
145                 {
146                         ServerInstance->Logs->Log("m_xline_db", LOG_DEBUG, "xlinedb: Cannot remove old database! %s (%d)", strerror(errno), errno);
147                         ServerInstance->SNO->WriteToSnoMask('a', "database: cannot remove old database: %s (%d)", strerror(errno), errno);
148                         return false;
149                 }
150 #endif
151                 // Use rename to move temporary to new db - this is guarenteed not to fuck up, even in case of a crash.
152                 if (rename(xlinenewdbpath.c_str(), xlinedbpath.c_str()) < 0)
153                 {
154                         ServerInstance->Logs->Log("m_xline_db", LOG_DEBUG, "xlinedb: Cannot move new to old database! %s (%d)", strerror(errno), errno);
155                         ServerInstance->SNO->WriteToSnoMask('a', "database: cannot replace old with new db: %s (%d)", strerror(errno), errno);
156                         return false;
157                 }
158
159                 return true;
160         }
161
162         bool ReadDatabase()
163         {
164                 // If the xline database doesn't exist then we don't need to load it.
165                 if (!ServerConfig::FileExists(xlinedbpath.c_str()))
166                         return true;
167
168                 std::ifstream stream(xlinedbpath.c_str());
169                 if (!stream.is_open())
170                 {
171                         ServerInstance->Logs->Log("m_xline_db", LOG_DEBUG, "xlinedb: Cannot read database! %s (%d)", strerror(errno), errno);
172                         ServerInstance->SNO->WriteToSnoMask('a', "database: cannot read db: %s (%d)", strerror(errno), errno);
173                         return false;
174                 }
175                 
176                 std::string line;
177                 while (std::getline(stream, line))
178                 {
179                         // Inspired by the command parser. :)
180                         irc::tokenstream tokens(line);
181                         int items = 0;
182                         std::string command_p[7];
183                         std::string tmp;
184
185                         while (tokens.GetToken(tmp) && (items < 7))
186                         {
187                                 command_p[items] = tmp;
188                                 items++;
189                         }
190
191                         ServerInstance->Logs->Log("m_xline_db", LOG_DEBUG, "xlinedb: Processing %s", line.c_str());
192
193                         if (command_p[0] == "VERSION")
194                         {
195                                 if (command_p[1] == "1")
196                                 {
197                                         ServerInstance->Logs->Log("m_xline_db", LOG_DEBUG, "xlinedb: Reading db version %s", command_p[1].c_str());
198                                 }
199                                 else
200                                 {
201                                         stream.close();
202                                         ServerInstance->Logs->Log("m_xline_db", LOG_DEBUG, "xlinedb: I got database version %s - I don't understand it", command_p[1].c_str());
203                                         ServerInstance->SNO->WriteToSnoMask('a', "database: I got a database version (%s) I don't understand", command_p[1].c_str());
204                                         return false;
205                                 }
206                         }
207                         else if (command_p[0] == "LINE")
208                         {
209                                 // Mercilessly stolen from spanningtree
210                                 XLineFactory* xlf = ServerInstance->XLines->GetFactory(command_p[1]);
211
212                                 if (!xlf)
213                                 {
214                                         ServerInstance->SNO->WriteToSnoMask('a', "database: Unknown line type (%s).", command_p[1].c_str());
215                                         continue;
216                                 }
217
218                                 XLine* xl = xlf->Generate(ServerInstance->Time(), atoi(command_p[5].c_str()), command_p[3], command_p[6], command_p[2]);
219                                 xl->SetCreateTime(atoi(command_p[4].c_str()));
220
221                                 if (ServerInstance->XLines->AddLine(xl, NULL))
222                                 {
223                                         ServerInstance->SNO->WriteToSnoMask('x', "database: Added a line of type %s", command_p[1].c_str());
224                                 }
225                                 else
226                                         delete xl;
227                         }
228                 }
229                 stream.close();
230                 return true;
231         }
232
233         Version GetVersion() CXX11_OVERRIDE
234         {
235                 return Version("Keeps a dynamic log of all XLines created, and stores them in a separate conf file (xline.db).", VF_VENDOR);
236         }
237 };
238
239 MODULE_INIT(ModuleXLineDB)