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.
460 * So we make an Aide message to alert to it but don't add it to the purge list
462 if (IsEmptyStr(us->fullname))
464 if (us->usernum > 0L)
467 if (users_corrupt_msg == NULL)
469 users_corrupt_msg = malloc(SIZ);
470 strcpy(users_corrupt_msg, "The auto-purger found the following user numbers with no name.\n"
471 "If the user number is 0 you should report this to the Citadel development\n"
472 "team either by a bugzilla report at http://bugzilla.citadel.org or\n"
473 "posting a message in the Citadel Support room on Uncensored at\n"
474 "https://uncensored.citadel.org You should make it clear that you have seen a\n"
475 "user 0 messages in the Aide room which means a module has not named its\n"
477 "Unfortunately the auto-purger is not yet able to fix this problem.\n"
478 "This problem is not considered serious since a user with no name can\n"
482 users_corrupt_msg=realloc(users_corrupt_msg, strlen(users_corrupt_msg)+SIZ);
483 snprintf(&users_corrupt_msg[strlen(users_corrupt_msg)], SIZ, " %ld\n", us->usernum);
485 else if (us->usernum == 0L)
488 if (users_zero_msg == NULL)
490 users_zero_msg = malloc(SIZ);
491 strcpy(users_zero_msg, "The auto-purger found a user with a user number of 0 but no name.\n"
492 "This is the result of a bug where a private contaxt has been created but\n"
494 "Please report this to the Citadel development team either by a bugzilla\n"
495 "report at http://bugzilla.citadel.org or by posting a message in the\n"
496 "Citadel Support room on Uncensored at https://uncensored.citadel.org\n"
497 "You should make it clear that you have seen a user 0 messages in the\n"
498 "Aide room which means a module has not named its private context.\n\n"
499 "This problem is not considered serious since it does not constitute a\n"
500 "security risk and should not impare system operation.\n"
509 pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList));
510 pptr->next = UserPurgeList;
511 strcpy(pptr->name, us->fullname);
512 UserPurgeList = pptr;
522 int PurgeUsers(void) {
523 struct PurgeList *pptr;
524 int num_users_purged = 0;
525 char *transcript = NULL;
527 CtdlLogPrintf(CTDL_DEBUG, "PurgeUsers() called\n");
528 users_not_purged = 0;
530 switch(config.c_auth_mode) {
531 case AUTHMODE_NATIVE:
532 ForEachUser(do_user_purge, NULL);
535 ForEachUser(do_uid_user_purge, NULL);
538 CtdlLogPrintf(CTDL_DEBUG, "Unknown authentication mode!\n");
542 transcript = malloc(SIZ);
544 if (users_not_purged == 0) {
545 strcpy(transcript, "The auto-purger was told to purge every user. It is\n"
546 "refusing to do this because it usually indicates a problem\n"
547 "such as an inability to communicate with a name service.\n"
549 while (UserPurgeList != NULL) {
550 pptr = UserPurgeList->next;
552 UserPurgeList = pptr;
558 strcpy(transcript, "The following users have been auto-purged:\n");
559 while (UserPurgeList != NULL) {
560 transcript=realloc(transcript, strlen(transcript)+SIZ);
561 snprintf(&transcript[strlen(transcript)], SIZ, " %s\n",
562 UserPurgeList->name);
563 purge_user(UserPurgeList->name);
564 pptr = UserPurgeList->next;
566 UserPurgeList = pptr;
571 if (num_users_purged > 0) aide_message(transcript, "User Purge Message");
574 if(users_corrupt_msg)
576 aide_message(users_corrupt_msg, "User Corruption Message");
577 free (users_corrupt_msg);
578 users_corrupt_msg = NULL;
583 aide_message(users_zero_msg, "User Zero Message");
584 free (users_zero_msg);
585 users_zero_msg = NULL;
588 CtdlLogPrintf(CTDL_DEBUG, "Purged %d users.\n", num_users_purged);
589 return(num_users_purged);
596 * This is a really cumbersome "garbage collection" function. We have to
597 * delete visits which refer to rooms and/or users which no longer exist. In
598 * order to prevent endless traversals of the room and user files, we first
599 * build linked lists of rooms and users which _do_ exist on the system, then
600 * traverse the visit file, checking each record against those two lists and
601 * purging the ones that do not have a match on _both_ lists. (Remember, if
602 * either the room or user being referred to is no longer on the system, the
603 * record is completely useless.)
605 int PurgeVisits(void) {
606 struct cdbdata *cdbvisit;
608 struct VPurgeList *VisitPurgeList = NULL;
609 struct VPurgeList *vptr;
613 struct ValidRoom *vrptr;
614 struct ValidUser *vuptr;
615 int RoomIsValid, UserIsValid;
617 /* First, load up a table full of valid room/gen combinations */
618 ForEachRoom(AddValidRoom, NULL);
620 /* Then load up a table full of valid user numbers */
621 ForEachUser(AddValidUser, NULL);
623 /* Now traverse through the visits, purging irrelevant records... */
624 cdb_rewind(CDB_VISIT);
625 while(cdbvisit = cdb_next_item(CDB_VISIT), cdbvisit != NULL) {
626 memset(&vbuf, 0, sizeof(struct visit));
627 memcpy(&vbuf, cdbvisit->ptr,
628 ( (cdbvisit->len > sizeof(struct visit)) ?
629 sizeof(struct visit) : cdbvisit->len) );
635 /* Check to see if the room exists */
636 for (vrptr=ValidRoomList; vrptr!=NULL; vrptr=vrptr->next) {
637 if ( (vrptr->vr_roomnum==vbuf.v_roomnum)
638 && (vrptr->vr_roomgen==vbuf.v_roomgen))
642 /* Check to see if the user exists */
643 for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) {
644 if (vuptr->vu_usernum == vbuf.v_usernum)
648 /* Put the record on the purge list if it's dead */
649 if ((RoomIsValid==0) || (UserIsValid==0)) {
650 vptr = (struct VPurgeList *)
651 malloc(sizeof(struct VPurgeList));
652 vptr->next = VisitPurgeList;
653 vptr->vp_roomnum = vbuf.v_roomnum;
654 vptr->vp_roomgen = vbuf.v_roomgen;
655 vptr->vp_usernum = vbuf.v_usernum;
656 VisitPurgeList = vptr;
661 /* Free the valid room/gen combination list */
662 while (ValidRoomList != NULL) {
663 vrptr = ValidRoomList->next;
665 ValidRoomList = vrptr;
668 /* Free the valid user list */
669 while (ValidUserList != NULL) {
670 vuptr = ValidUserList->next;
672 ValidUserList = vuptr;
675 /* Now delete every visit on the purged list */
676 while (VisitPurgeList != NULL) {
677 IndexLen = GenerateRelationshipIndex(IndexBuf,
678 VisitPurgeList->vp_roomnum,
679 VisitPurgeList->vp_roomgen,
680 VisitPurgeList->vp_usernum);
681 cdb_delete(CDB_VISIT, IndexBuf, IndexLen);
682 vptr = VisitPurgeList->next;
683 free(VisitPurgeList);
684 VisitPurgeList = vptr;
692 * Purge the use table of old entries.
695 int PurgeUseTable(void) {
697 struct cdbdata *cdbut;
699 struct UPurgeList *ul = NULL;
700 struct UPurgeList *uptr;
702 /* Phase 1: traverse through the table, discovering old records... */
703 CtdlLogPrintf(CTDL_DEBUG, "Purge use table: phase 1\n");
704 cdb_rewind(CDB_USETABLE);
705 while(cdbut = cdb_next_item(CDB_USETABLE), cdbut != NULL) {
708 * TODODRW: change this to create a new function time_t cdb_get_timestamp( struct cdbdata *)
709 * this will release this file from the serv_network.h
710 * Maybe it could be a macro that extracts and casts the reult
712 memcpy(&ut, cdbut->ptr,
713 ((cdbut->len > sizeof(struct UseTable)) ?
714 sizeof(struct UseTable) : cdbut->len));
717 if ( (time(NULL) - ut.ut_timestamp) > USETABLE_RETAIN ) {
718 uptr = (struct UPurgeList *) malloc(sizeof(struct UPurgeList));
721 safestrncpy(uptr->up_key, ut.ut_msgid, sizeof uptr->up_key);
729 /* Phase 2: delete the records */
730 CtdlLogPrintf(CTDL_DEBUG, "Purge use table: phase 2\n");
732 cdb_delete(CDB_USETABLE, ul->up_key, strlen(ul->up_key));
738 CtdlLogPrintf(CTDL_DEBUG, "Purge use table: finished (purged %d records)\n", purged);
745 * Purge the EUID Index of old records.
748 int PurgeEuidIndexTable(void) {
750 struct cdbdata *cdbei;
751 struct EPurgeList *el = NULL;
752 struct EPurgeList *eptr;
754 struct CtdlMessage *msg = NULL;
756 /* Phase 1: traverse through the table, discovering old records... */
757 CtdlLogPrintf(CTDL_DEBUG, "Purge EUID index: phase 1\n");
758 cdb_rewind(CDB_EUIDINDEX);
759 while(cdbei = cdb_next_item(CDB_EUIDINDEX), cdbei != NULL) {
761 memcpy(&msgnum, cdbei->ptr, sizeof(long));
763 msg = CtdlFetchMessage(msgnum, 0);
765 CtdlFreeMessage(msg); /* it still exists, so do nothing */
768 eptr = (struct EPurgeList *) malloc(sizeof(struct EPurgeList));
771 eptr->ep_keylen = cdbei->len - sizeof(long);
772 eptr->ep_key = malloc(cdbei->len);
773 memcpy(eptr->ep_key, &cdbei->ptr[sizeof(long)], eptr->ep_keylen);
783 /* Phase 2: delete the records */
784 CtdlLogPrintf(CTDL_DEBUG, "Purge euid index: phase 2\n");
786 cdb_delete(CDB_EUIDINDEX, el->ep_key, el->ep_keylen);
793 CtdlLogPrintf(CTDL_DEBUG, "Purge euid index: finished (purged %d records)\n", purged);
800 * Purge OpenID assocations for missing users (theoretically this will never delete anything)
802 int PurgeStaleOpenIDassociations(void) {
803 struct cdbdata *cdboi;
804 struct ctdluser usbuf;
805 HashList *keys = NULL;
807 char *deleteme = NULL;
813 keys = NewHash(1, NULL);
814 if (!keys) return(0);
817 cdb_rewind(CDB_OPENID);
818 while (cdboi = cdb_next_item(CDB_OPENID), cdboi != NULL) {
819 if (cdboi->len > sizeof(long)) {
821 usernum = ((long)*(cdboi->ptr));
822 if (getuserbynumber(&usbuf, usernum) != 0) {
823 deleteme = strdup(cdboi->ptr + sizeof(long)),
824 Put(keys, deleteme, strlen(deleteme), deleteme, generic_free_handler);
830 /* Go through the hash list, deleting keys we stored in it */
832 HashPos = GetNewHashPos();
833 while (GetNextHashPos(keys, HashPos, &len, &Key, &Value)!=0)
835 CtdlLogPrintf(CTDL_DEBUG, "Deleting associated OpenID <%s>\n", Value);
836 cdb_delete(CDB_OPENID, Value, strlen(Value));
837 /* note: don't free(Value) -- deleting the hash list will handle this for us */
840 DeleteHashPos(&HashPos);
849 void *purge_databases(void *args)
852 static time_t last_purge = 0;
855 struct CitContext purgerCC;
857 CtdlLogPrintf(CTDL_DEBUG, "Auto-purger_thread() initializing\n");
859 CtdlFillPrivateContext(&purgerCC, "purger");
860 citthread_setspecific(MyConKey, (void *)&purgerCC );
862 while (!CtdlThreadCheckStop()) {
863 /* Do the auto-purge if the current hour equals the purge hour,
864 * but not if the operation has already been performed in the
865 * last twelve hours. This is usually enough granularity.
868 localtime_r(&now, &tm);
869 if ((tm.tm_hour != config.c_purge_hour) || ((now - last_purge) < 43200)) {
875 CtdlLogPrintf(CTDL_INFO, "Auto-purger: starting.\n");
877 if (!CtdlThreadCheckStop())
879 retval = PurgeUsers();
880 CtdlLogPrintf(CTDL_NOTICE, "Purged %d users.\n", retval);
883 if (!CtdlThreadCheckStop())
886 CtdlLogPrintf(CTDL_NOTICE, "Expired %d messages.\n", messages_purged);
889 if (!CtdlThreadCheckStop())
891 retval = PurgeRooms();
892 CtdlLogPrintf(CTDL_NOTICE, "Expired %d rooms.\n", retval);
895 if (!CtdlThreadCheckStop())
897 retval = PurgeVisits();
898 CtdlLogPrintf(CTDL_NOTICE, "Purged %d visits.\n", retval);
901 if (!CtdlThreadCheckStop())
903 retval = PurgeUseTable();
904 CtdlLogPrintf(CTDL_NOTICE, "Purged %d entries from the use table.\n", retval);
907 if (!CtdlThreadCheckStop())
909 retval = PurgeEuidIndexTable();
910 CtdlLogPrintf(CTDL_NOTICE, "Purged %d entries from the EUID index.\n", retval);
913 if (!CtdlThreadCheckStop())
915 retval = PurgeStaleOpenIDassociations();
916 CtdlLogPrintf(CTDL_NOTICE, "Purged %d stale OpenID associations.\n", retval);
919 if (!CtdlThreadCheckStop())
921 retval = TDAP_ProcessAdjRefCountQueue();
922 CtdlLogPrintf(CTDL_NOTICE, "Processed %d message reference count adjustments.\n", retval);
925 if (!CtdlThreadCheckStop())
927 CtdlLogPrintf(CTDL_INFO, "Auto-purger: finished.\n");
928 last_purge = now; /* So we don't do it again soon */
931 CtdlLogPrintf(CTDL_INFO, "Auto-purger: STOPPED.\n");
936 /*****************************************************************************/
939 void do_fsck_msg(long msgnum, void *userdata) {
940 struct ctdlroomref *ptr;
942 ptr = (struct ctdlroomref *)malloc(sizeof(struct ctdlroomref));
944 ptr->msgnum = msgnum;
948 void do_fsck_room(struct ctdlroom *qrbuf, void *data)
950 getroom(&CC->room, qrbuf->QRname);
951 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, do_fsck_msg, NULL);
955 * Check message reference counts
957 void cmd_fsck(char *argbuf) {
959 struct cdbdata *cdbmsg;
961 struct ctdlroomref *ptr;
964 if (CtdlAccessCheck(ac_aide)) return;
966 /* Lame way of checking whether anyone else is doing this now */
968 cprintf("%d Another FSCK is already running.\n", ERROR + RESOURCE_BUSY);
972 cprintf("%d Checking message reference counts\n", LISTING_FOLLOWS);
974 cprintf("\nThis could take a while. Please be patient!\n\n");
975 cprintf("Gathering pointers...\n");
976 ForEachRoom(do_fsck_room, NULL);
979 cprintf("Checking message base...\n");
980 for (msgnum = 0L; msgnum <= CitControl.MMhighest; ++msgnum) {
982 cdbmsg = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
983 if (cdbmsg != NULL) {
985 cprintf("Message %7ld ", msgnum);
987 GetMetaData(&smi, msgnum);
988 cprintf("refcount=%-2d ", smi.meta_refcount);
991 for (ptr = rr; ptr != NULL; ptr = ptr->next) {
992 if (ptr->msgnum == msgnum) ++realcount;
994 cprintf("realcount=%-2d\n", realcount);
996 if ( (smi.meta_refcount != realcount)
997 || (realcount == 0) ) {
998 AdjRefCount(msgnum, (smi.meta_refcount - realcount));
1005 cprintf("Freeing memory...\n");
1006 while (rr != NULL) {
1020 /*****************************************************************************/
1022 CTDL_MODULE_INIT(expire)
1026 CtdlRegisterProtoHook(cmd_fsck, "FSCK", "Check message ref counts");
1029 CtdlThreadCreate("Auto Purger", CTDLTHREAD_BIGSTACK, purge_databases, NULL);
1030 /* return our Subversion id for the Log */