Hello :)

This patch (applies to 1.5-dev3) adds "include" configuration statement to 
haproxy configuration parser. I wrote this patch becouse my haproxy 
configuration became too big to be simply maintainable and becouse i really 
like "conf.d" configuration style.

Usage:

include glob_pattern

Include statement support absolute (/path/to/*.cfg) or relative (dir/*.cfg) 
glob(3) patterns. Current parsed config file's directory is used as base 
directory in case of relative glob patterns. Include directive also works in 
included configuration fragments.

Example (/etc/haproxy/haproxy.conf):

--- snip ---
global
        log     127.0.0.1 local0 info
        maxconn 10000
        user    nobody
        group   nobody
        daemon

# include defaults
# will include /etc/haproxy/conf.d/defaults.cfg
include conf.d/defaults.cfg

# include listen directives
include conf.d/listen-*.cfg

# include frontend declarations
include conf.d/frontend-*.cfg

# include backend declarations
include conf.d/backend-*.cfg

# include full proxy declarations
include conf.d/proxy-*.cfg

# EOF
-- snip ---

Best regards, Brane
diff --git a/include/common/cfgparse.h b/include/common/cfgparse.h
index b43f899..6e46c21 100644
--- a/include/common/cfgparse.h
+++ b/include/common/cfgparse.h
@@ -35,6 +35,10 @@
 #define CFG_USERLIST	3
 #define CFG_PEERS	4
 
+
+/* maximum include recursion level */
+#define INCLUDE_RECURSION_LEVEL_MAX 10
+
 struct cfg_keyword {
 	int section;                            /* section type for this keyword */
 	const char *kw;                         /* the keyword itself */
@@ -63,7 +67,7 @@ extern int cfg_maxconn;
 
 int cfg_parse_global(const char *file, int linenum, char **args, int inv);
 int cfg_parse_listen(const char *file, int linenum, char **args, int inv);
-int readcfgfile(const char *file);
+int readcfgfile(const char *file, int recdepth);
 void cfg_register_keywords(struct cfg_kw_list *kwl);
 void cfg_unregister_keywords(struct cfg_kw_list *kwl);
 void init_default_instance();
diff --git a/src/cfgparse.c b/src/cfgparse.c
index d3223ff..29b8b91 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -22,6 +22,8 @@
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <unistd.h>
+#include <glob.h>
+#include <libgen.h>
 
 #include <netinet/tcp.h>
 
@@ -5193,6 +5195,99 @@ out:
 	return err_code;
 }
 
+/**
+ * This function takes glob(3) pattern and tries to resolve
+ * that pattern to files and tries to include them.
+ *
+ * See readcfgfile() for return values.
+ */
+int cfgfile_include (char *pattern, char *dir, int recdepth) {
+	if (pattern == NULL) {
+		Alert("Config file include pattern == NULL; This should never happen.\n");
+		return ERR_ABORT;
+	}
+	if (recdepth >= INCLUDE_RECURSION_LEVEL_MAX) {
+		Alert(
+			"Refusing to include filename pattern: '%s': too deep recursion level: %d.\n",
+			pattern,
+			recdepth
+		);
+		return ERR_ABORT;
+	}
+
+	/** don't waste time with empty strings */
+	if (strlen(pattern) < 1) return 0;
+
+	/** we want to support relative to include file glob patterns */
+	int buf_len = 3;
+	if (dir != NULL)
+		buf_len += strlen(dir);
+	buf_len += strlen(pattern);
+	char *real_pattern = malloc(buf_len);
+	if (real_pattern == NULL) {
+		Alert("Error allocating memory for glob pattern: %s\n", strerror(errno));
+		return ERR_ABORT;
+	}
+	memset(real_pattern, '\0', buf_len);
+	if (dir != NULL && pattern[0] != '/') {
+		strcat(real_pattern, dir);
+		strcat(real_pattern, "/");
+	}
+	strcat(real_pattern, pattern);
+
+	/* file inclusion result */
+	int result = 0;
+
+	/** glob the pattern */
+	glob_t res;
+	int rv = glob(
+		real_pattern,
+		(GLOB_NOESCAPE | GLOB_BRACE | GLOB_TILDE),
+		NULL,
+		&res
+	);
+	/* check for glob(3) injuries */
+	switch (rv) {
+		case GLOB_NOMATCH:
+			/* nothing was found */
+			break;
+
+		case GLOB_ABORTED:
+			Alert("Error globbing pattern '%s': read error.\n", real_pattern);
+			result = ERR_ABORT;
+			break;
+
+		case GLOB_NOSPACE:
+			Alert("Error globbing pattern '%s': out of memory.\n", real_pattern);
+			result = ERR_ABORT;
+			break;
+
+		default:
+			;
+			int i = 0;
+			for (i = 0; i < res.gl_pathc; i++) {
+				char *file = res.gl_pathv[i];
+
+				/* parse configuration fragment */
+				int r = readcfgfile(file, recdepth);
+
+				/* check for injuries */
+				if (r != 0) {
+					result = r;
+					goto outta_cfgfile_include;
+				}
+			}
+	}
+
+	outta_cfgfile_include:
+
+	/** free glob result. */
+	globfree(&res);
+	free(real_pattern);
+
+	return result;
+}
+
 /*
  * This function reads and parses the configuration file given in the argument.
  * Returns the error code, 0 if OK, or any combination of :
@@ -5203,7 +5298,7 @@ out:
  * Only the two first ones can stop processing, the two others are just
  * indicators.
  */
-int readcfgfile(const char *file)
+int readcfgfile(const char *file, int recdepth)
 {
 	char thisline[LINESIZE];
 	FILE *f;
@@ -5211,8 +5306,10 @@ int readcfgfile(const char *file)
 	int confsect = CFG_NONE;
 	int err_code = 0;
 
-	if ((f=fopen(file,"r")) == NULL)
+	if ((f=fopen(file,"r")) == NULL) {
+		Alert("Error opening configuration file %s: %s\n", file, strerror(errno));
 		return -1;
+	}
 
 	while (fgets(thisline, sizeof(thisline), f) != NULL) {
 		int arg, kwm = KWM_STD;
@@ -5343,6 +5440,40 @@ int readcfgfile(const char *file)
 			err_code |= ERR_ALERT | ERR_FATAL;
 		}
 
+		/* include statement? */
+		if (strcmp(args[0], "include") == 0) {
+			if (args[1] == NULL || strlen(args[1]) < 1) {
+				Alert("parsing [%s:%d]: include statement requires file glob pattern.\n",
+				file, linenum);
+				err_code = ERR_ABORT;
+				break;
+			}
+			/**
+			 * compute file's dirname - this is necessary becouse
+			 * dirname(3) returns shared buffer address
+			 */
+			int buf_len = strlen(file) + 1;
+			char *file_dir = malloc(buf_len);
+			if (file_dir == NULL) {
+				Alert("Unable to allocate memory for config file dirname.");
+				err_code = ERR_ABORT;
+				break;
+			}
+			memset(file_dir, '\0', buf_len);
+			strcpy(file_dir, file);
+			strcpy(file_dir, dirname(file_dir));
+
+			/* include pattern */
+			int r = cfgfile_include(args[1], file_dir, (recdepth + 1));
+			free(file_dir);
+			/* check for injuries */
+			if (r != 0) {
+				err_code = r;
+				break;
+			}
+			continue;
+		}
+
 		if (!strcmp(args[0], "listen") ||
 		    !strcmp(args[0], "frontend") ||
 		    !strcmp(args[0], "backend") ||
diff --git a/src/haproxy.c b/src/haproxy.c
index c5aa3cc..320a089 100644
--- a/src/haproxy.c
+++ b/src/haproxy.c
@@ -515,7 +515,7 @@ void init(int argc, char **argv)
 	list_for_each_entry(wl, &cfg_cfgfiles, list) {
 		int ret;
 
-		ret = readcfgfile(wl->s);
+		ret = readcfgfile(wl->s, 0);
 		if (ret == -1) {
 			Alert("Could not open configuration file %s : %s\n",
 			      wl->s, strerror(errno));

Reply via email to