nut-debian/drivers/apcupsd-ups.c
2022-07-10 09:23:45 +02:00

326 lines
7.3 KiB
C

/* apcupsd-ups.c - client for apcupsd
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 "config.h"
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <poll.h>
#include <sys/ioctl.h>
#include "main.h"
#include "apcupsd-ups.h"
#include "attribute.h"
#define DRIVER_NAME "apcupsd network client UPS driver"
#define DRIVER_VERSION "0.6"
#define POLL_INTERVAL_MIN 10
/* driver description structure */
upsdrv_info_t upsdrv_info = {
DRIVER_NAME,
DRIVER_VERSION,
"Andreas Steinmetz <ast@domdv.de>",
DRV_STABLE,
{ NULL }
};
static uint16_t port=3551;
static struct sockaddr_in host;
static void process(char *item,char *data)
{
int i;
char *p1;
char *p2;
for(i=0;nut_data[i].info_type;i++)if(!(nut_data[i].apcupsd_item))
dstate_setinfo(nut_data[i].info_type,"%s",
nut_data[i].default_value);
else if(!strcmp(nut_data[i].apcupsd_item,item))
switch(nut_data[i].drv_flags&~DU_FLAG_INIT)
{
case DU_FLAG_STATUS:
status_init();
if(!strcmp(data,"COMMLOST")||!strcmp(data,"NETWORK ERROR")||
!strcmp(data,"ERROR"))status_set("OFF");
else if(!strcmp(data,"SELFTEST"))status_set("OB");
else for(;(data=strtok(data," "));data=NULL)
{
if(!strcmp(data,"CAL"))status_set("CAL");
else if(!strcmp(data,"TRIM"))status_set("TRIM");
else if(!strcmp(data,"BOOST"))status_set("BOOST");
else if(!strcmp(data,"ONLINE"))status_set("OL");
else if(!strcmp(data,"ONBATT"))status_set("OB");
else if(!strcmp(data,"OVERLOAD"))status_set("OVER");
else if(!strcmp(data,"SHUTTING DOWN")||
!strcmp(data,"LOWBATT"))status_set("LB");
else if(!strcmp(data,"REPLACEBATT"))status_set("RB");
else if(!strcmp(data,"NOBATT"))status_set("BYPASS");
}
status_commit();
break;
case DU_FLAG_DATE:
if((p1=strchr(data,' ')))
{
*p1=0;
dstate_setinfo(nut_data[i].info_type,"%s",data);
*p1=' ';
}
else dstate_setinfo(nut_data[i].info_type,"%s",data);
break;
case DU_FLAG_TIME:
if((p1=strchr(data,' ')))
{
*p1=0;
if((p2=strchr(p1+1,' ')))
{
*p2=0;
dstate_setinfo(nut_data[i].info_type,"%s",p1+1);
*p2=' ';
}
else dstate_setinfo(nut_data[i].info_type,"%s",p1+1);
*p1=' ';
}
break;
case DU_FLAG_FW1:
if((p1=strchr(data,'/')))
{
for(;p1!=data;p1--)if(p1[-1]!=' ')break;
if(*p1==' ')
{
*p1=0;
dstate_setinfo(nut_data[i].info_type,"%s",data);
*p1=' ';
}
else dstate_setinfo(nut_data[i].info_type,"%s",data);
}
else dstate_setinfo(nut_data[i].info_type,"%s",data);
break;
case DU_FLAG_FW2:
if((p1=strchr(data,'/')))
{
for(;*p1;p1++)if(p1[1]!=' ')break;
if(*p1&&p1[1])dstate_setinfo(nut_data[i].info_type,"%s",
p1+1);
}
break;
default:if(nut_data[i].info_flags&ST_FLAG_STRING)
{
if((int)strlen(data)>(int)nut_data[i].info_len)
data[(int)nut_data[i].info_len]=0;
dstate_setinfo(nut_data[i].info_type,"%s",data);
}
else
{
#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL
#pragma GCC diagnostic push
#endif
#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#endif
#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_SECURITY
#pragma GCC diagnostic ignored "-Wformat-security"
#endif
/* default_value acts as a format string in this case */
dstate_setinfo(nut_data[i].info_type,
nut_data[i].default_value,
atof(data)*nut_data[i].info_len);
#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL
#pragma GCC diagnostic pop
#endif
}
break;
}
}
static int getdata(void)
{
ssize_t x;
int fd_flags;
uint16_t n;
char *item;
char *data;
struct pollfd p;
char bfr[1024];
for(x=0;nut_data[x].info_type;x++)
if(!(nut_data[x].drv_flags & DU_FLAG_INIT) && !(nut_data[x].drv_flags & DU_FLAG_PRESERVE))
dstate_delinfo(nut_data[x].info_type);
if((p.fd=socket(AF_INET,SOCK_STREAM,0))==-1)
{
upsdebugx(1,"socket error");
return -1;
}
if(connect(p.fd,(struct sockaddr *)&host,sizeof(host)))
{
upsdebugx(1,"can't connect to apcupsd");
close(p.fd);
return -1;
}
fd_flags = fcntl(p.fd, F_GETFL);
fd_flags |= O_NONBLOCK;
if(fcntl(p.fd, F_SETFL, fd_flags))
{
upsdebugx(1,"unexpected fcntl(fd, F_SETFL, fd_flags|O_NONBLOCK) failure");
close(p.fd);
return -1;
}
p.events=POLLIN;
n=htons(6);
x=write(p.fd,&n,2);
x=write(p.fd,"status",6);
/* TODO: double-check for poll() in configure script */
while(poll(&p,1,15000)==1)
{
if(read(p.fd,&n,2)!=2)
{
upsdebugx(1,"apcupsd communication error");
close(p.fd);
return -1;
}
if(!(x=ntohs(n)))
{
close(p.fd);
return 0;
}
else if(x<0||x>=(int)sizeof(bfr))
/* Note: LGTM.com suggests "Comparison is always false because x >= 0"
* for the line above, probably because ntohs() returns an uint type.
* I am reluctant to fix this one, because googling for headers from
* random OSes showed various types used as the return value (uint16_t,
* unsigned_short, u_short, in_port_t...)
*/
{
upsdebugx(1,"apcupsd communication error");
close(p.fd);
return -1;
}
if(poll(&p,1,15000)!=1)break;
if(read(p.fd,bfr,(size_t)x)!=x)
{
upsdebugx(1,"apcupsd communication error");
close(p.fd);
return -1;
}
bfr[x]=0;
if(!(item=strtok(bfr," \t:\r\n")))
{
upsdebugx(1,"apcupsd communication error");
close(p.fd);
return -1;
}
if(!(data=strtok(NULL,"\r\n")))
{
upsdebugx(1,"apcupsd communication error");
close(p.fd);
return -1;
}
while(*data==' '||*data=='\t'||*data==':')data++;
process(item,data);
}
upsdebugx(1,"unexpected connection close by apcupsd");
close(p.fd);
return -1;
}
void upsdrv_initinfo(void)
{
if(!port)fatalx(EXIT_FAILURE,"invalid host or port specified!");
if(getdata())fatalx(EXIT_FAILURE,"can't communicate with apcupsd!");
else dstate_dataok();
poll_interval = (poll_interval > POLL_INTERVAL_MIN) ? POLL_INTERVAL_MIN : poll_interval;
}
void upsdrv_updateinfo(void)
{
if(getdata())upslogx(LOG_ERR,"can't communicate with apcupsd!");
else dstate_dataok();
poll_interval = (poll_interval > POLL_INTERVAL_MIN) ? POLL_INTERVAL_MIN : poll_interval;
}
void upsdrv_shutdown(void)
__attribute__((noreturn));
void upsdrv_shutdown(void)
{
fatalx(EXIT_FAILURE, "shutdown not supported");
}
void upsdrv_help(void)
{
}
void upsdrv_makevartable(void)
{
}
void upsdrv_initups(void)
{
char *p;
struct hostent *h;
if(device_path&&*device_path)
{
/* TODO: fix parsing since bare IPv6 addresses contain colons */
if((p=strchr(device_path,':')))
{
int i;
*p++=0;
i=atoi(p);
if(i<1||i>65535)i=0;
port = (uint16_t)i;
}
}
else device_path="localhost";
if(!(h=gethostbyname(device_path)))port=0;
else memcpy(&host.sin_addr,h->h_addr,4);
/* TODO: add IPv6 support */
host.sin_family=AF_INET;
host.sin_port=htons(port);
}
void upsdrv_cleanup(void)
{
}