plugins_dir convert to array

If OOT lws plugins will be packaged as separate projects,
they're going to want to install their plugins somewhere
that makes sense for the package instead of one big lws
plugin dir.

This patch changes info to have a const char ** to a NULL
terminated array of directories it should search for
plugins.  lwsws knows about this and you can add to the
dir array using config fragments like

{
  "global": {
   "plugin-dir": "/usr/local/share/coherent-timeline/plugins"
  }
}

if the config fragment in /etc/lwsws/conf.d/ is also managed by the
package with the plugin, it can very cleanly add and remove itself
from lwsws based on package install status.

Signed-off-by: Andy Green <andy@warmcat.com>
diff --git a/README.lwsws.md b/README.lwsws.md
index 28cf5c1..63b0266 100644
--- a/README.lwsws.md
+++ b/README.lwsws.md
@@ -284,6 +284,19 @@
 dumb increment, mirror and status protocol plugins are provided as examples.
 
 
+Additional plugin search paths
+------------------------------
+
+Packages that have their own lws plugins can install them in their own
+preferred dir and ask lwsws to scan there by using a config fragment
+like this, in its own conf.d/ file managed by the other package
+
+{
+  "global": {
+   "plugin-dir": "/usr/local/share/coherent-timeline/plugins"
+  }
+}
+
 
 lws-server-status plugin
 ------------------------
diff --git a/lib/libuv.c b/lib/libuv.c
index b00d2a8..a230b57 100644
--- a/lib/libuv.c
+++ b/lib/libuv.c
@@ -416,7 +416,7 @@
 #if defined(LWS_WITH_PLUGINS) && (UV_VERSION_MAJOR > 0)
 
 LWS_VISIBLE int
