wm: ticl

Download patch

ref: 66e66acea622b6183cdf8767c4d268bfe100bce0
parent: 8df402e53d02ca2c0b976715ce57c28750dd99d9
author: libredev <libredev@ircforever.org>
date: Sat Nov 19 18:30:10 EST 2022

fix duplicate clone and replace select() with poll()

--- a/Makefile
+++ b/Makefile
@@ -4,7 +4,7 @@
 PREFIX = /usr/local
 
 CC     = cc
-CFLAGS = -g -std=c89 -Wall -Wextra -pedantic -Wconversion\
+CFLAGS = -g -std=c89 -Wall -Wextra -pedantic -Wfatal-errors -Wconversion\
 	 -Wstrict-prototypes -Wold-style-definition\
 	 -D_POSIX_C_SOURCE=200809L
 #CFLAGS += -fsanitize=address -fno-omit-frame-pointer
--- a/README
+++ b/README
@@ -70,6 +70,10 @@
 
 	$ ./ticl ./in
 
+Or, to start the program with the log printing to a file:
+
+	$ ./ticl ./in > ticl.log
+
 To link channel #test20 from libera and channel #test21 from ircnow:
 
 	$ echo 'netadd libera L irc.libera.chat 6667 #test20' > ./in
@@ -79,6 +83,17 @@
 
 	$ echo 'netdel ircnow' > ./in
 
+To list all the sockets (connections):
+
+	$ echo 'print' > ./in
+
+	# -1 indicate original user
+	# -2 indicate kicked clone
+
+To list all the users:
+
+	$ echo 'users' > ./in
+
 To shutdown the program:
 
 	$ echo exit > ./in
@@ -163,36 +178,9 @@
 See COPYING file for more information.
 
 
-FAQ
----
+Note
+----
 
-Why not GPL?
-------------
-
-I was a big fan of GPL but slowly I realized that people should choose free
-software on their own intention and not by force. They should understand that
-free culture is the way to the future. But I don't hate GPL and I think it is
-a great license for new technologies and innovations that can be exploited by
-corporate.
-
-
-Why not MIT/BSD?
-----------------
-
-I hate attribution licenses like MIT, BSD or any permissive license because
-I think the attribution requirement is complete nonsense.
-
-
-Why shouldn't you call it open source?
---------------------------------------
-
-I hate the term 'open source' because it has nothing to do with free software
-and its philosophy. It is a misleading term used by corporate that refer to
-open collaboration.
-
-But that's not why you shouldn't call it open source, but because the
-Open Source Initiative (OSI) doesn't consider public domain software to be
-open source. For more information, visit https://opensource.org/node/878.
-
-It is free software because the FSF considers the public domain software to be
-free software.
+It is a free software and please do not call it open source as the
+Open Source Initiative (OSI) does not consider public domain software
+as open source. https://opensource.org/node/878
--- a/main.c
+++ b/main.c
@@ -8,8 +8,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <sys/select.h>
-#include <sys/stat.h>
+#include <poll.h>
 #include <unistd.h>
 
 #include "htable.h"
@@ -16,17 +15,20 @@
 #include "util.c"
 
 #define BUFFER_LEN	1024
-#define MAX_FD		1024
-
 #define MAX_NICK_LEN	16
+
+#define FD_ADDEND	100
+#define POLLFD_ADDEND	100
 #define NET_ADDEND	10
-#define USER_ADDEND	5
+#define USER_ADDEND	100
 
-#define FD_TYPE_LINKER	1
-#define FD_TYPE_CLONE	2
+#define FIFO_FD		1
+#define LINKER_FD	2
+#define CLONE_FD	3
 
 typedef struct {
 	int	 fd;	/* fd of linker	*/
+	int	 join;	/* is joined	*/
 	char	*name;	/* name		*/
 	char	*symb;	/* symbol	*/
 	char	*host;	/* hostname	*/
@@ -34,34 +36,41 @@
 	char	*chan;	/* channel	*/
 } Network;
 
-static int	 isrunning = 1;
+/* user defined data for each fd */
+struct fdData {
+	int	netid;		/* net index		*/
+	int	type;		/* fd type		*/
+	int	suffix;		/* fd suffix count	*/
+};
 
-static fd_set	 fdset;			/* fd_set (select)	*/
-static int	 nfds;			/* number of fds	*/
+static struct pollfd	*pfdset;	/* pollfd set		*/
+static struct fdData	*fddata;	/* fd user data set	*/
+static int		 pfdlen;	/* pollfd length	*/
+static int		 pfdcap;	/* pollfd capacity	*/
+static int		*fdtoid;	/* fd to pfdset index	*/
+static int		 fdscap;	/* fds capacity		*/
 
-static int	 fdnet	[MAX_FD];	/* fd -> network index	*/
-static int	 fdtype	[MAX_FD];	/* fd -> fd type	*/
-static int	 fdsuf	[MAX_FD];	/* fd -> nick suffix	*/
+static Network	*networks;	/* linked list of networks	*/
+static int	 netlen;	/* current network length	*/
+static int	 netcap;	/* total memory allocated	*/
+static Htable	*users;		/* users-clones hash table	*/
 
