]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_xline_db.cpp
a64dc7071185162bd52df98002d2d196defdf0e2
[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 class ModuleXLineDB : public Module
26 {
27         bool dirty;
28         std::string xlinedbpath;
29  public:
30         void init() CXX11_OVERRIDE
31         {
32                 /* Load the configuration
33                  * Note:
34                  *              This is on purpose not changed on a rehash. It would be non-trivial to change the database on-the-fly.
35                  *              Imagine a scenario where the new file already exists. Merging the current XLines with the existing database is likely a bad idea
36                  *              ...and so is discarding all current in-memory XLines for the ones in the database.
37                  */
38                 ConfigTag* Conf = ServerInstance->Config->ConfValue("xlinedb");
39                 xlinedbpath = ServerInstance->Config->Paths.PrependData(Conf->getString("filename", "xline.db"));
40
41                 // Read xlines before attaching to events
42                 ReadDatabase();
43
44                 dirty = false;
45         }
46
47         /** Called whenever an xline is added by a local user.
48          * This method is triggered after the line is added.
49          * @param source The sender of the line or NULL for local server
50          * @param line The xline being added
51          */
52         void OnAddLine(User* source, XLine* line) CXX11_OVERRIDE
53         {
54                 if (!line->from_config)
55                         dirty = true;
56         }
57
58         /** Called whenever an xline is deleted.
59          * This method is triggered after the line is deleted.
60          * @param source The user removing the line or NULL for local server
61          * @param line the line being deleted
62          */
63         void OnDelLine(User* source, XLine* line) CXX11_OVERRIDE
64         {
65                 if (!line->from_config)
66                         dirty = true;
67         }
68
69         void OnBackgroundTimer(time_t now) CXX11_OVERRIDE
70         {
71                 if (dirty)
72                 {
73                         if (WriteDatabase())
74                                 dirty = false;
75                 }
76         }
77
78         bool WriteDatabase()
79         {
80                 /*
81                  * We need to perform an atomic write so as not to fuck things up.
82                  * So, let's write to a temporary file, flush it, then rename the file..
83                  * Technically, that means that this can block, but I have *never* seen that.
84                  *     -- w00t
85                  */
86                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Opening temporary database");
87                 std::string xlinenewdbpath = xlinedbpath + ".new";
88                 std::ofstream stream(xlinenewdbpath.c_str());
89                 if (!stream.is_open())
90                 {
91                         ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Cannot create database \"%s\"! %s (%d)", xlinenewdbpath.c_str(), strerror(errno), errno);
92                         ServerInstance->SNO->WriteToSnoMask('x', "database: cannot create new xline db \"%s\": %s (%d)", xlinenewdbpath.c_str(), strerror(errno), errno);
93                         return false;
94                 }
95
96                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Opened. Writing..");
97
98                 /*
99                  * Now, much as I hate writing semi-unportable formats, additional
100                  * xline types may not have a conf tag, so let's just write them.
101                  * In addition, let's use a file version, so we can maintain some
102                  * semblance of backwards compatibility for reading on startup..
103                  *              -- w00t
104                  */
105                 stream << "VERSION 1" << std::endl;
106
107                 // Now, let's write.
108                 std::vector<std::string> types = ServerInstance->XLines->GetAllTypes();
109                 for (std::vector<std::string>::const_iterator it = types.begin(); it != types.end(); ++it)
110                 {
111                         XLineLookup* lookup = ServerInstance->XLines->GetAll(*it);
112                         if (!lookup)
113                                 continue; // Not possible as we just obtained the list from XLineManager
114
115                         for (LookupIter i = lookup->begin(); i != lookup->end(); ++i)
116                         {
117                                 XLine* line = i->second;
118                                 if (line->from_config)
119                                         continue;
120
121                                 stream << "LINE " << line->type << " " << line->Displayable() << " "
122                                         << line->source << " " << line->set_time << " "
123                                         << line->duration << " :" << line->reason << std::endl;
124                         }
125                 }
126
127                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Finished writing XLines. Checking for error..");
128
129                 if (stream.fail())
130                 {
131                         ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Cannot write to new database \"%s\"! %s (%d)", xlinenewdbpath.c_str(), strerror(errno), errno);
132                         ServerInstance->SNO->WriteToSnoMask('x', "database: cannot write to new xline db \"%s\": %s (%d)", xlinenewdbpath.c_str(), strerror(errno), errno);
133                         return false;
134                 }
135                 stream.close();
136
137 #ifdef _WIN32
138                 remove(xlinedbpath.c_str());
139 #endif
140                 // Use rename to move temporary to new db - this is guarenteed not to fuck up, even in case of a crash.
141                 if (rename(xlinenewdbpath.c_str(), xlinedbpath.c_str()) < 0)
142                 {
143                         ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Cannot replace old database \"%s\" with new database \"%s\"! %s (%d)", xlinedbpath.c_str(), xlinenewdbpath.c_str(), strerror(errno), errno);
144                         ServerInstance->SNO->WriteToSnoMask('x', "database: cannot replace old xline db \"%s\" with new db \"%s\": %s (%d)", xlinedbpath.c_str(), xlinenewdbpath.c_str(), strerror(errno), errno);
145                         return false;
146                 }
147
148                 return true;
149         }
150
151         bool ReadDatabase()
152         {
153                 // If the xline database doesn't exist then we don't need to load it.
154                 if (!FileSystem::FileExists(xlinedbpath))
155                         return true;
156
157                 std::ifstream stream(xlinedbpath.c_str());
158                 if (!stream.is_open())
159                 {
160                         ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Cannot read database \"%s\"! %s (%d)", xlinedbpath.c_str(), strerror(errno), errno);
161                         ServerInstance->SNO->WriteToSnoMask('x', "database: cannot read xline db \"%s\": %s (%d)", xlinedbpath.c_str(), strerror(errno), errno);
162                         return false;
163                 }
164
165                 std::string line;
166                 while (std::getline(stream, line))
167                 {
168                         // Inspired by the command parser. :)
169                         irc::tokenstream tokens(line);
170                         int items = 0;
171                         std::string command_p[7];
172                         std::string tmp;
173
174                         while (tokens.GetTrailing(tmp) && (items < 7))
175                         {
176                                 command_p[items] = tmp;
177                                 items++;
178                         }
179
180                         ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Processing %s", line.c_str());
181
182                         if (command_p[0] == "VERSION")
183                         {
184                                 if (command_p[1] != "1")
185                                 {
186                                         stream.close();
187                                         ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "I got database version %s - I don't understand it", command_p[1].c_str());
188                                         ServerInstance->SNO->WriteToSnoMask('x', "database: I got a database version (%s) I don't understand", command_p[1].c_str());
189                                         return false;
190                                 }
191                         }
192                         else if (command_p[0] == "LINE")
193                         {
194                                 // Mercilessly stolen from spanningtree
195                                 XLineFactory* xlf = ServerInstance->XLines->GetFactory(command_p[1]);
196
197                                 if (!xlf)
198                                 {
199                                         ServerInstance->SNO->WriteToSnoMask('x', "database: Unknown line type (%s).", command_p[1].c_str());
200                                         continue;
201                                 }
202
203                                 XLine* xl = xlf->Generate(ServerInstance->Time(), atoi(command_p[5].c_str()), command_p[3], command_p[6], command_p[2]);
204                                 xl->SetCreateTime(atoi(command_p[4].c_str()));
205
206                                 if (ServerInstance->XLines->AddLine(xl, NULL))
207                                 {
208                                         ServerInstance->SNO->WriteToSnoMask('x', "database: Added a line of type %s", command_p[1].c_str());
209                                 }
210                                 else
211                                         delete xl;
212                         }
213                 }
214                 stream.close();
215                 return true;
216         }
217
218         Version GetVersion() CXX11_OVERRIDE
219         {
220                 return Version("Keeps a dynamic log of all XLines created, and stores them in a separate conf file (xline.db).", VF_VENDOR);
221         }
222 };
223
224 MODULE_INIT(ModuleXLineDB)