Merge branch 'collectd-4.3'
[collectd.git] / src / unixsock.c
1 /**
2  * collectd - src/unixsock.c
3  * Copyright (C) 2007,2008  Florian octo Forster
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation; only version 2 of the License is applicable.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
17  *
18  * Author:
19  *   Florian octo Forster <octo at verplant.org>
20  **/
21
22 #include "collectd.h"
23 #include "common.h"
24 #include "plugin.h"
25 #include "configfile.h"
26
27 #include "utils_cmd_getval.h"
28 #include "utils_cmd_putval.h"
29 #include "utils_cmd_putnotif.h"
30
31 /* Folks without pthread will need to disable this plugin. */
32 #include <pthread.h>
33
34 #include <sys/socket.h>
35 #include <sys/stat.h>
36 #include <sys/un.h>
37
38 #include <grp.h>
39
40 #ifndef UNIX_PATH_MAX
41 # define UNIX_PATH_MAX sizeof (((struct sockaddr_un *)0)->sun_path)
42 #endif
43
44 #define US_DEFAULT_PATH LOCALSTATEDIR"/run/"PACKAGE_NAME"-unixsock"
45
46 /*
47  * Private data structures
48  */
49 /* linked list of cached values */
50 typedef struct value_cache_s
51 {
52         char       name[4*DATA_MAX_NAME_LEN];
53         int        values_num;
54         gauge_t   *gauge;
55         counter_t *counter;
56         const data_set_t *ds;
57         time_t     time;
58         struct value_cache_s *next;
59 } value_cache_t;
60
61 /*
62  * Private variables
63  */
64 /* valid configuration file keys */
65 static const char *config_keys[] =
66 {
67         "SocketFile",
68         "SocketGroup",
69         "SocketPerms",
70         NULL
71 };
72 static int config_keys_num = 3;
73
74 static int loop = 0;
75
76 /* socket configuration */
77 static int   sock_fd    = -1;
78 static char *sock_file  = NULL;
79 static char *sock_group = NULL;
80 static int   sock_perms = S_IRWXU | S_IRWXG;
81
82 static pthread_t listen_thread = (pthread_t) 0;
83
84 /* Linked list and auxilliary variables for saving values */
85 static value_cache_t   *cache_head = NULL;
86 static pthread_mutex_t  cache_lock = PTHREAD_MUTEX_INITIALIZER;
87 static time_t           cache_oldest = -1;
88
89 /*
90  * Functions
91  */
92 static value_cache_t *cache_search (const char *name)
93 {
94         value_cache_t *vc;
95
96         for (vc = cache_head; vc != NULL; vc = vc->next)
97         {
98                 if (strcmp (vc->name, name) == 0)
99                         break;
100         } /* for vc = cache_head .. NULL */
101
102         return (vc);
103 } /* value_cache_t *cache_search */
104
105 static int cache_insert (const data_set_t *ds, const value_list_t *vl)
106 {
107         /* We're called from `cache_update' so we don't need to lock the mutex */
108         value_cache_t *vc;
109         int i;
110
111         DEBUG ("unixsock plugin: cache_insert: ds->type = %s; ds->ds_num = %i;"
112                         " vl->values_len = %i;",
113                         ds->type, ds->ds_num, vl->values_len);
114 #if COLLECT_DEBUG
115         assert (ds->ds_num == vl->values_len);
116 #else
117         if (ds->ds_num != vl->values_len)
118         {
119                 ERROR ("unixsock plugin: ds->type = %s: (ds->ds_num = %i) != "
120                                 "(vl->values_len = %i)",
121                                 ds->type, ds->ds_num, vl->values_len);
122                 return (-1);
123         }
124 #endif
125
126         vc = (value_cache_t *) malloc (sizeof (value_cache_t));
127         if (vc == NULL)
128         {
129                 char errbuf[1024];
130                 pthread_mutex_unlock (&cache_lock);
131                 ERROR ("unixsock plugin: malloc failed: %s",
132                                 sstrerror (errno, errbuf, sizeof (errbuf)));
133                 return (-1);
134         }
135
136         vc->gauge = (gauge_t *) malloc (sizeof (gauge_t) * vl->values_len);
137         if (vc->gauge == NULL)
138         {
139                 char errbuf[1024];
140                 pthread_mutex_unlock (&cache_lock);
141                 ERROR ("unixsock plugin: malloc failed: %s",
142                                 sstrerror (errno, errbuf, sizeof (errbuf)));
143                 free (vc);
144                 return (-1);
145         }
146
147         vc->counter = (counter_t *) malloc (sizeof (counter_t) * vl->values_len);
148         if (vc->counter == NULL)
149         {
150                 char errbuf[1024];
151                 pthread_mutex_unlock (&cache_lock);
152                 ERROR ("unixsock plugin: malloc failed: %s",
153                                 sstrerror (errno, errbuf, sizeof (errbuf)));
154                 free (vc->gauge);
155                 free (vc);
156                 return (-1);
157         }
158
159         if (FORMAT_VL (vc->name, sizeof (vc->name), vl, ds))
160         {
161                 pthread_mutex_unlock (&cache_lock);
162                 ERROR ("unixsock plugin: FORMAT_VL failed.");
163                 free (vc->counter);
164                 free (vc->gauge);
165                 free (vc);
166                 return (-1);
167         }
168
169         for (i = 0; i < ds->ds_num; i++)
170         {
171                 if (ds->ds[i].type == DS_TYPE_COUNTER)
172                 {
173                         vc->gauge[i] = 0.0;
174                         vc->counter[i] = vl->values[i].counter;
175                 }
176                 else if (ds->ds[i].type == DS_TYPE_GAUGE)
177                 {
178                         vc->gauge[i] = vl->values[i].gauge;
179                         vc->counter[i] = 0;
180                 }
181                 else
182                 {
183                         vc->gauge[i] = 0.0;
184                         vc->counter[i] = 0;
185                 }
186         }
187         vc->values_num = ds->ds_num;
188         vc->ds = ds;
189
190         vc->next = cache_head;
191         cache_head = vc;
192
193         vc->time = vl->time;
194         if ((vc->time < cache_oldest) || (-1 == cache_oldest))
195                 cache_oldest = vc->time;
196
197         pthread_mutex_unlock (&cache_lock);
198         return (0);
199 } /* int cache_insert */
200
201 static int cache_update (const data_set_t *ds, const value_list_t *vl)
202 {
203         char name[4*DATA_MAX_NAME_LEN];;
204         value_cache_t *vc;
205         int i;
206
207         if (FORMAT_VL (name, sizeof (name), vl, ds) != 0)
208                 return (-1);
209
210         pthread_mutex_lock (&cache_lock);
211
212         vc = cache_search (name);
213
214         /* pthread_mutex_lock is called by cache_insert. */
215         if (vc == NULL)
216                 return (cache_insert (ds, vl));
217
218         assert (vc->values_num == ds->ds_num);
219         assert (vc->values_num == vl->values_len);
220
221         /* Avoid floating-point exceptions due to division by zero. */
222         if (vc->time >= vl->time)
223         {
224                 pthread_mutex_unlock (&cache_lock);
225                 ERROR ("unixsock plugin: vc->time >= vl->time. vc->time = %u; "
226                                 "vl->time = %u; vl = %s;",
227                                 (unsigned int) vc->time, (unsigned int) vl->time,
228                                 name);
229                 return (-1);
230         } /* if (vc->time >= vl->time) */
231
232         /*
233          * Update the values. This is possibly a lot more that you'd expect
234          * because we honor min and max values and handle counter overflows here.
235          */
236         for (i = 0; i < ds->ds_num; i++)
237         {
238                 if (ds->ds[i].type == DS_TYPE_COUNTER)
239                 {
240                         if (vl->values[i].counter < vc->counter[i])
241                         {
242                                 if (vl->values[i].counter <= 4294967295U)
243                                 {
244                                         vc->gauge[i] = ((4294967295U - vl->values[i].counter)
245                                                         + vc->counter[i]) / (vl->time - vc->time);
246                                 }
247                                 else
248                                 {
249                                         vc->gauge[i] = ((18446744073709551615ULL - vl->values[i].counter)
250                                                 + vc->counter[i]) / (vl->time - vc->time);
251                                 }
252                         }
253                         else
254                         {
255                                 vc->gauge[i] = (vl->values[i].counter - vc->counter[i])
256                                         / (vl->time - vc->time);
257                         }
258
259                         vc->counter[i] = vl->values[i].counter;
260                 }
261                 else if (ds->ds[i].type == DS_TYPE_GAUGE)
262                 {
263                         vc->gauge[i] = vl->values[i].gauge;
264                         vc->counter[i] = 0;
265                 }
266                 else
267                 {
268                         vc->gauge[i] = NAN;
269                         vc->counter[i] = 0;
270                 }
271
272                 if (isnan (vc->gauge[i])
273                                 || (!isnan (ds->ds[i].min) && (vc->gauge[i] < ds->ds[i].min))
274                                 || (!isnan (ds->ds[i].max) && (vc->gauge[i] > ds->ds[i].max)))
275                         vc->gauge[i] = NAN;
276         } /* for i = 0 .. ds->ds_num */
277
278         vc->ds = ds;
279         vc->time = vl->time;
280
281         if ((vc->time < cache_oldest) || (-1 == cache_oldest))
282                 cache_oldest = vc->time;
283
284         pthread_mutex_unlock (&cache_lock);
285         return (0);
286 } /* int cache_update */
287
288 static void cache_flush (int max_age)
289 {
290         value_cache_t *this;
291         value_cache_t *prev;
292         time_t now;
293
294         pthread_mutex_lock (&cache_lock);
295
296         now = time (NULL);
297
298         if ((now - cache_oldest) <= max_age)
299         {
300                 pthread_mutex_unlock (&cache_lock);
301                 return;
302         }
303         
304         cache_oldest = now;
305
306         prev = NULL;
307         this = cache_head;
308
309         while (this != NULL)
310         {
311                 if ((now - this->time) <= max_age)
312                 {
313                         if (this->time < cache_oldest)
314                                 cache_oldest = this->time;
315
316                         prev = this;
317                         this = this->next;
318                         continue;
319                 }
320
321                 if (prev == NULL)
322                         cache_head = this->next;
323                 else
324                         prev->next = this->next;
325
326                 free (this->gauge);
327                 free (this->counter);
328                 free (this);
329
330                 if (prev == NULL)
331                         this = cache_head;
332                 else
333                         this = prev->next;
334         } /* while (this != NULL) */
335
336         pthread_mutex_unlock (&cache_lock);
337 } /* void cache_flush */
338
339 static int us_open_socket (void)
340 {
341         struct sockaddr_un sa;
342         int status;
343
344         sock_fd = socket (PF_UNIX, SOCK_STREAM, 0);
345         if (sock_fd < 0)
346         {
347                 char errbuf[1024];
348                 ERROR ("unixsock plugin: socket failed: %s",
349                                 sstrerror (errno, errbuf, sizeof (errbuf)));
350                 return (-1);
351         }
352
353         memset (&sa, '\0', sizeof (sa));
354         sa.sun_family = AF_UNIX;
355         strncpy (sa.sun_path, (sock_file != NULL) ? sock_file : US_DEFAULT_PATH,
356                         sizeof (sa.sun_path) - 1);
357         /* unlink (sa.sun_path); */
358
359         DEBUG ("unixsock plugin: socket path = %s", sa.sun_path);
360
361         status = bind (sock_fd, (struct sockaddr *) &sa, sizeof (sa));
362         if (status != 0)
363         {
364                 char errbuf[1024];
365                 sstrerror (errno, errbuf, sizeof (errbuf));
366                 ERROR ("unixsock plugin: bind failed: %s", errbuf);
367                 close (sock_fd);
368                 sock_fd = -1;
369                 return (-1);
370         }
371
372         chmod (sa.sun_path, sock_perms);
373
374         status = listen (sock_fd, 8);
375         if (status != 0)
376         {
377                 char errbuf[1024];
378                 ERROR ("unixsock plugin: listen failed: %s",
379                                 sstrerror (errno, errbuf, sizeof (errbuf)));
380                 close (sock_fd);
381                 sock_fd = -1;
382                 return (-1);
383         }
384
385         do
386         {
387                 char *grpname;
388                 struct group *g;
389                 struct group sg;
390                 char grbuf[2048];
391
392                 grpname = (sock_group != NULL) ? sock_group : COLLECTD_GRP_NAME;
393                 g = NULL;
394
395                 status = getgrnam_r (grpname, &sg, grbuf, sizeof (grbuf), &g);
396                 if (status != 0)
397                 {
398                         char errbuf[1024];
399                         WARNING ("unixsock plugin: getgrnam_r (%s) failed: %s", grpname,
400                                         sstrerror (errno, errbuf, sizeof (errbuf)));
401                         break;
402                 }
403                 if (g == NULL)
404                 {
405                         WARNING ("unixsock plugin: No such group: `%s'",
406                                         grpname);
407                         break;
408                 }
409
410                 if (chown ((sock_file != NULL) ? sock_file : US_DEFAULT_PATH,
411                                         (uid_t) -1, g->gr_gid) != 0)
412                 {
413                         char errbuf[1024];
414                         WARNING ("unixsock plugin: chown (%s, -1, %i) failed: %s",
415                                         (sock_file != NULL) ? sock_file : US_DEFAULT_PATH,
416                                         (int) g->gr_gid,
417                                         sstrerror (errno, errbuf, sizeof (errbuf)));
418                 }
419         } while (0);
420
421         return (0);
422 } /* int us_open_socket */
423
424 static int us_handle_listval (FILE *fh, char **fields, int fields_num)
425 {
426         char buffer[1024];
427         char **value_list = NULL;
428         int value_list_len = 0;
429         value_cache_t *entry;
430         int i;
431
432         if (fields_num != 1)
433         {
434                 DEBUG ("unixsock plugin: us_handle_listval: "
435                                 "Wrong number of fields: %i", fields_num);
436                 fprintf (fh, "-1 Wrong number of fields: Got %i, expected 1.\n",
437                                 fields_num);
438                 fflush (fh);
439                 return (-1);
440         }
441
442         pthread_mutex_lock (&cache_lock);
443
444         for (entry = cache_head; entry != NULL; entry = entry->next)
445         {
446                 char **tmp;
447
448                 snprintf (buffer, sizeof (buffer), "%u %s\n",
449                                 (unsigned int) entry->time, entry->name);
450                 buffer[sizeof (buffer) - 1] = '\0';
451                 
452                 tmp = realloc (value_list, sizeof (char *) * (value_list_len + 1));
453                 if (tmp == NULL)
454                         continue;
455                 value_list = tmp;
456
457                 value_list[value_list_len] = strdup (buffer);
458
459                 if (value_list[value_list_len] != NULL)
460                         value_list_len++;
461         } /* for (entry) */
462
463         pthread_mutex_unlock (&cache_lock);
464
465         DEBUG ("unixsock plugin: us_handle_listval: value_list_len = %i", value_list_len);
466         fprintf (fh, "%i Values found\n", value_list_len);
467         for (i = 0; i < value_list_len; i++)
468                 fputs (value_list[i], fh);
469         fflush (fh);
470
471         return (0);
472 } /* int us_handle_listval */
473
474 static void *us_handle_client (void *arg)
475 {
476         int fd;
477         FILE *fh;
478         char buffer[1024];
479         char *fields[128];
480         int   fields_num;
481
482         fd = *((int *) arg);
483         free (arg);
484         arg = NULL;
485
486         DEBUG ("Reading from fd #%i", fd);
487
488         fh = fdopen (fd, "r+");
489         if (fh == NULL)
490         {
491                 char errbuf[1024];
492                 ERROR ("unixsock plugin: fdopen failed: %s",
493                                 sstrerror (errno, errbuf, sizeof (errbuf)));
494                 close (fd);
495                 pthread_exit ((void *) 1);
496         }
497
498         while (fgets (buffer, sizeof (buffer), fh) != NULL)
499         {
500                 int len;
501
502                 len = strlen (buffer);
503                 while ((len > 0)
504                                 && ((buffer[len - 1] == '\n') || (buffer[len - 1] == '\r')))
505                         buffer[--len] = '\0';
506
507                 if (len == 0)
508                         continue;
509
510                 DEBUG ("fgets -> buffer = %s; len = %i;", buffer, len);
511
512                 fields_num = strsplit (buffer, fields,
513                                 sizeof (fields) / sizeof (fields[0]));
514
515                 if (fields_num < 1)
516                 {
517                         close (fd);
518                         break;
519                 }
520
521                 if (strcasecmp (fields[0], "getval") == 0)
522                 {
523                         handle_getval (fh, fields, fields_num);
524                 }
525                 else if (strcasecmp (fields[0], "putval") == 0)
526                 {
527                         handle_putval (fh, fields, fields_num);
528                 }
529                 else if (strcasecmp (fields[0], "listval") == 0)
530                 {
531                         us_handle_listval (fh, fields, fields_num);
532                 }
533                 else if (strcasecmp (fields[0], "putnotif") == 0)
534                 {
535                         handle_putnotif (fh, fields, fields_num);
536                 }
537                 else
538                 {
539                         fprintf (fh, "-1 Unknown command: %s\n", fields[0]);
540                         fflush (fh);
541                 }
542         } /* while (fgets) */
543
544         DEBUG ("Exiting..");
545         close (fd);
546
547         pthread_exit ((void *) 0);
548 } /* void *us_handle_client */
549
550 static void *us_server_thread (void *arg)
551 {
552         int  status;
553         int *remote_fd;
554         pthread_t th;
555         pthread_attr_t th_attr;
556
557         if (us_open_socket () != 0)
558                 pthread_exit ((void *) 1);
559
560         while (loop != 0)
561         {
562                 DEBUG ("unixsock plugin: Calling accept..");
563                 status = accept (sock_fd, NULL, NULL);
564                 if (status < 0)
565                 {
566                         char errbuf[1024];
567
568                         if (errno == EINTR)
569                                 continue;
570
571                         ERROR ("unixsock plugin: accept failed: %s",
572                                         sstrerror (errno, errbuf, sizeof (errbuf)));
573                         close (sock_fd);
574                         sock_fd = -1;
575                         pthread_exit ((void *) 1);
576                 }
577
578                 remote_fd = (int *) malloc (sizeof (int));
579                 if (remote_fd == NULL)
580                 {
581                         char errbuf[1024];
582                         WARNING ("unixsock plugin: malloc failed: %s",
583                                         sstrerror (errno, errbuf, sizeof (errbuf)));
584                         close (status);
585                         continue;
586                 }
587                 *remote_fd = status;
588
589                 DEBUG ("Spawning child to handle connection on fd #%i", *remote_fd);
590
591                 pthread_attr_init (&th_attr);
592                 pthread_attr_setdetachstate (&th_attr, PTHREAD_CREATE_DETACHED);
593
594                 status = pthread_create (&th, &th_attr, us_handle_client, (void *) remote_fd);
595                 if (status != 0)
596                 {
597                         char errbuf[1024];
598                         WARNING ("unixsock plugin: pthread_create failed: %s",
599                                         sstrerror (errno, errbuf, sizeof (errbuf)));
600                         close (*remote_fd);
601                         free (remote_fd);
602                         continue;
603                 }
604         } /* while (loop) */
605
606         close (sock_fd);
607         sock_fd = -1;
608
609         status = unlink ((sock_file != NULL) ? sock_file : US_DEFAULT_PATH);
610         if (status != 0)
611         {
612                 char errbuf[1024];
613                 NOTICE ("unixsock plugin: unlink (%s) failed: %s",
614                                 (sock_file != NULL) ? sock_file : US_DEFAULT_PATH,
615                                 sstrerror (errno, errbuf, sizeof (errbuf)));
616         }
617
618         return ((void *) 0);
619 } /* void *us_server_thread */
620
621 static int us_config (const char *key, const char *val)
622 {
623         if (strcasecmp (key, "SocketFile") == 0)
624         {
625                 sfree (sock_file);
626                 sock_file = strdup (val);
627         }
628         else if (strcasecmp (key, "SocketGroup") == 0)
629         {
630                 sfree (sock_group);
631                 sock_group = strdup (val);
632         }
633         else if (strcasecmp (key, "SocketPerms") == 0)
634         {
635                 sock_perms = (int) strtol (val, NULL, 8);
636         }
637         else
638         {
639                 return (-1);
640         }
641
642         return (0);
643 } /* int us_config */
644
645 static int us_init (void)
646 {
647         int status;
648
649         loop = 1;
650
651         status = pthread_create (&listen_thread, NULL, us_server_thread, NULL);
652         if (status != 0)
653         {
654                 char errbuf[1024];
655                 ERROR ("unixsock plugin: pthread_create failed: %s",
656                                 sstrerror (errno, errbuf, sizeof (errbuf)));
657                 return (-1);
658         }
659
660         return (0);
661 } /* int us_init */
662
663 static int us_shutdown (void)
664 {
665         void *ret;
666
667         loop = 0;
668
669         if (listen_thread != (pthread_t) 0)
670         {
671                 pthread_kill (listen_thread, SIGTERM);
672                 pthread_join (listen_thread, &ret);
673                 listen_thread = (pthread_t) 0;
674         }
675
676         plugin_unregister_init ("unixsock");
677         plugin_unregister_write ("unixsock");
678         plugin_unregister_shutdown ("unixsock");
679
680         return (0);
681 } /* int us_shutdown */
682
683 static int us_write (const data_set_t *ds, const value_list_t *vl)
684 {
685         cache_update (ds, vl);
686         cache_flush (2 * interval_g);
687
688         return (0);
689 }
690
691 void module_register (void)
692 {
693         plugin_register_config ("unixsock", us_config,
694                         config_keys, config_keys_num);
695         plugin_register_init ("unixsock", us_init);
696         plugin_register_write ("unixsock", us_write);
697         plugin_register_shutdown ("unixsock", us_shutdown);
698 } /* void module_register (void) */
699
700 /* vim: set sw=4 ts=4 sts=4 tw=78 : */