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