wm: ticl

Download patch

ref: 202447793107a549c7e418dc3b2208caa89a0bb3
parent: 375f68cba6f87f824a01f604b65057138c754a43
author: libredev <libredev@ircforever.org>
date: Mon Feb 13 08:46:51 EST 2023

cleaned up the code

--- a/config.mk
+++ b/config.mk
@@ -6,9 +6,6 @@
 PREFIX = /usr/local
 MANPREFIX = $(PREFIX)/share/man
 
-# OpenBSD (comment)
-LIBS = -lbsd
-
 CPPFLAGS = -D_DEFAULT_SOURCE -DVERSION=\"$(VERSION)\"
 CFLAGS   = -g -std=c89 -Wall -Wextra -pedantic -Wfatal-errors -Wconversion\
 	   -Wstrict-prototypes -Wold-style-definition $(CPPFLAGS)
--- a/htable.c
+++ b/htable.c
@@ -3,70 +3,25 @@
  * See COPYING file for more information.
  */
 
-#include <errno.h>
 #include <stdlib.h>
 #include <stdio.h>
-#include <string.h>
 
 #include "htable.h"
 
-static unsigned int
-hash(Htable *ht, void *key)
-{
-	unsigned int i;
-	unsigned int sum = 0;
-
-	/* very simple hash function */
-	for (i = 0; i < (unsigned int)ht->keylenfn(key); i++) {
-		sum += ((unsigned char *)key)[i] * (i + 1);
-	}
-	return (sum % ht->cap);
-}
-
-Htable *
-htcreate(KeyLenFn *keylenfn, KeyCmpFn *keycmpfn, FreeKeyFn *freekeyfn, FreeValFn *freevalfn, unsigned int cap)
-{
-	unsigned int i;
-	Htable *ht;
-
-	if (keylenfn == NULL || keycmpfn == NULL || freekeyfn == NULL || freevalfn == NULL) {
-		printf("error: callback function(s) cannot be NULL\n");
-		return NULL;
-	}
-
-	/* create a hash table */
-	if ((ht = calloc(1, sizeof(Htable))) == NULL) {
-		printf("error: calloc: %s\n", strerror(errno));
-		return NULL;
-	}
-	if ((ht->nodes = calloc(cap, sizeof(Htnode))) == NULL) {
-		printf("error: calloc: %s\n", strerror(errno));
-		return NULL;
-	}
-	ht->cap		= cap;
-	ht->len		= 0;
-	ht->keylenfn	= keylenfn;
-	ht->keycmpfn	= keycmpfn;
-	ht->freekeyfn	= freekeyfn;
-	ht->freevalfn	= freevalfn;
-
-	for (i = 0; i < cap; i++)
-		ht->nodes[i] = NULL;
-	return ht;
-}
-
+/*
+ * Destroy the hash table and, if the free_key_val flag is true, free the keys and values.
+ */
 static void
