wm: ticl

Download patch

ref: 49ca82832b3505a7ce86e96f11a90599219afaab
parent: 8cdec92600494e5d844cb79e25d95215cda12f8a
author: libredev <libredev@ircforever.org>
date: Sun Jan 15 11:37:08 EST 2023

replaced picoev with kqueue and epoll

git/query: bad hash ff85d9ef578842a40f7c91d2544b7932cec74b9d
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,0 @@
-[submodule "picoev"]
-	path = picoev
-	url = https://github.com/kazuho/picoev
--- a/Makefile
+++ b/Makefile
@@ -1,51 +1,37 @@
 # This work is dedicated to the public domain.
 # See COPYING file for more information.
 
-PREFIX = /usr/local
+include config.mk
 
-CC     = cc
-CFLAGS = -g -std=c89 -Wall -Wextra -pedantic -Wfatal-errors -Wconversion\
-	 -Wstrict-prototypes -Wold-style-definition\
-	 -D_POSIX_C_SOURCE=200809L -isystem ./picoev/
-#CFLAGS += -fsanitize=address -fno-omit-frame-pointer
-#LDFLAGS = -fsanitize=address
-LDFLAGS =  -L ./picoev/ -l picoev
+OUT = ticl
+SRC = main.c htable.c
+OBJ = $(SRC:.c=.o)
 
-VERSION != date '+%Y-%m-%d'
-PROGRAM = ticl
-SOURCES = main.c htable.c
-HEADERS = htable.h util.c
-OBJECTS = $(SOURCES:.c=.o)
+all: clean $(OUT)
 
-all: clean libpicoev.a $(PROGRAM)
-
-libpicoev.a:
-	cd picoev && $(MAKE) libpicoev.a LINUX_BUILD=1 CC_DEBUG_FLAGS=-g
-
-$(PROGRAM): $(OBJECTS)
-	$(CC) -o $@ $(OBJECTS) $(LDFLAGS)
-
 .c.o:
 	$(CC) -c $(CFLAGS) $<
 
+$(OUT): $(OBJ)
+	$(CC) -o $@ $(OBJ) $(LDFLAGS)
+
 clean:
-	rm -f $(PROGRAM) $(OBJECTS) $(PROGRAM)-$(VERSION).tar.gz
-	cd picoev && $(MAKE) clean
+	rm -f $(OUT) $(OBJ) $(OUT)-*.tar.gz
 
 dist: clean
-	mkdir -p $(PROGRAM)-$(VERSION)
-	cp -R README COPYING Makefile $(SOURCES) $(HEADERS)\
-		$(PROGRAM)-$(VERSION)
-	tar -cf	$(PROGRAM)-$(VERSION).tar $(PROGRAM)-$(VERSION)
-	gzip $(PROGRAM)-$(VERSION).tar
-	rm -rf $(PROGRAM)-$(VERSION)
+	mkdir -p $(OUT)-$(VERSION)
+	cp -R README COPYING Makefile config.mk htable.h util.c $(SRC)\
+		$(OUT)-$(VERSION)
+	tar -cf	$(OUT)-$(VERSION).tar $(OUT)-$(VERSION)
+	gzip $(OUT)-$(VERSION).tar
+	rm -rf $(OUT)-$(VERSION)
 
 install: all
 	mkdir -p $(DESTDIR)$(PREFIX)/bin
-	cp -f $(PROGRAM) $(DESTDIR)$(PREFIX)/bin
-	chmod 755 $(DESTDIR)$(PREFIX)/bin/$(PROGRAM)
+	cp -f $(OUT) $(DESTDIR)$(PREFIX)/bin
+	chmod 755 $(DESTDIR)$(PREFIX)/bin/$(OUT)
 
 uninstall:
-	rm -f $(DESTDIR)$(PREFIX)/bin/$(PROGRAM)
+	rm -f $(DESTDIR)$(PREFIX)/bin/$(OUT)
 
 .PHONY: all clean dist install uninstall
--- a/README
+++ b/README
@@ -1,6 +1,6 @@
-====================================================================
+======================================================================
 WARNING: This program is still in progress, use it at your own risk.
-====================================================================
+======================================================================
 
 ticl - tiny irc channel linker
 ------------------------------
@@ -7,170 +7,217 @@
 
 ticl is a very small and simple multi-network irc channel linker.
 
+WARNING: If you use this program on networks that you are not allowed
+	 then your nick or IP may get banned. This is because this
+	 program opens multiple simultaneous connections to the server
+	 and most networks have some limit on each IP.
 
+
 Working
 -------
 
-A bot named 'linker' joins each given channels and clones each user of its
-channel on other channels and relays the user's messages received from its
-channel to the clones on other channels.
+- A bot named 'linker' joins each given channels and clones each user
+  of its channel on other channels and relays the user's messages
+  received from its channel to the clones on other channels.
 
-If a user JOIN, QUIT, PART, or NICK, the linker attempts to emulate the
-same action with the clones on the other channels.
+- If a user on a channel joins(JOIN), quits(QUIT), leaves(PART), or
+  changes their nick(NICK), the linker attempts to emulate the same
+  action with the respective clones on the other channels.
 
-The nick of clones has the network symbol (included in square brackets) as
-a suffix.
+- Each clone's nick is followed by the original user's channel symbol
+  (enclosed in square brackets).
 
-If nick is already taken then additional suffix '_' will be added until
-nick is accepted on the network.
+- If the nick is already taken by another user/clone on that network,
+  an additional suffix '_' is added until the nick is accepted on that
+  network.
 
+- Nick of users or clones that are longer than 16 characters are
+  ignored as some networks only allow nicknames of length 16 or less.
 
-Limitation
-----------
 
-- Networks and channels that require any type of registration or verification
-  will not work because clones cannot register themselves.
+Limitations
+-----------
 
-- Linking any two channels that are already linked will create an infinite
-  loop of clones and may get your IP banned.
+- This program will not work on any channel that requires any kind of
+  registration or verification as clones cannot register.
 
