/*  snmp-ups.c - NUT Meta SNMP driver (support different MIBS)
 *
 *  Based on NetSNMP API (Simple Network Management Protocol V1-2)
 *
 *  Copyright (C) 2002-2006 
 *  			Arnaud Quette <arnaud.quette@free.fr>
 *  			Dmitry Frolov <frolov@riss-telecom.ru>
 *  			J.W. Hoogervorst <jeroen@hoogervorst.net>
 *  			Niels Baggesen <niels@baggesen.net>
 *
 *  Sponsored by MGE UPS SYSTEMS <http://opensource.mgeups.com/>
 *
 *  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
 *
 */

/* NUT SNMP common functions */
#include "snmp-ups.h"
#include "main.h"
#include "parseconf.h"

/* include all known mib2nut lookup tables */
#include "apccmib.h"
#include "ietfmib.h"
#include "mgemib.h"
#include "netvisionmib.h"
#include "pwmib.h"

mib2nut_info_t mib2nut[] = {
	{ "apcc", APCC_MIB_VERSION, APCC_OID_POWER_STATUS,
		".1.3.6.1.4.1.318.1.1.1.1.1.1.0", apcc_mib },
	{ "mge", MGE_MIB_VERSION, "",
		MGE_OID_MODEL_NAME, mge_mib },
	{ "netvision", NETVISION_MIB_VERSION, "",
		NETVISION_OID_UPSIDENTMODEL, netvision_mib },
	{ "pw", PW_MIB_VERSION, "",
		PW_OID_MODEL_NAME, pw_mib },
	{ "ietf", IETF_MIB_VERSION, IETF_OID_POWER_STATUS,
		IETF_OID_MFR_NAME, ietf_mib },
	{ NULL }
};

/* pointer to the Snmp2Nut lookup table */
snmp_info_t *snmp_info;
const char *mibname;
const char *mibvers;

time_t lastpoll;

/* ---------------------------------------------
 * driver functions implementations
 * --------------------------------------------- */
void upsdrv_initinfo(void)
{
	snmp_info_t *su_info_p;
	char version[128];

	upsdebugx(1, "SNMP UPS driver : entering upsdrv_initinfo()");

	snprintf(version, sizeof version, "%s (mib: %s %s)",
		DRIVER_VERSION, mibname, mibvers);
	dstate_setinfo("driver.version.internal", version);
	
	/* add instant commands to the info database. */
	for (su_info_p = &snmp_info[0]; su_info_p->info_type != NULL ; su_info_p++)			
		su_info_p->flags |= SU_FLAG_OK;
		if (SU_TYPE(su_info_p) == SU_TYPE_CMD)
			dstate_addcmd(su_info_p->info_type);

	/* setup handlers for instcmd and setvar functions */
	upsh.setvar = su_setvar;
	upsh.instcmd = su_instcmd;

	if (testvar("notransferoids"))
		disable_transfer_oids();

	/* initialize all other INFO_ fields from list */
	if (snmp_ups_walk(SU_WALKMODE_INIT))
		dstate_dataok();
	else		
		dstate_datastale();

	/* store timestamp */
	lastpoll = time(NULL);
}

void upsdrv_updateinfo(void)
{
	upsdebugx(1,"SNMP UPS driver : entering upsdrv_updateinfo()");
	
	/* only update every pollfreq */
	if (time(NULL) > (lastpoll + pollfreq)) {

		/* update all dynamic info fields */
		if (snmp_ups_walk(SU_WALKMODE_UPDATE))
			dstate_dataok();
		else		
			dstate_datastale();	
	
		/* store timestamp */
		lastpoll = time(NULL);
	}
}

void upsdrv_shutdown(void)
{
	/* TODO: su_shutdown_ups(); */
	
	/* replace with a proper shutdown function */
	fatalx(EXIT_FAILURE, "shutdown not supported");
}

void upsdrv_help(void)
{
	upsdebugx(1, "entering upsdrv_help");
}

/* list flags and values that you want to receive via -x */
void upsdrv_makevartable(void)
{
	upsdebugx(1, "entering upsdrv_makevartable()");

	addvar(VAR_VALUE, SU_VAR_MIBS,
	    "Set MIB compliance (default=ietf, allowed mge,apcc,netvision,pw)");
	addvar(VAR_VALUE | VAR_SENSITIVE, SU_VAR_COMMUNITY,
	    "Set community name (default=public)");
	addvar(VAR_VALUE, SU_VAR_VERSION,
	    "Set SNMP version (default=v1, allowed v2c)");
	addvar(VAR_VALUE, SU_VAR_POLLFREQ,
	    "Set polling frequency in seconds, to reduce network flow (default=30)");
	addvar(VAR_FLAG, "notransferoids",
	    "Disable transfer OIDs (use on APCC Symmetras)");
}

