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