wm: ticl

Download patch

ref: fb90d152ef67420872ed6f7a479b0fa6f201a725
parent: 202447793107a549c7e418dc3b2208caa89a0bb3
author: libredev <libredev@ircforever.org>
date: Wed Feb 15 17:45:58 EST 2023

implemented re-connection

--- a/main.c
+++ b/main.c
@@ -29,6 +29,7 @@
 
 #define CLONE_COOLDOWN	1
 #define CLONE_ADDEND	10
+#define RECONN_TIME	10
 
 #define FD_ADDEND	100
 #define NET_ADDEND	10
@@ -45,10 +46,10 @@
 	IDLE = 0,
 	RESET,
 	CLONING,
-	DONE
+	EXIT
 };
 
-struct vuser {
+struct fd_data {
 	char		*user;	/* nick		*/
 	int		netid;	/* net index	*/
 	int	 	suffix;	/* suffix count	*/
@@ -55,6 +56,12 @@
 	int	 	ready;	/* joined	*/
 };
 
+struct fd_ref {
+	char		*user;	/* nick		*/
+	int		netid;	/* net index	*/
+	struct fd_ref	*next;	/* next node	*/
+};
+
 struct network {
 	int		 fd;	/* fd		*/
 	char		*name;	/* name		*/
@@ -80,11 +87,14 @@
 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 fd_data	**fdtodata;	/* fd -> data pointer	*/
+static int		  fdcap;	/* fdtodata capacity	*/
 static struct htable	 *usertofds;	/* user -> array of fds of clones
 					indexed according to networks array */
 
+static struct fd_ref	 *reconn_list_head, /* re-connection queue */
+			 *reconn_list_tail;
+
 /* functions prototype */
 void	 fd_register(int, int);
 void	 fifo_read(void);
@@ -92,19 +102,23 @@
 void	 fd_write(int);
 void	 fd_read(int);
 void	 net_add(char *, char *, char *, char *, char *);
-void	 net_del(int, char *);
-void	 net_del_raw(int);
+void	 net_del(int, char *, int);
 void	 user_add(char *, int, int);
 void	 user_del(char *, char *);
-int	 vuser_add(int, char *);
-void	 vuser_del(int, char *);
-int	*vuser_get_fds(char *, int);
+void	 reconn_list_add(char *, int);
+void	 reconn_list_del(char *, int);
+int	 fd_new(int, char *);
+void	 fd_del(int, char *, int);
+void	 fd_reconn(int, char *);
+int	*clone_get_fds(char *, int);
 void	 nick_add_symb(char *, int);
 void	 privmsg_update(char *, char *, int);
 void	 print_table(void);
 void	 print_htable(void);
 void	 print_users(void);
+void	 print_reconn_list(void);
 void	 print_border(void);
+ssize_t	 writeall(int, char *);
 
 int
 main(int argc, char *argv[])
@@ -115,9 +129,8 @@
 	struct kevent		kevs[HANDLE_EVENTS];
 	struct timespec		tmsp;
 #endif
-	int	i, fd, *fds, nev;	/* no. of returned events to handle */
+	int	i, fd, nev;	/* no. of returned events to handle */
 	time_t	timeout;
-	struct htiter it;
 
 	/* set stdout to unbufferd */
 	setvbuf(stdout, NULL, _IONBF, 0);
@@ -132,7 +145,7 @@
 	netcap = NET_ADDEND;
 	fdcap = FD_ADDEND;
 	networks = emalloc((size_t)netcap * sizeof(struct network));
-	fdtovuser = emalloc((size_t)fdcap * sizeof(struct vuser *));
+	fdtodata = emalloc((size_t)fdcap * sizeof(struct fd_data *));
 	usertofds = htcreate(hash_str, (key_cmp_fn *)strcmp, free, free,
 			USER_ADDEND);
 
@@ -147,7 +160,7 @@
 	fd_register(fifofd, EV_READ);
 
 	/* event loop */
-	while (state != DONE) {
+	while (state != EXIT) {
 		if (ntime == -1) {
 			timeout = -1;
 		} else {
@@ -192,38 +205,31 @@
 			} else if (kevs[i].filter == EVFILT_READ) {
 #endif
 				fd_read(fd);
-				if (break_evloop) {
-					break_evloop = FALSE;
-					break;
-				}
 			} else {
 				fatal("unknown event");
 			}
+
+			if (reconn_list_head != NULL && ntime == -1)
+				ntime = time(0) + RECONN_TIME;
+			if (break_evloop) {
+				break_evloop = FALSE;
+				break;
+			}
 		}
 	}
 
-	/* cleanup */
+	/*
+	 * delete and free all the networks with their users
+	 *	- delete in reverse order to prevent swapping.
+	 */
 	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);
+	for (i = netlen-1; i >= 0; i--)
+		net_del(i, msg, FALSE);
 
-	/* 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);
-
-	free(fdtovuser);
+	free(fdtodata);
+	printf("table len: %d\n", usertofds->len);
+	htdestroy(usertofds);
 	return 0;
 }
 
@@ -302,7 +308,7 @@
 			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);
+					net_del(i, msg, FALSE);
 					return;
 				}
 			}
@@ -314,8 +320,10 @@
 		print_htable();
 	} else if (strcmp(cmd, "users") == 0) {
 		print_users();
+	} else if (strcmp(cmd, "reconn") == 0) {
+		print_reconn_list();
 	} else if (strcmp(cmd, "exit") == 0) {
-		state = DONE;
+		state = EXIT;
 	} else {
 		warnf("%s is not a command", cmd);
 	}
@@ -329,7 +337,30 @@
 	int i, j, *fds;
 	char *user;
 
-	if (state == RESET) {
+	if (state == IDLE) {
+		struct fd_ref *tmp;
+		log1("Reconnecting");
+		while(reconn_list_head != NULL) {
+			user = reconn_list_head->user;
+			i = reconn_list_head->netid;
+
+			if (user == NULL) {
+				networks[i].fd = fd_new(i, user);
+			} else {
+				if ((fds = htsearch(usertofds, user)) == NULL)
+					fatal("%s: user doesn't exist", user);
+				if (fds[i] == -3 &&
+				    networks[i].fd > 0 &&
+				    fdtodata[networks[i].fd]->ready)
+					fds[i] = fd_new(i, user);
+			}
+			tmp = reconn_list_head;
+			reconn_list_head = reconn_list_head->next;
+			free(tmp);
+		}
+		reconn_list_tail = reconn_list_head;
+		return -1;
+	} else if (state == RESET) {
 		state = CLONING;
 		htiter_init(&it);
 	}
@@ -340,8 +371,9 @@
 		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);
+			if (fds[i] == 0 && networks[i].fd > 0 &&
+					fdtodata[networks[i].fd]->ready)
+				fds[i] = fd_new(i, user);
 		}
 		j++;
 		if (j >= CLONE_ADDEND)
@@ -367,16 +399,12 @@
 		fatal("kevent:");
 	fd_register(fd, EV_READ);
 #endif
-	if (fdtovuser[fd]->user == NULL) {	/* linker */
-		snprintf(msg, sizeof(msg), "NICK linker\r\n");
+	if (fdtodata[fd]->user == NULL) {	/* watcher */
+		snprintf(msg, sizeof(msg), "NICK watcher\r\nUSER watcher 0 * :watcher\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", fdtovuser[fd]->user);
+		snprintf(msg, sizeof(msg), "NICK %s\r\nUSER user 0 * :user\r\n", fdtodata[fd]->user);
 		writeall(fd, msg);
-		snprintf(msg, sizeof(msg), "USER user 0 * :user\r\n");
-		writeall(fd, msg);
 	}
 }
 