void upsdrv_banner(void)
{
	upslogx(1,"Network UPS Tools - Multi-MIBS SNMP UPS driver %s (%s)", 
		DRIVER_VERSION, UPS_VERSION);
	
	experimental_driver = 1;
}

void upsdrv_initups(void)
{
	snmp_info_t *su_info_p;
	char model[SU_INFOSIZE];
	bool_t status;
	const char *community, *version, *mibs;

	upsdebugx(1, "SNMP UPS driver : entering upsdrv_initups()");
	
	community = testvar(SU_VAR_COMMUNITY) ? getval(SU_VAR_COMMUNITY) : "public";
	version = testvar(SU_VAR_VERSION) ? getval(SU_VAR_VERSION) : "v1";
	mibs = testvar(SU_VAR_MIBS) ? getval(SU_VAR_MIBS) : "auto";

	/* init SNMP library, etc... */
	nut_snmp_init(progname, device_path, version, community);

	/* Load the SNMP to NUT translation data */
	/* read_mibconf(SU_VAR_MIBS) ? getval(SU_VAR_MIBS) : "ietf"); */
	load_mib2nut(mibs);

	/* init polling frequency */
	if (getval(SU_VAR_POLLFREQ))
		pollfreq = atoi(getval(SU_VAR_POLLFREQ));
	else
		pollfreq = DEFAULT_POLLFREQ;
	
  	/* Get UPS Model node to see if there's a MIB */
	su_info_p = su_find_info("ups.model");
	status = nut_snmp_get_str(su_info_p->OID, model, sizeof(model), NULL);

	if (status == TRUE)
		upslogx(0, "Detected %s on host %s (mib: %s %s)",
			 model, device_path, mibname, mibvers);
	else
		fatalx(EXIT_FAILURE, "%s MIB wasn't found on %s", mibs, g_snmp_sess.peername);   
}

void upsdrv_cleanup(void)
{
	nut_snmp_cleanup();
}

/* -----------------------------------------------------------
 * SNMP functions.
 * ----------------------------------------------------------- */

void nut_snmp_init(const char *type, const char *hostname, const char *version,
		const char *community)
{  
	upsdebugx(2, "SNMP UPS driver : entering nut_snmp_init(%s, %s, %s, %s)",
		type, hostname, version, community);

	/* Initialize the SNMP library */
	init_snmp(type);

	/* Initialize session */
	snmp_sess_init(&g_snmp_sess);

	g_snmp_sess.peername = xstrdup(hostname);
	g_snmp_sess.community = xstrdup(community);
	g_snmp_sess.community_len = strlen(community);
	if (strcmp(version, "v1") == 0)
		g_snmp_sess.version = SNMP_VERSION_1;
	else if (strcmp(version, "v2c") == 0)
		g_snmp_sess.version = SNMP_VERSION_2c;
	else
		fatalx(EXIT_FAILURE, "Bad SNMP version: %s", version);

	/* Open the session */
	SOCK_STARTUP;
	g_snmp_sess_p = snmp_open(&g_snmp_sess);	/* establish the session */
	if (g_snmp_sess_p == NULL) {
		nut_snmp_perror(&g_snmp_sess, 0, NULL, "nut_snmp_init: snmp_open");
		fatalx(EXIT_FAILURE, "Unable to establish communication");
	}
}

void nut_snmp_cleanup(void)
{
	/* close snmp session. */
	if (g_snmp_sess_p) {
		snmp_close(g_snmp_sess_p);
		g_snmp_sess_p = NULL;
	}
	SOCK_CLEANUP;
}

struct snmp_pdu *nut_snmp_get(const char *OID)
{
	int status;
	struct snmp_pdu *pdu, *response = NULL;
	oid name[MAX_OID_LEN];
	size_t name_len = MAX_OID_LEN;
	static unsigned int numerr = 0;

	/* create and send request. */
	if (!snmp_parse_oid(OID, name, &name_len)) {
		upslogx(LOG_ERR, "[%s] nut_snmp_get: %s: %s",
			upsname?upsname:device_name, OID, snmp_api_errstring(snmp_errno));
		return NULL;
	}

	pdu = snmp_pdu_create(SNMP_MSG_GET);
	
