/* 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see <http://www.gnu.org/licenses/>
 *
 * File: raexecocf.c
 * Author: Sun Jiang Dong <sunjd@cn.ibm.com>
 * Copyright (c) 2004 International Business Machines
 *
 * This code implements the Resource Agent Plugin Module for LSB style.
 * It's a part of Local Resource Manager. Currently it's used by lrmd only.
 */

#include <lha_internal.h>
#include <stdio.h>		
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <libgen.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <errno.h>
#include <glib.h>
#include <clplumbing/cl_log.h>
#include <clplumbing/realtime.h>
#include <pils/plugin.h>
#include <dirent.h>
#include <libgen.h>  /* Add it for compiling on OSX */
#ifdef HAVE_TIME_H
#include <time.h>
#endif

#include <lrm/raexec.h>

# define PIL_PLUGINTYPE		RA_EXEC_TYPE
# define PIL_PLUGINTYPE_S	"RAExec"
# define PIL_PLUGINLICENSE	LICENSE_PUBDOM
# define PIL_PLUGINLICENSEURL	URL_PUBDOM

# define PIL_PLUGIN		ocf
# define PIL_PLUGIN_S		"ocf"
/* 
 * Are there multiple paths? Now according to OCF spec, the answer is 'no'.
 * But actually or for future?
 */
static const char * RA_PATH = OCF_RA_DIR;

/* The begin of exported function list */
static int execra(const char * rsc_id,
		  const char * rsc_type,
		  const char * provider,
		  const char * op_type,
		  const int    timeout,
	 	  GHashTable * params);
static uniform_ret_execra_t map_ra_retvalue(int ret_execra, 
	   const char * op_type, const char * std_output);
static int get_resource_list(GList ** rsc_info);
static char* get_resource_meta(const char* rsc_type,  const char* provider);
static int get_provider_list(const char* ra_type, GList ** providers);

/* The end of exported function list */

/* The begin of internal used function & data list */
static void add_OCF_prefix(GHashTable * params, GHashTable * new_params);
static void add_OCF_env_vars(GHashTable * env, const char * rsc_id,
			     const char * rsc_type, const char * provider);
static void add_prefix_foreach(gpointer key, gpointer value,
				   gpointer user_data);

static void hash_to_str(GHashTable * , GString *);
static void hash_to_str_foreach(gpointer key, gpointer value,
				   gpointer user_data);

static int raexec_setenv(GHashTable * env_params);
static void set_env(gpointer key, gpointer value, gpointer user_data);
				   
static gboolean let_remove_eachitem(gpointer key, gpointer value,
				    gpointer user_data);
static int get_providers(const char* class_path, const char* op_type,
			 GList ** providers);
static void merge_string_list(GList** old, GList* new);
static gint compare_str(gconstpointer a, gconstpointer b);

/* The end of internal function & data list */

/* Rource agent execution plugin operations */
static struct RAExecOps raops =
{	execra,
	map_ra_retvalue,
	get_resource_list,
	get_provider_list,
	get_resource_meta
};

PIL_PLUGIN_BOILERPLATE2("1.0", Debug)

static const PILPluginImports*  PluginImports;
static PILPlugin*               OurPlugin;
static PILInterface*		OurInterface;
static void*			OurImports;
static void*			interfprivate;

/*
 * Our plugin initialization and registration function
 * It gets called when the plugin gets loaded.
 */
PIL_rc
PIL_PLUGIN_INIT(PILPlugin * us, const PILPluginImports* imports);

PIL_rc
PIL_PLUGIN_INIT(PILPlugin * us, const PILPluginImports* imports)
{
	/* Force the compiler to do a little type checking */
	(void)(PILPluginInitFun)PIL_PLUGIN_INIT;

	PluginImports = imports;
	OurPlugin = us;

	/* Register ourself as a plugin */
	imports->register_plugin(us, &OurPIExports);

	/*  Register our interfaces */
 	return imports->register_interface(us, PIL_PLUGINTYPE_S,  PIL_PLUGIN_S,
		&raops, NULL, &OurInterface, &OurImports,
		interfprivate);
}

