1 /*
2 ** Zabbix
3 ** Copyright (C) 2001-2021 Zabbix SIA
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 **/
19
20 #include "common.h"
21 #include "sysinfo.h"
22 #include "dir.h"
23 #include "zbxregexp.h"
24 #include "log.h"
25
26 #ifdef _WINDOWS
27 # include "disk.h"
28 #endif
29
30 /******************************************************************************
31 * *
32 * Function: filename_matches *
33 * *
34 * Purpose: checks if filename matches the include-regexp and doesn't match *
35 * the exclude-regexp *
36 * *
37 * Parameters: fname - [IN] filename to be checked *
38 * regex_incl - [IN] regexp for filenames to include (NULL means *
39 * include any file) *
40 * regex_excl - [IN] regexp for filenames to exclude (NULL means *
41 * exclude none) *
42 * *
43 * Return value: If filename passes both checks, nonzero value is returned. *
44 * If filename fails to pass, 0 is returned. *
45 * *
46 ******************************************************************************/
filename_matches(const char * fname,const zbx_regexp_t * regex_incl,const zbx_regexp_t * regex_excl)47 static int filename_matches(const char *fname, const zbx_regexp_t *regex_incl, const zbx_regexp_t *regex_excl)
48 {
49 return ((NULL == regex_incl || 0 == zbx_regexp_match_precompiled(fname, regex_incl)) &&
50 (NULL == regex_excl || 0 != zbx_regexp_match_precompiled(fname, regex_excl)));
51 }
52
53 /******************************************************************************
54 * *
55 * Function: queue_directory *
56 * *
57 * Purpose: adds directory to processing queue after checking if current *
58 * depth is less than 'max_depth' *
59 * *
60 * Parameters: list - [IN/OUT] vector used to replace recursion *
61 * with iterative approach *
62 * path - [IN] directory path *
63 * depth - [IN] current traversal depth of directory *
64 * max_depth - [IN] maximal traversal depth allowed (use -1 *
65 * for unlimited directory traversal) *
66 * *
67 * Return value: SUCCEED - directory is queued, *
68 * FAIL - directory depth is more than allowed traversal depth. *
69 * *
70 ******************************************************************************/
queue_directory(zbx_vector_ptr_t * list,char * path,int depth,int max_depth)71 static int queue_directory(zbx_vector_ptr_t *list, char *path, int depth, int max_depth)
72 {
73 zbx_directory_item_t *item;
74
75 if (TRAVERSAL_DEPTH_UNLIMITED == max_depth || depth < max_depth)
76 {
77 item = (zbx_directory_item_t*)zbx_malloc(NULL, sizeof(zbx_directory_item_t));
78 item->depth = depth + 1;
79 item->path = path; /* 'path' changes ownership. Do not free 'path' in the caller. */
80
81 zbx_vector_ptr_append(list, item);
82
83 return SUCCEED;
84 }
85
86 return FAIL; /* 'path' did not go into 'list' - don't forget to free 'path' in the caller */
87 }
88
89 /******************************************************************************
90 * *
91 * Function: compare_descriptors *
92 * *
93 * Purpose: compares two zbx_file_descriptor_t values to perform search *
94 * within descriptor vector *
95 * *
96 * Parameters: file_a - [IN] file descriptor A *
97 * file_b - [IN] file descriptor B *
98 * *
99 * Return value: If file descriptor values are the same, 0 is returned *
100 * otherwise nonzero value is returned. *
101 * *
102 ******************************************************************************/
compare_descriptors(const void * file_a,const void * file_b)103 static int compare_descriptors(const void *file_a, const void *file_b)
104 {
105 const zbx_file_descriptor_t *fa, *fb;
106
107 fa = *((zbx_file_descriptor_t **)file_a);
108 fb = *((zbx_file_descriptor_t **)file_b);
109
110 return (fa->st_ino != fb->st_ino || fa->st_dev != fb->st_dev);
111 }
112
prepare_common_parameters(const AGENT_REQUEST * request,AGENT_RESULT * result,zbx_regexp_t ** regex_incl,zbx_regexp_t ** regex_excl,zbx_regexp_t ** regex_excl_dir,int * max_depth,char ** dir,zbx_stat_t * status,int depth_param,int excl_dir_param,int param_count)113 static int prepare_common_parameters(const AGENT_REQUEST *request, AGENT_RESULT *result, zbx_regexp_t **regex_incl,
114 zbx_regexp_t **regex_excl, zbx_regexp_t **regex_excl_dir, int *max_depth, char **dir,
115 zbx_stat_t *status, int depth_param, int excl_dir_param, int param_count)
116 {
117 char *dir_param, *regex_incl_str, *regex_excl_str, *regex_excl_dir_str, *max_depth_str;
118 const char *error = NULL;
119
120 if (param_count < request->nparam)
121 {
122 SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
123 return FAIL;
124 }
125
126 dir_param = get_rparam(request, 0);
127 regex_incl_str = get_rparam(request, 1);
128 regex_excl_str = get_rparam(request, 2);
129 regex_excl_dir_str = get_rparam(request, excl_dir_param);
130 max_depth_str = get_rparam(request, depth_param);
131
132 if (NULL == dir_param || '\0' == *dir_param)
133 {
134 SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid first parameter."));
135 return FAIL;
136 }
137
138 if (NULL != regex_incl_str && '\0' != *regex_incl_str)
139 {
140 if (SUCCEED != zbx_regexp_compile(regex_incl_str, regex_incl, &error))
141 {
142 SET_MSG_RESULT(result, zbx_dsprintf(NULL,
143 "Invalid regular expression in second parameter: %s", error));
144 return FAIL;
145 }
146 }
147
148 if (NULL != regex_excl_str && '\0' != *regex_excl_str)
149 {
150 if (SUCCEED != zbx_regexp_compile(regex_excl_str, regex_excl, &error))
151 {
152 SET_MSG_RESULT(result, zbx_dsprintf(NULL,
153 "Invalid regular expression in third parameter: %s", error));
154 return FAIL;
155 }
156 }
157
158 if (NULL != regex_excl_dir_str && '\0' != *regex_excl_dir_str)
159 {
160 if (SUCCEED != zbx_regexp_compile(regex_excl_dir_str, regex_excl_dir, &error))
161 {
162 SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Invalid regular expression in %s parameter: %s",
163 (5 == excl_dir_param ? "sixth" : "eleventh"), error));
164 return FAIL;
165 }
166 }
167
168 if (NULL == max_depth_str || '\0' == *max_depth_str || 0 == strcmp(max_depth_str, "-1"))
169 {
170 *max_depth = TRAVERSAL_DEPTH_UNLIMITED; /* <max_depth> default value */
171 }
172 else if (SUCCEED != is_uint31(max_depth_str, max_depth))
173 {
174 SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Invalid %s parameter.", (4 == depth_param ?
175 "fifth" : "sixth")));
176 return FAIL;
177 }
178
179 *dir = zbx_strdup(*dir, dir_param);
180
181 /* remove directory suffix '/' or '\\' (if any, except for paths like "/" or "C:\\") as stat() fails on */
182 /* Windows for directories ending with slash */
183 if ('\0' != *(*dir + 1) && ':' != *(*dir + strlen(*dir) - 2))
184 zbx_rtrim(*dir, "/\\");
185
186 #ifdef _WINDOWS
187 if (0 != zbx_stat(*dir, status))
188 #else
189 if (0 != lstat(*dir, status))
190 #endif
191 {
192 SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot obtain directory information: %s",
193 zbx_strerror(errno)));
194 zbx_free(*dir);
195 return FAIL;
196 }
197
198 if (0 == S_ISDIR(status->st_mode))
199 {
200 SET_MSG_RESULT(result, zbx_strdup(NULL, "First parameter is not a directory."));
201 zbx_free(*dir);
202 return FAIL;
203 }
204
205 return SUCCEED;
206 }
207
prepare_mode_parameter(const AGENT_REQUEST * request,AGENT_RESULT * result,int * mode)208 static int prepare_mode_parameter(const AGENT_REQUEST *request, AGENT_RESULT *result, int *mode)
209 {
210 char *mode_str;
211
212 mode_str = get_rparam(request, 3);
213
214 if (NULL == mode_str || '\0' == *mode_str || 0 == strcmp(mode_str, "apparent")) /* <mode> default value */
215 {
216 *mode = SIZE_MODE_APPARENT;
217 }
218 else if (0 == strcmp(mode_str, "disk"))
219 {
220 *mode = SIZE_MODE_DISK;
221 }
222 else
223 {
224 SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid fourth parameter."));
225 return FAIL;
226 }
227
228 return SUCCEED;
229 }
230
231 /* Directory Entry Types */
232 #define DET_FILE 0x001
233 #define DET_DIR 0x002
234 #define DET_SYM 0x004
235 #define DET_SOCK 0x008
236 #define DET_BDEV 0x010
237 #define DET_CDEV 0x020
238 #define DET_FIFO 0x040
239 #define DET_ALL 0x080
240 #define DET_DEV 0x100
241 #define DET_OVERFLOW 0x200
242 #define DET_TEMPLATE "file\0dir\0sym\0sock\0bdev\0cdev\0fifo\0all\0dev\0"
243 #define DET_ALLMASK (DET_FILE | DET_DIR | DET_SYM | DET_SOCK | DET_BDEV | DET_CDEV | DET_FIFO)
244 #define DET_DEV2 (DET_BDEV | DET_CDEV)
245
etype_to_mask(char * etype)246 static int etype_to_mask(char *etype)
247 {
248 static const char *template_list = DET_TEMPLATE;
249 const char *tmp;
250 int ret = 1;
251
252 for (tmp = template_list; '\0' != *tmp; tmp += strlen(tmp) + 1)
253 {
254 if (0 == strcmp(etype, tmp))
255 break;
256
257 ret <<= 1;
258 }
259
260 return ret;
261 }
262
etypes_to_mask(char * etypes,AGENT_RESULT * result)263 static int etypes_to_mask(char *etypes, AGENT_RESULT *result)
264 {
265 char *etype;
266 int n, num, type, ret = 0;
267
268 if (NULL == etypes || '\0' == *etypes)
269 return 0;
270
271 num = num_param(etypes);
272 for (n = 1; n <= num; n++)
273 {
274 if (NULL == (etype = get_param_dyn(etypes, n, NULL)))
275 continue;
276
277 if (DET_OVERFLOW & (type = etype_to_mask(etype)))
278 {
279 SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Invalid directory entry type \"%s\".", etype));
280 zbx_free(etype);
281 return type;
282 }
283
284 ret |= type;
285 zbx_free(etype);
286 }
287
288 if (DET_DEV & ret)
289 ret |= DET_DEV2;
290
291 if (DET_ALL & ret)
292 ret |= DET_ALLMASK;
293
294 return ret;
295 }
296
parse_size_parameter(char * text,zbx_uint64_t * size_out)297 static int parse_size_parameter(char *text, zbx_uint64_t *size_out)
298 {
299 if (NULL == text || '\0' == *text)
300 return SUCCEED;
301
302 return str2uint64(text, "KMGT", size_out);
303 }
304
parse_age_parameter(char * text,time_t * time_out,time_t now)305 static int parse_age_parameter(char *text, time_t *time_out, time_t now)
306 {
307 zbx_uint64_t seconds;
308
309 if (NULL == text || '\0' == *text)
310 return SUCCEED;
311
312 if (SUCCEED != str2uint64(text, "smhdw", &seconds))
313 return FAIL;
314
315 *time_out = now - (time_t)seconds;
316
317 return SUCCEED;
318 }
319
prepare_count_parameters(const AGENT_REQUEST * request,AGENT_RESULT * result,int * types_out,zbx_uint64_t * min_size,zbx_uint64_t * max_size,time_t * min_time,time_t * max_time)320 static int prepare_count_parameters(const AGENT_REQUEST *request, AGENT_RESULT *result, int *types_out,
321 zbx_uint64_t *min_size, zbx_uint64_t *max_size, time_t *min_time, time_t *max_time)
322 {
323 int types_incl;
324 int types_excl;
325 char *min_size_str;
326 char *max_size_str;
327 char *min_age_str;
328 char *max_age_str;
329 time_t now;
330
331 types_incl = etypes_to_mask(get_rparam(request, 3), result);
332
333 if (ISSET_MSG(result))
334 return FAIL;
335
336 types_excl = etypes_to_mask(get_rparam(request, 4), result);
337
338 if (ISSET_MSG(result))
339 return FAIL;
340
341 if (DET_OVERFLOW & (types_incl | types_excl))
342 return FAIL;
343
344 if (0 == types_incl)
345 types_incl = DET_ALLMASK;
346
347 *types_out = types_incl & (~types_excl) & DET_ALLMASK;
348
349 /* min/max output variables must be already initialized to default values */
350
351 min_size_str = get_rparam(request, 6);
352 max_size_str = get_rparam(request, 7);
353
354 if (SUCCEED != parse_size_parameter(min_size_str, min_size))
355 {
356 SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Invalid minimum size \"%s\".", min_size_str));
357 return FAIL;
358 }
359
360 if (SUCCEED != parse_size_parameter(max_size_str, max_size))
361 {
362 SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Invalid maximum size \"%s\".", max_size_str));
363 return FAIL;
364 }
365
366 now = time(NULL);
367 min_age_str = get_rparam(request, 8);
368 max_age_str = get_rparam(request, 9);
369
370 if (SUCCEED != parse_age_parameter(min_age_str, max_time, now))
371 {
372 SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Invalid minimum age \"%s\".", min_age_str));
373 return FAIL;
374 }
375
376 if (SUCCEED != parse_age_parameter(max_age_str, min_time, now))
377 {
378 SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Invalid maximum age \"%s\".", max_age_str));
379 return FAIL;
380 }
381
382 return SUCCEED;
383 }
384
regex_incl_excl_free(zbx_regexp_t * regex_incl,zbx_regexp_t * regex_excl,zbx_regexp_t * regex_excl_dir)385 static void regex_incl_excl_free(zbx_regexp_t *regex_incl, zbx_regexp_t *regex_excl, zbx_regexp_t *regex_excl_dir)
386 {
387 if (NULL != regex_incl)
388 zbx_regexp_free(regex_incl);
389
390 if (NULL != regex_excl)
391 zbx_regexp_free(regex_excl);
392
393 if (NULL != regex_excl_dir)
394 zbx_regexp_free(regex_excl_dir);
395 }
396
list_vector_destroy(zbx_vector_ptr_t * list)397 static void list_vector_destroy(zbx_vector_ptr_t *list)
398 {
399 zbx_directory_item_t *item;
400
401 while (0 < list->values_num)
402 {
403 item = (zbx_directory_item_t *)list->values[--list->values_num];
404 zbx_free(item->path);
405 zbx_free(item);
406 }
407 zbx_vector_ptr_destroy(list);
408 }
409
descriptors_vector_destroy(zbx_vector_ptr_t * descriptors)410 static void descriptors_vector_destroy(zbx_vector_ptr_t *descriptors)
411 {
412 zbx_file_descriptor_t *file;
413
414 while (0 < descriptors->values_num)
415 {
416 file = (zbx_file_descriptor_t *)descriptors->values[--descriptors->values_num];
417 zbx_free(file);
418 }
419 zbx_vector_ptr_destroy(descriptors);
420 }
421
422 /******************************************************************************
423 * *
424 * Different approach is used for Windows implementation as Windows is not *
425 * taking size of a directory record in account when calculating size of *
426 * directory contents. *
427 * *
428 * Current implementation ignores special file types (symlinks, pipes, *
429 * sockets, etc.). *
430 * *
431 *****************************************************************************/
432 #ifdef _WINDOWS
433
434 #define DW2UI64(h,l) ((zbx_uint64_t)h << 32 | l)
435 #define FT2UT(ft) (time_t)(DW2UI64(ft.dwHighDateTime,ft.dwLowDateTime) / 10000000ULL - 11644473600ULL)
436
437 /******************************************************************************
438 * *
439 * Function: has_timed_out *
440 * *
441 * Purpose: Checks if timeout has occurred. If it is, thread should *
442 * immediately stop whatever it is doing, clean up everything and *
443 * return SYSINFO_RET_FAIL. *
444 * *
445 * Parameters: timeout_event - [IN] handle of a timeout event that was passed *
446 * to the metric function *
447 * *
448 * Return value: TRUE, if timeout or error was detected, FALSE otherwise. *
449 * *
450 ******************************************************************************/
has_timed_out(HANDLE timeout_event)451 static BOOL has_timed_out(HANDLE timeout_event)
452 {
453 DWORD rc;
454
455 rc = WaitForSingleObject(timeout_event, 0);
456
457 switch (rc)
458 {
459 case WAIT_OBJECT_0:
460 return TRUE;
461 case WAIT_TIMEOUT:
462 return FALSE;
463 case WAIT_FAILED:
464 zabbix_log(LOG_LEVEL_CRIT, "WaitForSingleObject() returned WAIT_FAILED: %s",
465 strerror_from_system(GetLastError()));
466 return TRUE;
467 default:
468 zabbix_log(LOG_LEVEL_CRIT, "WaitForSingleObject() returned 0x%x", (unsigned int)rc);
469 THIS_SHOULD_NEVER_HAPPEN;
470 return TRUE;
471 }
472 }
473
get_file_info_by_handle(wchar_t * wpath,BY_HANDLE_FILE_INFORMATION * link_info,char ** error)474 static int get_file_info_by_handle(wchar_t *wpath, BY_HANDLE_FILE_INFORMATION *link_info, char **error)
475 {
476 HANDLE file_handle;
477
478 file_handle = CreateFile(wpath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
479 FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL);
480
481 if (INVALID_HANDLE_VALUE == file_handle)
482 {
483 *error = zbx_strdup(NULL, strerror_from_system(GetLastError()));
484 return FAIL;
485 }
486
487 if (0 == GetFileInformationByHandle(file_handle, link_info))
488 {
489 CloseHandle(file_handle);
490 *error = zbx_strdup(NULL, strerror_from_system(GetLastError()));
491 return FAIL;
492 }
493
494 CloseHandle(file_handle);
495
496 return SUCCEED;
497 }
498
link_processed(DWORD attrib,wchar_t * wpath,zbx_vector_ptr_t * descriptors,char * path)499 static int link_processed(DWORD attrib, wchar_t *wpath, zbx_vector_ptr_t *descriptors, char *path)
500 {
501 const char *__function_name = "link_processed";
502 BY_HANDLE_FILE_INFORMATION link_info;
503 zbx_file_descriptor_t *file;
504 char *error;
505
506 /* Behavior like MS file explorer */
507 if (0 != (attrib & FILE_ATTRIBUTE_REPARSE_POINT))
508 return SUCCEED;
509
510 if (0 != (attrib & FILE_ATTRIBUTE_DIRECTORY))
511 return FAIL;
512
513 if (FAIL == get_file_info_by_handle(wpath, &link_info, &error))
514 {
515 zabbix_log(LOG_LEVEL_DEBUG, "%s() cannot get file information '%s': %s",
516 __function_name, path, error);
517 zbx_free(error);
518 return SUCCEED;
519 }
520
521 /* A file is a hard link only */
522 if (1 < link_info.nNumberOfLinks)
523 {
524 /* skip file if inode was already processed (multiple hardlinks) */
525 file = (zbx_file_descriptor_t*)zbx_malloc(NULL, sizeof(zbx_file_descriptor_t));
526
527 file->st_dev = link_info.dwVolumeSerialNumber;
528 file->st_ino = DW2UI64(link_info.nFileIndexHigh, link_info.nFileIndexLow);
529
530 if (FAIL != zbx_vector_ptr_search(descriptors, file, compare_descriptors))
531 {
532 zbx_free(file);
533 return SUCCEED;
534 }
535
536 zbx_vector_ptr_append(descriptors, file);
537 }
538
539 return FAIL;
540 }
541
vfs_dir_size(AGENT_REQUEST * request,AGENT_RESULT * result,HANDLE timeout_event)542 static int vfs_dir_size(AGENT_REQUEST *request, AGENT_RESULT *result, HANDLE timeout_event)
543 {
544 const char *__function_name = "vfs_dir_size";
545 char *dir = NULL;
546 int mode, max_depth, ret = SYSINFO_RET_FAIL;
547 zbx_uint64_t size = 0;
548 zbx_vector_ptr_t list, descriptors;
549 zbx_stat_t status;
550 zbx_regexp_t *regex_incl = NULL, *regex_excl = NULL, *regex_excl_dir = NULL;
551 size_t dir_len;
552
553 if (SUCCEED != prepare_mode_parameter(request, result, &mode))
554 return ret;
555
556 if (SUCCEED != prepare_common_parameters(request, result, ®ex_incl, ®ex_excl, ®ex_excl_dir, &max_depth,
557 &dir, &status, 4, 5, 6))
558 {
559 goto err1;
560 }
561
562 zbx_vector_ptr_create(&descriptors);
563 zbx_vector_ptr_create(&list);
564
565 dir_len = strlen(dir); /* store this value before giving away pointer ownership */
566
567 if (SUCCEED != queue_directory(&list, dir, -1, max_depth)) /* put top directory into list */
568 {
569 zbx_free(dir);
570 goto err2;
571 }
572
573 while (0 < list.values_num && FALSE == has_timed_out(timeout_event))
574 {
575 char *name, *error = NULL;
576 wchar_t *wpath;
577 zbx_uint64_t cluster_size = 0;
578 HANDLE handle;
579 WIN32_FIND_DATA data;
580 zbx_directory_item_t *item;
581
582 item = list.values[--list.values_num];
583
584 name = zbx_dsprintf(NULL, "%s\\*", item->path);
585
586 if (NULL == (wpath = zbx_utf8_to_unicode(name)))
587 {
588 zbx_free(name);
589
590 if (0 < item->depth)
591 {
592 zabbix_log(LOG_LEVEL_DEBUG, "%s() cannot convert directory name to UTF-16: '%s'",
593 __function_name, item->path);
594 goto skip;
595 }
596
597 SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot convert directory name to UTF-16."));
598 list.values_num++;
599 goto err2;
600 }
601
602 zbx_free(name);
603
604 handle = FindFirstFile(wpath, &data);
605 zbx_free(wpath);
606
607 if (INVALID_HANDLE_VALUE == handle)
608 {
609 if (0 < item->depth)
610 {
611 zabbix_log(LOG_LEVEL_DEBUG, "%s() cannot open directory listing '%s': %s",
612 __function_name, item->path, zbx_strerror(errno));
613 goto skip;
614 }
615
616 SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain directory listing."));
617 list.values_num++;
618 goto err2;
619 }
620
621 if (SIZE_MODE_DISK == mode && 0 == (cluster_size = get_cluster_size(item->path, &error)))
622 {
623 SET_MSG_RESULT(result, error);
624 list.values_num++;
625 goto err2;
626 }
627
628 do
629 {
630 char *path;
631
632 if (0 == wcscmp(data.cFileName, L".") || 0 == wcscmp(data.cFileName, L".."))
633 continue;
634
635 name = zbx_unicode_to_utf8(data.cFileName);
636 path = zbx_dsprintf(NULL, "%s/%s", item->path, name);
637 wpath = zbx_utf8_to_unicode(path);
638
639 if (NULL != regex_excl_dir && 0 != (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
640 {
641 /* consider only path relative to path given in first parameter */
642 if (0 == zbx_regexp_match_precompiled(path + dir_len + 1, regex_excl_dir))
643 {
644 zbx_free(wpath);
645 zbx_free(path);
646 zbx_free(name);
647 continue;
648 }
649 }
650
651 if (SUCCEED == link_processed(data.dwFileAttributes, wpath, &descriptors, path))
652 {
653 zbx_free(wpath);
654 zbx_free(path);
655 zbx_free(name);
656 continue;
657 }
658
659 if (0 == (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) /* not a directory */
660 {
661 if (0 != filename_matches(name, regex_incl, regex_excl))
662 {
663 DWORD size_high, size_low;
664
665 /* GetCompressedFileSize gives more accurate result than zbx_stat for */
666 /* compressed files */
667 size_low = GetCompressedFileSize(wpath, &size_high);
668
669 if (size_low != INVALID_FILE_SIZE || NO_ERROR == GetLastError())
670 {
671 zbx_uint64_t file_size, mod;
672
673 file_size = ((zbx_uint64_t)size_high << 32) | size_low;
674
675 if (SIZE_MODE_DISK == mode && 0 != (mod = file_size % cluster_size))
676 file_size += cluster_size - mod;
677
678 size += file_size;
679 }
680 }
681 zbx_free(path);
682 }
683 else if (SUCCEED != queue_directory(&list, path, item->depth, max_depth))
684 {
685 zbx_free(path);
686 }
687
688 zbx_free(wpath);
689 zbx_free(name);
690
691 }
692 while (0 != FindNextFile(handle, &data) && FALSE == has_timed_out(timeout_event));
693
694 if (0 == FindClose(handle))
695 {
696 zabbix_log(LOG_LEVEL_DEBUG, "%s() cannot close directory listing '%s': %s", __function_name,
697 item->path, zbx_strerror(errno));
698 }
699 skip:
700 zbx_free(item->path);
701 zbx_free(item);
702 }
703
704 if (TRUE == has_timed_out(timeout_event))
705 {
706 goto err2;
707 }
708
709 SET_UI64_RESULT(result, size);
710 ret = SYSINFO_RET_OK;
711 err2:
712 list_vector_destroy(&list);
713 descriptors_vector_destroy(&descriptors);
714 err1:
715 regex_incl_excl_free(regex_incl, regex_excl, regex_excl_dir);
716
717 return ret;
718 }
719 #else /* not _WINDOWS */
vfs_dir_size(AGENT_REQUEST * request,AGENT_RESULT * result)720 static int vfs_dir_size(AGENT_REQUEST *request, AGENT_RESULT *result)
721 {
722 const char *__function_name = "vfs_dir_size";
723 char *dir = NULL;
724 int mode, max_depth, ret = SYSINFO_RET_FAIL;
725 zbx_uint64_t size = 0;
726 zbx_vector_ptr_t list, descriptors;
727 zbx_stat_t status;
728 zbx_regexp_t *regex_incl = NULL, *regex_excl = NULL, *regex_excl_dir = NULL;
729 size_t dir_len;
730
731 if (SUCCEED != prepare_mode_parameter(request, result, &mode))
732 return ret;
733
734 if (SUCCEED != prepare_common_parameters(request, result, ®ex_incl, ®ex_excl, ®ex_excl_dir, &max_depth,
735 &dir, &status, 4, 5, 6))
736 {
737 goto err1;
738 }
739
740 zbx_vector_ptr_create(&descriptors);
741 zbx_vector_ptr_create(&list);
742
743 dir_len = strlen(dir); /* store this value before giving away pointer ownership */
744
745 if (SUCCEED != queue_directory(&list, dir, -1, max_depth)) /* put top directory into list */
746 {
747 zbx_free(dir);
748 goto err2;
749 }
750
751 /* on UNIX count top directory size */
752
753 if (0 != filename_matches(dir, regex_incl, regex_excl))
754 {
755 if (SIZE_MODE_APPARENT == mode)
756 size += (zbx_uint64_t)status.st_size;
757 else /* must be SIZE_MODE_DISK */
758 size += (zbx_uint64_t)status.st_blocks * DISK_BLOCK_SIZE;
759 }
760
761 while (0 < list.values_num)
762 {
763 zbx_directory_item_t *item;
764 struct dirent *entry;
765 DIR *directory;
766
767 item = (zbx_directory_item_t *)list.values[--list.values_num];
768
769 if (NULL == (directory = opendir(item->path)))
770 {
771 if (0 < item->depth) /* unreadable subdirectory - skip */
772 {
773 zabbix_log(LOG_LEVEL_DEBUG, "%s() cannot open directory listing '%s': %s",
774 __function_name, item->path, zbx_strerror(errno));
775 goto skip;
776 }
777
778 /* unreadable top directory - stop */
779
780 SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain directory listing."));
781 list.values_num++;
782 goto err2;
783 }
784
785 while (NULL != (entry = readdir(directory)))
786 {
787 char *path;
788
789 if (0 == strcmp(entry->d_name, ".") || 0 == strcmp(entry->d_name, ".."))
790 continue;
791
792 path = zbx_dsprintf(NULL, "%s/%s", item->path, entry->d_name);
793
794 if (0 == lstat(path, &status))
795 {
796 if (NULL != regex_excl_dir && 0 != S_ISDIR(status.st_mode))
797 {
798 /* consider only path relative to path given in first parameter */
799 if (0 == zbx_regexp_match_precompiled(path + dir_len + 1, regex_excl_dir))
800 {
801 zbx_free(path);
802 continue;
803 }
804 }
805
806 if ((0 != S_ISREG(status.st_mode) || 0 != S_ISLNK(status.st_mode) ||
807 0 != S_ISDIR(status.st_mode)) &&
808 0 != filename_matches(entry->d_name, regex_incl, regex_excl))
809 {
810 if (0 != S_ISREG(status.st_mode) && 1 < status.st_nlink)
811 {
812 zbx_file_descriptor_t *file;
813
814 /* skip file if inode was already processed (multiple hardlinks) */
815 file = (zbx_file_descriptor_t*)zbx_malloc(NULL,
816 sizeof(zbx_file_descriptor_t));
817
818 file->st_dev = status.st_dev;
819 file->st_ino = status.st_ino;
820
821 if (FAIL != zbx_vector_ptr_search(&descriptors, file,
822 compare_descriptors))
823 {
824 zbx_free(file);
825 zbx_free(path);
826 continue;
827 }
828
829 zbx_vector_ptr_append(&descriptors, file);
830 }
831
832 if (SIZE_MODE_APPARENT == mode)
833 size += (zbx_uint64_t)status.st_size;
834 else /* must be SIZE_MODE_DISK */
835 size += (zbx_uint64_t)status.st_blocks * DISK_BLOCK_SIZE;
836 }
837
838 if (!(0 != S_ISDIR(status.st_mode) && SUCCEED == queue_directory(&list, path,
839 item->depth, max_depth)))
840 {
841 zbx_free(path);
842 }
843 }
844 else
845 {
846 zabbix_log(LOG_LEVEL_DEBUG, "%s() cannot process directory entry '%s': %s",
847 __function_name, path, zbx_strerror(errno));
848 zbx_free(path);
849 }
850 }
851
852 closedir(directory);
853 skip:
854 zbx_free(item->path);
855 zbx_free(item);
856 }
857
858 SET_UI64_RESULT(result, size);
859 ret = SYSINFO_RET_OK;
860 err2:
861 list_vector_destroy(&list);
862 descriptors_vector_destroy(&descriptors);
863 err1:
864 regex_incl_excl_free(regex_incl, regex_excl, regex_excl_dir);
865
866 return ret;
867 }
868 #endif
869
VFS_DIR_SIZE(AGENT_REQUEST * request,AGENT_RESULT * result)870 int VFS_DIR_SIZE(AGENT_REQUEST *request, AGENT_RESULT *result)
871 {
872 return zbx_execute_threaded_metric(vfs_dir_size, request, result);
873 }
874
875 /******************************************************************************
876 * *
877 * Function: vfs_dir_count *
878 * *
879 * Purpose: counts files in directory, subject to regexp, type and depth *
880 * filters *
881 * *
882 * Return value: boolean failure flag *
883 * *
884 * Comments: under Widows we only support entry types "file" and "dir" *
885 * *
886 *****************************************************************************/
887 #ifdef _WINDOWS
vfs_dir_count(const AGENT_REQUEST * request,AGENT_RESULT * result,HANDLE timeout_event)888 static int vfs_dir_count(const AGENT_REQUEST *request, AGENT_RESULT *result, HANDLE timeout_event)
889 {
890 const char *__function_name = "vfs_dir_count";
891 char *dir = NULL;
892 int types, max_depth, ret = SYSINFO_RET_FAIL;
893 zbx_uint64_t count = 0;
894 zbx_vector_ptr_t list, descriptors;
895 zbx_stat_t status;
896 zbx_regexp_t *regex_incl = NULL, *regex_excl = NULL, *regex_excl_dir = NULL;
897 zbx_uint64_t min_size = 0, max_size = __UINT64_C(0x7fffffffffffffff);
898 time_t min_time = 0, max_time = 0x7fffffff;
899 size_t dir_len;
900
901 if (SUCCEED != prepare_count_parameters(request, result, &types, &min_size, &max_size, &min_time, &max_time))
902 return ret;
903
904 if (SUCCEED != prepare_common_parameters(request, result, ®ex_incl, ®ex_excl, ®ex_excl_dir, &max_depth,
905 &dir, &status, 5, 10, 11))
906 {
907 goto err1;
908 }
909
910 zbx_vector_ptr_create(&descriptors);
911 zbx_vector_ptr_create(&list);
912
913 dir_len = strlen(dir); /* store this value before giving away pointer ownership */
914
915 if (SUCCEED != queue_directory(&list, dir, -1, max_depth)) /* put top directory into list */
916 {
917 zbx_free(dir);
918 goto err2;
919 }
920
921 while (0 < list.values_num && FALSE == has_timed_out(timeout_event))
922 {
923 char *name;
924 wchar_t *wpath;
925 HANDLE handle;
926 WIN32_FIND_DATA data;
927 zbx_directory_item_t *item;
928
929 item = list.values[--list.values_num];
930
931 name = zbx_dsprintf(NULL, "%s\\*", item->path);
932
933 if (NULL == (wpath = zbx_utf8_to_unicode(name)))
934 {
935 zbx_free(name);
936
937 if (0 < item->depth)
938 {
939 zabbix_log(LOG_LEVEL_DEBUG, "%s() cannot convert directory name to UTF-16: '%s'",
940 __function_name, item->path);
941 goto skip;
942 }
943
944 SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot convert directory name to UTF-16."));
945 list.values_num++;
946 goto err2;
947 }
948
949 zbx_free(name);
950
951 handle = FindFirstFileW(wpath, &data);
952 zbx_free(wpath);
953
954 if (INVALID_HANDLE_VALUE == handle)
955 {
956 if (0 < item->depth)
957 {
958 zabbix_log(LOG_LEVEL_DEBUG, "%s() cannot open directory listing '%s': %s",
959 __function_name, item->path, zbx_strerror(errno));
960 goto skip;
961 }
962
963 SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain directory listing."));
964 list.values_num++;
965 goto err2;
966 }
967
968 do
969 {
970 char *path;
971 int match;
972
973 if (0 == wcscmp(data.cFileName, L".") || 0 == wcscmp(data.cFileName, L".."))
974 continue;
975
976 name = zbx_unicode_to_utf8(data.cFileName);
977 path = zbx_dsprintf(NULL, "%s/%s", item->path, name);
978
979 if (NULL != regex_excl_dir && 0 != (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
980 {
981 /* consider only path relative to path given in first parameter */
982 if (0 == zbx_regexp_match_precompiled(path + dir_len + 1, regex_excl_dir))
983 {
984 zbx_free(path);
985 zbx_free(name);
986 continue;
987 }
988 }
989
990 match = filename_matches(name, regex_incl, regex_excl);
991
992 if (min_size > DW2UI64(data.nFileSizeHigh, data.nFileSizeLow))
993 match = 0;
994
995 if (max_size < DW2UI64(data.nFileSizeHigh, data.nFileSizeLow))
996 match = 0;
997
998 if (min_time >= FT2UT(data.ftLastWriteTime))
999 match = 0;
1000
1001 if (max_time < FT2UT(data.ftLastWriteTime))
1002 match = 0;
1003
1004 switch (data.dwFileAttributes & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY))
1005 {
1006 case FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY:
1007 goto free_path;
1008 case FILE_ATTRIBUTE_REPARSE_POINT:
1009 /* not a symlink directory => symlink regular file*/
1010 /* counting symlink files as MS explorer */
1011 if (0 != (types & DET_FILE) && 0 != match)
1012 ++count;
1013 break;
1014 case FILE_ATTRIBUTE_DIRECTORY:
1015 if (SUCCEED != queue_directory(&list, path, item->depth, max_depth))
1016 zbx_free(path);
1017
1018 if (0 != (types & DET_DIR) && 0 != match)
1019 ++count;
1020 break;
1021 default: /* not a directory => regular file */
1022 if (0 != (types & DET_FILE) && 0 != match)
1023 {
1024 wpath = zbx_utf8_to_unicode(path);
1025 if (FAIL == link_processed(data.dwFileAttributes, wpath, &descriptors,
1026 path))
1027 {
1028 ++count;
1029 }
1030
1031 zbx_free(wpath);
1032 }
1033 free_path:
1034 zbx_free(path);
1035 }
1036
1037 zbx_free(name);
1038
1039 } while (0 != FindNextFile(handle, &data) && FALSE == has_timed_out(timeout_event));
1040
1041 if (0 == FindClose(handle))
1042 {
1043 zabbix_log(LOG_LEVEL_DEBUG, "%s() cannot close directory listing '%s': %s", __function_name,
1044 item->path, zbx_strerror(errno));
1045 }
1046 skip:
1047 zbx_free(item->path);
1048 zbx_free(item);
1049 }
1050
1051 if (TRUE == has_timed_out(timeout_event))
1052 {
1053 goto err2;
1054 }
1055
1056 SET_UI64_RESULT(result, count);
1057 ret = SYSINFO_RET_OK;
1058 err2:
1059 list_vector_destroy(&list);
1060 descriptors_vector_destroy(&descriptors);
1061 err1:
1062 regex_incl_excl_free(regex_incl, regex_excl, regex_excl_dir);
1063
1064 return ret;
1065 }
1066 #else /* not _WINDOWS */
vfs_dir_count(AGENT_REQUEST * request,AGENT_RESULT * result)1067 static int vfs_dir_count(AGENT_REQUEST *request, AGENT_RESULT *result)
1068 {
1069 const char *__function_name = "vfs_dir_count";
1070 char *dir = NULL;
1071 int types, max_depth, ret = SYSINFO_RET_FAIL;
1072 int count = 0;
1073 zbx_vector_ptr_t list;
1074 zbx_stat_t status;
1075 zbx_regexp_t *regex_incl = NULL, *regex_excl = NULL, *regex_excl_dir = NULL;
1076 zbx_uint64_t min_size = 0, max_size = __UINT64_C(0x7FFFffffFFFFffff);
1077 time_t min_time = 0, max_time = 0x7fffffff;
1078 size_t dir_len;
1079
1080 if (SUCCEED != prepare_count_parameters(request, result, &types, &min_size, &max_size, &min_time, &max_time))
1081 return ret;
1082
1083 if (SUCCEED != prepare_common_parameters(request, result, ®ex_incl, ®ex_excl, ®ex_excl_dir, &max_depth,
1084 &dir, &status, 5, 10, 11))
1085 {
1086 goto err1;
1087 }
1088
1089 zbx_vector_ptr_create(&list);
1090
1091 dir_len = strlen(dir); /* store this value before giving away pointer ownership */
1092
1093 if (SUCCEED != queue_directory(&list, dir, -1, max_depth)) /* put top directory into list */
1094 {
1095 zbx_free(dir);
1096 goto err2;
1097 }
1098
1099 while (0 < list.values_num)
1100 {
1101 zbx_directory_item_t *item;
1102 struct dirent *entry;
1103 DIR *directory;
1104
1105 item = (zbx_directory_item_t *)list.values[--list.values_num];
1106
1107 if (NULL == (directory = opendir(item->path)))
1108 {
1109 if (0 < item->depth) /* unreadable subdirectory - skip */
1110 {
1111 zabbix_log(LOG_LEVEL_DEBUG, "%s() cannot open directory listing '%s': %s",
1112 __function_name, item->path, zbx_strerror(errno));
1113 goto skip;
1114 }
1115
1116 /* unreadable top directory - stop */
1117
1118 SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain directory listing."));
1119 list.values_num++;
1120 goto err2;
1121 }
1122
1123 while (NULL != (entry = readdir(directory)))
1124 {
1125 char *path;
1126
1127 if (0 == strcmp(entry->d_name, ".") || 0 == strcmp(entry->d_name, ".."))
1128 continue;
1129
1130 path = zbx_dsprintf(NULL, "%s/%s", item->path, entry->d_name);
1131
1132 if (0 == lstat(path, &status))
1133 {
1134 if (NULL != regex_excl_dir && 0 != S_ISDIR(status.st_mode))
1135 {
1136 /* consider only path relative to path given in first parameter */
1137 if (0 == zbx_regexp_match_precompiled(path + dir_len + 1, regex_excl_dir))
1138 {
1139 zbx_free(path);
1140 continue;
1141 }
1142 }
1143
1144 if (0 != filename_matches(entry->d_name, regex_incl, regex_excl) && (
1145 (S_ISREG(status.st_mode) && 0 != (types & DET_FILE)) ||
1146 (S_ISDIR(status.st_mode) && 0 != (types & DET_DIR)) ||
1147 (S_ISLNK(status.st_mode) && 0 != (types & DET_SYM)) ||
1148 (S_ISSOCK(status.st_mode) && 0 != (types & DET_SOCK)) ||
1149 (S_ISBLK(status.st_mode) && 0 != (types & DET_BDEV)) ||
1150 (S_ISCHR(status.st_mode) && 0 != (types & DET_CDEV)) ||
1151 (S_ISFIFO(status.st_mode) && 0 != (types & DET_FIFO))) &&
1152 (min_size <= (zbx_uint64_t)status.st_size
1153 && (zbx_uint64_t)status.st_size <= max_size) &&
1154 (min_time < status.st_mtime &&
1155 status.st_mtime <= max_time))
1156 {
1157 ++count;
1158 }
1159
1160 if (!(0 != S_ISDIR(status.st_mode) && SUCCEED == queue_directory(&list, path,
1161 item->depth, max_depth)))
1162 {
1163 zbx_free(path);
1164 }
1165 }
1166 else
1167 {
1168 zabbix_log(LOG_LEVEL_DEBUG, "%s() cannot process directory entry '%s': %s",
1169 __function_name, path, zbx_strerror(errno));
1170 zbx_free(path);
1171 }
1172 }
1173
1174 closedir(directory);
1175 skip:
1176 zbx_free(item->path);
1177 zbx_free(item);
1178 }
1179
1180 SET_UI64_RESULT(result, count);
1181 ret = SYSINFO_RET_OK;
1182 err2:
1183 list_vector_destroy(&list);
1184 err1:
1185 regex_incl_excl_free(regex_incl, regex_excl, regex_excl_dir);
1186
1187 return ret;
1188 }
1189 #endif
1190
VFS_DIR_COUNT(AGENT_REQUEST * request,AGENT_RESULT * result)1191 int VFS_DIR_COUNT(AGENT_REQUEST *request, AGENT_RESULT *result)
1192 {
1193 return zbx_execute_threaded_metric(vfs_dir_count, request, result);
1194 }
1195