-- Channels on the same network will not link (can be possible in the future).
+- Linking the same channel with a different name is undefined and can
+  create an infinite loop of clones.
 
+- Linking more than one channel from a network is undefined.
+
 - No spam protection.
 
+- No support for TLS/SSL.
 
+
 Features
 --------
 
-- written in POSIX ANSI C.
-- one clone for every user on all other channels.
-- no support for TLS/SSL (this is a feature).
+- Written in OpenBSD's C style(9).
 
+- One clone per user on each channel.
 
+
 Dependencies
 ------------
 
 - C compiler (C89)
-- libc (with POSIX support)
+
+- C POSIX library
+
+- libbsd (on non-BSD operating systems)
+
 - POSIX make (optional)
 
 
-Compiling
----------
+Installation
+-------------
 
-	$ make
-or
-	$ gcc ticl.c htable.c utils.c -o ticl
+Edit config.mk for your system.
 
+Then to compile and install, run:
 
-Example
--------
+	$ make clean install
 
-To start the program with 'in' as the fifo file:
+To compile without POSIX make on BSD systems, run:
 
-	$ ./ticl ./in
+	$ cc -o ticl main.c htable.c
 
+Or on non-BSD systems, run:
+
+	$ cc -o ticl main.c htable.c -lbsd
+
+
+Usages
+------
+
+This program uses FIFO special file (a named pipe) for configuration.
+
+To start the program:
+
+	$ ticl <fifo>
+
+	# Example:
+
+	$ ticl in
+
+	# This will create a 'in' FIFO file if it doesn't already exist.
+
 Or, to start the program with the log printing to a file:
 
-	$ ./ticl ./in > ticl.log
+	$ ticl <fifo> > <logfile> 2>&1
 
-To link channel #test20 from libera and channel #test21 from ircnow:
+	# Example:
 
-	$ echo 'netadd libera L irc.libera.chat 6667 #test20' > ./in
-	$ echo 'netadd ircnow N irc.ircnow.org 6667 #test21' > ./in
+	$ ticl in > log 2>&1
 
-To unlink channel ircnow:
+	# This will create a 'log' file and print everything to the file.
 
-	$ echo 'netdel ircnow' > ./in
+Or, to start and run the program in background:
 
-To list all the sockets (connections):
+	$ ticl <fifo> > <logfile> 2>&1 &
 
-	$ echo 'print' > ./in
+	# Example:
 
-	# -1 indicate original user
-	# -2 indicate kicked clone
+	$ ticl in > log 2>&1 &
 
-To list all the users:
+To add a channel:
 
-	$ echo 'users' > ./in
+	$ echo 'netadd <name> <symbol> <host> <ip> <channel>' > <fifo>
 
-To shutdown the program:
+	# Example, to link #test20 from libera and #test21 from ircnow:
 
-	$ echo exit > ./in
+	$ echo 'netadd libera L irc.libera.chat 6667 #test20' > in
+	$ echo 'netadd ircnow N irc.ircnow.org 6667 #test21' > in
 
+To remove a channel from the link:
 
-Community
----------
+	$ echo 'netdel <name>' > <fifo>
 
-Join #playground on irc.ircnow.org:6697 (TLS)
+	# Example, to unlink channel ircnow:
 
-For any help, tag or private message me (libredev).
+	$ echo 'netdel ircnow' > in
 
-NOTE: Anything is allowed as long as you are not violating IRCNow TOS.
-      (https://wiki.ircnow.org/index.php?n=Terms.Terms)
+To list all the users:
 
-Email: libredev@ircforever.org (expect late response)
+	$ echo users > <fifo>
 
+To close the program:
 
-SSL/TLS support
+	$ echo exit > <fifo>
+
+
+SSL/TLS Support
 ---------------
 
-1. relayd (OpenBSD)
--------------------
+On Openbsd (relayd):
 
-/etc/relayd.conf:
+	Edit /etc/relayd.conf:
 
-	table <libera> { irc.libera.chat }
-	table <ircnow> { irc.ircnow.org }
+		table <libera> { irc.libera.chat }
+		table <ircnow> { irc.ircnow.org }
 
-	protocol "irctls" {
-		tcp { nodelay, sack }
-	}
+		protocol "irctls" {
+			tcp { nodelay, sack }
+		}
 
-	relay "libera" {
-		listen on 127.0.0.1 port 31220
-		protocol "irctls"
-		forward with tls to <libera> port 6697
-	}
+		relay "libera" {
+			listen on 127.0.0.1 port 31220
+			protocol "irctls"
+			forward with tls to <libera> port 6697
+		}
 
-	relay "ircnow" {
-		listen on 127.0.0.1 port 31221
-		protocol "irctls"
-		forward with tls to <ircnow> port 6697
-	}
+		relay "ircnow" {
+			listen on 127.0.0.1 port 31221
+			protocol "irctls"
+			forward with tls to <ircnow> port 6697
+		}
 
-2. stunnel (*BSD, GNU/Linux, GNU/Hurd, Plan9, etc)
---------------------------------------------------
+	To enable and start:
 
-On debian, /etc/stunnel/stunnel.conf:
+		$ doas rcctl enable relayd
+		$ doas rcctl start relayd
 
-	pid = /etc/stunnel/pid
+On other platforms (stunnel):
 
-	[libera]
-	client = yes
-	accept = 127.0.0.1:31220
-	connect = irc.libera.chat:6697
-	checkHost = irc.libera.chat
-	verifyChain = yes
-	CApath = /etc/ssl/certs
-	OCSPaia = yes
+	Edit /etc/stunnel/stunnel.conf:
 
-	[ircnow]
-	client = yes
-	accept = 127.0.0.1:31221
-	connect = irc.ircnow.org:6697
-	checkHost = irc.ircnow.org
-	verifyChain = yes
-	CApath = /etc/ssl/certs
-	OCSPaia = yes
+		pid = /etc/stunnel/pid
 
-3. To connect:
---------------
+		[libera]
+		client = yes
+		accept = 127.0.0.1:31220
+		connect = irc.libera.chat:6697
+		checkHost = irc.libera.chat
+		verifyChain = yes
+		CApath = /etc/ssl/certs
+		OCSPaia = yes
 
-	$ echo 'netadd libera L 127.0.0.1 31220 #test20' > ./in
-	$ echo 'netadd ircnow N 127.0.0.1 31221 #test21' > ./in
+		[ircnow]
+		client = yes
+		accept = 127.0.0.1:31221
+		connect = irc.ircnow.org:6697
+		checkHost = irc.ircnow.org
+		verifyChain = yes
+		CApath = /etc/ssl/certs
+		OCSPaia = yes
 
+	Then enable and start stunnel service.
 
+Now to connect:
+
+	$ echo 'netadd libera L 127.0.0.1 31220 #test20' > in
+	$ echo 'netadd ircnow N 127.0.0.1 31221 #test21' > in
+
+
+Community / Bug Report
+----------------------
+
+Email:	libredev@ircforever.org (expect late response)
+IRC:	#playground on irc.ircnow.org:6697 (TLS)
+
+
 License
 -------
 
@@ -181,6 +228,6 @@
 Note
 ----
 
-It is a free software and please do not call it open source as the
+This work is free software but please don't call it open source as the
 Open Source Initiative (OSI) does not consider public domain software
 as open source. https://opensource.org/node/878
--- /dev/null
+++ b/config.mk
@@ -1,0 +1,17 @@
+# This work is dedicated to the public domain.
+# See COPYING file for more information.
+
+VERSION != date '+%Y-%m-%d'
+
+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)
+LDFLAGS  = $(LIBS)
+
+CC = cc
--- a/main.c
+++ b/main.c
@@ -4,40 +4,57 @@
  */
 
 #include <errno.h>
