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-2011 by citadel.org (Art Cancro, Wilifried Goesgens, and others)
8 * This program is open source software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as published
10 * by 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 syslog(LOG_DEBUG, "PurgeMessages() called\n");
253 purgelist = tmpfile();
254 if (purgelist == NULL) {
255 syslog(LOG_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 syslog(LOG_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 syslog(LOG_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 syslog(LOG_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 syslog(LOG_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 syslog(LOG_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 syslog(LOG_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 syslog(LOG_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 syslog(LOG_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 syslog(LOG_DEBUG, "Purge use table: phase 2\n");
740 cdb_delete(CDB_USETABLE, ul->up_key, strlen(ul->up_key));
746 syslog(LOG_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 syslog(LOG_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 syslog(LOG_DEBUG, "Purge euid index: phase 2\n");
794 cdb_delete(CDB_EUIDINDEX, el->ep_key, el->ep_keylen);
801 syslog(LOG_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 syslog(LOG_DEBUG, "Deleting associated OpenID <%s>\n", (char*)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)
860 static time_t last_purge = 0;
864 /* Do the auto-purge if the current hour equals the purge hour,
865 * but not if the operation has already been performed in the
866 * last twelve hours. This is usually enough granularity.
869 localtime_r(&now, &tm);
871 ((tm.tm_hour != config.c_purge_hour) || ((now - last_purge) < 43200))
872 && (force_purge_now == 0)
877 syslog(LOG_INFO, "Auto-purger: starting.\n");
879 if (!server_shutting_down)
881 retval = PurgeUsers();
882 syslog(LOG_NOTICE, "Purged %d users.\n", retval);
885 if (!server_shutting_down)
888 syslog(LOG_NOTICE, "Expired %d messages.\n", messages_purged);
891 if (!server_shutting_down)
893 retval = PurgeRooms();
894 syslog(LOG_NOTICE, "Expired %d rooms.\n", retval);
897 if (!server_shutting_down)
899 retval = PurgeVisits();
900 syslog(LOG_NOTICE, "Purged %d visits.\n", retval);
903 if (!server_shutting_down)
905 retval = PurgeUseTable();
906 syslog(LOG_NOTICE, "Purged %d entries from the use table.\n", retval);
909 if (!server_shutting_down)
911 retval = PurgeEuidIndexTable();
912 syslog(LOG_NOTICE, "Purged %d entries from the EUID index.\n", retval);
915 if (!server_shutting_down)
917 retval = PurgeStaleOpenIDassociations();
918 syslog(LOG_NOTICE, "Purged %d stale OpenID associations.\n", retval);
921 if (!server_shutting_down)
923 retval = TDAP_ProcessAdjRefCountQueue();
924 syslog(LOG_NOTICE, "Processed %d message reference count adjustments.\n", retval);
927 if (!server_shutting_down)
929 syslog(LOG_INFO, "Auto-purger: finished.\n");
930 last_purge = now; /* So we don't do it again soon */
934 syslog(LOG_INFO, "Auto-purger: STOPPED.\n");
937 CtdlClearSystemContext();
942 * Manually initiate a run of The Dreaded Auto-Purger (tm)
944 void cmd_tdap(char *argbuf) {
945 if (CtdlAccessCheck(ac_aide)) return;
947 cprintf("%d Manually initiating a purger run now.\n", CIT_OK);
952 CTDL_MODULE_INIT(expire)
956 CtdlRegisterProtoHook(cmd_tdap, "TDAP", "Manually initiate auto-purger");
957 CtdlRegisterProtoHook(cmd_gpex, "GPEX", "Get expire policy");
958 CtdlRegisterProtoHook(cmd_spex, "SPEX", "Set expire policy");
959 CtdlRegisterSessionHook(purge_databases, EVT_TIMER);
962 /* return our Subversion id for the Log */