Bugfix: memory and thread-deleting problems
[monky] / src / hddtemp.c
index cffa213..b1ba81e 100644 (file)
@@ -1,4 +1,6 @@
-/*
+/* -*- mode: c; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: t -*-
+ * vim: ts=4 sw=4 noet ai cindent syntax=c
+ *
  * Conky, a system monitor, based on torsmo
  *
  * Any original torsmo code is licensed under the BSD license
@@ -8,7 +10,8 @@
  * Please see COPYING for details
  *
  * Copyright (c) 2004, Hannu Saransaari and Lauri Hakkarainen
- * Copyright (c) 2005-2007 Brenden Matthews, Philip Kovacs, et. al. (see AUTHORS)
+ * Copyright (c) 2005-2010 Brenden Matthews, Philip Kovacs, et. al.
+ *     (see AUTHORS)
  * All rights reserved.
  *
  * This program is free software: you can redistribute it and/or modify
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>. 
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *
- *  $Id$
  */
 
 #include "conky.h"
+#include "logging.h"
 #include <errno.h>
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
 #include <unistd.h>
 #include <fcntl.h>
 #include <netdb.h>
 #include <netinet/in.h>
 
 #define BUFLEN 512
-#define PORT 7634
+#define DEFAULT_PORT "7634"
+#define DEFAULT_HOST "127.0.0.1"
+
+static char *hddtemp_host = NULL;
+static char *hddtemp_port = NULL;
 
-char buf[BUFLEN];
+static struct hdd_info {
+       struct hdd_info *next;
+       char *dev;
+       short temp;
+       char unit;
+} hdd_info_head = {
+       .next = NULL,
+};
 
-int scan_hddtemp(const char *arg, char **dev, char **addr, int *port)
+void set_hddtemp_host(const char *host)
 {
-       char buf1[32], buf2[64];
-       int n, ret;
-       
-       ret = sscanf(arg, "%31s %63s %d", buf1, buf2, &n);
-       
-       if (ret < 1)
-               return -1;
+       if (hddtemp_host)
+               free(hddtemp_host);
+       hddtemp_host = strdup(host);
+}
 
-       *dev = strdup(buf1);
-       if (ret >= 2)
-               *addr = strdup(buf2);
-       else
-               *addr = strdup("127.0.0.1");
+void set_hddtemp_port(const char *port)
+{
+       if (hddtemp_port)
+               free(hddtemp_port);
+       hddtemp_port = strdup(port);
+}
 
-       if (ret == 3) 
-               *port = n;
-       else
-               *port = PORT;
+static void __free_hddtemp_info(struct hdd_info *hdi)
+{
+       if (hdi->next)
+               __free_hddtemp_info(hdi->next);
+       free(hdi->dev);
+       free(hdi);
+}
 
-       return 0;
+static void free_hddtemp_info(void)
+{
+       DBGP("free_hddtemp_info() called");
+       if (!hdd_info_head.next)
+               return;
+       __free_hddtemp_info(hdd_info_head.next);
+       hdd_info_head.next = NULL;
 }
 