+#include <execinfo.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <poll.h>
+#include <signal.h>
 #include <time.h>
 #include <unistd.h>
 
-#include <picoev.h>
+#ifdef __gnu_linux__
+#include <sys/epoll.h>
+#include <bsd/err.h>
+#include <bsd/stdlib.h>
+#include <bsd/string.h>
+#else
+#include <sys/event.h>
+#endif
 
 #include "htable.h"
 #include "util.c"
 
-#define BUFFER_LEN	1024
-#define NICK_LEN	16
+#define FALSE 0
+#define TRUE 1
 
-#define EVENT_ADDEND	10000
+#define EV_READ 1
+#define EV_WRITE 2
+
+#define EVENT_ADDEND	100
 #define NET_ADDEND	10
 #define USER_ADDEND	100
-#define EVENT_TIMEOUT	10000
+#define PING_TIMEOUT	240
+#define CLONE_COOLDOWN	1
+#define CLONE_ADDEND	10
 
-#define MAX_FDS		10000
-#define CLONE_COOLDOWN	5
-#define CLONE_ADDEND	5
+#define BUFSIZE		1024
+#define NICK_LEN	16
+#define MAX_EVENTS	10
 
-#define UNUSED(x) (void)(x)
+enum {
+	IDLE = 0,
+	RESET,
+	CLONING
+};
 
 struct network {
 	int	 id;	/* event index	*/
-	int	 join;	/* is joined	*/
 	char	*name;	/* name		*/
 	char	*symb;	/* symbol	*/
-	char	*host;	/* hostname	*/
+	char	*host;	/* host		*/
 	char	*port;	/* port		*/
 	char	*chan;	/* channel	*/
+	int	 ready;	/* joined	*/
 };
 
 struct event {
@@ -45,19 +62,36 @@
 	int	 netid;		/* net index	*/
 	char	*user;		/* user nick	*/
 	int	 suffix;	/* suffix count	*/
+	time_t	 time;		/* last response */
+	int	 ready;		/* joined	*/
 };
 
-static char	msg [BUFFER_LEN];
-static int	done;
 
-static struct event	*events;	/* events array	  */
-static int		 evlen;		/* array length	  */
-static int		 evcap;		/* array capacity */
+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;
 
-static struct network	*networks;	/* networks array */
-static int		 netlen;	/* array length	  */
-static int		 netcap;	/* array capacity */
+#ifdef __gnu_linux__
+static int	 epfd;			/* epoll instance  */
+#else
+static int	 kqfd;			/* kqueue instance */
+#endif
 
+static int	*fdtoid;		/* fd to event id  */
+static int	 fdslen;		/* maximum fd	   */
+
+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> + ']'
@@ -67,22 +101,24 @@
 static struct Htable	*users;
 
 /* functions prototype */
-void	 fifo_event_cb(picoev_loop *, int, int, void *);
-void	 server_event_cb(picoev_loop *, int, int, void *);
-void	 net_add(picoev_loop *, char *, char *, char *, char *, char *);
-void	 net_del(picoev_loop *, char *);
+
+void	 fd_register(int, int);
+void	 fifo_read(void);
+time_t	 event_timeout(void);
+void	 event_write(int);
+int	 event_read(int);
+void	 net_add(char *, char *, char *, char *, char *);
+void	 net_del(char *);
 void	 net_del_raw(int);
-void	 net_update(picoev_loop *, int);
-void	 user_add(picoev_loop *, char *, int);
-void	 user_del(picoev_loop *, char *, char *);
-int	*user_get_ids(char *);
-int	*clone_get_user_ids(char *, int);
-int	 clone_add(picoev_loop *, char *, int);
-int	 event_add(picoev_loop *, int, int, char *);
-void	 event_del(picoev_loop *, 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);
 void	 nick_add_symb(char *, int);
 void	 privmsg_update(char *, char *, int);
-void	 clean_exit(picoev_loop *, int);
 void	 print_table(void);
 void	 print_htable(void);
 void	 print_users(void);
