Bugfix: memory and thread-deleting problems
[monky] / src / common.c
index dfd911b..b9b093b 100644 (file)
@@ -1,4 +1,7 @@
-/* Conky, a system monitor, based on torsmo
+/* -*- 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
  *
@@ -7,7 +10,7 @@
  * Please see COPYING for details
  *
  * Copyright (c) 2004, Hannu Saransaari and Lauri Hakkarainen
- * Copyright (c) 2005-2007 Brenden Matthews, Philip Kovacs, et. al.
+ * Copyright (c) 2005-2010 Brenden Matthews, Philip Kovacs, et. al.
  *     (see AUTHORS)
  * All rights reserved.
  *
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *
- * $Id$ */
+ */
 
+#include "config.h"
 #include "conky.h"
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
+#include "fs.h"
+#include "logging.h"
+#include "net_stat.h"
+#include "specials.h"
+#include "timeinfo.h"
 #include <ctype.h>
 #include <errno.h>
 #include <sys/time.h>
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <netinet/in.h>
 #include <pthread.h>
+#include <semaphore.h>
+#include <unistd.h>
+#include "diskio.h"
+#include <fcntl.h>
+
+/* check for OS and include appropriate headers */
+#if defined(__linux__)
+#include "linux.h"
+#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+#include "freebsd.h"
+#elif defined(__OpenBSD__)
+#include "openbsd.h"
+#endif
+
+/* folds a string over top of itself, like so:
+ *
+ * if start is "blah", and you call it with count = 1, the result will be "lah"
+ */
+void strfold(char *start, int count)
+{
+       char *curplace;
+       for (curplace = start + count; *curplace != 0; curplace++) {
+               *(curplace - count) = *curplace;
+       }
+       *(curplace - count) = 0;
+}
 
-void update_uname(void)
+#ifndef HAVE_STRNDUP
+// use our own strndup() if it's not available
+char *strndup(const char *s, size_t n)
+{
+       if (strlen(s) > n) {
+               char *ret = malloc(n + 1);
+               strncpy(ret, s, n);
+               ret[n] = 0;
+               return ret;
+       } else {
+               return strdup(s);
+       }
+}
+#endif /* HAVE_STRNDUP */
+
+int update_uname(void)
 {
        uname(&info.uname_s);
+       return 0;
 }
 
 double get_time(void)
@@ -47,18 +98,62 @@ double get_time(void)
        return tv.tv_sec + (tv.tv_usec / 1000000.0);
 }
 
+/* Converts '~/...' paths to '/home/blah/...' assumes that 'dest' is at least
+ * DEFAULT_TEXT_BUFFER_SIZE.  It's similar to variable_substitute, except only
+ * cheques for $HOME and ~/ in path */
+void to_real_path(char *dest, const char *source)
+{
+       char tmp[DEFAULT_TEXT_BUFFER_SIZE];
+       if (sscanf(source, "~/%s", tmp) || sscanf(source, "$HOME/%s", tmp)) {
+               char *homedir = getenv("HOME");
+               if (homedir) {
+                       snprintf(dest, DEFAULT_TEXT_BUFFER_SIZE, "%s/%s", homedir, tmp);
+               } else {
+                       NORM_ERR("$HOME environment variable doesn't exist");
+                       strncpy(dest, source, DEFAULT_TEXT_BUFFER_SIZE);
+               }
+       } else if (dest != source) {    //see changelog 2009-06-29 if you doubt that this check is necessary 
+               strncpy(dest, source, DEFAULT_TEXT_BUFFER_SIZE);
+       }
+}
+
+int open_fifo(const char *file, int *reported)
+{
+       char path[DEFAULT_TEXT_BUFFER_SIZE];
+       int fd = 0;
+
+       to_real_path(path, file);
+       fd = open(file, O_RDONLY | O_NONBLOCK);
+
+       if (fd == -1) {
+               if (!reported || *reported == 0) {
+                       NORM_ERR("can't open %s: %s", file, strerror(errno));
+                       if (reported) {
+                               *reported = 1;
+                       }
+               }
+               return -1;
+       }
+
+       return fd;
+}
+
 FILE *open_file(const char *file, int *reported)
 {
-       FILE *fp = fopen(file, "r");
+       char path[DEFAULT_TEXT_BUFFER_SIZE];
+       FILE *fp = 0;
+
+       to_real_path(path, file);
+       fp = fopen(file, "r");
 
        if (!fp) {
                if (!reported || *reported == 0) {
-                       ERR("can't open %s: %s", file, strerror(errno));
+                       NORM_ERR("can't open %s: %s", file, strerror(errno));
                        if (reported) {
                                *reported = 1;
                        }
                }
-               return 0;
+               return NULL;
        }
 
        return fp;
@@ -120,132 +215,192 @@ void variable_substitute(const char *s, char *dest, unsigned int n)
        *dest = '\0';
 }
 
