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;
124 struct ctdlroomref *rr = NULL;
125 extern struct CitContext *ContextList;
126 int force_purge_now = 0; /* set to nonzero to force a run right now */
130 * First phase of message purge -- gather the locations of messages which
131 * qualify for purging and write them to a temp file.
133 void GatherPurgeMessages(struct ctdlroom *qrbuf, void *data) {
134 struct ExpirePolicy epbuf;
137 struct CtdlMessage *msg = NULL;
139 struct cdbdata *cdbfr;
140 long *msglist = NULL;
144 purgelist = (FILE *)data;
145 fprintf(purgelist, "r=%s\n", qrbuf->QRname);
148 GetExpirePolicy(&epbuf, qrbuf);
150 /* If the room is set to never expire messages ... do nothing */
151 if (epbuf.expire_mode == EXPIRE_NEXTLEVEL) return;
152 if (epbuf.expire_mode == EXPIRE_MANUAL) return;
154 /* Don't purge messages containing system configuration, dumbass. */
155 if (!strcasecmp(qrbuf->QRname, SYSCONFIGROOM)) return;
157 /* Ok, we got this far ... now let's see what's in the room */
158 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf->QRnumber, sizeof(long));
161 msglist = malloc(cdbfr->len);
162 memcpy(msglist, cdbfr->ptr, cdbfr->len);
163 num_msgs = cdbfr->len / sizeof(long);
167 /* Nothing to do if there aren't any messages */
169 if (msglist != NULL) free(msglist);
174 /* If the room is set to expire by count, do that */
175 if (epbuf.expire_mode == EXPIRE_NUMMSGS) {
176 if (num_msgs > epbuf.expire_value) {
177 for (a=0; a<(num_msgs - epbuf.expire_value); ++a) {
178 fprintf(purgelist, "m=%ld\n", msglist[a]);
184 /* If the room is set to expire by age... */
185 if (epbuf.expire_mode == EXPIRE_AGE) {
186 for (a=0; a<num_msgs; ++a) {
189 msg = CtdlFetchMessage(delnum, 0); /* dont need body */
191 xtime = atol(msg->cm_fields['T']);
192 CtdlFreeMessage(msg);
198 && (now - xtime > (time_t)(epbuf.expire_value * 86400L))) {
199 fprintf(purgelist, "m=%ld\n", delnum);
205 if (msglist != NULL) free(msglist);
210 * Second phase of message purge -- read list of msgs from temp file and
213 void DoPurgeMessages(FILE *purgelist) {
214 char roomname[ROOMNAMELEN];
219 strcpy(roomname, "nonexistent room ___ ___");
220 while (fgets(buf, sizeof buf, purgelist) != NULL) {
221 buf[strlen(buf)-1]=0;
222 if (!strncasecmp(buf, "r=", 2)) {
223 strcpy(roomname, &buf[2]);
225 if (!strncasecmp(buf, "m=", 2)) {
226 msgnum = atol(&buf[2]);
228 CtdlDeleteMessages(roomname, &msgnum, 1, "");
235 void PurgeMessages(void) {
238 CtdlLogPrintf(CTDL_DEBUG, "PurgeMessages() called\n");
241 purgelist = tmpfile();
242 if (purgelist == NULL) {
243 CtdlLogPrintf(CTDL_CRIT, "Can't create purgelist temp file: %s\n",
248 ForEachRoom(GatherPurgeMessages, (void *)purgelist );
249 DoPurgeMessages(purgelist);
254 void AddValidUser(struct ctdluser *usbuf, void *data) {
255 struct ValidUser *vuptr;
257 vuptr = (struct ValidUser *)malloc(sizeof(struct ValidUser));
258 vuptr->next = ValidUserList;
259 vuptr->vu_usernum = usbuf->usernum;
260 ValidUserList = vuptr;
263 void AddValidRoom(struct ctdlroom *qrbuf, void *data) {
264 struct ValidRoom *vrptr;
266 vrptr = (struct ValidRoom *)malloc(sizeof(struct ValidRoom));
267 vrptr->next = ValidRoomList;
268 vrptr->vr_roomnum = qrbuf->QRnumber;
269 vrptr->vr_roomgen = qrbuf->QRgen;
270 ValidRoomList = vrptr;
273 void DoPurgeRooms(struct ctdlroom *qrbuf, void *data) {
274 time_t age, purge_secs;
275 struct PurgeList *pptr;
276 struct ValidUser *vuptr;
279 /* For mailbox rooms, there's only one purging rule: if the user who
280 * owns the room still exists, we keep the room; otherwise, we purge
281 * it. Bypass any other rules.
283 if (qrbuf->QRflags & QR_MAILBOX) {
284 /* if user not found, do_purge will be 1 */
286 for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) {
287 if (vuptr->vu_usernum == atol(qrbuf->QRname)) {
293 /* Any of these attributes render a room non-purgable */
294 if (qrbuf->QRflags & QR_PERMANENT) return;
295 if (qrbuf->QRflags & QR_DIRECTORY) return;
296 if (qrbuf->QRflags & QR_NETWORK) return;
297 if (qrbuf->QRflags2 & QR2_SYSTEM) return;
298 if (!strcasecmp(qrbuf->QRname, SYSCONFIGROOM)) return;
299 if (is_noneditable(qrbuf)) return;
301 /* If we don't know the modification date, be safe and don't purge */
302 if (qrbuf->QRmtime <= (time_t)0) return;
304 /* If no room purge time is set, be safe and don't purge */
305 if (config.c_roompurge < 0) return;
307 /* Otherwise, check the date of last modification */
308 age = time(NULL) - (qrbuf->QRmtime);
309 purge_secs = (time_t)config.c_roompurge * (time_t)86400;
310 if (purge_secs <= (time_t)0) return;
311 CtdlLogPrintf(CTDL_DEBUG, "<%s> is <%ld> seconds old\n", qrbuf->QRname, (long)age);
312 if (age > purge_secs) do_purge = 1;
316 pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList));
317 pptr->next = RoomPurgeList;
318 strcpy(pptr->name, qrbuf->QRname);
319 RoomPurgeList = pptr;
326 int PurgeRooms(void) {
327 struct PurgeList *pptr;
328 int num_rooms_purged = 0;
329 struct ctdlroom qrbuf;
330 struct ValidUser *vuptr;
331 char *transcript = NULL;
333 CtdlLogPrintf(CTDL_DEBUG, "PurgeRooms() called\n");
336 /* Load up a table full of valid user numbers so we can delete
337 * user-owned rooms for users who no longer exist */
338 ForEachUser(AddValidUser, NULL);
340 /* Then cycle through the room file */
341 ForEachRoom(DoPurgeRooms, NULL);
343 /* Free the valid user list */
344 while (ValidUserList != NULL) {
345 vuptr = ValidUserList->next;
347 ValidUserList = vuptr;
351 transcript = malloc(SIZ);
352 strcpy(transcript, "The following rooms have been auto-purged:\n");
354 while (RoomPurgeList != NULL) {
355 if (getroom(&qrbuf, RoomPurgeList->name) == 0) {
356 transcript=realloc(transcript, strlen(transcript)+SIZ);
357 snprintf(&transcript[strlen(transcript)], SIZ, " %s\n",
361 pptr = RoomPurgeList->next;
363 RoomPurgeList = pptr;
367 if (num_rooms_purged > 0) aide_message(transcript, "Room Autopurger Message");
370 CtdlLogPrintf(CTDL_DEBUG, "Purged %d rooms.\n", num_rooms_purged);
371 return(num_rooms_purged);
376 * Back end function to check user accounts for associated Unix accounts
377 * which no longer exist. (Only relevant for host auth mode.)
379 void do_uid_user_purge(struct ctdluser *us, void *data) {
380 struct PurgeList *pptr;
382 if ((us->uid != (-1)) && (us->uid != CTDLUID)) {
383 if (getpwuid(us->uid) == NULL) {
384 pptr = (struct PurgeList *)
385 malloc(sizeof(struct PurgeList));
386 pptr->next = UserPurgeList;
387 strcpy(pptr->name, us->fullname);
388 UserPurgeList = pptr;
400 * Back end function to check user accounts for expiration.
402 void do_user_purge(struct ctdluser *us, void *data) {
406 struct PurgeList *pptr;
408 /* Set purge time; if the user overrides the system default, use it */
409 if (us->USuserpurge > 0) {
410 purge_time = ((time_t)us->USuserpurge) * 86400L;
413 purge_time = ((time_t)config.c_userpurge) * 86400L;
416 /* The default rule is to not purge. */
419 /* don't attempt to purge system users. */
420 if (!strncmp(us->fullname, "SYS_", 4))
423 /* If the user hasn't called in two months and expiring of accounts is turned on, his/her account
424 * has expired, so purge the record.
426 if (config.c_userpurge > 0)
429 if ((now - us->lastcall) > purge_time) purge = 1;
432 /* If the record is marked as permanent, don't purge it.
434 if (us->flags & US_PERM) purge = 0;
436 /* If the user is an Aide, don't purge him/her/it.
438 if (us->axlevel == 6) purge = 0;
440 /* If the access level is 0, the record should already have been
441 * deleted, but maybe the user was logged in at the time or something.
442 * Delete the record now.
444 if (us->axlevel == 0) purge = 1;
446 /* If the user set his/her password to 'deleteme', he/she
447 * wishes to be deleted, so purge the record.
448 * Moved this lower down so that aides and permanent users get purged if they ask to be.
450 if (!strcasecmp(us->password, "deleteme")) purge = 1;
452 /* 0 calls is impossible. If there are 0 calls, it must
453 * be a corrupted record, so purge it.
454 * Actually it is possible if an Aide created the user so now we check for less than 0 (DRW)
456 if (us->timescalled < 0) purge = 1;
458 /* any negative user number, is
461 if (us->usernum < 0L) purge = 1;
463 /** Don't purge user 0. That user is there for the system */
464 if (us->usernum == 0L)
466 /* FIXME: Temporary log message. Until we do unauth access with user 0 we should
467 * try to get rid of all user 0 occurences. Many will be remnants from old code so
468 * we will need to try and purge them from users data bases.Some will not have names but
469 * those with names should be purged.
471 CtdlLogPrintf(CTDL_DEBUG, "Auto purger found a user 0 with name \"%s\"\n", us->fullname);
475 /* If the user has no full name entry then we can't purge them
476 * since the actual purge can't find them.
477 * This shouldn't happen but does somehow.
479 if (IsEmptyStr(us->fullname))
483 if (us->usernum > 0L)
486 if (users_corrupt_msg == NULL)
488 users_corrupt_msg = malloc(SIZ);
489 strcpy(users_corrupt_msg, "The auto-purger found the following user numbers with no name.\n"
490 "The system has no way to purge user with no name and should not be able to\n"
491 "create them either.\n"
492 "This indicates corruption of the user DB or possibly a bug.\n"
493 "It may be a good idea to restore your DB from a backup.\n");
496 users_corrupt_msg=realloc(users_corrupt_msg, strlen(users_corrupt_msg)+30);
497 snprintf(&users_corrupt_msg[strlen(users_corrupt_msg)], 29, " %ld\n", us->usernum);
504 pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList));
505 pptr->next = UserPurgeList;
506 strcpy(pptr->name, us->fullname);
507 UserPurgeList = pptr;
517 int PurgeUsers(void) {
518 struct PurgeList *pptr;
519 int num_users_purged = 0;
520 char *transcript = NULL;
522 CtdlLogPrintf(CTDL_DEBUG, "PurgeUsers() called\n");
523 users_not_purged = 0;
525 switch(config.c_auth_mode) {
526 case AUTHMODE_NATIVE:
527 ForEachUser(do_user_purge, NULL);
530 ForEachUser(do_uid_user_purge, NULL);
533 CtdlLogPrintf(CTDL_DEBUG, "Unknown authentication mode!\n");
537 transcript = malloc(SIZ);
539 if (users_not_purged == 0) {
540 strcpy(transcript, "The auto-purger was told to purge every user. It is\n"
541 "refusing to do this because it usually indicates a problem\n"
542 "such as an inability to communicate with a name service.\n"
544 while (UserPurgeList != NULL) {
545 pptr = UserPurgeList->next;
547 UserPurgeList = pptr;
553 strcpy(transcript, "The following users have been auto-purged:\n");
554 while (UserPurgeList != NULL) {
555 transcript=realloc(transcript, strlen(transcript)+SIZ);
556 snprintf(&transcript[strlen(transcript)], SIZ, " %s\n",
557 UserPurgeList->name);
558 purge_user(UserPurgeList->name);
559 pptr = UserPurgeList->next;
561 UserPurgeList = pptr;
566 if (num_users_purged > 0) aide_message(transcript, "User Purge Message");
569 if(users_corrupt_msg)
571 aide_message(users_corrupt_msg, "User Corruption Message");
572 free (users_corrupt_msg);
573 users_corrupt_msg = NULL;
578 aide_message(users_zero_msg, "User Zero Message");
579 free (users_zero_msg);
580 users_zero_msg = NULL;
583 CtdlLogPrintf(CTDL_DEBUG, "Purged %d users.\n", num_users_purged);
584 return(num_users_purged);
591 * This is a really cumbersome "garbage collection" function. We have to
592 * delete visits which refer to rooms and/or users which no longer exist. In
593 * order to prevent endless traversals of the room and user files, we first
594 * build linked lists of rooms and users which _do_ exist on the system, then
595 * traverse the visit file, checking each record against those two lists and
596 * purging the ones that do not have a match on _both_ lists. (Remember, if
597 * either the room or user being referred to is no longer on the system, the
598 * record is completely useless.)
600 int PurgeVisits(void) {
601 struct cdbdata *cdbvisit;
603 struct VPurgeList *VisitPurgeList = NULL;
604 struct VPurgeList *vptr;
608 struct ValidRoom *vrptr;
609 struct ValidUser *vuptr;
610 int RoomIsValid, UserIsValid;
612 /* First, load up a table full of valid room/gen combinations */
613 ForEachRoom(AddValidRoom, NULL);
615 /* Then load up a table full of valid user numbers */
616 ForEachUser(AddValidUser, NULL);
618 /* Now traverse through the visits, purging irrelevant records... */
619 cdb_rewind(CDB_VISIT);
620 while(cdbvisit = cdb_next_item(CDB_VISIT), cdbvisit != NULL) {
621 memset(&vbuf, 0, sizeof(struct visit));
622 memcpy(&vbuf, cdbvisit->ptr,
623 ( (cdbvisit->len > sizeof(struct visit)) ?
624 sizeof(struct visit) : cdbvisit->len) );
630 /* Check to see if the room exists */
631 for (vrptr=ValidRoomList; vrptr!=NULL; vrptr=vrptr->next) {
632 if ( (vrptr->vr_roomnum==vbuf.v_roomnum)
633 && (vrptr->vr_roomgen==vbuf.v_roomgen))
637 /* Check to see if the user exists */
638 for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) {
639 if (vuptr->vu_usernum == vbuf.v_usernum)
643 /* Put the record on the purge list if it's dead */
644 if ((RoomIsValid==0) || (UserIsValid==0)) {
645 vptr = (struct VPurgeList *)
646 malloc(sizeof(struct VPurgeList));
647 vptr->next = VisitPurgeList;
648 vptr->vp_roomnum = vbuf.v_roomnum;
649 vptr->vp_roomgen = vbuf.v_roomgen;
650 vptr->vp_usernum = vbuf.v_usernum;
651 VisitPurgeList = vptr;
656 /* Free the valid room/gen combination list */
657 while (ValidRoomList != NULL) {
658 vrptr = ValidRoomList->next;
660 ValidRoomList = vrptr;
663 /* Free the valid user list */
664 while (ValidUserList != NULL) {
665 vuptr = ValidUserList->next;
667 ValidUserList = vuptr;
670 /* Now delete every visit on the purged list */
671 while (VisitPurgeList != NULL) {
672 IndexLen = GenerateRelationshipIndex(IndexBuf,
673 VisitPurgeList->vp_roomnum,
674 VisitPurgeList->vp_roomgen,
675 VisitPurgeList->vp_usernum);
676 cdb_delete(CDB_VISIT, IndexBuf, IndexLen);
677 vptr = VisitPurgeList->next;
678 free(VisitPurgeList);
679 VisitPurgeList = vptr;
687 * Purge the use table of old entries.
690 int PurgeUseTable(void) {
692 struct cdbdata *cdbut;
694 struct UPurgeList *ul = NULL;
695 struct UPurgeList *uptr;
697 /* Phase 1: traverse through the table, discovering old records... */
698 CtdlLogPrintf(CTDL_DEBUG, "Purge use table: phase 1\n");
699 cdb_rewind(CDB_USETABLE);
700 while(cdbut = cdb_next_item(CDB_USETABLE), cdbut != NULL) {
703 * TODODRW: change this to create a new function time_t cdb_get_timestamp( struct cdbdata *)
704 * this will release this file from the serv_network.h
705 * Maybe it could be a macro that extracts and casts the reult
707 memcpy(&ut, cdbut->ptr,
708 ((cdbut->len > sizeof(struct UseTable)) ?
709 sizeof(struct UseTable) : cdbut->len));
712 if ( (time(NULL) - ut.ut_timestamp) > USETABLE_RETAIN ) {
713 uptr = (struct UPurgeList *) malloc(sizeof(struct UPurgeList));
716 safestrncpy(uptr->up_key, ut.ut_msgid, sizeof uptr->up_key);
724 /* Phase 2: delete the records */
725 CtdlLogPrintf(CTDL_DEBUG, "Purge use table: phase 2\n");
727 cdb_delete(CDB_USETABLE, ul->up_key, strlen(ul->up_key));
733 CtdlLogPrintf(CTDL_DEBUG, "Purge use table: finished (purged %d records)\n", purged);
740 * Purge the EUID Index of old records.
743 int PurgeEuidIndexTable(void) {
745 struct cdbdata *cdbei;
746 struct EPurgeList *el = NULL;
747 struct EPurgeList *eptr;
749 struct CtdlMessage *msg = NULL;
751 /* Phase 1: traverse through the table, discovering old records... */
752 CtdlLogPrintf(CTDL_DEBUG, "Purge EUID index: phase 1\n");
753 cdb_rewind(CDB_EUIDINDEX);
754 while(cdbei = cdb_next_item(CDB_EUIDINDEX), cdbei != NULL) {
756 memcpy(&msgnum, cdbei->ptr, sizeof(long));
758 msg = CtdlFetchMessage(msgnum, 0);
760 CtdlFreeMessage(msg); /* it still exists, so do nothing */
763 eptr = (struct EPurgeList *) malloc(sizeof(struct EPurgeList));
766 eptr->ep_keylen = cdbei->len - sizeof(long);
767 eptr->ep_key = malloc(cdbei->len);
768 memcpy(eptr->ep_key, &cdbei->ptr[sizeof(long)], eptr->ep_keylen);
778 /* Phase 2: delete the records */
779 CtdlLogPrintf(CTDL_DEBUG, "Purge euid index: phase 2\n");
781 cdb_delete(CDB_EUIDINDEX, el->ep_key, el->ep_keylen);
788 CtdlLogPrintf(CTDL_DEBUG, "Purge euid index: finished (purged %d records)\n", purged);
795 * Purge OpenID assocations for missing users (theoretically this will never delete anything)
797 int PurgeStaleOpenIDassociations(void) {
798 struct cdbdata *cdboi;
799 struct ctdluser usbuf;
800 HashList *keys = NULL;
802 char *deleteme = NULL;
808 keys = NewHash(1, NULL);
809 if (!keys) return(0);
812 cdb_rewind(CDB_OPENID);
813 while (cdboi = cdb_next_item(CDB_OPENID), cdboi != NULL) {
814 if (cdboi->len > sizeof(long)) {
816 usernum = ((long)*(cdboi->ptr));
817 if (getuserbynumber(&usbuf, usernum) != 0) {
818 deleteme = strdup(cdboi->ptr + sizeof(long)),
819 Put(keys, deleteme, strlen(deleteme), deleteme, generic_free_handler);
825 /* Go through the hash list, deleting keys we stored in it */
827 HashPos = GetNewHashPos();
828 while (GetNextHashPos(keys, HashPos, &len, &Key, &Value)!=0)
830 CtdlLogPrintf(CTDL_DEBUG, "Deleting associated OpenID <%s>\n", Value);
831 cdb_delete(CDB_OPENID, Value, strlen(Value));
832 /* note: don't free(Value) -- deleting the hash list will handle this for us */
835 DeleteHashPos(&HashPos);
844 void *purge_databases(void *args)
847 static time_t last_purge = 0;
850 struct CitContext purgerCC;
852 CtdlLogPrintf(CTDL_DEBUG, "Auto-purger_thread() initializing\n");
854 CtdlFillSystemContext(&purgerCC, "purger");
855 citthread_setspecific(MyConKey, (void *)&purgerCC );
857 while (!CtdlThreadCheckStop()) {
858 /* Do the auto-purge if the current hour equals the purge hour,
859 * but not if the operation has already been performed in the
860 * last twelve hours. This is usually enough granularity.
863 localtime_r(&now, &tm);
865 ((tm.tm_hour != config.c_purge_hour) || ((now - last_purge) < 43200))
866 && (force_purge_now == 0)
873 CtdlLogPrintf(CTDL_INFO, "Auto-purger: starting.\n");
875 if (!CtdlThreadCheckStop())
877 retval = PurgeUsers();
878 CtdlLogPrintf(CTDL_NOTICE, "Purged %d users.\n", retval);
881 if (!CtdlThreadCheckStop())
884 CtdlLogPrintf(CTDL_NOTICE, "Expired %d messages.\n", messages_purged);
887 if (!CtdlThreadCheckStop())
889 retval = PurgeRooms();
890 CtdlLogPrintf(CTDL_NOTICE, "Expired %d rooms.\n", retval);
893 if (!CtdlThreadCheckStop())
895 retval = PurgeVisits();
896 CtdlLogPrintf(CTDL_NOTICE, "Purged %d visits.\n", retval);
899 if (!CtdlThreadCheckStop())
901 retval = PurgeUseTable();
902 CtdlLogPrintf(CTDL_NOTICE, "Purged %d entries from the use table.\n", retval);
905 if (!CtdlThreadCheckStop())
907 retval = PurgeEuidIndexTable();
908 CtdlLogPrintf(CTDL_NOTICE, "Purged %d entries from the EUID index.\n", retval);
911 if (!CtdlThreadCheckStop())
913 retval = PurgeStaleOpenIDassociations();
914 CtdlLogPrintf(CTDL_NOTICE, "Purged %d stale OpenID associations.\n", retval);
917 if (!CtdlThreadCheckStop())
919 retval = TDAP_ProcessAdjRefCountQueue();
920 CtdlLogPrintf(CTDL_NOTICE, "Processed %d message reference count adjustments.\n", retval);
923 if (!CtdlThreadCheckStop())
925 CtdlLogPrintf(CTDL_INFO, "Auto-purger: finished.\n");
926 last_purge = now; /* So we don't do it again soon */
930 CtdlLogPrintf(CTDL_INFO, "Auto-purger: STOPPED.\n");
935 /*****************************************************************************/
938 void do_fsck_msg(long msgnum, void *userdata) {
939 struct ctdlroomref *ptr;
941 ptr = (struct ctdlroomref *)malloc(sizeof(struct ctdlroomref));
943 ptr->msgnum = msgnum;
947 void do_fsck_room(struct ctdlroom *qrbuf, void *data)
949 getroom(&CC->room, qrbuf->QRname);
950 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, do_fsck_msg, NULL);
954 * Check message reference counts
956 void cmd_fsck(char *argbuf) {
958 struct cdbdata *cdbmsg;
960 struct ctdlroomref *ptr;
963 if (CtdlAccessCheck(ac_aide)) return;
965 /* Lame way of checking whether anyone else is doing this now */
967 cprintf("%d Another FSCK is already running.\n", ERROR + RESOURCE_BUSY);
971 cprintf("%d Checking message reference counts\n", LISTING_FOLLOWS);
973 cprintf("\nThis could take a while. Please be patient!\n\n");
974 cprintf("Gathering pointers...\n");
975 ForEachRoom(do_fsck_room, NULL);
978 cprintf("Checking message base...\n");
979 for (msgnum = 0L; msgnum <= CitControl.MMhighest; ++msgnum) {
981 cdbmsg = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
982 if (cdbmsg != NULL) {
984 cprintf("Message %7ld ", msgnum);
986 GetMetaData(&smi, msgnum);
987 cprintf("refcount=%-2d ", smi.meta_refcount);
990 for (ptr = rr; ptr != NULL; ptr = ptr->next) {
991 if (ptr->msgnum == msgnum) ++realcount;
993 cprintf("realcount=%-2d\n", realcount);
995 if ( (smi.meta_refcount != realcount)
996 || (realcount == 0) ) {
997 AdjRefCount(msgnum, (smi.meta_refcount - realcount));
1004 cprintf("Freeing memory...\n");
1005 while (rr != NULL) {
1018 * Manually initiate a run of The Dreaded Auto-Purger (tm)
1020 void cmd_tdap(char *argbuf) {
1021 if (CtdlAccessCheck(ac_aide)) return;
1022 force_purge_now = 1;
1023 cprintf("%d Manually initiating a purger run now.\n", CIT_OK);
1027 /*****************************************************************************/
1029 CTDL_MODULE_INIT(expire)
1033 CtdlRegisterProtoHook(cmd_fsck, "FSCK", "Check message ref counts");
1034 CtdlRegisterProtoHook(cmd_tdap, "TDAP", "Manually initiate auto-purger");
1037 CtdlThreadCreate("Auto Purger", CTDLTHREAD_BIGSTACK, purge_databases, NULL);
1038 /* return our Subversion id for the Log */