@@ -91,68 +127,180 @@
 int
 main(int argc, char *argv[])
 {
-	picoev_loop *loop;
-	/* int fd; */
+#ifdef __gnu_linux__
+	struct epoll_event epevs [MAX_EVENTS];	/* maximum event to handle */
+#else
+	struct kevent	kevs [MAX_EVENTS];	/* maximum event to handle */
+	struct timespec	tmsp;
+#endif
+	int	i, fd, id, nev;
+	time_t	timeout;
+	time_t	ntime;		/* next timeout time */
 
 	/* set stdout to unbufferd */
 	setvbuf(stdout, NULL, _IONBF, 0);
 
 	/* check arguments */
-	/* if (argc != 2) {
-		printf("usage: %s <fifo>\n", argv[0]);
-		return 1;
-	}*/
+	if (argc != 2) {
+		printf("usage: %s fifo\n", getprogname());
+		return 0;
+	} else {
+		fifopath = argv[1];
+	}
 
 	/* init global variables */
-	evcap = EVENT_ADDEND;
+	fdslen = evscap = EVENT_ADDEND;
 	netcap = NET_ADDEND;
-	events = ecalloc((size_t)evcap, sizeof(struct event));
+	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);
 
-	/* init picoev */
-	picoev_init(MAX_FDS);
-	/* create loop */
-	loop = picoev_create_loop(60);
-	/* get fifo fd */
-	/* fd = fifo_open(argv[1]); */
-	/* add fifo fd */
-	/* picoev_add(loop, fd, PICOEV_READ, 0, fifo_event_cb, argv[1]); */
+#ifdef __gnu_linux__
+	if ((epfd = epoll_create1(0)) == -1)
+		err(1, "epoll_create1");
+#else
+	if ((kqfd = kqueue()) == -1)
+		err(1, "kqueue");
+#endif
+	fifofd = fifo_open(fifopath);
+	fd_register(fifofd, EV_READ);
 
-	net_add(loop, "libre",  "L", "38.87.162.53", "6667", "#debian2");
-	net_add(loop, "ircnow",  "N", "irc.ircnow.org", "6667", "#debian");
+	ntime = -1;
+	/* event loop */
+	while (!done) {
+		if (ntime == -1)
+			timeout = -1;
+		else if ((timeout = ntime - time(NULL)) < 0)
+			timeout = 0;
+#ifdef __gnu_linux__
+		if ((nev = epoll_wait(epfd, epevs, MAX_EVENTS,
+				      (int)timeout * 1000)) == -1)
+			err(1, "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");
+#endif
+		if (nev == 0) {
+			ntime = event_timeout();
+			continue;
+		}
 
-	/* loop */
-	while (!done)
-		picoev_loop_once(loop, 10);
-	/* clean and exit */
-	clean_exit(loop, 0);
+		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;
+#else
+			fd = (int)kevs[i].ident;
+#endif
+			id = fdtoid[fd];
+			if (fd == fifofd) {
+				fifo_read();
+#ifdef __gnu_linux__
+			} else if (epevs[i].events & EPOLLOUT) {
+#else
+			} else if (kevs[i].filter == EVFILT_WRITE) {
+#endif
+				event_write(id);
+#ifdef __gnu_linux__
+			} else if (epevs[i].events & EPOLLIN) {
+#else
+			} else if (kevs[i].filter == EVFILT_READ) {
+#endif
+				if (event_read(id)) {
+					timeout_id = -1;
+					ntime = 0;
+				}
+			} else {
+				errx(1, "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);
+	}
+
+	free(fdtoid);
+	free(events);
+
+	/* delete all the networks */
+	for (i = 0; i < netlen; i++)
+		net_del_raw(i);
+	free(networks);
+
+	/* delete all the users */
+	htdestroy(users);
+
 	return 0;
 }
 
 void
-fifo_event_cb(picoev_loop *loop, int fd, int revents, void *cb_arg)
+fd_register(int fd, int type)
 {
-	char	 buffer[BUFFER_LEN];
+#ifdef __gnu_linux__
+	struct epoll_event ev;
+
+	if (type == EV_READ)
+		ev.events = EPOLLIN;
+	else if (type == EV_WRITE)
+		ev.events = EPOLLOUT;
+	else
+		errx(1, "unkown event type");
+
+	ev.data.fd = fd;
+	if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) == -1)
+		err(1, "epoll_ctl");
+#else
+	struct kevent ev;
+	int filter;
+
+	if (type == EV_READ)
+		filter = EVFILT_READ;
+	else if (type == EV_WRITE)
+		filter = EVFILT_WRITE;
+	else
+		errx(1, "unkown event type");
+
+	EV_SET(&ev, fd, filter, EV_ADD, 0, 0, 0);
+	if (kevent(kqfd, &ev, 1, NULL, 0, NULL) == -1)
+		err(1, "kevent");
+#endif
+	/* printf("added fd: %d\n", fd); */
+}
+
+void
+fifo_read(void)
+{
+	char	 buffer [BUFSIZE];
 	char	*buf;
 	char	*cmd;
 	ssize_t	 n;
 
-	UNUSED(revents);
-
-	n = readline(fd, buffer, sizeof(buffer));
+	n = readline(fifofd, buffer, sizeof(buffer));
 	if (n == -1) {
-		if (errno == EAGAIN || errno == EWOULDBLOCK)
-			return;
-		printf("error: %d: read: %s\n", fd, strerror(errno));
-		clean_exit(loop, 1);
-	} else if (n == 0) {	/* reopen fifo again */
-		picoev_del(loop, fd);
-		close(fd);
-		fd = fifo_open((char *)cb_arg);
-		picoev_add(loop, fd, PICOEV_READ, 0, fifo_event_cb, cb_arg);
+		err(1, "fifo: read");
+	} else if (n == 0) {
+		/* reopen fifo again */
+		close(fifofd);
+		fifofd = fifo_open(fifopath);
+		fd_register(fifofd, EV_READ);
 		return;
 	}
 
@@ -165,15 +313,15 @@
 		char *port = split(&buf, ' ');
 		char *chan = buf;
 		if (!*name || !*symb || !*host || !*port || !*chan)
-			printf("usage: netadd <name> <symbol> <hostname> <port> <channel>\n");
+			printf("usage: netadd <name> <symbol> <host> <port> <channel>\n");
 		else
-			net_add(loop, name, symb, host, port, chan);
+			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(loop, name);
+			net_del(name);
 	} else if (strcmp(cmd, "print") == 0) {
 		print_table();
 	} else if (strcmp(cmd, "htable") == 0) {
@@ -181,90 +329,139 @@
 	} else if (strcmp(cmd, "users") == 0) {
 		print_users();
 	} else if (strcmp(cmd, "exit") == 0) {
-		done = 1;
+		done = TRUE;
 	} else {
-		printf("error: %s is not a command\n", cmd);
+		warnx("%s is not a command", cmd);
 	}
 }
 
