/************************************************************************
 *   IRC - Internet Relay Chat, ircd/list.c
 *   Copyright (C) 1990 Jarkko Oikarinen and
 *                      University of Oulu, Finland
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 1, or (at your option)
 *   any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   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.
 */

#ifndef lint
static  char rcsid[] = "@(#)$Id: list.c,v 1.9 2003/11/28 08:32:41 gvs Exp $";
#endif

#include "os.h"
#include "s_defines.h"
#define LIST_C
#include "s_externs.h"
#undef LIST_C

char *DefInfo = "*Not On This Net*"; /* constant */

#ifdef	DEBUGMODE
static	struct	liststats {
	int	inuse;
} cloc, crem, users, servs, links, classs, aconfs;

#endif

aServer	*svrtop = NULL;

int	numclients = 0;

void	initlists()
{
#ifdef	DEBUGMODE
	bzero((char *)&cloc, sizeof(cloc));
	bzero((char *)&crem, sizeof(crem));
	bzero((char *)&users, sizeof(users));
	bzero((char *)&servs, sizeof(servs));
	bzero((char *)&links, sizeof(links));
	bzero((char *)&classs, sizeof(classs));
	bzero((char *)&aconfs, sizeof(aconfs));
#endif
}

void	outofmemory()
{
	Debug((DEBUG_FATAL, "Out of memory: restarting server..."));
	sendto_flag(SCH_NOTICE, "Ouch!!! Out of memory...");
	server_reboot("Out of Memory");
}

#ifdef	DEBUGMODE
void	checklists()
{
	aServer	*sp;
	anUser	*up;

	for (sp = svrtop; sp; sp = sp->nexts)
		if (sp->bcptr->serv != sp)
			Debug((DEBUG_ERROR, "svrtop: %#x->%#x->%#x != %#x",
				sp, sp->bcptr, sp->bcptr->serv, sp));
}
#endif
	
/*
** Create a new aClient structure and set it to initial state.
**
**	from == NULL,	create local client (a client connected
**			to a socket).
**
**	from,	create remote client (behind a socket
**			associated with the client defined by
**			'from'). ('from' is a local client!!).
*/
aClient	*make_client(from)
aClient	*from;
{
	Reg	aClient *cptr = NULL;
	Reg	unsigned size;

	/*
	 * Check freelists first to see if we can grab a client without
	 * having to call malloc.
	 */
	size = (from == NULL) ? CLIENT_LOCAL_SIZE : CLIENT_REMOTE_SIZE;

	if (!(cptr = (aClient *)MyMalloc(size)))
		outofmemory();
	bzero((char *)cptr, (int)size);

#ifdef	DEBUGMODE
	if (size == CLIENT_LOCAL_SIZE)
		cloc.inuse++;
	else
		crem.inuse++;
#endif

	/* Note:  structure is zero (calloc) */
	cptr->from = from ? from : cptr; /* 'from' of local client is self! */
	cptr->next = NULL; /* For machines with NON-ZERO NULL pointers >;) */
	cptr->prev = NULL;
	cptr->hnext = NULL;
	cptr->user = NULL;
	cptr->serv = NULL;
	cptr->status = STAT_UNKNOWN;
	cptr->fd = -1;
	(void)strcpy(cptr->username, "unknown");
	cptr->info = DefInfo;
	if (size == CLIENT_LOCAL_SIZE)
	    {
		cptr->since = cptr->lasttime = cptr->firsttime = timeofday;
		cptr->confs = NULL;
		cptr->sockhost[0] = '\0';
		cptr->buffer[0] = '\0';
		cptr->authfd = -1;
		cptr->auth = cptr->username;
		cptr->exitc = EXITC_UNDEF;
#ifdef	ZIP_LINKS
		cptr->zip = NULL;
#endif
	    }
	return (cptr);
}

void	free_client(cptr)
aClient	*cptr;
{
	if (cptr->info != DefInfo)
		MyFree(cptr->info);
	if (MyConnect(cptr) && cptr->auth != cptr->username)
	{
	    sendto_flag(SCH_ERROR, "Please report to ircd-bug@irc.org about cptr->auth allocated but not free()d!");
		istat.is_authmem -= strlen(cptr->auth) + 1;
		istat.is_auth -= 1;
		MyFree(cptr->auth);
	}
	MyFree(cptr);
}

