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)
9 * Brought to you, our happy user community, under the terms of the GNU General Public License v3
11 * A brief technical discussion:
13 * Several of the purge operations found in this module operate in two
14 * stages: the first stage generates a linked list of objects to be deleted,
15 * then the second stage deletes all listed objects from the database.
17 * At first glance this may seem cumbersome and unnecessary. The reason it is
18 * implemented in this way is because Berkeley DB, and possibly other backends
19 * we may hook into in the future, explicitly do _not_ support the deletion of
20 * records from a file while the file is being traversed. The delete operation
21 * will succeed, but the traversal is not guaranteed to visit every object if
22 * this is done. Therefore we utilize the two-stage purge.
24 * When using Berkeley DB, there's another reason for the two-phase purge: we
25 * don't want the entire thing being done as one huge transaction.
27 * You'll also notice that we build the in-memory list of records to be deleted
28 * sometimes with a linked list and sometimes with a hash table. There is no
29 * reason for this aside from the fact that the linked list ones were written
30 * before we had the hash table library available.
42 #include <sys/types.h>
44 #if TIME_WITH_SYS_TIME
45 # include <sys/time.h>
49 # include <sys/time.h>
58 #include <libcitadel.h>
61 #include "citserver.h"
70 #include "serv_network.h" /* Needed for definition of UseTable */
73 #include "ctdl_module.h"
77 struct PurgeList *next;
78 char name[ROOMNAMELEN]; /* use the larger of username or roomname */
82 struct VPurgeList *next;
89 struct ValidRoom *next;
95 struct ValidUser *next;
101 struct ctdlroomref *next;
106 struct UPurgeList *next;
111 struct EPurgeList *next;
117 struct PurgeList *UserPurgeList = NULL;
118 struct PurgeList *RoomPurgeList = NULL;
119 struct ValidRoom *ValidRoomList = NULL;
120 struct ValidUser *ValidUserList = NULL;
122 int users_not_purged;
123 char *users_corrupt_msg = NULL;
124 char *users_zero_msg = NULL;
125 struct ctdlroomref *rr = NULL;
126 extern struct CitContext *ContextList;
127 int force_purge_now = 0; /* set to nonzero to force a run right now */
131 * First phase of message purge -- gather the locations of messages which
132 * qualify for purging and write them to a temp file.
134 void GatherPurgeMessages(struct ctdlroom *qrbuf, void *data) {
135 struct ExpirePolicy epbuf;
138 struct CtdlMessage *msg = NULL;
140 struct cdbdata *cdbfr;
141 long *msglist = NULL;
145 purgelist = (FILE *)data;
146 fprintf(purgelist, "r=%s\n", qrbuf->QRname);
149 GetExpirePolicy(&epbuf, qrbuf);
151 /* If the room is set to never expire messages ... do nothing */
152 if (epbuf.expire_mode == EXPIRE_NEXTLEVEL) return;
153 if (epbuf.expire_mode == EXPIRE_MANUAL) return;
155 /* Don't purge messages containing system configuration, dumbass. */
156 if (!strcasecmp(qrbuf->QRname, SYSCONFIGROOM)) return;
158 /* Ok, we got this far ... now let's see what's in the room */
159 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf->QRnumber, sizeof(long));
162 msglist = malloc(cdbfr->len);
163 memcpy(msglist, cdbfr->ptr, cdbfr->len);
164 num_msgs = cdbfr->len / sizeof(long);
168 /* Nothing to do if there aren't any messages */
170 if (msglist != NULL) free(msglist);
175 /* If the room is set to expire by count, do that */
176 if (epbuf.expire_mode == EXPIRE_NUMMSGS) {
177 if (num_msgs > epbuf.expire_value) {
178 for (a=0; a<(num_msgs - epbuf.expire_value); ++a) {
179 fprintf(purgelist, "m=%ld\n", msglist[a]);
185 /* If the room is set to expire by age... */
186 if (epbuf.expire_mode == EXPIRE_AGE) {
187 for (a=0; a<num_msgs; ++a) {
190 msg = CtdlFetchMessage(delnum, 0); /* dont need body */
192 xtime = atol(msg->cm_fields['T']);
193 CtdlFreeMessage(msg);
199 && (now - xtime > (time_t)(epbuf.expire_value * 86400L))) {
200 fprintf(purgelist, "m=%ld\n", delnum);
206 if (msglist != NULL) free(msglist);
211 * Second phase of message purge -- read list of msgs from temp file and
214 void DoPurgeMessages(FILE *purgelist) {
215 char roomname[ROOMNAMELEN];
220 strcpy(roomname, "nonexistent room ___ ___");
221 while (fgets(buf, sizeof buf, purgelist) != NULL) {
222 buf[strlen(buf)-1]=0;
223 if (!strncasecmp(buf, "r=", 2)) {
224 strcpy(roomname, &buf[2]);
226 if (!strncasecmp(buf, "m=", 2)) {
227 msgnum = atol(&buf[2]);
229 CtdlDeleteMessages(roomname, &msgnum, 1, "");
236 void PurgeMessages(void) {
239 CtdlLogPrintf(CTDL_DEBUG, "PurgeMessages() called\n");
242 purgelist = tmpfile();
243 if (purgelist == NULL) {
244 CtdlLogPrintf(CTDL_CRIT, "Can't create purgelist temp file: %s\n",
249 ForEachRoom(GatherPurgeMessages, (void *)purgelist );
250 DoPurgeMessages(purgelist);
255 void AddValidUser(struct ctdluser *usbuf, void *data) {
256 struct ValidUser *vuptr;
258 vuptr = (struct ValidUser *)malloc(sizeof(struct ValidUser));
259 vuptr->next = ValidUserList;
260 vuptr->vu_usernum = usbuf->usernum;
261 ValidUserList = vuptr;
264 void AddValidRoom(struct ctdlroom *qrbuf, void *data) {
265 struct ValidRoom *vrptr;
267 vrptr = (struct ValidRoom *)malloc(sizeof(struct ValidRoom));
268 vrptr->next = ValidRoomList;
269 vrptr->vr_roomnum = qrbuf->QRnumber;
270 vrptr->vr_roomgen = qrbuf->QRgen;
271 ValidRoomList = vrptr;
274 void DoPurgeRooms(struct ctdlroom *qrbuf, void *data) {
275 time_t age, purge_secs;
276 struct PurgeList *pptr;
277 struct ValidUser *vuptr;
280 /* For mailbox rooms, there's only one purging rule: if the user who
281 * owns the room still exists, we keep the room; otherwise, we purge
282 * it. Bypass any other rules.
284 if (qrbuf->QRflags & QR_MAILBOX) {
285 /* if user not found, do_purge will be 1 */
287 for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) {
288 if (vuptr->vu_usernum == atol(qrbuf->QRname)) {
294 /* Any of these attributes render a room non-purgable */
295 if (qrbuf->QRflags & QR_PERMANENT) return;
296 if (qrbuf->QRflags & QR_DIRECTORY) return;
297 if (qrbuf->QRflags & QR_NETWORK) return;
298 if (qrbuf->QRflags2 & QR2_SYSTEM) return;
299 if (!strcasecmp(qrbuf->QRname, SYSCONFIGROOM)) return;
300 if (is_noneditable(qrbuf)) return;
302 /* If we don't know the modification date, be safe and don't purge */
303 if (qrbuf->QRmtime <= (time_t)0) return;
305 /* If no room purge time is set, be safe and don't purge */
306 if (config.c_roompurge < 0) return;
308 /* Otherwise, check the date of last modification */
309 age = time(NULL) - (qrbuf->QRmtime);
310 purge_secs = (time_t)config.c_roompurge * (time_t)86400;
311 if (purge_secs <= (time_t)0) return;
312 CtdlLogPrintf(CTDL_DEBUG, "<%s> is <%ld> seconds old\n", qrbuf->QRname, (long)age);
313 if (age > purge_secs) do_purge = 1;
317 pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList));
318 pptr->next = RoomPurgeList;
319 strcpy(pptr->name, qrbuf->QRname);
320 RoomPurgeList = pptr;
327 int PurgeRooms(void) {
328 struct PurgeList *pptr;
329 int num_rooms_purged = 0;
330 struct ctdlroom qrbuf;
331 struct ValidUser *vuptr;
332 char *transcript = NULL;
334 CtdlLogPrintf(CTDL_DEBUG, "PurgeRooms() called\n");
337 /* Load up a table full of valid user numbers so we can delete
338 * user-owned rooms for users who no longer exist */
339 ForEachUser(AddValidUser, NULL);
341 /* Then cycle through the room file */
342 ForEachRoom(DoPurgeRooms, NULL);
344 /* Free the valid user list */
345 while (ValidUserList != NULL) {
346 vuptr = ValidUserList->next;
348 ValidUserList = vuptr;
352 transcript = malloc(SIZ);
353 strcpy(transcript, "The following rooms have been auto-purged:\n");
355 while (RoomPurgeList != NULL) {
356 if (getroom(&qrbuf, RoomPurgeList->name) == 0) {
357 transcript=realloc(transcript, strlen(transcript)+SIZ);
358 snprintf(&transcript[strlen(transcript)], SIZ, " %s\n",
362 pptr = RoomPurgeList->next;
364 RoomPurgeList = pptr;
368 if (num_rooms_purged > 0) aide_message(transcript, "Room Autopurger Message");
371 CtdlLogPrintf(CTDL_DEBUG, "Purged %d rooms.\n", num_rooms_purged);
372 return(num_rooms_purged);
377 * Back end function to check user accounts for associated Unix accounts
378 * which no longer exist. (Only relevant for host auth mode.)
380 void do_uid_user_purge(struct ctdluser *us, void *data) {
381 struct PurgeList *pptr;
383 if ((us->uid != (-1)) && (us->uid != CTDLUID)) {
384 if (getpwuid(us->uid) == NULL) {
385 pptr = (struct PurgeList *)
386 malloc(sizeof(struct PurgeList));
387 pptr->next = UserPurgeList;
388 strcpy(pptr->name, us->fullname);
389 UserPurgeList = pptr;
401 * Back end function to check user accounts for expiration.
403 void do_user_purge(struct ctdluser *us, void *data) {
407 struct PurgeList *pptr;
409 /* Set purge time; if the user overrides the system default, use it */
410 if (us->USuserpurge > 0) {
411 purge_time = ((time_t)us->USuserpurge) * 86400L;
414 purge_time = ((time_t)config.c_userpurge) * 86400L;
417 /* The default rule is to not purge. */
420 /* don't attempt to purge system users. */
421 if (!strncmp(us->fullname, "SYS_", 4))
424 /* If the user hasn't called in two months and expiring of accounts is turned on, his/her account
425 * has expired, so purge the record.
427 if (config.c_userpurge > 0)
430 if ((now - us->lastcall) > purge_time) purge = 1;
433 /* If the record is marked as permanent, don't purge it.
435 if (us->flags & US_PERM) purge = 0;
437 /* If the user is an Aide, don't purge him/her/it.
439 if (us->axlevel == 6) purge = 0;
441 /* If the access level is 0, the record should already have been
442 * deleted, but maybe the user was logged in at the time or something.
443 * Delete the record now.
445 if (us->axlevel == 0) purge = 1;
447 /* If the user set his/her password to 'deleteme', he/she
448 * wishes to be deleted, so purge the record.
449 * Moved this lower down so that aides and permanent users get purged if they ask to be.
451 if (!strcasecmp(us->password, "deleteme")) purge = 1;
453 /* 0 calls is impossible. If there are 0 calls, it must
454 * be a corrupted record, so purge it.
455 * Actually it is possible if an Aide created the user so now we check for less than 0 (DRW)
457 if (us->timescalled < 0) purge = 1;
459 /* any negative user number, is
462 if (us->usernum < 0L) purge = 1;
464 /** Don't purge user 0. That user is there for the system */
465 if (us->usernum == 0L)
467 /* FIXME: Temporary log message. Until we do unauth access with user 0 we should
468 * try to get rid of all user 0 occurences. Many will be remnants from old code so
469 * we will need to try and purge them from users data bases.Some will not have names but
470 * those with names should be purged.
472 CtdlLogPrintf(CTDL_DEBUG, "Auto purger found a user 0 with name \"%s\"\n", us->fullname);
476 /* If the user has no full name entry then we can't purge them
477 * since the actual purge can't find them.
478 * This shouldn't happen but does somehow.
480 if (IsEmptyStr(us->fullname))
484 if (us->usernum > 0L)
487 if (users_corrupt_msg == NULL)
489 users_corrupt_msg = malloc(SIZ);
490 strcpy(users_corrupt_msg, "The auto-purger found the following user numbers with no name.\n"
491 "The system has no way to purge user with no name and should not be able to\n"
492 "create them either.\n"
493 "This indicates corruption of the user DB or possibly a bug.\n"
494 "It may be a good idea to restore your DB from a backup.\n");
497 users_corrupt_msg=realloc(users_corrupt_msg, strlen(users_corrupt_msg)+30);
498 snprintf(&users_corrupt_msg[strlen(users_corrupt_msg)], 29, " %ld\n", us->usernum);
505 pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList));
506 pptr->next = UserPurgeList;
507 strcpy(pptr->name, us->fullname);
508 UserPurgeList = pptr;
518 int PurgeUsers(void) {
519 struct PurgeList *pptr;
520 int num_users_purged = 0;
521 char *transcript = NULL;
523 CtdlLogPrintf(CTDL_DEBUG, "PurgeUsers() called\n");
524 users_not_purged = 0;
526 switch(config.c_auth_mode) {
527 case AUTHMODE_NATIVE:
528 ForEachUser(do_user_purge, NULL);
531 ForEachUser(do_uid_user_purge, NULL);
534 CtdlLogPrintf(CTDL_DEBUG, "User purge for auth mode %d is not implemented.\n",
539 transcript = malloc(SIZ);
541 if (users_not_purged == 0) {
542 strcpy(transcript, "The auto-purger was told to purge every user. It is\n"
543 "refusing to do this because it usually indicates a problem\n"
544 "such as an inability to communicate with a name service.\n"
546 while (UserPurgeList != NULL) {
547 pptr = UserPurgeList->next;
549 UserPurgeList = pptr;
555 strcpy(transcript, "The following users have been auto-purged:\n");
556 while (UserPurgeList != NULL) {
557 transcript=realloc(transcript, strlen(transcript)+SIZ);
558 snprintf(&transcript[strlen(transcript)], SIZ, " %s\n",
559 UserPurgeList->name);
560 purge_user(UserPurgeList->name);
561 pptr = UserPurgeList->next;
563 UserPurgeList = pptr;
568 if (num_users_purged > 0) aide_message(transcript, "User Purge Message");
571 if(users_corrupt_msg)
573 aide_message(users_corrupt_msg, "User Corruption Message");
574 free (users_corrupt_msg);
575 users_corrupt_msg = NULL;
580 aide_message(users_zero_msg, "User Zero Message");
581 free (users_zero_msg);
582 users_zero_msg = NULL;
585 CtdlLogPrintf(CTDL_DEBUG, "Purged %d users.\n", num_users_purged);
586 return(num_users_purged);
593 * This is a really cumbersome "garbage collection" function. We have to
594 * delete visits which refer to rooms and/or users which no longer exist. In
595 * order to prevent endless traversals of the room and user files, we first
596 * build linked lists of rooms and users which _do_ exist on the system, then
597 * traverse the visit file, checking each record against those two lists and
598 * purging the ones that do not have a match on _both_ lists. (Remember, if
599 * either the room or user being referred to is no longer on the system, the
600 * record is completely useless.)
602 int PurgeVisits(void) {
603 struct cdbdata *cdbvisit;
605 struct VPurgeList *VisitPurgeList = NULL;
606 struct VPurgeList *vptr;
610 struct ValidRoom *vrptr;
611 struct ValidUser *vuptr;
612 int RoomIsValid, UserIsValid;
614 /* First, load up a table full of valid room/gen combinations */
615 ForEachRoom(AddValidRoom, NULL);
617 /* Then load up a table full of valid user numbers */
618 ForEachUser(AddValidUser, NULL);
620 /* Now traverse through the visits, purging irrelevant records... */
621 cdb_rewind(CDB_VISIT);
622 while(cdbvisit = cdb_next_item(CDB_VISIT), cdbvisit != NULL) {
623 memset(&vbuf, 0, sizeof(struct visit));
624 memcpy(&vbuf, cdbvisit->ptr,
625 ( (cdbvisit->len > sizeof(struct visit)) ?
626 sizeof(struct visit) : cdbvisit->len) );
632 /* Check to see if the room exists */
633 for (vrptr=ValidRoomList; vrptr!=NULL; vrptr=vrptr->next) {
634 if ( (vrptr->vr_roomnum==vbuf.v_roomnum)
635 && (vrptr->vr_roomgen==vbuf.v_roomgen))
639 /* Check to see if the user exists */
640 for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) {
641 if (vuptr->vu_usernum == vbuf.v_usernum)
645 /* Put the record on the purge list if it's dead */
646 if ((RoomIsValid==0) || (UserIsValid==0)) {
647 vptr = (struct VPurgeList *)
648 malloc(sizeof(struct VPurgeList));
649 vptr->next = VisitPurgeList;
650 vptr->vp_roomnum = vbuf.v_roomnum;
651 vptr->vp_roomgen = vbuf.v_roomgen;
652 vptr->vp_usernum = vbuf.v_usernum;
653 VisitPurgeList = vptr;
658 /* Free the valid room/gen combination list */
659 while (ValidRoomList != NULL) {
660 vrptr = ValidRoomList->next;
662 ValidRoomList = vrptr;
665 /* Free the valid user list */
666 while (ValidUserList != NULL) {
667 vuptr = ValidUserList->next;
669 ValidUserList = vuptr;
672 /* Now delete every visit on the purged list */
673 while (VisitPurgeList != NULL) {
674 IndexLen = GenerateRelationshipIndex(IndexBuf,
675 VisitPurgeList->vp_roomnum,
676 VisitPurgeList->vp_roomgen,
677 VisitPurgeList->vp_usernum);
678 cdb_delete(CDB_VISIT, IndexBuf, IndexLen);
679 vptr = VisitPurgeList->next;
680 free(VisitPurgeList);
681 VisitPurgeList = vptr;
689 * Purge the use table of old entries.
692 int PurgeUseTable(void) {
694 struct cdbdata *cdbut;
696 struct UPurgeList *ul = NULL;
697 struct UPurgeList *uptr;
699 /* Phase 1: traverse through the table, discovering old records... */
700 CtdlLogPrintf(CTDL_DEBUG, "Purge use table: phase 1\n");
701 cdb_rewind(CDB_USETABLE);
702 while(cdbut = cdb_next_item(CDB_USETABLE), cdbut != NULL) {
705 * TODODRW: change this to create a new function time_t cdb_get_timestamp( struct cdbdata *)
706 * this will release this file from the serv_network.h
707 * Maybe it could be a macro that extracts and casts the reult
709 memcpy(&ut, cdbut->ptr,
710 ((cdbut->len > sizeof(struct UseTable)) ?
711 sizeof(struct UseTable) : cdbut->len));
714 if ( (time(NULL) - ut.ut_timestamp) > USETABLE_RETAIN ) {
715 uptr = (struct UPurgeList *) malloc(sizeof(struct UPurgeList));
718 safestrncpy(uptr->up_key, ut.ut_msgid, sizeof uptr->up_key);
726 /* Phase 2: delete the records */
727 CtdlLogPrintf(CTDL_DEBUG, "Purge use table: phase 2\n");
729 cdb_delete(CDB_USETABLE, ul->up_key, strlen(ul->up_key));
735 CtdlLogPrintf(CTDL_DEBUG, "Purge use table: finished (purged %d records)\n", purged);
742 * Purge the EUID Index of old records.
745 int PurgeEuidIndexTable(void) {
747 struct cdbdata *cdbei;
748 struct EPurgeList *el = NULL;
749 struct EPurgeList *eptr;
751 struct CtdlMessage *msg = NULL;
753 /* Phase 1: traverse through the table, discovering old records... */
754 CtdlLogPrintf(CTDL_DEBUG, "Purge EUID index: phase 1\n");
755 cdb_rewind(CDB_EUIDINDEX);
756 while(cdbei = cdb_next_item(CDB_EUIDINDEX), cdbei != NULL) {
758 memcpy(&msgnum, cdbei->ptr, sizeof(long));
760 msg = CtdlFetchMessage(msgnum, 0);
762 CtdlFreeMessage(msg); /* it still exists, so do nothing */
765 eptr = (struct EPurgeList *) malloc(sizeof(struct EPurgeList));
768 eptr->ep_keylen = cdbei->len - sizeof(long);
769 eptr->ep_key = malloc(cdbei->len);
770 memcpy(eptr->ep_key, &cdbei->ptr[sizeof(long)], eptr->ep_keylen);
780 /* Phase 2: delete the records */
781 CtdlLogPrintf(CTDL_DEBUG, "Purge euid index: phase 2\n");
783 cdb_delete(CDB_EUIDINDEX, el->ep_key, el->ep_keylen);
790 CtdlLogPrintf(CTDL_DEBUG, "Purge euid index: finished (purged %d records)\n", purged);
797 * Purge OpenID assocations for missing users (theoretically this will never delete anything)
799 int PurgeStaleOpenIDassociations(void) {
800 struct cdbdata *cdboi;
801 struct ctdluser usbuf;
802 HashList *keys = NULL;
804 char *deleteme = NULL;
810 keys = NewHash(1, NULL);
811 if (!keys) return(0);
814 cdb_rewind(CDB_OPENID);
815 while (cdboi = cdb_next_item(CDB_OPENID), cdboi != NULL) {
816 if (cdboi->len > sizeof(long)) {
818 usernum = ((long)*(cdboi->ptr));
819 /* FIXME two different things here, trying to figure out whether this is the
822 CtdlLogPrintf(CTDL_DEBUG, "#1 Evaluating openid association for user %ld\n", usernum);
824 memcpy(&usernum, cdboi->ptr, sizeof(long));
825 CtdlLogPrintf(CTDL_DEBUG, "#2 Evaluating openid association for user %ld\n", usernum);
826 if (getuserbynumber(&usbuf, usernum) != 0) {
827 deleteme = strdup(cdboi->ptr + sizeof(long)),
828 Put(keys, deleteme, strlen(deleteme), deleteme, generic_free_handler);
834 /* Go through the hash list, deleting keys we stored in it */
836 HashPos = GetNewHashPos(keys, 0);
837 while (GetNextHashPos(keys, HashPos, &len, &Key, &Value)!=0)
839 CtdlLogPrintf(CTDL_DEBUG, "Deleting associated OpenID <%s>\n", Value);
840 cdb_delete(CDB_OPENID, Value, strlen(Value));
841 /* note: don't free(Value) -- deleting the hash list will handle this for us */
844 DeleteHashPos(&HashPos);
853 void *purge_databases(void *args)
856 static time_t last_purge = 0;
859 struct CitContext purgerCC;
861 CtdlLogPrintf(CTDL_DEBUG, "Auto-purger_thread() initializing\n");
863 CtdlFillSystemContext(&purgerCC, "purger");
864 citthread_setspecific(MyConKey, (void *)&purgerCC );
866 while (!CtdlThreadCheckStop()) {
867 /* Do the auto-purge if the current hour equals the purge hour,
868 * but not if the operation has already been performed in the
869 * last twelve hours. This is usually enough granularity.
872 localtime_r(&now, &tm);
874 ((tm.tm_hour != config.c_purge_hour) || ((now - last_purge) < 43200))
875 && (force_purge_now == 0)
882 CtdlLogPrintf(CTDL_INFO, "Auto-purger: starting.\n");
884 if (!CtdlThreadCheckStop())
886 retval = PurgeUsers();
887 CtdlLogPrintf(CTDL_NOTICE, "Purged %d users.\n", retval);
890 if (!CtdlThreadCheckStop())
893 CtdlLogPrintf(CTDL_NOTICE, "Expired %d messages.\n", messages_purged);
896 if (!CtdlThreadCheckStop())
898 retval = PurgeRooms();
899 CtdlLogPrintf(CTDL_NOTICE, "Expired %d rooms.\n", retval);
902 if (!CtdlThreadCheckStop())
904 retval = PurgeVisits();
905 CtdlLogPrintf(CTDL_NOTICE, "Purged %d visits.\n", retval);
908 if (!CtdlThreadCheckStop())
910 retval = PurgeUseTable();
911 CtdlLogPrintf(CTDL_NOTICE, "Purged %d entries from the use table.\n", retval);
914 if (!CtdlThreadCheckStop())
916 retval = PurgeEuidIndexTable();
917 CtdlLogPrintf(CTDL_NOTICE, "Purged %d entries from the EUID index.\n", retval);
920 if (!CtdlThreadCheckStop())
922 retval = PurgeStaleOpenIDassociations();
923 CtdlLogPrintf(CTDL_NOTICE, "Purged %d stale OpenID associations.\n", retval);
926 if (!CtdlThreadCheckStop())
928 retval = TDAP_ProcessAdjRefCountQueue();
929 CtdlLogPrintf(CTDL_NOTICE, "Processed %d message reference count adjustments.\n", retval);
932 if (!CtdlThreadCheckStop())
934 CtdlLogPrintf(CTDL_INFO, "Auto-purger: finished.\n");
935 last_purge = now; /* So we don't do it again soon */
939 CtdlLogPrintf(CTDL_INFO, "Auto-purger: STOPPED.\n");
944 /*****************************************************************************/
947 /* The FSCK command has been removed because people were misusing it */
951 void do_fsck_msg(long msgnum, void *userdata) {
952 struct ctdlroomref *ptr;
954 ptr = (struct ctdlroomref *)malloc(sizeof(struct ctdlroomref));
956 ptr->msgnum = msgnum;
960 void do_fsck_room(struct ctdlroom *qrbuf, void *data)
962 getroom(&CC->room, qrbuf->QRname);
963 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, do_fsck_msg, NULL);
967 * Check message reference counts
969 void cmd_fsck(char *argbuf) {
971 struct cdbdata *cdbmsg;
973 struct ctdlroomref *ptr;
976 if (CtdlAccessCheck(ac_aide)) return;
978 /* Lame way of checking whether anyone else is doing this now */
980 cprintf("%d Another FSCK is already running.\n", ERROR + RESOURCE_BUSY);
984 cprintf("%d Checking message reference counts\n", LISTING_FOLLOWS);
986 cprintf("\nThis could take a while. Please be patient!\n\n");
987 cprintf("Gathering pointers...\n");
988 ForEachRoom(do_fsck_room, NULL);
991 cprintf("Checking message base...\n");
992 for (msgnum = 0L; msgnum <= CitControl.MMhighest; ++msgnum) {
994 cdbmsg = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
995 if (cdbmsg != NULL) {
997 cprintf("Message %7ld ", msgnum);
999 GetMetaData(&smi, msgnum);
1000 cprintf("refcount=%-2d ", smi.meta_refcount);
1003 for (ptr = rr; ptr != NULL; ptr = ptr->next) {
1004 if (ptr->msgnum == msgnum) ++realcount;
1006 cprintf("realcount=%-2d\n", realcount);
1008 if ( (smi.meta_refcount != realcount)
1009 || (realcount == 0) ) {
1010 AdjRefCount(msgnum, (smi.meta_refcount - realcount));
1017 cprintf("Freeing memory...\n");
1018 while (rr != NULL) {
1029 #endif /* end of commented-out fsck cmd */
1032 * Manually initiate a run of The Dreaded Auto-Purger (tm)
1034 void cmd_tdap(char *argbuf) {
1035 if (CtdlAccessCheck(ac_aide)) return;
1036 force_purge_now = 1;
1037 cprintf("%d Manually initiating a purger run now.\n", CIT_OK);
1041 /*****************************************************************************/
1043 CTDL_MODULE_INIT(expire)
1047 /* CtdlRegisterProtoHook(cmd_fsck, "FSCK", "Check message ref counts"); */
1048 CtdlRegisterProtoHook(cmd_tdap, "TDAP", "Manually initiate auto-purger");
1051 CtdlThreadCreate("Auto Purger", CTDLTHREAD_BIGSTACK, purge_databases, NULL);
1052 /* return our Subversion id for the Log */