X-Git-Url: http://git.pk910.de/?a=blobdiff_plain;f=ircd%2Flist.c;h=f6a11328ffedbc84b6f3f4a65309aa77b6bd9c15;hb=ccb26232551f5cf6abbddd0cfe175bc32ddf20f1;hp=f1089bb3a459a35f9cc2ad9c5aed092914b4e1d4;hpb=a20564091172397e32e2fa09494153c58ee97032;p=ircu2.10.12-pk.git diff --git a/ircd/list.c b/ircd/list.c index f1089bb..f6a1132 100644 --- a/ircd/list.c +++ b/ircd/list.c @@ -16,20 +16,26 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - * - * $Id$ */ -#include "list.h" +/** @file + * @brief Singly and doubly linked list manipulation implementation. + * @version $Id$ + */ +#include "config.h" -#include "class.h" +#include "list.h" #include "client.h" #include "ircd.h" #include "ircd_alloc.h" +#include "ircd_events.h" +#include "ircd_log.h" +#include "ircd_reply.h" #include "ircd_string.h" #include "listener.h" #include "match.h" #include "numeric.h" #include "res.h" +#include "s_auth.h" #include "s_bsd.h" #include "s_conf.h" #include "s_debug.h" @@ -37,232 +43,431 @@ #include "s_user.h" #include "send.h" #include "struct.h" -#include "support.h" #include "whowas.h" -#include +/* #include -- Now using assert in ircd_log.h */ #include /* offsetof */ #include /* close */ #include -#ifdef DEBUGMODE +/** Stores linked list statistics for various types of lists. */ static struct liststats { - int inuse; -} cloc, crem, users, servs, links, classs; -#endif + size_t alloc; /**< Number of structures ever allocated. */ + size_t inuse; /**< Number of structures currently in use. */ + size_t mem; /**< Memory used by in-use structures. */ +} clients, connections, servs, links; + +/** Linked list of currently unused Client structures. */ +static struct Client* clientFreeList; + +/** Linked list of currently unused Connection structures. */ +static struct Connection* connectionFreeList; + +/** Linked list of currently unused SLink structures. */ +static struct SLink* slinkFreeList; +/** Initialize the list manipulation support system. + * Pre-allocate MAXCONNECTIONS Client and Connection structures. + */ void init_list(void) { -#ifdef DEBUGMODE - memset(&cloc, 0, sizeof(cloc)); - memset(&crem, 0, sizeof(crem)); - memset(&users, 0, sizeof(users)); - memset(&servs, 0, sizeof(servs)); - memset(&links, 0, sizeof(links)); - memset(&classs, 0, sizeof(classs)); -#endif + struct Client* cptr; + struct Connection* con; + int i; + /* + * pre-allocate MAXCONNECTIONS clients and connections + */ + for (i = 0; i < MAXCONNECTIONS; ++i) { + cptr = (struct Client*) MyMalloc(sizeof(struct Client)); + cli_next(cptr) = clientFreeList; + clientFreeList = cptr; + clients.alloc++; + + con = (struct Connection*) MyMalloc(sizeof(struct Connection)); + con_next(con) = connectionFreeList; + connectionFreeList = con; + connections.alloc++; + } } -/* - * Create a new struct Client structure and set it to initial state. - * - * from == NULL, create local client (a client connected to a socket). - * - * from != NULL, create remote client (behind a socket associated with - * the client defined by 'from'). - * ('from' is a local client!!). +/** Allocate a new Client structure. + * If #clientFreeList != NULL, use the head of that list. + * Otherwise, allocate a new structure. + * @return Newly allocated Client. + */ +static struct Client* alloc_client(void) +{ + struct Client* cptr = clientFreeList; + + if (!cptr) { + cptr = (struct Client*) MyMalloc(sizeof(struct Client)); + clients.alloc++; + } else + clientFreeList = cli_next(cptr); + + clients.inuse++; + + memset(cptr, 0, sizeof(struct Client)); + + return cptr; +} + +/** Release a Client structure by prepending it to #clientFreeList. + * @param[in] cptr Client that is no longer being used. + */ +static void dealloc_client(struct Client* cptr) +{ + assert(cli_verify(cptr)); + assert(0 == cli_connect(cptr)); + + --clients.inuse; + + cli_next(cptr) = clientFreeList; + clientFreeList = cptr; + + cli_magic(cptr) = 0; +} + +/** Allocate a new Connection structure. + * If #connectionFreeList != NULL, use the head of that list. + * Otherwise, allocate a new structure. + * @return Newly allocated Connection. + */ +static struct Connection* alloc_connection(void) +{ + struct Connection* con = connectionFreeList; + + if (!con) { + con = (struct Connection*) MyMalloc(sizeof(struct Connection)); + connections.alloc++; + } else + connectionFreeList = con_next(con); + + connections.inuse++; + + memset(con, 0, sizeof(struct Connection)); + timer_init(&(con_proc(con))); + + return con; +} + +/** Release a Connection and all memory associated with it. + * The connection's DNS reply field is freed, its file descriptor is + * closed, its msgq and sendq are cleared, and its associated Listener + * is dereferenced. Then it is prepended to #connectionFreeList. + * @param[in] con Connection to free. + */ +static void dealloc_connection(struct Connection* con) +{ + assert(con_verify(con)); + assert(!t_active(&(con_proc(con)))); + assert(!t_onqueue(&(con_proc(con)))); + + Debug((DEBUG_LIST, "Deallocating connection %p", con)); + + if (-1 < con_fd(con)) + close(con_fd(con)); + MsgQClear(&(con_sendQ(con))); + client_drop_sendq(con); + DBufClear(&(con_recvQ(con))); + if (con_listener(con)) + release_listener(con_listener(con)); + + --connections.inuse; + + con_next(con) = connectionFreeList; + connectionFreeList = con; + + con_magic(con) = 0; +} + +/** Allocate a new client and initialize it. + * If \a from == NULL, initialize the fields for a local client, + * including allocating a Connection for him; otherwise initialize the + * fields for a remote client.. + * @param[in] from Server connection that introduced the client (or + * NULL). + * @param[in] status Initial Client::cli_status value. + * @return Newly allocated and initialized Client. */ struct Client* make_client(struct Client *from, int status) { - struct Client *cptr = NULL; - size_t size = CLIENT_REMOTE_SIZE; + struct Client* cptr = 0; - /* - * Check freelists first to see if we can grab a client without - * having to call malloc. - */ - if (!from) - size = CLIENT_LOCAL_SIZE; + assert(!from || cli_verify(from)); + + cptr = alloc_client(); - cptr = (struct Client*) MyMalloc(size); assert(0 != cptr); - /* - * NOTE: Do not remove this, a lot of code depends on the entire - * structure being zeroed out - */ - memset(cptr, 0, size); /* All variables are 0 by default */ + assert(!cli_magic(cptr)); + assert(0 == from || 0 != cli_connect(from)); -#ifdef DEBUGMODE - if (size == CLIENT_LOCAL_SIZE) - cloc.inuse++; - else - crem.inuse++; -#endif + if (!from) { /* local client, allocate a struct Connection */ + struct Connection *con = alloc_connection(); + + assert(0 != con); + assert(!con_magic(con)); + + con_magic(con) = CONNECTION_MAGIC; + con_fd(con) = -1; /* initialize struct Connection */ + con_freeflag(con) = 0; + con_nextnick(con) = CurrentTime - NICK_DELAY; + con_nexttarget(con) = CurrentTime - (TARGET_DELAY * (STARTTARGETS - 1)); + con_handler(con) = UNREGISTERED_HANDLER; + con_client(con) = cptr; + + cli_connect(cptr) = con; /* set the connection and other fields */ + cli_since(cptr) = cli_lasttime(cptr) = cli_firsttime(cptr) = CurrentTime; + cli_lastnick(cptr) = TStime(); + } else + cli_connect(cptr) = cli_connect(from); /* use 'from's connection */ + + assert(con_verify(cli_connect(cptr))); + + cli_magic(cptr) = CLIENT_MAGIC; + cli_status(cptr) = status; + cli_hnext(cptr) = cptr; + strcpy(cli_username(cptr), "unknown"); - /* Note: structure is zero (memset) */ - cptr->from = from ? from : cptr; /* 'from' of local client is self! */ - cptr->status = status; - cptr->hnext = cptr; - strcpy(cptr->username, "unknown"); - - if (CLIENT_LOCAL_SIZE == size) { - cptr->fd = -1; - cptr->local = 1; - cptr->since = cptr->lasttime = cptr->firsttime = CurrentTime; - cptr->lastnick = TStime(); - cptr->nextnick = CurrentTime - NICK_DELAY; - cptr->nexttarget = CurrentTime - (TARGET_DELAY * (STARTTARGETS - 1)); - cptr->handler = UNREGISTERED_HANDLER; - } return cptr; } -void free_client(struct Client *cptr) +/** Release a Connection. + * @param[in] con Connection to free. + */ +void free_connection(struct Connection* con) { - if (cptr && cptr->local) { - /* - * make sure we have cleaned up local resources - */ - if (cptr->dns_reply) - --cptr->dns_reply->ref_count; - if (-1 < cptr->fd) - close(cptr->fd); - DBufClear(&cptr->sendQ); - DBufClear(&cptr->recvQ); - if (cptr->listener) - release_listener(cptr->listener); - } + if (!con) + return; + + assert(con_verify(con)); + assert(0 == con_client(con)); + + dealloc_connection(con); /* deallocate the connection */ +} + +/** Release a Client. + * In addition to the cleanup done by dealloc_client(), this will free + * any pending auth request, free the connection for local clients, + * and delete the processing timer for the client. + * @param[in] cptr Client to free. + */ +void free_client(struct Client* cptr) +{ + if (!cptr) + return; /* * forget to remove the client from the hash table? */ - assert(cptr->hnext == cptr); + assert(cli_verify(cptr)); + assert(cli_hnext(cptr) == cptr); + /* or from linked list? */ + assert(cli_next(cptr) == 0); + assert(cli_prev(cptr) == 0); + + Debug((DEBUG_LIST, "Freeing client %s [%p], connection %p", cli_name(cptr), + cptr, cli_connect(cptr))); + + if (cli_auth(cptr)) + destroy_auth_request(cli_auth(cptr)); + + /* Make sure we didn't magically get re-added to the list */ + assert(cli_next(cptr) == 0); + assert(cli_prev(cptr) == 0); + + if (cli_from(cptr) == cptr) { /* in other words, we're local */ + cli_from(cptr) = 0; + /* timer must be marked as not active */ + if (!cli_freeflag(cptr) && !t_active(&(cli_proc(cptr)))) + dealloc_connection(cli_connect(cptr)); /* connection not open anymore */ + else { + if (-1 < cli_fd(cptr) && cli_freeflag(cptr) & FREEFLAG_SOCKET) + socket_del(&(cli_socket(cptr))); /* queue a socket delete */ + if (cli_freeflag(cptr) & FREEFLAG_TIMER) + timer_del(&(cli_proc(cptr))); /* queue a timer delete */ + } + } + + cli_connect(cptr) = 0; - MyFree(cptr); + dealloc_client(cptr); /* actually destroy the client */ } +/** Allocate a new Server object for a client. + * If Client::cli_serv == NULL, allocate a Server structure for it and + * initialize it. + * @param[in] cptr %Client to make into a server. + * @return The value of cli_serv(\a cptr). + */ struct Server *make_server(struct Client *cptr) { - struct Server *serv = cptr->serv; + struct Server *serv = cli_serv(cptr); + + assert(cli_verify(cptr)); if (!serv) { serv = (struct Server*) MyMalloc(sizeof(struct Server)); assert(0 != serv); memset(serv, 0, sizeof(struct Server)); /* All variables are 0 by default */ -#ifdef DEBUGMODE servs.inuse++; -#endif - cptr->serv = serv; - cptr->serv->lag = 60000; + servs.alloc++; + cli_serv(cptr) = serv; + cli_serv(cptr)->lag = 60000; *serv->by = '\0'; DupString(serv->last_error_msg, "<>"); /* String must be non-empty */ } - return cptr->serv; + return cli_serv(cptr); } -/* - * Taken the code from ExitOneClient() for this and placed it here. - * - avalon +/** Remove \a cptr from lists that it is a member of. + * Specifically, this delinks \a cptr from #GlobalClientList, updates + * the whowas history list, frees its Client::cli_user and + * Client::cli_serv fields, and finally calls free_client() on it. + * @param[in] cptr Client to remove from lists and free. */ void remove_client_from_list(struct Client *cptr) { - if (cptr->prev) - cptr->prev->next = cptr->next; - else { - GlobalClientList = cptr->next; - GlobalClientList->prev = 0; + assert(cli_verify(cptr)); + assert(con_verify(cli_connect(cptr))); + assert(!cli_prev(cptr) || cli_verify(cli_prev(cptr))); + assert(!cli_next(cptr) || cli_verify(cli_next(cptr))); + assert(!IsMe(cptr)); + + /* Only try remove cptr from the list if it IS in the list. + * cli_next(cptr) cannot be NULL here, as &me is always the end + * the list, and we never remove &me. -GW + */ + if(cli_next(cptr)) + { + if (cli_prev(cptr)) + cli_next(cli_prev(cptr)) = cli_next(cptr); + else { + GlobalClientList = cli_next(cptr); + cli_prev(GlobalClientList) = 0; + } + cli_prev(cli_next(cptr)) = cli_prev(cptr); } - if (cptr->next) - cptr->next->prev = cptr->prev; - - cptr->next = cptr->prev = 0; + cli_next(cptr) = cli_prev(cptr) = 0; - if (IsUser(cptr) && cptr->user) { + if (IsUser(cptr) && cli_user(cptr)) { add_history(cptr, 0); off_history(cptr); } - if (cptr->user) { - free_user(cptr->user); - cptr->user = 0; + if (cli_user(cptr)) { + free_user(cli_user(cptr)); + cli_user(cptr) = 0; } - if (cptr->serv) { - if (cptr->serv->user) { - free_user(cptr->serv->user); - cptr->serv->user = 0; + if (cli_serv(cptr)) { + if (cli_serv(cptr)->user) { + free_user(cli_serv(cptr)->user); + cli_serv(cptr)->user = 0; } - if (cptr->serv->client_list) - MyFree(cptr->serv->client_list); - MyFree(cptr->serv->last_error_msg); - MyFree(cptr->serv); -#ifdef DEBUGMODE + if (cli_serv(cptr)->client_list) + MyFree(cli_serv(cptr)->client_list); + MyFree(cli_serv(cptr)->last_error_msg); + MyFree(cli_serv(cptr)); --servs.inuse; -#endif + --servs.alloc; } -#ifdef DEBUGMODE - if (cptr->local) - --cloc.inuse; - else - --crem.inuse; -#endif free_client(cptr); } -/* - * Although only a small routine, it appears in a number of places - * as a collection of a few lines...functions like this *should* be - * in this file, shouldnt they ? after all, this is list.c, isn't it ? - * -avalon +/** Link \a cptr into #GlobalClientList. + * @param[in] cptr Client to link into the global list. */ void add_client_to_list(struct Client *cptr) { + assert(cli_verify(cptr)); + assert(cli_next(cptr) == 0); + assert(cli_prev(cptr) == 0); + /* * Since we always insert new clients to the top of the list, * this should mean the "me" is the bottom most item in the list. * XXX - don't always count on the above, things change */ - cptr->prev = 0; - cptr->next = GlobalClientList; + cli_prev(cptr) = 0; + cli_next(cptr) = GlobalClientList; GlobalClientList = cptr; - if (cptr->next) - cptr->next->prev = cptr; + if (cli_next(cptr)) + cli_prev(cli_next(cptr)) = cptr; } -/* - * Look for ptr in the linked listed pointed to by link. +#if 0 +/** Perform a very CPU-intensive verification of %GlobalClientList. + * This checks the Client::cli_magic and Client::cli_prev field for + * each element in the list, and also checks that there are no loops. + * Any detected error will lead to an assertion failure. */ -struct SLink *find_user_link(struct SLink *lp, struct Client *ptr) +void verify_client_list(void) { - if (ptr) - while (lp) - { - if (lp->value.cptr == ptr) - return (lp); - lp = lp->next; - } - return NULL; + struct Client *client, *prev = 0; + unsigned int visited = 0; + + for (client = GlobalClientList; client; client = cli_next(client), ++visited) { + /* Verify that this is a valid client, not a free'd one */ + assert(cli_verify(client)); + /* Verify that the list hasn't suddenly jumped around */ + assert(cli_prev(client) == prev); + /* Verify that the list hasn't become circular */ + assert(cli_next(client) != GlobalClientList); + assert(visited <= clients.alloc); + /* Remember what should precede us */ + prev = client; + } } +#endif /* DEBUGMODE */ -struct SLink *make_link(void) +/** Allocate a new SLink element. + * Pulls from #slinkFreeList if it contains anything, else it + * allocates a new one from the heap. + * @return Newly allocated list element. + */ +struct SLink* make_link(void) { - struct SLink *lp; - - lp = (struct SLink*) MyMalloc(sizeof(struct SLink)); +#if 1 + struct SLink* lp = (struct SLink*) MyMalloc(sizeof(struct SLink)); +#else + struct SLink* lp = slinkFreeList; + if (lp) + slinkFreeList = lp->next; + else { + lp = (struct SLink*) MyMalloc(sizeof(struct SLink)); + links.alloc++; + } +#endif assert(0 != lp); -#ifdef DEBUGMODE links.inuse++; -#endif + memset(lp, 0, sizeof(*lp)); return lp; } -void free_link(struct SLink *lp) +/** Release a singly linked list element. + * @param[in] lp List element to mark as unused. + */ +void free_link(struct SLink* lp) { - MyFree(lp); -#ifdef DEBUGMODE - links.inuse--; + if (lp) { +#if 1 + MyFree(lp); +#else + lp->next = slinkFreeList; + slinkFreeList = lp; #endif + links.inuse--; + } } +/** Add an element to a doubly linked list. + * If \a lpp points to a non-NULL pointer, its DLink::prev field is + * updated to point to the newly allocated element. Regardless, + * \a lpp is overwritten with the pointer to the new link. + * @param[in,out] lpp Pointer to insertion location. + * @param[in] cp %Client to put in newly allocated element. + * @return Allocated link structure (same as \a lpp on output). + */ struct DLink *add_dlink(struct DLink **lpp, struct Client *cp) { struct DLink* lp = (struct DLink*) MyMalloc(sizeof(struct DLink)); @@ -275,6 +480,10 @@ struct DLink *add_dlink(struct DLink **lpp, struct Client *cp) return lp; } +/** Remove a node from a doubly linked list. + * @param[out] lpp Pointer to next list element. + * @param[in] lp List node to unlink. + */ void remove_dlink(struct DLink **lpp, struct DLink *lp) { assert(0 != lpp); @@ -289,68 +498,54 @@ void remove_dlink(struct DLink **lpp, struct DLink *lp) MyFree(lp); } -struct ConfClass *make_class(void) -{ - struct ConfClass *tmp; - - tmp = (struct ConfClass*) MyMalloc(sizeof(struct ConfClass)); - assert(0 != tmp); -#ifdef DEBUGMODE - classs.inuse++; -#endif - return tmp; -} - -void free_class(struct ConfClass * tmp) +/** Report memory usage of a list to \a cptr. + * @param[in] cptr Client requesting information. + * @param[in] lstats List statistics descriptor. + * @param[in] itemname Plural name of item type. + * @param[in,out] totals If non-null, accumulates item counts and memory usage. + */ +void send_liststats(struct Client *cptr, const struct liststats *lstats, + const char *itemname, struct liststats *totals) { - MyFree(tmp); -#ifdef DEBUGMODE - classs.inuse--; -#endif + send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":%s: inuse %zu(%zu) alloc %zu", + itemname, lstats->inuse, lstats->mem, lstats->alloc); + if (totals) + { + totals->inuse += lstats->inuse; + totals->alloc += lstats->alloc; + totals->mem += lstats->mem; + } } -#ifdef DEBUGMODE -/* XXX uses sendto_one to send a RPL_STATSDEBUG -- no string for rpl */ +/** Report memory usage of list elements to \a cptr. + * @param[in] cptr Client requesting information. + * @param[in] name Unused pointer. + */ void send_listinfo(struct Client *cptr, char *name) { - int inuse = 0, mem = 0, tmp = 0; - - sendto_one(cptr, ":%s %d %s :Local: inuse: %d(%d)", - me.name, RPL_STATSDEBUG, name, inuse += cloc.inuse, - tmp = cloc.inuse * CLIENT_LOCAL_SIZE); - mem += tmp; - sendto_one(cptr, ":%s %d %s :Remote: inuse: %d(%d)", - me.name, RPL_STATSDEBUG, name, - crem.inuse, tmp = crem.inuse * CLIENT_REMOTE_SIZE); - mem += tmp; - inuse += crem.inuse; - sendto_one(cptr, ":%s %d %s :Users: inuse: %d(%d)", - me.name, RPL_STATSDEBUG, name, users.inuse, - tmp = users.inuse * sizeof(struct User)); - mem += tmp; - inuse += users.inuse, - sendto_one(cptr, ":%s %d %s :Servs: inuse: %d(%d)", - me.name, RPL_STATSDEBUG, name, servs.inuse, - tmp = servs.inuse * sizeof(struct Server)); - mem += tmp; - inuse += servs.inuse, - sendto_one(cptr, ":%s %d %s :Links: inuse: %d(%d)", - me.name, RPL_STATSDEBUG, name, links.inuse, - tmp = links.inuse * sizeof(struct SLink)); - mem += tmp; - inuse += links.inuse, - sendto_one(cptr, ":%s %d %s :Classes: inuse: %d(%d)", - me.name, RPL_STATSDEBUG, name, classs.inuse, - tmp = classs.inuse * sizeof(struct ConfClass)); - mem += tmp; - inuse += classs.inuse, - sendto_one(cptr, ":%s %d %s :Confs: inuse: %d(%d)", - me.name, RPL_STATSDEBUG, name, GlobalConfCount, - tmp = GlobalConfCount * sizeof(struct ConfItem)); - mem += tmp; - inuse += GlobalConfCount, - sendto_one(cptr, ":%s %d %s :Totals: inuse %d %d", - me.name, RPL_STATSDEBUG, name, inuse, mem); -} + struct liststats total; + struct liststats confs; + struct ConfItem *conf; -#endif + memset(&total, 0, sizeof(total)); + + clients.mem = clients.inuse * sizeof(struct Client); + send_liststats(cptr, &clients, "Clients", &total); + + connections.mem = connections.inuse * sizeof(struct Connection); + send_liststats(cptr, &connections, "Connections", &total); + + servs.mem = servs.inuse * sizeof(struct Server); + send_liststats(cptr, &servs, "Servers", &total); + + links.mem = links.inuse * sizeof(struct SLink); + send_liststats(cptr, &links, "Links", &total); + + confs.alloc = GlobalConfCount; + confs.mem = confs.alloc * sizeof(GlobalConfCount); + for (confs.inuse = 0, conf = GlobalConfList; conf; conf = conf->next) + confs.inuse++; + send_liststats(cptr, &confs, "Confs", &total); + + send_liststats(cptr, &total, "Totals", NULL); +}