-void
-server_event_cb(picoev_loop *loop, int fd, int revents, void *cb_arg)
+time_t
+event_timeout(void)
 {
-	char	 buffer	[BUFFER_LEN];
-	char	 backup	[BUFFER_LEN];
-	char	 lnick	[NICK_LEN];	/* linker nick */
-	char	*buf;
-	char	*cmd;
-	char	*nick;
-	int	 i, id, netid;
-	ssize_t	 n;
+	static Htiter it = {0};
 
-	id = (int)((struct event *)cb_arg - events);
-	netid = events[id].netid;
+	int i, j, *ids;
+	char *nick;
+	struct network *n;
+	time_t ntime;
 
-	if ((revents & PICOEV_TIMEOUT) != 0) {
-		int	*ids, cfd, count = 0;
-		Htiter	 it = {0};
+	/* 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);
 
-		printf("info: time to clone %d users\n", CLONE_ADDEND);
+		j = 0;
 		while (htiterate(users, &it)) {
+			nick = (char *)it.node->key;
 			ids = (int *)it.node->val;
-			buf = (char *)it.node->key;
-			if (ids[netid] == 0) {
-				cfd = clone_add(loop, buf, netid);
-				ids[netid] = cfd;
-				printf("%d. %d: %s\n", count, cfd, buf);
-				count++;
-				if (count >= CLONE_ADDEND) {
-					picoev_set_timeout(loop, fd, CLONE_COOLDOWN);
-					return;
-				}
+			for (i = 0; i < netlen; i++) {
+				n = &networks[i];
+				if (!n->ready || ids[i] != 0)
+					continue;
+				ids[i] = clone_add(nick, i);
+				printf("%d: %s\n", events[ids[i]].fd, nick);
 			}
+			if (++j >= CLONE_ADDEND)
+				goto calculate;
 		}
-		picoev_set_timeout(loop, fd, 0);
-		return;
-	}
+		state = IDLE;
+		it.index = 0;
+		it.node = NULL;
+	/* } */
 
-	if ((revents & PICOEV_WRITE) != 0) {
-		if (events[id].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);
-			writeall(fd, msg);
-			snprintf(msg, sizeof(msg), "USER user 0 * :user\r\n");
-			writeall(fd, msg);
+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;
 		}
-		picoev_set_events(loop, fd, PICOEV_READ);
+	} */
+	return ntime;
+}
+
+void
+event_write(int id)
+{
+	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");
+#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");
+	fd_register(fd, EV_READ);
+#endif
+	if (events[id].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);
+		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)
+{
+	char	 buffer	[BUFSIZE];
+	char	 backup	[BUFSIZE];
+	char	 lnick	[NICK_LEN];	/* linker nick */
+	char	*buf;
+	char	*cmd;
+	char	*nick;
+	int	 i, fd, netid;
+	ssize_t	 n;
+
+	fd = events[id].fd;
+	netid = events[id].netid;
+	events[id].time = time(NULL);
+
 	n = readline(fd, buffer, sizeof(buffer));
 	if (n == -1) {
-		if (errno == EAGAIN || errno == EWOULDBLOCK)
-			return;
-		printf("error: read: %d: %s\n", fd, strerror(errno));
-		event_del(loop, id);
-	} else if (n == 0) {	/* reopen fifo again */
-		printf("error: closed: %d\n", fd);
-		event_del(loop, id);
+		warn("%d: read", fd);
+		event_del(id);
+		return 0;
+	} else if (n == 0) {
+		warnx("%d: connection closed", fd);
+		event_del(id);
+		return 0;
 	}
 
-	/* remove CRLFs */
-	for (i = 0; i < (int)strlen(buffer); i++) {
-		if (buffer[i] == '\r' || buffer[i] == '\n') {
-			buffer[i] = '\0';
-			break;
-		}
-	}
+	if (!*buffer)
+		return 0;
 
 	/* clone the buffer */
-	strcpy(backup, buffer);
+	strlcpy(backup, buffer, sizeof(buffer));
 	buf = buffer;
 
 	/* set linker nick */
-	strcpy(lnick, "linker");
+	strlcpy(lnick, "linker", sizeof(lnick));
 	for (i = 0; i < events[id].suffix; i++)
 		strcat(lnick, "_");
 
@@ -271,13 +468,13 @@
 	/* first column */
 	cmd = split(&buf, ' ');
 	if (strcmp(cmd, "NOTICE") == 0) {
-		return;
+		return 0;
 	} else if (strcmp(cmd, "ERROR") == 0) {
 		goto printbuffer;
 	} else if (strcmp(cmd, "PING") == 0) {
 		snprintf(msg, sizeof(msg), "PONG %s\r\n", buf);
 		writeall(fd, msg);
-		return;
+		goto printbuffer;
 	}
 	/* strip nick from first column */
 	nick = split(&cmd, '!');
@@ -301,6 +498,8 @@
 	    || (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)
@@ -308,17 +507,17 @@
 	    || (strcmp(cmd, "366") == 0)
 	    || (strcmp(cmd, "MODE") == 0)
 	    || (strcmp(cmd, "NOTICE") == 0)) {
-		return;
+		return 0;
 	} else if (strcmp(cmd, "433") == 0) {	/* Nickname already in use */
 		split(&buf, ' ');
 		nick = split(&buf, ' ');
 		if (strlen(nick)+1 > NICK_LEN) {
-			printf("error: cannot append suffix, nick '%s' is too big\n", nick);
+			warnx("nick '%s' is too big", nick);
 			if (strcmp(nick, lnick) == 0) {
-				net_del(loop, networks[netid].name);
+				net_del(networks[netid].name);
 			} else {
 				snprintf(msg, sizeof(msg), "QUIT :nick is too big\r\n");
-				user_del(loop, nick, msg);
+				user_del(nick, msg);
 			}
 		} else {
 			strcat(nick, "_");
@@ -326,19 +525,19 @@
 			snprintf(msg, sizeof(msg), "NICK %s\r\n", nick);
 			writeall(fd, msg);
 		}
-		return;
+		return 0;
 	} else if (strcmp(cmd, "001") == 0) {
 		snprintf(msg, sizeof(msg), "JOIN %s\r\n", networks[netid].chan);
 		writeall(fd, msg);
-		return;
+		return 0;
 	} else if (strcmp(cmd, "PRIVMSG") == 0) {
-		char privmsg[BUFFER_LEN] = "";
+		char privmsg [BUFSIZE] = "";
 		int *ids;
 
 		if (events[id].user == NULL) {	/* if linker */
 			nick_add_symb(nick, netid);
 			if ((ids = htsearch(users, nick)) == NULL)
-				return;
+				return 0;
 
 			split(&buf, ':');	/* set buf to msg */
 			privmsg_update(privmsg, buf, netid);
@@ -354,11 +553,11 @@
 
 			/* ignore messages from channel (it is handled by linker) */
 			if ((user[0] == '#') || (user[0] == '&'))
-				return;
+				return 0;
 
 			nick_add_symb(nick, netid);
 			if ((ids = htsearch(users, nick)) == NULL)
-				return;
+				return 0;
 
 			/* split user nick and network symbol */
 			*strrchr(user, ']') = '\0';
@@ -376,22 +575,27 @@
 			snprintf(msg, sizeof(msg), "PRIVMSG %s :%s\r\n", user, privmsg);
 			writeall(events[ids[i]].fd, msg);
 		}
-		return;
+		return 0;
 	}
 	/* these messages are handled by linker */
 	if (events[id].user != NULL) {	/* if clone */
-		if ((strcmp(cmd, "353") == 0)
-		    || (strcmp(cmd, "JOIN") == 0)
+		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;
+		    || (strcmp(cmd, "NICK") == 0)) {
+			return 0;
+		}
 	} else if (strcmp(cmd, "353") == 0) {
 		char *nick;
 		split(&buf, ':');
-		networks[netid].join = 1;
-		/* net_update(loop, netid); */
+		networks[netid].ready = TRUE;
+		state = RESET;
+
+		/* then add all new users */
 		while (*(nick = split(&buf, ' ')) != '\0') {
 			if (*nick == '@'
 			    || *nick == '&'
@@ -401,26 +605,27 @@
 			    || *nick == '\\')
 				nick++;
 			if (strcmp(nick, lnick) != 0)
-				user_add(loop, nick, netid);
+				user_add(nick, netid, FALSE);
 		}
-		for (i = 0; i < netlen; i++)
-			picoev_set_timeout(loop, events[networks[i].id].fd, CLONE_COOLDOWN);
-		return;
+		return 1;
 	} else if (strcmp(cmd, "JOIN") == 0) {
 		if ((strcmp(nick, lnick) != 0)
-		    && (clone_get_user_ids(nick, netid) == NULL)) {	/* if not clone */
-			user_add(loop, nick, netid);
-			for (i = 0; i < netlen; i++)
-				picoev_set_timeout(loop, events[networks[i].id].fd, CLONE_COOLDOWN);
+		    && (user_event_ids(nick, netid) == NULL)) {	/* if not clone */
+			if (state != IDLE)
+				warn("ignoring user '%s' due to network cloning", nick);
+			else
+				user_add(nick, netid, TRUE);
 		}
-		return;
+		return 0;
 	} else if ((strcmp(cmd, "QUIT") == 0)
 		   || (strcmp(cmd, "PART") == 0)) {
 		snprintf(msg, sizeof(msg), "QUIT :%s\r\n", buf);
 		nick_add_symb(nick, netid);
-		if (htsearch(users, nick) != NULL)
-			user_del(loop, nick, msg);
-		return;
+		if (htsearch(users, nick) != NULL) {
+			printf("userdel: %s\n", nick);
+			user_del(nick, msg);
+		}
+		return 0;
 	} else if (strcmp(cmd, "NICK") == 0) {
 		int *ids, i;
 		char *newnick;
@@ -427,7 +632,7 @@
 
 		nick_add_symb(nick, netid);
 		if ((ids = htsearch(users, nick)) == NULL)
-			return;
+			return 0;
 
 		/* set buf to new nick */
 		split(&buf, ':');
@@ -441,7 +646,7 @@
 			if (ids[i] > 0)
 				writeall(events[ids[i]].fd, msg);
 		}
