2 * This module handles the expiry of old messages and the purging of old users.
4 * You might also see this module affectionately referred to as the DAP (the Dreaded Auto-Purger).
6 * Copyright (c) 1988-2009 by citadel.org (Art Cancro, Wilifried Goesgens, and others)
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 * A brief technical discussion:
25 * Several of the purge operations found in this module operate in two
26 * stages: the first stage generates a linked list of objects to be deleted,
27 * then the second stage deletes all listed objects from the database.
29 * At first glance this may seem cumbersome and unnecessary. The reason it is
30 * implemented in this way is because Berkeley DB, and possibly other backends
31 * we may hook into in the future, explicitly do _not_ support the deletion of
32 * records from a file while the file is being traversed. The delete operation
33 * will succeed, but the traversal is not guaranteed to visit every object if
34 * this is done. Therefore we utilize the two-stage purge.
36 * When using Berkeley DB, there's another reason for the two-phase purge: we
37 * don't want the entire thing being done as one huge transaction.
39 * You'll also notice that we build the in-memory list of records to be deleted
40 * sometimes with a linked list and sometimes with a hash table. There is no
41 * reason for this aside from the fact that the linked list ones were written
42 * before we had the hash table library available.
54 #include <sys/types.h>
56 #if TIME_WITH_SYS_TIME
57 # include <sys/time.h>
61 # include <sys/time.h>
70 #include <libcitadel.h>
73 #include "citserver.h"
81 #include "serv_network.h" /* Needed for definition of UseTable */
85 #include "ctdl_module.h"
89 struct PurgeList *next;
90 char name[ROOMNAMELEN]; /* use the larger of username or roomname */
94 struct VPurgeList *next;
101 struct ValidRoom *next;
107 struct ValidUser *next;
113 struct ctdlroomref *next;
118 struct UPurgeList *next;
123 struct EPurgeList *next;
129 struct PurgeList *UserPurgeList = NULL;
130 struct PurgeList *RoomPurgeList = NULL;
131 struct ValidRoom *ValidRoomList = NULL;
132 struct ValidUser *ValidUserList = NULL;
134 int users_not_purged;
135 char *users_corrupt_msg = NULL;
136 char *users_zero_msg = NULL;
137 struct ctdlroomref *rr = NULL;
138 int force_purge_now = 0; /* set to nonzero to force a run right now */
142 * First phase of message purge -- gather the locations of messages which
143 * qualify for purging and write them to a temp file.
145 void GatherPurgeMessages(struct ctdlroom *qrbuf, void *data) {
146 struct ExpirePolicy epbuf;
149 struct CtdlMessage *msg = NULL;
151 struct cdbdata *cdbfr;
152 long *msglist = NULL;
156 purgelist = (FILE *)data;
157 fprintf(purgelist, "r=%s\n", qrbuf->QRname);
160 GetExpirePolicy(&epbuf, qrbuf);
162 /* If the room is set to never expire messages ... do nothing */
163 if (epbuf.expire_mode == EXPIRE_NEXTLEVEL) return;
164 if (epbuf.expire_mode == EXPIRE_MANUAL) return;
166 /* Don't purge messages containing system configuration, dumbass. */
167 if (!strcasecmp(qrbuf->QRname, SYSCONFIGROOM)) return;
169 /* Ok, we got this far ... now let's see what's in the room */
170 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf->QRnumber, sizeof(long));
173 msglist = malloc(cdbfr->len);
174 memcpy(msglist, cdbfr->ptr, cdbfr->len);
175 num_msgs = cdbfr->len / sizeof(long);
179 /* Nothing to do if there aren't any messages */
181 if (msglist != NULL) free(msglist);
186 /* If the room is set to expire by count, do that */
187 if (epbuf.expire_mode == EXPIRE_NUMMSGS) {
188 if (num_msgs > epbuf.expire_value) {
189 for (a=0; a<(num_msgs - epbuf.expire_value); ++a) {
190 fprintf(purgelist, "m=%ld\n", msglist[a]);
196 /* If the room is set to expire by age... */
197 if (epbuf.expire_mode == EXPIRE_AGE) {
198 for (a=0; a<num_msgs; ++a) {
201 msg = CtdlFetchMessage(delnum, 0); /* dont need body */
203 xtime = atol(msg->cm_fields['T']);
204 CtdlFreeMessage(msg);
210 && (now - xtime > (time_t)(epbuf.expire_value * 86400L))) {
211 fprintf(purgelist, "m=%ld\n", delnum);
217 if (msglist != NULL) free(msglist);
222 * Second phase of message purge -- read list of msgs from temp file and
225 void DoPurgeMessages(FILE *purgelist) {
226 char roomname[ROOMNAMELEN];
231 strcpy(roomname, "nonexistent room ___ ___");
232 while (fgets(buf, sizeof buf, purgelist) != NULL) {
233 buf[strlen(buf)-1]=0;
234 if (!strncasecmp(buf, "r=", 2)) {
235 strcpy(roomname, &buf[2]);
237 if (!strncasecmp(buf, "m=", 2)) {
238 msgnum = atol(&buf[2]);
240 CtdlDeleteMessages(roomname, &msgnum, 1, "");
247 void PurgeMessages(void) {
250 CtdlLogPrintf(CTDL_DEBUG, "PurgeMessages() called\n");
253 purgelist = tmpfile();
254 if (purgelist == NULL) {
255 CtdlLogPrintf(CTDL_CRIT, "Can't create purgelist temp file: %s\n",
260 CtdlForEachRoom(GatherPurgeMessages, (void *)purgelist );
261 DoPurgeMessages(purgelist);
266 void AddValidUser(struct ctdluser *usbuf, void *data) {
267 struct ValidUser *vuptr;
269 vuptr = (struct ValidUser *)malloc(sizeof(struct ValidUser));
270 vuptr->next = ValidUserList;
271 vuptr->vu_usernum = usbuf->usernum;
272 ValidUserList = vuptr;
275 void AddValidRoom(struct ctdlroom *qrbuf, void *data) {
276 struct ValidRoom *vrptr;
278 vrptr = (struct ValidRoom *)malloc(sizeof(struct ValidRoom));
279 vrptr->next = ValidRoomList;
280 vrptr->vr_roomnum = qrbuf->QRnumber;
281 vrptr->vr_roomgen = qrbuf->QRgen;
282 ValidRoomList = vrptr;
285 void DoPurgeRooms(struct ctdlroom *qrbuf, void *data) {
286 time_t age, purge_secs;
287 struct PurgeList *pptr;
288 struct ValidUser *vuptr;
291 /* For mailbox rooms, there's only one purging rule: if the user who
292 * owns the room still exists, we keep the room; otherwise, we purge
293 * it. Bypass any other rules.
295 if (qrbuf->QRflags & QR_MAILBOX) {
296 /* if user not found, do_purge will be 1 */
298 for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) {
299 if (vuptr->vu_usernum == atol(qrbuf->QRname)) {
305 /* Any of these attributes render a room non-purgable */
306 if (qrbuf->QRflags & QR_PERMANENT) return;
307 if (qrbuf->QRflags & QR_DIRECTORY) return;
308 if (qrbuf->QRflags & QR_NETWORK) return;
309 if (qrbuf->QRflags2 & QR2_SYSTEM) return;
310 if (!strcasecmp(qrbuf->QRname, SYSCONFIGROOM)) return;
311 if (CtdlIsNonEditable(qrbuf)) return;
313 /* If we don't know the modification date, be safe and don't purge */
314 if (qrbuf->QRmtime <= (time_t)0) return;
316 /* If no room purge time is set, be safe and don't purge */
317 if (config.c_roompurge < 0) return;
319 /* Otherwise, check the date of last modification */
320 age = time(NULL) - (qrbuf->QRmtime);
321 purge_secs = (time_t)config.c_roompurge * (time_t)86400;
322 if (purge_secs <= (time_t)0) return;
323 CtdlLogPrintf(CTDL_DEBUG, "<%s> is <%ld> seconds old\n", qrbuf->QRname, (long)age);
324 if (age > purge_secs) do_purge = 1;
328 pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList));
329 pptr->next = RoomPurgeList;
330 strcpy(pptr->name, qrbuf->QRname);
331 RoomPurgeList = pptr;
338 int PurgeRooms(void) {
339 struct PurgeList *pptr;
340 int num_rooms_purged = 0;
341 struct ctdlroom qrbuf;
342 struct ValidUser *vuptr;
343 char *transcript = NULL;
345 CtdlLogPrintf(CTDL_DEBUG, "PurgeRooms() called\n");
348 /* Load up a table full of valid user numbers so we can delete
349 * user-owned rooms for users who no longer exist */
350 ForEachUser(AddValidUser, NULL);
352 /* Then cycle through the room file */
353 CtdlForEachRoom(DoPurgeRooms, NULL);
355 /* Free the valid user list */
356 while (ValidUserList != NULL) {
357 vuptr = ValidUserList->next;
359 ValidUserList = vuptr;
363 transcript = malloc(SIZ);
364 strcpy(transcript, "The following rooms have been auto-purged:\n");
366 while (RoomPurgeList != NULL) {
367 if (CtdlGetRoom(&qrbuf, RoomPurgeList->name) == 0) {
368 transcript=realloc(transcript, strlen(transcript)+SIZ);
369 snprintf(&transcript[strlen(transcript)], SIZ, " %s\n",
371 CtdlDeleteRoom(&qrbuf);
373 pptr = RoomPurgeList->next;
375 RoomPurgeList = pptr;
379 if (num_rooms_purged > 0) CtdlAideMessage(transcript, "Room Autopurger Message");
382 CtdlLogPrintf(CTDL_DEBUG, "Purged %d rooms.\n", num_rooms_purged);
383 return(num_rooms_purged);
388 * Back end function to check user accounts for associated Unix accounts
389 * which no longer exist. (Only relevant for host auth mode.)
391 void do_uid_user_purge(struct ctdluser *us, void *data) {
392 struct PurgeList *pptr;
394 if ((us->uid != (-1)) && (us->uid != CTDLUID)) {
395 if (getpwuid(us->uid) == NULL) {
396 pptr = (struct PurgeList *)
397 malloc(sizeof(struct PurgeList));
398 pptr->next = UserPurgeList;
399 strcpy(pptr->name, us->fullname);
400 UserPurgeList = pptr;
412 * Back end function to check user accounts for expiration.
414 void do_user_purge(struct ctdluser *us, void *data) {
418 struct PurgeList *pptr;
420 /* Set purge time; if the user overrides the system default, use it */
421 if (us->USuserpurge > 0) {
422 purge_time = ((time_t)us->USuserpurge) * 86400L;
425 purge_time = ((time_t)config.c_userpurge) * 86400L;
428 /* The default rule is to not purge. */
431 /* don't attempt to purge system users. */
432 if (!strncmp(us->fullname, "SYS_", 4))
435 /* If the user hasn't called in two months and expiring of accounts is turned on, his/her account
436 * has expired, so purge the record.
438 if (config.c_userpurge > 0)
441 if ((now - us->lastcall) > purge_time) purge = 1;
444 /* If the record is marked as permanent, don't purge it.
446 if (us->flags & US_PERM) purge = 0;
448 /* If the user is an Aide, don't purge him/her/it.
450 if (us->axlevel == 6) purge = 0;
452 /* If the access level is 0, the record should already have been
453 * deleted, but maybe the user was logged in at the time or something.
454 * Delete the record now.
456 if (us->axlevel == 0) purge = 1;
458 /* If the user set his/her password to 'deleteme', he/she
459 * wishes to be deleted, so purge the record.
460 * Moved this lower down so that aides and permanent users get purged if they ask to be.
462 if (!strcasecmp(us->password, "deleteme")) purge = 1;
464 /* 0 calls is impossible. If there are 0 calls, it must
465 * be a corrupted record, so purge it.
466 * Actually it is possible if an Aide created the user so now we check for less than 0 (DRW)
468 if (us->timescalled < 0) purge = 1;
470 /* any negative user number, is
473 if (us->usernum < 0L) purge = 1;
475 /** Don't purge user 0. That user is there for the system */
476 if (us->usernum == 0L)
478 /* FIXME: Temporary log message. Until we do unauth access with user 0 we should
479 * try to get rid of all user 0 occurences. Many will be remnants from old code so
480 * we will need to try and purge them from users data bases.Some will not have names but
481 * those with names should be purged.
483 CtdlLogPrintf(CTDL_DEBUG, "Auto purger found a user 0 with name \"%s\"\n", us->fullname);
487 /* If the user has no full name entry then we can't purge them
488 * since the actual purge can't find them.
489 * This shouldn't happen but does somehow.
491 if (IsEmptyStr(us->fullname))
495 if (us->usernum > 0L)
498 if (users_corrupt_msg == NULL)
500 users_corrupt_msg = malloc(SIZ);
501 strcpy(users_corrupt_msg, "The auto-purger found the following user numbers with no name.\n"
502 "The system has no way to purge user with no name and should not be able to\n"
503 "create them either.\n"
504 "This indicates corruption of the user DB or possibly a bug.\n"
505 "It may be a good idea to restore your DB from a backup.\n");
508 users_corrupt_msg=realloc(users_corrupt_msg, strlen(users_corrupt_msg)+30);
509 snprintf(&users_corrupt_msg[strlen(users_corrupt_msg)], 29, " %ld\n", us->usernum);
516 pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList));
517 pptr->next = UserPurgeList;
518 strcpy(pptr->name, us->fullname);
519 UserPurgeList = pptr;
529 int PurgeUsers(void) {
530 struct PurgeList *pptr;
531 int num_users_purged = 0;
532 char *transcript = NULL;
534 CtdlLogPrintf(CTDL_DEBUG, "PurgeUsers() called\n");
535 users_not_purged = 0;
537 switch(config.c_auth_mode) {
538 case AUTHMODE_NATIVE:
539 ForEachUser(do_user_purge, NULL);
542 ForEachUser(do_uid_user_purge, NULL);
545 CtdlLogPrintf(CTDL_DEBUG, "User purge for auth mode %d is not implemented.\n",
550 transcript = malloc(SIZ);
552 if (users_not_purged == 0) {
553 strcpy(transcript, "The auto-purger was told to purge every user. It is\n"
554 "refusing to do this because it usually indicates a problem\n"
555 "such as an inability to communicate with a name service.\n"
557 while (UserPurgeList != NULL) {
558 pptr = UserPurgeList->next;
560 UserPurgeList = pptr;
566 strcpy(transcript, "The following users have been auto-purged:\n");
567 while (UserPurgeList != NULL) {
568 transcript=realloc(transcript, strlen(transcript)+SIZ);
569 snprintf(&transcript[strlen(transcript)], SIZ, " %s\n",
570 UserPurgeList->name);
571 purge_user(UserPurgeList->name);
572 pptr = UserPurgeList->next;
574 UserPurgeList = pptr;
579 if (num_users_purged > 0) CtdlAideMessage(transcript, "User Purge Message");
582 if(users_corrupt_msg)
584 CtdlAideMessage(users_corrupt_msg, "User Corruption Message");
585 free (users_corrupt_msg);
586 users_corrupt_msg = NULL;
591 CtdlAideMessage(users_zero_msg, "User Zero Message");
592 free (users_zero_msg);
593 users_zero_msg = NULL;
596 CtdlLogPrintf(CTDL_DEBUG, "Purged %d users.\n", num_users_purged);
597 return(num_users_purged);
604 * This is a really cumbersome "garbage collection" function. We have to
605 * delete visits which refer to rooms and/or users which no longer exist. In
606 * order to prevent endless traversals of the room and user files, we first
607 * build linked lists of rooms and users which _do_ exist on the system, then
608 * traverse the visit file, checking each record against those two lists and
609 * purging the ones that do not have a match on _both_ lists. (Remember, if
610 * either the room or user being referred to is no longer on the system, the
611 * record is completely useless.)
613 int PurgeVisits(void) {
614 struct cdbdata *cdbvisit;
616 struct VPurgeList *VisitPurgeList = NULL;
617 struct VPurgeList *vptr;
621 struct ValidRoom *vrptr;
622 struct ValidUser *vuptr;
623 int RoomIsValid, UserIsValid;
625 /* First, load up a table full of valid room/gen combinations */
626 CtdlForEachRoom(AddValidRoom, NULL);
628 /* Then load up a table full of valid user numbers */
629 ForEachUser(AddValidUser, NULL);
631 /* Now traverse through the visits, purging irrelevant records... */
632 cdb_rewind(CDB_VISIT);
633 while(cdbvisit = cdb_next_item(CDB_VISIT), cdbvisit != NULL) {
634 memset(&vbuf, 0, sizeof(visit));
635 memcpy(&vbuf, cdbvisit->ptr,
636 ( (cdbvisit->len > sizeof(visit)) ?
637 sizeof(visit) : cdbvisit->len) );
643 /* Check to see if the room exists */
644 for (vrptr=ValidRoomList; vrptr!=NULL; vrptr=vrptr->next) {
645 if ( (vrptr->vr_roomnum==vbuf.v_roomnum)
646 && (vrptr->vr_roomgen==vbuf.v_roomgen))
650 /* Check to see if the user exists */
651 for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) {
652 if (vuptr->vu_usernum == vbuf.v_usernum)
656 /* Put the record on the purge list if it's dead */
657 if ((RoomIsValid==0) || (UserIsValid==0)) {
658 vptr = (struct VPurgeList *)
659 malloc(sizeof(struct VPurgeList));
660 vptr->next = VisitPurgeList;
661 vptr->vp_roomnum = vbuf.v_roomnum;
662 vptr->vp_roomgen = vbuf.v_roomgen;
663 vptr->vp_usernum = vbuf.v_usernum;
664 VisitPurgeList = vptr;
669 /* Free the valid room/gen combination list */
670 while (ValidRoomList != NULL) {
671 vrptr = ValidRoomList->next;
673 ValidRoomList = vrptr;
676 /* Free the valid user list */
677 while (ValidUserList != NULL) {
678 vuptr = ValidUserList->next;
680 ValidUserList = vuptr;
683 /* Now delete every visit on the purged list */
684 while (VisitPurgeList != NULL) {
685 IndexLen = GenerateRelationshipIndex(IndexBuf,
686 VisitPurgeList->vp_roomnum,
687 VisitPurgeList->vp_roomgen,
688 VisitPurgeList->vp_usernum);
689 cdb_delete(CDB_VISIT, IndexBuf, IndexLen);
690 vptr = VisitPurgeList->next;
691 free(VisitPurgeList);
692 VisitPurgeList = vptr;
700 * Purge the use table of old entries.
703 int PurgeUseTable(void) {
705 struct cdbdata *cdbut;
707 struct UPurgeList *ul = NULL;
708 struct UPurgeList *uptr;
710 /* Phase 1: traverse through the table, discovering old records... */
711 CtdlLogPrintf(CTDL_DEBUG, "Purge use table: phase 1\n");
712 cdb_rewind(CDB_USETABLE);
713 while(cdbut = cdb_next_item(CDB_USETABLE), cdbut != NULL) {
716 * TODODRW: change this to create a new function time_t cdb_get_timestamp( struct cdbdata *)
717 * this will release this file from the serv_network.h
718 * Maybe it could be a macro that extracts and casts the reult
720 memcpy(&ut, cdbut->ptr,
721 ((cdbut->len > sizeof(struct UseTable)) ?
722 sizeof(struct UseTable) : cdbut->len));
725 if ( (time(NULL) - ut.ut_timestamp) > USETABLE_RETAIN ) {
726 uptr = (struct UPurgeList *) malloc(sizeof(struct UPurgeList));
729 safestrncpy(uptr->up_key, ut.ut_msgid, sizeof uptr->up_key);
737 /* Phase 2: delete the records */
738 CtdlLogPrintf(CTDL_DEBUG, "Purge use table: phase 2\n");
740 cdb_delete(CDB_USETABLE, ul->up_key, strlen(ul->up_key));
746 CtdlLogPrintf(CTDL_DEBUG, "Purge use table: finished (purged %d records)\n", purged);
753 * Purge the EUID Index of old records.
756 int PurgeEuidIndexTable(void) {
758 struct cdbdata *cdbei;
759 struct EPurgeList *el = NULL;
760 struct EPurgeList *eptr;
762 struct CtdlMessage *msg = NULL;
764 /* Phase 1: traverse through the table, discovering old records... */
765 CtdlLogPrintf(CTDL_DEBUG, "Purge EUID index: phase 1\n");
766 cdb_rewind(CDB_EUIDINDEX);
767 while(cdbei = cdb_next_item(CDB_EUIDINDEX), cdbei != NULL) {
769 memcpy(&msgnum, cdbei->ptr, sizeof(long));
771 msg = CtdlFetchMessage(msgnum, 0);
773 CtdlFreeMessage(msg); /* it still exists, so do nothing */
776 eptr = (struct EPurgeList *) malloc(sizeof(struct EPurgeList));
779 eptr->ep_keylen = cdbei->len - sizeof(long);
780 eptr->ep_key = malloc(cdbei->len);
781 memcpy(eptr->ep_key, &cdbei->ptr[sizeof(long)], eptr->ep_keylen);
791 /* Phase 2: delete the records */
792 CtdlLogPrintf(CTDL_DEBUG, "Purge euid index: phase 2\n");
794 cdb_delete(CDB_EUIDINDEX, el->ep_key, el->ep_keylen);
801 CtdlLogPrintf(CTDL_DEBUG, "Purge euid index: finished (purged %d records)\n", purged);
808 * Purge OpenID assocations for missing users (theoretically this will never delete anything)
810 int PurgeStaleOpenIDassociations(void) {
811 struct cdbdata *cdboi;
812 struct ctdluser usbuf;
813 HashList *keys = NULL;
815 char *deleteme = NULL;
822 keys = NewHash(1, NULL);
823 if (!keys) return(0);
826 cdb_rewind(CDB_OPENID);
827 while (cdboi = cdb_next_item(CDB_OPENID), cdboi != NULL) {
828 if (cdboi->len > sizeof(long)) {
829 memcpy(&usernum, cdboi->ptr, sizeof(long));
830 if (CtdlGetUserByNumber(&usbuf, usernum) != 0) {
831 deleteme = strdup(cdboi->ptr + sizeof(long)),
832 Put(keys, deleteme, strlen(deleteme), deleteme, NULL);
838 /* Go through the hash list, deleting keys we stored in it */
840 HashPos = GetNewHashPos(keys, 0);
841 while (GetNextHashPos(keys, HashPos, &len, &Key, &Value)!=0)
843 CtdlLogPrintf(CTDL_DEBUG, "Deleting associated OpenID <%s>\n", Value);
844 cdb_delete(CDB_OPENID, Value, strlen(Value));
845 /* note: don't free(Value) -- deleting the hash list will handle this for us */
848 DeleteHashPos(&HashPos);
857 void *purge_databases(void *args)
860 static time_t last_purge = 0;
863 struct CitContext purgerCC;
865 CtdlFillSystemContext(&purgerCC, "purger");
866 citthread_setspecific(MyConKey, (void *)&purgerCC );
867 CtdlLogPrintf(CTDL_DEBUG, "Auto-purger_thread() initializing\n");
869 while (!CtdlThreadCheckStop()) {
870 /* Do the auto-purge if the current hour equals the purge hour,
871 * but not if the operation has already been performed in the
872 * last twelve hours. This is usually enough granularity.
875 localtime_r(&now, &tm);
877 ((tm.tm_hour != config.c_purge_hour) || ((now - last_purge) < 43200))
878 && (force_purge_now == 0)
885 CtdlLogPrintf(CTDL_INFO, "Auto-purger: starting.\n");
887 if (!CtdlThreadCheckStop())
889 retval = PurgeUsers();
890 CtdlLogPrintf(CTDL_NOTICE, "Purged %d users.\n", retval);
893 if (!CtdlThreadCheckStop())
896 CtdlLogPrintf(CTDL_NOTICE, "Expired %d messages.\n", messages_purged);
899 if (!CtdlThreadCheckStop())
901 retval = PurgeRooms();
902 CtdlLogPrintf(CTDL_NOTICE, "Expired %d rooms.\n", retval);
905 if (!CtdlThreadCheckStop())
907 retval = PurgeVisits();
908 CtdlLogPrintf(CTDL_NOTICE, "Purged %d visits.\n", retval);
911 if (!CtdlThreadCheckStop())
913 retval = PurgeUseTable();
914 CtdlLogPrintf(CTDL_NOTICE, "Purged %d entries from the use table.\n", retval);
917 if (!CtdlThreadCheckStop())
919 retval = PurgeEuidIndexTable();
920 CtdlLogPrintf(CTDL_NOTICE, "Purged %d entries from the EUID index.\n", retval);
923 if (!CtdlThreadCheckStop())
925 retval = PurgeStaleOpenIDassociations();
926 CtdlLogPrintf(CTDL_NOTICE, "Purged %d stale OpenID associations.\n", retval);
929 if (!CtdlThreadCheckStop())
931 retval = TDAP_ProcessAdjRefCountQueue();
932 CtdlLogPrintf(CTDL_NOTICE, "Processed %d message reference count adjustments.\n", retval);
935 if (!CtdlThreadCheckStop())
937 CtdlLogPrintf(CTDL_INFO, "Auto-purger: finished.\n");
938 last_purge = now; /* So we don't do it again soon */
942 CtdlLogPrintf(CTDL_INFO, "Auto-purger: STOPPED.\n");
945 CtdlClearSystemContext();
948 /*****************************************************************************/
951 /* The FSCK command has been removed because people were misusing it */
955 void do_fsck_msg(long msgnum, void *userdata) {
956 struct ctdlroomref *ptr;
958 ptr = (struct ctdlroomref *)malloc(sizeof(struct ctdlroomref));
960 ptr->msgnum = msgnum;
964 void do_fsck_room(struct ctdlroom *qrbuf, void *data)
966 CtdlGetRoom(&CC->room, qrbuf->QRname);
967 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, do_fsck_msg, NULL);
971 * Check message reference counts
973 void cmd_fsck(char *argbuf) {
975 struct cdbdata *cdbmsg;
977 struct ctdlroomref *ptr;
980 if (CtdlAccessCheck(ac_aide)) return;
982 /* Lame way of checking whether anyone else is doing this now */
984 cprintf("%d Another FSCK is already running.\n", ERROR + RESOURCE_BUSY);
988 cprintf("%d Checking message reference counts\n", LISTING_FOLLOWS);
990 cprintf("\nThis could take a while. Please be patient!\n\n");
991 cprintf("Gathering pointers...\n");
992 CtdlForEachRoom(do_fsck_room, NULL);
995 cprintf("Checking message base...\n");
996 for (msgnum = 0L; msgnum <= CitControl.MMhighest; ++msgnum) {
998 cdbmsg = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
999 if (cdbmsg != NULL) {
1001 cprintf("Message %7ld ", msgnum);
1003 GetMetaData(&smi, msgnum);
1004 cprintf("refcount=%-2d ", smi.meta_refcount);
1007 for (ptr = rr; ptr != NULL; ptr = ptr->next) {
1008 if (ptr->msgnum == msgnum) ++realcount;
1010 cprintf("realcount=%-2d\n", realcount);
1012 if ( (smi.meta_refcount != realcount)
1013 || (realcount == 0) ) {
1014 AdjRefCount(msgnum, (smi.meta_refcount - realcount));
1021 cprintf("Freeing memory...\n");
1022 while (rr != NULL) {
1033 #endif /* end of commented-out fsck cmd */
1036 * Manually initiate a run of The Dreaded Auto-Purger (tm)
1038 void cmd_tdap(char *argbuf) {
1039 if (CtdlAccessCheck(ac_aide)) return;
1040 force_purge_now = 1;
1041 cprintf("%d Manually initiating a purger run now.\n", CIT_OK);
1045 /*****************************************************************************/
1047 CTDL_MODULE_INIT(expire)
1051 /* CtdlRegisterProtoHook(cmd_fsck, "FSCK", "Check message ref counts"); */
1052 CtdlRegisterProtoHook(cmd_tdap, "TDAP", "Manually initiate auto-purger");
1054 CtdlRegisterProtoHook(cmd_gpex, "GPEX", "Get expire policy");
1055 CtdlRegisterProtoHook(cmd_spex, "SPEX", "Set expire policy");
1058 CtdlThreadCreate("Auto Purger", CTDLTHREAD_BIGSTACK, purge_databases, NULL);
1059 /* return our Subversion id for the Log */