1 /* apcupsd-ups.c - client for apcupsd
2 
3    This program is free software; you can redistribute it and/or modify
4    it under the terms of the GNU General Public License as published by
5    the Free Software Foundation; either version 2 of the License, or
6    (at your option) any later version.
7 
8    This program is distributed in the hope that it will be useful,
9    but WITHOUT ANY WARRANTY; without even the implied warranty of
10    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11    GNU General Public License for more details.
12 
13    You should have received a copy of the GNU General Public License
14    along with this program; if not, write to the Free Software
15    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16 
17 */
18 
19 #include "config.h"
20 
21 #include <sys/socket.h>
22 #include <netinet/in.h>
23 #include <netdb.h>
24 #include <poll.h>
25 #include <sys/ioctl.h>
26 
27 #include "main.h"
28 #include "apcupsd-ups.h"
29 #include "attribute.h"
30 
31 #define DRIVER_NAME	"apcupsd network client UPS driver"
32 #define DRIVER_VERSION	"0.5"
33 
34 #define POLL_INTERVAL_MIN 10
35 
36 /* driver description structure */
37 upsdrv_info_t upsdrv_info = {
38 	DRIVER_NAME,
39 	DRIVER_VERSION,
40 	"Andreas Steinmetz <ast@domdv.de>",
41 	DRV_STABLE,
42 	{ NULL }
43 };
44 
45 static uint16_t port=3551;
46 static struct sockaddr_in host;
47 
process(char * item,char * data)48 static void process(char *item,char *data)
49 {
50 	int i;
51 	char *p1;
52 	char *p2;
53 
54 	for(i=0;nut_data[i].info_type;i++)if(!(nut_data[i].apcupsd_item))
55 		dstate_setinfo(nut_data[i].info_type,"%s",
56 			nut_data[i].default_value);
57 	else if(!strcmp(nut_data[i].apcupsd_item,item))
58 			switch(nut_data[i].drv_flags&~DU_FLAG_INIT)
59 	{
60 	case DU_FLAG_STATUS:
61 		status_init();
62 		if(!strcmp(data,"COMMLOST")||!strcmp(data,"NETWORK ERROR")||
63 		   !strcmp(data,"ERROR"))status_set("OFF");
64 		else if(!strcmp(data,"SELFTEST"))status_set("OB");
65 		else for(;(data=strtok(data," "));data=NULL)
66 		{
67 			if(!strncmp(data, "CAL", 3))status_set("CAL");
68 			else if(!strcmp(data,"TRIM"))status_set("TRIM");
69 			else if(!strcmp(data,"BOOST"))status_set("BOOST");
70 			else if(!strcmp(data,"ONLINE"))status_set("OL");
71 			else if(!strcmp(data,"ONBATT"))status_set("OB");
72 			else if(!strcmp(data,"OVERLOAD"))status_set("OVER");
73 			else if(!strcmp(data,"SHUTTING DOWN")||
74 				!strcmp(data,"LOWBATT"))status_set("LB");
75 			else if(!strcmp(data,"REPLACEBATT"))status_set("RB");
76 			else if(!strcmp(data,"NOBATT"))status_set("BYPASS");
77 		}
78 		status_commit();
79 		break;
80 
81 	case DU_FLAG_DATE:
82 		if((p1=strchr(data,' ')))
83 		{
84 			*p1=0;
85 			dstate_setinfo(nut_data[i].info_type,"%s",data);
86 			*p1=' ';
87 		}
88 		else dstate_setinfo(nut_data[i].info_type,"%s",data);
89 		break;
90 
91 	case DU_FLAG_TIME:
92 		if((p1=strchr(data,' ')))
93 		{
94 			*p1=0;
95 			if((p2=strchr(p1+1,' ')))
96 			{
97 				*p2=0;
98 				dstate_setinfo(nut_data[i].info_type,"%s",p1+1);
99 				*p2=' ';
100 			}
101 			else dstate_setinfo(nut_data[i].info_type,"%s",p1+1);
102 			*p1=' ';
103 		}
104 		break;
105 
106 	case DU_FLAG_FW1:
107 		if((p1=strchr(data,'/')))
108 		{
109 			for(;p1!=data;p1--)if(p1[-1]!=' ')break;
110 			if(*p1==' ')
111 			{
112 				*p1=0;
113 				dstate_setinfo(nut_data[i].info_type,"%s",data);
114 				*p1=' ';
115 			}
116 			else dstate_setinfo(nut_data[i].info_type,"%s",data);
117 		}
118 		else dstate_setinfo(nut_data[i].info_type,"%s",data);
119 		break;
120 
121 	case DU_FLAG_FW2:
122 		if((p1=strchr(data,'/')))
123 		{
124 			for(;*p1;p1++)if(p1[1]!=' ')break;
125 			if(*p1&&p1[1])dstate_setinfo(nut_data[i].info_type,"%s",
126 				p1+1);
127 		}
128 		break;
129 
130 	default:if(nut_data[i].info_flags&ST_FLAG_STRING)
131 		{
132 			if((int)strlen(data)>(int)nut_data[i].info_len)
133 				data[(int)nut_data[i].info_len]=0;
134 			dstate_setinfo(nut_data[i].info_type,"%s",data);
135 		}
136 		else
137 		{
138 #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL
139 #pragma GCC diagnostic push
140 #endif
141 #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL
142 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
143 #endif
144 #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_SECURITY
145 #pragma GCC diagnostic ignored "-Wformat-security"
146 #endif
147 			/* default_value acts as a format string in this case */
148 			dstate_setinfo(nut_data[i].info_type,
149 				nut_data[i].default_value,
150 				atof(data)*nut_data[i].info_len);
151 #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL
152 #pragma GCC diagnostic pop
153 #endif
154 		}
155 		break;
156 	}
157 }
158 
getdata(void)159 static int getdata(void)
160 {
161 	ssize_t x;
162 	int fd_flags;
163 	uint16_t n;
164 	char *item;
165 	char *data;
166 	struct pollfd p;
167 	char bfr[1024];
168 
169 	for(x=0;nut_data[x].info_type;x++)
170 		if(!(nut_data[x].drv_flags & DU_FLAG_INIT) && !(nut_data[x].drv_flags & DU_FLAG_PRESERVE))
171 			dstate_delinfo(nut_data[x].info_type);
172 
173 	if((p.fd=socket(AF_INET,SOCK_STREAM,0))==-1)
174 	{
175 		upsdebugx(1,"socket error");
176 		return -1;
177 	}
178 
179 	if(connect(p.fd,(struct sockaddr *)&host,sizeof(host)))
180 	{
181 		upsdebugx(1,"can't connect to apcupsd");
182 		close(p.fd);
183 		return -1;
184 	}
185 
186 	fd_flags = fcntl(p.fd, F_GETFL);
187 	fd_flags |= O_NONBLOCK;
188 	if(fcntl(p.fd, F_SETFL, fd_flags))
189 	{
190 		upsdebugx(1,"unexpected fcntl(fd, F_SETFL, fd_flags|O_NONBLOCK) failure");
191 		close(p.fd);
192 		return -1;
193 	}
194 
195 	p.events=POLLIN;
196 
197 	n=htons(6);
198 	x=write(p.fd,&n,2);
199 	x=write(p.fd,"status",6);
200 
201 	/* TODO: double-check for poll() in configure script */
202 	while(poll(&p,1,15000)==1)
203 	{
204 		if(read(p.fd,&n,2)!=2)
205 		{
206 			upsdebugx(1,"apcupsd communication error");
207 			close(p.fd);
208 			return -1;
209 		}
210 
211 		if(!(x=ntohs(n)))
212 		{
213 			close(p.fd);
214 			return 0;
215 		}
216 		else if(x<0||x>=(int)sizeof(bfr))
217 		/* Note: LGTM.com suggests "Comparison is always false because x >= 0"
218 		 * for the line above, probably because ntohs() returns an uint type.
219 		 * I am reluctant to fix this one, because googling for headers from
220 		 * random OSes showed various types used as the return value (uint16_t,
221 		 * unsigned_short, u_short, in_port_t...)
222 		 */
223 		{
224 			upsdebugx(1,"apcupsd communication error");
225 			close(p.fd);
226 			return -1;
227 		}
228 
229 		if(poll(&p,1,15000)!=1)break;
230 
231 		if(read(p.fd,bfr,(size_t)x)!=x)
232 		{
233 			upsdebugx(1,"apcupsd communication error");
234 			close(p.fd);
235 			return -1;
236 		}
237 
238 		bfr[x]=0;
239 
240 		if(!(item=strtok(bfr," \t:\r\n")))
241 		{
242 			upsdebugx(1,"apcupsd communication error");
243 			close(p.fd);
244 			return -1;
245 		}
246 
247 		if(!(data=strtok(NULL,"\r\n")))
248 		{
249 			upsdebugx(1,"apcupsd communication error");
250 			close(p.fd);
251 			return -1;
252 		}
253 		while(*data==' '||*data=='\t'||*data==':')data++;
254 
255 		process(item,data);
256 	}
257 
258 	upsdebugx(1,"unexpected connection close by apcupsd");
259 	close(p.fd);
260 	return -1;
261 }
262 
upsdrv_initinfo(void)263 void upsdrv_initinfo(void)
264 {
265 	if(!port)fatalx(EXIT_FAILURE,"invalid host or port specified!");
266 	if(getdata())fatalx(EXIT_FAILURE,"can't communicate with apcupsd!");
267 	else dstate_dataok();
268 
269 	poll_interval = (poll_interval > POLL_INTERVAL_MIN) ? POLL_INTERVAL_MIN : poll_interval;
270 }
271 
upsdrv_updateinfo(void)272 void upsdrv_updateinfo(void)
273 {
274 	if(getdata())upslogx(LOG_ERR,"can't communicate with apcupsd!");
275 	else dstate_dataok();
276 
277 	poll_interval = (poll_interval > POLL_INTERVAL_MIN) ? POLL_INTERVAL_MIN : poll_interval;
278 }
279 
280 void upsdrv_shutdown(void)
281 	__attribute__((noreturn));
282 
upsdrv_shutdown(void)283 void upsdrv_shutdown(void)
284 {
285 	fatalx(EXIT_FAILURE, "shutdown not supported");
286 }
287 
upsdrv_help(void)288 void upsdrv_help(void)
289 {
290 }
291 
upsdrv_makevartable(void)292 void upsdrv_makevartable(void)
293 {
294 }
295 
upsdrv_initups(void)296 void upsdrv_initups(void)
297 {
298 	char *p;
299 	struct hostent *h;
300 
301 	if(device_path&&*device_path)
302 	{
303 		/* TODO: fix parsing since bare IPv6 addresses contain colons */
304 		if((p=strchr(device_path,':')))
305 		{
306 			int i;
307 			*p++=0;
308 			i=atoi(p);
309 			if(i<1||i>65535)i=0;
310 			port = (uint16_t)i;
311 		}
312 	}
313 	else device_path="localhost";
314 
315 	if(!(h=gethostbyname(device_path)))port=0;
316 	else memcpy(&host.sin_addr,h->h_addr,4);
317 
318 	/* TODO: add IPv6 support */
319 	host.sin_family=AF_INET;
320 	host.sin_port=htons(port);
321 }
322 
upsdrv_cleanup(void)323 void upsdrv_cleanup(void)
324 {
325 }
326