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