	if (pdu == NULL)
		fatalx(EXIT_FAILURE, "Not enough memory");
	
	snmp_add_null_var(pdu, name, name_len);

	status = snmp_synch_response(g_snmp_sess_p, pdu, &response);

	if (!response)
		return NULL;

	if (!((status == STAT_SUCCESS) && (response->errstat == SNMP_ERR_NOERROR)))
	{
		if (mibname == NULL) {
			/* We are probing for proper mib - ignore errors */
			snmp_free_pdu(response);
			return NULL;
		}

		numerr++;

		if ((numerr == SU_ERR_LIMIT) || ((numerr % SU_ERR_RATE) == 0))
			upslogx(LOG_WARNING, "[%s] Warning: excessive poll "
				"failures, limiting error reporting",
				upsname?upsname:device_name);

		if ((numerr < SU_ERR_LIMIT) || ((numerr % SU_ERR_RATE) == 0))
			nut_snmp_perror(g_snmp_sess_p, status, response, 
				"nut_snmp_get: %s", OID);

		snmp_free_pdu(response);
		response = NULL;
	} else {
		numerr = 0;
	}

	return response;
}

bool_t nut_snmp_get_str(const char *OID, char *buf, size_t buf_len, info_lkp_t *oid2info)
{
	size_t len = 0;
	struct snmp_pdu *pdu;

	/* zero out buffer. */
	memset(buf, 0, buf_len);

	pdu = nut_snmp_get(OID);
	if (pdu == NULL)
		return FALSE;

	switch (pdu->variables->type) {
	case ASN_OCTET_STR:
	case ASN_OPAQUE:
		len = pdu->variables->val_len > buf_len - 1 ?
			buf_len - 1 : pdu->variables->val_len;
		memcpy(buf, pdu->variables->val.string, len);
		buf[len] = '\0';
		break;
	case ASN_INTEGER:
	case ASN_GAUGE:
		if(oid2info) {
			const char *str;
			if((str=su_find_infoval(oid2info, *pdu->variables->val.integer))) {
				strncpy(buf, str, buf_len-1);
			}
			else {
				strncpy(buf, "UNKNOWN", buf_len-1);
			}
			buf[buf_len-1]='\0';
		}
		else {
			len = snprintf(buf, buf_len, "%ld", *pdu->variables->val.integer);
		}
		break;
	case ASN_TIMETICKS:
		/* convert timeticks to seconds */
		len = snprintf(buf, buf_len, "%ld", *pdu->variables->val.integer / 100);
		break;
	default:
		upslogx(LOG_ERR, "[%s] unhandled ASN 0x%x recieved from %s",
			upsname?upsname:device_name, pdu->variables->type, OID);
		return FALSE;
		break;
	}

	snmp_free_pdu(pdu);

	return TRUE;
}

bool_t nut_snmp_get_int(const char *OID, long *pval)
{
	struct snmp_pdu *pdu;
	long value;
	char *buf;

	pdu = nut_snmp_get(OID);
	if (pdu == NULL)
		return FALSE;

	switch (pdu->variables->type) {
	case ASN_OCTET_STR:
	case ASN_OPAQUE:
		buf = xmalloc(pdu->variables->val_len + 1);
		memcpy(buf, pdu->variables->val.string, pdu->variables->val_len);
		buf[pdu->variables->val_len] = '\0';
		value = strtol(buf, NULL, 0);
		free(buf);
		break;
	case ASN_INTEGER:
	case ASN_GAUGE:
		value = *pdu->variables->val.integer;
		break;
	case ASN_TIMETICKS:
		/* convert timeticks to seconds */
		value = *pdu->variables->val.integer / 100;
		break;
	default:
		upslogx(LOG_ERR, "[%s] unhandled ASN 0x%x recieved from %s",
			upsname?upsname:device_name, pdu->variables->type, OID);
		return FALSE;
		break;
	}

	snmp_free_pdu(pdu);

	if (pval != NULL)
		*pval = value;

	return TRUE;
}

