2 #include "../lib/irc_string.h"
3 #include "../lib/version.h"
7 MODULE_VERSION(QVERSION
);
9 static void cleanupdb(void *arg
);
10 static void schedulecleanup(int hooknum
, void *arg
);
12 static unsigned int cleanupdb_active
;
13 static DBModuleIdentifier q9cleanupdbid
;
16 q9cleanupdbid
= dbgetid();
18 registerhook(HOOK_CHANSERV_DBLOADED
, schedulecleanup
);
21 schedulecleanup(HOOK_CHANSERV_DBLOADED
, NULL
);
25 deleteallschedules(cleanupdb
);
26 dbfreeid(q9cleanupdbid
);
29 static void schedulecleanup(int hooknum
, void *arg
) {
30 /* run at 1am but only if we're more than 15m away from it, otherwise run tomorrow */
32 time_t t
= time(NULL
);
33 time_t next_run
= ((t
/ 86400) * 86400 + 86400) + 3600;
34 if(next_run
- t
< 900)
37 schedulerecurring(next_run
,0,86400,cleanupdb
,NULL
);
40 __attribute__ ((format (printf
, 1, 2)))
41 static void cleanuplog(char *format
, ...) {
46 vsnprintf(buf
, sizeof(buf
), format
, va
);
49 cs_log(NULL
, "CLEANUPDB %s", buf
);
50 chanservwallmessage("CLEANUPDB: %s", buf
);
53 static void cleanupdb_real(DBConn
*dbconn
, void *arg
) {
54 reguser
*vrup
, *srup
, *founder
;
55 regchanuser
*rcup
, *nrcup
;
58 time_t t
, to_age
, unused_age
, maxchan_age
, authhistory_age
;
59 int expired
= 0, unauthed
= 0, chansvaped
= 0, chansempty
= 0;
60 chanindex
*cip
, *ncip
;
63 unsigned int themarker
;
67 to_age
= t
- (CLEANUP_ACCOUNT_INACTIVE
* 3600 * 24);
68 unused_age
= t
- (CLEANUP_ACCOUNT_UNUSED
* 3600 * 24);
69 maxchan_age
= t
- (CLEANUP_CHANNEL_INACTIVE
* 3600 * 24);
70 authhistory_age
= t
- (CLEANUP_AUTHHISTORY
* 3600 * 24);
72 themarker
=nextauthnamemarker();
75 cleanuplog("No DB connection, aborting.");
79 pgres
=dbgetresult(dbconn
);
81 if (!dbquerysuccessful(pgres
)) {
82 cleanuplog("DB error, aborting.");
86 while (dbfetchrow(pgres
)) {
87 id
=strtoul(dbgetvalue(pgres
, 0), NULL
, 10);
90 anp
->marker
=themarker
;
95 cleanuplog("Phase 1 complete, starting phase 2 (regusers scan)...");
97 for (i
=0;i
<REGUSERHASHSIZE
;i
++) {
98 for (vrup
=regusernicktable
[i
]; vrup
; vrup
=srup
) {
99 srup
=vrup
->nextbyname
;
100 if (!(anp
=findauthname(vrup
->ID
)))
101 continue; /* should maybe raise hell instead */
103 /* If this user has the right marker, this means the authtracker data
104 * indicates that they have been active recently */
105 if (anp
->marker
== themarker
)
108 /* HACK: don't ever delete the last user -- prevents userids being reused */
109 if (vrup
->ID
== lastuserID
)
112 if(!anp
->nicks
&& !UHasStaffPriv(vrup
) && !UIsCleanupExempt(vrup
)) {
113 if(vrup
->lastauth
&& (vrup
->lastauth
< to_age
)) {
115 cs_log(NULL
, "CLEANUPDB inactive user %s %u", vrup
->username
, vrup
->ID
);
116 } else if(!vrup
->lastauth
&& (vrup
->created
< unused_age
)) {
118 cs_log(NULL
, "CLEANUPDB unused user %s %u", vrup
->username
, vrup
->ID
);
128 cleanuplog("Phase 2 complete, starting phase 3 (chanindex scan)...");
130 for (i
=0;i
<CHANNELHASHSIZE
;i
++) {
131 for (cip
=chantable
[i
];cip
;cip
=ncip
) {
133 if (!(rcp
=cip
->exts
[chanservext
]))
136 /* HACK: don't ever delete the last channel -- prevents channelids being reused */
137 if (rcp
->ID
== lastchannelID
)
140 /* there's a bug here... if no joins or modes are done within the threshold
141 * and someone leaves just before the cleanup then the channel will be nuked.
144 /* this is one possible soln but relies on cleanupdb being run more frequently than
147 /* slug: no longer required as we scan the entire network every 1h (cs_hourlyfunc) */
149 if(cip->channel && cs_ischannelactive(cip->channel, rcp)) {
151 if (rcp->lastcountersync < (t - COUNTERSYNCINTERVAL)) {
152 csdb_updatechannelcounters(rcp);
153 rcp->lastcountersync=t;
158 if(rcp
->lastactive
< maxchan_age
) {
159 /* don't remove channels with the original founder as an oper */
160 founder
=findreguserbyID(rcp
->founder
);
161 if(founder
&& UHasOperPriv(founder
))
164 cs_log(NULL
, "CLEANUPDB inactive channel %s", cip
->name
?cip
->name
->content
:"??");
165 cs_removechannel(rcp
, "Channel deleted due to lack of activity.");
170 /* Get rid of any dead chanlev entries */
171 for (j
=0;j
<REGCHANUSERHASHSIZE
;j
++) {
172 for (rcup
=rcp
->regusers
[j
];rcup
;rcup
=nrcup
) {
173 nrcup
=rcup
->nextbychan
;
176 cs_log(NULL
, "Removing user %s from channel %s (no flags)",rcup
->user
->username
,rcp
->index
->name
->content
);
177 csdb_deletechanuser(rcup
);
178 delreguserfromchannel(rcp
, rcup
->user
);
179 freeregchanuser(rcup
);
184 if (cs_removechannelifempty(NULL
, rcp
)) {
185 /* logged+parted by cs_removechannelifempty */
192 cleanuplog("Phase 3 complete, starting phase 4 (history database cleanup) -- runs in the background.");
194 csdb_cleanuphistories(authhistory_age
);
196 cleanuplog("Stats: %d accounts inactive for %d days, %d accounts weren't used within %d days, %d channels were inactive for %d days, %d channels empty.", expired
, CLEANUP_ACCOUNT_INACTIVE
, unauthed
, CLEANUP_ACCOUNT_UNUSED
, chansvaped
, CLEANUP_CHANNEL_INACTIVE
, chansempty
);
202 void cs_cleanupdb(nick
*np
) {
206 static void cleanupdb(void *arg
) {
208 nick
*np
= (nick
*)arg
;
210 to_age
= time(NULL
) - (CLEANUP_ACCOUNT_INACTIVE
* 3600 * 24);
213 cleanuplog("Manually started by %s.", np
->nick
);
215 cleanuplog("Automatically started.");
218 if (cleanupdb_active
) {
219 cleanuplog("ABORTED! Cleanup already in progress! BUG BUG BUG!");
223 cleanuplog("Phase 1 started (auth history data retrieval)...");
225 /* This query returns a single column containing the userids of all users
226 * who have active sessions now, or sessions which ended in the last
227 * CLEANUP_ACCOUNT_INACTIVE days. */
229 dbquery("BEGIN TRANSACTION;");
231 /* increase memory for aggregate (GROUP BY) -- query can take hours if this spills to disk */
232 dbquery("SET LOCAL work_mem = '512MB';");
233 q9cleanup_asyncquery(cleanupdb_real
, NULL
,
234 "SELECT userID from chanserv.authhistory WHERE disconnecttime=0 OR disconnecttime > %d GROUP BY userID;", to_age
);