/*
 * The function to execute a RA.
 */
static int
execra(const char * rsc_id, const char * rsc_type, const char * provider,
       const char * op_type, const int timeout, GHashTable * params)
{
	char ra_pathname[RA_MAX_NAME_LENGTH];
	GHashTable * tmp_for_setenv;
	GString * params_gstring;
	char * inherit_debuglevel = NULL;
	int save_errno;

	get_ra_pathname(RA_PATH, rsc_type, provider, ra_pathname);

	/* Setup environment correctly */
	tmp_for_setenv = g_hash_table_new(g_str_hash, g_str_equal);
	add_OCF_prefix(params, tmp_for_setenv);
	add_OCF_env_vars(tmp_for_setenv, rsc_id, rsc_type, provider);
	raexec_setenv(tmp_for_setenv);
	g_hash_table_foreach_remove(tmp_for_setenv, let_remove_eachitem, NULL);
	g_hash_table_destroy(tmp_for_setenv);

	/* let this log show only high loglevel. */
	inherit_debuglevel = getenv(HADEBUGVAL);
	if ((inherit_debuglevel != NULL) && (atoi(inherit_debuglevel) > 1)) {
		params_gstring = g_string_new("");
		hash_to_str(params, params_gstring);
		cl_log(LOG_DEBUG, "RA instance %s executing: OCF::%s %s. Parameters: "
			"{%s}", rsc_id, rsc_type, op_type, params_gstring->str);
		g_string_free(params_gstring, TRUE);
	}

	closefiles(); /* don't leak open files */
	/* execute the RA */
	execl(ra_pathname, ra_pathname, op_type, (const char *)NULL);
	/* oops, exec failed */
	save_errno = errno; /* cl_perror may change errno */
	cl_perror("(%s:%s:%d) execl failed for %s" 
		  , __FILE__, __FUNCTION__, __LINE__, ra_pathname);
	errno = save_errno;
	exit(get_failed_exec_rc());
}

static uniform_ret_execra_t
map_ra_retvalue(int ret_execra, const char * op_type, const char * std_output)
{
	/* Because the UNIFORM_RET_EXECRA is compatible with OCF standard,
         * no actual mapping except validating, which ensure the return code
         * will be in the range 0 to 7. Too strict?
         */
        if (ret_execra < 0 || ret_execra > 9) {
                cl_log(LOG_WARNING, "mapped the invalid return code %d."
                        , ret_execra);
                ret_execra = EXECRA_UNKNOWN_ERROR;
        }
	return ret_execra;
}

static gint
compare_str(gconstpointer a, gconstpointer b)
{
	return strncmp(a,b,RA_MAX_NAME_LENGTH);
}

static int
get_resource_list(GList ** rsc_info)
{
	struct dirent **namelist;
	GList* item;
	int file_num;
	char subdir[FILENAME_MAX+1];

	if ( rsc_info == NULL ) {
		cl_log(LOG_ERR, "Parameter error: get_resource_list");
		return -2;
	}

	if ( *rsc_info != NULL ) {
		cl_log(LOG_ERR, "Parameter error: get_resource_list."\
			"will cause memory leak.");
		*rsc_info = NULL;
	}
	file_num = scandir(RA_PATH, &namelist, NULL, alphasort);
	if (file_num < 0) {
		return -2;
	}
	while (file_num--) {
		GList* ra_subdir = NULL;
		struct stat prop;
		if ('.' == namelist[file_num]->d_name[0]) {
			free(namelist[file_num]);
			continue;
		}

		snprintf(subdir,FILENAME_MAX,"%s/%s",
			 RA_PATH, namelist[file_num]->d_name);

		if (stat(subdir, &prop) == -1) {
			cl_perror("%s:%s:%d: stat failed for %s" 
				  , __FILE__, __FUNCTION__, __LINE__, subdir);
			free(namelist[file_num]);
			continue;
		} else if (!S_ISDIR(prop.st_mode)) {
			free(namelist[file_num]);
			continue;
		}

		get_runnable_list(subdir,&ra_subdir);

		merge_string_list(rsc_info,ra_subdir);

		while (NULL != (item = g_list_first(ra_subdir))) {
			ra_subdir = g_list_remove_link(ra_subdir, item);
			g_free(item->data);
			g_list_free_1(item);
		}

		free(namelist[file_num]);
	}
	free(namelist);
			
	return 0;
}