bool_t nut_snmp_set(const char *OID, char type, const char *value)
{
	int status;
	bool_t ret = FALSE;
	struct snmp_pdu *pdu, *response = NULL;
	oid name[MAX_OID_LEN];
	size_t name_len = MAX_OID_LEN;
	
	if (!snmp_parse_oid(OID, name, &name_len)) {
		upslogx(LOG_ERR, "[%s] nut_snmp_set: %s: %s",
			upsname?upsname:device_name, OID, snmp_api_errstring(snmp_errno));
		return FALSE;
	}

	pdu = snmp_pdu_create(SNMP_MSG_SET);
	if (pdu == NULL)
		fatalx(EXIT_FAILURE, "Not enough memory");

	if (snmp_add_var(pdu, name, name_len, type, value)) {
		upslogx(LOG_ERR, "[%s] nut_snmp_set: %s: %s",
			upsname?upsname:device_name, OID, snmp_api_errstring(snmp_errno));
		
		return FALSE;
	}

	status = snmp_synch_response(g_snmp_sess_p, pdu, &response);

	if ((status == STAT_SUCCESS) && (response->errstat == SNMP_ERR_NOERROR))
		ret = TRUE;
	else
		nut_snmp_perror(g_snmp_sess_p, status, response,
			"nut_snmp_set: can't set %s", OID);

	snmp_free_pdu(response);
	return ret;
}

bool_t nut_snmp_set_str(const char *OID, const char *value)
{
	return nut_snmp_set(OID, 's', value);
}

bool_t nut_snmp_set_int(const char *OID, long value)
{
	char buf[SU_BUFSIZE];

	snprintf(buf, sizeof(buf), "%ld", value);
	return nut_snmp_set(OID, 'i', buf);
}

bool_t nut_snmp_set_time(const char *OID, long value)
{
	char buf[SU_BUFSIZE];

	snprintf(buf, SU_BUFSIZE, "%ld", value * 100);
	return nut_snmp_set(OID, 't', buf);
}

/* log descriptive SNMP error message. */
void nut_snmp_perror(struct snmp_session *sess, int status,
	struct snmp_pdu *response, const char *fmt, ...)
{
	va_list va;
	int cliberr, snmperr;
	char *snmperrstr;
	char buf[SU_LARGEBUF];

	va_start(va, fmt);
	vsnprintf(buf, sizeof(buf), fmt, va);
	va_end(va);

	if (response == NULL) {
		snmp_error(sess, &cliberr, &snmperr, &snmperrstr);
		upslogx(LOG_ERR, "[%s] %s: %s",
			upsname?upsname:device_name, buf, snmperrstr);
		free(snmperrstr);
	} else if (status == STAT_SUCCESS) {
		if (response->errstat != SNMP_ERR_NOERROR)
			upslogx(LOG_ERR, "[%s] %s: Error in packet: %s",
				upsname?upsname:device_name, buf, snmp_errstring(response->errstat));
	} else if (status == STAT_TIMEOUT) {
		upslogx(LOG_ERR, "[%s] %s: Timeout: no response from %s",
			upsname?upsname:device_name, buf, sess->peername);
	} else {
		snmp_sess_error(sess, &cliberr, &snmperr, &snmperrstr);
		upslogx(LOG_ERR, "[%s] %s: %s",
			upsname?upsname:device_name, buf, snmperrstr);
		free(snmperrstr);
	}
}

/* -----------------------------------------------------------
 * utility functions.
 * ----------------------------------------------------------- */

/* deal with APCC weirdness on Symmetras */
static void disable_transfer_oids(void)
{
	snmp_info_t *su_info_p;

	upslogx(LOG_INFO, "Disabling transfer OIDs");

	for (su_info_p = &snmp_info[0]; su_info_p->info_type != NULL ; su_info_p++) {
		if (!strcasecmp(su_info_p->info_type, "input.transfer.low")) {
			su_info_p->flags &= ~SU_FLAG_OK;
			continue;
		}

		if (!strcasecmp(su_info_p->info_type, "input.transfer.high")) {
			su_info_p->flags &= ~SU_FLAG_OK;
			continue;
		}
	}
}

/* universal function to add or update info element. */
void su_setinfo(const char *type, const char *value, int flags, int auxdata)
{
	snmp_info_t *su_info_p;
	
	upsdebugx(1, "SNMP UPS driver : entering su_setinfo(%s)", type);
			
	su_info_p = su_find_info(type);
	
	if (SU_TYPE(su_info_p) == SU_TYPE_CMD)
		return;

	if (strcasecmp(type, "ups.status")) {
		dstate_setinfo(type, value);
		dstate_setflags(type, flags);
		dstate_setaux(type, auxdata);
	}
}

void su_status_set(snmp_info_t *su_info_p, long value)
{	
	const char *info_value = NULL;

	upsdebugx(2, "SNMP UPS driver : entering su_status_set()");

	if ((info_value = su_find_infoval(su_info_p->oid2info, value)) != NULL)
	{
		if (strcmp(info_value, "")) {
			status_init();
			status_set(info_value);
			status_commit();
		}
	}
	/* TODO: else */
}

