net_stats: outsource network-related objects
[monky] / src / common.c
1 /* -*- mode: c; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: t -*-
2  * vim: ts=4 sw=4 noet ai cindent syntax=c
3  *
4  * Conky, a system monitor, based on torsmo
5  *
6  * Any original torsmo code is licensed under the BSD license
7  *
8  * All code written since the fork of torsmo is licensed under the GPL
9  *
10  * Please see COPYING for details
11  *
12  * Copyright (c) 2004, Hannu Saransaari and Lauri Hakkarainen
13  * Copyright (c) 2005-2009 Brenden Matthews, Philip Kovacs, et. al.
14  *      (see AUTHORS)
15  * All rights reserved.
16  *
17  * This program is free software: you can redistribute it and/or modify
18  * it under the terms of the GNU General Public License as published by
19  * the Free Software Foundation, either version 3 of the License, or
20  * (at your option) any later version.
21  *
22  * This program is distributed in the hope that it will be useful,
23  * but WITHOUT ANY WARRANTY; without even the implied warranty of
24  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25  * GNU General Public License for more details.
26  * You should have received a copy of the GNU General Public License
27  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
28  *
29  */
30
31 #include "config.h"
32 #include "conky.h"
33 #include "fs.h"
34 #include "logging.h"
35 #include "net_stat.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 <semaphore.h>
44 #include <unistd.h>
45 #include "diskio.h"
46 #include <fcntl.h>
47
48 /* check for OS and include appropriate headers */
49 #if defined(__linux__)
50 #include "linux.h"
51 #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
52 #include "freebsd.h"
53 #elif defined(__OpenBSD__)
54 #include "openbsd.h"
55 #endif
56
57 /* folds a string over top of itself, like so:
58  *
59  * if start is "blah", and you call it with count = 1, the result will be "lah"
60  */
61 void strfold(char *start, int count)
62 {
63         char *curplace;
64         for (curplace = start + count; *curplace != 0; curplace++) {
65                 *(curplace - count) = *curplace;
66         }
67         *(curplace - count) = 0;
68 }
69
70 #ifndef HAVE_STRNDUP
71 // use our own strndup() if it's not available
72 char *strndup(const char *s, size_t n)
73 {
74         if (strlen(s) > n) {
75                 char *ret = malloc(n + 1);
76                 strncpy(ret, s, n);
77                 ret[n] = 0;
78                 return ret;
79         } else {
80                 return strdup(s);
81         }
82 }
83 #endif /* HAVE_STRNDUP */
84
85 void update_uname(void)
86 {
87         uname(&info.uname_s);
88 }
89
90 double get_time(void)
91 {
92         struct timeval tv;
93
94         gettimeofday(&tv, 0);
95         return tv.tv_sec + (tv.tv_usec / 1000000.0);
96 }
97
98 /* Converts '~/...' paths to '/home/blah/...' assumes that 'dest' is at least
99  * DEFAULT_TEXT_BUFFER_SIZE.  It's similar to variable_substitute, except only
100  * cheques for $HOME and ~/ in path */
101 void to_real_path(char *dest, const char *source)
102 {
103         char tmp[DEFAULT_TEXT_BUFFER_SIZE];
104         if (sscanf(source, "~/%s", tmp) || sscanf(source, "$HOME/%s", tmp)) {
105                 char *homedir = getenv("HOME");
106                 if (homedir) {
107                         snprintf(dest, DEFAULT_TEXT_BUFFER_SIZE, "%s/%s", homedir, tmp);
108                 } else {
109                         NORM_ERR("$HOME environment variable doesn't exist");
110                         strncpy(dest, source, DEFAULT_TEXT_BUFFER_SIZE);
111                 }
112         } else if (dest != source) {    //see changelog 2009-06-29 if you doubt that this check is necessary 
113                 strncpy(dest, source, DEFAULT_TEXT_BUFFER_SIZE);
114         }
115 }
116
117 int open_fifo(const char *file, int *reported)
118 {
119         char path[DEFAULT_TEXT_BUFFER_SIZE];
120         int fd = 0;
121
122         to_real_path(path, file);
123         fd = open(file, O_RDONLY | O_NONBLOCK);
124
125         if (fd == -1) {
126                 if (!reported || *reported == 0) {
127                         NORM_ERR("can't open %s: %s", file, strerror(errno));
128                         if (reported) {
129                                 *reported = 1;
130                         }
131                 }
132                 return -1;
133         }
134
135         return fd;
136 }
137
138 FILE *open_file(const char *file, int *reported)
139 {
140         char path[DEFAULT_TEXT_BUFFER_SIZE];
141         FILE *fp = 0;
142
143         to_real_path(path, file);
144         fp = fopen(file, "r");
145
146         if (!fp) {
147                 if (!reported || *reported == 0) {
148                         NORM_ERR("can't open %s: %s", file, strerror(errno));
149                         if (reported) {
150                                 *reported = 1;
151                         }
152                 }
153                 return NULL;
154         }
155
156         return fp;
157 }
158
159 void variable_substitute(const char *s, char *dest, unsigned int n)
160 {
161         while (*s && n > 1) {
162                 if (*s == '$') {
163                         s++;
164                         if (*s != '$') {
165                                 char buf[256];
166                                 const char *a, *var;
167                                 unsigned int len;
168
169                                 /* variable is either $foo or ${foo} */
170                                 if (*s == '{') {
171                                         s++;
172                                         a = s;
173                                         while (*s && *s != '}') {
174                                                 s++;
175                                         }
176                                 } else {
177                                         a = s;
178                                         while (*s && (isalnum((int) *s) || *s == '_')) {
179                                                 s++;
180                                         }
181                                 }
182
183                                 /* copy variable to buffer and look it up */
184                                 len = (s - a > 255) ? 255 : (s - a);
185                                 strncpy(buf, a, len);
186                                 buf[len] = '\0';
187
188                                 if (*s == '}') {
189                                         s++;
190                                 }
191
192                                 var = getenv(buf);
193
194                                 if (var) {
195                                         /* add var to dest */
196                                         len = strlen(var);
197                                         if (len >= n) {
198                                                 len = n - 1;
199                                         }
200                                         strncpy(dest, var, len);
201                                         dest += len;
202                                         n -= len;
203                                 }
204                                 continue;
205                         }
206                 }
207
208                 *dest++ = *s++;
209                 n--;
210         }
211
212         *dest = '\0';
213 }
214
215 void format_seconds(char *buf, unsigned int n, long seconds)
216 {
217         long days;
218         int hours, minutes;
219
220         days = seconds / 86400;
221         seconds %= 86400;
222         hours = seconds / 3600;
223         seconds %= 3600;
224         minutes = seconds / 60;
225         seconds %= 60;
226
227         if (days > 0) {
228                 snprintf(buf, n, "%ldd %dh %dm", days, hours, minutes);
229         } else {
230                 snprintf(buf, n, "%dh %dm %lds", hours, minutes, seconds);
231         }
232 }
233
234 void format_seconds_short(char *buf, unsigned int n, long seconds)
235 {
236         long days;
237         int hours, minutes;
238
239         days = seconds / 86400;
240         seconds %= 86400;
241         hours = seconds / 3600;
242         seconds %= 3600;
243         minutes = seconds / 60;
244         seconds %= 60;
245
246         if (days > 0) {
247                 snprintf(buf, n, "%ldd %dh", days, hours);
248         } else if (hours > 0) {
249                 snprintf(buf, n, "%dh %dm", hours, minutes);
250         } else {
251                 snprintf(buf, n, "%dm %lds", minutes, seconds);
252         }
253 }
254
255 /* Linked list containing the functions to call upon each update interval.
256  * Populated while initialising text objects in construct_text_object(). */
257 static struct update_cb {
258         struct update_cb *next;
259         void (*func)(void);
260         pthread_t thread;
261         sem_t start_wait, end_wait;
262
263     /* set to 1 when starting the thread
264          * set to 0 to request thread termination */
265         volatile char running;
266 } update_cb_head = {
267         .next = NULL,
268 };
269
270 static void *run_update_callback(void *);
271 static int threading_started;
272
273 /* Register an update callback. Don't allow duplicates, to minimise side
274  * effects and overhead. */
275 void add_update_callback(void (*func)(void))
276 {
277         struct update_cb *uc = &update_cb_head;
278
279         if (!func)
280                 return;
281
282         while (uc->next) {
283                 if (uc->next->func == func)
284                         return;
285                 uc = uc->next;
286         }
287
288         uc->next = malloc(sizeof(struct update_cb));
289         uc = uc->next;
290
291         memset(uc, 0, sizeof(struct update_cb));
292         uc->func = func;
293         sem_init(&uc->start_wait, 0, 0);
294         sem_init(&uc->end_wait, 0, 0);
295
296         if (threading_started) {
297                 uc->running = 1;
298                 pthread_create(&uc->thread, NULL, &run_update_callback, uc);
299         }
300 }
301
302 /* Free the list element uc and all decendants recursively. */
303 static void __free_update_callbacks(struct update_cb *uc)
304 {
305         if (uc->next)
306                 __free_update_callbacks(uc->next);
307
308         if (uc->running) {
309                 /* send cancellation request, then trigger and join the thread */
310                 uc->running = 0;
311                 sem_post(&uc->start_wait);
312                 pthread_join(uc->thread, NULL);
313         }
314
315         /* finally destroy the semaphores */
316         sem_destroy(&uc->start_wait);
317         sem_destroy(&uc->end_wait);
318
319         free(uc);
320 }
321
322 /* Free the whole list of update callbacks. */
323 void free_update_callbacks(void)
324 {
325         if (update_cb_head.next)
326                 __free_update_callbacks(update_cb_head.next);
327         update_cb_head.next = NULL;
328 }
329
330 /* We cannot start threads before we forked to background, because the threads
331  * would remain in the wrong process. Because of that, add_update_callback()
332  * doesn't create threads before start_update_threading() is called.
333  * start_update_threading() starts all threads previously registered, and sets a
334  * flag so that future threads are automagically started by
335  * add_update_callback().
336  * This text is almost longer than the actual code.
337  */
338 void start_update_threading(void)
339 {
340         struct update_cb *uc;
341
342         threading_started = 1;
343
344         for(uc = update_cb_head.next; uc; uc = uc->next) {
345                 uc->running = 1;
346                 pthread_create(&uc->thread, NULL, &run_update_callback, uc);
347         }
348 }
349
350 static void *run_update_callback(void *data)
351 {
352         struct update_cb *ucb = data;
353
354         while (1) {
355                 sem_wait(&ucb->start_wait);
356                 if (ucb->running == 0)
357                         return NULL;
358                 (*ucb->func)();
359                 sem_post(&ucb->end_wait);
360         }
361 }
362
363 int no_buffers;
364
365 void update_stuff(void)
366 {
367         int i;
368         struct update_cb *uc;
369
370         /* clear speeds and up status in case device was removed and doesn't get
371          * updated */
372
373         #ifdef HAVE_OPENMP
374         #pragma omp parallel for schedule(dynamic,10)
375         #endif /* HAVE_OPENMP */
376         for (i = 0; i < 16; i++) {
377                 if (netstats[i].dev) {
378                         netstats[i].up = 0;
379                         netstats[i].recv_speed = 0.0;
380                         netstats[i].trans_speed = 0.0;
381                 }
382         }
383
384         prepare_update();
385
386         for (uc = update_cb_head.next; uc; uc = uc->next)
387                 sem_post(&uc->start_wait);
388         /* need to synchronise here, otherwise locking is needed (as data
389          * would be printed with some update callbacks still running) */
390         for (uc = update_cb_head.next; uc; uc = uc->next)
391                 sem_wait(&uc->end_wait);
392
393         /* XXX: move the following into the update_meminfo() functions? */
394         if (no_buffers) {
395                 info.mem -= info.bufmem;
396                 info.memeasyfree += info.bufmem;
397         }
398 }
399
400 /* Ohkie to return negative values for temperatures */
401 int round_to_int_temp(float f)
402 {
403         if (f >= 0.0) {
404                 return (int) (f + 0.5);
405         } else {
406                 return (int) (f - 0.5);
407         }
408 }
409 /* Don't return negative values for cpugraph, bar, gauge, percentage.
410  * Causes unreasonable numbers to show */
411 unsigned int round_to_int(float f)
412 {
413         if (f >= 0.0) {
414                 return (int) (f + 0.5);
415         } else {
416                 return 0;
417         }
418 }
419