* Replaced all occurances of malloc(), realloc(), and free() in the
authorArt Cancro <ajc@citadel.org>
Wed, 6 Jan 1999 04:26:06 +0000 (04:26 +0000)
committerArt Cancro <ajc@citadel.org>
Wed, 6 Jan 1999 04:26:06 +0000 (04:26 +0000)
          server and server-modules with mallok(), reallok(), and phree().
          Wrote macros and a set of leak-tracking functions.

12 files changed:
citadel/ChangeLog
citadel/citserver.c
citadel/database.c
citadel/dynloader.c
citadel/msgbase.c
citadel/room_ops.c
citadel/serv_chat.c
citadel/serv_expire.c
citadel/serv_upgrade.c
citadel/server.h
citadel/sysdep.c
citadel/tasklist.txt

index 77a151672bd762eb7a67f0c45bc65e6bb476714b..1c018fab280a1847dc80fc914e71547041379e6d 100644 (file)
@@ -1,3 +1,8 @@
+Tue Jan  5 23:24:52 EST 1999 Art Cancro <ajc@uncnsrd.mt-kisco.ny.us>
+       * Replaced all occurances of malloc(), realloc(), and free() in the
+         server and server-modules with mallok(), reallok(), and phree().
+         Wrote macros and a set of leak-tracking functions.
+
 Sun Jan  3 20:38:45 EST 1999 Art Cancro <ajc@uncnsrd.mt-kisco.ny.us>
        * Documentation changes
 
index a2aecf5918ebf9cba04203daafaf47dd0fc06cb3..1ab8a0f58256f097846627e4e27ded547a697867 100644 (file)
@@ -111,12 +111,12 @@ void cleanup_stuff(void *arg)
        while (CC->FirstExpressMessage != NULL) {
                emptr = CC->FirstExpressMessage;
                CC->FirstExpressMessage = CC->FirstExpressMessage->next;
-               free(emptr);
+               phree(emptr);
                }
        end_critical_section(S_SESSION_TABLE);
 
        /* Deallocate any message list we might have in memory */
-       if (CC->msglist != NULL) free(CC->msglist);
+       if (CC->msglist != NULL) phree(CC->msglist);
 
        /* Now get rid of the session and context */
        lprintf(7, "cleanup_stuff() calling RemoveContext(%d)\n", CC->cs_pid);
@@ -377,13 +377,13 @@ void cmd_mesg(char *mname)
        extract(buf,mname,0);
 
 
-       dirs[0]=malloc(64);
-       dirs[1]=malloc(64);
+       dirs[0]=mallok(64);
+       dirs[1]=mallok(64);
        strcpy(dirs[0],"messages");
        strcpy(dirs[1],"help");
        mesg_locate(targ,buf,2,dirs);
-       free(dirs[0]);
-       free(dirs[1]);
+       phree(dirs[0]);
+       phree(dirs[1]);
 
 
        if (strlen(targ)==0) {
@@ -432,13 +432,13 @@ void cmd_emsg(char *mname)
                if (buf[a] == '/') buf[a] = '.';
                }
 
-       dirs[0]=malloc(64);
-       dirs[1]=malloc(64);
+       dirs[0]=mallok(64);
+       dirs[1]=mallok(64);
        strcpy(dirs[0],"messages");
        strcpy(dirs[1],"help");
        mesg_locate(targ,buf,2,dirs);
-       free(dirs[0]);
-       free(dirs[1]);
+       phree(dirs[0]);
+       phree(dirs[1]);
 
        if (strlen(targ)==0) {
                snprintf(targ, sizeof targ, "./help/%s", buf);
@@ -1101,6 +1101,12 @@ void *context_loop(struct CitContext *con)
                        cmd_conf(&cmdbuf[5]);
                        }
 