-static Network	*networks;		/* linked list of networks */
-static int	 netlen;		/* current network length  */
-static int	 netcap;		/* total memory allocated  */
-static Htable	*users;	
+char		msg[BUFFER_LEN];
+static int	isrunning = 1;
 
 /* functions prototype */
-void	 handle_fifo_input(int);
+void	 handle_fifo_input(int, char *);
 void	 handle_server_output(int);
 void	 net_add(char *, char *, char *, char *, char *);
 void	 net_del(char *);
 void	 net_del_raw(int);
-void	 net_resize(void);
-void	 net_update(void);
+void	 net_update(int);
 void	 user_add(char *, int);
 void	 user_del(char *, char *);
 int	*user_fds(char *, int);
 int	 clone_add(int, char *);
-int	 fd_add(char *, char *);
-void	 fd_del(int, char *);
+void	 fd_add(int, int, int, int);
+void	 fd_del(int);
 void   	 nick_add_symb(char *, int);
 void   	 privmsg_update(char *, char *, int);
 void   	 terminate(int);
@@ -73,9 +82,7 @@
 int
 main(int argc, char *argv[])
 {
-	int		fifo_fd, r, i;
-	fd_set		rdset;
-	struct stat	fifo_st;
+	int i, n;
 
 	/* set stdout to unbufferd */
 	setvbuf(stdout, NULL, _IONBF, 0);
@@ -86,10 +93,14 @@
 		return 1;
 	}
 
-	/* init networks and users */
+	/* init global variables */
+	pfdcap = POLLFD_ADDEND;
+	pfdset = ecalloc((size_t)pfdcap, sizeof(struct pollfd));
+	fddata = ecalloc((size_t)pfdcap, sizeof(struct fdData));
+	fdscap = FD_ADDEND;
+	fdtoid = ecalloc((size_t)fdscap, sizeof(int));
 	netcap = NET_ADDEND;
 	networks = ecalloc((size_t)netcap, sizeof(Network));
-
 	/*
 	 * hash table of users
 	 * key   -> <nickname> + '[' + <network_symbol> + ']'
@@ -98,53 +109,28 @@
 	 */
 	users = htcreate((KeyLenFn *)strlen, (KeyCmpFn *)strcmp, free, free, USER_ADDEND);
 
-	/* init fdnet array to -1 */
-	for (i = 0; i < MAX_FD; i++)
-		fdnet[i] = -1;
+	/* add fifo_fd */
+	fd_add(fifo_open(argv[1]), 0, FIFO_FD, 0);
 
