netcmd plugin: Added a first version of a control socket plugin.
[collectd.git] / src / netcmd.c
1 /**
2  * collectd - src/netcmd.c
3  * Copyright (C) 2007-2009  Florian octo Forster
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21  * DEALINGS IN THE SOFTWARE.
22  *
23  * Author:
24  *   Florian octo Forster <octo at verplant.org>
25  **/
26
27 #include "collectd.h"
28 #include "common.h"
29 #include "plugin.h"
30 #include "configfile.h"
31
32 #include "utils_cmd_flush.h"
33 #include "utils_cmd_getval.h"
34 #include "utils_cmd_listval.h"
35 #include "utils_cmd_putval.h"
36 #include "utils_cmd_putnotif.h"
37
38 /* Folks without pthread will need to disable this plugin. */
39 #include <pthread.h>
40
41 #include <sys/socket.h>
42 #include <sys/poll.h>
43 #include <netdb.h>
44 #include <sys/stat.h>
45 #include <sys/un.h>
46
47 #include <grp.h>
48
49 #define NC_DEFAULT_PORT "25826"
50
51 /*
52  * Private data structures
53  */
54 struct socket_entry_s
55 {
56         char *node;
57         char *service;
58         int fd;
59 };
60 typedef struct socket_entry_s socket_entry_t;
61
62 /*
63  * Private variables
64  */
65 /* valid configuration file keys */
66 static const char *config_keys[] =
67 {
68         "Listen",
69         "SocketPerms"
70 };
71 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
72
73 /* socket configuration */
74 static socket_entry_t *sockets = NULL;
75 static size_t          sockets_num;
76
77 static struct pollfd  *pollfd = NULL;
78 static size_t          pollfd_num;
79
80 static int       listen_thread_loop = 0;
81 static int       listen_thread_running = 0;
82 static pthread_t listen_thread;
83
84 static int unix_sock_perms = S_IRWXU | S_IRWXG;
85 static char *unix_sock_group = NULL;
86
87 static char **unix_sock_paths = NULL;
88 static size_t unix_sock_paths_num = 0;
89
90 /*
91  * Functions
92  */
93 static int nc_register_fd (int fd, const char *path) /* {{{ */
94 {
95         struct pollfd *tmp;
96
97         if (path != NULL)
98         {
99                 char **tmp;
100
101                 tmp = realloc (unix_sock_paths,
102                                 (unix_sock_paths_num + 1) * sizeof (*unix_sock_paths));
103                 if (tmp == NULL)
104                 {
105                         ERROR ("netcmd plugin: realloc failed.");
106                         return (-1);
107                 }
108                 unix_sock_paths = tmp;
109
110                 unix_sock_paths[unix_sock_paths_num] = sstrdup (path);
111                 unix_sock_paths_num++;
112         }
113
114         tmp = realloc (pollfd, (pollfd_num + 1) * sizeof (*pollfd));
115         if (tmp == NULL)
116         {
117                 ERROR ("netcmd plugin: realloc failed.");
118                 if (path != NULL)
119                 {
120                         unix_sock_paths_num--;
121                         sfree (unix_sock_paths[unix_sock_paths_num]);
122                 }
123
124                 return (-1);
125         }
126         pollfd = tmp;
127
128         memset (&pollfd[pollfd_num], 0, sizeof (pollfd[pollfd_num]));
129         pollfd[pollfd_num].fd = fd;
130         pollfd[pollfd_num].events = POLLIN | POLLPRI;
131         pollfd[pollfd_num].revents = 0;
132
133         pollfd_num++;
134
135         return (0);
136 } /* }}} int nc_register_fd */
137
138 static int nc_open_unix_socket (const char *path, /* {{{ */
139                 const char *group)
140 {
141         struct sockaddr_un sa;
142         int fd;
143         char errbuf[1024];
144         int status;
145
146         if (path == NULL)
147                 return (-1);
148
149         DEBUG ("netcmd plugin: nc_open_unix_socket (path = %s, group = %s);",
150                         (path != NULL) ? path : "(null)",
151                         (group != NULL) ? group : "(null)");
152         if (strncasecmp ("unix:", path, strlen ("unix:")) == 0)
153                 path += strlen ("unix:");
154
155         fd = socket (PF_UNIX, SOCK_STREAM, 0);
156         if (fd < 0)
157         {
158                 ERROR ("netcmd plugin: socket(2) failed: %s",
159                                 sstrerror (errno, errbuf, sizeof (errbuf)));
160                 return (-1);
161         }
162
163         memset (&sa, '\0', sizeof (sa));
164         sa.sun_family = AF_UNIX;
165         sstrncpy (sa.sun_path, path, sizeof (sa.sun_path));
166         /* unlink (sa.sun_path); */
167
168         DEBUG ("netcmd plugin: socket path = %s", sa.sun_path);
169
170         status = bind (fd, (struct sockaddr *) &sa, sizeof (sa));
171         if (status != 0)
172         {
173                 ERROR ("netcmd plugin: bind failed: %s",
174                                 sstrerror (errno, errbuf, sizeof (errbuf)));
175                 close (fd);
176                 fd = -1;
177                 return (-1);
178         }
179
180         /* FIXME: Copy unix_sock_perms stuff from unixsock. */
181         chmod (sa.sun_path, unix_sock_perms);
182
183         status = listen (fd, 8);
184         if (status != 0)
185         {
186                 ERROR ("netcmd plugin: listen failed: %s",
187                                 sstrerror (errno, errbuf, sizeof (errbuf)));
188                 close (fd);
189                 fd = -1;
190                 return (-1);
191         }
192
193         /* If `group' is not NULL, `chown' the file. */
194         while (group != NULL) /* {{{ */
195         {
196                 struct group *g;
197                 struct group sg;
198                 char grbuf[2048];
199
200                 g = NULL;
201                 status = getgrnam_r (group, &sg, grbuf, sizeof (grbuf), &g);
202                 if (status != 0)
203                 {
204                         WARNING ("netcmd plugin: getgrnam_r (%s) failed: %s", group,
205                                         sstrerror (errno, errbuf, sizeof (errbuf)));
206                         break;
207                 }
208
209                 if (g == NULL)
210                 {
211                         WARNING ("netcmd plugin: No such group: `%s'", group);
212                         break;
213                 }
214
215                 status = chown (sa.sun_path, (uid_t) -1, g->gr_gid);
216                 if (status != 0)
217                 {
218                         WARNING ("netcmd plugin: chown (%s, -1, %i) failed: %s",
219                                         sa.sun_path, (int) g->gr_gid,
220                                         sstrerror (errno, errbuf, sizeof (errbuf)));
221                 }
222
223                 break;
224         } /* }}} while (group != NULL) */
225
226         status = nc_register_fd (fd, sa.sun_path);
227         if (status != 0)
228         {
229                 close (fd);
230                 unlink (sa.sun_path);
231                 return (status);
232         }
233
234         return (0);
235 } /* }}} int nc_open_unix_socket */
236
237 static int nc_open_network_socket (const char *node, /* {{{ */
238                 const char *service)
239 {
240         struct addrinfo ai_hints;
241         struct addrinfo *ai_list;
242         struct addrinfo *ai_ptr;
243         int status;
244
245         DEBUG ("netcmd plugin: nc_open_network_socket (node = %s, service = %s);",
246                         (node != NULL) ? node : "(null)",
247                         (service != NULL) ? service : "(null)");
248
249         memset (&ai_hints, 0, sizeof (ai_hints));
250 #ifdef AI_PASSIVE
251         ai_hints.ai_flags |= AI_PASSIVE;
252 #endif
253 #ifdef AI_ADDRCONFIG
254         ai_hints.ai_flags |= AI_ADDRCONFIG;
255 #endif
256         ai_hints.ai_family = AF_UNSPEC;
257         ai_hints.ai_socktype = SOCK_STREAM;
258
259         ai_list = NULL;
260
261         if (service == NULL)
262                 service = NC_DEFAULT_PORT;
263
264         status = getaddrinfo (node, service, &ai_hints, &ai_list);
265         if (status != 0)
266         {
267                 ERROR ("netcmd plugin: getaddrinfo failed: %s",
268                                 gai_strerror (status));
269                 return (-1);
270         }
271
272         for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
273         {
274                 char errbuf[1024];
275                 int fd;
276
277                 fd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype,
278                                 ai_ptr->ai_protocol);
279                 if (fd < 0)
280                 {
281                         ERROR ("netcmd plugin: socket(2) failed: %s",
282                                         sstrerror (errno, errbuf, sizeof (errbuf)));
283                         continue;
284                 }
285
286                 status = bind (fd, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
287                 if (status != 0)
288                 {
289                         close (fd);
290                         ERROR ("netcmd plugin: bind(2) failed: %s",
291                                         sstrerror (errno, errbuf, sizeof (errbuf)));
292                         continue;
293                 }
294
295                 status = listen (fd, /* backlog = */ 8);
296                 if (status != 0)
297                 {
298                         close (fd);
299                         ERROR ("netcmd plugin: listen(2) failed: %s",
300                                         sstrerror (errno, errbuf, sizeof (errbuf)));
301                         continue;
302                 }
303
304                 status = nc_register_fd (fd, /* path = */ NULL);
305                 if (status != 0)
306                 {
307                         close (fd);
308                         continue;
309                 }
310         } /* for (ai_next) */
311
312         freeaddrinfo (ai_list);
313
314         return (0);
315 } /* }}} int nc_open_network_socket */
316
317 static int nc_open_socket (const char *node, /* {{{ */
318                 const char *service)
319 {
320         if ((node != NULL)
321                         && (strncasecmp ("unix:", node, strlen ("unix:")) == 0))
322         {
323                 return (nc_open_unix_socket (node, service));
324         }
325         else
326         {
327                 return (nc_open_network_socket (node, service));
328         }
329 } /* }}} int nc_open_socket */
330
331 static void *nc_handle_client (void *arg) /* {{{ */
332 {
333         int fd;
334         FILE *fhin, *fhout;
335         char errbuf[1024];
336
337         fd = *((int *) arg);
338         sfree (arg);
339
340         DEBUG ("netcmd plugin: nc_handle_client: Reading from fd #%i", fd);
341
342         fhin  = fdopen (fd, "r");
343         if (fhin == NULL)
344         {
345                 ERROR ("netcmd plugin: fdopen failed: %s",
346                                 sstrerror (errno, errbuf, sizeof (errbuf)));
347                 close (fd);
348                 pthread_exit ((void *) 1);
349         }
350
351         fhout = fdopen (fd, "w");
352         if (fhout == NULL)
353         {
354                 ERROR ("netcmd plugin: fdopen failed: %s",
355                                 sstrerror (errno, errbuf, sizeof (errbuf)));
356                 fclose (fhin); /* this closes fd as well */
357                 pthread_exit ((void *) 1);
358         }
359
360         /* change output buffer to line buffered mode */
361         if (setvbuf (fhout, NULL, _IOLBF, 0) != 0)
362         {
363                 ERROR ("netcmd plugin: setvbuf failed: %s",
364                                 sstrerror (errno, errbuf, sizeof (errbuf)));
365                 fclose (fhin);
366                 fclose (fhout);
367                 pthread_exit ((void *) 1);
368         }
369
370         while (42)
371         {
372                 char buffer[1024];
373                 char buffer_copy[1024];
374                 char *fields[128];
375                 int   fields_num;
376                 int   len;
377
378                 errno = 0;
379                 if (fgets (buffer, sizeof (buffer), fhin) == NULL)
380                 {
381                         if (errno != 0)
382                         {
383                                 WARNING ("netcmd plugin: failed to read from socket #%i: %s",
384                                                 fileno (fhin),
385                                                 sstrerror (errno, errbuf, sizeof (errbuf)));
386                         }
387                         break;
388                 }
389
390                 len = strlen (buffer);
391                 while ((len > 0)
392                                 && ((buffer[len - 1] == '\n') || (buffer[len - 1] == '\r')))
393                         buffer[--len] = '\0';
394
395                 if (len == 0)
396                         continue;
397
398                 sstrncpy (buffer_copy, buffer, sizeof (buffer_copy));
399
400                 fields_num = strsplit (buffer_copy, fields,
401                                 sizeof (fields) / sizeof (fields[0]));
402
403                 if (fields_num < 1)
404                 {
405                         close (fd);
406                         break;
407                 }
408
409                 if (strcasecmp (fields[0], "getval") == 0)
410                 {
411                         handle_getval (fhout, buffer);
412                 }
413                 else if (strcasecmp (fields[0], "putval") == 0)
414                 {
415                         handle_putval (fhout, buffer);
416                 }
417                 else if (strcasecmp (fields[0], "listval") == 0)
418                 {
419                         handle_listval (fhout, buffer);
420                 }
421                 else if (strcasecmp (fields[0], "putnotif") == 0)
422                 {
423                         handle_putnotif (fhout, buffer);
424                 }
425                 else if (strcasecmp (fields[0], "flush") == 0)
426                 {
427                         handle_flush (fhout, buffer);
428                 }
429                 else
430                 {
431                         if (fprintf (fhout, "-1 Unknown command: %s\n", fields[0]) < 0)
432                         {
433                                 WARNING ("netcmd plugin: failed to write to socket #%i: %s",
434                                                 fileno (fhout),
435                                                 sstrerror (errno, errbuf, sizeof (errbuf)));
436                                 break;
437                         }
438                 }
439         } /* while (fgets) */
440
441         DEBUG ("netcmd plugin: nc_handle_client: Exiting..");
442         fclose (fhin);
443         fclose (fhout);
444
445         pthread_exit ((void *) 0);
446         return ((void *) 0);
447 } /* }}} void *nc_handle_client */
448
449 static void *nc_server_thread (void __attribute__((unused)) *arg) /* {{{ */
450 {
451         int  status;
452         pthread_t th;
453         pthread_attr_t th_attr;
454         char errbuf[1024];
455         size_t i;
456
457         for (i = 0; i < sockets_num; i++)
458                 nc_open_socket (sockets[i].node, sockets[i].service);
459
460         if (sockets_num == 0)
461                 nc_open_socket (NULL, NULL);
462
463         if (pollfd_num == 0)
464         {
465                 ERROR ("netcmd plugin: No sockets could be opened.");
466                 pthread_exit ((void *) -1);
467         }
468
469         while (listen_thread_loop != 0)
470         {
471                 status = poll (pollfd, (nfds_t) pollfd_num, /* timeout = */ -1);
472                 if (status < 0)
473                 {
474                         if ((errno == EINTR) || (errno == EAGAIN))
475                                 continue;
476
477                         ERROR ("netcmd plugin: poll(2) failed: %s",
478                                         sstrerror (errno, errbuf, sizeof (errbuf)));
479                         listen_thread_loop = 0;
480                         continue;
481                 }
482
483                 for (i = 0; i < pollfd_num; i++)
484                 {
485                         int *client_fd;
486
487                         if (pollfd[i].revents == 0)
488                         {
489                                 continue;
490                         }
491                         else if ((pollfd[i].revents & (POLLERR | POLLHUP | POLLNVAL))
492                                         != 0)
493                         {
494                                 WARNING ("netcmd plugin: File descriptor %i failed.",
495                                                 pollfd[i].fd);
496                                 close (pollfd[i].fd);
497                                 pollfd[i].fd = -1;
498                                 pollfd[i].events = 0;
499                                 pollfd[i].revents = 0;
500                                 continue;
501                         }
502                         pollfd[i].revents = 0;
503
504                         status = accept (pollfd[i].fd,
505                                         /* sockaddr = */ NULL,
506                                         /* sockaddr_len = */ NULL);
507                         if (status < 0)
508                         {
509                                 if (errno == EINTR)
510                                         continue;
511
512                                 ERROR ("netcmd plugin: accept failed: %s",
513                                                 sstrerror (errno, errbuf, sizeof (errbuf)));
514                                 continue;
515                         }
516
517                         client_fd = malloc (sizeof (*client_fd));
518                         if (client_fd == NULL)
519                         {
520                                 ERROR ("netcmd plugin: malloc failed.");
521                                 close (status);
522                                 continue;
523                         }
524                         *client_fd = status;
525
526                         DEBUG ("Spawning child to handle connection on fd %i", *client_fd);
527
528                         pthread_attr_init (&th_attr);
529                         pthread_attr_setdetachstate (&th_attr, PTHREAD_CREATE_DETACHED);
530
531                         status = pthread_create (&th, &th_attr, nc_handle_client,
532                                         client_fd);
533                         if (status != 0)
534                         {
535                                 WARNING ("netcmd plugin: pthread_create failed: %s",
536                                                 sstrerror (errno, errbuf, sizeof (errbuf)));
537                                 close (*client_fd);
538                                 continue;
539                         }
540                 }
541         } /* while (listen_thread_loop) */
542
543         for (i = 0; i < pollfd_num; i++)
544         {
545                 if (pollfd[i].fd < 0)
546                         continue;
547
548                 close (pollfd[i].fd);
549                 pollfd[i].fd = -1;
550                 pollfd[i].events = 0;
551                 pollfd[i].revents = 0;
552         }
553
554         sfree (pollfd);
555         pollfd_num = 0;
556
557         for (i = 0; i < unix_sock_paths_num; i++)
558         {
559                 DEBUG ("netcmd plugin: Unlinking `%s'.",
560                                 unix_sock_paths[i]);
561                 unlink (unix_sock_paths[i]);
562                 sfree (unix_sock_paths[i]);
563         }
564         sfree (unix_sock_paths);
565
566         return ((void *) 0);
567 } /* }}} void *nc_server_thread */
568
569 static int nc_config (const char *key, const char *val)
570 {
571         if (strcasecmp ("Listen", key) == 0)
572         {
573                 socket_entry_t *tmp;
574
575                 tmp = realloc (sockets, (sockets_num + 1) * sizeof (*sockets));
576                 if (tmp == NULL)
577                 {
578                         ERROR ("netcmd plugin: realloc failed.");
579                         return (-1);
580                 }
581                 sockets = tmp;
582                 tmp = sockets + sockets_num;
583
584                 memset (tmp, 0, sizeof (*tmp));
585                 tmp->node = sstrdup (val);
586                 tmp->service = strchr (tmp->node, ' ');
587                 if (tmp->service != NULL)
588                 {
589                         while ((tmp->service[0] == ' ') || (tmp->service[0] == '\t'))
590                         {
591                                 tmp->service[0] = 0;
592                                 tmp->service++;
593                         }
594                         if (tmp->service[0] == 0)
595                                 tmp->service = NULL;
596                 }
597                 tmp->fd = -1;
598
599                 sockets_num++;
600         }
601         else if (strcasecmp (key, "SocketGroup") == 0)
602         {
603                 char *new_sock_group = strdup (val);
604                 if (new_sock_group == NULL)
605                         return (1);
606
607                 sfree (unix_sock_group);
608                 unix_sock_group = new_sock_group;
609         }
610         else if (strcasecmp (key, "SocketPerms") == 0)
611         {
612                 int tmp;
613
614                 errno = 0;
615                 tmp = (int) strtol (val, NULL, 8);
616                 if ((errno != 0) || (tmp == 0))
617                         return (1);
618                 unix_sock_perms = tmp;
619         }
620         else
621         {
622                 return (-1);
623         }
624
625         return (0);
626 } /* int nc_config */
627
628 static int nc_init (void)
629 {
630         static int have_init = 0;
631
632         int status;
633
634         /* Initialize only once. */
635         if (have_init != 0)
636                 return (0);
637         have_init = 1;
638
639         listen_thread_loop = 1;
640
641         status = pthread_create (&listen_thread, NULL, nc_server_thread, NULL);
642         if (status != 0)
643         {
644                 char errbuf[1024];
645                 listen_thread_loop = 0;
646                 listen_thread_running = 0;
647                 ERROR ("netcmd plugin: pthread_create failed: %s",
648                                 sstrerror (errno, errbuf, sizeof (errbuf)));
649                 return (-1);
650         }
651
652         listen_thread_running = 1;
653         return (0);
654 } /* int nc_init */
655
656 static int nc_shutdown (void)
657 {
658         void *ret;
659
660         listen_thread_loop = 0;
661
662         if (listen_thread != (pthread_t) 0)
663         {
664                 pthread_kill (listen_thread, SIGTERM);
665                 pthread_join (listen_thread, &ret);
666                 listen_thread = (pthread_t) 0;
667         }
668
669         plugin_unregister_init ("netcmd");
670         plugin_unregister_shutdown ("netcmd");
671
672         return (0);
673 } /* int nc_shutdown */
674
675 void module_register (void)
676 {
677         plugin_register_config ("netcmd", nc_config,
678                         config_keys, config_keys_num);
679         plugin_register_init ("netcmd", nc_init);
680         plugin_register_shutdown ("netcmd", nc_shutdown);
681 } /* void module_register (void) */
682
683 /* vim: set sw=4 ts=4 sts=4 tw=78 fdm=marker : */