+#ifdef DEBUG_MEMORY_LEAKS
+               else if (!strncasecmp(cmdbuf, "LEAK", 4)) {
+                       dump_tracked();
+                       }
+#endif
+
                else if (!DLoader_Exec_Cmd(cmdbuf))
                        {
                           cprintf("%d Unrecognized or unsupported command.\n",
index aea8961b3bffc5bd0bce7159e3b601b51704030c..e67c6d37373028ae0cf4c16862adc9b43fd80bf5 100644 (file)
@@ -169,7 +169,7 @@ void close_databases(void) {
 
        for (a=0; a<MAXKEYS; ++a) {
                if (dtkey[a].dptr != NULL) {
-                       free(dtkey[a].dptr);
+                       phree(dtkey[a].dptr);
                        }
                }
 
@@ -245,7 +245,7 @@ struct cdbdata *cdb_fetch(int cdb, void *key, int keylen) {
                return NULL;
                }
 
-       tempcdb = (struct cdbdata *) malloc(sizeof(struct cdbdata));
+       tempcdb = (struct cdbdata *) mallok(sizeof(struct cdbdata));
        if (tempcdb == NULL) {
                lprintf(2, "Cannot allocate memory!\n");
                }
@@ -261,8 +261,8 @@ struct cdbdata *cdb_fetch(int cdb, void *key, int keylen) {
  * more complex stuff with other database managers in the future).
  */
 void cdb_free(struct cdbdata *cdb) {
-       free(cdb->ptr);
-       free(cdb);
+       phree(cdb->ptr);
+       phree(cdb);
        }
 
 
@@ -275,7 +275,7 @@ void cdb_free(struct cdbdata *cdb) {
 void cdb_rewind(int cdb) {
 
        if (dtkey[CC->cs_pid].dptr != NULL) {
-               free(dtkey[CC->cs_pid].dptr);
+               phree(dtkey[CC->cs_pid].dptr);
                }
 
        begin_critical_section(S_DATABASE);
@@ -301,11 +301,11 @@ struct cdbdata *cdb_next_item(int cdb) {
        dret = gdbm_fetch(gdbms[cdb], dtkey[CC->cs_pid]);
        end_critical_section(S_DATABASE);
        if (dret.dptr == NULL) {        /* bad read */
-               free(dtkey[CC->cs_pid].dptr);
+               phree(dtkey[CC->cs_pid].dptr);
                return NULL;
                }
 
-       cdbret = (struct cdbdata *) malloc(sizeof(struct cdbdata));
+       cdbret = (struct cdbdata *) mallok(sizeof(struct cdbdata));
        cdbret->len = dret.dsize;
        cdbret->ptr = dret.dptr;
 
index 6d452763d154396488df73703fdf2891c4f43817..e860fba37e10fbf9791cfc2a817184b7fd9e3c5b 100644 (file)
@@ -43,7 +43,7 @@ struct ProtoFunctionHook
 
 void CtdlRegisterProtoHook(void (*handler)(char *), char *cmd, char *desc)
 {
-  struct ProtoFunctionHook *p = malloc(sizeof *p);
+  struct ProtoFunctionHook *p = mallok(sizeof *p);
   
   if (p == NULL)
     {
@@ -127,7 +127,7 @@ void CtdlRegisterLogHook(void (*fcn_ptr)(char *), int loglevel) {
        struct LogFunctionHook *newfcn;
 
        newfcn = (struct LogFunctionHook *)
-               malloc(sizeof(struct LogFunctionHook));
+               mallok(sizeof(struct LogFunctionHook));
        newfcn->next = LogHookTable;
        newfcn->h_function_pointer = fcn_ptr;
        newfcn->loglevel = loglevel;
@@ -142,7 +142,7 @@ void CtdlRegisterCleanupHook(void (*fcn_ptr)(void)) {
        struct CleanupFunctionHook *newfcn;
 
        newfcn = (struct CleanupFunctionHook *)
-               malloc(sizeof(struct CleanupFunctionHook));
+               mallok(sizeof(struct CleanupFunctionHook));
        newfcn->next = CleanupHookTable;
        newfcn->h_function_pointer = fcn_ptr;
        CleanupHookTable = newfcn;
@@ -156,7 +156,7 @@ void CtdlRegisterSessionHook(void (*fcn_ptr)(void), int EventType) {
        struct SessionFunctionHook *newfcn;
 
        newfcn = (struct SessionFunctionHook *)
-               malloc(sizeof(struct SessionFunctionHook));
+               mallok(sizeof(struct SessionFunctionHook));
        newfcn->next = SessionHookTable;
        newfcn->h_function_pointer = fcn_ptr;
        newfcn->eventtype = EventType;
@@ -172,7 +172,7 @@ void CtdlRegisterUserHook(void (*fcn_ptr)(char*, long), int EventType) {
        struct UserFunctionHook *newfcn;
 
        newfcn = (struct UserFunctionHook *)
-               malloc(sizeof(struct UserFunctionHook));
+               mallok(sizeof(struct UserFunctionHook));
        newfcn->next = UserHookTable;
        newfcn->h_function_pointer = fcn_ptr;
        newfcn->eventtype = EventType;
index 51fbc20d4fbc1e6005b61e54972e03a91d5fbbb5..e76c6d3d91f7c51fa2e172cec40da3023ea9bcef 100644 (file)
@@ -663,7 +663,7 @@ long send_message(char *message_in_memory,  /* pointer to buffer */
        if (generate_id) {
                sprintf(msgidbuf, "I%ld", newmsgid);
                actual_length = message_length + strlen(msgidbuf) + 1;
-               actual_message = malloc(actual_length);
+               actual_message = mallok(actual_length);
                memcpy(actual_message, message_in_memory, 3);
                memcpy(&actual_message[3], msgidbuf, (strlen(msgidbuf)+1) );
                memcpy(&actual_message[strlen(msgidbuf)+4],
@@ -688,7 +688,7 @@ long send_message(char *message_in_memory,  /* pointer to buffer */
        end_critical_section(S_MSGMAIN);
 
        if (generate_id) {
-               free(actual_message);
+               phree(actual_message);
                }
 
        /* Finally, return the pointers */
@@ -758,7 +758,7 @@ void save_message(char *mtmp,       /* file containing proper message */
        templen = statbuf.st_size;
 
        /* Now read it into memory */
-       message_in_memory = (char *) malloc(templen);
+       message_in_memory = (char *) mallok(templen);
        if (message_in_memory == NULL) {
                lprintf(2, "Can't allocate memory to save message!\n");
                return;
@@ -769,7 +769,7 @@ void save_message(char *mtmp,       /* file containing proper message */
        fclose(fp);
 
        newmsgid = send_message(message_in_memory, templen, generate_id);
-       free(message_in_memory);
+       phree(message_in_memory);
        if (newmsgid <= 0L) return;
 
        strcpy(actual_rm, CC->quickroom.QRname);
index 2bdd6ed30eb483719b12cadd31a2f925f1d61397..7c9ee502c5c95096c60c1437c22e663271ebc94f 100644 (file)
@@ -287,7 +287,7 @@ void get_msglist(struct quickroom *whichroom) {
        struct cdbdata *cdbfr;
 
        if (CC->msglist != NULL) {
-               free(CC->msglist);
+               phree(CC->msglist);
                }
        CC->msglist = NULL;
        CC->num_msgs = 0;
@@ -297,7 +297,7 @@ void get_msglist(struct quickroom *whichroom) {
                return;
                }
 
-       CC->msglist = malloc(cdbfr->len);
+       CC->msglist = mallok(cdbfr->len);
        memcpy(CC->msglist, cdbfr->ptr, cdbfr->len);
        CC->num_msgs = cdbfr->len / sizeof(long);
        cdb_free(cdbfr);
@@ -349,7 +349,7 @@ long AddMessageToRoom(struct quickroom *whichroom, long newmsgid) {
                num_msgs = 0;
                }
        else {
-               msglist = malloc(cdbfr->len);
+               msglist = mallok(cdbfr->len);
                num_msgs = cdbfr->len / sizeof(long);
                memcpy(msglist, cdbfr->ptr, cdbfr->len);
                cdb_free(cdbfr);
@@ -357,7 +357,7 @@ long AddMessageToRoom(struct quickroom *whichroom, long newmsgid) {
        
        /* Now add the new message */
        ++num_msgs;
-       msglist = realloc(msglist,
+       msglist = reallok(msglist,
                (num_msgs * sizeof(long)) );
 
        if (msglist == NULL) {
@@ -1173,7 +1173,7 @@ void delete_room(struct quickroom *qrbuf) {
                cdb_delete(CDB_MSGMAIN, &MsgToDelete, sizeof(long));
                }
        put_msglist(qrbuf);
-       free(CC->msglist);
+       phree(CC->msglist);
        CC->msglist = NULL;
        CC->num_msgs = 0;
        delete_msglist(qrbuf);
index 1e8dd93e39abffadcf13267e9339a4daf17fc7fe..fcacf1fd5d40b4ceab5be7992e1a5afc3a8c7f83 100644 (file)
@@ -87,7 +87,7 @@ void allwrite(char *cmdbuf, int flag, char *roomname, char *username)
                fclose(fp);
                }
 
-       clnew = (struct ChatLine *) malloc(sizeof(struct ChatLine));
+       clnew = (struct ChatLine *) mallok(sizeof(struct ChatLine));
        memset(clnew, 0, sizeof(struct ChatLine));
        if (clnew == NULL) {
                fprintf(stderr, "citserver: cannot alloc chat line: %s\n",
@@ -130,7 +130,7 @@ void allwrite(char *cmdbuf, int flag, char *roomname, char *username)
                if ( (now - ChatQueue->chat_time) < 120L ) goto DONE_FREEING;
                clptr = ChatQueue;
                ChatQueue = ChatQueue->next;
-               free(clptr);
+               phree(clptr);
                }
 DONE_FREEING:  end_critical_section(S_CHATQUEUE);
        }
@@ -378,7 +378,7 @@ void cmd_pexp(char *argbuf) /* arg unused */ {
                begin_critical_section(S_SESSION_TABLE);
                emptr = CC->FirstExpressMessage;
                CC->FirstExpressMessage = CC->FirstExpressMessage->next;
-               free(emptr);
+               phree(emptr);
                end_critical_section(S_SESSION_TABLE);
                }
        cprintf("000\n");
@@ -446,7 +446,7 @@ void cmd_sexp(char *argbuf)
                   || (!strcasecmp(x_user, "broadcast")) ) {
                        strcpy(ccptr->last_pager, CC->curr_user);
                        emnew = (struct ExpressMessage *)
-                               malloc(sizeof(struct ExpressMessage));
+                               mallok(sizeof(struct ExpressMessage));
                        emnew->next = NULL;
                        sprintf(emnew->em_text, "%s from %s:\n %s\n",
                                ( (!strcasecmp(x_user, "broadcast")) ? "Broadcast message" : "Message" ),
index 271dbf2e92d1a786bcad6f3d60042a1711991aaf..c2d34b64cc3076b14d0a84cb7cf41f2c59eb1cb1 100644 (file)
@@ -197,7 +197,7 @@ void DoPurgeRooms(struct quickroom *qrbuf) {
 
        if (age > purge_secs) {
                
-               pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList));
+               pptr = (struct PurgeList *) mallok(sizeof(struct PurgeList));
                pptr->next = RoomPurgeList;
                strcpy(pptr->name, qrbuf->QRname);
                RoomPurgeList = pptr;
@@ -221,7 +221,7 @@ int PurgeRooms(void) {
                        delete_room(&qrbuf);
                        }
                pptr = RoomPurgeList->next;
-               free(RoomPurgeList);
+               phree(RoomPurgeList);
                RoomPurgeList = pptr;
                ++num_rooms_purged;
                }
@@ -275,7 +275,7 @@ void do_user_purge(struct usersupp *us) {
        if (us->timescalled == 0) purge = 1;
 
        if (purge == 1) {
-               pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList));
+               pptr = (struct PurgeList *) mallok(sizeof(struct PurgeList));
                pptr->next = UserPurgeList;
                strcpy(pptr->name, us->fullname);
                UserPurgeList = pptr;
@@ -297,7 +297,7 @@ int PurgeUsers(void) {
        while (UserPurgeList != NULL) {
                purge_user(UserPurgeList->name);
                pptr = UserPurgeList->next;
-               free(UserPurgeList);
+               phree(UserPurgeList);
                UserPurgeList = pptr;
                ++num_users_purged;
                }
@@ -309,7 +309,7 @@ int PurgeUsers(void) {
 void AddValidUser(struct usersupp *usbuf) {
        struct ValidUser *vuptr;
 
-       vuptr = (struct ValidUser *)malloc(sizeof(struct ValidUser));
+       vuptr = (struct ValidUser *)mallok(sizeof(struct ValidUser));
        vuptr->next = ValidUserList;
        vuptr->vu_usernum = usbuf->usernum;
        ValidUserList = vuptr;
@@ -318,7 +318,7 @@ void AddValidUser(struct usersupp *usbuf) {
 void AddValidRoom(struct quickroom *qrbuf) {
        struct ValidRoom *vrptr;
 
-       vrptr = (struct ValidRoom *)malloc(sizeof(struct ValidRoom));
+       vrptr = (struct ValidRoom *)mallok(sizeof(struct ValidRoom));
        vrptr->next = ValidRoomList;
        vrptr->vr_roomnum = qrbuf->QRnumber;
        vrptr->vr_roomgen = qrbuf->QRgen;
@@ -384,7 +384,7 @@ int PurgeVisits(void) {
                /* Put the record on the purge list if it's dead */
                if ((RoomIsValid==0) || (UserIsValid==0)) {
                        vptr = (struct VPurgeList *)
-                               malloc(sizeof(struct VPurgeList));
+                               mallok(sizeof(struct VPurgeList));
                        vptr->next = VisitPurgeList;
                        vptr->vp_roomnum = vbuf.v_roomnum;
                        vptr->vp_roomgen = vbuf.v_roomgen;
@@ -397,14 +397,14 @@ int PurgeVisits(void) {
        /* Free the valid room/gen combination list */
        while (ValidRoomList != NULL) {
                vrptr = ValidRoomList->next;
-               free(ValidRoomList);
+               phree(ValidRoomList);
                ValidRoomList = vrptr;
                }
 
        /* Free the valid user list */
        while (ValidUserList != NULL) {
                vuptr = ValidUserList->next;
-               free(ValidUserList);
+               phree(ValidUserList);
                ValidUserList = vuptr;
                }
 
@@ -416,7 +416,7 @@ int PurgeVisits(void) {
                                VisitPurgeList->vp_usernum);
                cdb_delete(CDB_VISIT, IndexBuf, IndexLen);
                vptr = VisitPurgeList->next;
-               free(VisitPurgeList);
+               phree(VisitPurgeList);
                VisitPurgeList = vptr;
                ++purged;
                }
index ffa6a51e59f6ddf47753b2b1fe9bfbe565b68eb7..6c49d5bb337c4666d0fabbb39939a873f778f37e 100644 (file)
@@ -73,7 +73,7 @@ void fpgetfield(FILE *fp, char *string)
 void import_message(long msgnum, long msglen) {
        char *msgtext;
 
-       msgtext = malloc(msglen);
+       msgtext = mallok(msglen);
        if (msgtext == NULL) {
                lprintf(3, "ERROR: cannot allocate memory\n");
                lprintf(3, "Your data files are now corrupt.\n");
@@ -83,7 +83,7 @@ void import_message(long msgnum, long msglen) {
 
        fread(msgtext, msglen, 1, imfp);
        cdb_store(CDB_MSGMAIN, &msgnum, sizeof(long), msgtext, msglen);
-       free(msgtext);
+       phree(msgtext);
        }
 
 void imp_floors(void) {
@@ -188,7 +188,7 @@ void imp_rooms(void) {
                                        msglen = atol(tval);
                                        import_message(msgnum, msglen);
                                        ++num_msgs;
-                                       msglist = realloc(msglist,
+                                       msglist = reallok(msglist,
                                                (sizeof(long)*num_msgs) );
                                        msglist[num_msgs - 1] = msgnum;
                                        }
@@ -206,7 +206,7 @@ void imp_rooms(void) {
                                        CC->num_msgs = num_msgs;
                                        put_msglist(&qr);
                                        }
-                               free(msglist);
+                               phree(msglist);
                                }
 
                        }
index d727f018e3256119169c10ca542564e867256573..89505dcd2b6c03d651dada3944bf9dc540d6db47 100644 (file)
@@ -1,6 +1,10 @@
 /* $Id$ */
 typedef pthread_t THREAD;
 
+/* Uncomment this if you want to track memory leaks.
+ * (Don't do this unless you're a developer!)
+ */
+#define DEBUG_MEMORY_LEAKS
 
 struct ExpressMessage {
        struct ExpressMessage *next;
@@ -208,3 +212,34 @@ struct visit {
 #define UA_GOTOALLOWED          4
 #define UA_HASNEWMSGS           8
 #define UA_ZAPPED              16
+
+
+
+/* Built-in debuggable stuff for checking for memory leaks */
+#ifdef DEBUG_MEMORY_LEAKS
+
+#define mallok(howbig) tracked_malloc(howbig, __FILE__, __LINE__)
+#define phree(whichptr)        tracked_free(whichptr)
+#define reallok(whichptr,howbig)       tracked_realloc(whichptr,howbig)
+
+void *tracked_malloc(size_t, char *, int);
+void tracked_free(void *);
+void *tracked_realloc(void *, size_t);
+void dump_tracked(void);
+
+struct TheHeap {
+        struct TheHeap *next;
+        char h_file[32];
+        int h_line;
+        void *h_ptr;
+        };
+
+extern struct TheHeap *heap;
+
+#else
+
+#define mallok(howbig) malloc(howbig)
+#define phree(whichptr)        free(whichptr)
+#define reallok(whichptr,howbig)       realloc(whichptr,howbig)
+
+#endif
index 02a5f826d23121368188db669cf7533612184751..d43ffc25d34df6e2d9bcd94eadde47c62d64ab66 100644 (file)
 #include "snprintf.h"
 #endif
 
+#ifdef DEBUG_MEMORY_LEAKS
+struct TheHeap *heap = NULL;
+#endif
+
 pthread_mutex_t Critters[MAX_SEMAPHORES];      /* Things needing locking */
 pthread_key_t MyConKey;                                /* TSD key for MyContext() */
 
@@ -81,6 +85,74 @@ void lprintf(int loglevel, const char *format, ...) {
        }   
 
 
+
+#ifdef DEBUG_MEMORY_LEAKS
+void *tracked_malloc(size_t tsize, char *tfile, int tline) {
+       void *ptr;
+       struct TheHeap *hptr;
+
+       ptr = malloc(tsize);
+       if (ptr == NULL) return(NULL);
+
+       hptr = (struct TheHeap *) malloc(sizeof(struct TheHeap));
+       strcpy(hptr->h_file, tfile);
+       hptr->h_line = tline;
+       hptr->next = heap;
+       hptr->h_ptr = ptr;
+       heap = hptr;
+       return ptr;
+       }
+
+
+void tracked_free(void *ptr) {
+       struct TheHeap *hptr, *freeme;
+
+       if (heap->h_ptr == ptr) {
+               hptr = heap->next;
+               free(heap);
+               heap = hptr;
+               }
+       else {
+               for (hptr=heap; hptr->next!=NULL; hptr=hptr->next) {
+                       if (hptr->next->h_ptr == ptr) {
+                               freeme = hptr->next;
+                               hptr->next = hptr->next->next;
+                               free(freeme);
+                               }
+                       }
+               }
+
+       free(ptr);
+       }
+
+void *tracked_realloc(void *ptr, size_t size) {
+       void *newptr;
+       struct TheHeap *hptr;
+       
+       newptr = realloc(ptr, size);
+
+       for (hptr=heap; hptr!=NULL; hptr=hptr->next) {
+               if (hptr->h_ptr == ptr) hptr->h_ptr = newptr;
+               }
+
+       return newptr;
+       }
+
+
+void dump_tracked() {
+       struct TheHeap *hptr;
+
+       cprintf("%d Here's what's allocated...\n", LISTING_FOLLOWS);    
+       for (hptr=heap; hptr!=NULL; hptr=hptr->next) {
+               cprintf("%20s %5d\n",
+                       hptr->h_file, hptr->h_line);
+               }
+       cprintf("000\n");
+       }
+#endif
+
+
+
 /*
  * Some initialization stuff...
  */
@@ -238,7 +310,7 @@ struct CitContext *CreateNewContext(void) {
        struct CitContext *me;
 
        lprintf(9, "CreateNewContext: calling malloc()\n");
-       me = (struct CitContext *) malloc(sizeof(struct CitContext));
+       me = (struct CitContext *) mallok(sizeof(struct CitContext));
        if (me == NULL) {
                lprintf(1, "citserver: can't allocate memory!!\n");
                pthread_exit(NULL);
@@ -304,7 +376,7 @@ void RemoveContext(struct CitContext *con)
                }
 
        lprintf(9, "Freeing session context...\n");     
-       free(con);
+       phree(con);
        lprintf(9, "...done.\n");
        end_critical_section(S_SESSION_TABLE);
 
index 61804edadb16f6a6fb3f24f53c96e08dd652958b..a10f3e101edb96a8295694f57d963b9f6780d473 100644 (file)
@@ -5,6 +5,7 @@ Priority items
 Important items
 ---------------
 * In the client, make <G>oto go directly to Mail> when there's new mail.
+* Implement multi-line pages.
 
 Honeydew items
 --------------