4 * This module handles the expiry of old messages and the purging of old users.
6 * You might also see this module affectionately referred to as the DAP (the Dreaded Auto-Purger).
8 * Copyright (c) 1988-2009 by citadel.org (Art Cancro, Wilifried Goesgens, and others)
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 3 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 * A brief technical discussion:
27 * Several of the purge operations found in this module operate in two
28 * stages: the first stage generates a linked list of objects to be deleted,
29 * then the second stage deletes all listed objects from the database.
31 * At first glance this may seem cumbersome and unnecessary. The reason it is
32 * implemented in this way is because Berkeley DB, and possibly other backends
33 * we may hook into in the future, explicitly do _not_ support the deletion of
34 * records from a file while the file is being traversed. The delete operation
35 * will succeed, but the traversal is not guaranteed to visit every object if
36 * this is done. Therefore we utilize the two-stage purge.
38 * When using Berkeley DB, there's another reason for the two-phase purge: we
39 * don't want the entire thing being done as one huge transaction.
41 * You'll also notice that we build the in-memory list of records to be deleted
42 * sometimes with a linked list and sometimes with a hash table. There is no
43 * reason for this aside from the fact that the linked list ones were written
44 * before we had the hash table library available.
56 #include <sys/types.h>
58 #if TIME_WITH_SYS_TIME
59 # include <sys/time.h>
63 # include <sys/time.h>
72 #include <libcitadel.h>
75 #include "citserver.h"
84 #include "serv_network.h" /* Needed for definition of UseTable */
87 #include "ctdl_module.h"
91 struct PurgeList *next;
92 char name[ROOMNAMELEN]; /* use the larger of username or roomname */
96 struct VPurgeList *next;
103 struct ValidRoom *next;
109 struct ValidUser *next;
115 struct ctdlroomref *next;
120 struct UPurgeList *next;
125 struct EPurgeList *next;
131 struct PurgeList *UserPurgeList = NULL;
132 struct PurgeList *RoomPurgeList = NULL;
133 struct ValidRoom *ValidRoomList = NULL;
134 struct ValidUser *ValidUserList = NULL;
136 int users_not_purged;
137 char *users_corrupt_msg = NULL;
138 char *users_zero_msg = NULL;
139 struct ctdlroomref *rr = NULL;
140 extern struct CitContext *ContextList;
141 int force_purge_now = 0; /* set to nonzero to force a run right now */
145 * First phase of message purge -- gather the locations of messages which
146 * qualify for purging and write them to a temp file.
148 void GatherPurgeMessages(struct ctdlroom *qrbuf, void *data) {
149 struct ExpirePolicy epbuf;
152 struct CtdlMessage *msg = NULL;
154 struct cdbdata *cdbfr;
155 long *msglist = NULL;
159 purgelist = (FILE *)data;
160 fprintf(purgelist, "r=%s\n", qrbuf->QRname);
163 GetExpirePolicy(&epbuf, qrbuf);
165 /* If the room is set to never expire messages ... do nothing */
166 if (epbuf.expire_mode == EXPIRE_NEXTLEVEL) return;
167 if (epbuf.expire_mode == EXPIRE_MANUAL) return;
169 /* Don't purge messages containing system configuration, dumbass. */
170 if (!strcasecmp(qrbuf->QRname, SYSCONFIGROOM)) return;
172 /* Ok, we got this far ... now let's see what's in the room */
173 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf->QRnumber, sizeof(long));
176 msglist = malloc(cdbfr->len);
177 memcpy(msglist, cdbfr->ptr, cdbfr->len);
178 num_msgs = cdbfr->len / sizeof(long);
182 /* Nothing to do if there aren't any messages */
184 if (msglist != NULL) free(msglist);
189 /* If the room is set to expire by count, do that */
190 if (epbuf.expire_mode == EXPIRE_NUMMSGS) {
191 if (num_msgs > epbuf.expire_value) {
192 for (a=0; a<(num_msgs - epbuf.expire_value); ++a) {
193 fprintf(purgelist, "m=%ld\n", msglist[a]);
199 /* If the room is set to expire by age... */
200 if (epbuf.expire_mode == EXPIRE_AGE) {
201 for (a=0; a<num_msgs; ++a) {
204 msg = CtdlFetchMessage(delnum, 0); /* dont need body */
206 xtime = atol(msg->cm_fields['T']);
207 CtdlFreeMessage(msg);
213 && (now - xtime > (time_t)(epbuf.expire_value * 86400L))) {
214 fprintf(purgelist, "m=%ld\n", delnum);
220 if (msglist != NULL) free(msglist);
225 * Second phase of message purge -- read list of msgs from temp file and
228 void DoPurgeMessages(FILE *purgelist) {
229 char roomname[ROOMNAMELEN];
234 strcpy(roomname, "nonexistent room ___ ___");
235 while (fgets(buf, sizeof buf, purgelist) != NULL) {
236 buf[strlen(buf)-1]=0;
237 if (!strncasecmp(buf, "r=", 2)) {
238 strcpy(roomname, &buf[2]);
240 if (!strncasecmp(buf, "m=", 2)) {
241 msgnum = atol(&buf[2]);
243 CtdlDeleteMessages(roomname, &msgnum, 1, "");
250 void PurgeMessages(void) {
253 CtdlLogPrintf(CTDL_DEBUG, "PurgeMessages() called\n");
256 purgelist = tmpfile();
257 if (purgelist == NULL) {
258 CtdlLogPrintf(CTDL_CRIT, "Can't create purgelist temp file: %s\n",
263 ForEachRoom(GatherPurgeMessages, (void *)purgelist );
264 DoPurgeMessages(purgelist);
269 void AddValidUser(struct ctdluser *usbuf, void *data) {
270 struct ValidUser *vuptr;
272 vuptr = (struct ValidUser *)malloc(sizeof(struct ValidUser));
273 vuptr->next = ValidUserList;
274 vuptr->vu_usernum = usbuf->usernum;
275 ValidUserList = vuptr;
278 void AddValidRoom(struct ctdlroom *qrbuf, void *data) {
279 struct ValidRoom *vrptr;
281 vrptr = (struct ValidRoom *)malloc(sizeof(struct ValidRoom));
282 vrptr->next = ValidRoomList;
283 vrptr->vr_roomnum = qrbuf->QRnumber;
284 vrptr->vr_roomgen = qrbuf->QRgen;
285 ValidRoomList = vrptr;
288 void DoPurgeRooms(struct ctdlroom *qrbuf, void *data) {
289 time_t age, purge_secs;
290 struct PurgeList *pptr;
291 struct ValidUser *vuptr;
294 /* For mailbox rooms, there's only one purging rule: if the user who
295 * owns the room still exists, we keep the room; otherwise, we purge
296 * it. Bypass any other rules.
298 if (qrbuf->QRflags & QR_MAILBOX) {
299 /* if user not found, do_purge will be 1 */
301 for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) {
302 if (vuptr->vu_usernum == atol(qrbuf->QRname)) {
308 /* Any of these attributes render a room non-purgable */
309 if (qrbuf->QRflags & QR_PERMANENT) return;
310 if (qrbuf->QRflags & QR_DIRECTORY) return;
311 if (qrbuf->QRflags & QR_NETWORK) return;
312 if (qrbuf->QRflags2 & QR2_SYSTEM) return;
313 if (!strcasecmp(qrbuf->QRname, SYSCONFIGROOM)) return;
314 if (is_noneditable(qrbuf)) return;
316 /* If we don't know the modification date, be safe and don't purge */
317 if (qrbuf->QRmtime <= (time_t)0) return;
319 /* If no room purge time is set, be safe and don't purge */
320 if (config.c_roompurge < 0) return;
322 /* Otherwise, check the date of last modification */
323 age = time(NULL) - (qrbuf->QRmtime);
324 purge_secs = (time_t)config.c_roompurge * (time_t)86400;
325 if (purge_secs <= (time_t)0) return;
326 CtdlLogPrintf(CTDL_DEBUG, "<%s> is <%ld> seconds old\n", qrbuf->QRname, (long)age);
327 if (age > purge_secs) do_purge = 1;
331 pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList));
332 pptr->next = RoomPurgeList;
333 strcpy(pptr->name, qrbuf->QRname);
334 RoomPurgeList = pptr;
341 int PurgeRooms(void) {
342 struct PurgeList *pptr;
343 int num_rooms_purged = 0;
344 struct ctdlroom qrbuf;
345 struct ValidUser *vuptr;
346 char *transcript = NULL;
348 CtdlLogPrintf(CTDL_DEBUG, "PurgeRooms() called\n");
351 /* Load up a table full of valid user numbers so we can delete
352 * user-owned rooms for users who no longer exist */
353 ForEachUser(AddValidUser, NULL);
355 /* Then cycle through the room file */
356 ForEachRoom(DoPurgeRooms, NULL);
358 /* Free the valid user list */
359 while (ValidUserList != NULL) {
360 vuptr = ValidUserList->next;
362 ValidUserList = vuptr;
366 transcript = malloc(SIZ);
367 strcpy(transcript, "The following rooms have been auto-purged:\n");
369 while (RoomPurgeList != NULL) {
370 if (getroom(&qrbuf, RoomPurgeList->name) == 0) {
371 transcript=realloc(transcript, strlen(transcript)+SIZ);
372 snprintf(&transcript[strlen(transcript)], SIZ, " %s\n",
376 pptr = RoomPurgeList->next;
378 RoomPurgeList = pptr;
382 if (num_rooms_purged > 0) aide_message(transcript, "Room Autopurger Message");
385 CtdlLogPrintf(CTDL_DEBUG, "Purged %d rooms.\n", num_rooms_purged);
386 return(num_rooms_purged);
391 * Back end function to check user accounts for associated Unix accounts
392 * which no longer exist. (Only relevant for host auth mode.)
394 void do_uid_user_purge(struct ctdluser *us, void *data) {
395 struct PurgeList *pptr;
397 if ((us->uid != (-1)) && (us->uid != CTDLUID)) {
398 if (getpwuid(us->uid) == NULL) {
399 pptr = (struct PurgeList *)
400 malloc(sizeof(struct PurgeList));
401 pptr->next = UserPurgeList;
402 strcpy(pptr->name, us->fullname);
403 UserPurgeList = pptr;
415 * Back end function to check user accounts for expiration.
417 void do_user_purge(struct ctdluser *us, void *data) {
421 struct PurgeList *pptr;
423 /* Set purge time; if the user overrides the system default, use it */
424 if (us->USuserpurge > 0) {
425 purge_time = ((time_t)us->USuserpurge) * 86400L;
428 purge_time = ((time_t)config.c_userpurge) * 86400L;
431 /* The default rule is to not purge. */
434 /* don't attempt to purge system users. */
435 if (!strncmp(us->fullname, "SYS_", 4))
438 /* If the user hasn't called in two months and expiring of accounts is turned on, his/her account
439 * has expired, so purge the record.
441 if (config.c_userpurge > 0)
444 if ((now - us->lastcall) > purge_time) purge = 1;
447 /* If the record is marked as permanent, don't purge it.
449 if (us->flags & US_PERM) purge = 0;
451 /* If the user is an Aide, don't purge him/her/it.
453 if (us->axlevel == 6) purge = 0;
455 /* If the access level is 0, the record should already have been
456 * deleted, but maybe the user was logged in at the time or something.
457 * Delete the record now.
459 if (us->axlevel == 0) purge = 1;
461 /* If the user set his/her password to 'deleteme', he/she
462 * wishes to be deleted, so purge the record.
463 * Moved this lower down so that aides and permanent users get purged if they ask to be.
465 if (!strcasecmp(us->password, "deleteme")) purge = 1;
467 /* 0 calls is impossible. If there are 0 calls, it must
468 * be a corrupted record, so purge it.
469 * Actually it is possible if an Aide created the user so now we check for less than 0 (DRW)
471 if (us->timescalled < 0) purge = 1;
473 /* any negative user number, is
476 if (us->usernum < 0L) purge = 1;
478 /** Don't purge user 0. That user is there for the system */
479 if (us->usernum == 0L)
481 /* FIXME: Temporary log message. Until we do unauth access with user 0 we should
482 * try to get rid of all user 0 occurences. Many will be remnants from old code so
483 * we will need to try and purge them from users data bases.Some will not have names but
484 * those with names should be purged.
486 CtdlLogPrintf(CTDL_DEBUG, "Auto purger found a user 0 with name \"%s\"\n", us->fullname);
490 /* If the user has no full name entry then we can't purge them
491 * since the actual purge can't find them.
492 * This shouldn't happen but does somehow.
494 if (IsEmptyStr(us->fullname))
498 if (us->usernum > 0L)
501 if (users_corrupt_msg == NULL)
503 users_corrupt_msg = malloc(SIZ);
504 strcpy(users_corrupt_msg, "The auto-purger found the following user numbers with no name.\n"
505 "The system has no way to purge user with no name and should not be able to\n"
506 "create them either.\n"
507 "This indicates corruption of the user DB or possibly a bug.\n"
508 "It may be a good idea to restore your DB from a backup.\n");
511 users_corrupt_msg=realloc(users_corrupt_msg, strlen(users_corrupt_msg)+30);
512 snprintf(&users_corrupt_msg[strlen(users_corrupt_msg)], 29, " %ld\n", us->usernum);
519 pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList));
520 pptr->next = UserPurgeList;
521 strcpy(pptr->name, us->fullname);
522 UserPurgeList = pptr;
532 int PurgeUsers(void) {
533 struct PurgeList *pptr;
534 int num_users_purged = 0;
535 char *transcript = NULL;
537 CtdlLogPrintf(CTDL_DEBUG, "PurgeUsers() called\n");
538 users_not_purged = 0;
540 switch(config.c_auth_mode) {
541 case AUTHMODE_NATIVE:
542 ForEachUser(do_user_purge, NULL);
545 ForEachUser(do_uid_user_purge, NULL);
548 CtdlLogPrintf(CTDL_DEBUG, "User purge for auth mode %d is not implemented.\n",
553 transcript = malloc(SIZ);
555 if (users_not_purged == 0) {
556 strcpy(transcript, "The auto-purger was told to purge every user. It is\n"
557 "refusing to do this because it usually indicates a problem\n"
558 "such as an inability to communicate with a name service.\n"
560 while (UserPurgeList != NULL) {
561 pptr = UserPurgeList->next;
563 UserPurgeList = pptr;
569 strcpy(transcript, "The following users have been auto-purged:\n");
570 while (UserPurgeList != NULL) {
571 transcript=realloc(transcript, strlen(transcript)+SIZ);
572 snprintf(&transcript[strlen(transcript)], SIZ, " %s\n",
573 UserPurgeList->name);
574 purge_user(UserPurgeList->name);
575 pptr = UserPurgeList->next;
577 UserPurgeList = pptr;
582 if (num_users_purged > 0) aide_message(transcript, "User Purge Message");
585 if(users_corrupt_msg)
587 aide_message(users_corrupt_msg, "User Corruption Message");
588 free (users_corrupt_msg);
589 users_corrupt_msg = NULL;
594 aide_message(users_zero_msg, "User Zero Message");
595 free (users_zero_msg);
596 users_zero_msg = NULL;
599 CtdlLogPrintf(CTDL_DEBUG, "Purged %d users.\n", num_users_purged);
600 return(num_users_purged);
607 * This is a really cumbersome "garbage collection" function. We have to
608 * delete visits which refer to rooms and/or users which no longer exist. In
609 * order to prevent endless traversals of the room and user files, we first
610 * build linked lists of rooms and users which _do_ exist on the system, then
611 * traverse the visit file, checking each record against those two lists and
612 * purging the ones that do not have a match on _both_ lists. (Remember, if
613 * either the room or user being referred to is no longer on the system, the
614 * record is completely useless.)
616 int PurgeVisits(void) {
617 struct cdbdata *cdbvisit;
619 struct VPurgeList *VisitPurgeList = NULL;
620 struct VPurgeList *vptr;
624 struct ValidRoom *vrptr;
625 struct ValidUser *vuptr;
626 int RoomIsValid, UserIsValid;
628 /* First, load up a table full of valid room/gen combinations */
629 ForEachRoom(AddValidRoom, NULL);
631 /* Then load up a table full of valid user numbers */
632 ForEachUser(AddValidUser, NULL);
634 /* Now traverse through the visits, purging irrelevant records... */
635 cdb_rewind(CDB_VISIT);
636 while(cdbvisit = cdb_next_item(CDB_VISIT), cdbvisit != NULL) {
637 memset(&vbuf, 0, sizeof(struct visit));
638 memcpy(&vbuf, cdbvisit->ptr,
639 ( (cdbvisit->len > sizeof(struct visit)) ?
640 sizeof(struct visit) : cdbvisit->len) );
646 /* Check to see if the room exists */
647 for (vrptr=ValidRoomList; vrptr!=NULL; vrptr=vrptr->next) {
648 if ( (vrptr->vr_roomnum==vbuf.v_roomnum)
649 && (vrptr->vr_roomgen==vbuf.v_roomgen))
653 /* Check to see if the user exists */
654 for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) {
655 if (vuptr->vu_usernum == vbuf.v_usernum)
659 /* Put the record on the purge list if it's dead */
660 if ((RoomIsValid==0) || (UserIsValid==0)) {
661 vptr = (struct VPurgeList *)
662 malloc(sizeof(struct VPurgeList));
663 vptr->next = VisitPurgeList;
664 vptr->vp_roomnum = vbuf.v_roomnum;
665 vptr->vp_roomgen = vbuf.v_roomgen;
666 vptr->vp_usernum = vbuf.v_usernum;
667 VisitPurgeList = vptr;
672 /* Free the valid room/gen combination list */
673 while (ValidRoomList != NULL) {
674 vrptr = ValidRoomList->next;
676 ValidRoomList = vrptr;
679 /* Free the valid user list */
680 while (ValidUserList != NULL) {
681 vuptr = ValidUserList->next;
683 ValidUserList = vuptr;
686 /* Now delete every visit on the purged list */
687 while (VisitPurgeList != NULL) {
688 IndexLen = GenerateRelationshipIndex(IndexBuf,
689 VisitPurgeList->vp_roomnum,
690 VisitPurgeList->vp_roomgen,
691 VisitPurgeList->vp_usernum);
692 cdb_delete(CDB_VISIT, IndexBuf, IndexLen);
693 vptr = VisitPurgeList->next;
694 free(VisitPurgeList);
695 VisitPurgeList = vptr;
703 * Purge the use table of old entries.
706 int PurgeUseTable(void) {
708 struct cdbdata *cdbut;
710 struct UPurgeList *ul = NULL;
711 struct UPurgeList *uptr;
713 /* Phase 1: traverse through the table, discovering old records... */
714 CtdlLogPrintf(CTDL_DEBUG, "Purge use table: phase 1\n");
715 cdb_rewind(CDB_USETABLE);
716 while(cdbut = cdb_next_item(CDB_USETABLE), cdbut != NULL) {
719 * TODODRW: change this to create a new function time_t cdb_get_timestamp( struct cdbdata *)
720 * this will release this file from the serv_network.h
721 * Maybe it could be a macro that extracts and casts the reult
723 memcpy(&ut, cdbut->ptr,
724 ((cdbut->len > sizeof(struct UseTable)) ?
725 sizeof(struct UseTable) : cdbut->len));
728 if ( (time(NULL) - ut.ut_timestamp) > USETABLE_RETAIN ) {
729 uptr = (struct UPurgeList *) malloc(sizeof(struct UPurgeList));
732 safestrncpy(uptr->up_key, ut.ut_msgid, sizeof uptr->up_key);
740 /* Phase 2: delete the records */
741 CtdlLogPrintf(CTDL_DEBUG, "Purge use table: phase 2\n");
743 cdb_delete(CDB_USETABLE, ul->up_key, strlen(ul->up_key));
749 CtdlLogPrintf(CTDL_DEBUG, "Purge use table: finished (purged %d records)\n", purged);
756 * Purge the EUID Index of old records.
759 int PurgeEuidIndexTable(void) {
761 struct cdbdata *cdbei;
762 struct EPurgeList *el = NULL;
763 struct EPurgeList *eptr;
765 struct CtdlMessage *msg = NULL;
767 /* Phase 1: traverse through the table, discovering old records... */
768 CtdlLogPrintf(CTDL_DEBUG, "Purge EUID index: phase 1\n");
769 cdb_rewind(CDB_EUIDINDEX);
770 while(cdbei = cdb_next_item(CDB_EUIDINDEX), cdbei != NULL) {
772 memcpy(&msgnum, cdbei->ptr, sizeof(long));
774 msg = CtdlFetchMessage(msgnum, 0);
776 CtdlFreeMessage(msg); /* it still exists, so do nothing */
779 eptr = (struct EPurgeList *) malloc(sizeof(struct EPurgeList));
782 eptr->ep_keylen = cdbei->len - sizeof(long);
783 eptr->ep_key = malloc(cdbei->len);
784 memcpy(eptr->ep_key, &cdbei->ptr[sizeof(long)], eptr->ep_keylen);
794 /* Phase 2: delete the records */
795 CtdlLogPrintf(CTDL_DEBUG, "Purge euid index: phase 2\n");
797 cdb_delete(CDB_EUIDINDEX, el->ep_key, el->ep_keylen);
804 CtdlLogPrintf(CTDL_DEBUG, "Purge euid index: finished (purged %d records)\n", purged);
811 * Purge OpenID assocations for missing users (theoretically this will never delete anything)
813 int PurgeStaleOpenIDassociations(void) {
814 struct cdbdata *cdboi;
815 struct ctdluser usbuf;
816 HashList *keys = NULL;
818 char *deleteme = NULL;
825 keys = NewHash(1, NULL);
826 if (!keys) return(0);
829 cdb_rewind(CDB_OPENID);
830 while (cdboi = cdb_next_item(CDB_OPENID), cdboi != NULL) {
831 if (cdboi->len > sizeof(long)) {
832 memcpy(&usernum, cdboi->ptr, sizeof(long));
833 if (getuserbynumber(&usbuf, usernum) != 0) {
834 deleteme = strdup(cdboi->ptr + sizeof(long)),
835 Put(keys, deleteme, strlen(deleteme), deleteme, generic_free_handler);
841 /* Go through the hash list, deleting keys we stored in it */
843 HashPos = GetNewHashPos(keys, 0);
844 while (GetNextHashPos(keys, HashPos, &len, &Key, &Value)!=0)
846 CtdlLogPrintf(CTDL_DEBUG, "Deleting associated OpenID <%s>\n", Value);
847 cdb_delete(CDB_OPENID, Value, strlen(Value));
848 /* note: don't free(Value) -- deleting the hash list will handle this for us */
851 DeleteHashPos(&HashPos);
860 void *purge_databases(void *args)
863 static time_t last_purge = 0;
866 struct CitContext purgerCC;
868 CtdlLogPrintf(CTDL_DEBUG, "Auto-purger_thread() initializing\n");
870 CtdlFillSystemContext(&purgerCC, "purger");
871 citthread_setspecific(MyConKey, (void *)&purgerCC );
873 while (!CtdlThreadCheckStop()) {
874 /* Do the auto-purge if the current hour equals the purge hour,
875 * but not if the operation has already been performed in the
876 * last twelve hours. This is usually enough granularity.
879 localtime_r(&now, &tm);
881 ((tm.tm_hour != config.c_purge_hour) || ((now - last_purge) < 43200))
882 && (force_purge_now == 0)
889 CtdlLogPrintf(CTDL_INFO, "Auto-purger: starting.\n");
891 if (!CtdlThreadCheckStop())
893 retval = PurgeUsers();
894 CtdlLogPrintf(CTDL_NOTICE, "Purged %d users.\n", retval);
897 if (!CtdlThreadCheckStop())
900 CtdlLogPrintf(CTDL_NOTICE, "Expired %d messages.\n", messages_purged);
903 if (!CtdlThreadCheckStop())
905 retval = PurgeRooms();
906 CtdlLogPrintf(CTDL_NOTICE, "Expired %d rooms.\n", retval);
909 if (!CtdlThreadCheckStop())
911 retval = PurgeVisits();
912 CtdlLogPrintf(CTDL_NOTICE, "Purged %d visits.\n", retval);
915 if (!CtdlThreadCheckStop())
917 retval = PurgeUseTable();
918 CtdlLogPrintf(CTDL_NOTICE, "Purged %d entries from the use table.\n", retval);
921 if (!CtdlThreadCheckStop())
923 retval = PurgeEuidIndexTable();
924 CtdlLogPrintf(CTDL_NOTICE, "Purged %d entries from the EUID index.\n", retval);
927 if (!CtdlThreadCheckStop())
929 retval = PurgeStaleOpenIDassociations();
930 CtdlLogPrintf(CTDL_NOTICE, "Purged %d stale OpenID associations.\n", retval);
933 if (!CtdlThreadCheckStop())
935 retval = TDAP_ProcessAdjRefCountQueue();
936 CtdlLogPrintf(CTDL_NOTICE, "Processed %d message reference count adjustments.\n", retval);
939 if (!CtdlThreadCheckStop())
941 CtdlLogPrintf(CTDL_INFO, "Auto-purger: finished.\n");
942 last_purge = now; /* So we don't do it again soon */
946 CtdlLogPrintf(CTDL_INFO, "Auto-purger: STOPPED.\n");
951 /*****************************************************************************/
954 /* The FSCK command has been removed because people were misusing it */
958 void do_fsck_msg(long msgnum, void *userdata) {
959 struct ctdlroomref *ptr;
961 ptr = (struct ctdlroomref *)malloc(sizeof(struct ctdlroomref));
963 ptr->msgnum = msgnum;
967 void do_fsck_room(struct ctdlroom *qrbuf, void *data)
969 getroom(&CC->room, qrbuf->QRname);
970 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, do_fsck_msg, NULL);
974 * Check message reference counts
976 void cmd_fsck(char *argbuf) {
978 struct cdbdata *cdbmsg;
980 struct ctdlroomref *ptr;
983 if (CtdlAccessCheck(ac_aide)) return;
985 /* Lame way of checking whether anyone else is doing this now */
987 cprintf("%d Another FSCK is already running.\n", ERROR + RESOURCE_BUSY);
991 cprintf("%d Checking message reference counts\n", LISTING_FOLLOWS);
993 cprintf("\nThis could take a while. Please be patient!\n\n");
994 cprintf("Gathering pointers...\n");
995 ForEachRoom(do_fsck_room, NULL);
998 cprintf("Checking message base...\n");
999 for (msgnum = 0L; msgnum <= CitControl.MMhighest; ++msgnum) {
1001 cdbmsg = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1002 if (cdbmsg != NULL) {
1004 cprintf("Message %7ld ", msgnum);
1006 GetMetaData(&smi, msgnum);
1007 cprintf("refcount=%-2d ", smi.meta_refcount);
1010 for (ptr = rr; ptr != NULL; ptr = ptr->next) {
1011 if (ptr->msgnum == msgnum) ++realcount;
1013 cprintf("realcount=%-2d\n", realcount);
1015 if ( (smi.meta_refcount != realcount)
1016 || (realcount == 0) ) {
1017 AdjRefCount(msgnum, (smi.meta_refcount - realcount));
1024 cprintf("Freeing memory...\n");
1025 while (rr != NULL) {
1036 #endif /* end of commented-out fsck cmd */
1039 * Manually initiate a run of The Dreaded Auto-Purger (tm)
1041 void cmd_tdap(char *argbuf) {
1042 if (CtdlAccessCheck(ac_aide)) return;
1043 force_purge_now = 1;
1044 cprintf("%d Manually initiating a purger run now.\n", CIT_OK);
1048 /*****************************************************************************/
1050 CTDL_MODULE_INIT(expire)
1054 /* CtdlRegisterProtoHook(cmd_fsck, "FSCK", "Check message ref counts"); */
1055 CtdlRegisterProtoHook(cmd_tdap, "TDAP", "Manually initiate auto-purger");
1058 CtdlThreadCreate("Auto Purger", CTDLTHREAD_BIGSTACK, purge_databases, NULL);
1059 /* return our Subversion id for the Log */