static void
merge_string_list(GList** old, GList* new)
{
	GList* item = NULL;
	char* newitem;
	for( item=g_list_first(new); NULL!=item; item=g_list_next(item)){
		if (!g_list_find_custom(*old, item->data,compare_str)){
			newitem = g_strndup(item->data,RA_MAX_NAME_LENGTH);
			*old = g_list_append(*old, newitem);
		}
	}
}

static int
get_provider_list(const char* ra_type, GList ** providers)
{
	int ret;
	ret = get_providers(RA_PATH, ra_type, providers);
	if (0>ret) {
		cl_log(LOG_ERR, "scandir failed in OCF RA plugin");
	}
	return ret;
}

static char*
get_resource_meta(const char* rsc_type, const char* provider)
{
	const int BUFF_LEN=4096;
	int read_len = 0;
	char buff[BUFF_LEN];
	char* data = NULL;
	GString* g_str_tmp = NULL;
	char ra_pathname[RA_MAX_NAME_LENGTH];
	FILE* file = NULL;
	GHashTable * tmp_for_setenv;
	struct timespec short_sleep = {0,200000000L}; /*20ms*/

	get_ra_pathname(RA_PATH, rsc_type, provider, ra_pathname);

	strncat(ra_pathname, " meta-data",RA_MAX_NAME_LENGTH-strlen(ra_pathname)-1);
	tmp_for_setenv = g_hash_table_new(g_str_hash, g_str_equal);
	add_OCF_env_vars(tmp_for_setenv, "DUMMY_INSTANCE", rsc_type, provider);
	raexec_setenv(tmp_for_setenv);
	g_hash_table_foreach_remove(tmp_for_setenv, let_remove_eachitem, NULL);
	g_hash_table_destroy(tmp_for_setenv);

	file = popen(ra_pathname, "r");
	if (NULL==file) {
		cl_log(LOG_ERR, "%s: popen failed: %s", __FUNCTION__, strerror(errno));
		return NULL;
	}

	g_str_tmp = g_string_new("");
	while(!feof(file)) {
		read_len = fread(buff, 1, BUFF_LEN - 1, file);
		if (0<read_len) {
			*(buff+read_len) = '\0';
			g_string_append(g_str_tmp, buff);
		}
		else {
			nanosleep(&short_sleep,NULL);
		}
	}
	if( pclose(file) ) {
		cl_log(LOG_ERR, "%s: pclose failed: %s", __FUNCTION__, strerror(errno));
	}
	if (0 == g_str_tmp->len) {
		g_string_free(g_str_tmp, TRUE);
		return NULL;
	}
	data = (char*)g_new(char, g_str_tmp->len+1);
	data[0] = data[g_str_tmp->len] = 0;
	strncpy(data, g_str_tmp->str, g_str_tmp->len);

	g_string_free(g_str_tmp, TRUE);
	
	return data;
}

static void 
add_OCF_prefix(GHashTable * env_params, GHashTable * new_env_params)
{
	if (env_params) {
		g_hash_table_foreach(env_params, add_prefix_foreach,
				     new_env_params);
	}
}

static void
add_prefix_foreach(gpointer key, gpointer value, gpointer user_data)
{
	const int MAX_LENGTH_OF_ENV = 128;
	int prefix = STRLEN_CONST("OCF_RESKEY_");
	GHashTable * new_hashtable = (GHashTable *) user_data;
	char * newkey;
	int keylen = strnlen((char*)key, MAX_LENGTH_OF_ENV-prefix)+prefix+1;
	
	newkey = g_new(gchar, keylen);
	strncpy(newkey, "OCF_RESKEY_", keylen);
	strncat(newkey, key, keylen-strlen(newkey)-1);
	g_hash_table_insert(new_hashtable, (gpointer)newkey, g_strdup(value));
}

