6
* powernet.c -- SNMP interface driver for APC SNMP devices.
7
* Tested on AP9606 APC Web/SNMP managenent card.
8
* Dmitry Frolov <frolov@riss-telecom.ru>
10
* Based on NUT snmp-ups driver:
11
* Copyright (C) 2002 Arnaud Quette <arnaud.quette@free.fr>
12
* some parts are Copyright (C) :
13
* Russell Kroll <rkroll@exploits.org>
14
* Hans Ekkehard Plesser <hans.plesser@itf.nlh.no>
16
* References: PowerNet MIB v 3.3.0
17
* You can get it from http://apcc.com/tools/download/
21
* Suggested entry in the makefile:
23
* COMMON_LIBDEP= main.o upscommon.o ../common/upsconf.o ../common/parseconf.o
24
* COMMON_INCDEP= ../include/shared.h ../include/shared-tables.h
26
* powernet: powernet.o $(COMMON_LIBDEP) $(LIBDEP)
27
* $(CC) $(CFLAGS) -o $@ $@.o $(COMMON_LIBDEP) $(LIBOBJ) \
30
* powernet.o: powernet.c powernet.h $(COMMON_INCDEP)
31
* $(CC) $(CFLAGS) -c powernet.c
35
* GNU GENERAL PUBLIC LICENSE
36
* Version 2, June 1991
38
* Copyright (C) 1989, 1991 Free Software Foundation, Inc.
39
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
41
* Everyone is permitted to copy and distribute verbatim copies
42
* of this license document, but changing it is not allowed.
44
* This program is free software; you can redistribute it and/or modify
45
* it under the terms of the GNU General Public License as published by
46
* the Free Software Foundation; either version 2 of the License, or
47
* (at your option) any later version.
49
* This program is distributed in the hope that it will be useful,
50
* but WITHOUT ANY WARRANTY; without even the implied warranty of
51
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
52
* GNU General Public License for more details.
54
* You should have received a copy of the GNU General Public License
55
* along with this program; if not, write to the Free Software
56
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
60
/* UCD SNMP includes and global data */
61
#include <arpa/inet.h> /* for ucd-snmp include bug */
62
#include <ucd-snmp/ucd-snmp-config.h>
63
#include <ucd-snmp/ucd-snmp-includes.h>
64
#include <ucd-snmp/system.h>
66
/* NUT includes and global data */
70
unsigned long g_ups_status[PN_STATUS_NUM_ELEM];
71
struct snmp_session g_snmp_sess, *g_snmp_sess_p = NULL;
75
/* -----------------------------------------------------------
77
* ----------------------------------------------------------- */
79
void pn_snmp_init(const char *type, const char *hostname, const char *community)
81
/* Initialize the SNMP library */
84
/* Initialize session */
85
snmp_sess_init(&g_snmp_sess);
87
g_snmp_sess.peername = (char *)hostname;
88
g_snmp_sess.community = (char *)community;
89
g_snmp_sess.community_len = strlen(community);
90
g_snmp_sess.version = SNMP_VERSION_1;
92
/* Open the session */
94
g_snmp_sess_p = snmp_open(&g_snmp_sess); /* establish the session */
95
if (g_snmp_sess_p == NULL) {
96
pn_snmp_perror(&g_snmp_sess, 0, NULL, "pn_snmp_init: snmp_open");
101
void pn_snmp_cleanup(void)
103
/* close snmp session. */
105
snmp_close(g_snmp_sess_p);
106
g_snmp_sess_p = NULL;
111
struct snmp_pdu *pn_snmp_get(const char *OID)
114
struct snmp_pdu *pdu, *response = NULL;
115
oid name[MAX_OID_LEN];
116
size_t name_len = MAX_OID_LEN;
118
/* create and send request. */
120
if (!read_objid(OID, name, &name_len)) {
121
upslogx(LOG_ERR, "[%s] pn_snmp_get: %s: %s",
122
upsname, OID, snmp_api_errstring(snmp_errno));
126
pdu = snmp_pdu_create(SNMP_MSG_GET);
128
fatalx("Not enough memory");
129
snmp_add_null_var(pdu, name, name_len);
131
status = snmp_synch_response(g_snmp_sess_p, pdu,
134
if (!((status == STAT_SUCCESS) && (response->errstat == SNMP_ERR_NOERROR)))
136
pn_snmp_perror(g_snmp_sess_p, status, response, "pn_snmp_get: %s", OID);
137
snmp_free_pdu(response);
144
bool pn_snmp_get_str(const char *OID, char *buf, size_t buf_len)
147
struct snmp_pdu *pdu;
149
/* zero out buffer. */
150
memset(buf, 0, buf_len);
152
pdu = pn_snmp_get(OID);
156
switch (pdu->variables->type) {
159
len = pdu->variables->val_len > buf_len - 1 ?
160
buf_len - 1 : pdu->variables->val_len;
161
memcpy(buf, pdu->variables->val.string, len);
166
len = snprintf(buf, buf_len, "%ld", *pdu->variables->val.integer);
169
/* convert timeticks to seconds */
170
len = snprintf(buf, buf_len, "%ld", *pdu->variables->val.integer / 100);
173
upslogx(LOG_ERR, "[%s] unhandled ASN 0x%x recieved from %s",
174
upsname, pdu->variables->type, OID);
184
bool pn_snmp_get_int(const char *OID, long *pval)
186
struct snmp_pdu *pdu;
190
pdu = pn_snmp_get(OID);
194
switch (pdu->variables->type) {
197
buf = xmalloc(pdu->variables->val_len + 1);
198
memcpy(buf, pdu->variables->val.string, pdu->variables->val_len);
199
buf[pdu->variables->val_len] = '\0';
200
value = strtol(buf, NULL, 0);
205
value = *pdu->variables->val.integer;
208
/* convert timeticks to seconds */
209
value = *pdu->variables->val.integer / 100;
212
upslogx(LOG_ERR, "[%s] unhandled ASN 0x%x recieved from %s",
213
upsname, pdu->variables->type, OID);
226
bool pn_snmp_set(const char *OID, char type, const char *value)
230
struct snmp_pdu *pdu, *response = NULL;
231
oid name[MAX_OID_LEN];
232
size_t name_len = MAX_OID_LEN;
234
if (!read_objid(OID, name, &name_len)) {
235
upslogx(LOG_ERR, "[%s] pn_snmp_set: %s: %s",
236
upsname, OID, snmp_api_errstring(snmp_errno));
240
pdu = snmp_pdu_create(SNMP_MSG_SET);
242
fatalx("Not enough memory");
244
if (snmp_add_var(pdu, name, name_len, type, value)) {
245
upslogx(LOG_ERR, "[%s] pn_snmp_set: %s: %s",
246
upsname, OID, snmp_api_errstring(snmp_errno));
250
status = snmp_synch_response(g_snmp_sess_p, pdu, &response);
252
if ((status == STAT_SUCCESS) && (response->errstat == SNMP_ERR_NOERROR))
255
pn_snmp_perror(g_snmp_sess_p, status, response,
256
"pn_snmp_set: can't set %s", OID);
258
snmp_free_pdu(response);
263
bool pn_snmp_set_str(const char *OID, const char *value)
265
return pn_snmp_set(OID, 's', value);
268
bool pn_snmp_set_int(const char *OID, long value)
271
char *buf = xmalloc(PN_BUFSIZE);
273
snprintf(buf, PN_BUFSIZE, "%ld", value);
274
ret = pn_snmp_set(OID, 'i', buf);
279
bool pn_snmp_set_time(const char *OID, long value)
282
char *buf = xmalloc(PN_BUFSIZE);
284
snprintf(buf, PN_BUFSIZE, "%ld", value * 100);
285
ret = pn_snmp_set(OID, 't', buf);
290
/* log descriptive SNMP error message. */
291
void pn_snmp_perror(struct snmp_session *sess, int status,
292
struct snmp_pdu *response, const char *fmt, ...)
295
int cliberr, snmperr;
299
buf = xmalloc(PN_LARGEBUF);
302
vsnprintf(buf, PN_LARGEBUF, fmt, va);
305
if (response == NULL) {
306
snmp_error(sess, &cliberr, &snmperr, &snmperrstr);
307
upslogx(LOG_ERR, "[%s] %s: %s",
308
upsname, buf, snmperrstr);
310
} else if (status == STAT_SUCCESS) {
311
if (response->errstat != SNMP_ERR_NOERROR)
312
upslogx(LOG_ERR, "[%s] %s: Error in packet: %s",
313
upsname, buf, snmp_errstring(response->errstat));
314
} else if (status == STAT_TIMEOUT) {
315
upslogx(LOG_ERR, "[%s] %s: Timeout: no response from %s",
316
upsname, sess->peername);
318
snmp_sess_error(sess, &cliberr, &snmperr, &snmperrstr);
319
upslogx(LOG_ERR, "[%s] %s: %s",
320
upsname, buf, snmperrstr);
327
/* -----------------------------------------------------------
329
* ----------------------------------------------------------- */
331
/* called on startup. */
332
void pn_startup(void)
334
pn_snmp_init(progname, device_path,
335
(testvar(PN_VAR_COMMUNITY) ? getval(PN_VAR_COMMUNITY) : "public"));
338
/* clean up before exit. */
339
void pn_cleanup(void)
344
/* add instant commands into info database. */
345
void pn_init_instcmds(void)
347
pn_info_t *pn_info_p;
349
for (pn_info_p = &pn_info[0]; pn_info_p->info_type; pn_info_p++)
350
if (pn_info_p->info_type & PN_CMD_MASK)
351
addinfo(INFO_INSTCMD, "", 0, pn_info_p->info_type);
354
/* install pointers to functions for msg handlers called from msgparse */
355
void pn_setuphandlers(void)
357
upsh.instcmd = pn_ups_instcmd;
358
upsh.setvar = pn_ups_set;
361
/* universal function to add or update info element. */
362
void pn_setinfo(int type, const char *value, int flags, int auxdata)
364
if (type == INFO_INSTCMD)
367
setinfo(type, "%s", value);
369
addinfo(type, value, flags, auxdata);
372
void pn_status_init(void)
374
memset(g_ups_status, 0, sizeof(g_ups_status));
377
void pn_status_set(pn_info_t *pn_info_p, long value)
379
g_ups_status[PN_STATUS_INDEX(pn_info_p->flags)] = value;
382
void pn_status_commit(void)
384
char buf[PN_INFOSIZE];
385
pn_info_t *pn_info_p;
387
switch (g_ups_status[PN_STATUS_INDEX(PN_STATUS_PWR)]) {
388
case PWR_OTHER: /* INFO_STATUS/? */
390
case PWR_SOFT_BYPASS:
391
case PWR_HARD_BYPASS:
392
case PWR_FAIL_BYPASS:
394
case PWR_NONE: /* none -> INFO_STATUS/OFF */
397
snprintf(buf, sizeof(buf), "OFF");
399
case PWR_NORMAL: /* normal -> INFO_STATUS/OL */
400
snprintf(buf, sizeof(buf), "OL");
402
case PWR_BATTERY: /* battery -> INFO_STATUS/OB */
403
snprintf(buf, sizeof(buf), "OB");
405
case PWR_BOOSTER: /* booster -> INFO_STATUS/BOOST */
406
snprintf(buf, sizeof(buf), "BOOST");
408
case PWR_REDUCER: /* reducer -> INFO_STATUS/TRIM */
409
snprintf(buf, sizeof(buf), "TRIM");
413
switch (g_ups_status[PN_STATUS_INDEX(PN_STATUS_BATT)]) {
414
case BATT_UNKNOWN: /* unknown -> INFO_STATUS/? */
415
case BATT_NORMAL: /* batteryNormal -> INFO_STATUS/? */
417
case BATT_LOW: /* batteryLow -> INFO_STATUS/LB */
418
snprintfcat(buf, sizeof(buf),
419
(strlen(buf) == 0 ? "%s" : " %s"), "LB");
423
switch (g_ups_status[PN_STATUS_INDEX(PN_STATUS_CAL)]) {
425
snprintfcat(buf, sizeof(buf),
426
(strlen(buf) == 0 ? "%s" : " %s"), "CAL");
430
switch (g_ups_status[PN_STATUS_INDEX(PN_STATUS_RB)]) {
432
snprintfcat(buf, sizeof(buf),
433
(strlen(buf) == 0 ? "%s" : " %s"), "RB");
437
pn_info_p = pn_find_info(INFO_STATUS);
439
pn_setinfo(pn_info_p->info_type, buf, pn_info_p->info_flags,
440
pn_info_p->info_len);
443
/* find info element definition in my info array. */
444
pn_info_t *pn_find_info(int type)
446
pn_info_t *pn_info_p;
448
for (pn_info_p = &pn_info[0]; pn_info_p->info_type; pn_info_p++)
449
if (pn_info_p->info_type == type)
451
fatalx("pn_find_info: unknown info type: 0x%x", type);
455
/* find description of INFO_ element. */
456
struct netvars_t *pn_find_netvar(int type)
458
struct netvars_t *netvar;
460
for (netvar = &netvars[0]; netvar->name; netvar++) {
461
if (netvar->type == type)
464
fatalx("pn_find_netvar: unknown netvar type: 0x%x", type);
468
/* find description of CMD_ element. */
469
struct instcmds_t *pn_find_instcmd(int cmd)
471
struct instcmds_t *instcmd;
473
for (instcmd = &instcmds[0]; instcmd->name; instcmd++) {
474
if (instcmd->cmd == cmd)
477
fatalx("pn_find_instcmd: unknown instcmd: 0x%x", cmd);
481
/* -----------------------------------------------------------
483
* ----------------------------------------------------------- */
485
/* walk ups variables and set elements of the info array. */
486
void pn_ups_walk(int mode)
488
static unsigned long iterations = 0;
490
pn_info_t *pn_info_p;
492
struct netvars_t *netvar;
496
for (pn_info_p = &pn_info[0]; pn_info_p->info_type; pn_info_p++) {
499
if (pn_info_p->info_type & PN_CMD_MASK)
502
/* skip elements we shouldn't show. */
503
if (!(pn_info_p->flags & PN_FLAG_OK))
506
/* skip static elements in update mode. */
507
if (mode == PN_WALKMODE_UPDATE &&
508
pn_info_p->flags & PN_FLAG_STATIC)
511
/* set default value if we cannot fetch it */
512
/* and set static flag on this element. */
513
if (pn_info_p->flags & PN_FLAG_ABSENT) {
514
if (mode == PN_WALKMODE_INIT) {
515
if (pn_info_p->dfl) {
516
/* Set default value if we cannot fetch it from ups. */
517
pn_setinfo(pn_info_p->info_type, pn_info_p->dfl,
518
pn_info_p->info_flags, pn_info_p->info_len);
520
pn_info_p->flags |= PN_FLAG_STATIC;
525
/* check stale elements only on each PN_STALE_RETRY iteration. */
526
if ((pn_info_p->flags & PN_FLAG_STALE) &&
527
(iterations % PN_STALE_RETRY) != 0)
530
/* ok, update this element. */
532
status = pn_ups_get(pn_info_p);
534
/* set stale flag if data is stale, clear if not. */
535
if (status == TRUE) {
536
if (pn_info_p->flags & PN_FLAG_STALE) {
537
netvar = pn_find_netvar(pn_info_p->info_type);
538
upslogx(LOG_INFO, "[%s] pn_ups_walk: data resumed for %s",
539
upsname, netvar->name);
541
pn_info_p->flags &= ~PN_FLAG_STALE;
543
if (!(pn_info_p->flags & PN_FLAG_STALE)) {
544
netvar = pn_find_netvar(pn_info_p->info_type);
545
upslogx(LOG_INFO, "[%s] pn_ups_walk: data stale for %s",
546
upsname, netvar->name);
548
pn_info_p->flags |= PN_FLAG_STALE;
551
} /* for (pn_info_p... */
558
bool pn_ups_get(pn_info_t *pn_info_p)
560
static char buf[PN_INFOSIZE];
564
switch (pn_info_p->info_type) {
567
status = pn_snmp_get_int(pn_info_p->OID, &value);
569
pn_status_set(pn_info_p, value);
574
status = pn_snmp_get_int(pn_info_p->OID, &value);
575
if (status == TRUE) {
576
static char *slftstres[] =
577
{ "OK", "FAILED", "TEST INVALID", "INPROGRESS" };
579
if (value >= 1 && value <= 4)
580
pn_setinfo(pn_info_p->info_type, slftstres[value - 1],
581
pn_info_p->info_flags, pn_info_p->info_len);
586
status = pn_snmp_get_int(pn_info_p->OID, &value);
587
if (status == TRUE) {
588
/* convert seconds to minutes. */
589
snprintf(buf, sizeof(buf), "%ld", value / 60);
590
pn_setinfo(pn_info_p->info_type, buf,
591
pn_info_p->info_flags, pn_info_p->info_len);
596
status = pn_snmp_get_int(pn_info_p->OID, &value);
597
if (status == TRUE) {
598
if (value >= 1 && value <= 4) {
599
static char *linesens[] = { "A", "L", "M", "H" };
600
pn_setinfo(pn_info_p->info_type, linesens[value - 1],
601
pn_info_p->info_flags, pn_info_p->info_len);
607
/* get snmp OID value. */
608
status = pn_snmp_get_str(pn_info_p->OID,
611
if (status == TRUE) {
612
pn_setinfo(pn_info_p->info_type, buf,
613
pn_info_p->info_flags, pn_info_p->info_len);
616
} /* switch (pn_info_p->info_type) */
621
/* set r/w INFO_ element to a value. */
622
void pn_ups_set(int type, int data_len, char *data)
624
pn_info_t *pn_info_p;
626
struct netvars_t *netvar;
628
pn_info_p = pn_find_info(type);
629
netvar = pn_find_netvar(type);
631
upslogx(LOG_INFO, "[%s] setting variable %s to %s",
632
upsname, netvar->name, data);
634
if (pn_info_p == NULL || pn_info_p->info_type == 0 ||
635
!(pn_info_p->flags & PN_FLAG_OK))
637
upslogx(LOG_ERR, "[%s] pn_ups_set: info element unavailable %s",
638
upsname, netvar->name);
642
if (! (pn_info_p->info_flags & FLAG_RW) || pn_info_p->OID == NULL) {
643
upslogx(LOG_ERR, "[%s] pn_ups_set: not writable %s",
644
upsname, netvar->name);
650
if (PN_TYPE(pn_info_p) == PN_TYPE_STRING) {
651
ret = pn_snmp_set_str(pn_info_p->OID, data);
652
} else if (PN_TYPE(pn_info_p) == PN_TYPE_TIME) {
653
ret = pn_snmp_set_time(pn_info_p->OID, strtol(data, NULL, 0));
655
switch (pn_info_p->info_type) {
656
case INFO_LINESENS: /* convert letter to int */
659
static char *linesens[] = { "A", "L", "M", "H" };
662
for (i = 1; i <= 4; i++) {
663
if (!strcasecmp(linesens[i - 1],data)) {
664
ret = pn_snmp_set_int(pn_info_p->OID, i);
671
ret = pn_snmp_set_int(pn_info_p->OID, strtol(data, NULL, 0));
676
upslogx(LOG_ERR, "[%s] pn_ups_set: cannot set value %s for %s",
677
upsname, data, pn_info_p->OID);
679
/* update info array. */
680
pn_setinfo(type, data, pn_info_p->info_flags, pn_info_p->info_len);
683
/* process instant command and take action. */
684
void pn_ups_instcmd(int auxcmd, int data_len, char *data)
686
pn_info_t *pn_info_p;
688
struct instcmds_t *instcmd;
690
pn_info_p = pn_find_info(auxcmd);
691
instcmd = pn_find_instcmd(auxcmd);
693
upslogx(LOG_INFO, "[%s] sending instant command %s",
694
upsname, instcmd->name);
696
if (pn_info_p->info_type == 0 || !(pn_info_p->flags & PN_FLAG_OK) ||
697
pn_info_p->OID == NULL)
699
upslogx(LOG_ERR, "[%s] pn_ups_instcmd: %s unavailable",
700
upsname, instcmd->name);
706
if (pn_info_p->info_flags & FLAG_STRING) {
707
status = pn_snmp_set_str(pn_info_p->OID, pn_info_p->dfl);
709
status = pn_snmp_set_int(pn_info_p->OID, pn_info_p->info_len);
713
upslogx(LOG_ERR, "[%s] pn_ups_instcmd: cannot set value for %s",
714
upsname, instcmd->name);
717
/* do the shutdown immediately. */
718
void pn_shutdown_ups(void)
723
if (pn_snmp_get_int(OID_POWER_STATUS, &pwr_status) == FALSE)
724
fatalx("cannot determine UPS status");
726
if (testvar(PN_VAR_SDTYPE))
727
sdtype = atoi(getval(PN_VAR_SDTYPE));
729
/* logic from newapc.c */
731
case 3: /* shutdown with grace period */
732
pn_ups_instcmd(CMD_SHUTDOWN, 0, 0);
734
case 2: /* instant shutdown */
735
pn_ups_instcmd(CMD_OFF, 0, 0);
738
/* Send a combined set of shutdown commands which can work better */
739
/* if the UPS gets power during shutdown process */
740
/* Specifically it sends both the soft shutdown 'S' */
741
/* and the powerdown after grace period - '@000' commands */
742
upslogx(LOG_INFO, "[%s] UPS - sending shutdown/powerdown",
744
if (pwr_status == PWR_BATTERY)
745
pn_ups_instcmd(CMD_SOFTDOWN, 0, 0);
746
pn_ups_instcmd(CMD_SDRET, 0, 0);
749
/* if on battery... */
750
if (pwr_status == PWR_BATTERY) {
752
"[%s] UPS is on battery, sending shutdown command...",
754
pn_ups_instcmd(CMD_SOFTDOWN, 0, 0);
757
"[%s] UPS is online, sending shutdown+return command...",
759
pn_ups_instcmd(CMD_SDRET, 0, 0);
765
/* ---------------------------------------------
766
* driver functions implementations
767
* --------------------------------------------- */
769
void upsdrv_initinfo(void)
771
/* add instant commands to the info database. */
774
/* set habdlers for instcmds and set commands. */
777
/* initialize all INFO_ fields */
778
pn_ups_walk(PN_WALKMODE_INIT);
781
void upsdrv_updateinfo(void)
783
/* update all dynamic info fields */
784
pn_ups_walk(PN_WALKMODE_UPDATE);
787
/* do the shutdown immediately and cleanup for exit. */
788
/* upsdrv_initups already called, but upsdrv_initinfo isn't. */
789
void upsdrv_shutdown(void)
795
void upsdrv_help(void)
797
printf("\nShutdown types:\n");
798
printf(" 0: soft shutdown or powerdown, depending on battery status\n");
799
printf(" 1: soft shutdown followed by powerdown\n");
800
printf(" 2: instant power off\n");
801
printf(" 3: power off with grace period\n");
802
printf("Modes 0-1 will make the UPS come back when power returns\n");
803
printf("Modes 2-3 will make the UPS stay turned off when power returns\n");
806
/* list flags and values that you want to receive via -x */
807
void upsdrv_makevartable(void)
809
addvar(VAR_VALUE, PN_VAR_SDTYPE,
810
"Specify shutdown type (1-3, default 0)");
811
addvar(VAR_VALUE, PN_VAR_COMMUNITY,
812
"Set SNMP community name (default=public)");
815
void upsdrv_banner(void)
817
upslogx(1,"Network UPS Tools %s APC Powernet SNMP driver %s",
818
UPS_VERSION, PN_UPSDRV_VERSION);
821
void upsdrv_initups(void)
823
pn_info_t *pn_info_p;
824
char model[PN_INFOSIZE];
827
/* init SNMP library, etc... */
830
/* get value of UPS model OID */
831
pn_info_p = pn_find_info(INFO_MODEL);
832
status = pn_snmp_get_str(pn_info_p->OID, model, sizeof(model));
834
upslogx(0, "detected %s on host %s", model, device_path);
836
fatalx("Powernet MIB %s wasn't found on %s",
837
OID_POWERNET_MIB, g_snmp_sess.peername);
840
void upsdrv_cleanup(void)