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, "User purge for auth mode %d is not implemented.\n",
538 transcript = malloc(SIZ);
540 if (users_not_purged == 0) {
541 strcpy(transcript, "The auto-purger was told to purge every user. It is\n"
542 "refusing to do this because it usually indicates a problem\n"
543 "such as an inability to communicate with a name service.\n"
545 while (UserPurgeList != NULL) {
546 pptr = UserPurgeList->next;
548 UserPurgeList = pptr;
554 strcpy(transcript, "The following users have been auto-purged:\n");
555 while (UserPurgeList != NULL) {
556 transcript=realloc(transcript, strlen(transcript)+SIZ);
557 snprintf(&transcript[strlen(transcript)], SIZ, " %s\n",
558 UserPurgeList->name);
559 purge_user(UserPurgeList->name);
560 pptr = UserPurgeList->next;
562 UserPurgeList = pptr;
567 if (num_users_purged > 0) aide_message(transcript, "User Purge Message");
570 if(users_corrupt_msg)
572 aide_message(users_corrupt_msg, "User Corruption Message");
573 free (users_corrupt_msg);
574 users_corrupt_msg = NULL;
579 aide_message(users_zero_msg, "User Zero Message");
580 free (users_zero_msg);
581 users_zero_msg = NULL;
584 CtdlLogPrintf(CTDL_DEBUG, "Purged %d users.\n", num_users_purged);
585 return(num_users_purged);
592 * This is a really cumbersome "garbage collection" function. We have to
593 * delete visits which refer to rooms and/or users which no longer exist. In
594 * order to prevent endless traversals of the room and user files, we first
595 * build linked lists of rooms and users which _do_ exist on the system, then
596 * traverse the visit file, checking each record against those two lists and
597 * purging the ones that do not have a match on _both_ lists. (Remember, if
598 * either the room or user being referred to is no longer on the system, the
599 * record is completely useless.)
601 int PurgeVisits(void) {
602 struct cdbdata *cdbvisit;
604 struct VPurgeList *VisitPurgeList = NULL;
605 struct VPurgeList *vptr;
609 struct ValidRoom *vrptr;
610 struct ValidUser *vuptr;
611 int RoomIsValid, UserIsValid;
613 /* First, load up a table full of valid room/gen combinations */
614 ForEachRoom(AddValidRoom, NULL);
616 /* Then load up a table full of valid user numbers */
617 ForEachUser(AddValidUser, NULL);
619 /* Now traverse through the visits, purging irrelevant records... */
620 cdb_rewind(CDB_VISIT);
621 while(cdbvisit = cdb_next_item(CDB_VISIT), cdbvisit != NULL) {
622 memset(&vbuf, 0, sizeof(struct visit));
623 memcpy(&vbuf, cdbvisit->ptr,
624 ( (cdbvisit->len > sizeof(struct visit)) ?
625 sizeof(struct visit) : cdbvisit->len) );
631 /* Check to see if the room exists */
632 for (vrptr=ValidRoomList; vrptr!=NULL; vrptr=vrptr->next) {
633 if ( (vrptr->vr_roomnum==vbuf.v_roomnum)
634 && (vrptr->vr_roomgen==vbuf.v_roomgen))
638 /* Check to see if the user exists */
639 for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) {
640 if (vuptr->vu_usernum == vbuf.v_usernum)
644 /* Put the record on the purge list if it's dead */
645 if ((RoomIsValid==0) || (UserIsValid==0)) {
646 vptr = (struct VPurgeList *)
647 malloc(sizeof(struct VPurgeList));
648 vptr->next = VisitPurgeList;
649 vptr->vp_roomnum = vbuf.v_roomnum;
650 vptr->vp_roomgen = vbuf.v_roomgen;
651 vptr->vp_usernum = vbuf.v_usernum;
652 VisitPurgeList = vptr;
657 /* Free the valid room/gen combination list */
658 while (ValidRoomList != NULL) {
659 vrptr = ValidRoomList->next;
661 ValidRoomList = vrptr;
664 /* Free the valid user list */
665 while (ValidUserList != NULL) {
666 vuptr = ValidUserList->next;
668 ValidUserList = vuptr;
671 /* Now delete every visit on the purged list */
672 while (VisitPurgeList != NULL) {
673 IndexLen = GenerateRelationshipIndex(IndexBuf,
674 VisitPurgeList->vp_roomnum,
675 VisitPurgeList->vp_roomgen,
676 VisitPurgeList->vp_usernum);
677 cdb_delete(CDB_VISIT, IndexBuf, IndexLen);
678 vptr = VisitPurgeList->next;
679 free(VisitPurgeList);
680 VisitPurgeList = vptr;
688 * Purge the use table of old entries.
691 int PurgeUseTable(void) {
693 struct cdbdata *cdbut;
695 struct UPurgeList *ul = NULL;
696 struct UPurgeList *uptr;
698 /* Phase 1: traverse through the table, discovering old records... */
699 CtdlLogPrintf(CTDL_DEBUG, "Purge use table: phase 1\n");
700 cdb_rewind(CDB_USETABLE);
701 while(cdbut = cdb_next_item(CDB_USETABLE), cdbut != NULL) {
704 * TODODRW: change this to create a new function time_t cdb_get_timestamp( struct cdbdata *)
705 * this will release this file from the serv_network.h
706 * Maybe it could be a macro that extracts and casts the reult
708 memcpy(&ut, cdbut->ptr,
709 ((cdbut->len > sizeof(struct UseTable)) ?
710 sizeof(struct UseTable) : cdbut->len));
713 if ( (time(NULL) - ut.ut_timestamp) > USETABLE_RETAIN ) {
714 uptr = (struct UPurgeList *) malloc(sizeof(struct UPurgeList));
717 safestrncpy(uptr->up_key, ut.ut_msgid, sizeof uptr->up_key);
725 /* Phase 2: delete the records */
726 CtdlLogPrintf(CTDL_DEBUG, "Purge use table: phase 2\n");
728 cdb_delete(CDB_USETABLE, ul->up_key, strlen(ul->up_key));
734 CtdlLogPrintf(CTDL_DEBUG, "Purge use table: finished (purged %d records)\n", purged);
741 * Purge the EUID Index of old records.
744 int PurgeEuidIndexTable(void) {
746 struct cdbdata *cdbei;
747 struct EPurgeList *el = NULL;
748 struct EPurgeList *eptr;
750 struct CtdlMessage *msg = NULL;
752 /* Phase 1: traverse through the table, discovering old records... */
753 CtdlLogPrintf(CTDL_DEBUG, "Purge EUID index: phase 1\n");
754 cdb_rewind(CDB_EUIDINDEX);
755 while(cdbei = cdb_next_item(CDB_EUIDINDEX), cdbei != NULL) {
757 memcpy(&msgnum, cdbei->ptr, sizeof(long));
759 msg = CtdlFetchMessage(msgnum, 0);
761 CtdlFreeMessage(msg); /* it still exists, so do nothing */
764 eptr = (struct EPurgeList *) malloc(sizeof(struct EPurgeList));
767 eptr->ep_keylen = cdbei->len - sizeof(long);
768 eptr->ep_key = malloc(cdbei->len);
769 memcpy(eptr->ep_key, &cdbei->ptr[sizeof(long)], eptr->ep_keylen);
779 /* Phase 2: delete the records */
780 CtdlLogPrintf(CTDL_DEBUG, "Purge euid index: phase 2\n");
782 cdb_delete(CDB_EUIDINDEX, el->ep_key, el->ep_keylen);
789 CtdlLogPrintf(CTDL_DEBUG, "Purge euid index: finished (purged %d records)\n", purged);
796 * Purge OpenID assocations for missing users (theoretically this will never delete anything)
798 int PurgeStaleOpenIDassociations(void) {
799 struct cdbdata *cdboi;
800 struct ctdluser usbuf;
801 HashList *keys = NULL;
803 char *deleteme = NULL;
809 keys = NewHash(1, NULL);
810 if (!keys) return(0);
813 cdb_rewind(CDB_OPENID);
814 while (cdboi = cdb_next_item(CDB_OPENID), cdboi != NULL) {
815 if (cdboi->len > sizeof(long)) {
817 usernum = ((long)*(cdboi->ptr));
818 if (getuserbynumber(&usbuf, usernum) != 0) {
819 deleteme = strdup(cdboi->ptr + sizeof(long)),
820 Put(keys, deleteme, strlen(deleteme), deleteme, generic_free_handler);
826 /* Go through the hash list, deleting keys we stored in it */
828 HashPos = GetNewHashPos(keys, 0);
829 while (GetNextHashPos(keys, HashPos, &len, &Key, &Value)!=0)
831 CtdlLogPrintf(CTDL_DEBUG, "Deleting associated OpenID <%s>\n", Value);
832 cdb_delete(CDB_OPENID, Value, strlen(Value));
833 /* note: don't free(Value) -- deleting the hash list will handle this for us */
836 DeleteHashPos(&HashPos);
845 void *purge_databases(void *args)
848 static time_t last_purge = 0;
851 struct CitContext purgerCC;
853 CtdlLogPrintf(CTDL_DEBUG, "Auto-purger_thread() initializing\n");
855 CtdlFillSystemContext(&purgerCC, "purger");
856 citthread_setspecific(MyConKey, (void *)&purgerCC );
858 while (!CtdlThreadCheckStop()) {
859 /* Do the auto-purge if the current hour equals the purge hour,
860 * but not if the operation has already been performed in the
861 * last twelve hours. This is usually enough granularity.
864 localtime_r(&now, &tm);
866 ((tm.tm_hour != config.c_purge_hour) || ((now - last_purge) < 43200))
867 && (force_purge_now == 0)
874 CtdlLogPrintf(CTDL_INFO, "Auto-purger: starting.\n");
876 if (!CtdlThreadCheckStop())
878 retval = PurgeUsers();
879 CtdlLogPrintf(CTDL_NOTICE, "Purged %d users.\n", retval);
882 if (!CtdlThreadCheckStop())
885 CtdlLogPrintf(CTDL_NOTICE, "Expired %d messages.\n", messages_purged);
888 if (!CtdlThreadCheckStop())
890 retval = PurgeRooms();
891 CtdlLogPrintf(CTDL_NOTICE, "Expired %d rooms.\n", retval);
894 if (!CtdlThreadCheckStop())
896 retval = PurgeVisits();
897 CtdlLogPrintf(CTDL_NOTICE, "Purged %d visits.\n", retval);
900 if (!CtdlThreadCheckStop())
902 retval = PurgeUseTable();
903 CtdlLogPrintf(CTDL_NOTICE, "Purged %d entries from the use table.\n", retval);
906 if (!CtdlThreadCheckStop())
908 retval = PurgeEuidIndexTable();
909 CtdlLogPrintf(CTDL_NOTICE, "Purged %d entries from the EUID index.\n", retval);
912 if (!CtdlThreadCheckStop())
914 retval = PurgeStaleOpenIDassociations();
915 CtdlLogPrintf(CTDL_NOTICE, "Purged %d stale OpenID associations.\n", retval);
918 if (!CtdlThreadCheckStop())
920 retval = TDAP_ProcessAdjRefCountQueue();
921 CtdlLogPrintf(CTDL_NOTICE, "Processed %d message reference count adjustments.\n", retval);
924 if (!CtdlThreadCheckStop())
926 CtdlLogPrintf(CTDL_INFO, "Auto-purger: finished.\n");
927 last_purge = now; /* So we don't do it again soon */
931 CtdlLogPrintf(CTDL_INFO, "Auto-purger: STOPPED.\n");
936 /*****************************************************************************/
939 /* The FSCK command has been removed because people were misusing it */
943 void do_fsck_msg(long msgnum, void *userdata) {
944 struct ctdlroomref *ptr;
946 ptr = (struct ctdlroomref *)malloc(sizeof(struct ctdlroomref));
948 ptr->msgnum = msgnum;
952 void do_fsck_room(struct ctdlroom *qrbuf, void *data)
954 getroom(&CC->room, qrbuf->QRname);
955 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, do_fsck_msg, NULL);
959 * Check message reference counts
961 void cmd_fsck(char *argbuf) {
963 struct cdbdata *cdbmsg;
965 struct ctdlroomref *ptr;
968 if (CtdlAccessCheck(ac_aide)) return;
970 /* Lame way of checking whether anyone else is doing this now */
972 cprintf("%d Another FSCK is already running.\n", ERROR + RESOURCE_BUSY);
976 cprintf("%d Checking message reference counts\n", LISTING_FOLLOWS);
978 cprintf("\nThis could take a while. Please be patient!\n\n");
979 cprintf("Gathering pointers...\n");
980 ForEachRoom(do_fsck_room, NULL);
983 cprintf("Checking message base...\n");
984 for (msgnum = 0L; msgnum <= CitControl.MMhighest; ++msgnum) {
986 cdbmsg = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
987 if (cdbmsg != NULL) {
989 cprintf("Message %7ld ", msgnum);
991 GetMetaData(&smi, msgnum);
992 cprintf("refcount=%-2d ", smi.meta_refcount);
995 for (ptr = rr; ptr != NULL; ptr = ptr->next) {
996 if (ptr->msgnum == msgnum) ++realcount;
998 cprintf("realcount=%-2d\n", realcount);
1000 if ( (smi.meta_refcount != realcount)
1001 || (realcount == 0) ) {
1002 AdjRefCount(msgnum, (smi.meta_refcount - realcount));
1009 cprintf("Freeing memory...\n");
1010 while (rr != NULL) {
1021 #endif /* end of commented-out fsck cmd */
1024 * Manually initiate a run of The Dreaded Auto-Purger (tm)
1026 void cmd_tdap(char *argbuf) {
1027 if (CtdlAccessCheck(ac_aide)) return;
1028 force_purge_now = 1;
1029 cprintf("%d Manually initiating a purger run now.\n", CIT_OK);
1033 /*****************************************************************************/
1035 CTDL_MODULE_INIT(expire)
1039 /* CtdlRegisterProtoHook(cmd_fsck, "FSCK", "Check message ref counts"); */
1040 CtdlRegisterProtoHook(cmd_tdap, "TDAP", "Manually initiate auto-purger");
1043 CtdlThreadCreate("Auto Purger", CTDLTHREAD_BIGSTACK, purge_databases, NULL);
1044 /* return our Subversion id for the Log */