/* find info element definition in my info array. */
snmp_info_t *su_find_info(const char *type)
{
	snmp_info_t *su_info_p;

	for (su_info_p = &snmp_info[0]; su_info_p->info_type != NULL ; su_info_p++)
		if (!strcasecmp(su_info_p->info_type, type))
			return su_info_p;
		
	fatalx(EXIT_FAILURE, "nut_snmp_find_info: unknown info type: %s", type);
	return NULL;
}

/* Load the right snmp_info_t structure matching mib parameter */
void load_mib2nut(const char *mib)
{
	mib2nut_info_t *mp = mib2nut;
	upsdebugx(2, "SNMP UPS driver : entering load_mib2nut(%s)", mib);
	
/*	read_mibconf(mib); */
	
	while (mp->mib_name) {
		if (strcmp(mib, mp->mib_name) == 0)
			break;
		else if (strcmp(mib, "auto") == 0) {
			int status;
			char buf[1024];
			upsdebugx(1, "load_mib2nut: trying %s mib", mp->mib_name);
			status = nut_snmp_get_str(mp->oid_auto_check,
						buf, sizeof buf, NULL);
			if (status)
				break;
		}
		mp++;
	}
	if (mp->mib_name) {
		snmp_info = mp->snmp_info;
		OID_pwr_status = mp->oid_pwr_status;
		mibname = mp->mib_name;
		mibvers = mp->mib_version;
		upsdebugx(1, "load_mib2nut: using %s mib", mibname);
	}
	else
		fatalx(EXIT_FAILURE, "Unknown mibs value: %s", mib);
}

/* find the OID value matching that INFO_* value */
long su_find_valinfo(info_lkp_t *oid2info, char* value)
{
	info_lkp_t *info_lkp;

	for (info_lkp = oid2info; (info_lkp != NULL) &&
		(strcmp(info_lkp->info_value, "NULL")); info_lkp++) {
			
		if (!(strcmp(info_lkp->info_value, value))) {
			upsdebugx(1, "su_find_valinfo: found %s (value: %s)",
					info_lkp->info_value, value);
			
			return info_lkp->oid_value;
		}
	}
	upsdebugx(1, "su_find_valinfo: no matching INFO_* value for this OID value (%s)", value);
	return -1;
}

/* find the INFO_* value matching that OID value */
const char *su_find_infoval(info_lkp_t *oid2info, long value)
{
	info_lkp_t *info_lkp;
		
	for (info_lkp = oid2info; (info_lkp != NULL) &&
		(strcmp(info_lkp->info_value, "NULL")); info_lkp++) {
			
		if (info_lkp->oid_value == value) {
			upsdebugx(1, "su_find_infoval: found %s (value: %ld)",
					info_lkp->info_value, value);
			
			return info_lkp->info_value;
		}
	}
	upsdebugx(1, "su_find_infoval: no matching INFO_* value for this OID value (%ld)", value);
	return NULL;
}

static void disable_competition(snmp_info_t *entry)
{
	snmp_info_t	*p;

	for(p=snmp_info; p->info_type!=NULL; p++) {
		if(p!=entry && !strcmp(p->info_type, entry->info_type)) {
			upsdebugx(2, "disable_competition: disabling %s %s",
					p->info_type, p->OID);
			p->flags &= ~SU_FLAG_OK;
		}
	}
}

