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