rework the update machinery to use callbacks
[monky] / src / common.c
1 /* -*- mode: c; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: t -*-
2  *
3  * Conky, a system monitor, based on torsmo
4  *
5  * Any original torsmo code is licensed under the BSD license
6  *
7  * All code written since the fork of torsmo is licensed under the GPL
8  *
9  * Please see COPYING for details
10  *
11  * Copyright (c) 2004, Hannu Saransaari and Lauri Hakkarainen
12  * Copyright (c) 2005-2009 Brenden Matthews, Philip Kovacs, et. al.
13  *      (see AUTHORS)
14  * All rights reserved.
15  *
16  * This program is free software: you can redistribute it and/or modify
17  * it under the terms of the GNU General Public License as published by
18  * the Free Software Foundation, either version 3 of the License, or
19  * (at your option) any later version.
20  *
21  * This program is distributed in the hope that it will be useful,
22  * but WITHOUT ANY WARRANTY; without even the implied warranty of
23  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24  * GNU General Public License for more details.
25  * You should have received a copy of the GNU General Public License
26  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
27  *
28  * vim: ts=4 sw=4 noet ai cindent syntax=c
29  *
30  */
31
32 #include "config.h"
33 #include "conky.h"
34 #include "fs.h"
35 #include "logging.h"
36 #include <ctype.h>
37 #include <errno.h>
38 #include <sys/time.h>
39 #include <sys/ioctl.h>
40 #include <net/if.h>
41 #include <netinet/in.h>
42 #include <pthread.h>
43 #include <unistd.h>
44 #include "diskio.h"
45 #include <fcntl.h>
46
47 /* check for OS and include appropriate headers */
48 #if defined(__linux__)
49 #include "linux.h"
50 #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
51 #include "freebsd.h"
52 #elif defined(__OpenBSD__)
53 #include "openbsd.h"
54 #endif
55
56 /* folds a string over top of itself, like so:
57  *
58  * if start is "blah", and you call it with count = 1, the result will be "lah"
59  */
60 void strfold(char *start, int count)
61 {
62         char *curplace;
63         for (curplace = start + count; *curplace != 0; curplace++) {
64                 *(curplace - count) = *curplace;
65         }
66         *(curplace - count) = 0;
67 }
68
69 #ifndef HAVE_STRNDUP
70 // use our own strndup() if it's not available
71 char *strndup(const char *s, size_t n)
72 {
73         if (strlen(s) > n) {
74                 char *ret = malloc(n + 1);
75                 strncpy(ret, s, n);
76                 ret[n] = 0;
77                 return ret;
78         } else {
79                 return strdup(s);
80         }
81 }
82 #endif /* HAVE_STRNDUP */
83
84 void update_uname(void)
85 {
86         uname(&info.uname_s);
87 }
88
89 double get_time(void)
90 {
91         struct timeval tv;
92
93         gettimeofday(&tv, 0);
94         return tv.tv_sec + (tv.tv_usec / 1000000.0);
95 }
96
97 /* Converts '~/...' paths to '/home/blah/...' assumes that 'dest' is at least
98  * DEFAULT_TEXT_BUFFER_SIZE.  It's similar to variable_substitute, except only
99  * cheques for $HOME and ~/ in path */
100 void to_real_path(char *dest, const char *source)
101 {
102         char tmp[DEFAULT_TEXT_BUFFER_SIZE];
103         if (sscanf(source, "~/%s", tmp) || sscanf(source, "$HOME/%s", tmp)) {
104                 char *homedir = getenv("HOME");
105                 if (homedir) {
106                         snprintf(dest, DEFAULT_TEXT_BUFFER_SIZE, "%s/%s", homedir, tmp);
107                 } else {
108                         NORM_ERR("$HOME environment variable doesn't exist");
109                         strncpy(dest, source, DEFAULT_TEXT_BUFFER_SIZE);
110                 }
111         } else if (dest != source) {    //see changelog 2009-06-29 if you doubt that this check is necessary 
112                 strncpy(dest, source, DEFAULT_TEXT_BUFFER_SIZE);
113         }
114 }
115
116 int open_fifo(const char *file, int *reported)
117 {
118         char path[DEFAULT_TEXT_BUFFER_SIZE];
119         int fd = 0;
120
121         to_real_path(path, file);
122         fd = open(file, O_RDONLY | O_NONBLOCK);
123
124         if (fd == -1) {
125                 if (!reported || *reported == 0) {
126                         NORM_ERR("can't open %s: %s", file, strerror(errno));
127                         if (reported) {
128                                 *reported = 1;
129                         }
130                 }
131                 return -1;
132         }
133
134         return fd;
135 }
136
137 FILE *open_file(const char *file, int *reported)
138 {
139         char path[DEFAULT_TEXT_BUFFER_SIZE];
140         FILE *fp = 0;
141
142         to_real_path(path, file);
143         fp = fopen(file, "r");
144
145         if (!fp) {
146                 if (!reported || *reported == 0) {
147                         NORM_ERR("can't open %s: %s", file, strerror(errno));
148                         if (reported) {
149                                 *reported = 1;
150                         }
151                 }
152                 return NULL;
153         }
154
155         return fp;
156 }
157
158 void variable_substitute(const char *s, char *dest, unsigned int n)
159 {
160         while (*s && n > 1) {
161                 if (*s == '$') {
162                         s++;
163                         if (*s != '$') {
164                                 char buf[256];
165                                 const char *a, *var;
166                                 unsigned int len;
167
168                                 /* variable is either $foo or ${foo} */
169                                 if (*s == '{') {
170                                         s++;
171                                         a = s;
172                                         while (*s && *s != '}') {
173                                                 s++;
174                                         }
175                                 } else {
176                                         a = s;
177                                         while (*s && (isalnum((int) *s) || *s == '_')) {
178                                                 s++;
179                                         }
180                                 }
181
182                                 /* copy variable to buffer and look it up */
183                                 len = (s - a > 255) ? 255 : (s - a);
184                                 strncpy(buf, a, len);
185                                 buf[len] = '\0';
186
187                                 if (*s == '}') {
188                                         s++;
189                                 }
190
191                                 var = getenv(buf);
192
193                                 if (var) {
194                                         /* add var to dest */
195                                         len = strlen(var);
196                                         if (len >= n) {
197                                                 len = n - 1;
198                                         }
199                                         strncpy(dest, var, len);
200                                         dest += len;
201                                         n -= len;
202                                 }
203                                 continue;
204                         }
205                 }
206
207                 *dest++ = *s++;
208                 n--;
209         }
210
211         *dest = '\0';
212 }
213
214 /* network interface stuff */
215
216 static struct net_stat netstats[16];
217
218 struct net_stat *get_net_stat(const char *dev, void *free_at_crash1, void *free_at_crash2)
219 {
220         unsigned int i;
221
222         if (!dev) {
223                 return 0;
224         }
225
226         /* find interface stat */
227         for (i = 0; i < 16; i++) {
228                 if (netstats[i].dev && strcmp(netstats[i].dev, dev) == 0) {
229                         return &netstats[i];
230                 }
231         }
232
233         /* wasn't found? add it */
234         for (i = 0; i < 16; i++) {
235                 if (netstats[i].dev == 0) {
236                         netstats[i].dev = strndup(dev, text_buffer_size);
237                         return &netstats[i];
238                 }
239         }
240
241         CRIT_ERR(free_at_crash1, free_at_crash2, "too many interfaces used (limit is 16)");
242         return 0;
243 }
244
245 void clear_net_stats(void)
246 {
247         int i;
248         for (i = 0; i < 16; i++) {
249                 if (netstats[i].dev) {
250                         free(netstats[i].dev);
251                 }
252         }
253         memset(netstats, 0, sizeof(netstats));
254 }
255
256 /* We should check if this is ok with OpenBSD and NetBSD as well. */
257 int interface_up(const char *dev)
258 {
259         int fd;
260         struct ifreq ifr;
261
262         if ((fd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
263                 CRIT_ERR(NULL, NULL, "could not create sockfd");
264                 return 0;
265         }
266         strncpy(ifr.ifr_name, dev, IFNAMSIZ);
267         if (ioctl(fd, SIOCGIFFLAGS, &ifr)) {
268                 /* if device does not exist, treat like not up */
269                 if (errno != ENODEV && errno != ENXIO)
270                         perror("SIOCGIFFLAGS");
271                 goto END_FALSE;
272         }
273
274         if (!(ifr.ifr_flags & IFF_UP)) /* iface is not up */
275                 goto END_FALSE;
276         if (ifup_strictness == IFUP_UP)
277                 goto END_TRUE;
278
279         if (!(ifr.ifr_flags & IFF_RUNNING))
280                 goto END_FALSE;
281         if (ifup_strictness == IFUP_LINK)
282                 goto END_TRUE;
283
284         if (ioctl(fd, SIOCGIFADDR, &ifr)) {
285                 perror("SIOCGIFADDR");
286                 goto END_FALSE;
287         }
288         if (((struct sockaddr_in *)&(ifr.ifr_ifru.ifru_addr))->sin_addr.s_addr)
289                 goto END_TRUE;
290
291 END_FALSE:
292         close(fd);
293         return 0;
294 END_TRUE:
295         close(fd);
296         return 1;
297 }
298
299 void free_dns_data(void)
300 {
301         int i;
302         struct dns_data *data = &info.nameserver_info;
303         for (i = 0; i < data->nscount; i++)
304                 free(data->ns_list[i]);
305         if (data->ns_list)
306                 free(data->ns_list);
307         memset(data, 0, sizeof(struct dns_data));
308 }
309
310 //static double last_dns_update;
311
312 void update_dns_data(void)
313 {
314         FILE *fp;
315         char line[256];
316         struct dns_data *data = &info.nameserver_info;
317
318         /* maybe updating too often causes higher load because of /etc lying on a real FS
319         if (current_update_time - last_dns_update < 10.0)
320                 return;
321         else
322                 last_dns_update = current_update_time;
323         */
324
325         free_dns_data();
326
327         if ((fp = fopen("/etc/resolv.conf", "r")) == NULL)
328                 return;
329         while(!feof(fp)) {
330                 if (fgets(line, 255, fp) == NULL) {
331                         break;
332                 }
333                 if (!strncmp(line, "nameserver ", 11)) {
334                         line[strlen(line) - 1] = '\0';  // remove trailing newline
335                         data->nscount++;
336                         data->ns_list = realloc(data->ns_list, data->nscount * sizeof(char *));
337                         data->ns_list[data->nscount - 1] = strndup(line + 11, text_buffer_size);
338                 }
339         }
340         fclose(fp);
341 }
342
343 void format_seconds(char *buf, unsigned int n, long seconds)
344 {
345         long days;
346         int hours, minutes;
347
348         days = seconds / 86400;
349         seconds %= 86400;
350         hours = seconds / 3600;
351         seconds %= 3600;
352         minutes = seconds / 60;
353         seconds %= 60;
354
355         if (days > 0) {
356                 snprintf(buf, n, "%ldd %dh %dm", days, hours, minutes);
357         } else {
358                 snprintf(buf, n, "%dh %dm %lds", hours, minutes, seconds);
359         }
360 }
361
362 void format_seconds_short(char *buf, unsigned int n, long seconds)
363 {
364         long days;
365         int hours, minutes;
366
367         days = seconds / 86400;
368         seconds %= 86400;
369         hours = seconds / 3600;
370         seconds %= 3600;
371         minutes = seconds / 60;
372         seconds %= 60;
373
374         if (days > 0) {
375                 snprintf(buf, n, "%ldd %dh", days, hours);
376         } else if (hours > 0) {
377                 snprintf(buf, n, "%dh %dm", hours, minutes);
378         } else {
379                 snprintf(buf, n, "%dm %lds", minutes, seconds);
380         }
381 }
382
383 static struct update_cb {
384         struct update_cb *next;
385         void (*func)(void);
386 } update_cb_head = {
387         .next = NULL,
388 };
389
390 void add_update_callback(void (*func)(void))
391 {
392         struct update_cb *uc = &update_cb_head;
393
394         while (uc->next) {
395                 if (uc->next->func == func)
396                         return;
397                 uc = uc->next;
398         }
399         uc->next = malloc(sizeof(struct update_cb));
400         memset(uc->next, 0, sizeof(struct update_cb));
401         uc->next->func = func;
402 }
403
404 static void __free_update_callbacks(struct update_cb *uc)
405 {
406         if (uc->next)
407                 __free_update_callbacks(uc->next);
408         free(uc);
409 }
410
411 void free_update_callbacks(void)
412 {
413         if (update_cb_head.next)
414                 __free_update_callbacks(update_cb_head.next);
415         update_cb_head.next = NULL;
416 }
417
418 unsigned long long need_mask;
419 int no_buffers;
420
421 #define NEED(a) ((need_mask & (1ULL << a)) && ((info.mask & (1ULL << a)) == 0))
422
423 void update_stuff(void)
424 {
425         int i;
426         static double last_meminfo_update;
427         struct update_cb *uc;
428
429         info.mask = 0;
430
431         if (no_buffers) {
432                 need_mask |= 1 << INFO_BUFFERS;
433         }
434
435         /* clear speeds and up status in case device was removed and doesn't get
436          * updated */
437
438         #ifdef HAVE_OPENMP
439         #pragma omp parallel for schedule(dynamic,10)
440         #endif /* HAVE_OPENMP */
441         for (i = 0; i < 16; i++) {
442                 if (netstats[i].dev) {
443                         netstats[i].up = 0;
444                         netstats[i].recv_speed = 0.0;
445                         netstats[i].trans_speed = 0.0;
446                 }
447         }
448
449         prepare_update();
450
451         for (uc = update_cb_head.next; uc; uc = uc->next)
452                 (*uc->func)();
453
454         if ((NEED(INFO_MEM) || NEED(INFO_BUFFERS) || NEED(INFO_TOP))
455                         && current_update_time - last_meminfo_update > 6.9) {
456                 update_meminfo();
457                 if (no_buffers) {
458                         info.mem -= info.bufmem;
459                         info.memeasyfree += info.bufmem;
460                 }
461                 last_meminfo_update = current_update_time;
462         }
463         
464 #ifdef X11
465         if (NEED(INFO_X11) && x_initialised == YES) {
466                 update_x11info();
467         }
468 #endif
469 }
470
471 /* Ohkie to return negative values for temperatures */
472 int round_to_int_temp(float f)
473 {
474         if (f >= 0.0) {
475                 return (int) (f + 0.5);
476         } else {
477                 return (int) (f - 0.5);
478         }
479 }
480 /* Don't return negative values for cpugraph, bar, gauge, percentage.
481  * Causes unreasonable numbers to show */
482 unsigned int round_to_int(float f)
483 {
484         if (f >= 0.0) {
485                 return (int) (f + 0.5);
486         } else {
487                 return 0;
488         }
489 }
490