4 * This module handles the expiry of old messages and the purging of old users.
10 * A brief technical discussion:
12 * Several of the purge operations found in this module operate in two
13 * stages: the first stage generates a linked list of objects to be deleted,
14 * then the second stage deletes all listed objects from the database.
16 * At first glance this may seem cumbersome and unnecessary. The reason it is
17 * implemented in this way is because Berkeley DB, and possibly other backends
18 * we may hook into in the future, explicitly do _not_ support the deletion of
19 * records from a file while the file is being traversed. The delete operation
20 * will succeed, but the traversal is not guaranteed to visit every object if
21 * this is done. Therefore we utilize the two-stage purge.
23 * When using Berkeley DB, there's another reason for the two-phase purge: we
24 * don't want the entire thing being done as one huge transaction.
26 * You'll also notice that we build the in-memory list of records to be deleted
27 * sometimes with a linked list and sometimes with a hash table. There is no
28 * reason for this aside from the fact that the linked list ones were written
29 * before we had the hash table library available.
41 #include <sys/types.h>
43 #if TIME_WITH_SYS_TIME
44 # include <sys/time.h>
48 # include <sys/time.h>
57 #include <libcitadel.h>
60 #include "citserver.h"
69 #include "serv_network.h" /* Needed for defenition of UseTable */
72 #include "ctdl_module.h"
76 struct PurgeList *next;
77 char name[ROOMNAMELEN]; /* use the larger of username or roomname */
81 struct VPurgeList *next;
88 struct ValidRoom *next;
94 struct ValidUser *next;
100 struct ctdlroomref *next;
105 struct UPurgeList *next;
110 struct EPurgeList *next;
116 struct PurgeList *UserPurgeList = NULL;
117 struct PurgeList *RoomPurgeList = NULL;
118 struct ValidRoom *ValidRoomList = NULL;
119 struct ValidUser *ValidUserList = NULL;
121 int users_not_purged;
122 char *users_corrupt_msg = NULL;
123 char *users_zero_msg = NULL;
125 struct ctdlroomref *rr = NULL;
127 extern struct CitContext *ContextList;
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 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf->QRnumber, sizeof(long));
158 msglist = malloc(cdbfr->len);
159 memcpy(msglist, cdbfr->ptr, cdbfr->len);
160 num_msgs = cdbfr->len / sizeof(long);
164 /* Nothing to do if there aren't any messages */
166 if (msglist != NULL) free(msglist);
170 /* If the room is set to expire by count, do that */
171 if (epbuf.expire_mode == EXPIRE_NUMMSGS) {
172 if (num_msgs > epbuf.expire_value) {
173 for (a=0; a<(num_msgs - epbuf.expire_value); ++a) {
174 fprintf(purgelist, "m=%ld\n", msglist[a]);
180 /* If the room is set to expire by age... */
181 if (epbuf.expire_mode == EXPIRE_AGE) {
182 for (a=0; a<num_msgs; ++a) {
185 msg = CtdlFetchMessage(delnum, 0); /* dont need body */
187 xtime = atol(msg->cm_fields['T']);
188 CtdlFreeMessage(msg);
194 && (now - xtime > (time_t)(epbuf.expire_value * 86400L))) {
195 fprintf(purgelist, "m=%ld\n", delnum);
201 if (msglist != NULL) free(msglist);
206 * Second phase of message purge -- read list of msgs from temp file and
209 void DoPurgeMessages(FILE *purgelist) {
210 char roomname[ROOMNAMELEN];
215 strcpy(roomname, "nonexistent room ___ ___");
216 while (fgets(buf, sizeof buf, purgelist) != NULL) {
217 buf[strlen(buf)-1]=0;
218 if (!strncasecmp(buf, "r=", 2)) {
219 strcpy(roomname, &buf[2]);
221 if (!strncasecmp(buf, "m=", 2)) {
222 msgnum = atol(&buf[2]);
224 CtdlDeleteMessages(roomname, &msgnum, 1, "");
231 void PurgeMessages(void) {
234 CtdlLogPrintf(CTDL_DEBUG, "PurgeMessages() called\n");
237 purgelist = tmpfile();
238 if (purgelist == NULL) {
239 CtdlLogPrintf(CTDL_CRIT, "Can't create purgelist temp file: %s\n",
244 ForEachRoom(GatherPurgeMessages, (void *)purgelist );
245 DoPurgeMessages(purgelist);
250 void AddValidUser(struct ctdluser *usbuf, void *data) {
251 struct ValidUser *vuptr;
253 vuptr = (struct ValidUser *)malloc(sizeof(struct ValidUser));
254 vuptr->next = ValidUserList;
255 vuptr->vu_usernum = usbuf->usernum;
256 ValidUserList = vuptr;
259 void AddValidRoom(struct ctdlroom *qrbuf, void *data) {
260 struct ValidRoom *vrptr;
262 vrptr = (struct ValidRoom *)malloc(sizeof(struct ValidRoom));
263 vrptr->next = ValidRoomList;
264 vrptr->vr_roomnum = qrbuf->QRnumber;
265 vrptr->vr_roomgen = qrbuf->QRgen;
266 ValidRoomList = vrptr;
269 void DoPurgeRooms(struct ctdlroom *qrbuf, void *data) {
270 time_t age, purge_secs;
271 struct PurgeList *pptr;
272 struct ValidUser *vuptr;
275 /* For mailbox rooms, there's only one purging rule: if the user who
276 * owns the room still exists, we keep the room; otherwise, we purge
277 * it. Bypass any other rules.
279 if (qrbuf->QRflags & QR_MAILBOX) {
280 /* if user not found, do_purge will be 1 */
282 for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) {
283 if (vuptr->vu_usernum == atol(qrbuf->QRname)) {
289 /* Any of these attributes render a room non-purgable */
290 if (qrbuf->QRflags & QR_PERMANENT) return;
291 if (qrbuf->QRflags & QR_DIRECTORY) return;
292 if (qrbuf->QRflags & QR_NETWORK) return;
293 if (!strcasecmp(qrbuf->QRname, SYSCONFIGROOM)) return;
294 if (is_noneditable(qrbuf)) return;
296 /* If we don't know the modification date, be safe and don't purge */
297 if (qrbuf->QRmtime <= (time_t)0) return;
299 /* If no room purge time is set, be safe and don't purge */
300 if (config.c_roompurge < 0) return;
302 /* Otherwise, check the date of last modification */
303 age = time(NULL) - (qrbuf->QRmtime);
304 purge_secs = (time_t)config.c_roompurge * (time_t)86400;
305 if (purge_secs <= (time_t)0) return;
306 CtdlLogPrintf(CTDL_DEBUG, "<%s> is <%ld> seconds old\n", qrbuf->QRname, (long)age);
307 if (age > purge_secs) do_purge = 1;
311 pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList));
312 pptr->next = RoomPurgeList;
313 strcpy(pptr->name, qrbuf->QRname);
314 RoomPurgeList = pptr;
321 int PurgeRooms(void) {
322 struct PurgeList *pptr;
323 int num_rooms_purged = 0;
324 struct ctdlroom qrbuf;
325 struct ValidUser *vuptr;
326 char *transcript = NULL;
328 CtdlLogPrintf(CTDL_DEBUG, "PurgeRooms() called\n");
331 /* Load up a table full of valid user numbers so we can delete
332 * user-owned rooms for users who no longer exist */
333 ForEachUser(AddValidUser, NULL);
335 /* Then cycle through the room file */
336 ForEachRoom(DoPurgeRooms, NULL);
338 /* Free the valid user list */
339 while (ValidUserList != NULL) {
340 vuptr = ValidUserList->next;
342 ValidUserList = vuptr;
346 transcript = malloc(SIZ);
347 strcpy(transcript, "The following rooms have been auto-purged:\n");
349 while (RoomPurgeList != NULL) {
350 if (getroom(&qrbuf, RoomPurgeList->name) == 0) {
351 transcript=realloc(transcript, strlen(transcript)+SIZ);
352 snprintf(&transcript[strlen(transcript)], SIZ, " %s\n",
356 pptr = RoomPurgeList->next;
358 RoomPurgeList = pptr;
362 if (num_rooms_purged > 0) aide_message(transcript, "Room Autopurger Message");
365 CtdlLogPrintf(CTDL_DEBUG, "Purged %d rooms.\n", num_rooms_purged);
366 return(num_rooms_purged);
371 * Back end function to check user accounts for associated Unix accounts
372 * which no longer exist. (Only relevant for host auth mode.)
374 void do_uid_user_purge(struct ctdluser *us, void *data) {
375 struct PurgeList *pptr;
377 if ((us->uid != (-1)) && (us->uid != CTDLUID)) {
378 if (getpwuid(us->uid) == NULL) {
379 pptr = (struct PurgeList *)
380 malloc(sizeof(struct PurgeList));
381 pptr->next = UserPurgeList;
382 strcpy(pptr->name, us->fullname);
383 UserPurgeList = pptr;
395 * Back end function to check user accounts for expiration.
397 void do_user_purge(struct ctdluser *us, void *data) {
401 struct PurgeList *pptr;
403 /* Set purge time; if the user overrides the system default, use it */
404 if (us->USuserpurge > 0) {
405 purge_time = ((time_t)us->USuserpurge) * 86400L;
408 purge_time = ((time_t)config.c_userpurge) * 86400L;
411 /* The default rule is to not purge. */
414 /* If the user hasn't called in two months and expiring of accounts is turned on, his/her account
415 * has expired, so purge the record.
417 if (config.c_userpurge > 0)
420 if ((now - us->lastcall) > purge_time) purge = 1;
423 /* If the record is marked as permanent, don't purge it.
425 if (us->flags & US_PERM) purge = 0;
427 /* If the user is an Aide, don't purge him/her/it.
429 if (us->axlevel == 6) purge = 0;
431 /* If the access level is 0, the record should already have been
432 * deleted, but maybe the user was logged in at the time or something.
433 * Delete the record now.
435 if (us->axlevel == 0) purge = 1;
437 /* If the user set his/her password to 'deleteme', he/she
438 * wishes to be deleted, so purge the record.
439 * Moved this lower down so that aides and permanent users get purged if they ask to be.
441 if (!strcasecmp(us->password, "deleteme")) purge = 1;
443 /* 0 calls is impossible. If there are 0 calls, it must
444 * be a corrupted record, so purge it.
445 * Actually it is possible if an Aide created the user so now we check for less than 0 (DRW)
447 if (us->timescalled < 0) purge = 1;
449 /* User number 0, as well as any negative user number, is
452 if (us->usernum < 0L) purge = 1;
454 /** Don't purge user 0. That user is there for the system */
455 if (us->usernum == 0) purge = 0;
457 /* If the user has no full name entry then we can't purge them
458 * since the actual purge can't find them.
459 * This shouldn't happen but does somehow.
461 if (IsEmptyStr(us->fullname))
465 ** Keeping this block of code for later referance.
466 * We don't actually want to send the user 0 messages as part of the purger cycle
467 * But we might want to use some of this code to do a user database integrity check
468 * some time in the future.
470 if (us->usernum > 0L)
473 if (users_corrupt_msg == NULL)
475 users_corrupt_msg = malloc(SIZ);
476 strcpy(users_corrupt_msg, "The auto-purger found the following user numbers with no name.\n"
477 "If the user number is 0 you should report this to the Citadel development\n"
478 "team either by a bugzilla report at http://bugzilla.citadel.org or\n"
479 "posting a message in the Citadel Support room on Uncensored at\n"
480 "https://uncensored.citadel.org You should make it clear that you have seen a\n"
481 "user 0 messages in the Aide room which means a module has not named its\n"
483 "Unfortunately the auto-purger is not yet able to fix this problem.\n"
484 "This problem is not considered serious since a user with no name can\n"
488 users_corrupt_msg=realloc(users_corrupt_msg, strlen(users_corrupt_msg)+SIZ);
489 snprintf(&users_corrupt_msg[strlen(users_corrupt_msg)], SIZ, " %ld\n", us->usernum);
491 else if (us->usernum == 0L)
494 if (users_zero_msg == NULL)
496 users_zero_msg = malloc(SIZ);
497 strcpy(users_zero_msg, "The auto-purger found a user with a user number of 0 but no name.\n"
498 "This is the result of a bug where a private contaxt has been created but\n"
500 "Please report this to the Citadel development team either by a bugzilla\n"
501 "report at http://bugzilla.citadel.org or by posting a message in the\n"
502 "Citadel Support room on Uncensored at https://uncensored.citadel.org\n"
503 "You should make it clear that you have seen a user 0 messages in the\n"
504 "Aide room which means a module has not named its private context.\n\n"
505 "This problem is not considered serious since it does not constitute a\n"
506 "security risk and should not impare system operation.\n"
515 pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList));
516 pptr->next = UserPurgeList;
517 strcpy(pptr->name, us->fullname);
518 UserPurgeList = pptr;
528 int PurgeUsers(void) {
529 struct PurgeList *pptr;
530 int num_users_purged = 0;
531 char *transcript = NULL;
533 CtdlLogPrintf(CTDL_DEBUG, "PurgeUsers() called\n");
534 users_not_purged = 0;
536 switch(config.c_auth_mode) {
537 case AUTHMODE_NATIVE:
538 ForEachUser(do_user_purge, NULL);
541 ForEachUser(do_uid_user_purge, NULL);
544 CtdlLogPrintf(CTDL_DEBUG, "Unknown authentication mode!\n");
548 transcript = malloc(SIZ);
550 if (users_not_purged == 0) {
551 strcpy(transcript, "The auto-purger was told to purge every user. It is\n"
552 "refusing to do this because it usually indicates a problem\n"
553 "such as an inability to communicate with a name service.\n"
555 while (UserPurgeList != NULL) {
556 pptr = UserPurgeList->next;
558 UserPurgeList = pptr;
564 strcpy(transcript, "The following users have been auto-purged:\n");
565 while (UserPurgeList != NULL) {
566 transcript=realloc(transcript, strlen(transcript)+SIZ);
567 snprintf(&transcript[strlen(transcript)], SIZ, " %s\n",
568 UserPurgeList->name);
569 purge_user(UserPurgeList->name);
570 pptr = UserPurgeList->next;
572 UserPurgeList = pptr;
577 if (num_users_purged > 0) aide_message(transcript, "User Purge Message");
580 if(users_corrupt_msg)
582 aide_message(users_corrupt_msg, "User Corruption Message");
583 free (users_corrupt_msg);
584 users_corrupt_msg = NULL;
589 aide_message(users_zero_msg, "User Zero Message");
590 free (users_zero_msg);
591 users_zero_msg = NULL;
594 CtdlLogPrintf(CTDL_DEBUG, "Purged %d users.\n", num_users_purged);
595 return(num_users_purged);
602 * This is a really cumbersome "garbage collection" function. We have to
603 * delete visits which refer to rooms and/or users which no longer exist. In
604 * order to prevent endless traversals of the room and user files, we first
605 * build linked lists of rooms and users which _do_ exist on the system, then
606 * traverse the visit file, checking each record against those two lists and
607 * purging the ones that do not have a match on _both_ lists. (Remember, if
608 * either the room or user being referred to is no longer on the system, the
609 * record is completely useless.)
611 int PurgeVisits(void) {
612 struct cdbdata *cdbvisit;
614 struct VPurgeList *VisitPurgeList = NULL;
615 struct VPurgeList *vptr;
619 struct ValidRoom *vrptr;
620 struct ValidUser *vuptr;
621 int RoomIsValid, UserIsValid;
623 /* First, load up a table full of valid room/gen combinations */
624 ForEachRoom(AddValidRoom, NULL);
626 /* Then load up a table full of valid user numbers */
627 ForEachUser(AddValidUser, NULL);
629 /* Now traverse through the visits, purging irrelevant records... */
630 cdb_rewind(CDB_VISIT);
631 while(cdbvisit = cdb_next_item(CDB_VISIT), cdbvisit != NULL) {
632 memset(&vbuf, 0, sizeof(struct visit));
633 memcpy(&vbuf, cdbvisit->ptr,
634 ( (cdbvisit->len > sizeof(struct visit)) ?
635 sizeof(struct visit) : cdbvisit->len) );
641 /* Check to see if the room exists */
642 for (vrptr=ValidRoomList; vrptr!=NULL; vrptr=vrptr->next) {
643 if ( (vrptr->vr_roomnum==vbuf.v_roomnum)
644 && (vrptr->vr_roomgen==vbuf.v_roomgen))
648 /* Check to see if the user exists */
649 for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) {
650 if (vuptr->vu_usernum == vbuf.v_usernum)
654 /* Put the record on the purge list if it's dead */
655 if ((RoomIsValid==0) || (UserIsValid==0)) {
656 vptr = (struct VPurgeList *)
657 malloc(sizeof(struct VPurgeList));
658 vptr->next = VisitPurgeList;
659 vptr->vp_roomnum = vbuf.v_roomnum;
660 vptr->vp_roomgen = vbuf.v_roomgen;
661 vptr->vp_usernum = vbuf.v_usernum;
662 VisitPurgeList = vptr;
667 /* Free the valid room/gen combination list */
668 while (ValidRoomList != NULL) {
669 vrptr = ValidRoomList->next;
671 ValidRoomList = vrptr;
674 /* Free the valid user list */
675 while (ValidUserList != NULL) {
676 vuptr = ValidUserList->next;
678 ValidUserList = vuptr;
681 /* Now delete every visit on the purged list */
682 while (VisitPurgeList != NULL) {
683 IndexLen = GenerateRelationshipIndex(IndexBuf,
684 VisitPurgeList->vp_roomnum,
685 VisitPurgeList->vp_roomgen,
686 VisitPurgeList->vp_usernum);
687 cdb_delete(CDB_VISIT, IndexBuf, IndexLen);
688 vptr = VisitPurgeList->next;
689 free(VisitPurgeList);
690 VisitPurgeList = vptr;
698 * Purge the use table of old entries.
701 int PurgeUseTable(void) {
703 struct cdbdata *cdbut;
705 struct UPurgeList *ul = NULL;
706 struct UPurgeList *uptr;
708 /* Phase 1: traverse through the table, discovering old records... */
709 CtdlLogPrintf(CTDL_DEBUG, "Purge use table: phase 1\n");
710 cdb_rewind(CDB_USETABLE);
711 while(cdbut = cdb_next_item(CDB_USETABLE), cdbut != NULL) {
714 * TODODRW: change this to create a new function time_t cdb_get_timestamp( struct cdbdata *)
715 * this will release this file from the serv_network.h
716 * Maybe it could be a macro that extracts and casts the reult
718 memcpy(&ut, cdbut->ptr,
719 ((cdbut->len > sizeof(struct UseTable)) ?
720 sizeof(struct UseTable) : cdbut->len));
723 if ( (time(NULL) - ut.ut_timestamp) > USETABLE_RETAIN ) {
724 uptr = (struct UPurgeList *) malloc(sizeof(struct UPurgeList));
727 safestrncpy(uptr->up_key, ut.ut_msgid, sizeof uptr->up_key);
735 /* Phase 2: delete the records */
736 CtdlLogPrintf(CTDL_DEBUG, "Purge use table: phase 2\n");
738 cdb_delete(CDB_USETABLE, ul->up_key, strlen(ul->up_key));
744 CtdlLogPrintf(CTDL_DEBUG, "Purge use table: finished (purged %d records)\n", purged);
751 * Purge the EUID Index of old records.
754 int PurgeEuidIndexTable(void) {
756 struct cdbdata *cdbei;
757 struct EPurgeList *el = NULL;
758 struct EPurgeList *eptr;
760 struct CtdlMessage *msg = NULL;
762 /* Phase 1: traverse through the table, discovering old records... */
763 CtdlLogPrintf(CTDL_DEBUG, "Purge EUID index: phase 1\n");
764 cdb_rewind(CDB_EUIDINDEX);
765 while(cdbei = cdb_next_item(CDB_EUIDINDEX), cdbei != NULL) {
767 memcpy(&msgnum, cdbei->ptr, sizeof(long));
769 msg = CtdlFetchMessage(msgnum, 0);
771 CtdlFreeMessage(msg); /* it still exists, so do nothing */
774 eptr = (struct EPurgeList *) malloc(sizeof(struct EPurgeList));
777 eptr->ep_keylen = cdbei->len - sizeof(long);
778 eptr->ep_key = malloc(cdbei->len);
779 memcpy(eptr->ep_key, &cdbei->ptr[sizeof(long)], eptr->ep_keylen);
789 /* Phase 2: delete the records */
790 CtdlLogPrintf(CTDL_DEBUG, "Purge euid index: phase 2\n");
792 cdb_delete(CDB_EUIDINDEX, el->ep_key, el->ep_keylen);
799 CtdlLogPrintf(CTDL_DEBUG, "Purge euid index: finished (purged %d records)\n", purged);
806 * Purge OpenID assocations for missing users (theoretically this will never delete anything)
808 int PurgeStaleOpenIDassociations(void) {
809 struct cdbdata *cdboi;
810 struct ctdluser usbuf;
811 HashList *keys = NULL;
813 char *deleteme = NULL;
819 keys = NewHash(1, NULL);
820 if (!keys) return(0);
823 cdb_rewind(CDB_OPENID);
824 while (cdboi = cdb_next_item(CDB_OPENID), cdboi != NULL) {
825 if (cdboi->len > sizeof(long)) {
827 usernum = ((long)*(cdboi->ptr));
828 if (getuserbynumber(&usbuf, usernum) != 0) {
829 deleteme = strdup(cdboi->ptr + sizeof(long)),
830 Put(keys, deleteme, strlen(deleteme), deleteme, generic_free_handler);
836 /* Go through the hash list, deleting keys we stored in it */
838 HashPos = GetNewHashPos();
839 while (GetNextHashPos(keys, HashPos, &len, &Key, &Value)!=0)
841 CtdlLogPrintf(CTDL_DEBUG, "Deleting associated OpenID <%s>\n", Value);
842 cdb_delete(CDB_OPENID, Value, strlen(Value));
843 /* note: don't free(Value) -- deleting the hash list will handle this for us */
846 DeleteHashPos(&HashPos);
855 void *purge_databases(void *args)
858 static time_t last_purge = 0;
861 struct CitContext purgerCC;
863 CtdlLogPrintf(CTDL_DEBUG, "Auto-purger_thread() initializing\n");
865 CtdlFillPrivateContext(&purgerCC, "purger");
866 citthread_setspecific(MyConKey, (void *)&purgerCC );
868 while (!CtdlThreadCheckStop()) {
869 /* Do the auto-purge if the current hour equals the purge hour,
870 * but not if the operation has already been performed in the
871 * last twelve hours. This is usually enough granularity.
874 localtime_r(&now, &tm);
875 if ((tm.tm_hour != config.c_purge_hour) || ((now - last_purge) < 43200)) {
881 CtdlLogPrintf(CTDL_INFO, "Auto-purger: starting.\n");
883 if (!CtdlThreadCheckStop())
885 retval = PurgeUsers();
886 CtdlLogPrintf(CTDL_NOTICE, "Purged %d users.\n", retval);
889 if (!CtdlThreadCheckStop())
892 CtdlLogPrintf(CTDL_NOTICE, "Expired %d messages.\n", messages_purged);
895 if (!CtdlThreadCheckStop())
897 retval = PurgeRooms();
898 CtdlLogPrintf(CTDL_NOTICE, "Expired %d rooms.\n", retval);
901 if (!CtdlThreadCheckStop())
903 retval = PurgeVisits();
904 CtdlLogPrintf(CTDL_NOTICE, "Purged %d visits.\n", retval);
907 if (!CtdlThreadCheckStop())
909 retval = PurgeUseTable();
910 CtdlLogPrintf(CTDL_NOTICE, "Purged %d entries from the use table.\n", retval);
913 if (!CtdlThreadCheckStop())
915 retval = PurgeEuidIndexTable();
916 CtdlLogPrintf(CTDL_NOTICE, "Purged %d entries from the EUID index.\n", retval);
919 if (!CtdlThreadCheckStop())
921 retval = PurgeStaleOpenIDassociations();
922 CtdlLogPrintf(CTDL_NOTICE, "Purged %d stale OpenID associations.\n", retval);
925 if (!CtdlThreadCheckStop())
927 retval = TDAP_ProcessAdjRefCountQueue();
928 CtdlLogPrintf(CTDL_NOTICE, "Processed %d message reference count adjustments.\n", retval);
931 if (!CtdlThreadCheckStop())
933 CtdlLogPrintf(CTDL_INFO, "Auto-purger: finished.\n");
934 last_purge = now; /* So we don't do it again soon */
937 CtdlLogPrintf(CTDL_INFO, "Auto-purger: STOPPED.\n");
942 /*****************************************************************************/
945 void do_fsck_msg(long msgnum, void *userdata) {
946 struct ctdlroomref *ptr;
948 ptr = (struct ctdlroomref *)malloc(sizeof(struct ctdlroomref));
950 ptr->msgnum = msgnum;
954 void do_fsck_room(struct ctdlroom *qrbuf, void *data)
956 getroom(&CC->room, qrbuf->QRname);
957 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, do_fsck_msg, NULL);
961 * Check message reference counts
963 void cmd_fsck(char *argbuf) {
965 struct cdbdata *cdbmsg;
967 struct ctdlroomref *ptr;
970 if (CtdlAccessCheck(ac_aide)) return;
972 /* Lame way of checking whether anyone else is doing this now */
974 cprintf("%d Another FSCK is already running.\n", ERROR + RESOURCE_BUSY);
978 cprintf("%d Checking message reference counts\n", LISTING_FOLLOWS);
980 cprintf("\nThis could take a while. Please be patient!\n\n");
981 cprintf("Gathering pointers...\n");
982 ForEachRoom(do_fsck_room, NULL);
985 cprintf("Checking message base...\n");
986 for (msgnum = 0L; msgnum <= CitControl.MMhighest; ++msgnum) {
988 cdbmsg = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
989 if (cdbmsg != NULL) {
991 cprintf("Message %7ld ", msgnum);
993 GetMetaData(&smi, msgnum);
994 cprintf("refcount=%-2d ", smi.meta_refcount);
997 for (ptr = rr; ptr != NULL; ptr = ptr->next) {
998 if (ptr->msgnum == msgnum) ++realcount;
1000 cprintf("realcount=%-2d\n", realcount);
1002 if ( (smi.meta_refcount != realcount)
1003 || (realcount == 0) ) {
1004 AdjRefCount(msgnum, (smi.meta_refcount - realcount));
1011 cprintf("Freeing memory...\n");
1012 while (rr != NULL) {
1026 /*****************************************************************************/
1028 CTDL_MODULE_INIT(expire)
1032 CtdlRegisterProtoHook(cmd_fsck, "FSCK", "Check message ref counts");
1035 CtdlThreadCreate("Auto Purger", CTDLTHREAD_BIGSTACK, purge_databases, NULL);
1036 /* return our Subversion id for the Log */