-/* network interface stuff */
-
-static struct net_stat netstats[16];
-
-struct net_stat *get_net_stat(const char *dev)
+void format_seconds(char *buf, unsigned int n, long seconds)
 {
-       unsigned int i;
+       long days;
+       int hours, minutes;
 
-       if (!dev) {
-               return 0;
+       if (times_in_seconds()) {
+               snprintf(buf, n, "%ld", seconds);
+               return;
        }
 
-       /* find interface stat */
-       for (i = 0; i < 16; i++) {
-               if (netstats[i].dev && strcmp(netstats[i].dev, dev) == 0) {
-                       return &netstats[i];
-               }
-       }
+       days = seconds / 86400;
+       seconds %= 86400;
+       hours = seconds / 3600;
+       seconds %= 3600;
+       minutes = seconds / 60;
+       seconds %= 60;
 
-       /* wasn't found? add it */
-       if (i == 16) {
-               for (i = 0; i < 16; i++) {
-                       if (netstats[i].dev == 0) {
-                               netstats[i].dev = strdup(dev);
-                               return &netstats[i];
-                       }
-               }
+       if (days > 0) {
+               snprintf(buf, n, "%ldd %dh %dm", days, hours, minutes);
+       } else {
+               snprintf(buf, n, "%dh %dm %lds", hours, minutes, seconds);
        }
-
-       CRIT_ERR("too many interfaces used (limit is 16)");
-       return 0;
 }
 
-void clear_net_stats(void)
+void format_seconds_short(char *buf, unsigned int n, long seconds)
 {
-       memset(netstats, 0, sizeof(netstats));
-}
+       long days;
+       int hours, minutes;
 
-void free_dns_data(void)
-{
-       int i;
-       struct dns_data *data = &info.nameserver_info;
-       for (i = 0; i < data->nscount; i++)
-               free(data->ns_list[i]);
-       if (data->ns_list)
-               free(data->ns_list);
-       memset(data, 0, sizeof(struct dns_data));
-}
+       if (times_in_seconds()) {
+               snprintf(buf, n, "%ld", seconds);
+               return;
+       }
 
-//static double last_dns_update;
+       days = seconds / 86400;
+       seconds %= 86400;
+       hours = seconds / 3600;
+       seconds %= 3600;
+       minutes = seconds / 60;
+       seconds %= 60;
+
+       if (days > 0) {
+               snprintf(buf, n, "%ldd %dh", days, hours);
+       } else if (hours > 0) {
+               snprintf(buf, n, "%dh %dm", hours, minutes);
+       } else {
+               snprintf(buf, n, "%dm %lds", minutes, seconds);
+       }
+}
 
-void update_dns_data(void)
+/* Linked list containing the functions to call upon each update interval.
+ * Populated while initialising text objects in construct_text_object(). */
+static struct update_cb {
+       struct update_cb *next;
+       int (*func)(void);
+       pthread_t thread;
+       sem_t start_wait, end_wait;
+
+    /* set to 1 when starting the thread
+        * set to 0 to request thread termination */
+       volatile char running;
+} update_cb_head = {
+       .next = NULL,
+       .func = NULL,
+};
+
+static void *run_update_callback(void *) __attribute__((noreturn));
+
+static int threading_started = 0;
+
+/* Register an update callback. Don't allow duplicates, to minimise side
+ * effects and overhead. */
+void add_update_callback(int (*func)(void))
 {
-       FILE *fp;
-       char line[256];
-       struct dns_data *data = &info.nameserver_info;
+       struct update_cb *uc = &update_cb_head;
 
-       /* maybe updating too often causes higher load because of /etc lying on a real FS
-       if (current_update_time - last_dns_update < 10.0)
+       if (!func)
                return;
-       else
-               last_dns_update = current_update_time;
-       */
 
-       free_dns_data();
+       while (uc->next) {
+               if (uc->next->func == func)
+                       return;
+               uc = uc->next;
+       }
+
+       uc->next = malloc(sizeof(struct update_cb));
+       uc = uc->next;
 
-       if ((fp = fopen("/etc/resolv.conf", "r")) == NULL)
-               return;
-       while(!feof(fp)) {
-               if (fgets(line, 255, fp) == NULL)
-                       goto OUT;
-               if (!strncmp(line, "nameserver ", 11)) {
-                       line[strlen(line) - 1] = '\0';  // remove trailing newline
-                       data->nscount++;
-                       data->ns_list = realloc(data->ns_list, data->nscount * sizeof(char *));
-                       data->ns_list[data->nscount - 1] = strdup(line + 11);
+       memset(uc, 0, sizeof(struct update_cb));
+       uc->func = func;
+       sem_init(&uc->start_wait, 0, 0);
+       sem_init(&uc->end_wait, 0, 0);
+
+       if (threading_started) {
+               if (!uc->running) {
+                       uc->running = 1;
+                       pthread_create(&uc->thread, NULL, &run_update_callback, uc);
                }
        }
-OUT:
-       fclose(fp);
 }
 
-void format_seconds(char *buf, unsigned int n, long t)
+/* Free the list element uc and all decendants recursively. */
+static void __free_update_callbacks(struct update_cb *uc)
 {
-       if (t >= 24 * 60 * 60) {        /* hours necessary when there are days? */
-               snprintf(buf, n, "%ldd %ldh %ldm", t / 60 / 60 / 24, (t / 60 / 60) % 24,
-                       (t / 60) % 60);
-       } else if (t >= 60 * 60) {
-               snprintf(buf, n, "%ldh %ldm", (t / 60 / 60) % 24, (t / 60) % 60);
-       } else {
-               snprintf(buf, n, "%ldm %lds", t / 60, t % 60);
+       if (uc->next)
+               __free_update_callbacks(uc->next);
+
+       if (uc->running) {
+               /* send cancellation request, then trigger and join the thread */
+               uc->running = 0;
+               sem_post(&uc->start_wait);
+       }
+       if (pthread_join(uc->thread, NULL)) {
+               NORM_ERR("Error destroying thread");
        }
+
+       /* finally destroy the semaphores */
+       sem_destroy(&uc->start_wait);
+       sem_destroy(&uc->end_wait);
+
+       free(uc);
 }
 
-void format_seconds_short(char *buf, unsigned int n, long t)
+/* Free the whole list of update callbacks. */
+void free_update_callbacks(void)
 {
-       if (t >= 24 * 60 * 60) {
-               snprintf(buf, n, "%ldd %ldh", t / 60 / 60 / 24, (t / 60 / 60) % 24);
-       } else if (t >= 60 * 60) {
-               snprintf(buf, n, "%ldh %ldm", (t / 60 / 60) % 24, (t / 60) % 60);
-       } else {
-               snprintf(buf, n, "%ldm", t / 60);
-       }
+       if (update_cb_head.next)
+               __free_update_callbacks(update_cb_head.next);
+       update_cb_head.next = NULL;
 }
 
-static double last_meminfo_update;
-static double last_fs_update;
+/* We cannot start threads before we forked to background, because the threads
+ * would remain in the wrong process. Because of that, add_update_callback()
+ * doesn't create threads before start_update_threading() is called.
+ * start_update_threading() starts all threads previously registered, and sets a
+ * flag so that future threads are automagically started by
+ * add_update_callback().
+ * This text is almost longer than the actual code.
+ */
+void start_update_threading(void)
+{
+       struct update_cb *uc;
 
-unsigned long long need_mask;
+       threading_started = 1;
 
-#define NEED(a) ((need_mask & (1 << a)) && ((info.mask & (1 << a)) == 0))
+       for (uc = update_cb_head.next; uc; uc = uc->next) {
+               if (!uc->running) {
+                       uc->running = 1;
+                       pthread_create(&uc->thread, NULL, &run_update_callback, uc);
+               }
+       }
+}
 
-void update_stuff(void)
+static void *run_update_callback(void *data)
 {
-       unsigned int i;
+       struct update_cb *ucb = data;
+
+       if (!ucb || !ucb->func) pthread_exit(NULL);
+
+       while (1) {
+               if (sem_wait(&ucb->start_wait)) pthread_exit(NULL);
+               if (ucb->running == 0) pthread_exit(NULL);
+               if((*ucb->func)()) {
+                       ucb->next = ucb;        //this is normally not be possible, so we use it to show that there was a critical error
+                       sem_post(&ucb->end_wait);
+                       sem_post(&ucb->end_wait);
+                       pthread_exit(NULL);
+               }
+               if (sem_post(&ucb->end_wait)) pthread_exit(NULL);
+       }
+}
 
-       info.mask = 0;
+int no_buffers;
 
-       if (no_buffers) {
-               need_mask |= 1 << INFO_BUFFERS;
-       }
+void update_stuff(void)
+{
+       int i;
+       struct update_cb *uc;
 
        /* clear speeds and up status in case device was removed and doesn't get
         * updated */
 
-       for (i = 0; i < 16; i++) {
+       #ifdef HAVE_OPENMP
+       #pragma omp parallel for schedule(dynamic,10)
+       #endif /* HAVE_OPENMP */
+       for (i = 0; i < MAX_NET_INTERFACES; i++) {
                if (netstats[i].dev) {
                        netstats[i].up = 0;
                        netstats[i].recv_speed = 0.0;
@@ -255,117 +410,86 @@ void update_stuff(void)
 
        prepare_update();
 
-       if (NEED(INFO_UPTIME)) {
-               update_uptime();
-       }
-
-       if (NEED(INFO_PROCS)) {
-               update_total_processes();
+       for (uc = update_cb_head.next; uc; uc = uc->next) {
+               if (sem_post(&uc->start_wait)) {
+                       NORM_ERR("Semaphore error");
+               }
        }
-
-       if (NEED(INFO_RUN_PROCS)) {
-               update_running_processes();
+       /* need to synchronise here, otherwise locking is needed (as data
+        * would be printed with some update callbacks still running) */
+       for (uc = update_cb_head.next; uc; uc = uc->next) {
+               sem_wait(&uc->end_wait);
+               if(uc == uc->next) {
+                       pthread_join(uc->thread, NULL);
+                       free(uc);
+                       exit(EXIT_FAILURE);
+               }
        }
 
-       if (NEED(INFO_CPU)) {
-               update_cpu_usage();
+       /* XXX: move the following into the update_meminfo() functions? */
+       if (no_buffers) {
+               info.mem -= info.bufmem;
+               info.memeasyfree += info.bufmem;
        }
+}
 
-       if (NEED(INFO_NET)) {
-               update_net_stats();
+/* Ohkie to return negative values for temperatures */
+int round_to_int_temp(float f)
+{
+       if (f >= 0.0) {
+               return (int) (f + 0.5);
+       } else {
+               return (int) (f - 0.5);
        }
-
-       if (NEED(INFO_DISKIO)) {
-               update_diskio();
+}
+/* Don't return negative values for cpugraph, bar, gauge, percentage.
+ * Causes unreasonable numbers to show */
+unsigned int round_to_int(float f)
+{
+       if (f >= 0.0) {
+               return (int) (f + 0.5);
+       } else {
+               return 0;
        }
+}
 
-#if defined(__linux__)
-       if (NEED(INFO_I8K)) {
-               update_i8k();
-       }
-#endif /* __linux__ */
-
-#ifdef MPD
-       if (NEED(INFO_MPD)) {
-               if (!mpd_timed_thread) {
-                       init_mpd_stats(&info);
-                       mpd_timed_thread = timed_thread_create(&update_mpd,
-                               (void *) NULL, info.music_player_interval * 1000000);
-                       if (!mpd_timed_thread) {
-                               ERR("Failed to create MPD timed thread");
-                       }
-                       timed_thread_register(mpd_timed_thread, &mpd_timed_thread);
-                       if (timed_thread_run(mpd_timed_thread)) {
-                               ERR("Failed to run MPD timed thread");
-                       }
+void scan_loadavg_arg(struct text_object *obj, const char *arg)
+{
+       obj->data.i = 0;
+       if (arg && !arg[1] && isdigit(arg[0])) {
+               obj->data.i = atoi(arg);
+               if (obj->data.i > 3 || obj->data.i < 1) {
+                       NORM_ERR("loadavg arg needs to be in range (1,3)");
+                       obj->data.i = 0;
                }
        }
-#endif
-
-#ifdef XMMS2
-       if (NEED(INFO_XMMS2)) {
-               update_xmms2();
-       }
-#endif
-
-#ifdef AUDACIOUS
-       if (NEED(INFO_AUDACIOUS)) {
-               update_audacious();
-       }
-#endif
-
-#ifdef BMPX
-       if (NEED(INFO_BMPX)) {
-               update_bmpx();
-       }
-#endif
+       /* convert to array index (or the default (-1)) */
+       obj->data.i--;
+}
 
-       if (NEED(INFO_LOADAVG)) {
-               update_load_average();
-       }
+void print_loadavg(struct text_object *obj, char *p, int p_max_size)
+{
+       float *v = info.loadavg;
 
-       if ((NEED(INFO_MEM) || NEED(INFO_BUFFERS) || NEED(INFO_TOP))
-                       && current_update_time - last_meminfo_update > 6.9) {
-               update_meminfo();
-               if (no_buffers) {
-                       info.mem -= info.bufmem;
-               }
-               last_meminfo_update = current_update_time;
+       if (obj->data.i < 0) {
+               snprintf(p, p_max_size, "%.2f %.2f %.2f", v[0], v[1], v[2]);
+       } else {
+               snprintf(p, p_max_size, "%.2f", v[obj->data.i]);
        }
+}
 
-       if (NEED(INFO_TOP)) {
-               update_top();
-       }
+#ifdef X11
+void scan_loadgraph_arg(struct text_object *obj, const char *arg)
+{
+       char *buf = 0;
 
-       /* update_fs_stat() won't do anything if there aren't fs -things */
-       if (NEED(INFO_FS) && current_update_time - last_fs_update > 12.9) {
-               update_fs_stats();
-               last_fs_update = current_update_time;
-       }
-#ifdef TCP_PORT_MONITOR
-       if (NEED(INFO_TCP_PORT_MONITOR)) {
-               update_tcp_port_monitor_collection(info.p_tcp_port_monitor_collection);
-       }
-#endif
-       if (NEED(INFO_ENTROPY)) {
-               update_entropy();
-       }
-       if (NEED(INFO_USERS)) {
-               update_users();
-       }
-       if (NEED(INFO_GW)) {
-               update_gateway_info();
-       }
-       if (NEED(INFO_DNS)) {
-               update_dns_data();
-       }
+       buf = scan_graph(obj, arg, 0);
+       if (buf)
+               free(buf);
 }
 
-int round_to_int(float f)
+void print_loadgraph(struct text_object *obj, char *p, int p_max_size)
 {
-       if (f >= 0.0) {
-               return (int) (f + 0.5);
-       } else {
-               return (int) (f - 0.5);
-       }
+       new_graph(obj, p, p_max_size, info.loadavg[0]);
 }
+#endif /* X11 */