-		return;
+		return 0;
 	} else if (strcmp(cmd, "KICK") == 0) {
 		/* :<nick_which_is_kicking>!~user@host KICK <channel> <nick_which_has_been_kicked> :<kick_msg> */
 		int *ids;
@@ -453,20 +658,20 @@
 
 		/* delete whole network if it is the linker */
 		if (strcmp(user, lnick) == 0) {
-			net_del(loop, networks[netid].name);
-			return;
+			net_del(networks[netid].name);
+			return 0;
 		}
 
 		/* delete the user if the message from the same network */
-		if ((ids = clone_get_user_ids(user, netid)) == NULL) {
+		if ((ids = user_event_ids(user, netid)) == NULL) {
 			nick_add_symb(user, netid);
-			user_del(loop, user, msg);
-			return;
+			user_del(user, msg);
+			return 0;
 		}
 
 		/* close the kicked fd */
 		writeall(fd, msg);
-		event_del(loop, id);
+		event_del(id);
 
 		/*
 		 * send notice in the channel through linker
@@ -485,32 +690,32 @@
 			"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;
+		return 0;
 	}
 printbuffer:
-	printf("%d: %s\n", fd, backup);
+	return 0;
 }
 
 void
-net_add(picoev_loop *loop, char *name, char *symb, char *host, char *port, char *chan)
+net_add(char *name, char *symb, char *host, char *port, char *chan)
 {
-	int		i, fd;
 	struct network *n;
+	int	i, fd;
 
 	/* if name, symbol or configuration already exists */
 	for (i = 0; i < netlen; i++) {
 		if (strcmp(networks[i].name, name) == 0) {
-			printf("error: network name '%s' already exists\n", name);
+			warnx("network name '%s' already exists", name);
 			return;
 		}
 		if (strcmp(networks[i].symb, symb) == 0) {
-			printf("error: network symbol '%s' already exists\n", symb);
+			warnx("network symbol '%s' already exists", symb);
 			return;
 		}
 		if ((strcmp(networks[i].host, host) == 0)
 		    && (strcmp(networks[i].port, port) == 0)
 		    && (strcmp(networks[i].chan, chan) == 0)) {
-			printf("error: network configuration already exists\n");
+			warnx("network configuration already exists");
 			return;
 		}
 	}
@@ -518,11 +723,11 @@
 	/* resize if full */
 	if (netlen == netcap) {
 		Htiter it = {0};
-		networks = erecalloc(networks, (size_t)netcap, NET_ADDEND,
-				     sizeof(struct network));
+		networks = realloc0(networks, sizeof(struct network) * (size_t)netcap,
+				     sizeof(struct network) * (size_t)(netcap + NET_ADDEND));
 		while (htiterate(users, &it))
-			it.node->val = erecalloc(it.node->val, (size_t)netcap,
-						 NET_ADDEND, sizeof(int));
+			it.node->val = realloc0(it.node->val, sizeof(int) * (size_t)netcap,
+						sizeof(int) * (size_t)(netcap + NET_ADDEND));
 		netcap += NET_ADDEND;
 	}
 
@@ -531,19 +736,20 @@
 		return;
 	/* add a network */
 	n = &networks[netlen];
-	n->id = event_add(loop, fd, netlen, NULL);
-	n->join = 0;
+	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;
 	netlen++;
+
 	printf("%d: network '%s' added\n", fd, name);
 }
 
 void
-net_del(picoev_loop *loop, char *name)
+net_del(char *name)
 {
 	int	i, netid, *ids;
 
@@ -559,7 +765,7 @@
 		}
 	}
 	if (netid == -1) {
-		printf("error: network '%s' doesn't exist\n", name);
+		warnx("network '%s' doesn't exist", name);
 		return;
 	}
 
