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 /* 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 == 0L)
457 /* FIXME: Temporary log message. Until we do unauth access with user 0 we should
458 * try to get rid of all user 0 occurences. Many will be remnants from old code so
459 * we will need to try and purge them from users data bases.Some will not have names but
460 * those with names should be purged.
462 CtdlLogPrintf(CTDL_DEBUG, "Auto purger found a user 0 with name \"%s\"\n", us->fullname);
466 /* If the user has no full name entry then we can't purge them
467 * since the actual purge can't find them.
468 * This shouldn't happen but does somehow.
470 if (IsEmptyStr(us->fullname))
474 if (us->usernum > 0L)
477 if (users_corrupt_msg == NULL)
479 users_corrupt_msg = malloc(SIZ);
480 strcpy(users_corrupt_msg, "The auto-purger found the following user numbers with no name.\n"
481 "The system has no way to purge user with no name and should not be able to\n"
482 "create them either.\n"
483 "This indicates corruption of the user DB or possibly a bug.\n"
484 "It may be a good idea to restore your DB from a backup.\n");
487 users_corrupt_msg=realloc(users_corrupt_msg, strlen(users_corrupt_msg)+30);
488 snprintf(&users_corrupt_msg[strlen(users_corrupt_msg)], 29, " %ld\n", us->usernum);
494 pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList));
495 pptr->next = UserPurgeList;
496 strcpy(pptr->name, us->fullname);
497 UserPurgeList = pptr;
507 int PurgeUsers(void) {
508 struct PurgeList *pptr;
509 int num_users_purged = 0;
510 char *transcript = NULL;
512 CtdlLogPrintf(CTDL_DEBUG, "PurgeUsers() called\n");
513 users_not_purged = 0;
515 switch(config.c_auth_mode) {
516 case AUTHMODE_NATIVE:
517 ForEachUser(do_user_purge, NULL);
520 ForEachUser(do_uid_user_purge, NULL);
523 CtdlLogPrintf(CTDL_DEBUG, "Unknown authentication mode!\n");
527 transcript = malloc(SIZ);
529 if (users_not_purged == 0) {
530 strcpy(transcript, "The auto-purger was told to purge every user. It is\n"
531 "refusing to do this because it usually indicates a problem\n"
532 "such as an inability to communicate with a name service.\n"
534 while (UserPurgeList != NULL) {
535 pptr = UserPurgeList->next;
537 UserPurgeList = pptr;
543 strcpy(transcript, "The following users have been auto-purged:\n");
544 while (UserPurgeList != NULL) {
545 transcript=realloc(transcript, strlen(transcript)+SIZ);
546 snprintf(&transcript[strlen(transcript)], SIZ, " %s\n",
547 UserPurgeList->name);
548 purge_user(UserPurgeList->name);
549 pptr = UserPurgeList->next;
551 UserPurgeList = pptr;
556 if (num_users_purged > 0) aide_message(transcript, "User Purge Message");
559 if(users_corrupt_msg)
561 aide_message(users_corrupt_msg, "User Corruption Message");
562 free (users_corrupt_msg);
563 users_corrupt_msg = NULL;
568 aide_message(users_zero_msg, "User Zero Message");
569 free (users_zero_msg);
570 users_zero_msg = NULL;
573 CtdlLogPrintf(CTDL_DEBUG, "Purged %d users.\n", num_users_purged);
574 return(num_users_purged);
581 * This is a really cumbersome "garbage collection" function. We have to
582 * delete visits which refer to rooms and/or users which no longer exist. In
583 * order to prevent endless traversals of the room and user files, we first
584 * build linked lists of rooms and users which _do_ exist on the system, then
585 * traverse the visit file, checking each record against those two lists and
586 * purging the ones that do not have a match on _both_ lists. (Remember, if
587 * either the room or user being referred to is no longer on the system, the
588 * record is completely useless.)
590 int PurgeVisits(void) {
591 struct cdbdata *cdbvisit;
593 struct VPurgeList *VisitPurgeList = NULL;
594 struct VPurgeList *vptr;
598 struct ValidRoom *vrptr;
599 struct ValidUser *vuptr;
600 int RoomIsValid, UserIsValid;
602 /* First, load up a table full of valid room/gen combinations */
603 ForEachRoom(AddValidRoom, NULL);
605 /* Then load up a table full of valid user numbers */
606 ForEachUser(AddValidUser, NULL);
608 /* Now traverse through the visits, purging irrelevant records... */
609 cdb_rewind(CDB_VISIT);
610 while(cdbvisit = cdb_next_item(CDB_VISIT), cdbvisit != NULL) {
611 memset(&vbuf, 0, sizeof(struct visit));
612 memcpy(&vbuf, cdbvisit->ptr,
613 ( (cdbvisit->len > sizeof(struct visit)) ?
614 sizeof(struct visit) : cdbvisit->len) );
620 /* Check to see if the room exists */
621 for (vrptr=ValidRoomList; vrptr!=NULL; vrptr=vrptr->next) {
622 if ( (vrptr->vr_roomnum==vbuf.v_roomnum)
623 && (vrptr->vr_roomgen==vbuf.v_roomgen))
627 /* Check to see if the user exists */
628 for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) {
629 if (vuptr->vu_usernum == vbuf.v_usernum)
633 /* Put the record on the purge list if it's dead */
634 if ((RoomIsValid==0) || (UserIsValid==0)) {
635 vptr = (struct VPurgeList *)
636 malloc(sizeof(struct VPurgeList));
637 vptr->next = VisitPurgeList;
638 vptr->vp_roomnum = vbuf.v_roomnum;
639 vptr->vp_roomgen = vbuf.v_roomgen;
640 vptr->vp_usernum = vbuf.v_usernum;
641 VisitPurgeList = vptr;
646 /* Free the valid room/gen combination list */
647 while (ValidRoomList != NULL) {
648 vrptr = ValidRoomList->next;
650 ValidRoomList = vrptr;
653 /* Free the valid user list */
654 while (ValidUserList != NULL) {
655 vuptr = ValidUserList->next;
657 ValidUserList = vuptr;
660 /* Now delete every visit on the purged list */
661 while (VisitPurgeList != NULL) {
662 IndexLen = GenerateRelationshipIndex(IndexBuf,
663 VisitPurgeList->vp_roomnum,
664 VisitPurgeList->vp_roomgen,
665 VisitPurgeList->vp_usernum);
666 cdb_delete(CDB_VISIT, IndexBuf, IndexLen);
667 vptr = VisitPurgeList->next;
668 free(VisitPurgeList);
669 VisitPurgeList = vptr;
677 * Purge the use table of old entries.
680 int PurgeUseTable(void) {
682 struct cdbdata *cdbut;
684 struct UPurgeList *ul = NULL;
685 struct UPurgeList *uptr;
687 /* Phase 1: traverse through the table, discovering old records... */
688 CtdlLogPrintf(CTDL_DEBUG, "Purge use table: phase 1\n");
689 cdb_rewind(CDB_USETABLE);
690 while(cdbut = cdb_next_item(CDB_USETABLE), cdbut != NULL) {
693 * TODODRW: change this to create a new function time_t cdb_get_timestamp( struct cdbdata *)
694 * this will release this file from the serv_network.h
695 * Maybe it could be a macro that extracts and casts the reult
697 memcpy(&ut, cdbut->ptr,
698 ((cdbut->len > sizeof(struct UseTable)) ?
699 sizeof(struct UseTable) : cdbut->len));
702 if ( (time(NULL) - ut.ut_timestamp) > USETABLE_RETAIN ) {
703 uptr = (struct UPurgeList *) malloc(sizeof(struct UPurgeList));
706 safestrncpy(uptr->up_key, ut.ut_msgid, sizeof uptr->up_key);
714 /* Phase 2: delete the records */
715 CtdlLogPrintf(CTDL_DEBUG, "Purge use table: phase 2\n");
717 cdb_delete(CDB_USETABLE, ul->up_key, strlen(ul->up_key));
723 CtdlLogPrintf(CTDL_DEBUG, "Purge use table: finished (purged %d records)\n", purged);
730 * Purge the EUID Index of old records.
733 int PurgeEuidIndexTable(void) {
735 struct cdbdata *cdbei;
736 struct EPurgeList *el = NULL;
737 struct EPurgeList *eptr;
739 struct CtdlMessage *msg = NULL;
741 /* Phase 1: traverse through the table, discovering old records... */
742 CtdlLogPrintf(CTDL_DEBUG, "Purge EUID index: phase 1\n");
743 cdb_rewind(CDB_EUIDINDEX);
744 while(cdbei = cdb_next_item(CDB_EUIDINDEX), cdbei != NULL) {
746 memcpy(&msgnum, cdbei->ptr, sizeof(long));
748 msg = CtdlFetchMessage(msgnum, 0);
750 CtdlFreeMessage(msg); /* it still exists, so do nothing */
753 eptr = (struct EPurgeList *) malloc(sizeof(struct EPurgeList));
756 eptr->ep_keylen = cdbei->len - sizeof(long);
757 eptr->ep_key = malloc(cdbei->len);
758 memcpy(eptr->ep_key, &cdbei->ptr[sizeof(long)], eptr->ep_keylen);
768 /* Phase 2: delete the records */
769 CtdlLogPrintf(CTDL_DEBUG, "Purge euid index: phase 2\n");
771 cdb_delete(CDB_EUIDINDEX, el->ep_key, el->ep_keylen);
778 CtdlLogPrintf(CTDL_DEBUG, "Purge euid index: finished (purged %d records)\n", purged);
785 * Purge OpenID assocations for missing users (theoretically this will never delete anything)
787 int PurgeStaleOpenIDassociations(void) {
788 struct cdbdata *cdboi;
789 struct ctdluser usbuf;
790 HashList *keys = NULL;
792 char *deleteme = NULL;
798 keys = NewHash(1, NULL);
799 if (!keys) return(0);
802 cdb_rewind(CDB_OPENID);
803 while (cdboi = cdb_next_item(CDB_OPENID), cdboi != NULL) {
804 if (cdboi->len > sizeof(long)) {
806 usernum = ((long)*(cdboi->ptr));
807 if (getuserbynumber(&usbuf, usernum) != 0) {
808 deleteme = strdup(cdboi->ptr + sizeof(long)),
809 Put(keys, deleteme, strlen(deleteme), deleteme, generic_free_handler);
815 /* Go through the hash list, deleting keys we stored in it */
817 HashPos = GetNewHashPos();
818 while (GetNextHashPos(keys, HashPos, &len, &Key, &Value)!=0)
820 CtdlLogPrintf(CTDL_DEBUG, "Deleting associated OpenID <%s>\n", Value);
821 cdb_delete(CDB_OPENID, Value, strlen(Value));
822 /* note: don't free(Value) -- deleting the hash list will handle this for us */
825 DeleteHashPos(&HashPos);
834 void *purge_databases(void *args)
837 static time_t last_purge = 0;
840 struct CitContext purgerCC;
842 CtdlLogPrintf(CTDL_DEBUG, "Auto-purger_thread() initializing\n");
844 CtdlFillSystemContext(&purgerCC, "purger");
845 citthread_setspecific(MyConKey, (void *)&purgerCC );
847 while (!CtdlThreadCheckStop()) {
848 /* Do the auto-purge if the current hour equals the purge hour,
849 * but not if the operation has already been performed in the
850 * last twelve hours. This is usually enough granularity.
853 localtime_r(&now, &tm);
854 if ((tm.tm_hour != config.c_purge_hour) || ((now - last_purge) < 43200)) {
860 CtdlLogPrintf(CTDL_INFO, "Auto-purger: starting.\n");
862 if (!CtdlThreadCheckStop())
864 retval = PurgeUsers();
865 CtdlLogPrintf(CTDL_NOTICE, "Purged %d users.\n", retval);
868 if (!CtdlThreadCheckStop())
871 CtdlLogPrintf(CTDL_NOTICE, "Expired %d messages.\n", messages_purged);
874 if (!CtdlThreadCheckStop())
876 retval = PurgeRooms();
877 CtdlLogPrintf(CTDL_NOTICE, "Expired %d rooms.\n", retval);
880 if (!CtdlThreadCheckStop())
882 retval = PurgeVisits();
883 CtdlLogPrintf(CTDL_NOTICE, "Purged %d visits.\n", retval);
886 if (!CtdlThreadCheckStop())
888 retval = PurgeUseTable();
889 CtdlLogPrintf(CTDL_NOTICE, "Purged %d entries from the use table.\n", retval);
892 if (!CtdlThreadCheckStop())
894 retval = PurgeEuidIndexTable();
895 CtdlLogPrintf(CTDL_NOTICE, "Purged %d entries from the EUID index.\n", retval);
898 if (!CtdlThreadCheckStop())
900 retval = PurgeStaleOpenIDassociations();
901 CtdlLogPrintf(CTDL_NOTICE, "Purged %d stale OpenID associations.\n", retval);
904 if (!CtdlThreadCheckStop())
906 retval = TDAP_ProcessAdjRefCountQueue();
907 CtdlLogPrintf(CTDL_NOTICE, "Processed %d message reference count adjustments.\n", retval);
910 if (!CtdlThreadCheckStop())
912 CtdlLogPrintf(CTDL_INFO, "Auto-purger: finished.\n");
913 last_purge = now; /* So we don't do it again soon */
916 CtdlLogPrintf(CTDL_INFO, "Auto-purger: STOPPED.\n");
921 /*****************************************************************************/
924 void do_fsck_msg(long msgnum, void *userdata) {
925 struct ctdlroomref *ptr;
927 ptr = (struct ctdlroomref *)malloc(sizeof(struct ctdlroomref));
929 ptr->msgnum = msgnum;
933 void do_fsck_room(struct ctdlroom *qrbuf, void *data)
935 getroom(&CC->room, qrbuf->QRname);
936 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, do_fsck_msg, NULL);
940 * Check message reference counts
942 void cmd_fsck(char *argbuf) {
944 struct cdbdata *cdbmsg;
946 struct ctdlroomref *ptr;
949 if (CtdlAccessCheck(ac_aide)) return;
951 /* Lame way of checking whether anyone else is doing this now */
953 cprintf("%d Another FSCK is already running.\n", ERROR + RESOURCE_BUSY);
957 cprintf("%d Checking message reference counts\n", LISTING_FOLLOWS);
959 cprintf("\nThis could take a while. Please be patient!\n\n");
960 cprintf("Gathering pointers...\n");
961 ForEachRoom(do_fsck_room, NULL);
964 cprintf("Checking message base...\n");
965 for (msgnum = 0L; msgnum <= CitControl.MMhighest; ++msgnum) {
967 cdbmsg = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
968 if (cdbmsg != NULL) {
970 cprintf("Message %7ld ", msgnum);
972 GetMetaData(&smi, msgnum);
973 cprintf("refcount=%-2d ", smi.meta_refcount);
976 for (ptr = rr; ptr != NULL; ptr = ptr->next) {
977 if (ptr->msgnum == msgnum) ++realcount;
979 cprintf("realcount=%-2d\n", realcount);
981 if ( (smi.meta_refcount != realcount)
982 || (realcount == 0) ) {
983 AdjRefCount(msgnum, (smi.meta_refcount - realcount));
990 cprintf("Freeing memory...\n");
1005 /*****************************************************************************/
1007 CTDL_MODULE_INIT(expire)
1011 CtdlRegisterProtoHook(cmd_fsck, "FSCK", "Check message ref counts");
1014 CtdlThreadCreate("Auto Purger", CTDLTHREAD_BIGSTACK, purge_databases, NULL);
1015 /* return our Subversion id for the Log */