filecount plugin: Make it possible to include hidden files and directories.
[collectd.git] / src / filecount.c
1 /**
2  * collectd - src/filecount.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 #define FC_RECURSIVE 1
35 #define FC_HIDDEN 1
36
37 struct fc_directory_conf_s
38 {
39   char *path;
40   char *instance;
41
42   int options;
43
44   /* Data counters */
45   uint64_t files_num;
46   uint64_t files_size;
47
48   /* Selectors */
49   char *name;
50   int64_t mtime;
51   int64_t size;
52
53   /* Helper for the recursive functions */
54   time_t now;
55 };
56 typedef struct fc_directory_conf_s fc_directory_conf_t;
57
58 static fc_directory_conf_t **directories = NULL;
59 static size_t directories_num = 0;
60
61 static void fc_submit_dir (const fc_directory_conf_t *dir)
62 {
63   value_t values[1];
64   value_list_t vl = VALUE_LIST_INIT;
65
66   values[0].gauge = (gauge_t) dir->files_num;
67
68   vl.values = values;
69   vl.values_len = STATIC_ARRAY_SIZE (values);
70   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
71   sstrncpy (vl.plugin, "filecount", sizeof (vl.plugin));
72   sstrncpy (vl.plugin_instance, dir->instance, sizeof (vl.plugin_instance));
73   sstrncpy (vl.type, "files", sizeof (vl.type));
74
75   plugin_dispatch_values (&vl);
76
77   values[0].gauge = (gauge_t) dir->files_size;
78   sstrncpy (vl.type, "bytes", sizeof (vl.type));
79
80   plugin_dispatch_values (&vl);
81 } /* void fc_submit_dir */
82
83 /*
84  * Config:
85  * <Plugin filecount>
86  *   <Directory /path/to/dir>
87  *     Instance "foobar"
88  *     Name "*.conf"
89  *     MTime -3600
90  *     Size "+10M"
91  *   </Directory>
92  * </Plugin>
93  *
94  * Collect:
95  * - Number of files
96  * - Total size
97  */
98
99 static int fc_config_set_instance (fc_directory_conf_t *dir, const char *str)
100 {
101   char buffer[1024];
102   char *ptr;
103   char *copy;
104
105   sstrncpy (buffer, str, sizeof (buffer));
106   for (ptr = buffer; *ptr != 0; ptr++)
107     if (*ptr == '/')
108       *ptr = '_';
109
110   for (ptr = buffer; *ptr == '_'; ptr++)
111     /* do nothing */;
112
113   if (*ptr == 0)
114     return (-1);
115
116   copy = strdup (ptr);
117   if (copy == NULL)
118     return (-1);
119
120   sfree (dir->instance);
121   dir->instance = copy;
122
123   return (0);
124 } /* int fc_config_set_instance */
125
126 static int fc_config_add_dir_instance (fc_directory_conf_t *dir,
127     oconfig_item_t *ci)
128 {
129   if ((ci->values_num != 1)
130       || (ci->values[0].type != OCONFIG_TYPE_STRING))
131   {
132     WARNING ("filecount plugin: The `Instance' config option needs exactly "
133         "one string argument.");
134     return (-1);
135   }
136
137   return (fc_config_set_instance (dir, ci->values[0].value.string));
138 } /* int fc_config_add_dir_instance */
139
140 static int fc_config_add_dir_name (fc_directory_conf_t *dir,
141     oconfig_item_t *ci)
142 {
143   char *temp;
144
145   if ((ci->values_num != 1)
146       || (ci->values[0].type != OCONFIG_TYPE_STRING))
147   {
148     WARNING ("filecount plugin: The `Name' config option needs exactly one "
149         "string argument.");
150     return (-1);
151   }
152
153   temp = strdup (ci->values[0].value.string);
154   if (temp == NULL)
155   {
156     ERROR ("filecount plugin: strdup failed.");
157     return (-1);
158   }
159
160   sfree (dir->name);
161   dir->name = temp;
162
163   return (0);
164 } /* int fc_config_add_dir_name */
165
166 static int fc_config_add_dir_mtime (fc_directory_conf_t *dir,
167     oconfig_item_t *ci)
168 {
169   char *endptr;
170   double temp;
171
172   if ((ci->values_num != 1)
173       || ((ci->values[0].type != OCONFIG_TYPE_STRING)
174         && (ci->values[0].type != OCONFIG_TYPE_NUMBER)))
175   {
176     WARNING ("filecount plugin: The `MTime' config option needs exactly one "
177         "string or numeric argument.");
178     return (-1);
179   }
180
181   if (ci->values[0].type == OCONFIG_TYPE_NUMBER)
182   {
183     dir->mtime = (int64_t) ci->values[0].value.number;
184     return (0);
185   }
186
187   errno = 0;
188   endptr = NULL;
189   temp = strtod (ci->values[0].value.string, &endptr);
190   if ((errno != 0) || (endptr == NULL)
191       || (endptr == ci->values[0].value.string))
192   {
193     WARNING ("filecount plugin: Converting `%s' to a number failed.",
194         ci->values[0].value.string);
195     return (-1);
196   }
197
198   switch (*endptr)
199   {
200     case 0:
201     case 's':
202     case 'S':
203       break;
204
205     case 'm':
206     case 'M':
207       temp *= 60;
208       break;
209
210     case 'h':
211     case 'H':
212       temp *= 3600;
213       break;
214
215     case 'd':
216     case 'D':
217       temp *= 86400;
218       break;
219
220     case 'w':
221     case 'W':
222       temp *= 7 * 86400;
223       break;
224
225     case 'y':
226     case 'Y':
227       temp *= 31557600; /* == 365.25 * 86400 */
228       break;
229
230     default:
231       WARNING ("filecount plugin: Invalid suffix for `MTime': `%c'", *endptr);
232       return (-1);
233   } /* switch (*endptr) */
234
235   dir->mtime = (int64_t) temp;
236
237   return (0);
238 } /* int fc_config_add_dir_mtime */
239
240 static int fc_config_add_dir_size (fc_directory_conf_t *dir,
241     oconfig_item_t *ci)
242 {
243   char *endptr;
244   double temp;
245
246   if ((ci->values_num != 1)
247       || ((ci->values[0].type != OCONFIG_TYPE_STRING)
248         && (ci->values[0].type != OCONFIG_TYPE_NUMBER)))
249   {
250     WARNING ("filecount plugin: The `Size' config option needs exactly one "
251         "string or numeric argument.");
252     return (-1);
253   }
254
255   if (ci->values[0].type == OCONFIG_TYPE_NUMBER)
256   {
257     dir->size = (int64_t) ci->values[0].value.number;
258     return (0);
259   }
260
261   errno = 0;
262   endptr = NULL;
263   temp = strtod (ci->values[0].value.string, &endptr);
264   if ((errno != 0) || (endptr == NULL)
265       || (endptr == ci->values[0].value.string))
266   {
267     WARNING ("filecount plugin: Converting `%s' to a number failed.",
268         ci->values[0].value.string);
269     return (-1);
270   }
271
272   switch (*endptr)
273   {
274     case 0:
275     case 'b':
276     case 'B':
277       break;
278
279     case 'k':
280     case 'K':
281       temp *= 1000.0;
282       break;
283
284     case 'm':
285     case 'M':
286       temp *= 1000.0 * 1000.0;
287       break;
288
289     case 'g':
290     case 'G':
291       temp *= 1000.0 * 1000.0 * 1000.0;
292       break;
293
294     case 't':
295     case 'T':
296       temp *= 1000.0 * 1000.0 * 1000.0 * 1000.0;
297       break;
298
299     case 'p':
300     case 'P':
301       temp *= 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0;
302       break;
303
304     default:
305       WARNING ("filecount plugin: Invalid suffix for `Size': `%c'", *endptr);
306       return (-1);
307   } /* switch (*endptr) */
308
309   dir->size = (int64_t) temp;
310
311   return (0);
312 } /* int fc_config_add_dir_size */
313
314 static int fc_config_add_dir_recursive (fc_directory_conf_t *dir,
315     oconfig_item_t *ci)
316 {
317   if ((ci->values_num != 1)
318       || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN))
319   {
320     WARNING ("filecount plugin: The `Recursive' config options needs exactly "
321         "one boolean argument.");
322     return (-1);
323   }
324
325   if (ci->values[0].value.boolean)
326     dir->options |= FC_RECURSIVE;
327   else
328     dir->options &= ~FC_RECURSIVE;
329
330   return (0);
331 } /* int fc_config_add_dir_recursive */
332
333 static int fc_config_add_dir_hidden (fc_directory_conf_t *dir,
334     oconfig_item_t *ci)
335 {
336   if ((ci->values_num != 1)
337       || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN))
338   {
339     WARNING ("filecount plugin: The `Hidden' config options needs exactly "
340         "one boolean argument.");
341     return (-1);
342   }
343
344   if (ci->values[0].value.boolean)
345     dir->options |= FC_HIDDEN;
346   else
347     dir->options &= ~FC_HIDDEN;
348
349   return (0);
350 } /* int fc_config_add_dir_hidden */
351
352 static int fc_config_add_dir (oconfig_item_t *ci)
353 {
354   fc_directory_conf_t *dir;
355   int status;
356   int i;
357
358   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
359   {
360     WARNING ("filecount plugin: `Directory' needs exactly one string "
361         "argument.");
362     return (-1);
363   }
364
365   /* Initialize `dir' */
366   dir = (fc_directory_conf_t *) malloc (sizeof (*dir));
367   if (dir == NULL)
368   {
369     ERROR ("filecount plugin: malloc failed.");
370     return (-1);
371   }
372   memset (dir, 0, sizeof (*dir));
373
374   dir->path = strdup (ci->values[0].value.string);
375   if (dir->path == NULL)
376   {
377     ERROR ("filecount plugin: strdup failed.");
378     return (-1);
379   }
380
381   fc_config_set_instance (dir, dir->path);
382
383   dir->options = FC_RECURSIVE;
384   dir->options = FC_HIDDEN;
385
386   dir->name = NULL;
387   dir->mtime = 0;
388   dir->size = 0;
389
390   status = 0;
391   for (i = 0; i < ci->children_num; i++)
392   {
393     oconfig_item_t *option = ci->children + i;
394
395     if (strcasecmp ("Instance", option->key) == 0)
396       status = fc_config_add_dir_instance (dir, option);
397     else if (strcasecmp ("Name", option->key) == 0)
398       status = fc_config_add_dir_name (dir, option);
399     else if (strcasecmp ("MTime", option->key) == 0)
400       status = fc_config_add_dir_mtime (dir, option);
401     else if (strcasecmp ("Size", option->key) == 0)
402       status = fc_config_add_dir_size (dir, option);
403     else if (strcasecmp ("Recursive", option->key) == 0)
404       status = fc_config_add_dir_recursive (dir, option);
405     else if (strcasecmp ("Hidden", option->key) == 0)
406       status = fc_config_add_dir_hidden (dir, option);
407     else
408     {
409       WARNING ("filecount plugin: fc_config_add_dir: "
410           "Option `%s' not allowed here.", option->key);
411       status = -1;
412     }
413
414     if (status != 0)
415       break;
416   } /* for (ci->children) */
417
418   if (status == 0)
419   {
420     fc_directory_conf_t **temp;
421
422     temp = (fc_directory_conf_t **) realloc (directories,
423         sizeof (*directories) * (directories_num + 1));
424     if (temp == NULL)
425     {
426       ERROR ("filecount plugin: realloc failed.");
427       status = -1;
428     }
429     else
430     {
431       directories = temp;
432       directories[directories_num] = dir;
433       directories_num++;
434     }
435   }
436
437   if (status != 0)
438   {
439     sfree (dir->name);
440     sfree (dir->instance);
441     sfree (dir->path);
442     sfree (dir);
443     return (-1);
444   }
445
446   return (0);
447 } /* int fc_config_add_dir */
448
449 static int fc_config (oconfig_item_t *ci)
450 {
451   int i;
452
453   for (i = 0; i < ci->children_num; i++)
454   {
455     oconfig_item_t *child = ci->children + i;
456     if (strcasecmp ("Directory", child->key) == 0)
457       fc_config_add_dir (child);
458     else
459     {
460       WARNING ("filecount plugin: Ignoring unknown config option `%s'.",
461           child->key);
462     }
463   } /* for (ci->children) */
464
465   return (0);
466 } /* int fc_config */
467
468 static int fc_init (void)
469 {
470   if (directories_num < 1)
471   {
472     WARNING ("filecount plugin: No directories have been configured.");
473     return (-1);
474   }
475
476   return (0);
477 } /* int fc_init */
478
479 static int fc_read_dir_callback (const char *dirname, const char *filename,
480     void *user_data)
481 {
482   fc_directory_conf_t *dir = user_data;
483   char abs_path[PATH_MAX];
484   struct stat statbuf;
485   int status;
486
487   if (dir == NULL)
488     return (-1);
489
490   ssnprintf (abs_path, sizeof (abs_path), "%s/%s", dirname, filename);
491
492   status = lstat (abs_path, &statbuf);
493   if (status != 0)
494   {
495     ERROR ("filecount plugin: stat (%s) failed.", abs_path);
496     return (-1);
497   }
498
499   if (S_ISDIR (statbuf.st_mode) && (dir->options & FC_RECURSIVE))
500   {
501     if(dir->options & FC_HIDDEN)
502         status = walk_directory (abs_path, fc_read_dir_callback, dir,/* include hidden */1);
503     else
504         status = walk_directory (abs_path, fc_read_dir_callback, dir,/* include hidden */ 0);
505     return (status);
506   }
507   else if (!S_ISREG (statbuf.st_mode))
508   {
509     return (0);
510   }
511
512   if (dir->name != NULL)
513   {
514     status = fnmatch (dir->name, filename, /* flags = */ 0);
515     if (status != 0)
516       return (0);
517   }
518
519   if (dir->mtime != 0)
520   {
521     time_t mtime = dir->now;
522
523     if (dir->mtime < 0)
524       mtime += dir->mtime;
525     else
526       mtime -= dir->mtime;
527
528     DEBUG ("filecount plugin: Only collecting files that were touched %s %u.",
529         (dir->mtime < 0) ? "after" : "before",
530         (unsigned int) mtime);
531
532     if (((dir->mtime < 0) && (statbuf.st_mtime < mtime))
533         || ((dir->mtime > 0) && (statbuf.st_mtime > mtime)))
534       return (0);
535   }
536
537   if (dir->size != 0)
538   {
539     off_t size;
540
541     if (dir->size < 0)
542       size = (off_t) ((-1) * dir->size);
543     else
544       size = (off_t) dir->size;
545
546     if (((dir->size < 0) && (statbuf.st_size > size))
547         || ((dir->size > 0) && (statbuf.st_size < size)))
548       return (0);
549   }
550
551   dir->files_num++;
552   dir->files_size += (uint64_t) statbuf.st_size;
553
554   return (0);
555 } /* int fc_read_dir_callback */
556
557 static int fc_read_dir (fc_directory_conf_t *dir)
558 {
559   int status;
560
561   dir->files_num = 0;
562   dir->files_size = 0;
563
564   if (dir->mtime != 0)
565     dir->now = time (NULL);
566     
567   if(dir->options & FC_HIDDEN)
568     status = walk_directory (dir->path, fc_read_dir_callback, dir,/* include hidden */ 1);
569   else
570     status = walk_directory (dir->path, fc_read_dir_callback, dir,/* include hidden */ 0);
571   if (status != 0)
572   {
573     WARNING ("filecount plugin: walk_directory (%s) failed.", dir->path);
574     return (-1);
575   }
576
577   fc_submit_dir (dir);
578
579   return (0);
580 } /* int fc_read_dir */
581
582 static int fc_read (void)
583 {
584   size_t i;
585
586   for (i = 0; i < directories_num; i++)
587     fc_read_dir (directories[i]);
588
589   return (0);
590 } /* int fc_read */
591
592 void module_register (void)
593 {
594   plugin_register_complex_config ("filecount", fc_config);
595   plugin_register_init ("filecount", fc_init);
596   plugin_register_read ("filecount", fc_read);
597 } /* void module_register */
598
599 /*
600  * vim: set sw=2 sts=2 et :
601  */