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