]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_xline_db.cpp
Fix problem #1 by setting a lock on writes when we're reading. This means no more...
[user/henk/code/inspircd.git] / src / modules / m_xline_db.cpp
1 /*       +------------------------------------+
2  *       | Inspire Internet Relay Chat Daemon |
3  *       +------------------------------------+
4  *
5  *  InspIRCd: (C) 2002-2007 InspIRCd Development Team
6  * See: http://www.inspircd.org/wiki/index.php/Credits
7  *
8  * This program is free but copyrighted software; see
9  *            the file COPYING for details.
10  *
11  * ---------------------------------------------------
12  */
13
14 #include "inspircd.h"
15 #include "xline.h"
16
17 /* $ModDesc: Keeps a dynamic log of all XLines created, and stores them in a seperate conf file (xline.db). */
18
19 class ModuleXLineDB : public Module
20 {
21         std::vector<XLine *> xlines;
22         bool reading_db;                        // If this is true, addlines are as a result of db reading, so don't bother flushing the db to disk.
23                                                 // DO REMEMBER TO SET IT, otherwise it's annoying :P
24  public:
25         ModuleXLineDB(InspIRCd* Me) : Module(Me)
26         {
27                 Implementation eventlist[] = { I_OnAddLine, I_OnDelLine };
28                 ServerInstance->Modules->Attach(eventlist, this, 2);
29
30                 reading_db = true;
31                 ReadDatabase();
32                 reading_db = false;
33         }
34
35         virtual ~ModuleXLineDB()
36         {
37         }
38
39         /** Called whenever an xline is added by a local user.
40          * This method is triggered after the line is added.
41          * @param source The sender of the line or NULL for local server
42          * @param line The xline being added
43          */
44         void OnAddLine(User* source, XLine* line)
45         {
46                 xlines.push_back(line);
47
48                 for (std::vector<XLine *>::iterator i = xlines.begin(); i != xlines.end(); i++)
49                 {
50                         line = (*i);
51                         ServerInstance->WriteOpers("%s %s %s %lu %lu :%s", line->type.c_str(), line->Displayable(),
52 ServerInstance->Config->ServerName, line->set_time, line->duration, line->reason);
53                 }
54
55                 if (!reading_db)
56                 {
57                         WriteDatabase();
58                 }
59         }
60
61         /** Called whenever an xline is deleted.
62          * This method is triggered after the line is deleted.
63          * @param source The user removing the line or NULL for local server
64          * @param line the line being deleted
65          */
66         void OnDelLine(User* source, XLine* line)
67         {
68                 for (std::vector<XLine *>::iterator i = xlines.begin(); i != xlines.end(); i++)
69                 {
70                         if ((*i) == line)
71                         {
72                                 xlines.erase(i);
73                                 break;
74                         }
75                 }
76
77                 WriteDatabase();
78         }
79
80         bool WriteDatabase()
81         {
82                 FILE *f;
83
84                 /*
85                  * We need to perform an atomic write so as not to fuck things up.
86                  * So, let's write to a temporary file, flush and sync the FD, then rename the file..
87                  * Technically, that means that this can block, but I have *never* seen that.
88                  *              -- w00t
89                  */
90                 ServerInstance->Log(DEBUG, "xlinedb: Opening temporary database");
91                 f = fopen("xline.db.new", "w");
92                 if (!f)
93                 {
94                         ServerInstance->Log(DEBUG, "xlinedb: Cannot create database! %s (%d)", strerror(errno), errno);
95                         ServerInstance->SNO->WriteToSnoMask('x', "database: cannot create new db: %s (%d)", strerror(errno), errno);
96                         return false;
97                 }
98
99                 ServerInstance->Log(DEBUG, "xlinedb: Opened. Writing..");
100
101                 /*
102                  * Now, much as I hate writing semi-unportable formats, additional
103                  * xline types may not have a conf tag, so let's just write them.
104                  * In addition, let's use a file version, so we can maintain some
105                  * semblance of backwards compatibility for reading on startup..
106                  *              -- w00t
107                  */
108                 fprintf(f, "VERSION 1\n");
109
110                 // Now, let's write.
111                 XLine *line;
112                 for (std::vector<XLine *>::iterator i = xlines.begin(); i != xlines.end(); i++)
113                 {
114                         line = (*i);
115                         fprintf(f, "LINE %s %s %s %lu %lu :%s\n", line->type.c_str(), line->Displayable(),
116                                 ServerInstance->Config->ServerName, line->set_time, line->duration, line->reason);
117                 }
118
119                 ServerInstance->Log(DEBUG, "xlinedb: Finished writing XLines. Checking for error..");
120
121                 int write_error = 0;
122                 write_error = ferror(f);
123                 write_error |= fclose(f);
124                 if (write_error)
125                 {
126                         ServerInstance->Log(DEBUG, "xlinedb: Cannot write to new database! %s (%d)", strerror(errno), errno);
127                         ServerInstance->SNO->WriteToSnoMask('x', "database: cannot write to new db: %s (%d)", strerror(errno), errno);
128                         return false;
129                 }
130
131                 // Use rename to move temporary to new db - this is guarenteed not to fuck up, even in case of a crash.
132                 if (rename("xline.db.new", "xline.db") < 0)
133                 {
134                         ServerInstance->Log(DEBUG, "xlinedb: Cannot move new to old database! %s (%d)", strerror(errno), errno);
135                         ServerInstance->SNO->WriteToSnoMask('x', "database: cannot replace old with new db: %s (%d)", strerror(errno), errno);
136                         return false;
137                 }
138
139                 return true;
140         }
141
142         bool ReadDatabase()
143         {
144                 FILE *f;
145                 char linebuf[MAXBUF];
146                 unsigned int lineno = 0;
147
148                 f = fopen("xline.db", "r");
149                 if (!f)
150                 {
151                         if (errno == ENOENT)
152                         {
153                                 /* xline.db doesn't exist, fake good return value (we don't care about this) */
154                                 return true;
155                         }
156                         else
157                         {
158                                 /* this might be slightly more problematic. */
159                                 ServerInstance->Log(DEBUG, "xlinedb: Cannot read database! %s (%d)", strerror(errno), errno);
160                                 ServerInstance->SNO->WriteToSnoMask('x', "database: cannot read db: %s (%d)", strerror(errno), errno);
161                                 return false;
162                         }
163                 }
164
165                 while (fgets(linebuf, MAXBUF, f))
166                 {
167                         char *c = linebuf;
168
169                         while (c && *c)
170                         {
171                                 if (*c == '\n')
172                                 {
173                                         *c = '\0';
174                                 }
175
176                                 c++;
177                         }
178                         // Smart man might think of initing to 1, and moving this to the bottom. Don't. We use continue in this loop.
179                         lineno++;
180
181                         // Inspired by the command parser. :)
182                         irc::tokenstream tokens(linebuf);
183                         int items = 0;
184                         std::string command_p[MAXPARAMETERS];
185                         std::string tmp;
186
187                         while (tokens.GetToken(tmp) && (items < MAXPARAMETERS))
188                         {
189                                 command_p[items] = tmp.c_str();
190                                 items++;
191                         }
192
193                         if (command_p[0] == "VERSION")
194                         {
195                                 if (command_p[1] == "1")
196                                 {
197                                         ServerInstance->Log(DEBUG, "xlinedb: Reading db version %s", command_p[1].c_str());
198                                 }
199                                 else
200                                 {
201                                         fclose(f);
202                                         ServerInstance->Log(DEBUG, "xlinedb: I got database version %s - I don't understand it", command_p[1].c_str());
203                                         ServerInstance->SNO->WriteToSnoMask('x', "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[0]);
211
212                                 if (!xlf)
213                                 {
214                                         ServerInstance->SNO->WriteToSnoMask('x', "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].c_str(), command_p[6].c_str(), command_p[2].c_str());
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                         }
226                 }
227
228                 return true;
229         }
230
231         
232
233         virtual Version GetVersion()
234         {
235                 return Version(1, 1, 0, 0, VF_VENDOR, API_VERSION);
236         }
237 };
238
239 MODULE_INIT(ModuleXLineDB)
240