#include "uwsgi.h"

extern struct uwsgi_server uwsgi;

#ifdef UWSGI_ELF
static void uwsgi_plugin_parse_section(char *filename) {
	size_t s_len = 0;
	char *buf = uwsgi_elf_section(filename, "uwsgi", &s_len);
	if (buf) {
		char *ctx = NULL;
		char *p = strtok_r(buf, "\n", &ctx);
		while (p) {
			char *equal = strchr(p, '=');
			if (equal) {
				*equal = 0;
				if (!strcmp(p, "requires")) {
					uwsgi_load_plugin(-1, equal + 1, NULL);
				}
			}
			p = strtok_r(NULL, "\n", &ctx);
		}
		free(buf);
	}
}
#endif

static int plugin_already_loaded(const char *plugin) {
	int i;

	for (i = 0; i < 256; i++) {
		if (uwsgi.p[i]->name) {
			if (!strcmp(plugin, uwsgi.p[i]->name)) {
#ifdef UWSGI_DEBUG
				uwsgi_log("%s plugin already available\n", plugin);
#endif
				return 1;
			}
		}
		if (uwsgi.p[i]->alias) {
			if (!strcmp(plugin, uwsgi.p[i]->alias)) {
#ifdef UWSGI_DEBUG
				uwsgi_log("%s plugin already available\n", plugin);
#endif
				return 1;
			}
		}
	}

	for (i = 0; i < uwsgi.gp_cnt; i++) {

		if (uwsgi.gp[i]->name) {
			if (!strcmp(plugin, uwsgi.gp[i]->name)) {
#ifdef UWSGI_DEBUG
				uwsgi_log("%s plugin already available\n", plugin);
#endif
				return 1;
			}
		}
		if (uwsgi.gp[i]->alias) {
			if (!strcmp(plugin, uwsgi.gp[i]->alias)) {
#ifdef UWSGI_DEBUG
				uwsgi_log("%s plugin already available\n", plugin);
#endif
				return 1;
			}
		}
	}

	return 0;
}

