Merge branch 'collectd-5.5' into collectd-5.6
[collectd.git] / src / hddtemp.c
1 /**
2  * collectd - src/hddtemp.c
3  * Copyright (C) 2005,2006  Vincent StehlĂ©
4  * Copyright (C) 2006-2010  Florian octo Forster
5  * Copyright (C) 2008       Sebastian Harl
6  *
7  * This program is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published by the
9  * Free Software Foundation; either version 2 of the License, or (at your
10  * option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
20  *
21  * Authors:
22  *   Vincent StehlĂ© <vincent.stehle at free.fr>
23  *   Florian octo Forster <octo at collectd.org>
24  *   Sebastian Harl <sh at tokkee.org>
25  *
26  * TODO:
27  *   Do a pass, some day, and spare some memory. We consume too much for now
28  *   in string buffers and the like.
29  *
30  **/
31
32 #include "collectd.h"
33
34 #include "common.h"
35 #include "plugin.h"
36
37 #include <libgen.h> /* for basename */
38 #include <netdb.h>
39 #include <netinet/in.h>
40 #include <netinet/tcp.h>
41
42 #if HAVE_LINUX_MAJOR_H
43 #include <linux/major.h>
44 #endif
45
46 #define HDDTEMP_DEF_HOST "127.0.0.1"
47 #define HDDTEMP_DEF_PORT "7634"
48
49 static const char *config_keys[] = {"Host", "Port"};
50 static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
51
52 static char *hddtemp_host = NULL;
53 static char hddtemp_port[16];
54
55 /*
56  * NAME
57  *  hddtemp_query_daemon
58  *
59  * DESCRIPTION
60  * Connect to the hddtemp daemon and receive data.
61  *
62  * ARGUMENTS:
63  *  `buffer'            The buffer where we put the received ascii string.
64  *  `buffer_size'       Size of the buffer
65  *
66  * RETURN VALUE:
67  *   >= 0 if ok, < 0 otherwise.
68  *
69  * NOTES:
70  *  Example of possible strings, as received from daemon:
71  *    |/dev/hda|ST340014A|36|C|
72  *    |/dev/hda|ST380011A|46|C||/dev/hdd|ST340016A|SLP|*|
73  *
74  * FIXME:
75  *  we need to create a new socket each time. Is there another way?
76  *  Hm, maybe we can re-use the `sockaddr' structure? -octo
77  */
78 static int hddtemp_query_daemon(char *buffer, int buffer_size) {
79   int fd;
80   ssize_t status;
81   int buffer_fill;
82
83   const char *host;
84   const char *port;
85
86   struct addrinfo *ai_list;
87   int ai_return;
88
89   host = hddtemp_host;
90   if (host == NULL)
91     host = HDDTEMP_DEF_HOST;
92
93   port = hddtemp_port;
94   if (strlen(port) == 0)
95     port = HDDTEMP_DEF_PORT;
96
97   struct addrinfo ai_hints = {.ai_flags = AI_ADDRCONFIG,
98                               .ai_family = AF_UNSPEC,
99                               .ai_protocol = IPPROTO_TCP,
100                               .ai_socktype = SOCK_STREAM};
101
102   if ((ai_return = getaddrinfo(host, port, &ai_hints, &ai_list)) != 0) {
103     char errbuf[1024];
104     ERROR("hddtemp plugin: getaddrinfo (%s, %s): %s", host, port,
105           (ai_return == EAI_SYSTEM) ? sstrerror(errno, errbuf, sizeof(errbuf))
106                                     : gai_strerror(ai_return));
107     return (-1);
108   }
109
110   fd = -1;
111   for (struct addrinfo *ai_ptr = ai_list; ai_ptr != NULL;
112        ai_ptr = ai_ptr->ai_next) {
113     /* create our socket descriptor */
114     fd = socket(ai_ptr->ai_family, ai_ptr->ai_socktype, ai_ptr->ai_protocol);
115     if (fd < 0) {
116       char errbuf[1024];
117       ERROR("hddtemp plugin: socket: %s",
118             sstrerror(errno, errbuf, sizeof(errbuf)));
119       continue;
120     }
121
122     /* connect to the hddtemp daemon */
123     if (connect(fd, (struct sockaddr *)ai_ptr->ai_addr, ai_ptr->ai_addrlen)) {
124       char errbuf[1024];
125       INFO("hddtemp plugin: connect (%s, %s) failed: %s", host, port,
126            sstrerror(errno, errbuf, sizeof(errbuf)));
127       close(fd);
128       fd = -1;
129       continue;
130     }
131
132     /* A socket could be opened and connecting succeeded. We're
133      * done. */
134     break;
135   }
136
137   freeaddrinfo(ai_list);
138
139   if (fd < 0) {
140     ERROR("hddtemp plugin: Could not connect to daemon.");
141     return (-1);
142   }
143
144   /* receive data from the hddtemp daemon */
145   memset(buffer, '\0', buffer_size);
146
147   buffer_fill = 0;
148   while ((status = read(fd, buffer + buffer_fill, buffer_size - buffer_fill)) !=
149          0) {
150     if (status == -1) {
151       char errbuf[1024];
152
153       if ((errno == EAGAIN) || (errno == EINTR))
154         continue;
155
156       ERROR("hddtemp plugin: Error reading from socket: %s",
157             sstrerror(errno, errbuf, sizeof(errbuf)));
158       close(fd);
159       return (-1);
160     }
161     buffer_fill += status;
162
163     if (buffer_fill >= buffer_size)
164       break;
165   }
166
167   if (buffer_fill >= buffer_size) {
168     buffer[buffer_size - 1] = '\0';
169     WARNING("hddtemp plugin: Message from hddtemp has been "
170             "truncated.");
171   } else if (buffer_fill == 0) {
172     WARNING("hddtemp plugin: Peer has unexpectedly shut down "
173             "the socket. Buffer: `%s'",
174             buffer);
175     close(fd);
176     return (-1);
177   }
178
179   close(fd);
180   return (0);
181 }
182
183 static int hddtemp_config(const char *key, const char *value) {
184   if (strcasecmp(key, "Host") == 0) {
185     if (hddtemp_host != NULL)
186       free(hddtemp_host);
187     hddtemp_host = strdup(value);
188   } else if (strcasecmp(key, "Port") == 0) {
189     int port = (int)(atof(value));
190     if ((port > 0) && (port <= 65535))
191       ssnprintf(hddtemp_port, sizeof(hddtemp_port), "%i", port);
192     else
193       sstrncpy(hddtemp_port, value, sizeof(hddtemp_port));
194   } else {
195     return (-1);
196   }
197
198   return (0);
199 }
200
201 static void hddtemp_submit(char *type_instance, double value) {
202   value_t values[1];
203   value_list_t vl = VALUE_LIST_INIT;
204
205   values[0].gauge = value;
206
207   vl.values = values;
208   vl.values_len = 1;
209   sstrncpy(vl.host, hostname_g, sizeof(vl.host));
210   sstrncpy(vl.plugin, "hddtemp", sizeof(vl.plugin));
211   sstrncpy(vl.type, "temperature", sizeof(vl.type));
212   sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
213
214   plugin_dispatch_values(&vl);
215 }
216
217 static int hddtemp_read(void) {
218   char buf[1024];
219   char *fields[128];
220   char *ptr;
221   char *saveptr;
222   int num_fields;
223   int num_disks;
224
225   /* get data from daemon */
226   if (hddtemp_query_daemon(buf, sizeof(buf)) < 0)
227     return (-1);
228
229   /* NB: strtok_r will eat up "||" and leading "|"'s */
230   num_fields = 0;
231   ptr = buf;
232   saveptr = NULL;
233   while ((fields[num_fields] = strtok_r(ptr, "|", &saveptr)) != NULL) {
234     ptr = NULL;
235     num_fields++;
236
237     if (num_fields >= 128)
238       break;
239   }
240
241   num_disks = num_fields / 4;
242
243   for (int i = 0; i < num_disks; i++) {
244     char *name;
245     double temperature;
246     char *mode;
247
248     mode = fields[4 * i + 3];
249     name = basename(fields[4 * i + 0]);
250
251     /* Skip non-temperature information */
252     if (mode[0] != 'C' && mode[0] != 'F')
253       continue;
254
255     temperature = atof(fields[4 * i + 2]);
256
257     /* Convert farenheit to celsius */
258     if (mode[0] == 'F')
259       temperature = (temperature - 32.0) * 5.0 / 9.0;
260
261     hddtemp_submit(name, temperature);
262   }
263
264   return (0);
265 } /* int hddtemp_read */
266
267 /* module_register
268    Register collectd plugin. */
269 void module_register(void) {
270   plugin_register_config("hddtemp", hddtemp_config, config_keys,
271                          config_keys_num);
272   plugin_register_read("hddtemp", hddtemp_read);
273 }