/*
** 'make_user' add's an User information block to a client
** if it was not previously allocated.
*/
anUser	*make_user(cptr)
aClient *cptr;
{
	Reg	anUser	*user;

	user = cptr->user;
	if (!user)
	    {
		user = (anUser *)MyMalloc(sizeof(anUser));
#ifdef	DEBUGMODE
		users.inuse++;
#endif
		user->away = NULL;
		user->refcnt = 1;
		user->joined = 0;
		user->flags = 0;
		user->channel = NULL;
		user->invited = NULL;
		user->uwas = NULL;
		cptr->user = user;
		user->servp = NULL;
		user->bcptr = cptr;
		if (cptr->next)	/* the only cptr->next == NULL is me */
			istat.is_users++;
	    }
	return user;
}

aServer	*make_server(cptr)
aClient	*cptr;
{
	Reg	aServer	*serv = cptr->serv, *sp, *spp = NULL;

	if (!serv)
	    {
		serv = (aServer *)MyMalloc(sizeof(aServer));
#ifdef	DEBUGMODE
		servs.inuse++;
#endif
		serv->user = NULL;
		serv->snum = -1;
		*serv->by = '\0';
		*serv->tok = '\0';
		serv->stok = 0;
		serv->up = NULL;
		serv->refcnt = 1;
		serv->nexts = NULL;
		cptr->serv = serv;

		for (sp = svrtop; sp; spp = sp, sp = sp->nexts)
			if (spp && ((spp->ltok) + 1 < sp->ltok))
				break;
		serv->prevs = spp;
		if (spp)
		    {
			serv->ltok = spp->ltok + 1;
			spp->nexts = serv;
		    }
		else
		    {	/* Me, myself and I alone */
			svrtop = serv;
			serv->ltok = 1;
		    }

		if (sp)
		    {
			serv->nexts = sp;
			sp->prevs = serv;
		    }
		serv->bcptr = cptr;
		SPRINTF(serv->tok, "%d", serv->ltok);
		serv->lastload = 0;
	    }
	return cptr->serv;
}

/*
** free_user
**	Decrease user reference count by one and realease block,
**	if count reaches 0
*/
void	free_user(user, cptr)
Reg	anUser	*user;
aClient	*cptr;
{
	aServer *serv;

	if (--user->refcnt <= 0)
	    {
		if ((serv = user->servp))
		    {
			user->servp = NULL; /* to avoid some impossible loop */
			free_server(serv, cptr);
		    }
		if (user->away)
		    {
			istat.is_away--;
			istat.is_awaymem -= (strlen(user->away) + 1);
			MyFree(user->away);
		    }
		/*
		 * sanity check
		 */
		if (user->joined || user->refcnt < 0 ||
		    user->invited || user->channel || user->uwas ||
		    user->bcptr)
		    {
			char buf[512];
			/*too many arguments for dumpcore() and sendto_flag()*/
			SPRINTF(buf, "%#x %#x %#x %#x %d %d %#x (%s)",
				user, user->invited, user->channel, user->uwas,
				user->joined, user->refcnt,
				user->bcptr,
				(user->bcptr) ? user->bcptr->name :"none");
#ifdef DEBUGMODE
			if (cptr)
				dumpcore("%#x user (%s!%s@%s) %s",
					 cptr, cptr->name, user->username,
#ifdef RUSNET_IRCD
						cptr->sockhost
#else
						user->host
#endif
								, buf);
			else
				dumpcore("(null) user (<noname>!%s@%s) %s",
					 user->username, user->host, buf);
#else
			if (cptr)
				sendto_flag(SCH_ERROR,
					    "* %#x user (%s!%s@%s) %s *",
					    cptr, cptr->name, user->username,
#ifdef RUSNET_IRCD
						cptr->sockhost
#else
						user->host
#endif
								, buf);
			else
				sendto_flag(SCH_ERROR, "* (null) user "
					    "(<noname>!%s@%s) %s *",
					    user->username, user->host, buf);
#endif
		    }
		MyFree(user);
#ifdef	DEBUGMODE
		users.inuse--;
#endif
	    }
}

void	free_server(serv, cptr)
aServer	*serv;
aClient	*cptr;
{
	if (--serv->refcnt <= 0)
	    {
		if (serv->refcnt < 0 ||	serv->prevs || serv->nexts ||
		    serv->bcptr || serv->user)
		    {
			char buf[512];
			SPRINTF(buf, "%d %#x %#x %#x %#x (%s)",
				serv->refcnt, serv->prevs, serv->nexts,
				serv->user, serv->bcptr,
				(serv->bcptr) ? serv->bcptr->name : "none");
#ifdef DEBUGMODE
			dumpcore("%#x server %s %s",
				 cptr, cptr ? cptr->name : "<noname>", buf);
			servs.inuse--;
#else
			sendto_flag(SCH_ERROR, "* %#x server %s %s *",
				    cptr, cptr ? cptr->name : "<noname>", buf);
#endif
		    }
		MyFree(serv);
	    }
}

