4 * This module handles the expiry of old messages and the purging of old users.
6 * You might also see this module affectionately referred to as the DAP (the Dreaded Auto-Purger).
8 * Copyright (c) 1988-2009 by citadel.org (Art Cancro, Wilifried Goesgens, and others)
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 3 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 * A brief technical discussion:
27 * Several of the purge operations found in this module operate in two
28 * stages: the first stage generates a linked list of objects to be deleted,
29 * then the second stage deletes all listed objects from the database.
31 * At first glance this may seem cumbersome and unnecessary. The reason it is
32 * implemented in this way is because Berkeley DB, and possibly other backends
33 * we may hook into in the future, explicitly do _not_ support the deletion of
34 * records from a file while the file is being traversed. The delete operation
35 * will succeed, but the traversal is not guaranteed to visit every object if
36 * this is done. Therefore we utilize the two-stage purge.
38 * When using Berkeley DB, there's another reason for the two-phase purge: we
39 * don't want the entire thing being done as one huge transaction.
41 * You'll also notice that we build the in-memory list of records to be deleted
42 * sometimes with a linked list and sometimes with a hash table. There is no
43 * reason for this aside from the fact that the linked list ones were written
44 * before we had the hash table library available.
56 #include <sys/types.h>
58 #if TIME_WITH_SYS_TIME
59 # include <sys/time.h>
63 # include <sys/time.h>
72 #include <libcitadel.h>
75 #include "citserver.h"
83 #include "serv_network.h" /* Needed for definition of UseTable */
87 #include "ctdl_module.h"
91 struct PurgeList *next;
92 char name[ROOMNAMELEN]; /* use the larger of username or roomname */
96 struct VPurgeList *next;
103 struct ValidRoom *next;
109 struct ValidUser *next;
115 struct ctdlroomref *next;
120 struct UPurgeList *next;
125 struct EPurgeList *next;
131 struct PurgeList *UserPurgeList = NULL;
132 struct PurgeList *RoomPurgeList = NULL;
133 struct ValidRoom *ValidRoomList = NULL;
134 struct ValidUser *ValidUserList = NULL;
136 int users_not_purged;
137 char *users_corrupt_msg = NULL;
138 char *users_zero_msg = NULL;
139 struct ctdlroomref *rr = NULL;
140 int force_purge_now = 0; /* set to nonzero to force a run right now */
144 * First phase of message purge -- gather the locations of messages which
145 * qualify for purging and write them to a temp file.
147 void GatherPurgeMessages(struct ctdlroom *qrbuf, void *data) {
148 struct ExpirePolicy epbuf;
151 struct CtdlMessage *msg = NULL;
153 struct cdbdata *cdbfr;
154 long *msglist = NULL;
158 purgelist = (FILE *)data;
159 fprintf(purgelist, "r=%s\n", qrbuf->QRname);
162 GetExpirePolicy(&epbuf, qrbuf);
164 /* If the room is set to never expire messages ... do nothing */
165 if (epbuf.expire_mode == EXPIRE_NEXTLEVEL) return;
166 if (epbuf.expire_mode == EXPIRE_MANUAL) return;
168 /* Don't purge messages containing system configuration, dumbass. */
169 if (!strcasecmp(qrbuf->QRname, SYSCONFIGROOM)) return;
171 /* Ok, we got this far ... now let's see what's in the room */
172 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf->QRnumber, sizeof(long));
175 msglist = malloc(cdbfr->len);
176 memcpy(msglist, cdbfr->ptr, cdbfr->len);
177 num_msgs = cdbfr->len / sizeof(long);
181 /* Nothing to do if there aren't any messages */
183 if (msglist != NULL) free(msglist);
188 /* If the room is set to expire by count, do that */
189 if (epbuf.expire_mode == EXPIRE_NUMMSGS) {
190 if (num_msgs > epbuf.expire_value) {
191 for (a=0; a<(num_msgs - epbuf.expire_value); ++a) {
192 fprintf(purgelist, "m=%ld\n", msglist[a]);
198 /* If the room is set to expire by age... */
199 if (epbuf.expire_mode == EXPIRE_AGE) {
200 for (a=0; a<num_msgs; ++a) {
203 msg = CtdlFetchMessage(delnum, 0); /* dont need body */
205 xtime = atol(msg->cm_fields['T']);
206 CtdlFreeMessage(msg);
212 && (now - xtime > (time_t)(epbuf.expire_value * 86400L))) {
213 fprintf(purgelist, "m=%ld\n", delnum);
219 if (msglist != NULL) free(msglist);
224 * Second phase of message purge -- read list of msgs from temp file and
227 void DoPurgeMessages(FILE *purgelist) {
228 char roomname[ROOMNAMELEN];
233 strcpy(roomname, "nonexistent room ___ ___");
234 while (fgets(buf, sizeof buf, purgelist) != NULL) {
235 buf[strlen(buf)-1]=0;
236 if (!strncasecmp(buf, "r=", 2)) {
237 strcpy(roomname, &buf[2]);
239 if (!strncasecmp(buf, "m=", 2)) {
240 msgnum = atol(&buf[2]);
242 CtdlDeleteMessages(roomname, &msgnum, 1, "");
249 void PurgeMessages(void) {
252 CtdlLogPrintf(CTDL_DEBUG, "PurgeMessages() called\n");
255 purgelist = tmpfile();
256 if (purgelist == NULL) {
257 CtdlLogPrintf(CTDL_CRIT, "Can't create purgelist temp file: %s\n",
262 CtdlForEachRoom(GatherPurgeMessages, (void *)purgelist );
263 DoPurgeMessages(purgelist);
268 void AddValidUser(struct ctdluser *usbuf, void *data) {
269 struct ValidUser *vuptr;
271 vuptr = (struct ValidUser *)malloc(sizeof(struct ValidUser));
272 vuptr->next = ValidUserList;
273 vuptr->vu_usernum = usbuf->usernum;
274 ValidUserList = vuptr;
277 void AddValidRoom(struct ctdlroom *qrbuf, void *data) {
278 struct ValidRoom *vrptr;
280 vrptr = (struct ValidRoom *)malloc(sizeof(struct ValidRoom));
281 vrptr->next = ValidRoomList;
282 vrptr->vr_roomnum = qrbuf->QRnumber;
283 vrptr->vr_roomgen = qrbuf->QRgen;
284 ValidRoomList = vrptr;
287 void DoPurgeRooms(struct ctdlroom *qrbuf, void *data) {
288 time_t age, purge_secs;
289 struct PurgeList *pptr;
290 struct ValidUser *vuptr;
293 /* For mailbox rooms, there's only one purging rule: if the user who
294 * owns the room still exists, we keep the room; otherwise, we purge
295 * it. Bypass any other rules.
297 if (qrbuf->QRflags & QR_MAILBOX) {
298 /* if user not found, do_purge will be 1 */
300 for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) {
301 if (vuptr->vu_usernum == atol(qrbuf->QRname)) {
307 /* Any of these attributes render a room non-purgable */
308 if (qrbuf->QRflags & QR_PERMANENT) return;
309 if (qrbuf->QRflags & QR_DIRECTORY) return;
310 if (qrbuf->QRflags & QR_NETWORK) return;
311 if (qrbuf->QRflags2 & QR2_SYSTEM) return;
312 if (!strcasecmp(qrbuf->QRname, SYSCONFIGROOM)) return;
313 if (CtdlIsNonEditable(qrbuf)) return;
315 /* If we don't know the modification date, be safe and don't purge */
316 if (qrbuf->QRmtime <= (time_t)0) return;
318 /* If no room purge time is set, be safe and don't purge */
319 if (config.c_roompurge < 0) return;
321 /* Otherwise, check the date of last modification */
322 age = time(NULL) - (qrbuf->QRmtime);
323 purge_secs = (time_t)config.c_roompurge * (time_t)86400;
324 if (purge_secs <= (time_t)0) return;
325 CtdlLogPrintf(CTDL_DEBUG, "<%s> is <%ld> seconds old\n", qrbuf->QRname, (long)age);
326 if (age > purge_secs) do_purge = 1;
330 pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList));
331 pptr->next = RoomPurgeList;
332 strcpy(pptr->name, qrbuf->QRname);
333 RoomPurgeList = pptr;
340 int PurgeRooms(void) {
341 struct PurgeList *pptr;
342 int num_rooms_purged = 0;
343 struct ctdlroom qrbuf;
344 struct ValidUser *vuptr;
345 char *transcript = NULL;
347 CtdlLogPrintf(CTDL_DEBUG, "PurgeRooms() called\n");
350 /* Load up a table full of valid user numbers so we can delete
351 * user-owned rooms for users who no longer exist */
352 ForEachUser(AddValidUser, NULL);
354 /* Then cycle through the room file */
355 CtdlForEachRoom(DoPurgeRooms, NULL);
357 /* Free the valid user list */
358 while (ValidUserList != NULL) {
359 vuptr = ValidUserList->next;
361 ValidUserList = vuptr;
365 transcript = malloc(SIZ);
366 strcpy(transcript, "The following rooms have been auto-purged:\n");
368 while (RoomPurgeList != NULL) {
369 if (CtdlGetRoom(&qrbuf, RoomPurgeList->name) == 0) {
370 transcript=realloc(transcript, strlen(transcript)+SIZ);
371 snprintf(&transcript[strlen(transcript)], SIZ, " %s\n",
373 CtdlDeleteRoom(&qrbuf);
375 pptr = RoomPurgeList->next;
377 RoomPurgeList = pptr;
381 if (num_rooms_purged > 0) CtdlAideMessage(transcript, "Room Autopurger Message");
384 CtdlLogPrintf(CTDL_DEBUG, "Purged %d rooms.\n", num_rooms_purged);
385 return(num_rooms_purged);
390 * Back end function to check user accounts for associated Unix accounts
391 * which no longer exist. (Only relevant for host auth mode.)
393 void do_uid_user_purge(struct ctdluser *us, void *data) {
394 struct PurgeList *pptr;
396 if ((us->uid != (-1)) && (us->uid != CTDLUID)) {
397 if (getpwuid(us->uid) == NULL) {
398 pptr = (struct PurgeList *)
399 malloc(sizeof(struct PurgeList));
400 pptr->next = UserPurgeList;
401 strcpy(pptr->name, us->fullname);
402 UserPurgeList = pptr;
414 * Back end function to check user accounts for expiration.
416 void do_user_purge(struct ctdluser *us, void *data) {
420 struct PurgeList *pptr;
422 /* Set purge time; if the user overrides the system default, use it */
423 if (us->USuserpurge > 0) {
424 purge_time = ((time_t)us->USuserpurge) * 86400L;
427 purge_time = ((time_t)config.c_userpurge) * 86400L;
430 /* The default rule is to not purge. */
433 /* don't attempt to purge system users. */
434 if (!strncmp(us->fullname, "SYS_", 4))
437 /* If the user hasn't called in two months and expiring of accounts is turned on, his/her account
438 * has expired, so purge the record.
440 if (config.c_userpurge > 0)
443 if ((now - us->lastcall) > purge_time) purge = 1;
446 /* If the record is marked as permanent, don't purge it.
448 if (us->flags & US_PERM) purge = 0;
450 /* If the user is an Aide, don't purge him/her/it.
452 if (us->axlevel == 6) purge = 0;
454 /* If the access level is 0, the record should already have been
455 * deleted, but maybe the user was logged in at the time or something.
456 * Delete the record now.
458 if (us->axlevel == 0) purge = 1;
460 /* If the user set his/her password to 'deleteme', he/she
461 * wishes to be deleted, so purge the record.
462 * Moved this lower down so that aides and permanent users get purged if they ask to be.
464 if (!strcasecmp(us->password, "deleteme")) purge = 1;
466 /* 0 calls is impossible. If there are 0 calls, it must
467 * be a corrupted record, so purge it.
468 * Actually it is possible if an Aide created the user so now we check for less than 0 (DRW)
470 if (us->timescalled < 0) purge = 1;
472 /* any negative user number, is
475 if (us->usernum < 0L) purge = 1;
477 /** Don't purge user 0. That user is there for the system */
478 if (us->usernum == 0L)
480 /* FIXME: Temporary log message. Until we do unauth access with user 0 we should
481 * try to get rid of all user 0 occurences. Many will be remnants from old code so
482 * we will need to try and purge them from users data bases.Some will not have names but
483 * those with names should be purged.
485 CtdlLogPrintf(CTDL_DEBUG, "Auto purger found a user 0 with name \"%s\"\n", us->fullname);
489 /* If the user has no full name entry then we can't purge them
490 * since the actual purge can't find them.
491 * This shouldn't happen but does somehow.
493 if (IsEmptyStr(us->fullname))
497 if (us->usernum > 0L)
500 if (users_corrupt_msg == NULL)
502 users_corrupt_msg = malloc(SIZ);
503 strcpy(users_corrupt_msg, "The auto-purger found the following user numbers with no name.\n"
504 "The system has no way to purge user with no name and should not be able to\n"
505 "create them either.\n"
506 "This indicates corruption of the user DB or possibly a bug.\n"
507 "It may be a good idea to restore your DB from a backup.\n");
510 users_corrupt_msg=realloc(users_corrupt_msg, strlen(users_corrupt_msg)+30);
511 snprintf(&users_corrupt_msg[strlen(users_corrupt_msg)], 29, " %ld\n", us->usernum);
518 pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList));
519 pptr->next = UserPurgeList;
520 strcpy(pptr->name, us->fullname);
521 UserPurgeList = pptr;
531 int PurgeUsers(void) {
532 struct PurgeList *pptr;
533 int num_users_purged = 0;
534 char *transcript = NULL;
536 CtdlLogPrintf(CTDL_DEBUG, "PurgeUsers() called\n");
537 users_not_purged = 0;
539 switch(config.c_auth_mode) {
540 case AUTHMODE_NATIVE:
541 ForEachUser(do_user_purge, NULL);
544 ForEachUser(do_uid_user_purge, NULL);
547 CtdlLogPrintf(CTDL_DEBUG, "User purge for auth mode %d is not implemented.\n",
552 transcript = malloc(SIZ);
554 if (users_not_purged == 0) {
555 strcpy(transcript, "The auto-purger was told to purge every user. It is\n"
556 "refusing to do this because it usually indicates a problem\n"
557 "such as an inability to communicate with a name service.\n"
559 while (UserPurgeList != NULL) {
560 pptr = UserPurgeList->next;
562 UserPurgeList = pptr;
568 strcpy(transcript, "The following users have been auto-purged:\n");
569 while (UserPurgeList != NULL) {
570 transcript=realloc(transcript, strlen(transcript)+SIZ);
571 snprintf(&transcript[strlen(transcript)], SIZ, " %s\n",
572 UserPurgeList->name);
573 purge_user(UserPurgeList->name);
574 pptr = UserPurgeList->next;
576 UserPurgeList = pptr;
581 if (num_users_purged > 0) CtdlAideMessage(transcript, "User Purge Message");
584 if(users_corrupt_msg)
586 CtdlAideMessage(users_corrupt_msg, "User Corruption Message");
587 free (users_corrupt_msg);
588 users_corrupt_msg = NULL;
593 CtdlAideMessage(users_zero_msg, "User Zero Message");
594 free (users_zero_msg);
595 users_zero_msg = NULL;
598 CtdlLogPrintf(CTDL_DEBUG, "Purged %d users.\n", num_users_purged);
599 return(num_users_purged);
606 * This is a really cumbersome "garbage collection" function. We have to
607 * delete visits which refer to rooms and/or users which no longer exist. In
608 * order to prevent endless traversals of the room and user files, we first
609 * build linked lists of rooms and users which _do_ exist on the system, then
610 * traverse the visit file, checking each record against those two lists and
611 * purging the ones that do not have a match on _both_ lists. (Remember, if
612 * either the room or user being referred to is no longer on the system, the
613 * record is completely useless.)
615 int PurgeVisits(void) {
616 struct cdbdata *cdbvisit;
618 struct VPurgeList *VisitPurgeList = NULL;
619 struct VPurgeList *vptr;
623 struct ValidRoom *vrptr;
624 struct ValidUser *vuptr;
625 int RoomIsValid, UserIsValid;
627 /* First, load up a table full of valid room/gen combinations */
628 CtdlForEachRoom(AddValidRoom, NULL);
630 /* Then load up a table full of valid user numbers */
631 ForEachUser(AddValidUser, NULL);
633 /* Now traverse through the visits, purging irrelevant records... */
634 cdb_rewind(CDB_VISIT);
635 while(cdbvisit = cdb_next_item(CDB_VISIT), cdbvisit != NULL) {
636 memset(&vbuf, 0, sizeof(struct visit));
637 memcpy(&vbuf, cdbvisit->ptr,
638 ( (cdbvisit->len > sizeof(struct visit)) ?
639 sizeof(struct visit) : cdbvisit->len) );
645 /* Check to see if the room exists */
646 for (vrptr=ValidRoomList; vrptr!=NULL; vrptr=vrptr->next) {
647 if ( (vrptr->vr_roomnum==vbuf.v_roomnum)
648 && (vrptr->vr_roomgen==vbuf.v_roomgen))
652 /* Check to see if the user exists */
653 for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) {
654 if (vuptr->vu_usernum == vbuf.v_usernum)
658 /* Put the record on the purge list if it's dead */
659 if ((RoomIsValid==0) || (UserIsValid==0)) {
660 vptr = (struct VPurgeList *)
661 malloc(sizeof(struct VPurgeList));
662 vptr->next = VisitPurgeList;
663 vptr->vp_roomnum = vbuf.v_roomnum;
664 vptr->vp_roomgen = vbuf.v_roomgen;
665 vptr->vp_usernum = vbuf.v_usernum;
666 VisitPurgeList = vptr;
671 /* Free the valid room/gen combination list */
672 while (ValidRoomList != NULL) {
673 vrptr = ValidRoomList->next;
675 ValidRoomList = vrptr;
678 /* Free the valid user list */
679 while (ValidUserList != NULL) {
680 vuptr = ValidUserList->next;
682 ValidUserList = vuptr;
685 /* Now delete every visit on the purged list */
686 while (VisitPurgeList != NULL) {
687 IndexLen = GenerateRelationshipIndex(IndexBuf,
688 VisitPurgeList->vp_roomnum,
689 VisitPurgeList->vp_roomgen,
690 VisitPurgeList->vp_usernum);
691 cdb_delete(CDB_VISIT, IndexBuf, IndexLen);
692 vptr = VisitPurgeList->next;
693 free(VisitPurgeList);
694 VisitPurgeList = vptr;
702 * Purge the use table of old entries.
705 int PurgeUseTable(void) {
707 struct cdbdata *cdbut;
709 struct UPurgeList *ul = NULL;
710 struct UPurgeList *uptr;
712 /* Phase 1: traverse through the table, discovering old records... */
713 CtdlLogPrintf(CTDL_DEBUG, "Purge use table: phase 1\n");
714 cdb_rewind(CDB_USETABLE);
715 while(cdbut = cdb_next_item(CDB_USETABLE), cdbut != NULL) {
718 * TODODRW: change this to create a new function time_t cdb_get_timestamp( struct cdbdata *)
719 * this will release this file from the serv_network.h
720 * Maybe it could be a macro that extracts and casts the reult
722 memcpy(&ut, cdbut->ptr,
723 ((cdbut->len > sizeof(struct UseTable)) ?
724 sizeof(struct UseTable) : cdbut->len));
727 if ( (time(NULL) - ut.ut_timestamp) > USETABLE_RETAIN ) {
728 uptr = (struct UPurgeList *) malloc(sizeof(struct UPurgeList));
731 safestrncpy(uptr->up_key, ut.ut_msgid, sizeof uptr->up_key);
739 /* Phase 2: delete the records */
740 CtdlLogPrintf(CTDL_DEBUG, "Purge use table: phase 2\n");
742 cdb_delete(CDB_USETABLE, ul->up_key, strlen(ul->up_key));
748 CtdlLogPrintf(CTDL_DEBUG, "Purge use table: finished (purged %d records)\n", purged);
755 * Purge the EUID Index of old records.
758 int PurgeEuidIndexTable(void) {
760 struct cdbdata *cdbei;
761 struct EPurgeList *el = NULL;
762 struct EPurgeList *eptr;
764 struct CtdlMessage *msg = NULL;
766 /* Phase 1: traverse through the table, discovering old records... */
767 CtdlLogPrintf(CTDL_DEBUG, "Purge EUID index: phase 1\n");
768 cdb_rewind(CDB_EUIDINDEX);
769 while(cdbei = cdb_next_item(CDB_EUIDINDEX), cdbei != NULL) {
771 memcpy(&msgnum, cdbei->ptr, sizeof(long));
773 msg = CtdlFetchMessage(msgnum, 0);
775 CtdlFreeMessage(msg); /* it still exists, so do nothing */
778 eptr = (struct EPurgeList *) malloc(sizeof(struct EPurgeList));
781 eptr->ep_keylen = cdbei->len - sizeof(long);
782 eptr->ep_key = malloc(cdbei->len);
783 memcpy(eptr->ep_key, &cdbei->ptr[sizeof(long)], eptr->ep_keylen);
793 /* Phase 2: delete the records */
794 CtdlLogPrintf(CTDL_DEBUG, "Purge euid index: phase 2\n");
796 cdb_delete(CDB_EUIDINDEX, el->ep_key, el->ep_keylen);
803 CtdlLogPrintf(CTDL_DEBUG, "Purge euid index: finished (purged %d records)\n", purged);
810 * Purge OpenID assocations for missing users (theoretically this will never delete anything)
812 int PurgeStaleOpenIDassociations(void) {
813 struct cdbdata *cdboi;
814 struct ctdluser usbuf;
815 HashList *keys = NULL;
817 char *deleteme = NULL;
824 keys = NewHash(1, NULL);
825 if (!keys) return(0);
828 cdb_rewind(CDB_OPENID);
829 while (cdboi = cdb_next_item(CDB_OPENID), cdboi != NULL) {
830 if (cdboi->len > sizeof(long)) {
831 memcpy(&usernum, cdboi->ptr, sizeof(long));
832 if (CtdlGetUserByNumber(&usbuf, usernum) != 0) {
833 deleteme = strdup(cdboi->ptr + sizeof(long)),
834 Put(keys, deleteme, strlen(deleteme), deleteme, NULL);
840 /* Go through the hash list, deleting keys we stored in it */
842 HashPos = GetNewHashPos(keys, 0);
843 while (GetNextHashPos(keys, HashPos, &len, &Key, &Value)!=0)
845 CtdlLogPrintf(CTDL_DEBUG, "Deleting associated OpenID <%s>\n", Value);
846 cdb_delete(CDB_OPENID, Value, strlen(Value));
847 /* note: don't free(Value) -- deleting the hash list will handle this for us */
850 DeleteHashPos(&HashPos);
859 void *purge_databases(void *args)
862 static time_t last_purge = 0;
865 struct CitContext purgerCC;
867 CtdlFillSystemContext(&purgerCC, "purger");
868 citthread_setspecific(MyConKey, (void *)&purgerCC );
869 CtdlLogPrintf(CTDL_DEBUG, "Auto-purger_thread() initializing\n");
871 while (!CtdlThreadCheckStop()) {
872 /* Do the auto-purge if the current hour equals the purge hour,
873 * but not if the operation has already been performed in the
874 * last twelve hours. This is usually enough granularity.
877 localtime_r(&now, &tm);
879 ((tm.tm_hour != config.c_purge_hour) || ((now - last_purge) < 43200))
880 && (force_purge_now == 0)
887 CtdlLogPrintf(CTDL_INFO, "Auto-purger: starting.\n");
889 if (!CtdlThreadCheckStop())
891 retval = PurgeUsers();
892 CtdlLogPrintf(CTDL_NOTICE, "Purged %d users.\n", retval);
895 if (!CtdlThreadCheckStop())
898 CtdlLogPrintf(CTDL_NOTICE, "Expired %d messages.\n", messages_purged);
901 if (!CtdlThreadCheckStop())
903 retval = PurgeRooms();
904 CtdlLogPrintf(CTDL_NOTICE, "Expired %d rooms.\n", retval);
907 if (!CtdlThreadCheckStop())
909 retval = PurgeVisits();
910 CtdlLogPrintf(CTDL_NOTICE, "Purged %d visits.\n", retval);
913 if (!CtdlThreadCheckStop())
915 retval = PurgeUseTable();
916 CtdlLogPrintf(CTDL_NOTICE, "Purged %d entries from the use table.\n", retval);
919 if (!CtdlThreadCheckStop())
921 retval = PurgeEuidIndexTable();
922 CtdlLogPrintf(CTDL_NOTICE, "Purged %d entries from the EUID index.\n", retval);
925 if (!CtdlThreadCheckStop())
927 retval = PurgeStaleOpenIDassociations();
928 CtdlLogPrintf(CTDL_NOTICE, "Purged %d stale OpenID associations.\n", retval);
931 if (!CtdlThreadCheckStop())
933 retval = TDAP_ProcessAdjRefCountQueue();
934 CtdlLogPrintf(CTDL_NOTICE, "Processed %d message reference count adjustments.\n", retval);
937 if (!CtdlThreadCheckStop())
939 CtdlLogPrintf(CTDL_INFO, "Auto-purger: finished.\n");
940 last_purge = now; /* So we don't do it again soon */
944 CtdlLogPrintf(CTDL_INFO, "Auto-purger: STOPPED.\n");
947 CtdlClearSystemContext();
950 /*****************************************************************************/
953 /* The FSCK command has been removed because people were misusing it */
957 void do_fsck_msg(long msgnum, void *userdata) {
958 struct ctdlroomref *ptr;
960 ptr = (struct ctdlroomref *)malloc(sizeof(struct ctdlroomref));
962 ptr->msgnum = msgnum;
966 void do_fsck_room(struct ctdlroom *qrbuf, void *data)
968 CtdlGetRoom(&CC->room, qrbuf->QRname);
969 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, do_fsck_msg, NULL);
973 * Check message reference counts
975 void cmd_fsck(char *argbuf) {
977 struct cdbdata *cdbmsg;
979 struct ctdlroomref *ptr;
982 if (CtdlAccessCheck(ac_aide)) return;
984 /* Lame way of checking whether anyone else is doing this now */
986 cprintf("%d Another FSCK is already running.\n", ERROR + RESOURCE_BUSY);
990 cprintf("%d Checking message reference counts\n", LISTING_FOLLOWS);
992 cprintf("\nThis could take a while. Please be patient!\n\n");
993 cprintf("Gathering pointers...\n");
994 CtdlForEachRoom(do_fsck_room, NULL);
997 cprintf("Checking message base...\n");
998 for (msgnum = 0L; msgnum <= CitControl.MMhighest; ++msgnum) {
1000 cdbmsg = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1001 if (cdbmsg != NULL) {
1003 cprintf("Message %7ld ", msgnum);
1005 GetMetaData(&smi, msgnum);
1006 cprintf("refcount=%-2d ", smi.meta_refcount);
1009 for (ptr = rr; ptr != NULL; ptr = ptr->next) {
1010 if (ptr->msgnum == msgnum) ++realcount;
1012 cprintf("realcount=%-2d\n", realcount);
1014 if ( (smi.meta_refcount != realcount)
1015 || (realcount == 0) ) {
1016 AdjRefCount(msgnum, (smi.meta_refcount - realcount));
1023 cprintf("Freeing memory...\n");
1024 while (rr != NULL) {
1035 #endif /* end of commented-out fsck cmd */
1038 * Manually initiate a run of The Dreaded Auto-Purger (tm)
1040 void cmd_tdap(char *argbuf) {
1041 if (CtdlAccessCheck(ac_aide)) return;
1042 force_purge_now = 1;
1043 cprintf("%d Manually initiating a purger run now.\n", CIT_OK);
1047 /*****************************************************************************/
1049 CTDL_MODULE_INIT(expire)
1053 /* CtdlRegisterProtoHook(cmd_fsck, "FSCK", "Check message ref counts"); */
1054 CtdlRegisterProtoHook(cmd_tdap, "TDAP", "Manually initiate auto-purger");
1056 CtdlRegisterProtoHook(cmd_gpex, "GPEX", "Autoconverted. TODO: document me.");
1057 CtdlRegisterProtoHook(cmd_spex, "SPEX", "Autoconverted. TODO: document me.");
1060 CtdlThreadCreate("Auto Purger", CTDLTHREAD_BIGSTACK, purge_databases, NULL);
1061 /* return our Subversion id for the Log */