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