@@ -385,7 +413,7 @@
 {
 	char	 buffer	[BUFSIZE];
 	char	 backup	[BUFSIZE];
-	char	 lnick	[NICK_LEN];	/* linker nick */
+	char	 wnick	[NICK_LEN];	/* watcher nick */
 	char	*buf;
 	char	*cmd;
 	char	*nick;
@@ -392,17 +420,19 @@
 	int	 i, netid;
 	ssize_t	 n;
 
-	netid = fdtovuser[fd]->netid;
+	netid = fdtodata[fd]->netid;
 
 	n = readline(fd, buffer, sizeof(buffer));
 	if (n == -1) {
 		warnf("%d: read:", fd);
 		snprintf(msg, sizeof(msg), "QUIT :read failed\r\n");
-		goto del;
+		fd_reconn(fd, msg);
+		return;
 	} else if (n == 0) {
-		warnf("%d: connection closed", fd);
+		warnf("%d: read: connection closed", fd);
 		snprintf(msg, sizeof(msg), "QUIT :connection closed\r\n");
-		goto del;
+		fd_reconn(fd, msg);
+		return;
 	}
 	if (*buffer == '\0')
 		return;
@@ -411,10 +441,10 @@
 	strlcpy(backup, buffer, sizeof(backup));
 	buf = buffer;
 
-	/* set linker nick */
-	strlcpy(lnick, "linker", sizeof(lnick));
-	for (i = 0; i < fdtovuser[fd]->suffix; i++)
-		strcat(lnick, "_");
+	/* set watcher nick */
+	strlcpy(wnick, "watcher", sizeof(wnick));
+	for (i = 0; i < fdtodata[fd]->suffix; i++)
+		strcat(wnick, "_");
 
 	/* first column */
 	cmd = split(&buf, ' ');
@@ -421,7 +451,10 @@
 	if (strcmp(cmd, "NOTICE") == 0) {	/* ignore */
 		return;
 	} else if (strcmp(cmd, "ERROR") == 0) {
-		fatal("%d: %s", fd, backup);
+		warnf("%d: %s", fd, backup);
+		snprintf(msg, sizeof(msg), "QUIT :ERROR\r\n");
+		fd_reconn(fd, msg);
+		return;
 	} else if (strcmp(cmd, "PING") == 0) {
 		snprintf(msg, sizeof(msg), "PONG %s\r\n", buf);
 		writeall(fd, msg);
@@ -466,11 +499,15 @@
 		split(&buf, ' ');
 		nick = split(&buf, ' ');
 		strcat(nick, "_");
-		fdtovuser[fd]->suffix++;
+		fdtodata[fd]->suffix++;
 		if (strlen(nick) > NICK_LEN) {
 			warnf("%s: nickname too long", nick);
 			snprintf(msg, sizeof(msg), "QUIT :%s: nickname too long\r\n", nick);
-			goto del;
+			if (fdtodata[fd]->user == NULL)
+				net_del(netid, msg, FALSE);
+			else
+				fd_del(fd, msg, FALSE);
+			return;
 		} else {
 			snprintf(msg, sizeof(msg), "NICK %s\r\n", nick);
 			writeall(fd, msg);
@@ -484,7 +521,7 @@
 		char privmsg[BUFSIZE] = "";
 		int *fds;
 
-		if (fdtovuser[fd]->user == NULL) {	/* if linker */
+		if (fdtodata[fd]->user == NULL) {	/* if watcher */
 			nick_add_symb(nick, netid);
 			if ((fds = htsearch(usertofds, nick)) == NULL)
 				return;
@@ -501,7 +538,7 @@
 			char *netsymb;
 			char *user = split(&buf, ' ');
 
-			/* ignore messages from channel (it is handled by linker) */
+			/* ignore messages from channel (it is handled by watcher) */
 			if (user[0] == '#' || user[0] == '&')
 				return;
 
@@ -528,20 +565,15 @@
 		return;
 	}
 	else if (strcmp(cmd, "353") == 0) {
-		fdtovuser[fd]->ready = TRUE;
+		fdtodata[fd]->ready = TRUE;
 		/* FALLBACK */
 	}
-	/* these messages are handled by linker */
-	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) {
+
+	/* from now, the messages are handled by watcher */
+	if (fdtodata[fd]->user != NULL)	/* if clone */
+		return;
+
+	if (strcmp(cmd, "353") == 0) {
 		char *nick;
 		split(&buf, ':');
 		state = RESET;
@@ -556,14 +588,14 @@
 			    *nick == '+' ||
 			    *nick == '\\')
 				nick++;
-			if (strcmp(nick, lnick) != 0)
+			if (strcmp(nick, wnick) != 0)
 				user_add(nick, netid, FALSE);
 		}
 		return;
 	} else if (strcmp(cmd, "JOIN") == 0) {
 		/* if real user */
-		if ((strcmp(nick, lnick) != 0) &&
-		    (vuser_get_fds(nick, netid) == NULL)) {
+		if ((strcmp(nick, wnick) != 0) &&
+		    (clone_get_fds(nick, netid) == NULL)) {
 			if (state != IDLE)
 				warnf("%s: ignored (network cloning)", nick);
 			else
@@ -574,11 +606,8 @@
 		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(usertofds, nick) != NULL) {
+		if (htsearch(usertofds, nick) != NULL)
 			user_del(nick, msg);
-			break_evloop = TRUE;
-			return;
-		}
 		return;
 	} else if (strcmp(cmd, "NICK") == 0) {
 		int *fds, i;
@@ -597,7 +626,7 @@
 		snprintf(msg, sizeof(msg), "NICK %s\r\n", newnick);
 		for (i = 0; i < netlen; i++) {
 			if (fds[i] > 0) {
-				fdtovuser[fds[i]]->user = newnick;
+				fdtodata[fds[i]]->user = newnick;
 				writeall(fds[i], msg);
 			}
 		}
@@ -611,27 +640,25 @@
 		user = split(&buf, ' ');	/* user who is being kicked		*/
 		split(&buf, ':');		/* ignore ':' and store reason to buf	*/
 
-		/* if linker is being kicked, delete the network */
-		if (strcmp(user, lnick) == 0) {
+		/* if watcher is being kicked, delete the network */
+		if (strcmp(user, wnick) == 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;
+					networks[netid].name, wnick, nick);
+			fd_reconn(fd, msg);
 			return;
 		}
 
 		/* if message is from a real user, delete the user */
-		if ((fds = vuser_get_fds(user, netid)) == NULL) {
+		if ((fds = clone_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);
-			break_evloop = TRUE;
 			return;
 		}
 
 		/* delete the kicked clone */
 		snprintf(msg, sizeof(msg), "QUIT :kicked by %s\r\n", nick);
-		vuser_del(fds[netid], msg);
+		fd_reconn(fds[netid], msg);
 
 		/* get the original user netid */
 		for (i = 0; i < netlen; i++) {
@@ -638,22 +665,16 @@
 			if (fds[i] == -1)
 				break;
 		}
-		/* 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;
+		/* send notice in the channel through watcher */
+		if (networks[i].fd > 0) {
+			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);
+		}
 		return;
 	}
 	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
@@ -705,19 +726,23 @@
 	n->host = strdup(host);
 	n->port = strdup(port);
 	n->chan = strdup(chan);
-	n->fd	= vuser_add(netlen, NULL);
+	n->fd	= fd_new(netlen, NULL);
 	netlen++;
 }
 
 void
-net_del(int netid, char *msg)
+net_del(int netid, char *msg, int reconnect)
 {
 	int *fds;
+	char *user;
+	struct network *n;
 	struct htiter lit, it;	/* last, current iterator */
 
+	n = &networks[netid];
 	htiter_init(&it);
 	htiter_init(&lit);
 	while (htiterate(usertofds, &it)) {
+		user = (char *)it.node->key;
 		fds = (int *)it.node->val;
 		if (fds[netid] == -1) {
 			user_del(it.node->key, msg);
@@ -724,32 +749,51 @@
 			it = lit; /* this node is deleted */
 		} else {
 			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;
+				fd_del(fds[netid], msg, FALSE);
+			else if (fds[netid] == -3)
+				reconn_list_del(user, netid);
+
+			if (!reconnect) {
+				if(netid != netlen-1) {
+					fds[netid] = fds[netlen-1];
+					if (fds[netid] > 0) {
+						fdtodata[fds[netid]]->netid = netid;
+					} else if (fds[netid] == -3) {
+						reconn_list_del(user, netlen-1);
+						reconn_list_add(user, netid);
+					}
+				}
+				fds[netlen-1] = 0;
+			} else {
+				fds[netid] = 0;
+			}
 		}
 		lit = it;
 	}
 
-	vuser_del(networks[netid].fd, msg);
-	net_del_raw(netid);
-	networks[netid] = networks[netlen-1];
-	if (networks[netid].fd > 0)
-		fdtovuser[networks[netid].fd]->netid = netid;
-	netlen--;
-}
+	if (n->fd > 0)
+		fd_del(n->fd, msg, reconnect);
+	else if (n->fd == -3)
+		reconn_list_del(NULL, netid);
 
-void
-net_del_raw(int netid)
-{
-	struct network *n = &networks[netid];
-	free(n->name);
-	free(n->symb);
-	free(n->host);
-	free(n->port);
-	free(n->chan);
+	if (!reconnect) {
+		free(n->name);
+		free(n->symb);
+		free(n->host);
+		free(n->port);
+		free(n->chan);
+		/* swap */
+		if(netid != netlen-1) {
+			networks[netid] = networks[netlen-1];
+			if (networks[netid].fd > 0) {
+				fdtodata[n->fd]->netid = netid;
+			} else if (networks[netid].fd == -3) {
+				reconn_list_del(NULL, netlen-1);
+				reconn_list_add(NULL, netid);
+			}
+		}
+		netlen--;
+	}
 }
 
 void
@@ -778,8 +822,9 @@
 	if (clone) {
 		int i;
 		for (i = 0; i < netlen; i++) {
-			if (fds[i] == 0 && fdtovuser[networks[i].fd]->ready)
-				fds[i] = vuser_add(i, nick);
+			if (fds[i] == 0 && networks[i].fd > 0 &&
+					fdtodata[networks[i].fd]->ready)
+				fds[i] = fd_new(i, nick);
 		}
 	}
 
@@ -788,25 +833,74 @@
 }
 
 void
-user_del(char *nick, char *msg)
+user_del(char *user, char *msg)
 {
 	int i, *fds;
 
-	if ((fds = htsearch(usertofds, nick)) == NULL)
-		fatal("%s: user doesn't exist", nick);
+	if ((fds = htsearch(usertofds, user)) == NULL)
+		fatal("%s: user doesn't exist", user);
 	for (i = 0; i < netlen; i++) {
-		if (fds[i] > 0)
-			vuser_del(fds[i], msg);
+		if (fds[i] > 0) {
+			fd_del(fds[i], msg, FALSE);
+		} else if (fds[i] == -3) {
+			reconn_list_del(user, i);
+		}
 	}
-	htremove(usertofds, nick);
+	htremove(usertofds, user);
 }
 
+void
+reconn_list_add(char *user, int netid)
+{
+	struct fd_ref *node;
+
+	node = emalloc(sizeof(struct fd_ref));
+	node->user = user;
+	node->netid = netid;
+	node->next = NULL;
+	if (reconn_list_tail == NULL) {
+		reconn_list_head = reconn_list_tail = node;
+	} else {
+		reconn_list_tail->next = node;
+		reconn_list_tail = reconn_list_tail->next;
+	}
+}
+
+void
+reconn_list_del(char *user, int netid)
+{
+	struct fd_ref *n, *pn;	/* current and previous node */
+
+	n = reconn_list_head;
+	pn = NULL;
+	while (n != NULL) {
+		if (n->netid == netid &&
+		    ((user == NULL && n->user == NULL) ||
+		     (user != NULL && n->user != NULL &&
+		      (strcmp(n->user, user) == 0)))) {
+			if (n == reconn_list_head && n == reconn_list_tail)
+				reconn_list_head = reconn_list_tail = NULL;
+			else if (n == reconn_list_head)
+				reconn_list_head = n->next;
+			else if (n == reconn_list_tail)
+				reconn_list_tail = pn;
+			else
+				pn->next = n->next;
+			free(n);
+			return;
+		}
+		pn = n;
+		n = n->next;
+	}
+	fatal("%d:%s: failed to find in re-connection list", netid, user);
+}
+
 int
-vuser_add(int netid, char *user)
+fd_new(int netid, char *user)
 {
 	int fd;
 	struct network *n;
-	struct vuser *vuser;
+	struct fd_data *data;
 
 	n = &networks[netid];
 	fd = dial(n->host, n->port);
@@ -823,46 +917,62 @@
 
 	if (fd+1 > fdcap) {
 		fdcap *= 2;
-		fdtovuser = erealloc(fdtovuser, (size_t)fdcap * sizeof(struct vuser *));
+		fdtodata = erealloc(fdtodata, (size_t)fdcap * sizeof(struct fd_data *));
 	}
 
-	vuser = emalloc(1 * sizeof(struct vuser));
-	vuser->netid = netid;
-	vuser->user = user;
-	vuser->suffix = 0;
-	vuser->ready = FALSE;
+	data = emalloc(1 * sizeof(struct fd_data));
+	data->netid = netid;
+	data->user = user;
+	data->suffix = 0;
+	data->ready = FALSE;
 
-	fdtovuser[fd] = vuser;
+	fdtodata[fd] = data;
 
 	return fd;
 }
 
 void
-vuser_del(int fd, char *msg)
+fd_del(int fd, char *msg, int reconnection)
 {
-	char *user = fdtovuser[fd]->user;
-	int netid = fdtovuser[fd]->netid;
-	int *fds;
+	char	*user;
+	int	 netid;
+	int	*fds;
 
+	user = fdtodata[fd]->user;
+	netid = fdtodata[fd]->netid;
+
 	if (user == NULL) {
 		log1("%d: netdel: %s[%s]", fd, networks[netid].name, networks[netid].symb);
-		networks[netid].fd = -2;
+		networks[netid].fd = reconnection ? -3 : -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;
+		fds[netid] = reconnection ? -3 : -2;
 	}
 
-	if (fdtovuser[fd]->ready)
+	if (fdtodata[fd]->ready)
 		writeall(fd, msg);
 	close(fd);
-	free(fdtovuser[fd]);
-	fdtovuser[fd] = NULL;
+
+	if (reconnection)
+		reconn_list_add(user, netid);
+	free(fdtodata[fd]);
+	fdtodata[fd] = NULL;
+	break_evloop = TRUE;
 }
 
+void
+fd_reconn(int fd, char *msg)
+{
+	if (fdtodata[fd]->user == NULL)
+		net_del(fdtodata[fd]->netid, msg, TRUE);
+	else
+		fd_del(fd, msg, TRUE);
+}
+
 int *
-vuser_get_fds(char *nick, int netid)
+clone_get_fds(char *nick, int netid)
 {
 	unsigned int s;
 	int *fds = NULL;
@@ -875,7 +985,7 @@
 
 	fds = htsearch(usertofds, nick);
 	/* if match but suffix doesn't match */
-	if ((fds != NULL) && (fdtovuser[fds[netid]]->suffix != (int)s))
+	if ((fds != NULL) && (fdtodata[fds[netid]]->suffix != (int)s))
 		fds = NULL;
 
 	/* add suffix back */
@@ -914,7 +1024,7 @@
 		}
 
 		/* check if the word is nick */
-		if (vuser_get_fds(src, netid) != NULL)
+		if (clone_get_fds(src, netid) != NULL)
 			*strrchr(src, '[') = '\0';
 
 		strcat(dst, src);
@@ -940,8 +1050,8 @@
 	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);
