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