/* 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 #include #include #include #include #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 ", 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) { }