@@ -571,7 +777,7 @@
 		ids = (int *)it.node->val;
 		/* delete all the users of deleting network */
 		if (ids[netid] == -1) {
-			user_del(loop, it.node->key, msg);
+			user_del(it.node->key, msg);
 			/* this node is deleted */
 			it = lastit;
 		/* delete the clones */
@@ -578,7 +784,7 @@
 		} else {
 			if (ids[netid] > 0) {
 				writeall(events[ids[netid]].fd, msg);
-				event_del(loop, ids[netid]);
+				event_del(ids[netid]);
 			}
 			/* swap last with current one */
 			ids[netid] = ids[netlen-1];
@@ -587,13 +793,13 @@
 	}
 
 	/* set last netid of events to current netid. */
-	for (i = 0; i < evlen; i++) {
+	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(loop, networks[netid].id);
+	event_del(networks[netid].id);
 	net_del_raw(netid);
 	printf("%d: network '%s' deleted\n", events[networks[netid].id].fd, name);
 	/* swap the network with the last */
@@ -613,103 +819,84 @@
 }
 
 void
-net_update(picoev_loop *loop, int netid)
+user_add(char *unick, int netid, int clone)
 {
-	Htiter it = {0};
-	while (htiterate(users, &it))
-		((int *)it.node->val)[netid] = clone_add(loop, it.node->key,
-							 netid);
-}
-
-void
-user_add(picoev_loop *loop, char *unick, int netid)
-{
-	int	i, *ids;
-	size_t	len;
+	size_t	 len;
 	char	*nick;
+	int	*ids, i;
 
 	len = strlen(unick) + strlen(networks[netid].symb) + 2 + 1;
 
 	/* too long nick */
-	if (len - 1 > NICK_LEN) {
-		printf("error: user nick '%s' is too big\n", unick);
-		return;
-	}
+	if (len - 1 > NICK_LEN)
+		warnx("nick '%s' is too big", unick);
 
 	/* resize hash table if storage is low */
 	if ((users->cap - users->len) < USER_ADDEND)
 		htresize(users, users->cap + USER_ADDEND);
 
-	printf("useradd: %s\n", unick);
-
 	/* allocate a new user */
 	nick = ecalloc(len, sizeof(char));
 	ids = ecalloc((size_t)netcap, sizeof(int));
 	sprintf(nick, "%s[%s]", unick, networks[netid].symb);
+	ids[netid] = -1;
 
-	/* clone the user on all other network */
-	for (i = 0; i < netlen; i++) {
-		if (networks[i].join == 0)
-			continue;
-		if (i == netid) {
-			ids[i] = -1;
-		} else {
-			/* ids[i] = clone_add(loop, nick, i); */
+	printf("useradd: %s\n", nick);
+
+	if (clone) {
+		/* clone the user on all other network */
+		for (i = 0; i < netlen; i++) {
+			if (networks[i].ready == FALSE)
+				continue;
+			if (i != netid) {
+				ids[i] = clone_add(nick, i);
+				printf("%d: %s\n", events[ids[i]].fd, nick);
+			}
 		}
 	}
+
 	/* insert it to the users hash table */
-	if (htinsert(users, nick, ids) == -1) {
+	if (htinsert(users, nick, ids) == -1)
 		/* this shouldn't happen as it was already checked */
-		printf("error: user '%s' already exists\n", nick);
-		clean_exit(loop, 1);
-	}
+		errx(1, "user '%s' already exists", nick);
 }
 
 void
-user_del(picoev_loop *loop, char *nick, char *msg)
+user_del(char *nick, char *msg)
 {
-	int i;
-	int *ids = user_get_ids(nick);
+	int i, *ids;
 
+	if ((ids = htsearch(users, nick)) == NULL)
+		errx(1, "user '%s' 1 doesn't exists", nick);
 	for (i = 0; i < netlen; i++) {
-		if (ids[i] > 0) {
+		if (ids[i] <= 0)
+			continue;
+		if (events[ids[i]].ready == TRUE)
 			writeall(events[ids[i]].fd, msg);
-			event_del(loop, ids[i]);
-		}
+		event_del(ids[i]);
 	}
 	htremove(users, nick);
 }
 
 int *
-user_get_ids(char *user)
+user_event_ids(char *nick, int netid)
 {
-	int *ids;
-	if ((ids = htsearch(users, user)) == NULL) {
-		printf("error: cannot find user '%s'\n", user);
-		exit(1);
-	}
-	return ids;
-}
-
-int *
-clone_get_user_ids(char *nick, int netid)
-{
-	unsigned int suffix;
+	unsigned int s;
 	int *ids = NULL;
 
 	/* count suffix */
-	for (suffix = 0; nick[strlen(nick)-suffix-1] == '_'; suffix++);
+	for (s = 0; nick[strlen(nick)-s-1] == '_'; s++);
 	/* remove suffix */
-	if (suffix > 0)
-		nick[strlen(nick)-suffix] = '\0';
+	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)suffix))
+	if ((ids != NULL) && (events[ids[netid]].suffix != (int)s))
 		ids = NULL;
 
 	/* add suffix back */
