From 784a5405a3eccfdabfe4ce9ecb94507284a008cc Mon Sep 17 00:00:00 2001
From: Quentin Armitage <quentin@armitage.org.uk>
Date: Sun, 10 Jan 2021 17:54:58 +0000
Subject: [PATCH] parser: fix building with Musl libc

The code was using the GLOB_ALTDIRFUNC GNU extension to glob() without
checking if it was supported.

This commit now checks for GLOB_ALTDIRFUNC and reduces the include
checks if it is not supported.

Signed-off-by: Quentin Armitage <quentin@armitage.org.uk>
---
 configure.ac                      |  3 ++
 doc/man/man5/keepalived.conf.5.in |  7 ++++-
 lib/parser.c                      | 49 ++++++++++++++++++++++++++-----
 3 files changed, 51 insertions(+), 8 deletions(-)

diff --git a/configure.ac b/configure.ac
index 93adfd10e..0219ed67b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2176,6 +2176,9 @@ CPPFLAGS="$SAV_CPPFLAGS"
 dnl ----[ Check for GLOB_BRACE support ]----
 AC_CHECK_DECLS([GLOB_BRACE], [add_system_opt([GLOB_BRACE])], [], [[#include <glob.h>]])
 
+dnl ----[ Check for GLOB_ALTDIRFUNC support ]----
+AC_CHECK_DECLS([GLOB_ALTDIRFUNC], [add_system_opt([GLOB_ALTDIRFUNC])], [], [[#include <glob.h>]])
+
 dnl ----[ Check for timegm() support ]----
 AC_MSG_CHECKING([for timegm()])
 SAV_CPPFLAGS="$CPPFLAGS"
diff --git a/doc/man/man5/keepalived.conf.5.in b/doc/man/man5/keepalived.conf.5.in
index 6f06f8a26..4f2adb88c 100644
--- a/doc/man/man5/keepalived.conf.5.in
+++ b/doc/man/man5/keepalived.conf.5.in
@@ -44,10 +44,13 @@ The variants are:
 .br
 \fBincludea\fR FILENAME - all include_check checks
 .PP
+NOTE: If the libc glob() function does not support GLOB_ALTDIRFUNC (e.g. Musl libc as on Alpine Linux etc.),
+then only includea, includer and includew of the above options will work.
+.PP
 Why do we want to allow errors? Suppose a configuration has optional files in /etc/keepalived/conf.d,
 then \fIinclude_/etc/keepalived/conf.d/*\fR could be specified, but it should not
 error if there are no files in the directory; in this case \fIincluder\fR should be used. Otherwise it
-is sensible to use \fIincludet\fR.
+is sensible to use \fIincludea\fR.
 
 include handling will not work with if the include line uses conditional configuration or
 parameter substitution, since the detection of the include keywords is done before the
@@ -759,6 +762,8 @@ and
     # file cannot be changed by a subsequently included file. To change the setting for all
     # included files, include_check should be set at the beginning of the configuration file
     # specified in the command line (default /etc/keepalived/keepalived.conf).
+    # Note2: If the libc glob() function does not support GLOB_ALTDIRFUNC (e.g. Musl libc as
+    # on Alpine Linux etc.), then only readable and wildcard_match of the above options will work.
     # It is possible to add or remove individual settings; '+' means add the following
     # checks, '-' means remove the following checks. For example
     #   \fIinclude_check +match -wildcard_match\fR
diff --git a/lib/parser.c b/lib/parser.c
index 7dd90377b..9e9af007b 100644
--- a/lib/parser.c
+++ b/lib/parser.c
@@ -62,7 +62,6 @@
 #include "utils.h"
 #include "process.h"
 
-
 #ifdef USE_MEMFD_CREATE_SYSCALL
 #ifndef SYS_memfd_create
 #define SYS_memfd_create __NR_memfd_create
@@ -203,9 +202,11 @@ bool do_dump_keywords;
 static unsigned include_check;
 
 /* The following 3 variables should be static, but that causes an optimiser bug in GCC */
+#if HAVE_DECL_GLOB_ALTDIRFUNC
 unsigned missing_directories;
 unsigned missing_files;
 bool have_wildcards;
+#endif
 static bool config_file_error;
 
 /* local vars */
@@ -1583,6 +1584,7 @@ dump_definitions(void)
 }
 #endif
 
+#if HAVE_DECL_GLOB_ALTDIRFUNC
 static DIR *
 gl_opendir(const char *name)
 {
@@ -1628,6 +1630,7 @@ have_brace(const char *conf_file)
 
 	return false;
 }
+#endif
 
 static bool
 open_and_check_glob(glob_t *globbuf, const char *conf_file, include_t include_type)
@@ -1636,11 +1639,13 @@ open_and_check_glob(glob_t *globbuf, const char *conf_file, include_t include_ty
 
 	globbuf->gl_offs = 0;
 
+#if HAVE_DECL_GLOB_ALTDIRFUNC
 	globbuf->gl_closedir = (void *)closedir;
 	globbuf->gl_readdir = (void *)readdir;
 	globbuf->gl_opendir = (void *)gl_opendir;
 	globbuf->gl_lstat = (void *)gl_lstat;
 	globbuf->gl_stat = (void *)stat;
+#endif
 
 	/* NOTE: the following three variables are not declared static, since otherwise GCC (at least v9.3.0,
 	 * 9.3.1 and 10.2.1) -O1 optimisation assumes that they cannot be altered by the call to glob(), if
@@ -1653,28 +1658,39 @@ open_and_check_glob(glob_t *globbuf, const char *conf_file, include_t include_ty
 	 *
 	 * See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=97783 for more details.
 	 */
+#if HAVE_DECL_GLOB_ALTDIRFUNC
 	missing_files = 0;
 	missing_directories = 0;
 	have_wildcards = false;
+#endif
 
-	res = glob(conf_file, GLOB_MARK | GLOB_ALTDIRFUNC
+	res = glob(conf_file, GLOB_MARK
 #if HAVE_DECL_GLOB_BRACE
 					| GLOB_BRACE
+#endif
+#if HAVE_DECL_GLOB_ALTDIRFUNC
+					| GLOB_ALTDIRFUNC
 #endif
 						    , NULL, globbuf);
 
 	if (res) {
 		if (res == GLOB_NOMATCH) {
+#if HAVE_DECL_GLOB_ALTDIRFUNC
 			if (missing_files || missing_directories)
 				file_config_error(have_brace(conf_file) ? INCLUDE_B : INCLUDE_M, "Config files missing '%s'.", conf_file);
 			else if (have_wildcards && ((include_check | include_type) & INCLUDE_W))
 				file_config_error(INCLUDE_W, "No config files matched '%s'.", conf_file);
+#else
+			if ((include_check | include_type) & INCLUDE_W)
+				file_config_error(INCLUDE_W, "No config files matched '%s'.", conf_file);
+#endif
 		} else
 			file_config_error(INCLUDE_R, "Error reading config file(s): glob(\"%s\") returned %d, skipping.", conf_file, res);
 
 		return false;
 	}
 
+#if HAVE_DECL_GLOB_ALTDIRFUNC
 	if (missing_directories || missing_files) {
 		file_config_error(INCLUDE_B, "Some config files missing: \"%s\".", conf_file);
 
@@ -1683,6 +1699,7 @@ open_and_check_glob(glob_t *globbuf, const char *conf_file, include_t include_ty
 			return false;
 		}
 	}
+#endif
 
 	return true;
 }
@@ -2449,14 +2466,21 @@ check_include(const char *buf)
 		p = buf + 9;
 		if (buf[7] == 'r')
 			include_type = INCLUDE_R;
-		else if (buf[7] == 'm')
-			include_type = INCLUDE_R | INCLUDE_M;
+		else if (buf[7] == 'a')
+			include_type = INCLUDE_R | INCLUDE_M | INCLUDE_B | INCLUDE_W;
 		else if (buf[7] == 'w')
 			include_type = INCLUDE_R | INCLUDE_M | INCLUDE_W;
-		else if (buf[7] == 'b')
+#if HAVE_DECL_GLOB_ALTDIRFUNC
+		else if (buf[7] == 'm')
+			include_type = INCLUDE_R | INCLUDE_M;
+		else /* if (buf[7] == 'b') */
 			include_type = INCLUDE_R | INCLUDE_B;
-		else /* if (buf[7] == 'a') */
-			include_type = INCLUDE_R | INCLUDE_M | INCLUDE_B | INCLUDE_W;
+#else
+		else {
+			report_config_error(CONFIG_WARNING, "include%c not supported - treating as includer", buf[7]);
+			include_type = INCLUDE_R;
+		}
+#endif
 	}
 
 	p += strspn(p, " \t");
@@ -3277,6 +3301,17 @@ void include_check_set(const vector_t *strvec)
 				new_flag = INCLUDE_B;
 			else
 				report_config_error(CONFIG_GENERAL_ERROR, "Unknown include_check type '%s' - ignoring", word + offset);
+#if !HAVE_DECL_GLOB_ALTDIRFUNC
+			if (new_flag & (INCLUDE_M | INCLUDE_B)) {
+				if (!add_remove) {
+					report_config_error(CONFIG_WARNING, "include_check type '%s' - not supported, treating as 'read'", word + offset);
+					new_flag = INCLUDE_R;
+				} else {
+					report_config_error(CONFIG_WARNING, "include_check type '%s' - not supported, ignoring", word + offset);
+					new_flag = 0;
+				}
+			}
+#endif
 
 			if (new_flag) {
 				if (!add_remove)