static void 
hash_to_str(GHashTable * params , GString * str)
{
	if (params) {
		g_hash_table_foreach(params, hash_to_str_foreach, str);
	}
}

static void
hash_to_str_foreach(gpointer key, gpointer value, gpointer user_data)
{
	char buffer_tmp[60];
	GString * str = (GString *)user_data;

	snprintf(buffer_tmp, 60, "%s=%s ", (char *)key, (char *)value);
	str = g_string_append(str, buffer_tmp);
}

static gboolean
let_remove_eachitem(gpointer key, gpointer value, gpointer user_data)
{
	g_free(key);
	g_free(value);
	return TRUE;
}

static int
raexec_setenv(GHashTable * env_params)
{
        if (env_params) {
        	g_hash_table_foreach(env_params, set_env, NULL);
        }
        return 0;
}

static void
set_env(gpointer key, gpointer value, gpointer user_data)
{
       if (setenv(key, value, 1) != 0) {
		cl_log(LOG_ERR, "setenv failed in raexecocf.");
	}
}

static int
get_providers(const char* class_path, const char* ra_type, GList ** providers)
{
	struct dirent **namelist;
	int file_num;

	if ( providers == NULL ) {
		cl_log(LOG_ERR, "Parameter error: get_providers");
		return -2;
	}

	if ( *providers != NULL ) {
		cl_log(LOG_ERR, "Parameter error: get_providers."\
			"will cause memory leak.");
		*providers = NULL;
	}

	file_num = scandir(class_path, &namelist, 0, alphasort);
	if (file_num < 0) {
		return -2;
	}else{
		char tmp_buffer[FILENAME_MAX+1];
		struct stat prop;

		while (file_num--) {
			if ('.' == namelist[file_num]->d_name[0]) {
				free(namelist[file_num]);
				continue;
			}
			snprintf(tmp_buffer,FILENAME_MAX,"%s/%s",
				 class_path, namelist[file_num]->d_name);
			stat(tmp_buffer, &prop);
			if (!S_ISDIR(prop.st_mode)) {
				free(namelist[file_num]);
				continue;
			}

			snprintf(tmp_buffer,FILENAME_MAX,"%s/%s/%s",
				 class_path, namelist[file_num]->d_name, ra_type);

			if ( filtered(tmp_buffer) == TRUE ) {
				*providers = g_list_append(*providers,
					g_strdup(namelist[file_num]->d_name));
			}
			free(namelist[file_num]);
		}
		free(namelist);
	}
	return g_list_length(*providers);
}

static void
add_OCF_env_vars(GHashTable * env, const char * rsc_id,
	         const char * rsc_type, const char * provider)
{
	if ( env == NULL ) {
		cl_log(LOG_WARNING, "env should not be a NULL pointer.");
		return;
	}
	
	g_hash_table_insert(env, g_strdup("OCF_RA_VERSION_MAJOR"), 
			    g_strdup("1"));
	g_hash_table_insert(env, g_strdup("OCF_RA_VERSION_MINOR"), 
			    g_strdup("0"));
	g_hash_table_insert(env, g_strdup("OCF_ROOT"), 
			    g_strdup(OCF_ROOT_DIR));

	if ( rsc_id != NULL ) {
		g_hash_table_insert(env, g_strdup("OCF_RESOURCE_INSTANCE"),
				    g_strdup(rsc_id));
	}

	/* Currently the rsc_type=="the filename of the RA script/executable",
	 * It seems always correct even in the furture. ;-)
	 */
	if ( rsc_type != NULL ) {
		g_hash_table_insert(env, g_strdup("OCF_RESOURCE_TYPE"), 
				    g_strdup(rsc_type));
	}

	/* Notes: this is not added to specification yet. Sept 10,2004 */
	if ( provider != NULL ) {
		g_hash_table_insert(env, g_strdup("OCF_RESOURCE_PROVIDER"),
			    	    g_strdup(provider));
	}
}