-char *get_hddtemp_info(char *dev, char *hostaddr, int port, char *unit)
+static void add_hddtemp_info(char *dev, short temp, char unit)
 {
-       int sockfd = 0;
-       struct hostent *he;
-       struct sockaddr_in addr;
-       struct timeval tv;
-       fd_set rfds;
-       int len, i, devlen = strlen(dev);
-       char sep;
-       char *p, *out, *r = NULL;
-       
-       if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
-               perror("socket");
+       struct hdd_info *hdi = &hdd_info_head;
+
+       DBGP("add_hddtemp_info(%s, %d, %c) being called", dev, temp, unit);
+       while (hdi->next)
+               hdi = hdi->next;
+
+       hdi->next = malloc(sizeof(struct hdd_info));
+       memset(hdi->next, 0, sizeof(struct hdd_info));
+       hdi->next->dev = strdup(dev);
+       hdi->next->temp = temp;
+       hdi->next->unit = unit;
+}
+
+static char *fetch_hddtemp_output(void)
+{
+       int sockfd;
+       const char *dst_host, *dst_port;
+       char *buf = NULL;
+       int buflen, offset = 0, rlen;
+       struct addrinfo hints, *result, *rp;
+       int i;
+
+       dst_host = hddtemp_host ? hddtemp_host : DEFAULT_HOST;
+       dst_port = hddtemp_port ? hddtemp_port : DEFAULT_PORT;
+
+       memset(&hints, 0, sizeof(hints));
+       hints.ai_family = AF_INET;      /* XXX: hddtemp has no ipv6 support (yet?) */
+       hints.ai_socktype = SOCK_STREAM;
+
+       if ((i = getaddrinfo(dst_host, dst_port, &hints, &result))) {
+               NORM_ERR("getaddrinfo(): %s", gai_strerror(i));
                return NULL;
        }
 
-       he = gethostbyname(hostaddr);
-       if (!he) {
-               perror("gethostbyname");
-               goto out;
+       for (rp = result; rp; rp = rp->ai_next) {
+               sockfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+               if (sockfd == -1)
+                       continue;
+               if (connect(sockfd, rp->ai_addr, rp->ai_addrlen) != -1)
+                       break;
+               close(sockfd);
+       }
+       if (!rp) {
+               NORM_ERR("could not connect to hddtemp host");
+               goto GET_OUT;
        }
 
-       addr.sin_family = AF_INET;
-       addr.sin_port = htons(port);
-       addr.sin_addr = *((struct in_addr *)he->h_addr);
-       memset(&(addr.sin_zero), 0, 8);
-
-       if (connect(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr)) == -1) {
-               perror("connect");
-               goto out;
+       buflen = 1024;
+       buf = malloc(buflen);
+       while ((rlen = recv(sockfd, buf + offset, buflen - offset - 1, 0)) > 0) {
+               offset += rlen;
+               if (buflen - offset < 1) {
+                       buflen += 1024;
+                       buf = realloc(buf, buflen);
+               }
        }
+       if (rlen < 0)
+               perror("recv");
 
-       FD_ZERO(&rfds);
-       FD_SET(sockfd, &rfds);
+       buf[offset] = '\0';
 
-       /* We're going to wait up to a quarter a second to see whether
-        * there's any data available. Polling with timeout set to 0
-        * doesn't seem to work with hddtemp. */
-       tv.tv_sec = 0;
-       tv.tv_usec = 250000;
-       
-       i = select(sockfd+1, &rfds, NULL, NULL, &tv);
-       if (i == -1)
-       {
-               if (errno == EINTR)     /* silently ignore interrupted system call */
-                   goto out;
-               else
-                   perror("select");
+       close(sockfd);
+GET_OUT:
+       freeaddrinfo(result);
+       return buf;
+}
+
+/* this is an iterator:
+ * set line to NULL in consecutive calls to get the next field
+ * note that exhausing iteration is assumed - otherwise *saveptr
+ * is not being freed!
+ */
+static int read_hdd_val(const char *line, char **dev, short *val, char *unit,
+               char **saveptr)
+{
+       char *line_s, *cval, *endptr;
+       static char *p = 0;
+
+       if (line) {
+               *saveptr = strdup(line);
+               p = *saveptr;
        }
+       line_s = *saveptr;
+
+again:
+       if(!*p)
+               goto out_fail;
+       /* read the device */
+       *dev = ++p;
+       if (!(p = strchr(p, line_s[0])))
+               goto out_fail;
+       *(p++) = '\0';
+       /* jump over the devname */
+       if (!(p = strchr(p, line_s[0])))
+               goto out_fail;
+       /* read the value */
+       cval = ++p;
+       if (!(p = strchr(p, line_s[0])))
+               goto out_fail;
+       *(p++) = '\0';
+       *unit = *(p++);
+       *val = strtol(cval, &endptr, 10);
+       if (*endptr) {
+               if (!(p = strchr(p, line_s[0])))
+                       goto out_fail;
 
-       /* No data available */
-       if (i <= 0)
-               goto out;
+               p++;
+               goto again;
+       }
        
-       p = buf;
-       len = 0;
+       /* preset p for next call */
+       p++;
+
+       return 0;
+out_fail:
+       free(*saveptr);
+       return 1;
+}
+
+int update_hddtemp(void) {
+       char *data, *dev, unit, *saveptr;
+       short val;
+       static double last_hddtemp_update = 0.0;
+
+       /* limit tcp connection overhead */
+       if (current_update_time - last_hddtemp_update < 5)
+               return 0;
+       last_hddtemp_update = current_update_time;
+
+       free_hddtemp_info();
+
+       if (!(data = fetch_hddtemp_output()))
+               return 0;
+
+       if (read_hdd_val(data, &dev, &val, &unit, &saveptr)) {
+               free(data);
+               return 0;
+       }
        do {
-               i = recv(sockfd, p, BUFLEN - (p-buf), 0);
-               if (i < 0) {
-                       perror("recv");
-                       goto out;
-               }
-               len += i;
-               p += i;
-       } while (i > 0 && p < buf + BUFLEN - 1);
-       
-       if (len < 2) {
-               goto out;
+               add_hddtemp_info(dev, val, unit);
+       } while (!read_hdd_val(NULL, &dev, &val, &unit, &saveptr));
+       free(data);
+       return 0;
+}
+
+void free_hddtemp(void)
+{
+       free_hddtemp_info();
+       if (hddtemp_host) {
+               free(hddtemp_host);
+               hddtemp_host = NULL;
        }
-       
-       buf[len] = 0;
-               
-       /* The first character read is the separator. */
-       sep = buf[0];
-       p = buf+1;
-       
-       while (*p) {
-               if (!strncmp(p, dev, devlen)) {
-                       p += devlen + 1; 
-                       p = strchr(p, sep);
-                       if (!p) 
-                               goto out;
-                       p++;
-                       out = p;        
-                       p = strchr(p, sep);
-                       if (!p) 
-                               goto out;
-                       *p = '\0';
-                       p++;
-                       *unit = *p;     
-                       if (!strncmp(out, "NA", 2)) {
-                               strcpy(buf, "N/A");
-                               r = buf;
-                       } else {                                        
-                               r = out;
-                       }
-                       goto out;
-               } else {
-                       for (i = 0; i < 5; i++) {
-                               p = strchr(p, sep);
-                               if (!p)
-                                       goto out;
-                               p++;
-                       }
-               }
+       if (hddtemp_port) {
+               free(hddtemp_port);
+               hddtemp_port = NULL;
        }
-out:   close(sockfd);
-       return r;
 }
 
+int get_hddtemp_info(const char *dev, short *val, char *unit)
+{
+       struct hdd_info *hdi = hdd_info_head.next;
+
+       /* if no dev is given, just use hdd_info_head->next */
+       while(dev && hdi) {
+               if (!strcmp(dev, hdi->dev))
+                       break;
+               hdi = hdi->next;
+       }
+       if (!hdi)
+               return 1;
+       
+       *val = hdi->temp;
+       *unit = hdi->unit;
+       return 0;
+}