void *uwsgi_load_plugin(int modifier, char *plugin, char *has_option) {

	void *plugin_handle = NULL;
	char *plugin_abs_path = NULL;
	char *plugin_filename = NULL;

	int need_free = 0;
	char *plugin_name = plugin;
	char *plugin_symbol_name_start = plugin;

	struct uwsgi_plugin *up;
	char linkpath_buf[1024], linkpath[1024];
	int linkpath_size;

	char *colon = strchr(plugin_name, ':');
	if (colon) {
		colon[0] = 0;
		modifier = atoi(plugin_name);
		plugin_name = colon + 1;
		colon[0] = ':';
	}

	if (!uwsgi_endswith(plugin_name, "_plugin.so")) {
		plugin_name = uwsgi_concat2(plugin_name, "_plugin.so");
		need_free = 1;
	}

	plugin_symbol_name_start = plugin_name;

	// step 1: check for absolute plugin (stop if it fails)
	if (strchr(plugin_name, '/')) {
#ifdef UWSGI_ELF
		uwsgi_plugin_parse_section(plugin_name);
#endif
		plugin_handle = dlopen(plugin_name, RTLD_NOW | RTLD_GLOBAL);
		if (!plugin_handle) {
			if (!has_option)
				uwsgi_log("%s\n", dlerror());
			goto end;
		}
		plugin_symbol_name_start = uwsgi_get_last_char(plugin_name, '/');
		plugin_symbol_name_start++;
		plugin_abs_path = plugin_name;
		goto success;
	}

	// step dir, check for user-supplied plugins directory
	struct uwsgi_string_list *pdir = uwsgi.plugins_dir;
	while (pdir) {
		plugin_filename = uwsgi_concat3(pdir->value, "/", plugin_name);
#ifdef UWSGI_ELF
		uwsgi_plugin_parse_section(plugin_filename);
#endif
		plugin_handle = dlopen(plugin_filename, RTLD_NOW | RTLD_GLOBAL);
		if (plugin_handle) {
			plugin_abs_path = plugin_filename;
			//free(plugin_filename);
			goto success;
		}
		free(plugin_filename);
		plugin_filename = NULL;
		pdir = pdir->next;
	}

	// last step: search in compile-time plugin_dir
	if (!plugin_handle) {
		plugin_filename = uwsgi_concat3(UWSGI_PLUGIN_DIR, "/", plugin_name);
#ifdef UWSGI_ELF
		uwsgi_plugin_parse_section(plugin_filename);
#endif
		plugin_handle = dlopen(plugin_filename, RTLD_NOW | RTLD_GLOBAL);
		plugin_abs_path = plugin_filename;
		//free(plugin_filename);
	}

success:
	if (!plugin_handle) {
		if (!has_option)
			uwsgi_log("!!! UNABLE to load uWSGI plugin: %s !!!\n", dlerror());
	}
	else {
		char *plugin_entry_symbol = uwsgi_concat2n(plugin_symbol_name_start, strlen(plugin_symbol_name_start) - 3, "", 0);
		up = dlsym(plugin_handle, plugin_entry_symbol);
		if (!up) {
			// is it a link ?
			memset(linkpath_buf, 0, 1024);
			memset(linkpath, 0, 1024);
			if ((linkpath_size = readlink(plugin_abs_path, linkpath_buf, 1023)) > 0) {
				do {
					linkpath_buf[linkpath_size] = '\0';
					strncpy(linkpath, linkpath_buf, linkpath_size + 1);
				} while ((linkpath_size = readlink(linkpath, linkpath_buf, 1023)) > 0);
#ifdef UWSGI_DEBUG
				uwsgi_log("%s\n", linkpath);
#endif
				free(plugin_entry_symbol);
				up = dlsym(plugin_handle, plugin_entry_symbol);
				char *slash = uwsgi_get_last_char(linkpath, '/');
				if (!slash) {
					slash = linkpath;
				}
				else {
					slash++;
				}
				plugin_entry_symbol = uwsgi_concat2n(slash, strlen(slash) - 3, "", 0);
				up = dlsym(plugin_handle, plugin_entry_symbol);
			}
		}
		if (up) {
			if (!up->name) {
				uwsgi_log("the loaded plugin (%s) has no .name attribute\n", plugin_name);
				if (dlclose(plugin_handle)) {
					uwsgi_error("dlclose()");
				}
				if (need_free)
					free(plugin_name);
				if (plugin_filename)
					free(plugin_filename);
				free(plugin_entry_symbol);
				return NULL;
			}
			if (plugin_already_loaded(up->name)) {
				if (dlclose(plugin_handle)) {
					uwsgi_error("dlclose()");
				}
				if (need_free)
					free(plugin_name);
				if (plugin_filename)
					free(plugin_filename);
				free(plugin_entry_symbol);
				return NULL;
			}
			if (has_option) {
				struct uwsgi_option *op = up->options;
				int found = 0;
				while (op && op->name) {
					if (!strcmp(has_option, op->name)) {
						found = 1;
						break;
					}
					op++;
				}
				if (!found) {
					if (dlclose(plugin_handle)) {
						uwsgi_error("dlclose()");
					}
					if (need_free)
						free(plugin_name);
					if (plugin_filename)
						free(plugin_filename);
					free(plugin_entry_symbol);
					return NULL;
				}

			}
			if (modifier != -1) {
				fill_plugin_table(modifier, up);
				up->modifier1 = modifier;
			}
			else {
				fill_plugin_table(up->modifier1, up);
			}
			if (need_free)
				free(plugin_name);
			if (plugin_filename)
				free(plugin_filename);
			free(plugin_entry_symbol);

			if (up->on_load)
				up->on_load();
			return plugin_handle;
		}
		if (!has_option)
			uwsgi_log("%s\n", dlerror());
	}

end:
	if (need_free)
		free(plugin_name);
	if (plugin_filename)
		free(plugin_filename);

	return NULL;
}

int uwsgi_try_autoload(char *option) {
	DIR *d;
	struct dirent *dp;
	// step dir, check for user-supplied plugins directory
	struct uwsgi_string_list *pdir = uwsgi.plugins_dir;
	while (pdir) {
		d = opendir(pdir->value);
		if (d) {
			while ((dp = readdir(d)) != NULL) {
				if (uwsgi_endswith(dp->d_name, "_plugin.so")) {
					char *filename = uwsgi_concat3(pdir->value, "/", dp->d_name);
					if (uwsgi_load_plugin(-1, filename, option)) {
						uwsgi_log("option \"%s\" found in plugin %s\n", option, filename);
						free(filename);
						closedir(d);
						// add new options
						build_options();
						return 1;
					}
					free(filename);
				}
			}
			closedir(d);
		}
		pdir = pdir->next;
	}

	// last step: search in compile-time plugin_dir
	d = opendir(UWSGI_PLUGIN_DIR);
	if (!d)
		return 0;

	while ((dp = readdir(d)) != NULL) {
		if (uwsgi_endswith(dp->d_name, "_plugin.so")) {
			char *filename = uwsgi_concat3(UWSGI_PLUGIN_DIR, "/", dp->d_name);
			if (uwsgi_load_plugin(-1, filename, option)) {
				uwsgi_log("option \"%s\" found in plugin %s\n", option, filename);
				free(filename);
				closedir(d);
				// add new options
				build_options();
				return 1;
			}
			free(filename);
		}
	}

	closedir(d);

	return 0;

}
