netcmd plugin: Re-indented the entire file. Switched to "complex" config.
[collectd.git] / src / netcmd.c
1 /**
2  * collectd - src/netcmd.c
3  * Copyright (C) 2007-2011  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 collectd.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 nc_peer_s
55 {
56   char *node;
57   char *service;
58   int fd;
59 };
60 typedef struct nc_peer_s nc_peer_t;
61
62 /*
63  * Private variables
64  */
65
66 /* socket configuration */
67 static nc_peer_t *peers = NULL;
68 static size_t     peers_num;
69
70 static struct pollfd  *pollfd = NULL;
71 static size_t          pollfd_num;
72
73 static int       listen_thread_loop = 0;
74 static int       listen_thread_running = 0;
75 static pthread_t listen_thread;
76
77 /*
78  * Functions
79  */
80 static int nc_register_fd (int fd, const char *path) /* {{{ */
81 {
82   struct pollfd *tmp;
83
84   tmp = realloc (pollfd, (pollfd_num + 1) * sizeof (*pollfd));
85   if (tmp == NULL)
86   {
87     ERROR ("netcmd plugin: realloc failed.");
88     return (-1);
89   }
90   pollfd = tmp;
91
92   memset (&pollfd[pollfd_num], 0, sizeof (pollfd[pollfd_num]));
93   pollfd[pollfd_num].fd = fd;
94   pollfd[pollfd_num].events = POLLIN | POLLPRI;
95   pollfd[pollfd_num].revents = 0;
96
97   pollfd_num++;
98
99   return (0);
100 } /* }}} int nc_register_fd */
101
102 static int nc_open_socket (nc_peer_t *peer) /* {{{ */
103 {
104   struct addrinfo ai_hints;
105   struct addrinfo *ai_list;
106   struct addrinfo *ai_ptr;
107   int status;
108
109   const char *node = NULL;
110   const char *service = NULL;
111
112   if (peer != NULL)
113   {
114     node = peer->node;
115     service = peer->service;
116   }
117
118   if (service == NULL)
119     service = NC_DEFAULT_PORT;
120
121   memset (&ai_hints, 0, sizeof (ai_hints));
122 #ifdef AI_PASSIVE
123   ai_hints.ai_flags |= AI_PASSIVE;
124 #endif
125 #ifdef AI_ADDRCONFIG
126   ai_hints.ai_flags |= AI_ADDRCONFIG;
127 #endif
128   ai_hints.ai_family = AF_UNSPEC;
129   ai_hints.ai_socktype = SOCK_STREAM;
130
131   ai_list = NULL;
132
133   if (service == NULL)
134     service = NC_DEFAULT_PORT;
135
136   status = getaddrinfo (node, service, &ai_hints, &ai_list);
137   if (status != 0)
138   {
139     ERROR ("netcmd plugin: getaddrinfo failed: %s",
140         gai_strerror (status));
141     return (-1);
142   }
143
144   for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
145   {
146     char errbuf[1024];
147     int fd;
148
149     fd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype,
150         ai_ptr->ai_protocol);
151     if (fd < 0)
152     {
153       ERROR ("netcmd plugin: socket(2) failed: %s",
154           sstrerror (errno, errbuf, sizeof (errbuf)));
155       continue;
156     }
157
158     status = bind (fd, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
159     if (status != 0)
160     {
161       close (fd);
162       ERROR ("netcmd plugin: bind(2) failed: %s",
163           sstrerror (errno, errbuf, sizeof (errbuf)));
164       continue;
165     }
166
167     status = listen (fd, /* backlog = */ 8);
168     if (status != 0)
169     {
170       close (fd);
171       ERROR ("netcmd plugin: listen(2) failed: %s",
172           sstrerror (errno, errbuf, sizeof (errbuf)));
173       continue;
174     }
175
176     status = nc_register_fd (fd, /* path = */ NULL);
177     if (status != 0)
178     {
179       close (fd);
180       continue;
181     }
182   } /* for (ai_next) */
183
184   freeaddrinfo (ai_list);
185
186   return (0);
187 } /* }}} int nc_open_socket */
188
189 static void *nc_handle_client (void *arg) /* {{{ */
190 {
191   int fd;
192   FILE *fhin, *fhout;
193   char errbuf[1024];
194
195   fd = *((int *) arg);
196   sfree (arg);
197
198   DEBUG ("netcmd plugin: nc_handle_client: Reading from fd #%i", fd);
199
200   fhin  = fdopen (fd, "r");
201   if (fhin == NULL)
202   {
203     ERROR ("netcmd plugin: fdopen failed: %s",
204         sstrerror (errno, errbuf, sizeof (errbuf)));
205     close (fd);
206     pthread_exit ((void *) 1);
207   }
208
209   fhout = fdopen (fd, "w");
210   if (fhout == NULL)
211   {
212     ERROR ("netcmd plugin: fdopen failed: %s",
213         sstrerror (errno, errbuf, sizeof (errbuf)));
214     fclose (fhin); /* this closes fd as well */
215     pthread_exit ((void *) 1);
216   }
217
218   /* change output buffer to line buffered mode */
219   if (setvbuf (fhout, NULL, _IOLBF, 0) != 0)
220   {
221     ERROR ("netcmd plugin: setvbuf failed: %s",
222         sstrerror (errno, errbuf, sizeof (errbuf)));
223     fclose (fhin);
224     fclose (fhout);
225     pthread_exit ((void *) 1);
226   }
227
228   while (42)
229   {
230     char buffer[1024];
231     char buffer_copy[1024];
232     char *fields[128];
233     int   fields_num;
234     int   len;
235
236     errno = 0;
237     if (fgets (buffer, sizeof (buffer), fhin) == NULL)
238     {
239       if (errno != 0)
240       {
241         WARNING ("netcmd plugin: failed to read from socket #%i: %s",
242             fileno (fhin),
243             sstrerror (errno, errbuf, sizeof (errbuf)));
244       }
245       break;
246     }
247
248     len = strlen (buffer);
249     while ((len > 0)
250         && ((buffer[len - 1] == '\n') || (buffer[len - 1] == '\r')))
251       buffer[--len] = '\0';
252
253     if (len == 0)
254       continue;
255
256     sstrncpy (buffer_copy, buffer, sizeof (buffer_copy));
257
258     fields_num = strsplit (buffer_copy, fields,
259         sizeof (fields) / sizeof (fields[0]));
260
261     if (fields_num < 1)
262     {
263       close (fd);
264       break;
265     }
266
267     if (strcasecmp (fields[0], "getval") == 0)
268     {
269       handle_getval (fhout, buffer);
270     }
271     else if (strcasecmp (fields[0], "putval") == 0)
272     {
273       handle_putval (fhout, buffer);
274     }
275     else if (strcasecmp (fields[0], "listval") == 0)
276     {
277       handle_listval (fhout, buffer);
278     }
279     else if (strcasecmp (fields[0], "putnotif") == 0)
280     {
281       handle_putnotif (fhout, buffer);
282     }
283     else if (strcasecmp (fields[0], "flush") == 0)
284     {
285       handle_flush (fhout, buffer);
286     }
287     else
288     {
289       if (fprintf (fhout, "-1 Unknown command: %s\n", fields[0]) < 0)
290       {
291         WARNING ("netcmd plugin: failed to write to socket #%i: %s",
292             fileno (fhout),
293             sstrerror (errno, errbuf, sizeof (errbuf)));
294         break;
295       }
296     }
297   } /* while (fgets) */
298
299   DEBUG ("netcmd plugin: nc_handle_client: Exiting..");
300   fclose (fhin);
301   fclose (fhout);
302
303   pthread_exit ((void *) 0);
304   return ((void *) 0);
305 } /* }}} void *nc_handle_client */
306
307 static void *nc_server_thread (void __attribute__((unused)) *arg) /* {{{ */
308 {
309   int  status;
310   pthread_t th;
311   pthread_attr_t th_attr;
312   char errbuf[1024];
313   size_t i;
314
315   for (i = 0; i < peers_num; i++)
316     nc_open_socket (peers + i);
317
318   if (peers_num == 0)
319     nc_open_socket (NULL);
320
321   if (pollfd_num == 0)
322   {
323     ERROR ("netcmd plugin: No sockets could be opened.");
324     pthread_exit ((void *) -1);
325   }
326
327   while (listen_thread_loop != 0)
328   {
329     status = poll (pollfd, (nfds_t) pollfd_num, /* timeout = */ -1);
330     if (status < 0)
331     {
332       if ((errno == EINTR) || (errno == EAGAIN))
333         continue;
334
335       ERROR ("netcmd plugin: poll(2) failed: %s",
336           sstrerror (errno, errbuf, sizeof (errbuf)));
337       listen_thread_loop = 0;
338       continue;
339     }
340
341     for (i = 0; i < pollfd_num; i++)
342     {
343       int *client_fd;
344
345       if (pollfd[i].revents == 0)
346       {
347         continue;
348       }
349       else if ((pollfd[i].revents & (POLLERR | POLLHUP | POLLNVAL))
350           != 0)
351       {
352         WARNING ("netcmd plugin: File descriptor %i failed.",
353             pollfd[i].fd);
354         close (pollfd[i].fd);
355         pollfd[i].fd = -1;
356         pollfd[i].events = 0;
357         pollfd[i].revents = 0;
358         continue;
359       }
360       pollfd[i].revents = 0;
361
362       status = accept (pollfd[i].fd,
363           /* sockaddr = */ NULL,
364           /* sockaddr_len = */ NULL);
365       if (status < 0)
366       {
367         if (errno == EINTR)
368           continue;
369
370         ERROR ("netcmd plugin: accept failed: %s",
371             sstrerror (errno, errbuf, sizeof (errbuf)));
372         continue;
373       }
374
375       client_fd = malloc (sizeof (*client_fd));
376       if (client_fd == NULL)
377       {
378         ERROR ("netcmd plugin: malloc failed.");
379         close (status);
380         continue;
381       }
382       *client_fd = status;
383
384       DEBUG ("Spawning child to handle connection on fd %i", *client_fd);
385
386       pthread_attr_init (&th_attr);
387       pthread_attr_setdetachstate (&th_attr, PTHREAD_CREATE_DETACHED);
388
389       status = pthread_create (&th, &th_attr, nc_handle_client,
390           client_fd);
391       if (status != 0)
392       {
393         WARNING ("netcmd plugin: pthread_create failed: %s",
394             sstrerror (errno, errbuf, sizeof (errbuf)));
395         close (*client_fd);
396         continue;
397       }
398     }
399   } /* while (listen_thread_loop) */
400
401   for (i = 0; i < pollfd_num; i++)
402   {
403     if (pollfd[i].fd < 0)
404       continue;
405
406     close (pollfd[i].fd);
407     pollfd[i].fd = -1;
408     pollfd[i].events = 0;
409     pollfd[i].revents = 0;
410   }
411
412   sfree (pollfd);
413   pollfd_num = 0;
414
415   return ((void *) 0);
416 } /* }}} void *nc_server_thread */
417
418 /*
419  * <Plugin netcmd>
420  *   <Listen>
421  *     Address "::1"
422  *     Port "1234"
423  *     TLSCertFile "/path/to/cert"
424  *     TLSKeyFile  "/path/to/key"
425  *     TLSCAFile   "/path/to/ca"
426  *     TLSCRLFile  "/path/to/crl"
427  *     VerifyPeer yes|no
428  *   </Listen>
429  * </Plugin>
430  */
431 static int nc_config_peer (const oconfig_item_t *ci)
432 {
433   nc_peer_t *p;
434   int i;
435
436   p = realloc (peers, sizeof (*peers) * (peers_num + 1));
437   if (p == NULL)
438   {
439     ERROR ("netcmd plugin: realloc failed.");
440     return (ENOMEM);
441   }
442   peers = p;
443   p = peers + peers_num;
444   memset (p, 0, sizeof (*p));
445   p->node = NULL;
446   p->service = NULL;
447
448   for (i = 0; i < ci->children_num; i++)
449   {
450     oconfig_item_t *child = ci->children + i;
451
452     if (strcasecmp ("Address", child->key) == 0)
453       cf_util_get_string (child, &p->node);
454     else if (strcasecmp ("Port", child->key) == 0)
455       cf_util_get_string (child, &p->service);
456     else
457       WARNING ("netcmd plugin: The option \"%s\" is not recognized within "
458           "a \"%s\" block.", child->key, ci->key);
459   }
460
461   return (0);
462 } /* }}} int nc_config_peer */
463
464 static int nc_config (oconfig_item_t *ci)
465 {
466   int i;
467
468   for (i = 0; i < ci->children_num; i++)
469   {
470     oconfig_item_t *child = ci->children + i;
471
472     if (strcasecmp ("Listen", child->key) == 0)
473       nc_config_peer (child);
474     else
475       WARNING ("netcmd plugin: The option \"%s\" is not recognized.",
476           child->key);
477   }
478
479   return (0);
480 } /* int nc_config */
481
482 static int nc_init (void)
483 {
484   static int have_init = 0;
485
486   int status;
487
488   /* Initialize only once. */
489   if (have_init != 0)
490     return (0);
491   have_init = 1;
492
493   listen_thread_loop = 1;
494
495   status = pthread_create (&listen_thread, NULL, nc_server_thread, NULL);
496   if (status != 0)
497   {
498     char errbuf[1024];
499     listen_thread_loop = 0;
500     listen_thread_running = 0;
501     ERROR ("netcmd plugin: pthread_create failed: %s",
502         sstrerror (errno, errbuf, sizeof (errbuf)));
503     return (-1);
504   }
505
506   listen_thread_running = 1;
507   return (0);
508 } /* int nc_init */
509
510 static int nc_shutdown (void)
511 {
512   void *ret;
513
514   listen_thread_loop = 0;
515
516   if (listen_thread != (pthread_t) 0)
517   {
518     pthread_kill (listen_thread, SIGTERM);
519     pthread_join (listen_thread, &ret);
520     listen_thread = (pthread_t) 0;
521   }
522
523   plugin_unregister_init ("netcmd");
524   plugin_unregister_shutdown ("netcmd");
525
526   return (0);
527 } /* int nc_shutdown */
528
529 void module_register (void)
530 {
531   plugin_register_complex_config ("netcmd", nc_config);
532   plugin_register_init ("netcmd", nc_init);
533   plugin_register_shutdown ("netcmd", nc_shutdown);
534 } /* void module_register (void) */
535
536 /* vim: set sw=2 sts=2 tw=78 et fdm=marker : */