-lws_plat_plugins_init(struct lws_context * context, const char *d)
+lws_plat_plugins_init(struct lws_context * context, const char * const *d)
 {
 	struct lws_plugin_capability lcaps;
 	struct lws_plugin *plugin;
@@ -434,65 +434,71 @@
 
 	uv_loop_init(&loop);
 
-	if (!uv_fs_scandir(&loop, &req, d, 0, NULL)) {
-		lwsl_err("Scandir on %s failed\n", d);
-		return 1;
-	}
-
 	lwsl_notice("  Plugins:\n");
 
-	while (uv_fs_scandir_next(&req, &dent) != UV_EOF) {
-		if (strlen(dent.name) < 7)
+	while (d && *d) {
+
+		lwsl_notice("  Scanning %s\n", *d);
+		m =uv_fs_scandir(&loop, &req, *d, 0, NULL);
+		if (m < 1) {
+			lwsl_err("Scandir on %s failed\n", *d);
+			return 1;
+		}
+
+		while (uv_fs_scandir_next(&req, &dent) != UV_EOF) {
+			if (strlen(dent.name) < 7)
+				continue;
+
+			lwsl_notice("   %s\n", dent.name);
+
+			snprintf(path, sizeof(path) - 1, "%s/%s", *d, dent.name);
+			if (uv_dlopen(path, &lib)) {
+				uv_dlerror(&lib);
+				lwsl_err("Error loading DSO: %s\n", lib.errmsg);
+				goto bail;
+			}
+			/* we could open it, can we get his init function? */
+			m = snprintf(path, sizeof(path) - 1, "init_%s",
+				     dent.name + 3 /* snip lib... */);
+			path[m - 3] = '\0'; /* snip the .so */
+			if (uv_dlsym(&lib, path, &v)) {
+				uv_dlerror(&lib);
+				lwsl_err("Failed to get init on %s: %s",
+						dent.name, lib.errmsg);
+				goto bail;
+			}
+			initfunc = (lws_plugin_init_func)v;
+			lcaps.api_magic = LWS_PLUGIN_API_MAGIC;
+			m = initfunc(context, &lcaps);
+			if (m) {
+				lwsl_err("Initializing %s failed %d\n", dent.name, m);
+				goto skip;
+			}
+
+			plugin = lws_malloc(sizeof(*plugin));
+			if (!plugin) {
+				lwsl_err("OOM\n");
+				goto bail;
+			}
+			plugin->list = context->plugin_list;
+			context->plugin_list = plugin;
+			strncpy(plugin->name, dent.name, sizeof(plugin->name) - 1);
+			plugin->name[sizeof(plugin->name) - 1] = '\0';
+			plugin->lib = lib;
+			plugin->caps = lcaps;
+			context->plugin_protocol_count += lcaps.count_protocols;
+			context->plugin_extension_count += lcaps.count_extensions;
+
 			continue;
 
-		lwsl_notice("   %s\n", dent.name);
-
-		snprintf(path, sizeof(path) - 1, "%s/%s", d, dent.name);
-		if (uv_dlopen(path, &lib)) {
-			uv_dlerror(&lib);
-			lwsl_err("Error loading DSO: %s\n", lib.errmsg);
-			goto bail;
-		}
-		/* we could open it, can we get his init function? */
-		m = snprintf(path, sizeof(path) - 1, "init_%s",
-			     dent.name + 3 /* snip lib... */);
-		path[m - 3] = '\0'; /* snip the .so */
-		if (uv_dlsym(&lib, path, &v)) {
-			uv_dlerror(&lib);
-			lwsl_err("Failed to get init on %s: %s",
-					dent.name, lib.errmsg);
-			goto bail;
-		}
-		initfunc = (lws_plugin_init_func)v;
-		lcaps.api_magic = LWS_PLUGIN_API_MAGIC;
-		m = initfunc(context, &lcaps);
-		if (m) {
-			lwsl_err("Initializing %s failed %d\n", dent.name, m);
-			goto skip;
-		}
-
-		plugin = lws_malloc(sizeof(*plugin));
-		if (!plugin) {
-			lwsl_err("OOM\n");
-			goto bail;
-		}
-		plugin->list = context->plugin_list;
-		context->plugin_list = plugin;
-		strncpy(plugin->name, dent.name, sizeof(plugin->name) - 1);
-		plugin->name[sizeof(plugin->name) - 1] = '\0';
-		plugin->lib = lib;
-		plugin->caps = lcaps;
-		context->plugin_protocol_count += lcaps.count_protocols;
-		context->plugin_extension_count += lcaps.count_extensions;
-
-		continue;
-
 skip:
-		uv_dlclose(&lib);
+			uv_dlclose(&lib);
+		}
+bail:
+		uv_fs_req_cleanup(&req);
+		d++;
 	}
 
-bail:
-	uv_fs_req_cleanup(&req);
 	uv_loop_close(&loop);
 
 	return ret;
diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h
index 1f69346..82ed98d 100644
--- a/lib/libwebsockets.h
+++ b/lib/libwebsockets.h
@@ -1478,8 +1478,8 @@
  * @vhost_name: VHOST: name of vhost, must match external DNS name used to
  *		access the site, like "warmcat.com" as it's used to match
  *		Host: header and / or SNI name for SSL.
- * @plugins_dir: CONTEXT: directory to scan for lws protocol plugins at
- *		context creation time
+ * @plugin_dirs: CONTEXT: NULL, or NULL-terminated array of directories to
+ *		scan for lws protocol plugins at context creation time
  * @pvo:	VHOST: pointer to optional linked list of per-vhost
  *		options made accessible to protocols
  * @keepalive_timeout: VHOST: (default = 0 = 60s) seconds to allow remote
@@ -1525,7 +1525,7 @@
 	unsigned int timeout_secs;			/* VH */
 	const char *ecdh_curve;				/* VH */
 	const char *vhost_name;				/* VH */
-	const char *plugins_dir;			/* context */
+	const char * const *plugin_dirs;		/* context */
 	const struct lws_protocol_vhost_options *pvo;	/* VH */
 	int keepalive_timeout;				/* VH */
 	const char *log_filepath;			/* VH */
diff --git a/lib/lws-plat-unix.c b/lib/lws-plat-unix.c
index e4894fa..98b6c58 100644
--- a/lib/lws-plat-unix.c
+++ b/lib/lws-plat-unix.c
@@ -306,7 +306,7 @@
 }
 
 LWS_VISIBLE int
-lws_plat_plugins_init(struct lws_context * context, const char *d)
+lws_plat_plugins_init(struct lws_context * context, const char * const *d)
 {
 	struct lws_plugin_capability lcaps;
 	struct lws_plugin *plugin;
@@ -316,70 +316,73 @@
 	char path[256];
 	void *l;
 
-
-	n = scandir(d, &namelist, filter, alphasort);
-	if (n < 0) {
-		lwsl_err("Scandir on %s failed\n", d);
-		return 1;
-	}
-
 	lwsl_notice("  Plugins:\n");
 
-	for (i = 0; i < n; i++) {
-		if (strlen(namelist[i]->d_name) < 7)
-			goto inval;
-
-		lwsl_notice("   %s\n", namelist[i]->d_name);
-
-		snprintf(path, sizeof(path) - 1, "%s/%s", d,
-			 namelist[i]->d_name);
-		l = dlopen(path, RTLD_NOW);
-		if (!l) {
-			lwsl_err("Error loading DSO: %s\n", dlerror());
-			while (i++ < n)
-				free(namelist[i]);
-			goto bail;
+	while (d && *d) {
+		n = scandir(*d, &namelist, filter, alphasort);
+		if (n < 0) {
+			lwsl_err("Scandir on %s failed\n", *d);
+			return 1;
 		}
-		/* we could open it, can we get his init function? */
-		m = snprintf(path, sizeof(path) - 1, "init_%s",
-			     namelist[i]->d_name + 3 /* snip lib... */);
-		path[m - 3] = '\0'; /* snip the .so */
-		initfunc = dlsym(l, path);
-		if (!initfunc) {
-			lwsl_err("Failed to get init on %s: %s",
-					namelist[i]->d_name, dlerror());
+
+		for (i = 0; i < n; i++) {
+			if (strlen(namelist[i]->d_name) < 7)
+				goto inval;
+
+			lwsl_notice("   %s\n", namelist[i]->d_name);
+
+			snprintf(path, sizeof(path) - 1, "%s/%s", *d,
+				 namelist[i]->d_name);
+			l = dlopen(path, RTLD_NOW);
+			if (!l) {
+				lwsl_err("Error loading DSO: %s\n", dlerror());
+				while (i++ < n)
+					free(namelist[i]);
+				goto bail;
+			}
+			/* we could open it, can we get his init function? */
+			m = snprintf(path, sizeof(path) - 1, "init_%s",
+				     namelist[i]->d_name + 3 /* snip lib... */);
+			path[m - 3] = '\0'; /* snip the .so */
+			initfunc = dlsym(l, path);
+			if (!initfunc) {
+				lwsl_err("Failed to get init on %s: %s",
+						namelist[i]->d_name, dlerror());
+				dlclose(l);
+			}
+			lcaps.api_magic = LWS_PLUGIN_API_MAGIC;
+			m = initfunc(context, &lcaps);
+			if (m) {
+				lwsl_err("Initializing %s failed %d\n",
+					namelist[i]->d_name, m);
+				dlclose(l);
+				goto skip;
+			}
+
+			plugin = lws_malloc(sizeof(*plugin));
+			if (!plugin) {
+				lwsl_err("OOM\n");
+				goto bail;
+			}
+			plugin->list = context->plugin_list;
+			context->plugin_list = plugin;
+			strncpy(plugin->name, namelist[i]->d_name, sizeof(plugin->name) - 1);
+			plugin->name[sizeof(plugin->name) - 1] = '\0';
+			plugin->l = l;
+			plugin->caps = lcaps;
+			context->plugin_protocol_count += lcaps.count_protocols;
+			context->plugin_extension_count += lcaps.count_extensions;
+
+			free(namelist[i]);
+			continue;
+
+	skip:
 			dlclose(l);
+	inval:
+			free(namelist[i]);
 		}
-		lcaps.api_magic = LWS_PLUGIN_API_MAGIC;
-		m = initfunc(context, &lcaps);
-		if (m) {
-			lwsl_err("Initializing %s failed %d\n",
-				namelist[i]->d_name, m);
-			dlclose(l);
-			goto skip;
-		}
-
-		plugin = lws_malloc(sizeof(*plugin));
-		if (!plugin) {
-			lwsl_err("OOM\n");
-			goto bail;
-		}
-		plugin->list = context->plugin_list;
-		context->plugin_list = plugin;
-		strncpy(plugin->name, namelist[i]->d_name, sizeof(plugin->name) - 1);
-		plugin->name[sizeof(plugin->name) - 1] = '\0';
-		plugin->l = l;
-		plugin->caps = lcaps;
-		context->plugin_protocol_count += lcaps.count_protocols;
-		context->plugin_extension_count += lcaps.count_extensions;
-
-		free(namelist[i]);
-		continue;
-
-skip:
-		dlclose(l);
-inval:
-		free(namelist[i]);
+		free(namelist);
+		d++;
 	}
 
 bail:
@@ -707,8 +710,8 @@
 	context->fops.write	= _lws_plat_file_write;
 
 #ifdef LWS_WITH_PLUGINS
-	if (info->plugins_dir)
-		lws_plat_plugins_init(context, info->plugins_dir);
+	if (info->plugin_dirs)
+		lws_plat_plugins_init(context, info->plugin_dirs);
 #endif
 
 	return 0;
diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h
index 91f4d75..df2bbf6 100644
--- a/lib/private-libwebsockets.h
+++ b/lib/private-libwebsockets.h
@@ -770,7 +770,7 @@
 lws_libuv_closehandle(struct lws *wsi);
 
 LWS_VISIBLE LWS_EXTERN int
-lws_plat_plugins_init(struct lws_context * context, const char *d);
+lws_plat_plugins_init(struct lws_context * context, const char * const *d);
 
 LWS_VISIBLE LWS_EXTERN int
 lws_plat_plugins_destroy(struct lws_context * context);
diff --git a/lwsws/conf.c b/lwsws/conf.c
index 31d38f8..f222ed6 100644
--- a/lwsws/conf.c
+++ b/lwsws/conf.c
@@ -27,6 +27,7 @@
 	"global.count-threads",
 	"global.init-ssl",
 	"global.server-string",
+	"global.plugin-dir"
 };
 
 enum lejp_global_paths {
@@ -35,6 +36,7 @@
 	LEJPGP_COUNT_THREADS,
 	LWJPGP_INIT_SSL,
 	LEJPGP_SERVER_STRING,
+	LEJPGP_PLUGIN_DIR
 };
 
 static const char * const paths_vhosts[] = {
@@ -91,6 +93,8 @@
 	LEJPVP_KEEPALIVE_TIMEOUT,
 };
 
+#define MAX_PLUGIN_DIRS 10
+
 struct jpargs {
 	struct lws_context_creation_info *info;
 	struct lws_context *context;
@@ -101,6 +105,8 @@
 
 	struct lws_protocol_vhost_options *pvo;
 	struct lws_http_mount m;
+	const char **plugin_dirs;
+	int count_plugin_dirs;
 };
 
 static void *
@@ -154,6 +160,13 @@
 	case LEJPGP_SERVER_STRING:
 		a->info->server_string = a->p;
 		break;
+	case LEJPGP_PLUGIN_DIR:
+		if (a->count_plugin_dirs == MAX_PLUGIN_DIRS - 1) {
+			lwsl_err("Too many plugin dirs\n");
+			return -1;
+		}
+		a->plugin_dirs[a->count_plugin_dirs++] = a->p;
+		break;
 
 	default:
 		return 0;
@@ -537,12 +550,27 @@
 			 char **cs, int *len)
 {
 	struct jpargs a;
+	const char * const *old = info->plugin_dirs;
+
+	memset(&a, 0, sizeof(a));
 
 	a.info = info;
 	a.p = *cs;
 	a.end = (a.p + *len) - 1;
 	a.valid = 0;
 
+	lwsws_align(&a);
+	info->plugin_dirs = (void *)a.p;
+	a.plugin_dirs = (void *)a.p; /* writeable version */
+	a.p += MAX_PLUGIN_DIRS * sizeof(void *);
+
+	/* copy any default paths */
+
+	while (old && *old) {
+		a.plugin_dirs[a.count_plugin_dirs++] = *old;
+		old++;
+	}
+
 	if (lwsws_get_config(&a, "/etc/lwsws/conf", paths_global,
 			     ARRAY_SIZE(paths_global), lejp_globals_cb) > 1)
 		return 1;
@@ -550,6 +578,8 @@
 			       ARRAY_SIZE(paths_global), lejp_globals_cb) > 1)
 		return 1;
 
+	a.plugin_dirs[a.count_plugin_dirs] = NULL;
+
 	*cs = a.p;
 	*len = a.end - a.p;
 
@@ -563,6 +593,8 @@
 {
 	struct jpargs a;
 
+	memset(&a, 0, sizeof(a));
+
 	a.info = info;
 	a.p = *cs;
 	a.end = a.p + *len;
diff --git a/lwsws/main.c b/lwsws/main.c
index 8d84552..2a7579f 100644
--- a/lwsws/main.c
+++ b/lwsws/main.c
@@ -80,6 +80,11 @@
 	{ NULL, NULL, NULL /* terminator */ }
 };
 
+static const char * const plugin_dirs[] = {
+		INSTALL_DATADIR"/libwebsockets-test-server/plugins/",
+		NULL
+};
+
 static struct option options[] = {
 	{ "help",	no_argument,		NULL, 'h' },
 	{ "debug",	required_argument,	NULL, 'd' },
@@ -107,6 +112,8 @@
 }
 #endif
 
+
+
 int main(int argc, char **argv)
 {
 	struct lws_context_creation_info info;
@@ -184,8 +191,7 @@
 			      LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
 			      LWS_SERVER_OPTION_LIBUV;
 
-	info.plugins_dir = INSTALL_DATADIR"/libwebsockets-test-server/plugins/";
-
+	info.plugin_dirs = plugin_dirs;
 	lwsl_notice("Using config dir: \"%s\"\n", config_dir);
 
 	/*
diff --git a/test-server/test-server-v2.0.c b/test-server/test-server-v2.0.c
index 28a9264..8702049 100644
--- a/test-server/test-server-v2.0.c
+++ b/test-server/test-server-v2.0.c
@@ -155,6 +155,11 @@
 	{ NULL, 0, 0, 0 }
 };
 
+static const char * const plugin_dirs[] = {
+		INSTALL_DATADIR"/libwebsockets-test-server/plugins/",
+		NULL
+};
+
 int main(int argc, char **argv)
 {
 	struct lws_context_creation_info info;
@@ -344,7 +349,7 @@
 			       "!AES256-SHA256";
 
 	/* tell lws to look for protocol plugins here */
-	info.plugins_dir = INSTALL_DATADIR"/libwebsockets-test-server/plugins/";
+	info.plugin_dirs = plugin_dirs;
 
 	/* tell lws about our mount we want */
 	info.mounts = &mount;