/*
 * taken the code from ExitOneClient() for this and placed it here.
 * - avalon
 * remove client **AND** _related structures_ from lists,
 * *free* them too. -krys
 */
void	remove_client_from_list(cptr)
Reg	aClient	*cptr;
{
	checklist();
	if (cptr->hopcount == 0) /* is there another way, at this point? */
		istat.is_localc--;
	else
		istat.is_remc--;
	if (cptr->prev)
		cptr->prev->next = cptr->next;
	else
	    {
		client = cptr->next;
		client->prev = NULL;
	    }
	if (cptr->next)
		cptr->next->prev = cptr->prev;

	if (cptr->user)
	    {
		istat.is_users--;
		/* decrement reference counter, and eventually free it */
		cptr->user->bcptr = NULL;
		(void)free_user(cptr->user, cptr);
	    }

	if (cptr->serv)
	    {
		/* has to be removed from the list of aServer structures */
		if (cptr->serv->nexts)
			cptr->serv->nexts->prevs = cptr->serv->prevs;
		if (cptr->serv->prevs)
			cptr->serv->prevs->nexts = cptr->serv->nexts;
		if (svrtop == cptr->serv)
			svrtop = cptr->serv->nexts;
		cptr->serv->prevs = NULL;
		cptr->serv->nexts = NULL;
		
		if (cptr->serv->user)
		    {
			free_user(cptr->serv->user, cptr);
			cptr->serv->user = NULL;
		    }

		/* decrement reference counter, and eventually free it */
		cptr->serv->bcptr = NULL;
		free_server(cptr->serv, cptr);
	    }

	if (cptr->service)
		/*
		** has to be removed from the list of aService structures,
		** no reference counter for services, thus this part of the
		** code can safely be included in free_service()
		*/
		free_service(cptr);

#ifdef	DEBUGMODE
	if (cptr->fd == -2)
		cloc.inuse--;
	else
		crem.inuse--;
#endif

	(void)free_client(cptr);
	numclients--;
	return;
}

/*
 * move the client aClient struct before its server's
 */
