qmail plugin: Made the counting of files more generalized.
[collectd.git] / src / qmail.c
1 /**
2  * collectd - src/qmail.c
3  * Copyright (C) 2008  Alessandro Iurlano
4  * Copyright (C) 2008  Florian octo Forster
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the
8  * Free Software Foundation; only version 2 of the License is applicable.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
18  *
19  * Authors:
20  *   Alessandro Iurlano <alessandro.iurlano at gmail.com>
21  *   Florian octo Forster <octo at verplant.org>
22  **/
23
24 #include "collectd.h"
25 #include "common.h"
26 #include "plugin.h"       
27
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <fcntl.h>
31 #include <dirent.h>
32 #include <fnmatch.h>
33
34 struct fc_directory_conf_s
35 {
36   char *path;
37   char *instance;
38
39   /* Data counters */
40   uint64_t files_num;
41   uint64_t files_size;
42
43   /* Selectors */
44   char *name;
45   int64_t mtime;
46   int64_t size;
47
48   /* Helper for the recursive functions */
49   time_t now;
50 };
51 typedef struct fc_directory_conf_s fc_directory_conf_t;
52
53 static fc_directory_conf_t **directories = NULL;
54 static size_t directories_num = 0;
55
56 static void fc_submit_dir (const fc_directory_conf_t *dir)
57 {
58   value_t values[1];
59   value_list_t vl = VALUE_LIST_INIT;
60
61   values[0].gauge = (gauge_t) dir->files_num;
62
63   vl.values = values;
64   vl.values_len = STATIC_ARRAY_SIZE (values);
65   vl.time = time (NULL);
66   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
67   sstrncpy (vl.plugin, "qmail", sizeof (vl.plugin));
68   sstrncpy (vl.plugin_instance, dir->instance, sizeof (vl.plugin_instance));
69   sstrncpy (vl.type, "files", sizeof (vl.type));
70
71   plugin_dispatch_values (&vl);
72
73   values[0].gauge = (gauge_t) dir->files_size;
74   sstrncpy (vl.type, "bytes", sizeof (vl.type));
75
76   plugin_dispatch_values (&vl);
77 } /* void qmail_submit */
78
79 /*
80  * Config:
81  * <Plugin qmail>
82  *   <Directory /path/to/dir>
83  *     Instance "foobar"
84  *     Name "*.conf"
85  *     MTime -3600
86  *     Size "+10M"
87  *   </Directory>
88  * </Plugin>
89  *
90  * Collect:
91  * - Number of files
92  * - Total size
93  */
94
95 static int fc_config_set_instance (fc_directory_conf_t *dir, const char *str)
96 {
97   char buffer[1024];
98   char *ptr;
99   char *copy;
100
101   strncpy (buffer, str, sizeof (buffer));
102   for (ptr = buffer; *ptr != 0; ptr++)
103     if (*ptr == '/')
104       *ptr = '-';
105
106   for (ptr = buffer; *ptr == '-'; ptr++)
107     /* do nothing */;
108
109   if (*ptr == 0)
110     return (-1);
111
112   copy = strdup (ptr);
113   if (copy == NULL)
114     return (-1);
115
116   sfree (dir->instance);
117   dir->instance = copy;
118
119   return (0);
120 } /* int fc_config_set_instance */
121
122 static int fc_config_add_dir_instance (fc_directory_conf_t *dir,
123     oconfig_item_t *ci)
124 {
125   if ((ci->values_num != 1)
126       || (ci->values[0].type != OCONFIG_TYPE_STRING))
127   {
128     WARNING ("qmail plugin: The `Instance' config option needs exactly one "
129         "string argument.");
130     return (-1);
131   }
132
133   return (fc_config_set_instance (dir, ci->values[0].value.string));
134 } /* int fc_config_add_dir_instance */
135
136 static int fc_config_add_dir_name (fc_directory_conf_t *dir,
137     oconfig_item_t *ci)
138 {
139   char *temp;
140
141   if ((ci->values_num != 1)
142       || (ci->values[0].type != OCONFIG_TYPE_STRING))
143   {
144     WARNING ("qmail plugin: The `Name' config option needs exactly one "
145         "string argument.");
146     return (-1);
147   }
148
149   temp = strdup (ci->values[0].value.string);
150   if (temp == NULL)
151   {
152     ERROR ("qmail plugin: strdup failed.");
153     return (-1);
154   }
155
156   sfree (dir->name);
157   dir->name = temp;
158
159   return (0);
160 } /* int fc_config_add_dir_name */
161
162 static int fc_config_add_dir_mtime (fc_directory_conf_t *dir,
163     oconfig_item_t *ci)
164 {
165   char *endptr;
166   double temp;
167
168   if ((ci->values_num != 1)
169       || ((ci->values[0].type != OCONFIG_TYPE_STRING)
170         && (ci->values[0].type != OCONFIG_TYPE_NUMBER)))
171   {
172     WARNING ("qmail plugin: The `MTime' config option needs exactly one "
173         "string or numeric argument.");
174     return (-1);
175   }
176
177   if (ci->values[0].type == OCONFIG_TYPE_NUMBER)
178   {
179     dir->mtime = (int64_t) ci->values[0].value.number;
180     return (0);
181   }
182
183   errno = 0;
184   endptr = NULL;
185   temp = strtod (ci->values[0].value.string, &endptr);
186   if ((errno != 0) || (endptr == NULL)
187       || (endptr == ci->values[0].value.string))
188   {
189     WARNING ("qmail plugin: Converting `%s' to a number failed.",
190         ci->values[0].value.string);
191     return (-1);
192   }
193
194   switch (*endptr)
195   {
196     case 0:
197     case 's':
198     case 'S':
199       break;
200
201     case 'm':
202     case 'M':
203       temp *= 60;
204       break;
205
206     case 'h':
207     case 'H':
208       temp *= 3600;
209       break;
210
211     case 'd':
212     case 'D':
213       temp *= 86400;
214       break;
215
216     case 'w':
217     case 'W':
218       temp *= 7 * 86400;
219       break;
220
221     case 'y':
222     case 'Y':
223       temp *= 31557600; /* == 365.25 * 86400 */
224       break;
225
226     default:
227       WARNING ("qmail plugin: Invalid suffix for `MTime': `%c'", *endptr);
228       return (-1);
229   } /* switch (*endptr) */
230
231   dir->mtime = (int64_t) temp;
232
233   return (0);
234 } /* int fc_config_add_dir_mtime */
235
236 static int fc_config_add_dir_size (fc_directory_conf_t *dir,
237     oconfig_item_t *ci)
238 {
239   char *endptr;
240   double temp;
241
242   if ((ci->values_num != 1)
243       || ((ci->values[0].type != OCONFIG_TYPE_STRING)
244         && (ci->values[0].type != OCONFIG_TYPE_NUMBER)))
245   {
246     WARNING ("qmail plugin: The `Size' config option needs exactly one "
247         "string or numeric argument.");
248     return (-1);
249   }
250
251   if (ci->values[0].type == OCONFIG_TYPE_NUMBER)
252   {
253     dir->size = (int64_t) ci->values[0].value.number;
254     return (0);
255   }
256
257   errno = 0;
258   endptr = NULL;
259   temp = strtod (ci->values[0].value.string, &endptr);
260   if ((errno != 0) || (endptr == NULL)
261       || (endptr == ci->values[0].value.string))
262   {
263     WARNING ("qmail plugin: Converting `%s' to a number failed.",
264         ci->values[0].value.string);
265     return (-1);
266   }
267
268   switch (*endptr)
269   {
270     case 0:
271     case 'b':
272     case 'B':
273       break;
274
275     case 'k':
276     case 'K':
277       temp *= 1000.0;
278       break;
279
280     case 'm':
281     case 'M':
282       temp *= 1000.0 * 1000.0;
283       break;
284
285     case 'g':
286     case 'G':
287       temp *= 1000.0 * 1000.0 * 1000.0;
288       break;
289
290     case 't':
291     case 'T':
292       temp *= 1000.0 * 1000.0 * 1000.0 * 1000.0;
293       break;
294
295     case 'p':
296     case 'P':
297       temp *= 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0;
298       break;
299
300     default:
301       WARNING ("qmail plugin: Invalid suffix for `Size': `%c'", *endptr);
302       return (-1);
303   } /* switch (*endptr) */
304
305   dir->size = (int64_t) temp;
306
307   return (0);
308 } /* int fc_config_add_dir_size */
309
310 static int fc_config_add_dir (oconfig_item_t *ci)
311 {
312   fc_directory_conf_t *dir;
313   int status;
314   int i;
315
316   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
317   {
318     WARNING ("qmail plugin: `Directory' needs exactly one string argument.");
319     return (-1);
320   }
321
322   /* Initialize `dir' */
323   dir = (fc_directory_conf_t *) malloc (sizeof (*dir));
324   if (dir == NULL)
325   {
326     ERROR ("qmail plugin: mallow failed.");
327     return (-1);
328   }
329   memset (dir, 0, sizeof (*dir));
330
331   dir->path = strdup (ci->values[0].value.string);
332   if (dir->path == NULL)
333   {
334     ERROR ("qmail plugin: strdup failed.");
335     return (-1);
336   }
337
338   fc_config_set_instance (dir, dir->path);
339
340   dir->name = NULL;
341   dir->mtime = 0;
342   dir->size = 0;
343
344   for (i = 0; i < ci->children_num; i++)
345   {
346     oconfig_item_t *option = ci->children + i;
347     status = 0;
348
349     if (strcasecmp ("Instance", option->key) == 0)
350       status = fc_config_add_dir_instance (dir, option);
351     else if (strcasecmp ("Name", option->key) == 0)
352       status = fc_config_add_dir_name (dir, option);
353     else if (strcasecmp ("MTime", option->key) == 0)
354       status = fc_config_add_dir_mtime (dir, option);
355     else if (strcasecmp ("Size", option->key) == 0)
356       status = fc_config_add_dir_size (dir, option);
357     else
358     {
359       WARNING ("qmail plugin: fc_config_add_dir: "
360           "Option `%s' not allowed here.", option->key);
361       status = -1;
362     }
363
364     if (status != 0)
365       break;
366   } /* for (ci->children) */
367
368   if (status == 0)
369   {
370     fc_directory_conf_t **temp;
371
372     temp = (fc_directory_conf_t **) realloc (directories,
373         sizeof (*directories) * directories_num);
374     if (temp == NULL)
375     {
376       ERROR ("qmail plugin: realloc failed.");
377       status = -1;
378     }
379     else
380     {
381       directories = temp;
382       directories[directories_num] = dir;
383       directories_num++;
384     }
385   }
386
387   if (status != 0)
388   {
389     sfree (dir->name);
390     sfree (dir->instance);
391     sfree (dir->path);
392     sfree (dir);
393     return (-1);
394   }
395
396   return (0);
397 } /* int fc_config_add_dir */
398
399 static int fc_config (oconfig_item_t *ci)
400 {
401   int i;
402
403   for (i = 0; i < ci->children_num; i++)
404   {
405     oconfig_item_t *child = ci->children + i;
406     if (strcasecmp ("Directory", child->key) == 0)
407       fc_config_add_dir (child);
408     else
409     {
410       WARNING ("qmail plugin: Ignoring unknown config option `%s'.",
411           child->key);
412     }
413   } /* for (ci->children) */
414
415   return (0);
416 } /* int qmail_config */
417
418 static int fc_init (void)
419 {
420   if (directories_num < 1)
421   {
422     WARNING ("qmail plugin: No directories have been configured.");
423     return (-1);
424   }
425
426   return (0);
427 } /* int fc_init */
428
429 static int fc_read_dir_callback (const char *dirname, const char *filename,
430     void *user_data)
431 {
432   fc_directory_conf_t *dir = user_data;
433   char abs_path[PATH_MAX];
434   struct stat statbuf;
435   int status;
436
437   if (dir == NULL)
438     return (-1);
439
440   ssnprintf (abs_path, sizeof (abs_path), "%s/%s", dirname, filename);
441
442   status = lstat (abs_path, &statbuf);
443   if (status != 0)
444   {
445     ERROR ("qmail plugin: stat (%s) failed.", abs_path);
446     return (-1);
447   }
448
449   if (S_ISDIR (statbuf.st_mode))
450   {
451     status = walk_directory (abs_path, fc_read_dir_callback, dir);
452     return (status);
453   }
454   else if (!S_ISREG (statbuf.st_mode))
455   {
456     return (0);
457   }
458
459   if (dir->name != NULL)
460   {
461     status = fnmatch (dir->name, filename, /* flags = */ 0);
462     if (status != 0)
463       return (0);
464   }
465
466   if (dir->mtime != 0)
467   {
468     time_t mtime = dir->now;
469
470     if (dir->mtime < 0)
471       mtime += dir->mtime;
472     else
473       mtime -= dir->mtime;
474
475     DEBUG ("qmail plugin: Only collecting files that were touched %s %u.",
476         (dir->mtime < 0) ? "after" : "before",
477         (unsigned int) mtime);
478
479     if (((dir->mtime < 0) && (statbuf.st_mtime < mtime))
480         || ((dir->mtime > 0) && (statbuf.st_mtime > mtime)))
481       return (0);
482   }
483
484   if (dir->size != 0)
485   {
486     off_t size;
487
488     if (dir->size < 0)
489       size = (off_t) ((-1) * dir->size);
490     else
491       size = (off_t) dir->size;
492
493     if (((dir->size < 0) && (statbuf.st_size > size))
494         || ((dir->size > 0) && (statbuf.st_size < size)))
495       return (0);
496   }
497
498   dir->files_num++;
499   dir->files_size += (uint64_t) statbuf.st_size;
500
501   return (0);
502 } /* int fc_read_dir_callback */
503
504 static int fc_read_dir (fc_directory_conf_t *dir)
505 {
506   int status;
507
508   dir->files_num = 0;
509   dir->files_size = 0;
510
511   if (dir->mtime != 0)
512     dir->now = time (NULL);
513
514   status = walk_directory (dir->path, fc_read_dir_callback, dir);
515   if (status != 0)
516   {
517     WARNING ("qmail plugin: walk_directory (%s) failed.", dir->path);
518     return (-1);
519   }
520
521   fc_submit_dir (dir);
522
523   return (0);
524 } /* int fc_read_dir */
525
526 static int fc_read (void)
527 {
528   size_t i;
529
530   for (i = 0; i < directories_num; i++)
531     fc_read_dir (directories[i]);
532
533   return (0);
534 } /* int fc_read */
535
536 void module_register (void)
537 {
538   plugin_register_complex_config ("qmail", fc_config);
539   plugin_register_init ("qmail", fc_init);
540   plugin_register_read ("qmail", fc_read);
541 } /* void module_register */
542
543 /*
544  * vim: set sw=2 sts=2 et :
545  */