MEDIUM: log: Use linked lists for loggers

This patch settles the 2 loggers limitation.
Loggers are now stored in linked lists.

Using "global log", the global loggers list content is added at the end
of the current proxy list. Each "log" entries are added at the end of
the proxy list.

"no log" flush a logger list.
diff --git a/src/cfgparse.c b/src/cfgparse.c
index b6650cd..bf2eb36 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -891,40 +891,57 @@
 			goto out;
                 }
 	}
+	else if (!strcmp(args[0], "log") && kwm == KWM_NO) { /* no log */
+		/* delete previous herited or defined syslog servers */
+		struct logsrv *back;
+		struct logsrv *tmp;
+
+		if (*(args[1]) != 0) {
+			Alert("parsing [%s:%d]:%s : 'no log' does not expect arguments.\n", file, linenum, args[1]);
+			err_code |= ERR_ALERT | ERR_FATAL;
+			goto out;
+		}
+
+		list_for_each_entry_safe(tmp, back, &global.logsrvs, list) {
+			LIST_DEL(&tmp->list);
+			free(tmp);
+		}
+	}
 	else if (!strcmp(args[0], "log")) {  /* syslog server address */
-		struct logsrv logsrv;
-		int facility, level, minlvl;
-	
+		struct logsrv *logsrv;
+
 		if (*(args[1]) == 0 || *(args[2]) == 0) {
 			Alert("parsing [%s:%d] : '%s' expects <address> and <facility> as arguments.\n", file, linenum, args[0]);
 			err_code |= ERR_ALERT | ERR_FATAL;
 			goto out;
 		}
-	
-		facility = get_log_facility(args[2]);
-		if (facility < 0) {
+
+		logsrv = calloc(1, sizeof(struct logsrv));
+
+		logsrv->facility = get_log_facility(args[2]);
+		if (logsrv->facility < 0) {
 			Alert("parsing [%s:%d] : unknown log facility '%s'\n", file, linenum, args[2]);
 			err_code |= ERR_ALERT | ERR_FATAL;
-			facility = 0;
+			logsrv->facility = 0;
 		}
 
-		level = 7; /* max syslog level = debug */
+		logsrv->level = 7; /* max syslog level = debug */
 		if (*(args[3])) {
-			level = get_log_level(args[3]);
-			if (level < 0) {
+			logsrv->level = get_log_level(args[3]);
+			if (logsrv->level < 0) {
 				Alert("parsing [%s:%d] : unknown optional log level '%s'\n", file, linenum, args[3]);
 				err_code |= ERR_ALERT | ERR_FATAL;
-				level = 0;
+				logsrv->level = 0;
 			}
 		}
 
-		minlvl = 0; /* limit syslog level to this level (emerg) */
+		logsrv->minlvl = 0; /* limit syslog level to this level (emerg) */
 		if (*(args[4])) {
-			minlvl = get_log_level(args[4]);
-			if (minlvl < 0) {
+			logsrv->minlvl = get_log_level(args[4]);
+			if (logsrv->minlvl < 0) {
 				Alert("parsing [%s:%d] : unknown optional minimum log level '%s'\n", file, linenum, args[4]);
 				err_code |= ERR_ALERT | ERR_FATAL;
-				minlvl = 0;
+				logsrv->minlvl = 0;
 			}
 		}
 
@@ -934,37 +951,24 @@
 				Alert("parsing [%s:%d] : Socket path '%s' too long (max %d)\n", file, linenum,
 				      args[1], (int)sizeof(((struct sockaddr_un *)&sk)->sun_path) - 1);
 				err_code |= ERR_ALERT | ERR_FATAL;
+				free(logsrv);
 				goto out;
 			}
-			logsrv.addr = *sk;
+			logsrv->addr = *sk;
 		} else {
 			struct sockaddr_storage *sk = str2sa(args[1]);
 			if (!sk) {
 				Alert("parsing [%s:%d] : Unknown host in '%s'\n", file, linenum, args[1]);
 				err_code |= ERR_ALERT | ERR_FATAL;
+				free(logsrv);
 				goto out;
 			}
-			logsrv.addr = *sk;
-			if (!get_host_port(&logsrv.addr))
-				set_host_port(&logsrv.addr, SYSLOG_PORT);
+			logsrv->addr = *sk;
+			if (!get_host_port(&logsrv->addr))
+				set_host_port(&logsrv->addr, SYSLOG_PORT);
 		}
 
-		if (global.logfac1 == -1) {
-			global.logsrv1 = logsrv;
-			global.logfac1 = facility;
-			global.loglev1 = level;
-			global.minlvl1 = minlvl;
-		}
-		else if (global.logfac2 == -1) {
-			global.logsrv2 = logsrv;
-			global.logfac2 = facility;
-			global.loglev2 = level;
-			global.minlvl2 = minlvl;
-		}
-		else {
-			Alert("parsing [%s:%d] : too many syslog servers\n", file, linenum);
-			err_code |= ERR_ALERT | ERR_FATAL;
-		}
+		LIST_ADDQ(&global.logsrvs, &logsrv->list);
 	}
 	else if (!strcmp(args[0], "log-send-hostname")) { /* set the hostname in syslog header */
 		char *name;
@@ -1319,6 +1323,7 @@
 	unsigned val;
 	int err_code = 0;
 	struct acl_cond *cond = NULL;
+	struct logsrv *tmp;
 
 	if (!strcmp(args[0], "listen"))
 		rc = PR_CAP_LISTEN;
@@ -1521,14 +1526,15 @@
 		}
 
 		curproxy->mode = defproxy.mode;
-		curproxy->logfac1 = defproxy.logfac1;
-		curproxy->logsrv1 = defproxy.logsrv1;
-		curproxy->loglev1 = defproxy.loglev1;
-		curproxy->minlvl1 = defproxy.minlvl1;
-		curproxy->logfac2 = defproxy.logfac2;
-		curproxy->logsrv2 = defproxy.logsrv2;
-		curproxy->loglev2 = defproxy.loglev2;
-		curproxy->minlvl2 = defproxy.minlvl2;
+
+		/* copy default logsrvs to curproxy */
+		list_for_each_entry(tmp, &defproxy.logsrvs, list) {
+			struct logsrv *node = malloc(sizeof(struct logsrv));
+			memcpy(node, tmp, sizeof(struct logsrv));
+			LIST_INIT(&node->list);
+			LIST_ADDQ(&curproxy->logsrvs, &node->list);
+		}
+
 		curproxy->grace  = defproxy.grace;
 		curproxy->conf.used_listener_id = EB_ROOT;
 		curproxy->conf.used_server_id = EB_ROOT;
@@ -4503,44 +4509,64 @@
 			newsrv->prev_state = newsrv->state;
 		}
 	}