void	reorder_client_in_list(cptr)
aClient	*cptr;
{
    if (cptr->user == NULL && cptr->service == NULL)
	    return;

    /* update neighbours */
    if (cptr->next)
	    cptr->next->prev = cptr->prev;
    if (cptr->prev)
	    cptr->prev->next = cptr->next;
    else
	    client = cptr->next;

    /* re-insert */
    if (cptr->user)
	{
	    cptr->next = cptr->user->servp->bcptr;
	    cptr->prev = cptr->user->servp->bcptr->prev;
#ifdef DEBUGMODE
	    sendto_flag(SCH_DEBUG, "%p [%s] moved before server: %p [%s]", 
			cptr, cptr->name, cptr->user->servp->bcptr, 
			cptr->user->servp->bcptr->name);
#endif
	}
    else if (cptr->service)
	{
	    cptr->next = cptr->service->servp->bcptr;
	    cptr->prev = cptr->service->servp->bcptr->prev;
	}

    /* update new neighbours */
    if (cptr->prev)
	    cptr->prev->next = cptr;
    else
	    client = cptr;
    cptr->next->prev = 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, isnt it ?
 * -avalon
 */
void	add_client_to_list(cptr)
aClient	*cptr;
{
	/*
	 * 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.
	 */
	if (cptr->from == cptr)
		istat.is_localc++;
	else
		istat.is_remc++;
	if (cptr->user)
		istat.is_users++;
	
	cptr->next = client;
	client = cptr;
	
	if (cptr->next)
		cptr->next->prev = cptr;

	numclients++;
	return;
}

/*
 * Look for ptr in the linked listed pointed to by link.
 */
Link	*find_user_link(lp, ptr)
Reg	Link	*lp;
Reg	aClient *ptr;
{
	if (ptr)
		for (; lp; lp = lp->next)
			if (lp->value.cptr == ptr)
				return (lp);
	return NULL;
}

Link  *find_channel_link(lp, ptr)
Reg   Link    *lp;
Reg   aChannel *ptr; 
{ 
	if (ptr)
		for (; lp; lp = lp->next)
			if (lp->value.chptr == ptr)
				return (lp);
	return NULL;    
}

Link	*make_link()
{
	Reg	Link	*lp;

	lp = (Link *)MyMalloc(sizeof(Link));
#ifdef	DEBUGMODE
	links.inuse++;
#endif
	lp->flags = 0;
	return lp;
}

void	free_link(lp)
Reg	Link	*lp;
{
	MyFree(lp);
#ifdef	DEBUGMODE
	links.inuse--;
#endif
}


aClass	*make_class()
{
	Reg	aClass	*tmp;

	tmp = (aClass *)MyMalloc(sizeof(aClass));
#ifdef	DEBUGMODE
	classs.inuse++;
#endif
	return tmp;
}

void	free_class(tmp)
Reg	aClass	*tmp;
{
	MyFree(tmp);
#ifdef	DEBUGMODE
	classs.inuse--;
#endif
}

aConfItem	*make_conf()
{
	Reg	aConfItem *aconf;

	aconf = (struct ConfItem *)MyMalloc(sizeof(aConfItem));

#ifdef	DEBUGMODE
	aconfs.inuse++;
#endif
	istat.is_conf++;
	istat.is_confmem += sizeof(aConfItem);

	bzero((char *)&aconf->ipnum, sizeof(struct in_addr));
	aconf->clients = aconf->port = 0;
	aconf->next = NULL;
	aconf->host = aconf->passwd = aconf->name = NULL;
	aconf->ping = NULL;
	aconf->status = CONF_ILLEGAL;
	aconf->pref = -1;
	aconf->hold = time(NULL);
	Class(aconf) = NULL;
	return (aconf);
}

void	delist_conf(aconf)
aConfItem	*aconf;
{
	if (aconf == conf)
		conf = conf->next;
	else
	    {
		aConfItem	*bconf;

		for (bconf = conf; aconf != bconf->next; bconf = bconf->next)
			;
		bconf->next = aconf->next;
	    }
	aconf->next = NULL;
}

void	free_conf(aconf)
aConfItem *aconf;
{
	del_queries((char *)aconf);

	istat.is_conf--;
	istat.is_confmem -= aconf->host ? strlen(aconf->host)+1 : 0;
	istat.is_confmem -= aconf->passwd ? strlen(aconf->passwd)+1 : 0;
	istat.is_confmem -= aconf->name ? strlen(aconf->name)+1 : 0;
	istat.is_confmem -= aconf->ping ? sizeof(*aconf->ping) : 0;
	istat.is_confmem -= sizeof(aConfItem);

	MyFree(aconf->host);
	if (aconf->passwd)
		bzero(aconf->passwd, strlen(aconf->passwd));
	if (aconf->ping)
		MyFree(aconf->ping);
	MyFree(aconf->passwd);
	MyFree(aconf->name);
	MyFree(aconf);
#ifdef	DEBUGMODE
	aconfs.inuse--;
#endif
	return;
}

#ifdef	DEBUGMODE
void	send_listinfo(cptr, name)
aClient	*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(anUser));
	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(aServer));
	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(Link));
	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(aClass));
	mem += tmp;
	inuse += classs.inuse,
	sendto_one(cptr, ":%s %d %s :Confs: inuse: %d(%d)",
		   me.name, RPL_STATSDEBUG, name, aconfs.inuse,
		   tmp = aconfs.inuse * sizeof(aConfItem));
	mem += tmp;
	inuse += aconfs.inuse,
	sendto_one(cptr, ":%s %d %s :Totals: inuse %d %d",
		   me.name, RPL_STATSDEBUG, name, inuse, mem);
}
#endif


void	add_fd(fd, ary)
int	fd;
FdAry	*ary;
{
	Debug((DEBUG_DEBUG,"add_fd(%d,%#x)", fd, ary));
	if (fd >= 0)
		ary->fd[++(ary->highest)] = fd;
}


int	del_fd(fd, ary)
int	fd;
FdAry	*ary;
{
	int	i;

	Debug((DEBUG_DEBUG,"del_fd(%d,%#x)", fd, ary));
	if ((ary->highest == -1) || (fd < 0))
		return -1;
	for (i = 0; i <= ary->highest; i++)
		if (ary->fd[i] == fd)
			break;
	if (i < ary->highest)
	    {
		ary->fd[i] = ary->fd[ary->highest--];
		return 0;
	    }
	else if (i > ary->highest)
		return -1;
	ary->highest--;
	return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1