/* walk ups variables and set elements of the info array. */
bool_t snmp_ups_walk(int mode)
{
	static unsigned long iterations = 0;
	snmp_info_t *su_info_p;
	bool_t status = FALSE;
	
	for (su_info_p = &snmp_info[0]; su_info_p->info_type != NULL ; su_info_p++) {

		/* skip instcmd. */
		if (SU_TYPE(su_info_p) == SU_TYPE_CMD) {
			upsdebugx(1, "SU_CMD_MASK => %s", su_info_p->OID);
			continue;
		}
		/* skip elements we shouldn't show. */
		if (!(su_info_p->flags & SU_FLAG_OK))
			continue;
		
		/* skip static elements in update mode. */
		if (mode == SU_WALKMODE_UPDATE && 
				su_info_p->flags & SU_FLAG_STATIC)
			continue;

		/* set default value if we cannot fetch it */
		/* and set static flag on this element. */
		if (su_info_p->flags & SU_FLAG_ABSENT) {
			if (mode == SU_WALKMODE_INIT) {
				if (su_info_p->dfl) {
					/* Set default value if we cannot fetch it from ups. */
					su_setinfo(su_info_p->info_type, su_info_p->dfl,
						su_info_p->info_flags, su_info_p->info_len);
				}
				su_info_p->flags |= SU_FLAG_STATIC;
			}
			continue;
		}

		/* check stale elements only on each PN_STALE_RETRY iteration. */
		if ((su_info_p->flags & SU_FLAG_STALE) &&
				(iterations % SU_STALE_RETRY) != 0)
			continue;

		if (su_info_p->flags & SU_INPHASES) {
			upsdebugx(1, "Check inphases");
		    	if (input_phases == 0) continue;
			upsdebugx(1, "inphases is set");
			if (su_info_p->flags & SU_INPUT_1) {
			    	if (input_phases == 1)
					su_info_p->flags &= ~SU_INPHASES;
				else {
					upsdebugx(1, "inphases is not 1");
				    	su_info_p->flags &= ~SU_FLAG_OK;
					continue;
				}
			}
			else if (su_info_p->flags & SU_INPUT_3) {
			    	if (input_phases == 3)
					su_info_p->flags &= ~SU_INPHASES;
				else {
					upsdebugx(1, "inphases is not 3");
				    	su_info_p->flags &= ~SU_FLAG_OK;
					continue;
				}
			}
		}

		if (su_info_p->flags & SU_OUTPHASES) {
		    	if (output_phases == 0) continue;
			if (su_info_p->flags & SU_OUTPUT_1) {
			    	if (output_phases == 1)
					su_info_p->flags &= ~SU_OUTPHASES;
				else {
					su_info_p->flags &= ~SU_FLAG_OK;
					continue;
				}
			}
			else if (su_info_p->flags & SU_OUTPUT_3) {
			    	if (output_phases == 3)
					su_info_p->flags &= ~SU_OUTPHASES;
				else {
				    	su_info_p->flags &= ~SU_FLAG_OK;
					continue;
				}
			}
		}

		/* ok, update this element. */
		status = su_ups_get(su_info_p);
		
		/* set stale flag if data is stale, clear if not. */
		if (status == TRUE) {
			if (su_info_p->flags & SU_FLAG_STALE) {
				upslogx(LOG_INFO, "[%s] snmp_ups_walk: data resumed for %s",
					upsname?upsname:device_name, su_info_p->info_type);
				su_info_p->flags &= ~SU_FLAG_STALE;
			}
			if(su_info_p->flags & SU_FLAG_UNIQUE) {
				/* We should be the only provider of this */
				disable_competition(su_info_p);
				su_info_p->flags &= ~SU_FLAG_UNIQUE;
			}
			dstate_dataok();
		} else {
			if (mode == SU_WALKMODE_INIT) {
				/* handle unsupported vars */
				su_info_p->flags &= ~SU_FLAG_OK;
			} else	{
				if (!(su_info_p->flags & SU_FLAG_STALE)) {
					upslogx(LOG_INFO, "[%s] snmp_ups_walk: data stale for %s",
						upsname?upsname:device_name, su_info_p->info_type);
					su_info_p->flags |= SU_FLAG_STALE;
				}
				dstate_datastale();
			}
		}
	}	/* for (su_info_p... */

	iterations++;
	
	return status;	
}

