19f314083e3463e12717aaeb30bf7d28261bb926
[monky] / src / apcupsd.c
1 /* apcupsd.c:  conky module for APC UPS daemon monitoring
2  *
3  * Copyright (C) 2009 Jaromir Smrcek <jaromir.smrcek@zoner.com>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
18  * USA. */
19
20 #include "conky.h"
21 #include "apcupsd.h"
22 #include "logging.h"
23
24 #include <errno.h>
25 #include <netinet/in.h>
26 #include <netdb.h>
27 #include <sys/time.h>
28 #include <unistd.h>
29
30 //
31 // encapsulated recv()
32 //
33 static int net_recv_ex(int sock, void *buf, int size, struct timeval *tv)
34 {
35         
36         fd_set  fds;
37         int             res;
38
39         // wait for some data to be read
40         do {
41                 errno = 0;
42                 FD_ZERO(&fds);
43                 FD_SET(sock, &fds);
44                 res = select(sock + 1, &fds, NULL, NULL, tv);
45         } while (res < 0 && errno == EINTR);
46         if (res < 0) return 0;
47         if (res == 0) {
48                 // timeout
49                 errno = ETIMEDOUT;  // select was succesfull, errno is now 0
50                 return 0;
51         }
52
53         // socket ready, read the data
54         do {
55                 errno = 0;
56                 res = recv(sock, (char*)buf, size, 0);
57         } while (res < 0 && errno == EINTR);
58         if (res < 0) return 0;
59         if (res == 0) {
60                 // orderly shutdown
61                 errno = ENOTCONN;
62                 return 0;
63         }
64
65         return res;
66 }
67
68 //
69 // read whole buffer or fail
70 //
71 static int net_recv(int sock, void* buf, int size) {
72
73         int todo = size;
74         int off = 0;
75         int len;
76         struct timeval tv = { 0, 250000 };
77
78         while (todo) {
79                 len = net_recv_ex(sock, (char*)buf + off, todo, &tv);
80                 if (!len) return 0;
81                 todo -= len;
82                 off  += len;
83         }
84         return 1;
85 }
86
87 //
88 // get one response line
89 //
90 static int get_line(int sock, char line[], short linesize) {
91
92         // get the line length
93         short sz;
94         if (!net_recv(sock, &sz, sizeof(sz))) return -1;
95         sz = ntohs(sz);
96         if (!sz) return 0;
97
98         // get the line
99         while (sz > linesize) {
100                 // this is just a hack (being lazy), this should not happen anyway
101                 net_recv(sock, line, linesize);
102                 sz -= sizeof(line);
103         }
104         if (!net_recv(sock, line, sz)) return 0;
105         line[sz] = 0;
106         return sz;
107 }
108
109 #define FILL(NAME,FIELD,FIRST)                                                                                                          \
110         if (!strncmp(NAME, line, sizeof(NAME)-1)) {                                                                             \
111                 strncpy(apc->items[FIELD], line+11, APCUPSD_MAXSTR);                                            \
112                 /* remove trailing newline and assure termination */                                            \
113                 apc->items[FIELD][len-11 > APCUPSD_MAXSTR ? APCUPSD_MAXSTR : len-12] = 0;       \
114                 if (FIRST) {                                                                                                                            \
115                         char* c;                                                                                                                                \
116                         for (c = apc->items[FIELD]; *c; ++c)                                                                    \
117                                 if (*c == ' ' && c > apc->items[FIELD]+2) {                                                     \
118                                         *c = 0;                                                                                                                 \
119                                         break;                                                                                                                  \
120                                 }                                                                                                                                       \
121                 }                                                                                                                                                       \
122         }
123
124 //
125 // fills in the data received from a socket
126 //
127 static int fill_items(int sock, PAPCUPSD_S apc) {
128
129         char line[512];
130         int len;
131         while ((len = get_line(sock, line, sizeof(line)))) {
132                 // fill the right types in
133                 FILL("UPSNAME",         APCUPSD_NAME,           FALSE);
134                 FILL("MODEL",           APCUPSD_MODEL,          FALSE);
135                 FILL("UPSMODE",         APCUPSD_UPSMODE,        FALSE);
136                 FILL("CABLE",           APCUPSD_CABLE,          FALSE);
137                 FILL("STATUS",          APCUPSD_STATUS,         FALSE);
138                 FILL("LINEV",           APCUPSD_LINEV,          TRUE);
139                 FILL("LOADPCT",         APCUPSD_LOAD,           TRUE);
140                 FILL("BCHARGE",         APCUPSD_CHARGE,         TRUE);
141                 FILL("TIMELEFT",        APCUPSD_TIMELEFT,       TRUE);
142                 FILL("ITEMP",           APCUPSD_TEMP,           TRUE);
143                 FILL("LASTXFER",        APCUPSD_LASTXFER,       FALSE);
144         }
145         
146         return len == 0;
147 }
148
149 //
150 // Conky update function for apcupsd data
151 //
152 void update_apcupsd(void) {
153
154         int i;
155         APCUPSD_S apc;
156         int sock;
157
158         for (i = 0; i < _APCUPSD_COUNT; ++i)
159                 memcpy(apc.items[i], "N/A", 4); // including \0
160
161         do {
162                 struct hostent* he = 0;
163                 struct sockaddr_in addr;
164                 short sz = 0;
165 #ifdef HAVE_GETHOSTBYNAME_R
166                 struct hostent he_mem;
167                 int he_errno;
168                 char hostbuff[2048];
169 #endif
170                 //
171                 // connect to apcupsd daemon
172                 //
173                 sock = socket(AF_INET, SOCK_STREAM, 0);
174                 if (sock < 0) {
175                         perror("socket");
176                         break;
177                 }
178 #ifdef HAVE_GETHOSTBYNAME_R
179                 if (gethostbyname_r(info.apcupsd.host, &he_mem, hostbuff, sizeof(hostbuff), &he, &he_errno)) {
180                         ERR("APCUPSD gethostbyname_r: %s", hstrerror(h_errno));
181                         break;
182                 }
183 #else /* HAVE_GETHOSTBYNAME_R */
184                 he = gethostbyname(info.apcupsd.host);
185                 if (!he) {
186                         herror("gethostbyname");
187                         break;
188                 }
189 #endif /* HAVE_GETHOSTBYNAME_R */
190                 
191                 memset(&addr, 0, sizeof(addr));
192                 addr.sin_family = AF_INET;
193                 addr.sin_port = info.apcupsd.port;
194                 memcpy(&addr.sin_addr, he->h_addr, he->h_length);
195                 if (connect(sock, (struct sockaddr*)&addr, sizeof(struct sockaddr)) < 0) {
196                         // no error reporting, the daemon is probably not running
197                         break;
198                 }
199         
200                 //
201                 // send status request - "status" - 6B
202                 //
203                 sz = htons(6);
204                 // no waiting to become writeable is really needed
205                 if (send(sock, &sz, sizeof(sz), 0) != sizeof(sz) || send(sock, "status", 6, 0) != 6) {
206                         perror("send");
207                         break;
208                 }
209         
210                 //
211                 // read the lines of output and put them into the info structure
212                 //
213                 if (!fill_items(sock, &apc)) break;
214
215         } while (0);
216
217         close(sock);
218
219         //
220         // "atomically" copy the data into working set
221         //
222         memcpy(info.apcupsd.items, apc.items, sizeof(info.apcupsd.items));
223         return;
224 }
225
226 //
227 // fills in the N/A strings and default host:port
228 //
229 void init_apcupsd(void) {
230
231         int i;
232         for (i = 0; i < _APCUPSD_COUNT; ++i)
233                 memcpy(info.apcupsd.items[i], "N/A", 4); // including \0
234         memcpy(info.apcupsd.host, "localhost", 10);
235         info.apcupsd.port = htons(3551);
236 }