-	if (suffix > 0)
+	if (s > 0)
 		nick[strlen(nick)] = '_';
 
 	return ids;
@@ -716,64 +903,78 @@
 }
 
 int
-clone_add(picoev_loop *loop, char *nick, int netid)
+clone_add(char *nick, int netid)
 {
-	int		fd;
-	struct network	*n = &networks[netid];
+	struct network	*n;
+	int		 fd;
 
-	if ((fd = dial(n->host, n->port)) == -1)
+	n = &networks[netid];
+	if ((fd = dial(n->host, n->port)) == -1) {
+		warn("enable to connect to %s\n", n->host);
 		return -1;
-	return event_add(loop, fd, netid, nick);
+	}
+	return event_add(fd, netid, nick);
 }
 
 int
-event_add(picoev_loop *loop, int fd, int netid, char *user)
+event_add(int fd, int netid, char *user)
 {
-	int i = evlen;
+	int i = evslen;
 
-	if (evlen == evcap) {
-		events = erecalloc(events, (size_t)evcap, EVENT_ADDEND,
-				   sizeof(struct event));
-		evcap += EVENT_ADDEND;
+	if (evslen == evscap) {
+		events = realloc0(events, sizeof(struct event) * (size_t)evscap,
+				   sizeof(struct event) * (size_t)(evscap + EVENT_ADDEND));
+		evscap += EVENT_ADDEND;
 	}
 
-	events[i].fd = fd;
-	events[i].netid = netid;
-	events[i].user = user;
+	events[i].fd	 = fd;
+	events[i].netid	 = netid;
+	events[i].user	 = user;
 	events[i].suffix = 0;
-	picoev_add(loop, fd, PICOEV_WRITE, 0, server_event_cb, &events[i]);
+	events[i].time	 = 0;
+	events[i].ready	 = FALSE;
 
-	return evlen++;
+	fd_register(fd, EV_WRITE);
+
+	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++;
 }
 
 void
-event_del(picoev_loop *loop, int id)
+event_del(int id)
 {
 	int *ids;
-	int  l = evlen - 1;			/* last id */
+	int  l = evslen - 1;		/* last id */
 
 	/* swap id */
-	if (events[l].user == NULL) {			/* if linker */
+	if (events[l].user == NULL) {	/* if linker */
 		networks[events[l].netid].id = id;
-	} else {					/* if user */
-		ids = user_get_ids(events[l].user);
+	} 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;
 	}
 
 	/* disable id */
-	if (events[id].user == NULL) {			/* if linker */
+	if (events[id].user == NULL) {	/* if linker */
 		networks[events[id].netid].id = -2;
-	} else {					/* if user */
-		ids = user_get_ids(events[id].user);
+	} 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;
 	}
 
-	picoev_del(loop, events[id].fd);
 	close(events[id].fd);
 
 	events[id] = events[l];
-	events[id] = events[l];
-	evlen--;
+	evslen--;
+	fdtoid[events[id].fd] = id;
 }
 
 void
@@ -805,7 +1006,7 @@
 		}
 
 		/* check if the word is nick */
-		if (clone_get_user_ids(src, netid) != NULL)
+		if (user_event_ids(src, netid) != NULL)
 			*strrchr(src, '[') = '\0';
 
 		strcat(dst, src);
@@ -812,37 +1013,6 @@
 		strncat(dst, &d, 1);
 
 		src = n;
-	}
-}
-
-void
-clean_exit(picoev_loop *loop, int status)
-{
-	int i;
-	snprintf(msg, sizeof(msg), "QUIT :linker shutting down\r\n");
-	for (i = 0; i < evlen; i++) {
-		writeall(events[i].fd, msg);
-		event_del(loop, i);
-	}
-	picoev_destroy_loop(loop);
-	picoev_deinit();
-
-	/* delete all the users */
-	htdestroy(users);
-
-	/* delete all the networks */
-	for (i = 0; i < netlen; i++)
-		net_del_raw(i);
-
-	free(networks);
-	free(events);
-
-	if (status == 0) {
-		printf("exit successfully\n");
-		exit(0);
-	} else {
-		printf("aborted\n");
-		exit(1);
 	}
 }