bool_t su_ups_get(snmp_info_t *su_info_p)
{
	static char buf[SU_INFOSIZE];
	bool_t status;
	long value;

	upsdebugx(2, "su_ups_get: %s %s", su_info_p->info_type, su_info_p->OID);
	
	if (!strcasecmp(su_info_p->info_type, "ups.status")) {

		status = nut_snmp_get_int(su_info_p->OID, &value);
		if (status == TRUE)
		{
			su_status_set(su_info_p, value);
			upsdebugx(2, "=> value: %ld", value);
		}
		else upsdebugx(2, "=> Failed");

		return status;
	}

	/* another special case */
	if (!strcasecmp(su_info_p->info_type, "ambient.temperature")) {
		float temp=0;

		status = nut_snmp_get_int(su_info_p->OID, &value);

		if(status != TRUE) {
			return status;
		}

		/* only do this if using the IEM sensor */
		if (!strcmp(su_info_p->OID, APCC_OID_IEM_TEMP)) {
			int	su;
			long	units;

			su = nut_snmp_get_int(APCC_OID_IEM_TEMP_UNIT, &units);

			/* no response, or units == F */
			if ((su == FALSE) || (units == APCC_IEM_FAHRENHEIT))
				temp = (value - 32) / 1.8;
		}
		else {
			temp=value;
		}

		sprintf(buf, "%.1f", temp);
		su_setinfo(su_info_p->info_type, buf,
			su_info_p->info_flags, su_info_p->info_len);

		return TRUE;
	}			

	if (su_info_p->info_flags == 0) {
		status = nut_snmp_get_int(su_info_p->OID, &value);
		if (status == TRUE) {
			if (su_info_p->flags&SU_FLAG_NEGINVALID && value<0) {
				su_info_p->flags &= ~SU_FLAG_OK;
				if(su_info_p->flags&SU_FLAG_UNIQUE) {
					disable_competition(su_info_p);
					su_info_p->flags &= ~SU_FLAG_UNIQUE;
				}
				return FALSE;
			}
			if (su_info_p->flags & SU_FLAG_SETINT) {
			    	upsdebugx(1, "setvar %s", su_info_p->OID);
			    	*su_info_p->setvar = value;
			}
			sprintf(buf, "%.1f", value * su_info_p->info_len);
		}
	} else {
		status = nut_snmp_get_str(su_info_p->OID, buf, sizeof(buf), su_info_p->oid2info);
	}
		
	if (status == TRUE) {
		su_setinfo(su_info_p->info_type, buf,
			su_info_p->info_flags, su_info_p->info_len);
		
		upsdebugx(2, "=> value: %s", buf);
	}
	else upsdebugx(2, "=> Failed");
	return status;
}

/* set r/w INFO_ element to a value. */
int su_setvar(const char *varname, const char *val)
{
	snmp_info_t *su_info_p;
	bool_t ret;

	upsdebugx(2, "entering su_setvar()");

	su_info_p = su_find_info(varname);

	if (su_info_p == NULL || su_info_p->info_type == NULL ||
		!(su_info_p->flags & SU_FLAG_OK))
	{
		upslogx(LOG_ERR, "su_setvar: info element unavailable %s", varname);
		return STAT_SET_UNKNOWN;
	}

	if (!(su_info_p->info_flags & ST_FLAG_RW) || su_info_p->OID == NULL) {
		upslogx(LOG_ERR, "su_setvar: not writable %s", varname);
		return STAT_SET_UNKNOWN; /* STAT_SET_UNHANDLED would be better */
	}

	/* set value. */
	if (SU_TYPE(su_info_p) == SU_TYPE_STRING) {
		ret = nut_snmp_set_str(su_info_p->OID, val);
	} else {
		ret = nut_snmp_set_int(su_info_p->OID, strtol(val, NULL, 0));
	}

	if (ret == FALSE)
		upslogx(LOG_ERR, "su_setvar: cannot set value %s for %s", val, su_info_p->OID);
	else
		upsdebugx(1, "su_setvar: sucessfully set %s to \"%s\"", su_info_p->info_type, val);

	/* update info array. */
	su_setinfo(varname, val, su_info_p->info_flags, su_info_p->info_len);

	/* TODO: check su_setinfo() retcode */
	return STAT_SET_HANDLED;
}

/* process instant command and take action. */
int su_instcmd(const char *cmdname, const char *extradata)
{
	snmp_info_t *su_info_p;
	int status;

	upsdebugx(2, "entering su_instcmd()");

	su_info_p = su_find_info(cmdname);

	if ((su_info_p->info_type == NULL) || !(su_info_p->flags & SU_FLAG_OK) ||
		(su_info_p->OID == NULL))
	{
		upslogx(LOG_ERR, "su_instcmd: %s unavailable", cmdname);
		return STAT_INSTCMD_UNKNOWN;
	}

	/* set value. */
	if (su_info_p->info_flags & ST_FLAG_STRING) {
		status = nut_snmp_set_str(su_info_p->OID, su_info_p->dfl);
	} else {
		status = nut_snmp_set_int(su_info_p->OID, su_info_p->info_len);
	}

	if (status == FALSE) {
		upslogx(LOG_ERR, "su_instcmd: cannot set value for %s", cmdname);
		return STAT_INSTCMD_UNKNOWN;
	} else {
		upsdebugx(1, "su_instcmd: successfully sent command %s", cmdname);
		return STAT_INSTCMD_HANDLED;
	}	
}