+	else if (!strcmp(args[0], "log") && kwm == KWM_NO) {
+		/* delete previous herited or defined syslog servers */
+		struct logsrv *back;
+
+		if (*(args[1]) != 0) {
+			Alert("parsing [%s:%d]:%s : 'no log' does not expect arguments.\n", file, linenum, args[1]);
+			err_code |= ERR_ALERT | ERR_FATAL;
+			goto out;
+		}
+
+		list_for_each_entry_safe(tmp, back, &curproxy->logsrvs, list) {
+			LIST_DEL(&tmp->list);
+			free(tmp);
+		}
+	}
 	else if (!strcmp(args[0], "log")) {  /* syslog server address */
-		struct logsrv logsrv;
-		int facility;
-	
+		struct logsrv *logsrv;
+
 		if (*(args[1]) && *(args[2]) == 0 && !strcmp(args[1], "global")) {
-			curproxy->logfac1 = global.logfac1;
-			curproxy->logsrv1 = global.logsrv1;
-			curproxy->loglev1 = global.loglev1;
-			curproxy->minlvl1 = global.minlvl1;
-			curproxy->logfac2 = global.logfac2;
-			curproxy->logsrv2 = global.logsrv2;
-			curproxy->loglev2 = global.loglev2;
-			curproxy->minlvl2 = global.minlvl2;
+			/* copy global.logrsvs linked list to the end of curproxy->logsrvs */
+			list_for_each_entry(tmp, &global.logsrvs, list) {
+				struct logsrv *node = malloc(sizeof(struct logsrv));
+				memcpy(node, tmp, sizeof(struct logsrv));
+				LIST_INIT(&node->list);
+				LIST_ADDQ(&curproxy->logsrvs, &node->list);
+			}
 		}
 		else if (*(args[1]) && *(args[2])) {
-			int level, minlvl;
+
+			logsrv = calloc(1, sizeof(struct logsrv));
 
-			facility = get_log_facility(args[2]);
-			if (facility < 0) {
+			logsrv->facility = get_log_facility(args[2]);
+			if (logsrv->facility < 0) {
 				Alert("parsing [%s:%d] : unknown log facility '%s'\n", file, linenum, args[2]);
-				exit(1);
+				err_code |= ERR_ALERT | ERR_FATAL;
+				goto out;
+
 			}
 	    
-			level = 7; /* max syslog level = debug */
+			logsrv->level = 7; /* max syslog level = debug */
 			if (*(args[3])) {
-				level = get_log_level(args[3]);
-				if (level < 0) {
+				logsrv->level = get_log_level(args[3]);
+				if (logsrv->level < 0) {
 					Alert("parsing [%s:%d] : unknown optional log level '%s'\n", file, linenum, args[3]);
-					exit(1);
+					err_code |= ERR_ALERT | ERR_FATAL;
+					goto out;
+
 				}
 			}
 
-			minlvl = 0; /* limit syslog level to this level (emerg) */
+			logsrv->minlvl = 0; /* limit syslog level to this level (emerg) */
 			if (*(args[4])) {
-				minlvl = get_log_level(args[4]);
-				if (level < 0) {
+				logsrv->minlvl = get_log_level(args[4]);
+				if (logsrv->level < 0) {
 					Alert("parsing [%s:%d] : unknown optional minimum log level '%s'\n", file, linenum, args[4]);
-					exit(1);
+					err_code |= ERR_ALERT | ERR_FATAL;
+					goto out;
+
 				}
 			}
 
@@ -4552,7 +4578,7 @@
 					err_code |= ERR_ALERT | ERR_FATAL;
 					goto out;
 				}
-				logsrv.addr = *sk;
+				logsrv->addr = *sk;
 			} else {
 				struct sockaddr_storage *sk = str2sa(args[1]);
 				if (!sk) {
@@ -4560,28 +4586,12 @@
 					err_code |= ERR_ALERT | ERR_FATAL;
 					goto out;
 				}
-				logsrv.addr = *sk;
-				if (!get_host_port(&logsrv.addr))
-					set_host_port(&logsrv.addr, SYSLOG_PORT);
-			}
-	    
-			if (curproxy->logfac1 == -1) {
-				curproxy->logsrv1 = logsrv;
-				curproxy->logfac1 = facility;
-				curproxy->loglev1 = level;
-				curproxy->minlvl1 = minlvl;
-			}
-			else if (curproxy->logfac2 == -1) {
-				curproxy->logsrv2 = logsrv;
-				curproxy->logfac2 = facility;
-				curproxy->loglev2 = level;
-				curproxy->minlvl2 = minlvl;
+				logsrv->addr = *sk;
+				if (!get_host_port(&logsrv->addr))
+					set_host_port(&logsrv->addr, SYSLOG_PORT);
 			}
-			else {
-				Alert("parsing [%s:%d] : too many syslog servers\n", file, linenum);
-				err_code |= ERR_ALERT | ERR_FATAL;
-				goto out;
-			}
+
+			LIST_ADDQ(&curproxy->logsrvs, &logsrv->list);
 		}
 		else {
 			Alert("parsing [%s:%d] : 'log' expects either <address[:port]> and <facility> or 'global' as arguments.\n",
@@ -5427,9 +5437,14 @@
 
 		/* check for keyword modifiers "no" and "default" */
 		if (!strcmp(args[0], "no")) {
+			char *tmp;
+
 			kwm = KWM_NO;
+			tmp = args[0];
 			for (arg=0; *args[arg+1]; arg++)
 				args[arg] = args[arg+1];		// shift args after inversion
+			*tmp = '\0'; 					// fix the next arg to \0
+			args[arg] = tmp;
 		}
 		else if (!strcmp(args[0], "default")) {
 			kwm = KWM_DEF;
@@ -5437,8 +5452,9 @@
 				args[arg] = args[arg+1];		// shift args after inversion
 		}
 
-		if (kwm != KWM_STD && strcmp(args[0], "option") != 0) {
-			Alert("parsing [%s:%d]: negation/default currently supported only for options.\n", file, linenum);
+		if (kwm != KWM_STD && strcmp(args[0], "option") != 0 && 	\
+		     strcmp(args[0], "log") != 0) {
+			Alert("parsing [%s:%d]: negation/default currently supported only for options and log.\n", file, linenum);
 			err_code |= ERR_ALERT | ERR_FATAL;
 		}
 
@@ -6072,7 +6088,7 @@
 			curproxy->to_log &= ~LW_BYTES;
 
 		if ((curproxy->mode == PR_MODE_TCP || curproxy->mode == PR_MODE_HTTP) &&
-		    (curproxy->cap & PR_CAP_FE) && curproxy->to_log && curproxy->logfac1 < 0) {
+		    (curproxy->cap & PR_CAP_FE) && curproxy->to_log && LIST_ISEMPTY(&curproxy->logsrvs)) {
 			Warning("config : log format ignored for %s '%s' since it has no log address.\n",
 				proxy_type_str(curproxy), curproxy->id);
 			err_code |= ERR_WARN;
diff --git a/src/frontend.c b/src/frontend.c
index 19980c0..a32018c 100644
--- a/src/frontend.c
+++ b/src/frontend.c
@@ -144,7 +144,7 @@
 	}
 
 	if ((s->fe->mode == PR_MODE_TCP || s->fe->mode == PR_MODE_HTTP)
-	    && (s->fe->logfac1 >= 0 || s->fe->logfac2 >= 0)) {
+	    && (!LIST_ISEMPTY(&s->fe->logsrvs))) {
 		if (likely(s->fe->to_log)) {
 			/* we have the client ip */
 			if (s->logs.logwait & LW_CLIP)
diff --git a/src/haproxy.c b/src/haproxy.c
index cc16359..363f306 100644
--- a/src/haproxy.c
+++ b/src/haproxy.c
@@ -103,10 +103,7 @@
 
 /* global options */
 struct global global = {
-	logfac1 : -1,
-	logfac2 : -1,
-	loglev1 : 7, /* max syslog level : debug */
-	loglev2 : 7,
+	.logsrvs = LIST_HEAD_INIT(global.logsrvs),
 	.stats_sock = {
 		.perm = {
 			 .ux = {
@@ -788,6 +785,7 @@
 	struct wordlist *wl, *wlb;
 	struct cond_wordlist *cwl, *cwlb;
 	struct uri_auth *uap, *ua = NULL;
+	struct logsrv *log, *logb;
 	int i;
 
 	deinit_signals();
@@ -893,6 +891,11 @@
 			free(rdr);
 		}
 
+		list_for_each_entry_safe(log, logb, &p->logsrvs, list) {
+			LIST_DEL(&log->list);
+			free(log);
+		}
+
 		deinit_tcp_rules(&p->tcp_req.inspect_rules);
 		deinit_tcp_rules(&p->tcp_req.l4_rules);
 
@@ -995,6 +998,10 @@
 	free(oldpids);        oldpids = NULL;
 	free(global_listener_queue_task); global_listener_queue_task = NULL;
 
+	list_for_each_entry_safe(log, logb, &global.logsrvs, list) {
+			LIST_DEL(&log->list);
+			free(log);
+		}
 	list_for_each_entry_safe(wl, wlb, &cfg_cfgfiles, list) {
 		LIST_DEL(&wl->list);
 		free(wl);
diff --git a/src/log.c b/src/log.c
index 162ff48..dd1b63f 100644
--- a/src/log.c
+++ b/src/log.c
@@ -157,10 +157,9 @@
 	static char *dataptr = NULL;
 	int fac_level;
 	int hdr_len, data_len;
-	struct logsrv *logsrvs[2];
-	int facilities[2], loglevel[2], minlvl[2];
+	struct list *logsrvs = NULL;
+	struct logsrv *tmp = NULL;
 	int nblogger;
-	int nbloggers = 0;
 	char *log_ptr;
 
 	if (level < 0 || message == NULL)
@@ -201,42 +200,25 @@
 	dataptr[data_len - 1] = '\n'; /* force a break on ultra-long lines */
 
 	if (p == NULL) {
-		if (global.logfac1 >= 0) {
-			logsrvs[nbloggers] = &global.logsrv1;
-			facilities[nbloggers] = global.logfac1;
-			loglevel[nbloggers] = global.loglev1;
-			minlvl[nbloggers] = global.minlvl1;
-			nbloggers++;
-		}
-		if (global.logfac2 >= 0) {
-			logsrvs[nbloggers] = &global.logsrv2;
-			facilities[nbloggers] = global.logfac2;
-			loglevel[nbloggers] = global.loglev2;
-			minlvl[nbloggers] = global.minlvl2;
-			nbloggers++;
+		if (!LIST_ISEMPTY(&global.logsrvs)) {
+			logsrvs = &global.logsrvs;
 		}
 	} else {
-		if (p->logfac1 >= 0) {
-			logsrvs[nbloggers] = &p->logsrv1;
-			facilities[nbloggers] = p->logfac1;
-			loglevel[nbloggers] = p->loglev1;
-			minlvl[nbloggers] = p->minlvl1;
-			nbloggers++;
-		}
-		if (p->logfac2 >= 0) {
-			logsrvs[nbloggers] = &p->logsrv2;
-			facilities[nbloggers] = p->logfac2;
-			loglevel[nbloggers] = p->loglev2;
-			minlvl[nbloggers] = p->minlvl2;
-			nbloggers++;
+		if (!LIST_ISEMPTY(&p->logsrvs)) {
+			logsrvs = &p->logsrvs;
 		}
 	}
 
+	if (!logsrvs)
+		return;
+
 	/* Lazily set up syslog sockets for protocol families of configured
 	 * syslog servers. */
-	for (nblogger = 0; nblogger < nbloggers; nblogger++) {
-		const struct logsrv *logsrv = logsrvs[nblogger];
+	nblogger = 0;
+	list_for_each_entry(tmp, logsrvs, list) {
+		const struct logsrv *logsrv = tmp;
 		int proto, *plogfd;
+
 		if (logsrv->addr.ss_family == AF_UNIX) {
 			proto = 0;
 			plogfd = &logfdunix;
@@ -258,17 +240,19 @@
 		setsockopt(*plogfd, SOL_SOCKET, SO_RCVBUF, &zero, sizeof(zero));
 		/* does nothing under Linux, maybe needed for others */
 		shutdown(*plogfd, SHUT_RD);
+		nblogger++;
 	}
 
 	/* Send log messages to syslog server. */
-	for (nblogger = 0; nblogger < nbloggers; nblogger++) {
-		const struct logsrv *logsrv = logsrvs[nblogger];
+	nblogger = 0;
+	list_for_each_entry(tmp, logsrvs, list) {
+		const struct logsrv *logsrv = tmp;
 		int *plogfd = logsrv->addr.ss_family == AF_UNIX ?
 			&logfdunix : &logfdinet;
 		int sent;
 
 		/* we can filter the level of the messages that are sent to each logger */
-		if (level > loglevel[nblogger])
+		if (level > logsrv->level)
 			continue;
 	
 		/* For each target, we may have a different facility.
@@ -278,7 +262,7 @@
 		 * time, we only change the facility in the pre-computed header,
 		 * and we change the pointer to the header accordingly.
 		 */
-		fac_level = (facilities[nblogger] << 3) + MAX(level, minlvl[nblogger]);
+		fac_level = (logsrv->facility << 3) + MAX(level, logsrv->minlvl);
 		log_ptr = logmsg + 3; /* last digit of the log level */
 		do {
 			*log_ptr = '0' + fac_level % 10;
@@ -295,6 +279,7 @@
 			Alert("sendto logger #%d failed: %s (errno=%d)\n",
 				nblogger, strerror(errno), errno);
 		}
+		nblogger++;
 	}
 }
 
@@ -320,7 +305,7 @@
 	addr_to_str(&s->si[0].addr.from, pn, sizeof(pn));
 	get_localtime(s->logs.tv_accept.tv_sec, &tm);
 
-	if (fe->logfac1 < 0 && fe->logfac2 < 0)
+	if(LIST_ISEMPTY(&fe->logsrvs))
 		return;
 
 	prx_log = fe;
diff --git a/src/proto_http.c b/src/proto_http.c
index fd298b7..87115d2 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -1097,7 +1097,7 @@
 	if (!err && (fe->options2 & PR_O2_NOLOGNORM))
 		return;
 
-	if (fe->logfac1 < 0 && fe->logfac2 < 0)
+	if (LIST_ISEMPTY(&fe->logsrvs))
 		return;
 	prx_log = fe;
 
diff --git a/src/proxy.c b/src/proxy.c
index caec45d..28094a8 100644
--- a/src/proxy.c
+++ b/src/proxy.c
@@ -436,11 +436,11 @@
 	LIST_INIT(&p->req_add);
 	LIST_INIT(&p->rsp_add);
 	LIST_INIT(&p->listener_queue);
+	LIST_INIT(&p->logsrvs);
 
 	/* Timeouts are defined as -1 */
 	proxy_reset_timeouts(p);
 	p->tcp_rep.inspect_delay = TICK_ETERNITY;
-	p->logfac1 = p->logfac2 = -1; /* log disabled */
 }
 
 /*