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