-__htdestroy(Htable *ht, int dofree)
+destroy_and_free(struct htable *ht, int free_key_val)
 {
 	unsigned int i;
-	Htnode *n;
-	Htnode *tmp;
+	struct htnode *n, *tmp;
 
 	for (i = 0; i < ht->cap; i++) {
 		for (n = ht->nodes[i]; n != NULL;) {
-			if (dofree) {
-				ht->freekeyfn(n->key);
-				ht->freevalfn(n->val);
+			if (free_key_val) {
+				ht->key_free(n->key);
+				ht->val_free(n->val);
 			}
 			tmp = n;
 			n = n->next;
@@ -77,160 +32,196 @@
 	free(ht);
 }
 
-void
-htdestroy(Htable *ht)
+/*
+ * Insert a new node or, if the replace flag is true, replace the value of an existing node.
+ */
+static int
+insert_or_replace_val(struct htable *ht, void *key, void *val, int replace)
 {
-	__htdestroy(ht, 1);
-}
-
-void *
-htsearch(Htable *ht, void *key)
-{
 	unsigned int i;
-	Htnode *n;
+	struct htnode *n, *ln;	/* current and last node */
 
-	i = hash(ht, key);
+	i = ht->hash(key, ht->cap);
+	ln = NULL;
 	for (n = ht->nodes[i]; n != NULL; n = n->next) {
-		if (ht->keycmpfn(key, n->key) == 0)
-			return n->val;
-	}
-	return NULL;
-}
-
-int
-htinsert(Htable *ht, void *key, void *val)
-{
-	unsigned int i;
-	Htnode *n;	/* current node */
-	Htnode *pn;	/* previous node */
-
-	pn = NULL;
-	i = hash(ht, key);
-	for (n = ht->nodes[i]; n != NULL; n = n->next) {
-		/*if key already exist, override value */
-		if (ht->keycmpfn(key, n->key) == 0) {
-			ht->freekeyfn(n->key);
-			ht->freevalfn(n->val);
-			n->key = key;
-			n->val = val;
-			return 0;
+		/* if key already exist */
+		if (ht->key_cmp(key, n->key) == 0) {
+			if (replace) {
+				ht->val_free(n->val);
+				n->val = val;
+				return 0;
+			} else {
+				return -1;
+			}
 		}
-		pn = n;
+		ln = n;
 	}
 
-	/* create a new node */
-	if ((n = calloc(1, sizeof(Htnode))) == NULL) {
-		printf("error: calloc: %s\n", strerror(errno));
+	if (replace)	/* failed to replace */
 		return -1;
+
+	if ((n = malloc(sizeof(struct htnode))) == NULL) {
+		perror("error: malloc");
+		return -1;
 	}
 	n->key = key;
 	n->val = val;
 	n->next = NULL;
-	/* link to the previous node */
-	if (pn == NULL)
+	/* link to the last node */
+	if (ln == NULL)
 		ht->nodes[i] = n;
 	else
-		pn->next = n;
-	/* increment the hash table length */
+		ln->next = n;
 	ht->len++;
-	return 1;
+	return 0;
 }
 
+/*
+ * Remove a node or, if the replace flag is true, change the key of the node.
+ */
 static int
-__htremove(Htable *ht, void *key, int freeval)
+remove_or_replace_key(struct htable *ht, void *key, int replace, void *newkey)
 {
 	unsigned int i;
-	Htnode *n;	/* current node */
-	Htnode *pn;	/* previous node */
+	struct htnode *n, *ln;	/* current and last node */
 
-	pn = NULL;
-	i = hash(ht, key);
+	i = ht->hash(key, ht->cap);
+	ln = NULL;
 	for (n = ht->nodes[i]; n != NULL; n = n->next) {
-		if (ht->keycmpfn(key, n->key) == 0) {
-			/* free node memory */
-			ht->freekeyfn(n->key);
-			if (freeval)
-				ht->freevalfn(n->val);
-			/* link to the previous node */
-			if (pn == NULL)
+		if (ht->key_cmp(key, n->key) == 0) {
+			ht->key_free(n->key);
+			if (!replace) {
+				ht->val_free(n->val);
+			} else {
+				if (htinsert(ht, newkey, n->val) == -1)
+					return -1;
+			}
+			/* link to the last node */
+			if (ln == NULL)
 				ht->nodes[i] = n->next;
 			else
-				pn->next = n->next;
-			/* free the node */
+				ln->next = n->next;
 			free(n);
-			return 1;
+			return 0;
 		}
-		pn = n;
+		ln = n;
 	}
 	return -1;
 }
 
+struct htable *
+htcreate(hash_fn *hash, key_cmp_fn *key_cmp, key_free_fn *key_free,
+			val_free_fn *val_free, unsigned int cap)
+{
+	unsigned int i;
+	struct htable *ht;
+
+	if ((ht = malloc(sizeof(struct htable))) == NULL) {
+		perror("error: malloc");
+		return NULL;
+	}
+	if ((ht->nodes = malloc(cap * sizeof(struct htnode *))) == NULL) {
+		perror("error: malloc");
+		return NULL;
+	}
+	for (i = 0; i < cap; i++)
+		ht->nodes[i] = NULL;
+
+	ht->cap		= cap;
+	ht->len		= 0;
+	ht->hash	= hash;
+	ht->key_cmp	= key_cmp;
+	ht->key_free	= key_free;
+	ht->val_free	= val_free;
+	return ht;
+}
+
+void
+htdestroy(struct htable *ht)
+{
+	destroy_and_free(ht, 1);
+}
+
+void *
+htsearch(struct htable *ht, void *key)
+{
+	unsigned int i;
+	struct htnode *n;
+
+	i = ht->hash(key, ht->cap);
+	for (n = ht->nodes[i]; n != NULL; n = n->next) {
+		if (ht->key_cmp(key, n->key) == 0)
+			return n->val;
+	}
+	return NULL;
+}
+
 int
-htremove(Htable *ht, void *key)
+htinsert(struct htable *ht, void *key, void *val)
 {
-	return __htremove(ht, key, 1);
+	return insert_or_replace_val(ht, key, val, 0);
 }
 
 int
-htsetkey(Htable *ht, void *oldkey, void *newkey)
+htremove(struct htable *ht, void *key)
 {
-	void *val = NULL;
-	if ((val = htsearch(ht, oldkey)) == NULL)
-		return -1;
-	__htremove(ht, oldkey, 0);
-	htinsert(ht, newkey, val);
-	return 1;
+	return remove_or_replace_key(ht, key, 0, NULL);
 }
 
-void
-htresize(Htable *ht, unsigned int ncap)
+int
+htmodkey(struct htable *ht, void *oldkey, void *newkey)
 {
-	unsigned int i, tmp;
-	Htable *nht;	/* new hash table */
-	Htnode *n;	/* current node */
-	Htnode **list;	/* list of nodes */
+	return remove_or_replace_key(ht, oldkey, 1, newkey);
+}
 
-	/* create a new hash table */
-	nht = htcreate(ht->keylenfn, ht->keycmpfn, ht->freekeyfn, ht->freevalfn, ncap);
-	for (i = 0; i < ht->cap; i++) {
-		for (n = ht->nodes[i]; n != NULL; n = n->next)
-			htinsert(nht, n->key, n->val);
-	}
+int
+htmodval(struct htable *ht, void *key, void *newval)
+{
+	return insert_or_replace_val(ht, key, newval, 1);
+}
 
-	/* swap old hash table with new one */
-	list = ht->nodes;
-	ht->nodes = nht->nodes;
-	nht->nodes = list;
+struct htable *
+htresize(struct htable *ht, unsigned int newcap)
+{
+	struct htable	 *newht;
+	struct htiter	  it;
 
-	tmp = ht->cap;
-	ht->cap = nht->cap;
-	nht->cap = tmp;
+	newht = htcreate(ht->hash, ht->key_cmp, ht->key_free, ht->val_free, newcap);
+	if (newht == NULL)
+		return NULL;
 
-	tmp = ht->len;
-	ht->len = nht->len;
-	nht->len = tmp;
+	htiter_init(&it);
+	while(htiterate(ht, &it)) {
+		if (htinsert(newht, it.node->key, it.node->val) == -1) {
+			htdestroy(newht);
+			return NULL;
+		}
+	}
 
-	/* destroy new(old) hash table */
-	__htdestroy(nht, 0);
+	destroy_and_free(ht, 0);
+	return newht;
 }
 
+void
+htiter_init(struct htiter *it)
+{
+	it->index = 0;
+	it->node = NULL;
+}
+
 int
-htiterate(Htable *ht, Htiter *it)
+htiterate(struct htable *ht, struct htiter *it)
 {
-	if (it->index == 0 && it->node == NULL) {
-		it->index = 0;
-		it->node = ht->nodes[0];
-	} else {
+	if (it->node != NULL)
 		it->node = it->node->next;
-	}
 
-	while (it->index < ht->cap) {
-		while (it->node != NULL) {
-			return 1;
-			it->node = it->node->next;
-		}
-		it->index++;
+	while (it->node == NULL && it->index < ht->cap) {
 		it->node = ht->nodes[it->index];
+		it->index++;
 	}
-	return 0;
+
+	if (it->node != NULL)
+		return 1;
+	else
+		return 0;
 }
--- a/htable.h
+++ b/htable.h
@@ -3,41 +3,48 @@
  * See COPYING file for more information.
  */
 
-typedef struct Htnode Htnode;
-typedef struct Htable Htable;
-typedef struct Htiter Htiter;
+#ifndef HTABLE_H
+#define HTABLE_H
 
-typedef size_t	(KeyLenFn)(const void *key);
-typedef int	(KeyCmpFn)(const void *key1, const void *key2);
-typedef void	(FreeKeyFn)(void *ptr);
-typedef void	(FreeValFn)(void *ptr);
+typedef unsigned int	(hash_fn)(void *key, unsigned int cap);
+typedef int		(key_cmp_fn)(const void *key1, const void *key2);
+typedef void		(key_free_fn)(void *key);
+typedef void		(val_free_fn)(void *val);
 
-struct Htnode {
-	void	*key;		/* key for the node */
-	void	*val;		/* value for the node */
-	Htnode	*next;		/* next node (open hash table) */
+struct htnode {
+	void		*key;
+	void		*val;
+	struct htnode	*next;
 };
 
-struct Htable {
-	unsigned int	  cap;		/* capacity */
-	unsigned int	  len;		/* length */
-	Htnode		**nodes;	/* list of nodes */
-	KeyLenFn	 *keylenfn;	/* key length function */
-	KeyCmpFn	 *keycmpfn;	/* key compare function */
-	FreeKeyFn	 *freekeyfn;
-	FreeValFn	 *freevalfn;
+struct htable {
+	struct htnode	**nodes;
+	unsigned int	  cap;
+	unsigned int	  len;
+	hash_fn		 *hash;
+	key_cmp_fn	 *key_cmp;
+	key_free_fn	 *key_free;
+	val_free_fn	 *val_free;
 };
 
-struct Htiter {
+struct htiter {
 	unsigned int	 index;
-	Htnode		*node;
+	struct htnode	*node;
 };
 
-Htable	*htcreate(KeyLenFn *keylenfn, KeyCmpFn *keycmpfn, FreeKeyFn *freekeyfn, FreeValFn *freevalfn, unsigned int cap);
-void	 htdestroy(Htable *ht);
-void	*htsearch(Htable *ht, void *key);
-int	 htinsert(Htable *ht, void *key, void *val);
-int 	 htremove(Htable *ht, void *key);
-int	 htsetkey(Htable *ht, void *oldkey, void *newkey);
-void 	 htresize(Htable *ht, unsigned int ncap);
-int	 htiterate(Htable *ht, Htiter *it);
+struct htable	*htcreate(hash_fn *hash,
+			key_cmp_fn *key_cmp,
+			key_free_fn *key_free,
+			val_free_fn *val_free,
+			unsigned int cap);
+void		 htdestroy(struct htable *ht);
+void		*htsearch(struct htable *ht, void *key);
+int		 htinsert(struct htable *ht, void *key, void *val);
+int		 htremove(struct htable *ht, void *key);
+int		 htmodkey(struct htable *ht, void *oldkey, void *newkey);
+int		 htmodval(struct htable *ht, void *key, void *newval);
+struct htable	*htresize(struct htable *ht, unsigned int newcap);
+void		 htiter_init(struct htiter *it);
+int		 htiterate(struct htable *ht, struct htiter *it);
+
+#endif /* HTABLE_H */
--- a/main.c
+++ b/main.c
@@ -14,107 +14,91 @@
 
 #ifdef __gnu_linux__
 #include <sys/epoll.h>
-#include <bsd/err.h>
-#include <bsd/stdlib.h>
-#include <bsd/string.h>
+char *strlcpy(char *dst, const char *src, size_t n)
+{
+	return strncpy(dst, src, n);
+}
 #else
 #include <sys/event.h>
 #endif
 
+#define LOG_LEVEL	1
+
 #include "htable.h"
 #include "util.c"
 
 #define CLONE_COOLDOWN	1
 #define CLONE_ADDEND	10
-/* #define PING_TIMEOUT	240 */
 
-#define EVENT_ADDEND	100
+#define FD_ADDEND	100
 #define NET_ADDEND	10
 #define USER_ADDEND	100
 
 #define BUFSIZE		1024
 #define NICK_LEN	16
-#define MAX_EVENTS	10
+#define HANDLE_EVENTS	10	/* no. of events to handle at a time */
 
-#define EV_READ 1
-#define EV_WRITE 2
+#define EV_READ		1
+#define EV_WRITE	2
 
 enum {
 	IDLE = 0,
 	RESET,
-	CLONING
+	CLONING,
+	DONE
 };
 
-struct network {
-	int	 id;	/* event index	*/
-	char	*name;	/* name		*/
-	char	*symb;	/* symbol	*/
-	char	*host;	/* host		*/
-	char	*port;	/* port		*/
-	char	*chan;	/* channel	*/
-	int	 ready;	/* joined	*/
+struct vuser {
+	char		*user;	/* nick		*/
+	int		netid;	/* net index	*/
+	int	 	suffix;	/* suffix count	*/
+	int	 	ready;	/* joined	*/
 };
 
-struct event {
-	int	 fd;		/* event fd	*/
-	int	 netid;		/* net index	*/
-	char	*user;		/* user nick	*/
-	int	 suffix;	/* suffix count	*/
-	time_t	 time;		/* last response */
-	int	 ready;		/* joined	*/
+struct network {
+	int		 fd;	/* fd		*/
+	char		*name;	/* name		*/
+	char		*symb;	/* symbol	*/
+	char		*host;	/* host		*/
+	char		*port;	/* port		*/
+	char		*chan;	/* channel	*/
 };
 
+static time_t		 ntime = -1;	/* next timeout		*/
+static int		 state = IDLE;	/* program state 	*/
+static int		 fifofd;	/* fifo fd		*/
+static int		 break_evloop;	/* break event loop	*/
+static char		*fifopath;	/* fifo path		*/
+static char		 msg[BUFSIZE];	/* message buffer	*/
 
-static int	 debug;
-static int	 done;			/* program state  */
-static int	 fifofd;		/* fifo fd	  */
-static char	*fifopath;		/* fifo path	  */
-static char	 msg [BUFSIZE];		/* message buffer */
-static int	 timeout_id = -1;
-
 #ifdef __gnu_linux__
-static int	 epfd;			/* epoll instance  */
+static int		 epfd;		/* epoll instance	*/
 #else
-static int	 kqfd;			/* kqueue instance */
+static int		 kqfd;		/* kqueue instance	*/
 #endif
 
-static int	*fdtoid;		/* fd to event id  */
-static int	 fdslen;		/* maximum fd	   */
+static struct network	 *networks;	/* networks array	*/
+static int		  netlen;	/* array length		*/
+static int		  netcap;	/* array capacity	*/
+static struct vuser	**fdtovuser;	/* fd -> vuser	pointer	*/
+static int		  fdcap;	/* fdtovuser capacity	*/
+static struct htable	 *usertofds;	/* user -> array of fds of clones
+					indexed according to networks array */
 
-static struct event	*events;	/* events array	   */
-static int		 evslen;	/* array length	   */
-static int		 evscap;	/* array capacity  */
-
-static struct network	*networks;	/* networks array  */
-static int		 netlen;	/* array length	   */
-static int		 netcap;	/* array capacity  */
-static int		 state = IDLE;
-
-/*
- * hash table of users
- * key   -> <user_nick> + '[' + <network_symbol> + ']'
- * value -> array of all user's clones event ids indexed
- *	    corresponding to its connected network
- */
-static struct Htable	*users;
-
 /* functions prototype */
-
 void	 fd_register(int, int);
 void	 fifo_read(void);
 time_t	 event_timeout(void);
-void	 event_write(int);
-int	 event_read(int);
+void	 fd_write(int);
+void	 fd_read(int);
 void	 net_add(char *, char *, char *, char *, char *);
-void	 net_del(char *);
+void	 net_del(int, char *);
 void	 net_del_raw(int);
-void	 net_users_add(int, char *);
 void	 user_add(char *, int, int);
 void	 user_del(char *, char *);
-int	*user_event_ids(char *, int);
-int	 clone_add(char *, int);
-int	 event_add(int, int, char *);
-void	 event_del(int);
+int	 vuser_add(int, char *);
+void	 vuser_del(int, char *);
+int	*vuser_get_fds(char *, int);
 void	 nick_add_symb(char *, int);
 void	 privmsg_update(char *, char *, int);
 void	 print_table(void);
@@ -126,76 +110,67 @@
 main(int argc, char *argv[])
 {
 #ifdef __gnu_linux__
-	struct epoll_event epevs [MAX_EVENTS];	/* maximum event to handle */
+	struct epoll_event	epevs[HANDLE_EVENTS];
 #else
-	struct kevent	kevs [MAX_EVENTS];	/* maximum event to handle */
-	struct timespec	tmsp;
+	struct kevent		kevs[HANDLE_EVENTS];
+	struct timespec		tmsp;
 #endif
-	int	i, r, fd, id, nev;
+	int	i, fd, *fds, nev;	/* no. of returned events to handle */
 	time_t	timeout;
-	time_t	ntime;		/* next timeout time */
+	struct htiter it;
 
 	/* set stdout to unbufferd */
 	setvbuf(stdout, NULL, _IONBF, 0);
 
-	/* check arguments */
-	if (argc != 2) {
-		printf("usage: %s fifo\n", getprogname());
-		return 0;
-	} else {
+	/* handle arguments */
+	if (argc != 2)
+		fatal("usage: ticl fifo");
+	else
 		fifopath = argv[1];
-	}
 
 	/* init global variables */
-	fdslen = evscap = EVENT_ADDEND;
 	netcap = NET_ADDEND;
-	fdtoid = ecalloc((size_t)fdslen, sizeof(int));
-	events = ecalloc((size_t)evscap, sizeof(struct event));
-	networks = ecalloc((size_t)netcap, sizeof(struct network));
-	users = htcreate((KeyLenFn *)strlen, (KeyCmpFn *)strcmp, free, free,
-			 USER_ADDEND);
+	fdcap = FD_ADDEND;
+	networks = emalloc((size_t)netcap * sizeof(struct network));
+	fdtovuser = emalloc((size_t)fdcap * sizeof(struct vuser *));
+	usertofds = htcreate(hash_str, (key_cmp_fn *)strcmp, free, free,
+			USER_ADDEND);
 
 #ifdef __gnu_linux__
 	if ((epfd = epoll_create1(0)) == -1)
-		err(1, "epoll_create1");
+		fatal("epoll_create1:");
 #else
 	if ((kqfd = kqueue()) == -1)
-		err(1, "kqueue");
+		fatal("kqueue:");
 #endif
 	fifofd = fifo_open(fifopath);
 	fd_register(fifofd, EV_READ);
 
-	ntime = -1;
 	/* event loop */
-	while (!done) {
-		if (ntime == -1)
+	while (state != DONE) {
+		if (ntime == -1) {
 			timeout = -1;
-		else if ((timeout = ntime - time(NULL)) < 0)
-			timeout = 0;
+		} else {
+			timeout = ntime - time(NULL);
+			if (timeout < 0)
+				timeout = 0;
+		}
 #ifdef __gnu_linux__
-		if ((nev = epoll_wait(epfd, epevs, MAX_EVENTS,
-				      (int)timeout * 1000)) == -1)
-			err(1, "epoll_wait");
+		nev = epoll_wait(epfd, epevs, HANDLE_EVENTS, (int)timeout * 1000);
+		if (nev == -1)
+			fatal("epoll_wait:");
 #else
 		tmsp.tv_sec = timeout;
 		tmsp.tv_nsec = 0;
-		if ((nev = kevent(kqfd, NULL, 0, kevs, MAX_EVENTS,
-				  timeout < 0 ? NULL : &tmsp)) == -1)
-			err(1, "kevent");
+		nev = kevent(kqfd, NULL, 0, kevs, HANDLE_EVENTS, timeout < 0 ? NULL : &tmsp);
+		if (nev == -1)
+			fatal("kevent:");
 #endif
-		if (nev == 0) {
+		else if (nev == 0) {
 			ntime = event_timeout();
 			continue;
 		}
 
-		if (debug) {
-			printf("fds:");
-			for (i = 0; i < nev; i++) {
-				printf(" %d", epevs[i].data.fd);
-			}
-			printf("\n");
-		}
-
 		for (i = 0; i < nev; i++) {
 #ifdef __gnu_linux__
 			fd = epevs[i].data.fd;
@@ -202,94 +177,93 @@
 #else
 			fd = (int)kevs[i].ident;
 #endif
-			id = fdtoid[fd];
 			if (fd == fifofd) {
 				fifo_read();
+				break;
 #ifdef __gnu_linux__
 			} else if (epevs[i].events & EPOLLOUT) {
 #else
 			} else if (kevs[i].filter == EVFILT_WRITE) {
 #endif
-				event_write(id);
+				fd_write(fd);
 #ifdef __gnu_linux__
 			} else if (epevs[i].events & EPOLLIN) {
 #else
 			} else if (kevs[i].filter == EVFILT_READ) {
 #endif
-				r = event_read(id);
-				if (r == 1) {
-					timeout_id = -1;
-					ntime = 0;
-				} else if (r == -2) {
+				fd_read(fd);
+				if (break_evloop) {
+					break_evloop = FALSE;
 					break;
 				}
 			} else {
-				errx(1, "unknown event");
+				fatal("unknown event");
 			}
 		}
 	}
 
-	/*
-	 * cleanup
-	 */
-	snprintf(msg, sizeof(msg), "QUIT :linker shutting down\r\n");
-	for (i = 0; i < evslen; i++) {
-		writeall(events[i].fd, msg);
-		close(events[i].fd);
+	/* cleanup */
+	snprintf(msg, sizeof(msg), "QUIT :relay shutting down\r\n");
+	
+	/* delete and free all the users with their fds */
+	htiter_init(&it);
+	while(htiterate(usertofds, &it)) {
+		fds = (int *)it.node->val;
+		for (i = 0; i < netlen; i++) {
+			if (fds[i] > 0)
+				vuser_del(fds[i], msg);
+		}
 	}
+	htdestroy(usertofds);
 
-	free(fdtoid);
-	free(events);
-
-	/* delete all the networks */
-	for (i = 0; i < netlen; i++)
+	/* delete and free all the networks with their fd */
+	for (i = 0; i < netlen; i++) {
+		vuser_del(networks[i].fd, msg);
 		net_del_raw(i);
+	}
 	free(networks);
 
-	/* delete all the users */
-	htdestroy(users);
-
+	free(fdtovuser);
 	return 0;
 }
 
 void
-fd_register(int fd, int type)
+fd_register(int fd, int mode)
 {
 #ifdef __gnu_linux__
 	struct epoll_event ev;
 
-	if (type == EV_READ)
+	if (mode == EV_READ)
 		ev.events = EPOLLIN;
-	else if (type == EV_WRITE)
+	else if (mode == EV_WRITE)
 		ev.events = EPOLLOUT;
 	else
-		errx(1, "unkown event type");
+		fatal("unknown event mode");
 
 	ev.data.fd = fd;
 	if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) == -1)
-		err(1, "epoll_ctl");
+		fatal("epoll_ctl:");
 #else
 	struct kevent ev;
 	int filter;
 
-	if (type == EV_READ)
+	if (mode == EV_READ)
 		filter = EVFILT_READ;
-	else if (type == EV_WRITE)
+	else if (mode == EV_WRITE)
 		filter = EVFILT_WRITE;
 	else
-		errx(1, "unkown event type");
+		fatal("unknown event mode");
 
 	EV_SET(&ev, fd, filter, EV_ADD, 0, 0, 0);
 	if (kevent(kqfd, &ev, 1, NULL, 0, NULL) == -1)
-		err(1, "kevent");
+		fatal("kevent:");
 #endif
-	/* printf("added fd: %d\n", fd); */
 }
 
 void
 fifo_read(void)
 {
-	char	 buffer [BUFSIZE];
+	char	 buffer[BUFSIZE];
 	char	*buf;
 	char	*cmd;
 	ssize_t	 n;
@@ -296,7 +270,7 @@
 
 	n = readline(fifofd, buffer, sizeof(buffer));
 	if (n == -1) {
-		err(1, "fifo: read");
+		fatal("read:");
 	} else if (n == 0) {
 		/* reopen fifo again */
 		close(fifofd);
@@ -304,8 +278,7 @@
 		fd_register(fifofd, EV_READ);
 		return;
 	}
-
-	if (!*buffer)
+	if (*buffer == '\0')
 		return;
 
 	buf = buffer;
@@ -317,15 +290,24 @@
 		char *port = split(&buf, ' ');
 		char *chan = buf;
 		if (!*name || !*symb || !*host || !*port || !*chan)
-			printf("usage: netadd <name> <symbol> <host> <port> <channel>\n");
+			warnf("usage: netadd <name> <symbol> <host> <port> <channel>");
 		else
 			net_add(name, symb, host, port, chan);
 	} else if (strcmp(cmd, "netdel") == 0) {
 		char *name = buf;
-		if (!*name)
-			printf("usage: netdel <name>\n");
-		else
-			net_del(name);
+		if (!*name) {
+			warnf("usage: netdel <name>");
+		} else {
+			int i;
+			for (i = 0; i < netlen; i++) {
+				if (strcmp(name, networks[i].name) == 0) {
+					snprintf(msg, sizeof(msg), "QUIT :unlinking %s\r\n", networks[i].name);
+					net_del(i, msg);
+					return;
+				}
+			}
+			warnf("%s: network doesn't exist", name);
+		}
 	} else if (strcmp(cmd, "print") == 0) {
 		print_table();
 	} else if (strcmp(cmd, "htable") == 0) {
@@ -333,9 +315,9 @@
 	} else if (strcmp(cmd, "users") == 0) {
 		print_users();
 	} else if (strcmp(cmd, "exit") == 0) {
-		done = TRUE;
+		state = DONE;
 	} else {
-		warnx("%s is not a command", cmd);
+		warnf("%s is not a command", cmd);
 	}
 }
 
@@ -342,95 +324,64 @@
 time_t
 event_timeout(void)
 {
-	static Htiter it = {0};
+	static struct htiter it;
 
-	int i, j, *ids;
-	char *nick;
-	struct network *n;
-	time_t ntime;
+	int i, j, *fds;
+	char *user;
 
-	/* if (timeout_id != -1) {
-		printf("%d: PING\n", events[timeout_id].fd);
-		snprintf(msg, sizeof(msg), "PING %s\r\n",
-			 networks[events[timeout_id].netid].host);
-		writeall(events[timeout_id].fd, msg);
-		events[timeout_id].time = time(NULL);
-	} else { */
-		if (state == RESET) {
-			state = CLONING;
-			it.index = 0;
-			it.node = NULL;
-		}
-		printf("Adding %d bot\n", CLONE_ADDEND);
+	if (state == RESET) {
+		state = CLONING;
+		htiter_init(&it);
+	}
+	log1(".");
 
-		j = 0;
-		while (htiterate(users, &it)) {
-			nick = (char *)it.node->key;
-			ids = (int *)it.node->val;
-			for (i = 0; i < netlen; i++) {
-				n = &networks[i];
-				if (!n->ready || ids[i] != 0)
-					continue;
-				ids[i] = clone_add(nick, i);
-			}
-			if (++j >= CLONE_ADDEND)
-				goto calculate;
+	j = 0;
+	while (htiterate(usertofds, &it)) {
+		user = (char *)it.node->key;
+		fds = (int *)it.node->val;
+		for (i = 0; i < netlen; i++) {
+			if (fds[i] == 0 && fdtovuser[networks[i].fd]->ready)
+				fds[i] = vuser_add(i, user);
 		}
-		state = IDLE;
-		it.index = 0;
-		it.node = NULL;
-	/* } */
-
-calculate:
-	ntime = -1;
-	timeout_id = -1;
-
-	if (state == CLONING)
-		ntime = time(NULL) + CLONE_COOLDOWN;
-
-	/* for(i = 0; i < evslen; i++) {
-		if ((events[i].time != 0)
-		    && ((ntime == -1) || (events[i].time + PING_TIMEOUT < ntime))) {
-			ntime = events[i].time + PING_TIMEOUT;
-			timeout_id = i;
-		}
-	} */
-	return ntime;
+		j++;
+		if (j >= CLONE_ADDEND)
+			return time(NULL) + CLONE_COOLDOWN;
+	}
+	state = IDLE;
+	return -1;
 }
 
 void
-event_write(int id)
+fd_write(int fd)
 {
-	int fd = events[id].fd;
 #ifdef __gnu_linux__
 	struct epoll_event ev;
 	ev.events = EPOLLIN;
 	ev.data.fd = fd;
 	if (epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev) == -1)
-		err(1, "epoll_ctl");
+		fatal("epoll_ctl:");
 #else
 	struct kevent ev;
 	EV_SET(&ev, fd, EV_WRITE, EV_DISABLE, 0, 0, 0);
 	if (kevent(kqfd, &ev, 1, NULL, 0, NULL) == -1)
-		err(1, "kevent");
+		fatal("kevent:");
 	fd_register(fd, EV_READ);
 #endif
-	if (events[id].user == NULL) {	/* linker */
+	if (fdtovuser[fd]->user == NULL) {	/* linker */
 		snprintf(msg, sizeof(msg), "NICK linker\r\n");
 		writeall(fd, msg);
 		snprintf(msg, sizeof(msg), "USER linker 0 * :linker\r\n");
 		writeall(fd, msg);
 	} else {			/* user */
-		snprintf(msg, sizeof(msg), "NICK %s\r\n", events[id].user);
+		snprintf(msg, sizeof(msg), "NICK %s\r\n", fdtovuser[fd]->user);
 		writeall(fd, msg);
 		snprintf(msg, sizeof(msg), "USER user 0 * :user\r\n");
 		writeall(fd, msg);
 	}
-	events[id].time = time(NULL);
 }
 
-int
-event_read(int id)
+void
+fd_read(int fd)
 {
 	char	 buffer	[BUFSIZE];
 	char	 backup	[BUFSIZE];
@@ -438,47 +389,43 @@
 	char	*buf;
 	char	*cmd;
 	char	*nick;
-	int	 i, fd, netid;
+	int	 i, netid;
 	ssize_t	 n;
 
-	fd = events[id].fd;
-	netid = events[id].netid;
-	events[id].time = time(NULL);
+	netid = fdtovuser[fd]->netid;
 
 	n = readline(fd, buffer, sizeof(buffer));
 	if (n == -1) {
-		warn("%d: read", fd);
-		event_del(id);
-		return 0;
+		warnf("%d: read:", fd);
+		snprintf(msg, sizeof(msg), "QUIT :read failed\r\n");
+		goto del;
 	} else if (n == 0) {
-		warnx("%d: connection closed", fd);
-		event_del(id);
-		return 0;
+		warnf("%d: connection closed", fd);
+		snprintf(msg, sizeof(msg), "QUIT :connection closed\r\n");
+		goto del;
 	}
+	if (*buffer == '\0')
+		return;
 
-	if (!*buffer)
-		return 0;
-
 	/* clone the buffer */
-	strlcpy(backup, buffer, sizeof(buffer));
+	strlcpy(backup, buffer, sizeof(backup));
 	buf = buffer;
 
 	/* set linker nick */
 	strlcpy(lnick, "linker", sizeof(lnick));
-	for (i = 0; i < events[id].suffix; i++)
+	for (i = 0; i < fdtovuser[fd]->suffix; i++)
 		strcat(lnick, "_");
 
 	/* first column */
 	cmd = split(&buf, ' ');
-	if (strcmp(cmd, "NOTICE") == 0) {
-		return 0;
+	if (strcmp(cmd, "NOTICE") == 0) {	/* ignore */
+		return;
 	} else if (strcmp(cmd, "ERROR") == 0) {
-		event_del(id);
-		goto printbuffer;
+		fatal("%d: %s", fd, backup);
 	} else if (strcmp(cmd, "PING") == 0) {
 		snprintf(msg, sizeof(msg), "PONG %s\r\n", buf);
 		writeall(fd, msg);
-		return 0;
+		return;
 	}
 	/* strip nick from first column */
 	nick = split(&cmd, '!');
@@ -489,70 +436,65 @@
 	cmd = split(&buf, ' ');
 
 	/* ignore all the info messages */
-	if ((strcmp(cmd, "002") == 0)
-	    || (strcmp(cmd, "003") == 0)
-	    || (strcmp(cmd, "004") == 0)
-	    || (strcmp(cmd, "005") == 0)
-	    || (strcmp(cmd, "003") == 0)
-	    || (strcmp(cmd, "251") == 0)
-	    || (strcmp(cmd, "252") == 0)
-	    || (strcmp(cmd, "253") == 0)	/* unknown connection(s) */
-	    || (strcmp(cmd, "254") == 0)
-	    || (strcmp(cmd, "255") == 0)
-	    || (strcmp(cmd, "265") == 0)
-	    || (strcmp(cmd, "266") == 0)
-	    || (strcmp(cmd, "250") == 0)
-	    || (strcmp(cmd, "332") == 0)
-	    || (strcmp(cmd, "333") == 0)
-	    || (strcmp(cmd, "375") == 0)
-	    || (strcmp(cmd, "372") == 0)
-	    || (strcmp(cmd, "376") == 0)
-	    || (strcmp(cmd, "396") == 0)
-	    || (strcmp(cmd, "366") == 0)
-	    || (strcmp(cmd, "MODE") == 0)
-	    || (strcmp(cmd, "TOPIC") == 0)
-	    || (strcmp(cmd, "NOTICE") == 0)) {
-		return 0;
+	if ((strcmp(cmd, "002") == 0) ||
+	    (strcmp(cmd, "003") == 0) ||
+	    (strcmp(cmd, "004") == 0) ||
+	    (strcmp(cmd, "005") == 0) ||
+	    (strcmp(cmd, "003") == 0) ||
+	    (strcmp(cmd, "251") == 0) ||
+	    (strcmp(cmd, "252") == 0) ||
+	    (strcmp(cmd, "253") == 0) || /* unknown connection(s) */
+	    (strcmp(cmd, "254") == 0) ||
+	    (strcmp(cmd, "255") == 0) ||
+	    (strcmp(cmd, "265") == 0) ||
+	    (strcmp(cmd, "266") == 0) ||
+	    (strcmp(cmd, "250") == 0) ||
+	    (strcmp(cmd, "332") == 0) ||
+	    (strcmp(cmd, "333") == 0) ||
+	    (strcmp(cmd, "375") == 0) ||
+	    (strcmp(cmd, "372") == 0) ||
+	    (strcmp(cmd, "376") == 0) ||
+	    (strcmp(cmd, "396") == 0) ||
+	    (strcmp(cmd, "366") == 0) ||
+	    (strcmp(cmd, "MODE") == 0) ||
+	    (strcmp(cmd, "TOPIC") == 0) ||
+	    (strcmp(cmd, "NOTICE") == 0)) {
+		return;
 	} else if (strcmp(cmd, "432") == 0) {	/* Erroneous Nickname */
-		event_del(id);
-		goto printbuffer;
+		fatal("%d: %s", fd, backup);
 	} else if (strcmp(cmd, "433") == 0) {	/* Nickname already in use */
 		split(&buf, ' ');
 		nick = split(&buf, ' ');
 		strcat(nick, "_");
-		events[id].suffix++;
+		fdtovuser[fd]->suffix++;
 		if (strlen(nick) > NICK_LEN) {
-			warnx("nick '%s' is too big", nick);
-			if (strcmp(nick, lnick) == 0) {
-				net_del(networks[netid].name);
-			} else {
-				snprintf(msg, sizeof(msg), "QUIT :nick is too big\r\n");
-				user_del(nick, msg);
-			}
+			warnf("%s: nickname too long", nick);
+			snprintf(msg, sizeof(msg), "QUIT :%s: nickname too long\r\n", nick);
+			goto del;
 		} else {
 			snprintf(msg, sizeof(msg), "NICK %s\r\n", nick);
 			writeall(fd, msg);
 		}
-		return 0;
+		return;
 	} else if (strcmp(cmd, "001") == 0) {
 		snprintf(msg, sizeof(msg), "JOIN %s\r\n", networks[netid].chan);
 		writeall(fd, msg);
-		return 0;
+		return;
 	} else if (strcmp(cmd, "PRIVMSG") == 0) {
-		char privmsg [BUFSIZE] = "";
-		int *ids;
+		char privmsg[BUFSIZE] = "";
+		int *fds;
 
-		if (events[id].user == NULL) {	/* if linker */
+		if (fdtovuser[fd]->user == NULL) {	/* if linker */
 			nick_add_symb(nick, netid);
-			if ((ids = htsearch(users, nick)) == NULL)
-				return 0;
+			if ((fds = htsearch(usertofds, nick)) == NULL)
+				return;
 
 			split(&buf, ':');	/* set buf to msg */
 			privmsg_update(privmsg, buf, netid);
 			for (i = 0; i < netlen; i++) {
-				if (ids[i] > 0) {
+				if (fds[i] > 0) {
 					snprintf(msg, sizeof(msg), "PRIVMSG %s :%s\r\n", networks[i].chan, privmsg);
-					writeall(events[ids[i]].fd, msg);
+					writeall(fds[i], msg);
 				}
 			}
 		} else {
@@ -560,12 +502,12 @@
 			char *user = split(&buf, ' ');
 
 			/* ignore messages from channel (it is handled by linker) */
-			if ((user[0] == '#') || (user[0] == '&'))
-				return 0;
+			if (user[0] == '#' || user[0] == '&')
+				return;
 
 			nick_add_symb(nick, netid);
-			if ((ids = htsearch(users, nick)) == NULL)
-				return 0;
+			if ((fds = htsearch(usertofds, nick)) == NULL)
+				return;
 
 			/* split user nick and network symbol */
 			*strrchr(user, ']') = '\0';
@@ -581,127 +523,137 @@
 			split(&buf, ':');	/* set buf to msg */
 			privmsg_update(privmsg, buf, netid);
 			snprintf(msg, sizeof(msg), "PRIVMSG %s :%s\r\n", user, privmsg);
-			writeall(events[ids[i]].fd, msg);
+			writeall(fds[i], msg);
 		}
-		return 0;
+		return;
 	}
+	else if (strcmp(cmd, "353") == 0) {
+		fdtovuser[fd]->ready = TRUE;
+		/* FALLBACK */
+	}
 	/* these messages are handled by linker */
-	if (events[id].user != NULL) {	/* if clone */
-		if (strcmp(cmd, "353") == 0) {
-			events[id].ready = TRUE;
-			return 0;
-		} else if ((strcmp(cmd, "JOIN") == 0)
-		    || (strcmp(cmd, "QUIT") == 0)
-		    || (strcmp(cmd, "PART") == 0)
-		    || (strcmp(cmd, "KICK") == 0)
-		    || (strcmp(cmd, "NICK") == 0)) {
-			return 0;
+	if (fdtovuser[fd]->user != NULL) {	/* if clone */
+		if ((strcmp(cmd, "353") == 0) ||
+		    (strcmp(cmd, "JOIN") == 0) ||
+		    (strcmp(cmd, "QUIT") == 0) ||
+		    (strcmp(cmd, "PART") == 0) ||
+		    (strcmp(cmd, "KICK") == 0) ||
+		    (strcmp(cmd, "NICK") == 0)) {
+			return;
 		}
 	} else if (strcmp(cmd, "353") == 0) {
 		char *nick;
 		split(&buf, ':');
-		networks[netid].ready = TRUE;
 		state = RESET;
+		ntime = 0;
 
 		/* then add all new users */
 		while (*(nick = split(&buf, ' ')) != '\0') {
-			if (*nick == '@'
-			    || *nick == '&'
-			    || *nick == '~'
-			    || *nick == '%'
-			    || *nick == '+'
-			    || *nick == '\\')
+			if (*nick == '@' ||
+			    *nick == '&' ||
+			    *nick == '~' ||
+			    *nick == '%' ||
+			    *nick == '+' ||
+			    *nick == '\\')
 				nick++;
 			if (strcmp(nick, lnick) != 0)
 				user_add(nick, netid, FALSE);
 		}
-		return 1;
+		return;
 	} else if (strcmp(cmd, "JOIN") == 0) {
-		if ((strcmp(nick, lnick) != 0)
-		    && (user_event_ids(nick, netid) == NULL)) {	/* if not clone */
+		/* if real user */
+		if ((strcmp(nick, lnick) != 0) &&
+		    (vuser_get_fds(nick, netid) == NULL)) {
 			if (state != IDLE)
-				warnx("ignoring user '%s' due to network cloning", nick);
+				warnf("%s: ignored (network cloning)", nick);
 			else
 				user_add(nick, netid, TRUE);
 		}
-		return 0;
-	} else if ((strcmp(cmd, "QUIT") == 0)
-		   || (strcmp(cmd, "PART") == 0)) {
+		return;
+	} else if ((strcmp(cmd, "QUIT") == 0) || (strcmp(cmd, "PART") == 0)) {
+		split(&buf, ':'); /* ignore ':' and assign QUIT/PART msg to buf */
 		snprintf(msg, sizeof(msg), "QUIT :%s\r\n", buf);
 		nick_add_symb(nick, netid);
-		if (htsearch(users, nick) != NULL)
+		if (htsearch(usertofds, nick) != NULL) {
 			user_del(nick, msg);
-		return -2;
+			break_evloop = TRUE;
+			return;
+		}
+		return;
 	} else if (strcmp(cmd, "NICK") == 0) {
-		int *ids, i;
+		int *fds, i;
 		char *newnick;
 
 		nick_add_symb(nick, netid);
-		if ((ids = htsearch(users, nick)) == NULL)
-			return 0;
+		if ((fds = htsearch(usertofds, nick)) == NULL)
+			return;
 
 		/* set buf to new nick */
 		split(&buf, ':');
-		/* allocate a newnick and append the netsym and then replace the old */
-		newnick = ecalloc(strlen(buf) + strlen(networks[netid].symb) + 2 + 1, sizeof(char));
+		/* allocate a new nick with net symbol and replace the old one */
+		newnick = emalloc((strlen(buf) + strlen(networks[netid].symb) + 2 + 1) * sizeof(char));
 		sprintf(newnick, "%s[%s]", buf, networks[netid].symb);
-		htsetkey(users, nick, newnick);
-
+		htmodkey(usertofds, nick, newnick);
 		snprintf(msg, sizeof(msg), "NICK %s\r\n", newnick);
 		for (i = 0; i < netlen; i++) {
-			events[ids[i]].user = newnick;
-			if (ids[i] > 0)
-				writeall(events[ids[i]].fd, msg);
+			if (fds[i] > 0) {
+				fdtovuser[fds[i]]->user = newnick;
+				writeall(fds[i], msg);
+			}
 		}
-		return 0;
+		return;
 	} else if (strcmp(cmd, "KICK") == 0) {
-		/* :<nick_which_is_kicking>!~user@host KICK <channel> <nick_which_has_been_kicked> :<kick_msg> */
-		int *ids;
-		char *chan = split(&buf, ' ');
-		char *user = split(&buf, ' ');
+		/* :<user_who_kicked>!~username@host KICK <channel> <user_who_is_being_kicked> :<kick_msg> */
+		int *fds;
+		char *user;
 
-		/* set the quit msg */
-		snprintf(msg, sizeof(msg), "QUIT : kicked by %s\r\n", nick);
+		split(&buf, ' ');		/* channel name				*/
+		user = split(&buf, ' ');	/* user who is being kicked		*/
+		split(&buf, ':');		/* ignore ':' and store reason to buf	*/
 
-		/* delete whole network if it is the linker */
+		/* if linker is being kicked, delete the network */
 		if (strcmp(user, lnick) == 0) {
-			net_del(networks[netid].name);
-			return 0;
+			snprintf(msg, sizeof(msg), "QUIT :netdel: %s (%s is kicked by %s)\r\n",
+					networks[netid].name, lnick, nick);
+			net_del(netid, msg);
+			break_evloop = TRUE;
+			return;
 		}
 
-		/* delete the user if the message from the same network */
-		if ((ids = user_event_ids(user, netid)) == NULL) {
+		/* if message is from a real user, delete the user */
+		if ((fds = vuser_get_fds(user, netid)) == NULL) {
+			snprintf(msg, sizeof(msg), "QUIT :userdel: %s (kicked by %s)\r\n", user, nick);
 			nick_add_symb(user, netid);
 			user_del(user, msg);
-			return 0;
+			break_evloop = TRUE;
+			return;
 		}
 
-		/* close the kicked fd */
-		writeall(fd, msg);
-		event_del(id);
+		/* delete the kicked clone */
+		snprintf(msg, sizeof(msg), "QUIT :kicked by %s\r\n", nick);
+		vuser_del(fds[netid], msg);
 
-		/*
-		 * send notice in the channel through linker
-		 */
 		/* get the original user netid */
 		for (i = 0; i < netlen; i++) {
-			if (ids[i] == -1)
+			if (fds[i] == -1)
 				break;
 		}
-		/* set buf to msg */
-		split(&buf, ':');
-		/* remove netsymb and suffix */
-		*strrchr(user, '[') = '\0';
-		/* send notice */
-		snprintf(msg, sizeof(msg),
-			"PRIVMSG %s :%s was kicked out by %s from network %s %s [%s]\r\n",
-			networks[i].chan, user, nick, networks[netid].name, chan, buf);
-		writeall(events[networks[i].id].fd, msg);
-		return 0;
+		/* send notice in the channel through linker */
+		snprintf(msg, sizeof(msg), "PRIVMSG %s :%s is kicked by %s [%s]\r\n",
+				networks[i].chan, user, nick, buf);
+		writeall(networks[i].fd, msg);
+		break_evloop = TRUE;
+		return;
 	}
-printbuffer:
-	printf("%d: %s\n", fd, backup);
-	return 0;
+	warnf("%d: %s", fd, backup);
+	return;
+del:
+	if (fdtovuser[fd]->user == NULL)
+		net_del(netid, msg);
+	else
+		vuser_del(fd, msg);
+	break_evloop = TRUE;
+	return;
 }
 
 void
@@ -708,110 +660,84 @@
 net_add(char *name, char *symb, char *host, char *port, char *chan)
 {
 	struct network *n;
-	int	i, fd;
+	int i;
 
-	/* if name, symbol or configuration already exists */
 	for (i = 0; i < netlen; i++) {
 		if (strcmp(networks[i].name, name) == 0) {
-			warnx("network name '%s' already exists", name);
+			warnf("%s: network name already exists", name);
 			return;
 		}
 		if (strcmp(networks[i].symb, symb) == 0) {
-			warnx("network symbol '%s' already exists", symb);
+			warnf("%s: network symbol already exists", symb);
 			return;
 		}
-		if ((strcmp(networks[i].host, host) == 0)
-		    && (strcmp(networks[i].port, port) == 0)
-		    && (strcmp(networks[i].chan, chan) == 0)) {
-			warnx("network configuration already exists");
+		if ((strcmp(networks[i].host, host) == 0) &&
+		    (strcmp(networks[i].port, port) == 0) &&
+		    (strcmp(networks[i].chan, chan) == 0)) {
+			warnf("%s:%s%s: network configuration already exists",
+			    host, port, chan);
 			return;
 		}
 	}
 
-	/* resize if full */
+	/* if full, resize the network and user fds */
 	if (netlen == netcap) {
-		Htiter it = {0};
-		networks = realloc0(networks, sizeof(struct network) * (size_t)netcap,
-				     sizeof(struct network) * (size_t)(netcap + NET_ADDEND));
-		while (htiterate(users, &it))
-			it.node->val = realloc0(it.node->val, sizeof(int) * (size_t)netcap,
-						sizeof(int) * (size_t)(netcap + NET_ADDEND));
+		struct htiter it;
+		htiter_init(&it);
+
+		networks = erealloc(networks, (size_t)(netcap + NET_ADDEND) *
+				sizeof(struct network));
+		while (htiterate(usertofds, &it)) {
+			it.node->val = erealloc(it.node->val,
+					(size_t)(netcap + NET_ADDEND) *
+					sizeof(int));
+			/* zero out the extended array */
+			for (i = netcap; i < netcap + NET_ADDEND; i++)
+				((int *)it.node->val)[i] = 0;
+		}
 		netcap += NET_ADDEND;
 	}
 
-	/* connect */
-	if ((fd = dial(host, port)) == -1)
-		return;
 	/* add a network */
 	n = &networks[netlen];
-	n->id = event_add(fd, netlen, NULL);
 	n->name = strdup(name);
 	n->symb = strdup(symb);
 	n->host = strdup(host);
 	n->port = strdup(port);
 	n->chan = strdup(chan);
-	n->ready = FALSE;
+	n->fd	= vuser_add(netlen, NULL);
 	netlen++;
-
-	printf("%d: network '%s' added\n", fd, name);
 }
 
 void
-net_del(char *name)
+net_del(int netid, char *msg)
 {
-	int	i, netid, *ids;
+	int *fds;
+	struct htiter lit, it;	/* last, current iterator */
 
-	Htiter	it	= {0};	/* current iterator */
-	Htiter	lastit	= {0};	/* last iterator */
-
-	/* get netid */
-	netid = -1;
-	for (i = 0; i < netlen; i++) {
-		if (strcmp(name, networks[i].name) == 0) {
-			netid = i;
-			break;
-		}
-	}
-	if (netid == -1) {
-		warnx("network '%s' doesn't exist", name);
-		return;
-	}
-
-	/* set the quit msg */
-	snprintf(msg, sizeof(msg), "QUIT :unlinking network %s\r\n", name);
-
-	/* reconstruct the user-clones table */
-	while (htiterate(users, &it)) {
-		ids = (int *)it.node->val;
-		/* delete all the users of deleting network */
-		if (ids[netid] == -1) {
+	htiter_init(&it);
+	htiter_init(&lit);
+	while (htiterate(usertofds, &it)) {
+		fds = (int *)it.node->val;
+		if (fds[netid] == -1) {
 			user_del(it.node->key, msg);
-			/* this node is deleted */
-			it = lastit;
-		/* delete the clones */
+			it = lit; /* this node is deleted */
 		} else {
-			if (ids[netid] > 0) {
-				writeall(events[ids[netid]].fd, msg);
-				event_del(ids[netid]);
-			}
-			/* swap last with current one */
-			ids[netid] = ids[netlen-1];
+			if (fds[netid] > 0)
+				vuser_del(fds[netid], msg);
+			fds[netid] = fds[netlen-1];
+			if (fds[netid] > 0)
+				fdtovuser[fds[netid]]->netid = netid;
+			fds[netlen-1] = 0;
 		}
-		lastit = it;
+		lit = it;
 	}
 
-	/* set last netid of events to current netid. */
-	for (i = 0; i < evslen; i++) {
-		if (events[i].netid == netlen-1)
-			events[i].netid = netid;
-	}
-
-	writeall(events[networks[netid].id].fd, msg);
-	event_del(networks[netid].id);
+	vuser_del(networks[netid].fd, msg);
 	net_del_raw(netid);
-	printf("%d: network '%s' deleted\n", events[networks[netid].id].fd, name);
-	/* swap the network with the last */
 	networks[netid] = networks[netlen-1];
+	if (networks[netid].fd > 0)
+		fdtovuser[networks[netid].fd]->netid = netid;
 	netlen--;
 }
 
@@ -831,161 +757,132 @@
 {
 	size_t	 len;
 	char	*nick;
-	int	*ids, i;
+	int	*fds;
 
 	len = strlen(unick) + strlen(networks[netid].symb) + 2 + 1;
-
-	/* too long nick */
 	if (len-1 > NICK_LEN) {
-		warnx("nick '%s' is too big", unick);
+		warnf("%s[%s]: nickname too long", unick, networks[netid].symb);
 		return;
 	}
 
 	/* resize hash table if storage is low */
-	if ((users->cap - users->len) < USER_ADDEND)
-		htresize(users, users->cap + USER_ADDEND);
+	if ((usertofds->cap - usertofds->len) < USER_ADDEND)
+		usertofds = htresize(usertofds, usertofds->cap + USER_ADDEND);
 
 	/* allocate a new user */
-	nick = ecalloc(len, sizeof(char));
-	ids = ecalloc((size_t)netcap, sizeof(int));
+	nick = emalloc(len * sizeof(char));
+	fds = ecalloc((size_t)netcap, sizeof(int));
 	sprintf(nick, "%s[%s]", unick, networks[netid].symb);
-	ids[netid] = -1;
+	fds[netid] = -1;
 
-	if (debug)
-		printf("useradd: %s\n", nick);
-
 	if (clone) {
-		/* clone the user on all other network */
+		int i;
 		for (i = 0; i < netlen; i++) {
-			if (networks[i].ready && i != netid)
-				ids[i] = clone_add(nick, i);
+			if (fds[i] == 0 && fdtovuser[networks[i].fd]->ready)
+				fds[i] = vuser_add(i, nick);
 		}
 	}
 
-	/* insert it to the users hash table */
-	if (htinsert(users, nick, ids) == -1)
-		/* this shouldn't happen as it was already checked */
-		errx(1, "user '%s' already exists", nick);
+	if (htinsert(usertofds, nick, fds) == -1)
+		fatal("%s: user already exists", nick);
 }
 
 void
 user_del(char *nick, char *msg)
 {
-	int i, *ids;
+	int i, *fds;
 
-	if ((ids = htsearch(users, nick)) == NULL)
-		errx(1, "user '%s' 1 doesn't exists", nick);
+	if ((fds = htsearch(usertofds, nick)) == NULL)
+		fatal("%s: user doesn't exist", nick);
 	for (i = 0; i < netlen; i++) {
-		if (ids[i] <= 0)
-			continue;
-		printf("%d: del[%s]: %s\n", events[ids[i]].fd,
-		    networks[i].symb, nick);
-
-		if (events[ids[i]].ready)
-			writeall(events[ids[i]].fd, msg);
-		event_del(ids[i]);
+		if (fds[i] > 0)
+			vuser_del(fds[i], msg);
 	}
-	htremove(users, nick);
+	htremove(usertofds, nick);
 }
 
-int *
-user_event_ids(char *nick, int netid)
-{
-	unsigned int s;
-	int *ids = NULL;
-
-	/* count suffix */
-	for (s = 0; nick[strlen(nick)-s-1] == '_'; s++);
-	/* remove suffix */
-	if (s > 0)
-		nick[strlen(nick)-s] = '\0';
-
-	ids = htsearch(users, nick);
-	/* if match but suffix doesn't match */
-	if ((ids != NULL) && (events[ids[netid]].suffix != (int)s))
-		ids = NULL;
-
-	/* add suffix back */
-	if (s > 0)
-		nick[strlen(nick)] = '_';
-
-	return ids;
-}
-
 int
-clone_add(char *nick, int netid)
+vuser_add(int netid, char *user)
 {
-	struct network	*n;
-	int		 fd;
+	int fd;
+	struct network *n;
+	struct vuser *vuser;
 
 	n = &networks[netid];
-	if ((fd = dial(n->host, n->port)) == -1) {
-		warn("enable to connect to %s\n", n->host);
-		return -1;
+	fd = dial(n->host, n->port);
+	if (fd == -1) {
+		warnf("%s:%s: failed to connect", n->host, n->port);
+		return -2;
 	}
-	printf("%d: add[%s]: %s\n", fd, n->symb, nick);
-	return event_add(fd, netid, nick);
-}
+	fd_register(fd, EV_WRITE);
 
-int
-event_add(int fd, int netid, char *user)
-{
-	int i = evslen;
+	if (user == NULL)
+		log1("%d: netadd: %s[%s]", fd, n->name, n->symb);
+	else
+		log1("%d: add[%s]: %s", fd, n->symb, user);
 
-	if (evslen == evscap) {
-		events = realloc0(events, sizeof(struct event) * (size_t)evscap,
-				   sizeof(struct event) * (size_t)(evscap + EVENT_ADDEND));
-		evscap += EVENT_ADDEND;
+	if (fd+1 > fdcap) {
+		fdcap *= 2;
+		fdtovuser = erealloc(fdtovuser, (size_t)fdcap * sizeof(struct vuser *));
 	}
 
-	events[i].fd	 = fd;
-	events[i].netid	 = netid;
-	events[i].user	 = user;
-	events[i].suffix = 0;
-	events[i].time	 = 0;
-	events[i].ready	 = FALSE;
+	vuser = emalloc(1 * sizeof(struct vuser));
+	vuser->netid = netid;
+	vuser->user = user;
+	vuser->suffix = 0;
+	vuser->ready = FALSE;
 
-	fd_register(fd, EV_WRITE);
+	fdtovuser[fd] = vuser;
 
-	if (fd == fdslen - 1) {
-		fdtoid = realloc0(fdtoid, sizeof(int) * (size_t)fdslen,
-				   sizeof(int) * (size_t)fdslen * 2);
-		fdslen *= 2;
-	}
-	fdtoid[fd] = i;
-
-	return evslen++;
+	return fd;
 }
 
 void
-event_del(int id)
+vuser_del(int fd, char *msg)
 {
-	int *ids;
-	int  l = evslen - 1;		/* last id */
+	char *user = fdtovuser[fd]->user;
+	int netid = fdtovuser[fd]->netid;
+	int *fds;
 
-	/* swap id */
-	if (events[l].user == NULL) {	/* if linker */
-		networks[events[l].netid].id = id;
-	} else {			/* else user */
-		if ((ids = htsearch(users, events[l].user)) == NULL)
-			errx(1, "user '%s' 2 doesn't exists", events[l].user);
-		ids[events[l].netid] = id;
+	if (user == NULL) {
+		log1("%d: netdel: %s[%s]", fd, networks[netid].name, networks[netid].symb);
+		networks[netid].fd = -2;
+	} else {
+		log1("%d: del[%s]: %s", fd, networks[netid].symb, user);
+		if ((fds = htsearch(usertofds, user)) == NULL)
+			fatal("%s: user doesn't exist", user);
+		fds[netid] = -2;
 	}
 
-	/* disable id */
-	if (events[id].user == NULL) {	/* if linker */
-		networks[events[id].netid].id = -2;
-	} else {			/* else user */
-		if ((ids = htsearch(users, events[id].user)) == NULL)
-			errx(1, "user '%s' 3 doesn't exists", events[id].user);
-		ids[events[id].netid] = -2;
-	}
+	if (fdtovuser[fd]->ready)
+		writeall(fd, msg);
+	close(fd);
+	free(fdtovuser[fd]);
+	fdtovuser[fd] = NULL;
+}
 
-	close(events[id].fd);
+int *
+vuser_get_fds(char *nick, int netid)
+{
+	unsigned int s;
+	int *fds = NULL;
 
-	events[id] = events[l];
-	evslen--;
-	fdtoid[events[id].fd] = id;
+	/* count suffix */
+	for (s = 0; nick[strlen(nick)-s-1] == '_'; s++);
+	/* remove suffix */
+	if (s > 0)
+		nick[strlen(nick)-s] = '\0';
+
+	fds = htsearch(usertofds, nick);
+	/* if match but suffix doesn't match */
+	if ((fds != NULL) && (fdtovuser[fds[netid]]->suffix != (int)s))
+		fds = NULL;
+
+	/* add suffix back */
+	if (s > 0)
+		nick[strlen(nick)] = '_';
+
+	return fds;
 }
 
 void
@@ -1017,7 +914,7 @@
 		}
 
 		/* check if the word is nick */
-		if (user_event_ids(src, netid) != NULL)
+		if (vuser_get_fds(src, netid) != NULL)
 			*strrchr(src, '[') = '\0';
 
 		strcat(dst, src);
@@ -1030,9 +927,9 @@
 void
 print_table(void)
 {
-	int	i, *ids, diff, tabs;
-	Htiter	it = {0};
+	int	i, *fds, diff, tabs;
 	char	*nick;
+	struct htiter it;
 
 	if (netlen == 0)
 		return;
@@ -1039,13 +936,20 @@
 
 	print_border();
 	/* print networks */
-	printf("struct networks\t\t");
-	for (i = 0; i < netlen; i++)
-		printf("%s(%d)\t", networks[i].symb, events[networks[i].id].fd);
+	printf("Networks\t\t");
+	for (i = 0; i < netlen; i++) {
+		printf("%s->%d", networks[i].symb, networks[i].fd);
+		/* print suffix */
+		if (fdtovuser[networks[i].fd]->suffix > 0)
+			printf("(%d)\t", fdtovuser[networks[i].fd]->suffix);
+		else
+			printf("\t\t");
+	}
 	printf("\n");
 
-	while (htiterate(users, &it)) {
-		ids  = (int *)it.node->val;
+	htiter_init(&it);
+	while (htiterate(usertofds, &it)) {
+		fds  = (int *)it.node->val;
 		nick = (char *)it.node->key;
 		/* print tabbed user nick */
 		printf("%s", nick);
@@ -1052,13 +956,13 @@
 		diff = 24 - (int)strlen(nick);
 		tabs = ((diff / 8) + (diff % 8 > 0));
 		printf("%.*s", tabs, "\t\t\t");
-		/* print tabbed clones ids */
+		/* print tabbed fds */
 		for (i = 0; i < netlen; i++) {
-			printf("%d", ids[i]);
+			printf("%d", fds[i]);
 			/* print suffix */
-			if ((ids[i] > 0) && (events[ids[i]].suffix > 0))
-				printf("(%d)", events[ids[i]].suffix);
-			printf("\t");
+			if ((fds[i] > 0) && (fdtovuser[fds[i]]->suffix > 0))
+				printf("(%d)", fdtovuser[fds[i]]->suffix);
+			printf("\t\t");
 		}
 		printf("\n");
 	}
@@ -1068,16 +972,17 @@
 void
 print_htable(void)
 {
-	Htiter	it = {0};
-	int	index = -1;
+	struct htiter it;
+	int index = -1;
 
 	print_border();
-	while (htiterate(users, &it)) {
+	htiter_init(&it);
+	while (htiterate(usertofds, &it)) {
 		if (index != (int)it.index) {
 			/* ignore first new line */
 			if (index != -1)
 				printf("\n");
-			printf("%d", it.index);
+			printf("%d", it.index-1);
 			index = (int)it.index;
 		}
 		printf(" -> %s", (char *)it.node->key);
@@ -1089,11 +994,12 @@
 void
 print_users(void)
 {
-	Htiter	it = {0};
+	struct htiter it;
 	int i = 0;
 
 	print_border();
-	while (htiterate(users, &it))
+	htiter_init(&it);
+	while (htiterate(usertofds, &it))
 		printf("%d: %s\n", i++, (char *)it.node->key);
 	print_border();
 }
--- a/util.c
+++ b/util.c
@@ -9,45 +9,103 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <netdb.h>
+#include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 
-#ifdef __gnu_linux__
-#include <bsd/err.h>
-#else
-#include <err.h>
-#endif
+#define FALSE	0
+#define TRUE	1
 
-#define FALSE 0
-#define TRUE 1
+void
+print_log(int level, FILE *stream, const char *fmt, va_list arg)
+{
+	size_t	len;
 
-/*
- * ecalloc - calloc with error handling
- */
+	if (level > LOG_LEVEL)
+		return;
+
+	vfprintf(stream, fmt, arg);
+	len = strlen(fmt);
+	if (len && fmt[len-1] == ':')
+		fprintf(stream, " %s\n", strerror(errno));
+	else
+		fprintf(stream, "\n");
+}
+
+void
+log1(const char *fmt, ...)
+{
+	va_list arg;
+	va_start(arg, fmt);
+	print_log(1, stdout, fmt, arg);
+	va_end(arg);
+}
+
+void
+log2(const char *fmt, ...)
+{
+	va_list arg;
+	va_start(arg, fmt);
+	print_log(2, stdout, fmt, arg);
+	va_end(arg);
+}
+
+void
+log3(const char *fmt, ...)
+{
+	va_list arg;
+	va_start(arg, fmt);
+	print_log(3, stdout, fmt, arg);
+	va_end(arg);
+}
+
+void
+warnf(const char *fmt, ...)
+{
+	va_list arg;
+	fprintf(stderr, "warn: ");
+	va_start(arg, fmt);
+	print_log(0, stderr, fmt, arg);
+	va_end(arg);
+}
+
+void
+fatal(const char *fmt, ...)
+{
+	va_list arg;
+	fprintf(stderr, "error: ");
+	va_start(arg, fmt);
+	print_log(0, stderr, fmt, arg);
+	va_end(arg);
+	exit(1);
+}
+
 void *
+emalloc(size_t size)
+{
+	void *p;
+	if ((p = malloc(size)) == NULL)
+		fatal("malloc:");
+	return p;
+}
+
+void *
 ecalloc(size_t nmemb, size_t size)
 {
 	void *p;
 	if ((p = calloc(nmemb, size)) == NULL)
-		err(1, "calloc");
+		fatal("calloc:");
 	return p;
 }
 
-/*
- * realloc0 -- allocate more memory and zero new memory
- * 	ptr	- pointer of the old memory
- * 	osize	- size of old memory
- * 	nsize	- size to new memory
- */
 void *
-realloc0(void *ptr, size_t osize, size_t nsize)
+erealloc(void *ptr, size_t size)
 {
 	void *p;
-	if ((p = realloc(ptr, nsize)) == NULL)
-		err(1, "realloc");
-	memset(((char *)p + osize), 0, nsize - osize);
+	if ((p = realloc(ptr, size)) == NULL)
+		fatal("realloc:");
 	return p;
 }
 
@@ -70,42 +128,6 @@
 }
 
 int
-fifo_open(char *path)
-{
-	struct stat st;
-	int fd;
-
-	/* make fifo if it doesn't exists */
-	if (lstat(path, &st) != -1) {
-		if (!(st.st_mode & S_IFIFO))
-			errx(1, "'%s' is not a fifo file", path);
-	} else if (mkfifo(path, S_IRWXU) == -1) {
-		err(1, "mkfifo: %s", path);
-	}
-
-	fd = open(path, O_RDONLY | O_NONBLOCK, 0);
-	if (fd == -1)
-		err(1, "open: %s", path);
-
-	return fd;
-}
-
-ssize_t
-writeall(int fd, char *buf)
-{
-	ssize_t left, sent, n;
-	left = (ssize_t)strlen(buf);
-	sent = 0;
-	while (sent < left) {
-		if ((n = write(fd, buf+sent, (size_t)left)) == -1)
-			err(1, "%d: write", fd);
-		sent += n;
-		left -= n;
-	}
-	return sent;
-}
-
-int
 dial(char *host, char *port)
 {
 	static struct addrinfo hints, *res = NULL, *res0;
@@ -116,32 +138,54 @@
 	hints.ai_socktype = SOCK_STREAM;
 
 	if ((r = getaddrinfo(host, port, &hints, &res0)) != 0) {
-		warnx("getaddrinfo: %s", gai_strerror(r));
+		warnf("getaddrinfo: %s", gai_strerror(r));
 		return -1;
 	}
 	for (res = res0; res != NULL; res = res->ai_next) {
-		if ((fd = socket(res->ai_family, res->ai_socktype,
-				 res->ai_protocol)) == -1)
+		fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+		if (fd == -1) {
+			warnf("socket:");
 			continue;
+		}
 		if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
-			warn("fnctl");
+			warnf("fnctl:");
 			continue;
 		}
-		if ((connect(fd, res->ai_addr, res->ai_addrlen) == -1)
-		    && (errno == EINPROGRESS))
+		if ((connect(fd, res->ai_addr, res->ai_addrlen) == -1) &&
+		    (errno == EINPROGRESS))
 			break;
-
-		warn("connect");
+		warnf("connect:");
 		close(fd);
 		fd = -1;
 	}
 	if (fd == -1)
-		warnx("cannot connect to %s", host);
+		warnf("failed to connect to %s", host);
 
 	freeaddrinfo(res0);
 	return fd;
 }
 
+int
+fifo_open(char *path)
+{
+	struct stat st;
+	int fd;
+
+	/* make a fifo file if it doesn't exists */
+	if (lstat(path, &st) != -1) {
+		if (!(st.st_mode & S_IFIFO))
+			fatal("%s: not a fifo file", path);
+	} else if (mkfifo(path, S_IRWXU) == -1) {
+		fatal("mkfifo: %s:", path);
+	}
+
+	fd = open(path, O_RDONLY | O_NONBLOCK, 0);
+	if (fd == -1)
+		fatal("open: %s:", path);
+
+	return fd;
+}
+
 ssize_t
 readline(int fd, char *buffer, size_t size)
 {
@@ -149,18 +193,44 @@
 	char c;
 	ssize_t l;
 
-	do {
+	while (i < size) {
 		l = read(fd, &c, sizeof(char));
-		if (l > 0)
-			buffer[i++] = c;
-		else if (l == 0)
-			return 0;
-		else if (l == -1 && errno == EAGAIN)
+		if (l == -1 && errno == EAGAIN)
 			continue;
-		else
-			return -1;
-	} while ((i < size) && (c != '\r') && (c != '\n'));
+		else if (l == -1 || l == 0)
+			return l;
 
-	buffer[i-1] = '\0';
+		if (c == '\r' || c == '\n')
+			break;
+		buffer[i++] = c;
+	}
+	buffer[i++] = '\0';
 	return (ssize_t)i;
+}
+
+ssize_t
+writeall(int fd, char *buf)
+{
+	ssize_t left, sent, n;
+	left = (ssize_t)strlen(buf);
+	sent = 0;
+	while (sent < left) {
+		if ((n = write(fd, buf+sent, (size_t)left)) == -1)
+			fatal("%d: write:", fd);
+		sent += n;
+		left -= n;
+	}
+	return sent;
+}
+
+/* very simple string hash function */
+unsigned int
+hash_str(void *key, unsigned int cap)
+{
+	unsigned int i, sum = 0;
+
+	for (i = 0; i < (unsigned int)strlen(key); i++) {
+		sum += ((unsigned char *)key)[i] * (i + 1);
+	}
+	return (sum % cap);
 }