/*
 * array-info - Array Controllers Informations
 * Copyright (C) 2002  Benoit Gaussen (ben@trez42.net)
 *
 * $Log: array_plugin.c,v $
 * Revision 1.4  2007/02/01 14:43:26  pere
 * Rewrite plugin API to pass driver data between the functions.
 *
 * Revision 1.3  2007/01/31 13:32:29  pere
 * Remove the need to link libarray-info into plugins by providing the functions needed in a list of callbacks.
 *
 * Revision 1.2  2002/07/30 14:12:46  trez42
 * Functions add and show infos
 *
 * Revision 1.1  2002/07/29 16:49:10  trez42
 * Add plugin support
 * Change directory structure
 *
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <dlfcn.h>

#include "array_plugin.h"

void
array_add_plugin(array_plugin_list_t ** list, array_plugin_t * plugin,
		 void *so_handle, char *so_path)
{
	array_plugin_list_t *new;

	new = malloc(sizeof (array_plugin_list_t));
	new->plugin = plugin;
	new->plugin_path = so_path;
	new->plugin_handle = so_handle;
	new->next = *list;

	*list = new;
}

int
array_plugin_register(array_plugin_list_t ** plugin_list, char *so_path)
{
	array_plugin_t *plugin;
	void *so_handle;

	if ((so_handle = dlopen(so_path, RTLD_NOW)) == NULL) {
		printf("dlopen error: %s\n", dlerror());
		return -1;
	}
	if ((plugin = dlsym(so_handle, "array_plugin")) == NULL) {
		printf("dlsym error: %s\n", dlerror());
		return -1;
	}
	if (VERSION_MAJOR(plugin->array_info_version) <
	    ARRAY_INFO_VERSION_MAJOR) {
		printf("%s: wrong version (compiled for %d.%d.%d)\n", so_path,
		       VERSION_MAJOR(plugin->array_info_version),
		       VERSION_MINOR(plugin->array_info_version),
		       VERSION_PATCH(plugin->array_info_version));
		return -1;
	}

	array_add_plugin(plugin_list, plugin, so_handle, so_path);

	return 0;
}

array_plugin_list_t *
array_load_plugins(char *so_path)
{
	DIR *so_dir;
	struct dirent *dir_ent;
	array_plugin_list_t *plugin_list = NULL;

	if ((so_dir = opendir(so_path)) == NULL) {
		perror("array_plugin_open");
		return NULL;
	}
	while ((dir_ent = readdir(so_dir))) {
		char *path;
		struct stat sbuf;

		if (strcasecmp
		    (dir_ent->d_name + strlen(dir_ent->d_name) - 3, ".so"))
			continue;
		path = malloc(strlen(so_path) + strlen(dir_ent->d_name) + 2);
		strcpy(path, so_path);
		strcat(path, "/");
		strcat(path, dir_ent->d_name);
		if (stat(path, &sbuf) == -1) {
			perror("array_plugin_open");
			return NULL;
		}
		if (!S_ISREG(sbuf.st_mode)) {
			free(path);
			continue;
		}
		array_plugin_register(&plugin_list, path);
	}
	closedir(so_dir);

	return plugin_list;
}

void
array_close_plugins(array_plugin_list_t * plugin_list)
{
	array_plugin_list_t *prev;

	while (plugin_list) {
		prev = plugin_list;
		dlclose(plugin_list->plugin_handle);
		plugin_list = plugin_list->next;
		free(prev);
	}
}

void
array_show_plugins(array_plugin_list_t * plugin_list)
{
	while (plugin_list) {
		printf("%s : %s\n", plugin_list->plugin_path,
		       plugin_list->plugin->plugin_description());
		plugin_list = plugin_list->next;
	}
}

array_plugin_t *
find_plugin(array_plugin_list_t * plugin_list, array_data_t * array_data)
{
	int i;

	while (plugin_list) {
		for (i = 0; plugin_list->plugin->device_majors[i].type; i++)
			if (plugin_list->plugin->device_majors[i].type ==
			    array_data->device_major.type
			    && plugin_list->plugin->device_majors[i].major ==
			    array_data->device_major.major
			    && (plugin_list->plugin->device_majors[i].minor ==
				-1
				|| plugin_list->plugin->device_majors[i].
				minor == array_data->device_major.minor))
				return plugin_list->plugin;
		plugin_list = plugin_list->next;
	}
	return NULL;
}

int
array_dispatch(array_plugin_list_t * plugin_list, array_data_t * array_data)
{
	array_plugin_t *plugin;
	array_infos_t *info;
	array_plugin_callbacks_t callbacks;

	if (!(plugin = find_plugin(plugin_list, array_data)))
		return -1;

	callbacks.array_add_infos = &array_add_infos;

	plugin->driver_data = plugin->ctrl_open(array_data);
	plugin->ctrl_req(plugin->driver_data);
	info = plugin->ctrl_infos(plugin->driver_data, &callbacks);
	array_show_infos(info, array_data->dump_flags);
	plugin->ctrl_free(plugin->driver_data);
	plugin->ctrl_close(plugin->driver_data);

	return 0;
}