/* TODO: complete rewrite */
void su_shutdown_ups(void)
{
	int sdtype = 0;
	long pwr_status;

	if (nut_snmp_get_int(OID_pwr_status, &pwr_status) == FALSE)
		fatalx(EXIT_FAILURE, "cannot determine UPS status");

	if (testvar(SU_VAR_SDTYPE))
		sdtype = atoi(getval(SU_VAR_SDTYPE));

	/* logic from newapc.c */
	switch (sdtype) {
	case 3:		/* shutdown with grace period */
		upslogx(LOG_INFO, "sending delayed power off command to UPS");
		su_instcmd("shutdown.stayoff", "0");
		break;
	case 2:		/* instant shutdown */
		upslogx(LOG_INFO, "sending power off command to UPS");
		su_instcmd("load.off", "0");
		break;
	case 1:
		/* Send a combined set of shutdown commands which can work better */
		/* if the UPS gets power during shutdown process */
		/* Specifically it sends both the soft shutdown 'S' */
		/* and the powerdown after grace period - '@000' commands */
/*		upslogx(LOG_INFO, "UPS - sending shutdown/powerdown");
		if (pwr_status == g_pwr_battery)
			su_ups_instcmd(CMD_SOFTDOWN, 0, 0);
		su_ups_instcmd(CMD_SDRET, 0, 0);
		break;
*/	
	default:
		/* if on battery... */
/*		if (pwr_status == su_find_valinfo(info_lkp_t *oid2info, "OB")) {
			upslogx(LOG_INFO,
				"UPS is on battery, sending shutdown command...");
			su_ups_instcmd(CMD_SOFTDOWN, 0, 0);
		} else {
			upslogx(LOG_INFO, "UPS is online, sending shutdown+return command...");
			su_ups_instcmd(CMD_SDRET, 0, 0);
		}
*/
		break;
	}
}

/* return 1 if usable, 0 if not */
static int parse_mibconf_args(int numargs, char **arg)
{
	bool_t ret;
	
	/* everything below here uses up through arg[1] */
	if (numargs < 6)
		return 0;

	/* <info type> <info flags> <info len> <OID name> <default value> <value lookup> */

	/* special case for setting some OIDs value at driver startup */
	if (!strcmp(arg[0], "init")) {
		/* set value. */
		if (!strcmp(arg[1], "str")) {
			ret = nut_snmp_set_str(arg[3], arg[4]);
		} else {
			ret = nut_snmp_set_int(arg[3], strtol(arg[4], NULL, 0));
		}
	
		if (ret == FALSE)
			upslogx(LOG_ERR, "su_setvar: cannot set value %s for %s", arg[4], arg[3]);
		else
			upsdebugx(1, "su_setvar: sucessfully set %s to \"%s\"", arg[0], arg[4]);

		return 1;
	}

	/* TODO: create the lookup table */
	upsdebugx(2, "%s, %s, %s, %s, %s, %s", arg[0], arg[1], arg[2], arg[3], arg[4], arg[5]);

	return 1;
}
/* called for fatal errors in parseconf like malloc failures */
static void mibconf_err(const char *errmsg)
{
	upslogx(LOG_ERR, "Fatal error in parseconf (*mib.conf): %s", errmsg);
}
/* load *mib.conf into an snmp_info_t structure */
void read_mibconf(char *mib)
{
	char	fn[SMALLBUF];
	PCONF_CTX_t	ctx;

	upsdebugx(2, "SNMP UPS driver : entering read_mibconf(%s)", mib);
	
	snprintf(fn, sizeof(fn), "%s/snmp/%s.conf", CONFPATH, mib);

	pconf_init(&ctx, mibconf_err);

	if (!pconf_file_begin(&ctx, fn))
		fatalx(EXIT_FAILURE, "%s", ctx.errmsg);

	while (pconf_file_next(&ctx)) {
		if (pconf_parse_error(&ctx)) {
			upslogx(LOG_ERR, "Parse error: %s:%d: %s",
				fn, ctx.linenum, ctx.errmsg);
			continue;
		}

		if (ctx.numargs < 1)
			continue;

		if (!parse_mibconf_args(ctx.numargs, ctx.arglist)) {
			unsigned int	i;
			char	errmsg[SMALLBUF];

			snprintf(errmsg, sizeof(errmsg), 
				"mib.conf: invalid directive");

			for (i = 0; i < ctx.numargs; i++)
				snprintfcat(errmsg, sizeof(errmsg), " %s", 
					ctx.arglist[i]);

			upslogx(LOG_WARNING, "%s", errmsg);
		}
	}
	pconf_finish(&ctx);		
}


syntax highlighted by Code2HTML, v. 0.9.1