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 /* Don't purge messages containing system configuration, dumbass. */
156 if (!strcasecmp(qrbuf->QRname, SYSCONFIGROOM)) return;
158 /* Ok, we got this far ... now let's see what's in the room */
159 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf->QRnumber, sizeof(long));
162 msglist = malloc(cdbfr->len);
163 memcpy(msglist, cdbfr->ptr, cdbfr->len);
164 num_msgs = cdbfr->len / sizeof(long);
168 /* Nothing to do if there aren't any messages */
170 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 /* If the user hasn't called in two months and expiring of accounts is turned on, his/her account
420 * has expired, so purge the record.
422 if (config.c_userpurge > 0)
425 if ((now - us->lastcall) > purge_time) purge = 1;
428 /* If the record is marked as permanent, don't purge it.
430 if (us->flags & US_PERM) purge = 0;
432 /* If the user is an Aide, don't purge him/her/it.
434 if (us->axlevel == 6) purge = 0;
436 /* If the access level is 0, the record should already have been
437 * deleted, but maybe the user was logged in at the time or something.
438 * Delete the record now.
440 if (us->axlevel == 0) purge = 1;
442 /* If the user set his/her password to 'deleteme', he/she
443 * wishes to be deleted, so purge the record.
444 * Moved this lower down so that aides and permanent users get purged if they ask to be.
446 if (!strcasecmp(us->password, "deleteme")) purge = 1;
448 /* 0 calls is impossible. If there are 0 calls, it must
449 * be a corrupted record, so purge it.
450 * Actually it is possible if an Aide created the user so now we check for less than 0 (DRW)
452 if (us->timescalled < 0) purge = 1;
454 /* any negative user number, is
457 if (us->usernum < 0L) purge = 1;
459 /** Don't purge user 0. That user is there for the system */
460 if (us->usernum == 0L)
462 /* FIXME: Temporary log message. Until we do unauth access with user 0 we should
463 * try to get rid of all user 0 occurences. Many will be remnants from old code so
464 * we will need to try and purge them from users data bases.Some will not have names but
465 * those with names should be purged.
467 CtdlLogPrintf(CTDL_DEBUG, "Auto purger found a user 0 with name \"%s\"\n", us->fullname);
471 /* If the user has no full name entry then we can't purge them
472 * since the actual purge can't find them.
473 * This shouldn't happen but does somehow.
475 if (IsEmptyStr(us->fullname))
479 if (us->usernum > 0L)
482 if (users_corrupt_msg == NULL)
484 users_corrupt_msg = malloc(SIZ);
485 strcpy(users_corrupt_msg, "The auto-purger found the following user numbers with no name.\n"
486 "The system has no way to purge user with no name and should not be able to\n"
487 "create them either.\n"
488 "This indicates corruption of the user DB or possibly a bug.\n"
489 "It may be a good idea to restore your DB from a backup.\n");
492 users_corrupt_msg=realloc(users_corrupt_msg, strlen(users_corrupt_msg)+30);
493 snprintf(&users_corrupt_msg[strlen(users_corrupt_msg)], 29, " %ld\n", us->usernum);
499 pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList));
500 pptr->next = UserPurgeList;
501 strcpy(pptr->name, us->fullname);
502 UserPurgeList = pptr;
512 int PurgeUsers(void) {
513 struct PurgeList *pptr;
514 int num_users_purged = 0;
515 char *transcript = NULL;
517 CtdlLogPrintf(CTDL_DEBUG, "PurgeUsers() called\n");
518 users_not_purged = 0;
520 switch(config.c_auth_mode) {
521 case AUTHMODE_NATIVE:
522 ForEachUser(do_user_purge, NULL);
525 ForEachUser(do_uid_user_purge, NULL);
528 CtdlLogPrintf(CTDL_DEBUG, "Unknown authentication mode!\n");
532 transcript = malloc(SIZ);
534 if (users_not_purged == 0) {
535 strcpy(transcript, "The auto-purger was told to purge every user. It is\n"
536 "refusing to do this because it usually indicates a problem\n"
537 "such as an inability to communicate with a name service.\n"
539 while (UserPurgeList != NULL) {
540 pptr = UserPurgeList->next;
542 UserPurgeList = pptr;
548 strcpy(transcript, "The following users have been auto-purged:\n");
549 while (UserPurgeList != NULL) {
550 transcript=realloc(transcript, strlen(transcript)+SIZ);
551 snprintf(&transcript[strlen(transcript)], SIZ, " %s\n",
552 UserPurgeList->name);
553 purge_user(UserPurgeList->name);
554 pptr = UserPurgeList->next;
556 UserPurgeList = pptr;
561 if (num_users_purged > 0) aide_message(transcript, "User Purge Message");
564 if(users_corrupt_msg)
566 aide_message(users_corrupt_msg, "User Corruption Message");
567 free (users_corrupt_msg);
568 users_corrupt_msg = NULL;
573 aide_message(users_zero_msg, "User Zero Message");
574 free (users_zero_msg);
575 users_zero_msg = NULL;
578 CtdlLogPrintf(CTDL_DEBUG, "Purged %d users.\n", num_users_purged);
579 return(num_users_purged);
586 * This is a really cumbersome "garbage collection" function. We have to
587 * delete visits which refer to rooms and/or users which no longer exist. In
588 * order to prevent endless traversals of the room and user files, we first
589 * build linked lists of rooms and users which _do_ exist on the system, then
590 * traverse the visit file, checking each record against those two lists and
591 * purging the ones that do not have a match on _both_ lists. (Remember, if
592 * either the room or user being referred to is no longer on the system, the
593 * record is completely useless.)
595 int PurgeVisits(void) {
596 struct cdbdata *cdbvisit;
598 struct VPurgeList *VisitPurgeList = NULL;
599 struct VPurgeList *vptr;
603 struct ValidRoom *vrptr;
604 struct ValidUser *vuptr;
605 int RoomIsValid, UserIsValid;
607 /* First, load up a table full of valid room/gen combinations */
608 ForEachRoom(AddValidRoom, NULL);
610 /* Then load up a table full of valid user numbers */
611 ForEachUser(AddValidUser, NULL);
613 /* Now traverse through the visits, purging irrelevant records... */
614 cdb_rewind(CDB_VISIT);
615 while(cdbvisit = cdb_next_item(CDB_VISIT), cdbvisit != NULL) {
616 memset(&vbuf, 0, sizeof(struct visit));
617 memcpy(&vbuf, cdbvisit->ptr,
618 ( (cdbvisit->len > sizeof(struct visit)) ?
619 sizeof(struct visit) : cdbvisit->len) );
625 /* Check to see if the room exists */
626 for (vrptr=ValidRoomList; vrptr!=NULL; vrptr=vrptr->next) {
627 if ( (vrptr->vr_roomnum==vbuf.v_roomnum)
628 && (vrptr->vr_roomgen==vbuf.v_roomgen))
632 /* Check to see if the user exists */
633 for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) {
634 if (vuptr->vu_usernum == vbuf.v_usernum)
638 /* Put the record on the purge list if it's dead */
639 if ((RoomIsValid==0) || (UserIsValid==0)) {
640 vptr = (struct VPurgeList *)
641 malloc(sizeof(struct VPurgeList));
642 vptr->next = VisitPurgeList;
643 vptr->vp_roomnum = vbuf.v_roomnum;
644 vptr->vp_roomgen = vbuf.v_roomgen;
645 vptr->vp_usernum = vbuf.v_usernum;
646 VisitPurgeList = vptr;
651 /* Free the valid room/gen combination list */
652 while (ValidRoomList != NULL) {
653 vrptr = ValidRoomList->next;
655 ValidRoomList = vrptr;
658 /* Free the valid user list */
659 while (ValidUserList != NULL) {
660 vuptr = ValidUserList->next;
662 ValidUserList = vuptr;
665 /* Now delete every visit on the purged list */
666 while (VisitPurgeList != NULL) {
667 IndexLen = GenerateRelationshipIndex(IndexBuf,
668 VisitPurgeList->vp_roomnum,
669 VisitPurgeList->vp_roomgen,
670 VisitPurgeList->vp_usernum);
671 cdb_delete(CDB_VISIT, IndexBuf, IndexLen);
672 vptr = VisitPurgeList->next;
673 free(VisitPurgeList);
674 VisitPurgeList = vptr;
682 * Purge the use table of old entries.
685 int PurgeUseTable(void) {
687 struct cdbdata *cdbut;
689 struct UPurgeList *ul = NULL;
690 struct UPurgeList *uptr;
692 /* Phase 1: traverse through the table, discovering old records... */
693 CtdlLogPrintf(CTDL_DEBUG, "Purge use table: phase 1\n");
694 cdb_rewind(CDB_USETABLE);
695 while(cdbut = cdb_next_item(CDB_USETABLE), cdbut != NULL) {
698 * TODODRW: change this to create a new function time_t cdb_get_timestamp( struct cdbdata *)
699 * this will release this file from the serv_network.h
700 * Maybe it could be a macro that extracts and casts the reult
702 memcpy(&ut, cdbut->ptr,
703 ((cdbut->len > sizeof(struct UseTable)) ?
704 sizeof(struct UseTable) : cdbut->len));
707 if ( (time(NULL) - ut.ut_timestamp) > USETABLE_RETAIN ) {
708 uptr = (struct UPurgeList *) malloc(sizeof(struct UPurgeList));
711 safestrncpy(uptr->up_key, ut.ut_msgid, sizeof uptr->up_key);
719 /* Phase 2: delete the records */
720 CtdlLogPrintf(CTDL_DEBUG, "Purge use table: phase 2\n");
722 cdb_delete(CDB_USETABLE, ul->up_key, strlen(ul->up_key));
728 CtdlLogPrintf(CTDL_DEBUG, "Purge use table: finished (purged %d records)\n", purged);
735 * Purge the EUID Index of old records.
738 int PurgeEuidIndexTable(void) {
740 struct cdbdata *cdbei;
741 struct EPurgeList *el = NULL;
742 struct EPurgeList *eptr;
744 struct CtdlMessage *msg = NULL;
746 /* Phase 1: traverse through the table, discovering old records... */
747 CtdlLogPrintf(CTDL_DEBUG, "Purge EUID index: phase 1\n");
748 cdb_rewind(CDB_EUIDINDEX);
749 while(cdbei = cdb_next_item(CDB_EUIDINDEX), cdbei != NULL) {
751 memcpy(&msgnum, cdbei->ptr, sizeof(long));
753 msg = CtdlFetchMessage(msgnum, 0);
755 CtdlFreeMessage(msg); /* it still exists, so do nothing */
758 eptr = (struct EPurgeList *) malloc(sizeof(struct EPurgeList));
761 eptr->ep_keylen = cdbei->len - sizeof(long);
762 eptr->ep_key = malloc(cdbei->len);
763 memcpy(eptr->ep_key, &cdbei->ptr[sizeof(long)], eptr->ep_keylen);
773 /* Phase 2: delete the records */
774 CtdlLogPrintf(CTDL_DEBUG, "Purge euid index: phase 2\n");
776 cdb_delete(CDB_EUIDINDEX, el->ep_key, el->ep_keylen);
783 CtdlLogPrintf(CTDL_DEBUG, "Purge euid index: finished (purged %d records)\n", purged);
790 * Purge OpenID assocations for missing users (theoretically this will never delete anything)
792 int PurgeStaleOpenIDassociations(void) {
793 struct cdbdata *cdboi;
794 struct ctdluser usbuf;
795 HashList *keys = NULL;
797 char *deleteme = NULL;
803 keys = NewHash(1, NULL);
804 if (!keys) return(0);
807 cdb_rewind(CDB_OPENID);
808 while (cdboi = cdb_next_item(CDB_OPENID), cdboi != NULL) {
809 if (cdboi->len > sizeof(long)) {
811 usernum = ((long)*(cdboi->ptr));
812 if (getuserbynumber(&usbuf, usernum) != 0) {
813 deleteme = strdup(cdboi->ptr + sizeof(long)),
814 Put(keys, deleteme, strlen(deleteme), deleteme, generic_free_handler);
820 /* Go through the hash list, deleting keys we stored in it */
822 HashPos = GetNewHashPos();
823 while (GetNextHashPos(keys, HashPos, &len, &Key, &Value)!=0)
825 CtdlLogPrintf(CTDL_DEBUG, "Deleting associated OpenID <%s>\n", Value);
826 cdb_delete(CDB_OPENID, Value, strlen(Value));
827 /* note: don't free(Value) -- deleting the hash list will handle this for us */
830 DeleteHashPos(&HashPos);
839 void *purge_databases(void *args)
842 static time_t last_purge = 0;
845 struct CitContext purgerCC;
847 CtdlLogPrintf(CTDL_DEBUG, "Auto-purger_thread() initializing\n");
849 CtdlFillSystemContext(&purgerCC, "purger");
850 citthread_setspecific(MyConKey, (void *)&purgerCC );
852 while (!CtdlThreadCheckStop()) {
853 /* Do the auto-purge if the current hour equals the purge hour,
854 * but not if the operation has already been performed in the
855 * last twelve hours. This is usually enough granularity.
858 localtime_r(&now, &tm);
859 if ((tm.tm_hour != config.c_purge_hour) || ((now - last_purge) < 43200)) {
865 CtdlLogPrintf(CTDL_INFO, "Auto-purger: starting.\n");
867 if (!CtdlThreadCheckStop())
869 retval = PurgeUsers();
870 CtdlLogPrintf(CTDL_NOTICE, "Purged %d users.\n", retval);
873 if (!CtdlThreadCheckStop())
876 CtdlLogPrintf(CTDL_NOTICE, "Expired %d messages.\n", messages_purged);
879 if (!CtdlThreadCheckStop())
881 retval = PurgeRooms();
882 CtdlLogPrintf(CTDL_NOTICE, "Expired %d rooms.\n", retval);
885 if (!CtdlThreadCheckStop())
887 retval = PurgeVisits();
888 CtdlLogPrintf(CTDL_NOTICE, "Purged %d visits.\n", retval);
891 if (!CtdlThreadCheckStop())
893 retval = PurgeUseTable();
894 CtdlLogPrintf(CTDL_NOTICE, "Purged %d entries from the use table.\n", retval);
897 if (!CtdlThreadCheckStop())
899 retval = PurgeEuidIndexTable();
900 CtdlLogPrintf(CTDL_NOTICE, "Purged %d entries from the EUID index.\n", retval);
903 if (!CtdlThreadCheckStop())
905 retval = PurgeStaleOpenIDassociations();
906 CtdlLogPrintf(CTDL_NOTICE, "Purged %d stale OpenID associations.\n", retval);
909 if (!CtdlThreadCheckStop())
911 retval = TDAP_ProcessAdjRefCountQueue();
912 CtdlLogPrintf(CTDL_NOTICE, "Processed %d message reference count adjustments.\n", retval);
915 if (!CtdlThreadCheckStop())
917 CtdlLogPrintf(CTDL_INFO, "Auto-purger: finished.\n");
918 last_purge = now; /* So we don't do it again soon */
921 CtdlLogPrintf(CTDL_INFO, "Auto-purger: STOPPED.\n");
926 /*****************************************************************************/
929 void do_fsck_msg(long msgnum, void *userdata) {
930 struct ctdlroomref *ptr;
932 ptr = (struct ctdlroomref *)malloc(sizeof(struct ctdlroomref));
934 ptr->msgnum = msgnum;
938 void do_fsck_room(struct ctdlroom *qrbuf, void *data)
940 getroom(&CC->room, qrbuf->QRname);
941 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, do_fsck_msg, NULL);
945 * Check message reference counts
947 void cmd_fsck(char *argbuf) {
949 struct cdbdata *cdbmsg;
951 struct ctdlroomref *ptr;
954 if (CtdlAccessCheck(ac_aide)) return;
956 /* Lame way of checking whether anyone else is doing this now */
958 cprintf("%d Another FSCK is already running.\n", ERROR + RESOURCE_BUSY);
962 cprintf("%d Checking message reference counts\n", LISTING_FOLLOWS);
964 cprintf("\nThis could take a while. Please be patient!\n\n");
965 cprintf("Gathering pointers...\n");
966 ForEachRoom(do_fsck_room, NULL);
969 cprintf("Checking message base...\n");
970 for (msgnum = 0L; msgnum <= CitControl.MMhighest; ++msgnum) {
972 cdbmsg = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
973 if (cdbmsg != NULL) {
975 cprintf("Message %7ld ", msgnum);
977 GetMetaData(&smi, msgnum);
978 cprintf("refcount=%-2d ", smi.meta_refcount);
981 for (ptr = rr; ptr != NULL; ptr = ptr->next) {
982 if (ptr->msgnum == msgnum) ++realcount;
984 cprintf("realcount=%-2d\n", realcount);
986 if ( (smi.meta_refcount != realcount)
987 || (realcount == 0) ) {
988 AdjRefCount(msgnum, (smi.meta_refcount - realcount));
995 cprintf("Freeing memory...\n");
1010 /*****************************************************************************/
1012 CTDL_MODULE_INIT(expire)
1016 CtdlRegisterProtoHook(cmd_fsck, "FSCK", "Check message ref counts");
1019 CtdlThreadCreate("Auto Purger", CTDLTHREAD_BIGSTACK, purge_databases, NULL);
1020 /* return our Subversion id for the Log */