-	/* crate or/and open fifo */
-	if (stat(argv[1], &fifo_st) == 0) {
-		if (!(fifo_st.st_mode & S_IFIFO)) {
-			printf("error: '%s' is not a fifo file\n", argv[1]);
-			return 1;
-		}
-	} else if (mkfifo(argv[1], S_IRWXU) != 0) {
-		printf("error: failed to create fifo file '%s'\n", argv[1]);
-		return 1;
-	}
-	fifo_fd = open(argv[1], O_RDWR);
-	if (fifo_fd < 0) {
-		printf("error: cannot open() '%s'\n", argv[1]);
-		return 1;
-	}
-
-	/* initialize fdset */
-	FD_ZERO(&fdset);
-	FD_SET(fifo_fd, &fdset);
-	nfds = fifo_fd + 1;
-
 	/* select loop */
 	while (isrunning) {
-		rdset = fdset;
-		r = select(nfds, &rdset, 0, 0, NULL);
-		if (r < 0) {
-			if (errno == EINTR)
-				continue;
-			printf("error: select: %s\n", strerror(errno));
+		if ((n = poll(pfdset, (nfds_t)pfdlen, -1)) == -1) {
+			printf("error: poll: %s\n", strerror(errno));
 			terminate(1);
-		} else if (r == 0) {
-			printf("error: select timeout\n");
-			terminate(1);
 		}
-
-		for (i = 0; i < nfds; i++) {
-			if (!FD_ISSET(i, &rdset))
+		for (i = 0; i < pfdlen; i++) {
+			if (!(pfdset[i].revents & (POLLIN|POLLHUP)))
 				continue;
-			if (i == fifo_fd)
-				handle_fifo_input(i);
+			printf("poll: %d\n", pfdset[i].fd);
+			if (fddata[i].type == FIFO_FD)
+				handle_fifo_input(i, argv[1]);
 			else
 				handle_server_output(i);
-			break;	/* one fd at a time because of FD_CLR */
+			/*
+			 * handle one ready fd at one time because if we
+			 * close upcoming ready fd, it cause infinite loop.
+			 */
+			break;
 		}
 	}
 	terminate(0);
@@ -152,15 +138,22 @@
 }
 
 void
-handle_fifo_input(int fd)
+handle_fifo_input(int id, char *path)
 {
 	char buffer[BUFFER_LEN];
 	char *buf;
 	char *cmd;
+	ssize_t n;
 
-	if (readline(fd, buffer, sizeof(buffer)) < 1) {
-		printf("error: %d: failed to read from fifo: %s\n", fd, strerror(errno));
-		terminate(1);
+	/* if failed to read data */
+	if ((n = readline(pfdset[id].fd, buffer, sizeof(buffer))) < 1) {
+		if (n == 0) {	/* restart again */
+			fd_del(pfdset[id].fd);
+			fd_add(fifo_open(path), 0, FIFO_FD, 0);
+		} else if ((errno != EAGAIN) && (errno != EINTR)) {
+			printf("error: read: %s\n", strerror(errno));
+		}
+		return;
 	}
 	/* printf("fifo: %s\n", buffer); */
 
@@ -196,38 +189,46 @@
 }
 
 void
-handle_server_output(int fd)
+handle_server_output(int id)
 {
-	char backup [BUFFER_LEN];
-	char buffer [BUFFER_LEN];
+	char buffer	[BUFFER_LEN];
+	char backup	[BUFFER_LEN];
 	char linker_nick [MAX_NICK_LEN];
 	char *buf;
 	char *cmd;
 	char *nick;
-	int i;
+	int i, netid;
+	ssize_t n;
 
-	if (readline(fd, buffer, sizeof(buffer)) < 1) {
-		printf("error: %d: remote host closed connection: %s\n", fd, strerror(errno));
-		terminate(1);
+	/* if failed to read data */
+	if ((n = readline(pfdset[id].fd, buffer, sizeof(buffer))) < 1) {
+		if (n == 0) {
+			printf("error: remote host closed connection: %s\n", strerror(errno));
+			/* fd_del(pfdset[id].fd); */
+		} else if ((errno != EAGAIN) && (errno != EINTR)) {
+			printf("error: read: %s\n", strerror(errno));
+		}
+		return;
 	}
 
+	/* remove CRLFs */
+	for (i = 0; i < (int)strlen(buffer); i++) {
+		if (buffer[i] == '\r' || buffer[i] == '\n') {
+			buffer[i] = '\0';
+			break;
+		}
+	}
+
 	/* clone the buffer */
 	strcpy(backup, buffer);
 	buf = buffer;
 
+	netid = fddata[id].netid;
 	/* set linker nick */
 	strcpy(linker_nick, "linker");
-	for (i = 0; i < fdsuf[fd]; i++)
+	for (i = 0; i < fddata[id].suffix; i++)
 		strcat(linker_nick, "_");
 
-	/* remove CRLFs */
-	for (i = 0; i < (int)strlen(buf); i++) {
-		if (buf[i] == '\r' || buf[i] == '\n') {
-			buf[i] = '\0';
-			break;
-		}
-	}
-
 	/* first column */
 	cmd = split(&buf, ' ');
 	if (strcmp(cmd, "NOTICE") == 0) {
@@ -235,7 +236,8 @@
 	} else if (strcmp(cmd, "ERROR") == 0) {
 		goto printbuffer;
 	} else if (strcmp(cmd, "PING") == 0) {
-		fdprintf(fd, "PONG %s\r\n", buf);
+		snprintf(msg, sizeof(msg), "PONG %s\r\n", buf);
+		writeall(pfdset[id].fd, msg);
 		return;
 	}
 	/* strip nick from first column */
@@ -271,38 +273,41 @@
 	} else if (strcmp(cmd, "433") == 0) {	/* Nickname already in use */
 		split(&buf, ' ');
 		nick = split(&buf, ' ');
-		strcat(nick, "_");
-		fdsuf[fd]++;
-		if (strlen(nick) > MAX_NICK_LEN) {
-			/* remove suffix from the nick */
-			nick[strlen(nick) - (size_t)fdsuf[fd]] = '\0';
+		if (strlen(nick)+1 > MAX_NICK_LEN) {
 			printf("error: cannot append suffix, nick '%s' is too big\n", nick);
-			if (strcmp(nick, "linker") == 0) {
-				net_del(networks[fdnet[fd]].name);
+			if (strcmp(nick, linker_nick) == 0) {
+				net_del(networks[netid].name);
 			} else {
-				user_del(nick, "nick is too big");
+				snprintf(msg, sizeof(msg), "QUIT :nick is too big\r\n");
+				user_del(nick, msg);
 			}
 		} else {
-			fdprintf(fd, "NICK %s\r\n", nick);
+			strcat(nick, "_");
+			fddata[id].suffix++;
+			snprintf(msg, sizeof(msg), "NICK %s\r\n", nick);
+			writeall(pfdset[id].fd, msg);
 		}
 		return;
 	} else if (strcmp(cmd, "001") == 0) {
-		fdprintf(fd, "JOIN %s\r\n", networks[fdnet[fd]].chan);
+		snprintf(msg, sizeof(msg), "JOIN %s\r\n", networks[netid].chan);
+		writeall(pfdset[id].fd, msg);
 		return;
 	} else if (strcmp(cmd, "PRIVMSG") == 0) {
-		char msg[BUFFER_LEN] = "";
+		char privmsg[BUFFER_LEN] = "";
 		int *fds;
 
-		if (fdtype[fd] == FD_TYPE_LINKER) {
-			nick_add_symb(nick, fd);
+		if (fddata[id].type == LINKER_FD) {
+			nick_add_symb(nick, netid);
 			if ((fds = htsearch(users, nick)) == NULL)
 				return;
 
 			split(&buf, ':');	/* set buf to msg */
-			privmsg_update(msg, buf, fd);
+			privmsg_update(privmsg, buf, netid);
 			for (i = 0; i < netlen; i++) {
-				if (fds[i] > 0)
-					fdprintf(fds[i], "PRIVMSG %s :%s\r\n", networks[i].chan, msg);
+				if (fds[i] > 0) {
+					snprintf(msg, sizeof(msg), "PRIVMSG %s :%s\r\n", networks[i].chan, privmsg);
+					writeall(fds[i], msg);
+				}
 			}
 		} else {
 			char *netsymb;
@@ -312,7 +317,7 @@
 			if ((user[0] == '#') || (user[0] == '&'))
 				return;
 
-			nick_add_symb(nick, fd);
+			nick_add_symb(nick, netid);
 			if ((fds = htsearch(users, nick)) == NULL)
 				return;
 
@@ -328,13 +333,14 @@
 			}
 
 			split(&buf, ':');	/* set buf to msg */
-			privmsg_update(msg, buf, fd);
-			fdprintf(fds[i], "PRIVMSG %s :%s\r\n", user, msg);
+			privmsg_update(privmsg, buf, netid);
+			snprintf(msg, sizeof(msg), "PRIVMSG %s :%s\r\n", user, privmsg);
+			writeall(fds[i], msg);
 		}
 		return;
 	}
 	/* these messages are handled by linker */
-	if (fdtype[fd] == FD_TYPE_CLONE) {
+	if (fddata[id].type == CLONE_FD) {
 		if ((strcmp(cmd, "353") == 0)
 		|| (strcmp(cmd, "JOIN") == 0)
 		|| (strcmp(cmd, "QUIT") == 0)
@@ -345,7 +351,8 @@
 	} else if (strcmp(cmd, "353") == 0) {
 		char *nick;
 		split(&buf, ':');
-		net_update();
+		networks[netid].join = 1;
+		net_update(netid);
 		while (*(nick = split(&buf, ' ')) != '\0') {
 			if (*nick == '@'
 			|| *nick == '&'
@@ -355,24 +362,25 @@
 			|| *nick == '\\')
 				nick++;
 			if (strcmp(nick, linker_nick) != 0)
-				user_add(nick, fd);
+				user_add(nick, netid);
 		}
 		return;
 	} else if (strcmp(cmd, "JOIN") == 0) {
 		if ((strcmp(nick, linker_nick) != 0)
-		&& (user_fds(nick, fd) == NULL))	/* if not clone */
-			user_add(nick, fd);
+		&& (user_fds(nick, netid) == NULL))	/* if not clone */
+			user_add(nick, netid);
 		return;
 	} else if ((strcmp(cmd, "QUIT") == 0)
 		|| (strcmp(cmd, "PART") == 0)) {
-		nick_add_symb(nick, fd);
-		user_del(nick, buf);
+		nick_add_symb(nick, netid);
+		snprintf(msg, sizeof(msg), "QUIT :%s\r\n", buf);
+		user_del(nick, msg);
 		return;
 	} else if (strcmp(cmd, "NICK") == 0) {
 		int *fds, i;
 		char *newnick;
 
-		nick_add_symb(nick, fd);
+		nick_add_symb(nick, netid);
 		if ((fds = htsearch(users, nick)) == NULL)
 			return;
 
@@ -379,13 +387,14 @@
 		/* 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[fdnet[fd]].symb) + 2 + 1, sizeof(char));
-		sprintf(newnick, "%s[%s]", buf, networks[fdnet[fd]].symb);
+		newnick = ecalloc(strlen(buf) + strlen(networks[netid].symb) + 2 + 1, sizeof(char));
+		sprintf(newnick, "%s[%s]", buf, networks[netid].symb);
 		htsetkey(users, nick, newnick);
 
+		snprintf(msg, sizeof(msg), "NICK %s\r\n", newnick);
 		for (i = 0; i < netlen; i++) {
 			if (fds[i] > 0)
-				fdprintf(fds[i], "NICK %s\r\n", newnick);
+				writeall(fds[i], msg);
 		}
 		return;
 	} else if (strcmp(cmd, "KICK") == 0) {
@@ -393,45 +402,49 @@
 		int *fds;
 		char *chan = split(&buf, ' ');
 		char *user = split(&buf, ' ');
-		char quit_msg[BUFFER_LEN];
 
 		/* set the quit msg */
-		snprintf(quit_msg, BUFFER_LEN, "kicked by %s", nick);
+		snprintf(msg, sizeof(msg), "QUIT : kicked by %s\r\n", nick);
 
+		/* delete whole network if it is the linker */
 		if (strcmp(user, linker_nick) == 0) {
-			net_del(networks[fdnet[fd]].name);
+			net_del(networks[netid].name);
 			return;
 		}
 
 		/* delete the user if the message from the same network */
-		if ((fds = user_fds(user, fd)) == NULL) {
-			nick_add_symb(user, fd);
-			user_del(user, quit_msg);
+		if ((fds = user_fds(user, netid)) == NULL) {
+			nick_add_symb(user, netid);
+			user_del(user, msg);
 			return;
 		}
 
-		/* remove netsymb and suffix */
-		*strrchr(user, '[') = '\0';
+		/* close the kicked fd */
+		writeall(fds[netid], msg);
+		fd_del(fds[netid]);
+		fds[netid] = -2;
 
-		/* get the original user fd index */
+		/*
+		 * send notice in the channel through linker
+		 */
+		/* get the original user netid */
 		for (i = 0; i < netlen; i++) {
 			if (fds[i] == -1)
 				break;
 		}
-
 		/* set buf to msg */
-		split(&buf, ':');	
-		/* send notice in the channel through linker */
-		fdprintf(networks[i].fd, "PRIVMSG %s :%s was kicked out by %s from network %s %s [%s]\r\n",
-				networks[i].chan, user, nick, networks[fdnet[fd]].name, chan, buf);
-		
-		/* close the kicked fd */
-		fd_del(fds[fdnet[fd]], quit_msg);
-		fds[fdnet[fd]] = -2;
+		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(networks[i].fd, msg);
 		return;
 	}
 printbuffer:
-	printf("%d: %s\n", fd, backup);
+	printf("%d: %s\n", pfdset[id].fd, backup);
 }
 
 void
@@ -440,7 +453,7 @@
 	int	 i, fd;
 	Network *n;
 
-	/* if name or symbol or configuration already exists */
+	/* 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);
@@ -451,29 +464,35 @@
 			return;
 		}
 		if ((strcmp(networks[i].host, host) == 0)
-		&& (strcmp(networks[i].port, port) == 0)
-		&& (strcmp(networks[i].chan, chan) == 0)) {
+		&&  (strcmp(networks[i].port, port) == 0)
+		&&  (strcmp(networks[i].chan, chan) == 0)) {
 			printf("error: network configuration already exists\n");
 			return;
 		}
 	}
 
-	/* resize if no space */
-	if (netlen == netcap)
-		net_resize();
+	/* resize if full */
+	if (netlen == netcap) {
+		Htiter it = {0};
+		networks = erecalloc(networks, (size_t)netcap, NET_ADDEND, sizeof(Network));
+		while (htiterate(users, &it))
+			it.node->val = erecalloc(it.node->val, (size_t)netcap, NET_ADDEND, sizeof(int));
+		netcap += NET_ADDEND;
+	}
 
 	/* connect */
-	if ((fd = fd_add(host, port)) == -1)
+	if ((fd = dial(host, port)) == -1)
 		return;
+	fd_add(fd, netlen, LINKER_FD, 0);
 	/* send NICK and USER commands */
-	fdprintf(fd, "NICK linker\r\n");
-	fdprintf(fd, "USER linker 0 * :linker\r\n");
+	snprintf(msg, sizeof(msg), "NICK linker\r\n");
+	writeall(fd, msg);
+	snprintf(msg, sizeof(msg), "USER linker 0 * :linker\r\n");
+	writeall(fd, msg);
 	/* add a network */
-	fdnet[fd] = netlen;
-	fdtype[fd] = FD_TYPE_LINKER;
-
 	n = &networks[netlen];
 	n->fd = fd;
+	n->join = 0;
 	n->name = strdup(name);
 	n->symb = strdup(symb);
 	n->host = strdup(host);
@@ -486,64 +505,66 @@
 void
 net_del(char *name)
 {
-	int	i, index, *fds;
-	char	quit_msg [BUFFER_LEN];
+	int	i, netid, *fds;
 
 	Htiter	it	= {0};	/* current iterator */
 	Htiter	lastit	= {0};	/* last iterator */
 
-	/* check if the name exists */
-	index = -1;
+	/* get netid */
+	netid = -1;
 	for (i = 0; i < netlen; i++) {
 		if (strcmp(name, networks[i].name) == 0) {
-			index = i;
+			netid = i;
 			break;
 		}
 	}
-	if (index == -1) {
-		printf("error: network name '%s' doesn't exist\n", name);
+	if (netid == -1) {
+		printf("error: network '%s' doesn't exist\n", name);
 		return;
 	}
 
 	/* set the quit msg */
-	snprintf(quit_msg, BUFFER_LEN, "unlinking network %s", name);
+	snprintf(msg, sizeof(msg), "QUIT :unlinking network %s\r\n", name);
 
-	/* set all fds with last network index to the current index. */
-	for (i = 0; i < nfds; i++) {
-		if (fdnet[i] == netlen-1)
-			fdnet[i] = index;
-	}
-
+	/* reconstruct the user-clones table */
 	while (htiterate(users, &it)) {
 		fds = (int *)it.node->val;
-		/* delete the user */
-		if (fds[index] == -1) {
-			user_del(it.node->key, quit_msg);
-			/* this node is deleted and has no next */
+		/* delete all the users of deleting network */
+		if (fds[netid] == -1) {
+			user_del(it.node->key, msg);
+			/* this node is deleted */
 			it = lastit;
-		/* delete the clone */
+		/* delete the clones */
 		} else {
-			if (fds[index] > 0)
-				fd_del(fds[index], quit_msg);
-			/* swap last index with the index */
-			fds[index] = fds[netlen-1];
-			fds[netlen-1] = 0;
+			if (fds[netid] > 0) {
+				writeall(fds[netid], msg);
+				fd_del(fds[netid]);
+			}
+			/* swap last with current one */
+			fds[netid] = fds[netlen-1];
 		}
 		lastit = it;
 	}
 
-	fd_del(networks[index].fd, quit_msg);
-	net_del_raw(index);
-	printf("%d: network '%s' deleted\n", networks[index].fd, name);
+	/* set pollfds netid with last netid to current netid. */
+	for (i = 0; i < pfdlen; i++) {
+		if (fddata[i].netid == netlen-1)
+			fddata[i].netid = netid;
+	}
+
+	writeall(networks[netid].fd, msg);
+	fd_del(networks[netid].fd);
+	net_del_raw(netid);
+	printf("%d: network '%s' deleted\n", networks[netid].fd, name);
 	/* swap the network with the last */
-	networks[index] = networks[netlen-1];
+	networks[netid] = networks[netlen-1];
 	netlen--;
 }
 
 void
-net_del_raw(int index)
+net_del_raw(int netid)
 {
-	Network *n = &networks[index];
+	Network *n = &networks[netid];
 	free(n->name);
 	free(n->symb);
 	free(n->host);
@@ -552,53 +573,25 @@
 }
 
 void
-net_resize(void)
+net_update(int netid)
 {
-	Htiter	 it = {0};
-
-	/* resize networks array */
-	if ((networks = realloc(networks, ((size_t)netcap + NET_ADDEND) * sizeof(Network))) == NULL)
-		goto error;
-	memset(networks + netcap, 0, NET_ADDEND * sizeof(Network));
-	/* also resize all the user's array */
-	while (htiterate(users, &it)) {
-		if ((it.node->val = realloc(it.node->val, ((size_t)netcap + NET_ADDEND) * sizeof(int))) == NULL)
-			goto error;
-		memset((int *)it.node->val + netcap, 0, NET_ADDEND * sizeof(int));
-	}
-	netcap += NET_ADDEND;
-	return;
-error:
-	printf("error: realloc: %s\n", strerror(errno));
-	terminate(1);
-}
-
-void
-net_update(void)
-{
 	int	*fds;
 	Htiter	 it = {0};
 
 	while (htiterate(users, &it)) {
 		fds = (int *)it.node->val;
-		/* netlen-1 can be occupied if networks are added quickly
-		 * and has not enough time to connect to it's server */
-		if (fds[netlen-1] == 0) {
-			fds[netlen-1] = clone_add(netlen-1, it.node->key);
-		}
+		fds[netid] = clone_add(netid, it.node->key);
 	}
 }
 
 void
-user_add(char *unick, int nfd)
+user_add(char *unick, int netid)
 {
-	int i, *fds;
-	Network *n;
-	size_t len;
-	char *nick;
+	int	i, *fds;
+	size_t	len;
+	char	*nick;
 
-	n = &networks[fdnet[nfd]];
-	len = strlen(unick) + strlen(n->symb) + 2 + 1;
+	len = strlen(unick) + strlen(networks[netid].symb) + 2 + 1;
 
 	/* too long nick */
 	if (len-1 > MAX_NICK_LEN) {
@@ -610,15 +603,18 @@
 	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));
 	fds = ecalloc((size_t)netcap, sizeof(int));
-	sprintf(nick, "%s[%s]", unick, n->symb);
+	sprintf(nick, "%s[%s]", unick, networks[netid].symb);
 
 	/* clone the user on all other network */
 	for (i = 0; i < netlen; i++) {
-		/* set -1 on user's network */
-		if (i == fdnet[nfd]) {
+		if (networks[i].join == 0)
+			continue;
+		if (i == netid) {
 			fds[i] = -1;
 		} else {
 			fds[i] = clone_add(i, nick);
@@ -637,36 +633,38 @@
 {
 	int *fds, i;
 	if ((fds = htsearch(users, nick)) == NULL) {
-		printf("error: user '%s' doesn't exits\n", nick);
+		printf("error: user '%s' doesn't exists\n", nick);
 		return;
 	}
 
 	for (i = 0; i < netlen; i++) {
-		if (fds[i] > 0)
-			fd_del(fds[i], msg);
+		if (fds[i] > 0) {
+			writeall(fds[i], msg);
+			fd_del(fds[i]);
+		}
 	}
 	htremove(users, nick);
 }
 
 int *
-user_fds(char *nick, int nfd)
+user_fds(char *nick, int netid)
 {
-	unsigned int suf;
+	unsigned int suffix;
 	int *fds = NULL;
 
 	/* count suffix */
-	for (suf = 0; nick[strlen(nick)-suf-1] == '_'; suf++);
+	for (suffix = 0; nick[strlen(nick)-suffix-1] == '_'; suffix++);
 	/* remove suffix */
-	if (suf > 0)
-		nick[strlen(nick)-suf] = '\0';
+	if (suffix > 0)
+		nick[strlen(nick)-suffix] = '\0';
 
 	fds = htsearch(users, nick);
 	/* if match but suffix doesn't match */
-	if ((fds != NULL) && (fdsuf[fds[fdnet[nfd]]] != (int)suf))
+	if ((fds != NULL) && (fddata[fdtoid[fds[netid]]].suffix != (int)suffix))
 		fds = NULL;
 
-	/* add back suffix */
-	if (suf > 0)
+	/* add suffix back */
+	if (suffix > 0)
 		nick[strlen(nick)] = '_';
 
 	return fds;
@@ -673,51 +671,59 @@
 }
 
 int
-clone_add(int netindex, char *nick)
+clone_add(int netid, char *nick)
 {
-	int fd;
-	Network *n = &networks[netindex];
+	int	fd;
+	Network	*n = &networks[netid];
 
-	if ((fd = fd_add(n->host, n->port)) == -1)
+	if ((fd = dial(n->host, n->port)) == -1)
 		return -1;
+	fd_add(fd, netid, CLONE_FD, 0);
 	/* send NICK and USER commands */
-	fdprintf(fd, "NICK %s\r\n", nick);
-	fdprintf(fd, "USER %s 0 * :%s\r\n", n->name, n->name);
-	/* add a user */
-	fdnet[fd] = netindex;
-	fdtype[fd] = FD_TYPE_CLONE;
-	fdsuf[fd] = 0;
+	snprintf(msg, sizeof(msg), "NICK %s\r\n", nick);
+	writeall(fd, msg);
+	snprintf(msg, sizeof(msg), "USER user 0 * :user\r\n");
+	writeall(fd, msg);
 	return fd;
 }
 
-int
-fd_add(char *host, char *port)
+void
+fd_add(int fd, int netid, int type, int suffix)
 {
-	int fd;
-	if ((fd = dial(host, port)) == -1)
-		return -1;
-
-	FD_SET(fd, &fdset);
-	if (fd+1 > nfds)
-		nfds = fd+1;
-
-	return fd;
+	/* resize if full */
+	if (fd+1 == fdscap) {
+		fdtoid = erecalloc(fdtoid, (size_t)fdscap, FD_ADDEND, sizeof(int));
+		fdscap += FD_ADDEND;
+	}
+	if (pfdlen == pfdcap) {
+		pfdset = erecalloc(pfdset, (size_t)pfdcap, POLLFD_ADDEND, sizeof(struct pollfd));
+		fddata = erecalloc(fddata, (size_t)pfdcap, POLLFD_ADDEND, sizeof(struct fdData));
+		pfdcap += POLLFD_ADDEND;
+	}
+	pfdset[pfdlen].fd = fd;
+	pfdset[pfdlen].events = POLLIN;
+	fddata[pfdlen].netid = netid;
+	fddata[pfdlen].type = type;
+	fddata[pfdlen].suffix = suffix;
+	fdtoid[fd] = pfdlen;
+	pfdlen++;
 }
 
 void
-fd_del(int fd, char *msg)
+fd_del(int fd)
 {
-	fdprintf(fd, "QUIT :%s\r\n", msg);
 	close(fd);
-	FD_CLR(fd, &fdset);
-	fdnet[fd] = -1;
+	pfdset[fdtoid[fd]] = pfdset[pfdlen-1];
+	fddata[fdtoid[fd]] = fddata[pfdlen-1];
+	fdtoid[fd] = pfdlen-1;
+	pfdlen--;
 }
 
 void
-nick_add_symb(char *nick, int fd)
+nick_add_symb(char *nick, int netid)
 {
 	strcat(nick, "[");
-	strcat(nick, networks[fdnet[fd]].symb);
+	strcat(nick, networks[netid].symb);
 	strcat(nick, "]");
 }
 
@@ -726,7 +732,7 @@
  * src will be destructed
  */
 void
-privmsg_update(char *dst, char *src, int fd)
+privmsg_update(char *dst, char *src, int netid)
 {
 	char d;		/* delimiter */
 	char *n;
@@ -742,7 +748,7 @@
 		}
 
 		/* check if the word is nick */
-		if (user_fds(src, fd) != NULL)
+		if (user_fds(src, netid) != NULL)
 			*strrchr(src, '[') = '\0';
 
 		strcat(dst, src);
@@ -756,11 +762,11 @@
 terminate(int status)
 {
 	int i;
-	for (i = 0; i < nfds; i++) {
-		if (fdnet[i] >= 0) {
-			/* printf("%d: shutting down\n", i); */
-			fd_del(i, "linker shutting down");
-		}
+	snprintf(msg, sizeof(msg), "QUIT :linker shutting down\r\n");
+	for (i = 0; i < pfdlen; i++) {
+		if (fddata[i].type != FIFO_FD)
+			writeall(pfdset[i].fd, msg);
+		fd_del(pfdset[i].fd);
 	}
 	/* delete all the users */
 	htdestroy(users);
@@ -768,13 +774,19 @@
 	/* delete all the networks */
 	for (i = 0; i < netlen; i++)
 		net_del_raw(i);
-
 	free(networks);
-	if (status == 0)
+
+	free(pfdset);
+	free(fddata);
+	free(fdtoid);
+
+	if (status == 0) {
 		printf("exit successfully\n");
-	else
+		exit(0);
+	} else {
 		printf("aborted\n");
-	exit(1);
+		exit(1);
+	}
 }
 
 void
@@ -806,8 +818,8 @@
 		for (i = 0; i < netlen; i++) {
 			printf("%d", fds[i]);
 			/* print suffix */
-			if ((fds[i] > 0) && (fdsuf[fds[i]] > 0))
-				printf("(%d)", fdsuf[fds[i]]);
+			if ((fds[i] > 0) && (fddata[fdtoid[fds[i]]].suffix > 0))
+				printf("(%d)", fddata[fdtoid[fds[i]]].suffix);
 			printf("\t");
 		}
 		printf("\n");
--- a/util.c
+++ b/util.c
@@ -4,6 +4,8 @@
  */
 
 #include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -10,10 +12,15 @@
 #include <string.h>
 #include <sys/types.h>
 #include <sys/socket.h>
+#include <sys/stat.h>
 #include <netdb.h>
+#include <poll.h>
 #include <unistd.h>
-#include <errno.h>
 
+
+/*
+ * ecalloc - calloc with error handling
+ */
 void *
 ecalloc(size_t nmemb, size_t size)
 {
@@ -20,11 +27,30 @@
 	void *p;
 	if ((p = calloc(nmemb, size)) == NULL) {
 		printf("error: calloc: %s\n", strerror(errno));
-		return NULL;
+		exit(1);
 	}
 	return p;
 }
 
+/*
+ * erecalloc -- allocate more memory, zero new memory, handle error
+ * 	ptr	- pointer of the old memory
+ * 	omemb	- no. of member of old pointer
+ * 	nmemb	- no. of member to extend
+ * 	size	- size of one member
+ */
+void *
+erecalloc(void *ptr, size_t omemb, size_t nmemb, size_t size)
+{
+	void *p;
+	if ((p = realloc(ptr, (omemb + nmemb) * size)) == NULL) {
+		printf("error: realloc: %s\n", strerror(errno));
+		exit(1);
+	}
+	/* memset(p + omemb * size, 0, nmemb * size); */
+	return p;
+}
+
 char*
 split(char **str, char ch)
 {
@@ -43,27 +69,46 @@
 	return token;
 }
 
-ssize_t
-fdprintf(int fd, const char *fmt, ...)
+int
+fifo_open(char *path)
 {
-	va_list args;
-	char buf[1024];
-	size_t len;
-	ssize_t n, i = 0;
+	struct stat st;
+	int fd;
 
-	va_start(args, fmt);
-	vsnprintf(buf, sizeof(buf), fmt, args);
-	va_end(args);
+	/* make fifo if it doesn't exists */
+	if (lstat(path, &st) != -1) {
+		if (!(st.st_mode & S_IFIFO)) {
+			printf("error: '%s' is not a fifo file\n", path);
+			return -1;
+		}
+	} else if (mkfifo(path, S_IRWXU) != 0) {
+		printf("error: failed to create fifo file '%s'\n", path);
+		return -1;
+	}
 
-	len = strlen(buf);
-	while (i < (ssize_t)len) {
-		if ((n = write(fd, buf + i, len - (size_t)i)) == -1) {
+	/* open fifo */
+	fd = open(path, O_RDONLY | O_NONBLOCK, 0);
+	if (fd == -1)
+		printf("error: cannot open() '%s'\n", 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) {
 			printf("error: write failed: %s\n", strerror(errno));
 			return -1;
 		}
-		i += n;
+		sent += n;
+		left -= n;
 	}
-	return i;
+	return sent;
 }
 
 int
@@ -83,7 +128,7 @@
 	for (res = res0; res != NULL; res = res->ai_next) {
 		if ((fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) < 0)
 			continue;
-		if (connect(fd, res->ai_addr, res->ai_addrlen) != 0) {
+		if (connect(fd, res->ai_addr, res->ai_addrlen) == -1) {
 			close(fd);
 			fd = -1;
 			continue;