+		if (networks[i].fd > 0 && fdtodata[networks[i].fd]->suffix > 0)
+			printf("(%d)\t", fdtodata[networks[i].fd]->suffix);
 		else
 			printf("\t\t");
 	}
@@ -960,8 +1070,8 @@
 		for (i = 0; i < netlen; i++) {
 			printf("%d", fds[i]);
 			/* print suffix */
-			if ((fds[i] > 0) && (fdtovuser[fds[i]]->suffix > 0))
-				printf("(%d)", fdtovuser[fds[i]]->suffix);
+			if ((fds[i] > 0) && (fdtodata[fds[i]]->suffix > 0))
+				printf("(%d)", fdtodata[fds[i]]->suffix);
 			printf("\t\t");
 		}
 		printf("\n");
@@ -1005,6 +1115,20 @@
 }
 
 void
+print_reconn_list(void)
+{
+	struct fd_ref *n;
+
+	print_border();
+	n = reconn_list_head;
+	while(n != NULL) {
+		printf("%d: %s\n", n->netid, n->user);
+		n = n->next;
+	}
+	print_border();
+}
+
+void
 print_border(void)
 {
 	int i;
@@ -1011,4 +1135,23 @@
 	for (i = 0; i < 64; i++)
 		printf("-");
 	printf("\n");
+}
+
+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) {
+			warnf("%d: write:", fd);
+			snprintf(msg, sizeof(msg), "QUIT :write failed\r\n");
+			fd_reconn(fd, msg);
+			return -1;
+		}
+		sent += n;
+		left -= n;
+	}
+	return sent;
 }
--- a/util.c
+++ b/util.c
@@ -208,21 +208,6 @@
 	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)