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 #if defined(_WINDOWS) || defined(__MINGW32__)
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 #if defined(_WINDOWS) || defined(__MINGW32__)
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
etype_to_mask(const char * etype)231 static int etype_to_mask(const char *etype)
232 {
233 static const char *template_list = ZBX_FT_TEMPLATE;
234 const char *tmp;
235 int ret = 1;
236
237 for (tmp = template_list; '\0' != *tmp; tmp += strlen(tmp) + 1)
238 {
239 if (0 == strcmp(etype, tmp))
240 break;
241
242 ret <<= 1;
243 }
244
245 return ret;
246 }
247
zbx_etypes_to_mask(const char * etypes,AGENT_RESULT * result)248 int zbx_etypes_to_mask(const char *etypes, AGENT_RESULT *result)
249 {
250 int n, num, ret = 0;
251
252 if (NULL == etypes || '\0' == *etypes)
253 return 0;
254
255 num = num_param(etypes);
256 for (n = 1; n <= num; n++)
257 {
258 char *etype;
259 int type;
260
261 if (NULL == (etype = get_param_dyn(etypes, n, NULL)))
262 continue;
263
264 if (ZBX_FT_OVERFLOW & (type = etype_to_mask(etype)))
265 {
266 SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Invalid type \"%s\".", etype));
267 zbx_free(etype);
268 return FAIL;
269 }
270
271 ret |= type;
272 zbx_free(etype);
273 }
274
275 if (ZBX_FT_DEV & ret)
276 ret |= ZBX_FT_DEV2;
277
278 if (ZBX_FT_ALL & ret)
279 ret |= ZBX_FT_ALLMASK;
280
281 return ret;
282 }
283
parse_size_parameter(char * text,zbx_uint64_t * size_out)284 static int parse_size_parameter(char *text, zbx_uint64_t *size_out)
285 {
286 if (NULL == text || '\0' == *text)
287 return SUCCEED;
288
289 return str2uint64(text, "KMGT", size_out);
290 }
291
parse_age_parameter(char * text,time_t * time_out,time_t now)292 static int parse_age_parameter(char *text, time_t *time_out, time_t now)
293 {
294 zbx_uint64_t seconds;
295
296 if (NULL == text || '\0' == *text)
297 return SUCCEED;
298
299 if (SUCCEED != str2uint64(text, "smhdw", &seconds))
300 return FAIL;
301
302 *time_out = now - (time_t)seconds;
303
304 return SUCCEED;
305 }
306
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)307 static int prepare_count_parameters(const AGENT_REQUEST *request, AGENT_RESULT *result, int *types_out,
308 zbx_uint64_t *min_size, zbx_uint64_t *max_size, time_t *min_time, time_t *max_time)
309 {
310 int types_incl, types_excl;
311 char *min_size_str, *max_size_str, *min_age_str, *max_age_str;
312 time_t now;
313
314 if (FAIL == (types_incl = zbx_etypes_to_mask(get_rparam(request, 3), result)) ||
315 FAIL == (types_excl = zbx_etypes_to_mask(get_rparam(request, 4), result)))
316 {
317 return FAIL;
318 }
319
320 if (0 == types_incl)
321 types_incl = ZBX_FT_ALLMASK;
322
323 *types_out = types_incl & (~types_excl) & ZBX_FT_ALLMASK;
324
325 /* min/max output variables must be already initialized to default values */
326
327 min_size_str = get_rparam(request, 6);
328 max_size_str = get_rparam(request, 7);
329
330 if (SUCCEED != parse_size_parameter(min_size_str, min_size))
331 {
332 SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Invalid minimum size \"%s\".", min_size_str));
333 return FAIL;
334 }
335
336 if (SUCCEED != parse_size_parameter(max_size_str, max_size))
337 {
338 SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Invalid maximum size \"%s\".", max_size_str));
339 return FAIL;
340 }
341
342 now = time(NULL);
343 min_age_str = get_rparam(request, 8);
344 max_age_str = get_rparam(request, 9);
345
346 if (SUCCEED != parse_age_parameter(min_age_str, max_time, now))
347 {
348 SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Invalid minimum age \"%s\".", min_age_str));
349 return FAIL;
350 }
351
352 if (SUCCEED != parse_age_parameter(max_age_str, min_time, now))
353 {
354 SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Invalid maximum age \"%s\".", max_age_str));
355 return FAIL;
356 }
357
358 return SUCCEED;
359 }
360
regex_incl_excl_free(zbx_regexp_t * regex_incl,zbx_regexp_t * regex_excl,zbx_regexp_t * regex_excl_dir)361 static void regex_incl_excl_free(zbx_regexp_t *regex_incl, zbx_regexp_t *regex_excl, zbx_regexp_t *regex_excl_dir)
362 {
363 if (NULL != regex_incl)
364 zbx_regexp_free(regex_incl);
365
366 if (NULL != regex_excl)
367 zbx_regexp_free(regex_excl);
368
369 if (NULL != regex_excl_dir)
370 zbx_regexp_free(regex_excl_dir);
371 }
372
list_vector_destroy(zbx_vector_ptr_t * list)373 static void list_vector_destroy(zbx_vector_ptr_t *list)
374 {
375 zbx_directory_item_t *item;
376
377 while (0 < list->values_num)
378 {
379 item = (zbx_directory_item_t *)list->values[--list->values_num];
380 zbx_free(item->path);
381 zbx_free(item);
382 }
383 zbx_vector_ptr_destroy(list);
384 }
385
descriptors_vector_destroy(zbx_vector_ptr_t * descriptors)386 static void descriptors_vector_destroy(zbx_vector_ptr_t *descriptors)
387 {
388 zbx_file_descriptor_t *file;
389
390 while (0 < descriptors->values_num)
391 {
392 file = (zbx_file_descriptor_t *)descriptors->values[--descriptors->values_num];
393 zbx_free(file);
394 }
395 zbx_vector_ptr_destroy(descriptors);
396 }
397
398 /******************************************************************************
399 * *
400 * Different approach is used for Windows implementation as Windows is not *
401 * taking size of a directory record in account when calculating size of *
402 * directory contents. *
403 * *
404 * Current implementation ignores special file types (symlinks, pipes, *
405 * sockets, etc.). *
406 * *
407 *****************************************************************************/
408 #if defined(_WINDOWS) || defined(__MINGW32__)
409
410 #define DW2UI64(h,l) ((zbx_uint64_t)h << 32 | l)
411 #define FT2UT(ft) (time_t)(DW2UI64(ft.dwHighDateTime,ft.dwLowDateTime) / 10000000ULL - 11644473600ULL)
412
413 /******************************************************************************
414 * *
415 * Function: has_timed_out *
416 * *
417 * Purpose: Checks if timeout has occurred. If it is, thread should *
418 * immediately stop whatever it is doing, clean up everything and *
419 * return SYSINFO_RET_FAIL. *
420 * *
421 * Parameters: timeout_event - [IN] handle of a timeout event that was passed *
422 * to the metric function *
423 * *
424 * Return value: TRUE, if timeout or error was detected, FALSE otherwise. *
425 * *
426 ******************************************************************************/
has_timed_out(HANDLE timeout_event)427 static BOOL has_timed_out(HANDLE timeout_event)
428 {
429 DWORD rc;
430
431 rc = WaitForSingleObject(timeout_event, 0);
432
433 switch (rc)
434 {
435 case WAIT_OBJECT_0:
436 return TRUE;
437 case WAIT_TIMEOUT:
438 return FALSE;
439 case WAIT_FAILED:
440 zabbix_log(LOG_LEVEL_CRIT, "WaitForSingleObject() returned WAIT_FAILED: %s",
441 strerror_from_system(GetLastError()));
442 return TRUE;
443 default:
444 zabbix_log(LOG_LEVEL_CRIT, "WaitForSingleObject() returned 0x%x", (unsigned int)rc);
445 THIS_SHOULD_NEVER_HAPPEN;
446 return TRUE;
447 }
448 }
449
get_file_info_by_handle(wchar_t * wpath,BY_HANDLE_FILE_INFORMATION * link_info,char ** error)450 static int get_file_info_by_handle(wchar_t *wpath, BY_HANDLE_FILE_INFORMATION *link_info, char **error)
451 {
452 HANDLE file_handle;
453
454 file_handle = CreateFile(wpath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
455 FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL);
456
457 if (INVALID_HANDLE_VALUE == file_handle)
458 {
459 *error = zbx_strdup(NULL, strerror_from_system(GetLastError()));
460 return FAIL;
461 }
462
463 if (0 == GetFileInformationByHandle(file_handle, link_info))
464 {
465 CloseHandle(file_handle);
466 *error = zbx_strdup(NULL, strerror_from_system(GetLastError()));
467 return FAIL;
468 }
469
470 CloseHandle(file_handle);
471
472 return SUCCEED;
473 }
474
link_processed(DWORD attrib,wchar_t * wpath,zbx_vector_ptr_t * descriptors,char * path)475 static int link_processed(DWORD attrib, wchar_t *wpath, zbx_vector_ptr_t *descriptors, char *path)
476 {
477 BY_HANDLE_FILE_INFORMATION link_info;
478 zbx_file_descriptor_t *file;
479 char *error;
480
481 /* Behavior like MS file explorer */
482 if (0 != (attrib & FILE_ATTRIBUTE_REPARSE_POINT))
483 return SUCCEED;
484
485 if (0 != (attrib & FILE_ATTRIBUTE_DIRECTORY))
486 return FAIL;
487
488 if (FAIL == get_file_info_by_handle(wpath, &link_info, &error))
489 {
490 zabbix_log(LOG_LEVEL_DEBUG, "%s() cannot get file information '%s': %s", __func__, path, error);
491 zbx_free(error);
492 return SUCCEED;
493 }
494
495 /* A file is a hard link only */
496 if (1 < link_info.nNumberOfLinks)
497 {
498 /* skip file if inode was already processed (multiple hardlinks) */
499 file = (zbx_file_descriptor_t*)zbx_malloc(NULL, sizeof(zbx_file_descriptor_t));
500
501 file->st_dev = link_info.dwVolumeSerialNumber;
502 file->st_ino = DW2UI64(link_info.nFileIndexHigh, link_info.nFileIndexLow);
503
504 if (FAIL != zbx_vector_ptr_search(descriptors, file, compare_descriptors))
505 {
506 zbx_free(file);
507 return SUCCEED;
508 }
509
510 zbx_vector_ptr_append(descriptors, file);
511 }
512
513 return FAIL;
514 }
515
vfs_dir_size(AGENT_REQUEST * request,AGENT_RESULT * result,HANDLE timeout_event)516 static int vfs_dir_size(AGENT_REQUEST *request, AGENT_RESULT *result, HANDLE timeout_event)
517 {
518 char *dir = NULL;
519 int mode, max_depth, ret = SYSINFO_RET_FAIL;
520 zbx_uint64_t size = 0;
521 zbx_vector_ptr_t list, descriptors;
522 zbx_stat_t status;
523 zbx_regexp_t *regex_incl = NULL, *regex_excl = NULL, *regex_excl_dir = NULL;
524 size_t dir_len;
525
526 if (SUCCEED != prepare_mode_parameter(request, result, &mode))
527 return ret;
528
529 if (SUCCEED != prepare_common_parameters(request, result, ®ex_incl, ®ex_excl, ®ex_excl_dir, &max_depth,
530 &dir, &status, 4, 5, 6))
531 {
532 goto err1;
533 }
534
535 zbx_vector_ptr_create(&descriptors);
536 zbx_vector_ptr_create(&list);
537
538 dir_len = strlen(dir); /* store this value before giving away pointer ownership */
539
540 if (SUCCEED != queue_directory(&list, dir, -1, max_depth)) /* put top directory into list */
541 {
542 zbx_free(dir);
543 goto err2;
544 }
545
546 while (0 < list.values_num && FALSE == has_timed_out(timeout_event))
547 {
548 char *name, *error = NULL;
549 wchar_t *wpath;
550 zbx_uint64_t cluster_size = 0;
551 HANDLE handle;
552 WIN32_FIND_DATA data;
553 zbx_directory_item_t *item;
554
555 item = list.values[--list.values_num];
556
557 name = zbx_dsprintf(NULL, "%s\\*", item->path);
558
559 if (NULL == (wpath = zbx_utf8_to_unicode(name)))
560 {
561 zbx_free(name);
562
563 if (0 < item->depth)
564 {
565 zabbix_log(LOG_LEVEL_DEBUG, "%s() cannot convert directory name to UTF-16: '%s'",
566 __func__, item->path);
567 goto skip;
568 }
569
570 SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot convert directory name to UTF-16."));
571 list.values_num++;
572 goto err2;
573 }
574
575 zbx_free(name);
576
577 handle = FindFirstFile(wpath, &data);
578 zbx_free(wpath);
579
580 if (INVALID_HANDLE_VALUE == handle)
581 {
582 if (0 < item->depth)
583 {
584 zabbix_log(LOG_LEVEL_DEBUG, "%s() cannot open directory listing '%s': %s",
585 __func__, item->path, zbx_strerror(errno));
586 goto skip;
587 }
588
589 SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain directory listing."));
590 list.values_num++;
591 goto err2;
592 }
593
594 if (SIZE_MODE_DISK == mode && 0 == (cluster_size = get_cluster_size(item->path, &error)))
595 {
596 SET_MSG_RESULT(result, error);
597 list.values_num++;
598 goto err2;
599 }
600
601 do
602 {
603 char *path;
604
605 if (0 == wcscmp(data.cFileName, L".") || 0 == wcscmp(data.cFileName, L".."))
606 continue;
607
608 name = zbx_unicode_to_utf8(data.cFileName);
609 path = zbx_dsprintf(NULL, "%s/%s", item->path, name);
610 wpath = zbx_utf8_to_unicode(path);
611
612 if (NULL != regex_excl_dir && 0 != (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
613 {
614 /* consider only path relative to path given in first parameter */
615 if (0 == zbx_regexp_match_precompiled(path + dir_len + 1, regex_excl_dir))
616 {
617 zbx_free(wpath);
618 zbx_free(path);
619 zbx_free(name);
620 continue;
621 }
622 }
623
624 if (SUCCEED == link_processed(data.dwFileAttributes, wpath, &descriptors, path))
625 {
626 zbx_free(wpath);
627 zbx_free(path);
628 zbx_free(name);
629 continue;
630 }
631
632 if (0 == (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) /* not a directory */
633 {
634 if (0 != filename_matches(name, regex_incl, regex_excl))
635 {
636 DWORD size_high, size_low;
637
638 /* GetCompressedFileSize gives more accurate result than zbx_stat for */
639 /* compressed files */
640 size_low = GetCompressedFileSize(wpath, &size_high);
641
642 if (size_low != INVALID_FILE_SIZE || NO_ERROR == GetLastError())
643 {
644 zbx_uint64_t file_size, mod;
645
646 file_size = ((zbx_uint64_t)size_high << 32) | size_low;
647
648 if (SIZE_MODE_DISK == mode && 0 != (mod = file_size % cluster_size))
649 file_size += cluster_size - mod;
650
651 size += file_size;
652 }
653 }
654 zbx_free(path);
655 }
656 else if (SUCCEED != queue_directory(&list, path, item->depth, max_depth))
657 {
658 zbx_free(path);
659 }
660
661 zbx_free(wpath);
662 zbx_free(name);
663
664 }
665 while (0 != FindNextFile(handle, &data) && FALSE == has_timed_out(timeout_event));
666
667 if (0 == FindClose(handle))
668 {
669 zabbix_log(LOG_LEVEL_DEBUG, "%s() cannot close directory listing '%s': %s", __func__,
670 item->path, zbx_strerror(errno));
671 }
672 skip:
673 zbx_free(item->path);
674 zbx_free(item);
675 }
676
677 if (TRUE == has_timed_out(timeout_event))
678 {
679 goto err2;
680 }
681
682 SET_UI64_RESULT(result, size);
683 ret = SYSINFO_RET_OK;
684 err2:
685 list_vector_destroy(&list);
686 descriptors_vector_destroy(&descriptors);
687 err1:
688 regex_incl_excl_free(regex_incl, regex_excl, regex_excl_dir);
689
690 return ret;
691 }
692 #else /* not _WINDOWS or __MINGW32__ */
vfs_dir_size(AGENT_REQUEST * request,AGENT_RESULT * result)693 static int vfs_dir_size(AGENT_REQUEST *request, AGENT_RESULT *result)
694 {
695 char *dir = NULL;
696 int mode, max_depth, ret = SYSINFO_RET_FAIL;
697 zbx_uint64_t size = 0;
698 zbx_vector_ptr_t list, descriptors;
699 zbx_stat_t status;
700 zbx_regexp_t *regex_incl = NULL, *regex_excl = NULL, *regex_excl_dir = NULL;
701 size_t dir_len;
702
703 if (SUCCEED != prepare_mode_parameter(request, result, &mode))
704 return ret;
705
706 if (SUCCEED != prepare_common_parameters(request, result, ®ex_incl, ®ex_excl, ®ex_excl_dir, &max_depth,
707 &dir, &status, 4, 5, 6))
708 {
709 goto err1;
710 }
711
712 zbx_vector_ptr_create(&descriptors);
713 zbx_vector_ptr_create(&list);
714
715 dir_len = strlen(dir); /* store this value before giving away pointer ownership */
716
717 if (SUCCEED != queue_directory(&list, dir, -1, max_depth)) /* put top directory into list */
718 {
719 zbx_free(dir);
720 goto err2;
721 }
722
723 /* on UNIX count top directory size */
724
725 if (0 != filename_matches(dir, regex_incl, regex_excl))
726 {
727 if (SIZE_MODE_APPARENT == mode)
728 size += (zbx_uint64_t)status.st_size;
729 else /* must be SIZE_MODE_DISK */
730 size += (zbx_uint64_t)status.st_blocks * DISK_BLOCK_SIZE;
731 }
732
733 while (0 < list.values_num)
734 {
735 zbx_directory_item_t *item;
736 struct dirent *entry;
737 DIR *directory;
738
739 item = (zbx_directory_item_t *)list.values[--list.values_num];
740
741 if (NULL == (directory = opendir(item->path)))
742 {
743 if (0 < item->depth) /* unreadable subdirectory - skip */
744 {
745 zabbix_log(LOG_LEVEL_DEBUG, "%s() cannot open directory listing '%s': %s",
746 __func__, item->path, zbx_strerror(errno));
747 goto skip;
748 }
749
750 /* unreadable top directory - stop */
751
752 SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain directory listing."));
753 list.values_num++;
754 goto err2;
755 }
756
757 while (NULL != (entry = readdir(directory)))
758 {
759 char *path;
760
761 if (0 == strcmp(entry->d_name, ".") || 0 == strcmp(entry->d_name, ".."))
762 continue;
763
764 path = zbx_dsprintf(NULL, "%s/%s", item->path, entry->d_name);
765
766 if (0 == lstat(path, &status))
767 {
768 if (NULL != regex_excl_dir && 0 != S_ISDIR(status.st_mode))
769 {
770 /* consider only path relative to path given in first parameter */
771 if (0 == zbx_regexp_match_precompiled(path + dir_len + 1, regex_excl_dir))
772 {
773 zbx_free(path);
774 continue;
775 }
776 }
777
778 if ((0 != S_ISREG(status.st_mode) || 0 != S_ISLNK(status.st_mode) ||
779 0 != S_ISDIR(status.st_mode)) &&
780 0 != filename_matches(entry->d_name, regex_incl, regex_excl))
781 {
782 if (0 != S_ISREG(status.st_mode) && 1 < status.st_nlink)
783 {
784 zbx_file_descriptor_t *file;
785
786 /* skip file if inode was already processed (multiple hardlinks) */
787 file = (zbx_file_descriptor_t*)zbx_malloc(NULL,
788 sizeof(zbx_file_descriptor_t));
789
790 file->st_dev = status.st_dev;
791 file->st_ino = status.st_ino;
792
793 if (FAIL != zbx_vector_ptr_search(&descriptors, file,
794 compare_descriptors))
795 {
796 zbx_free(file);
797 zbx_free(path);
798 continue;
799 }
800
801 zbx_vector_ptr_append(&descriptors, file);
802 }
803
804 if (SIZE_MODE_APPARENT == mode)
805 size += (zbx_uint64_t)status.st_size;
806 else /* must be SIZE_MODE_DISK */
807 size += (zbx_uint64_t)status.st_blocks * DISK_BLOCK_SIZE;
808 }
809
810 if (!(0 != S_ISDIR(status.st_mode) && SUCCEED == queue_directory(&list, path,
811 item->depth, max_depth)))
812 {
813 zbx_free(path);
814 }
815 }
816 else
817 {
818 zabbix_log(LOG_LEVEL_DEBUG, "%s() cannot process directory entry '%s': %s",
819 __func__, path, zbx_strerror(errno));
820 zbx_free(path);
821 }
822 }
823
824 closedir(directory);
825 skip:
826 zbx_free(item->path);
827 zbx_free(item);
828 }
829
830 SET_UI64_RESULT(result, size);
831 ret = SYSINFO_RET_OK;
832 err2:
833 list_vector_destroy(&list);
834 descriptors_vector_destroy(&descriptors);
835 err1:
836 regex_incl_excl_free(regex_incl, regex_excl, regex_excl_dir);
837
838 return ret;
839 }
840 #endif
841
VFS_DIR_SIZE(AGENT_REQUEST * request,AGENT_RESULT * result)842 int VFS_DIR_SIZE(AGENT_REQUEST *request, AGENT_RESULT *result)
843 {
844 return zbx_execute_threaded_metric(vfs_dir_size, request, result);
845 }
846
847 /******************************************************************************
848 * *
849 * Function: vfs_dir_count *
850 * *
851 * Purpose: counts files in directory, subject to regexp, type and depth *
852 * filters *
853 * *
854 * Return value: boolean failure flag *
855 * *
856 * Comments: under Widows we only support entry types "file" and "dir" *
857 * *
858 *****************************************************************************/
859 #if defined(_WINDOWS) || defined(__MINGW32__)
vfs_dir_count(AGENT_REQUEST * request,AGENT_RESULT * result,HANDLE timeout_event)860 static int vfs_dir_count(AGENT_REQUEST *request, AGENT_RESULT *result, HANDLE timeout_event)
861 {
862 char *dir = NULL;
863 int types, max_depth, ret = SYSINFO_RET_FAIL;
864 zbx_uint64_t count = 0;
865 zbx_vector_ptr_t list, descriptors;
866 zbx_stat_t status;
867 zbx_regexp_t *regex_incl = NULL, *regex_excl = NULL, *regex_excl_dir = NULL;
868 zbx_uint64_t min_size = 0, max_size = __UINT64_C(0x7fffffffffffffff);
869 time_t min_time = 0, max_time = 0x7fffffff;
870 size_t dir_len;
871
872 if (SUCCEED != prepare_count_parameters(request, result, &types, &min_size, &max_size, &min_time, &max_time))
873 return ret;
874
875 if (SUCCEED != prepare_common_parameters(request, result, ®ex_incl, ®ex_excl, ®ex_excl_dir, &max_depth,
876 &dir, &status, 5, 10, 11))
877 {
878 goto err1;
879 }
880
881 zbx_vector_ptr_create(&descriptors);
882 zbx_vector_ptr_create(&list);
883
884 dir_len = strlen(dir); /* store this value before giving away pointer ownership */
885
886 if (SUCCEED != queue_directory(&list, dir, -1, max_depth)) /* put top directory into list */
887 {
888 zbx_free(dir);
889 goto err2;
890 }
891
892 while (0 < list.values_num && FALSE == has_timed_out(timeout_event))
893 {
894 char *name;
895 wchar_t *wpath;
896 HANDLE handle;
897 WIN32_FIND_DATA data;
898 zbx_directory_item_t *item;
899
900 item = list.values[--list.values_num];
901
902 name = zbx_dsprintf(NULL, "%s\\*", item->path);
903
904 if (NULL == (wpath = zbx_utf8_to_unicode(name)))
905 {
906 zbx_free(name);
907
908 if (0 < item->depth)
909 {
910 zabbix_log(LOG_LEVEL_DEBUG, "%s() cannot convert directory name to UTF-16: '%s'",
911 __func__, item->path);
912 goto skip;
913 }
914
915 SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot convert directory name to UTF-16."));
916 list.values_num++;
917 goto err2;
918 }
919
920 zbx_free(name);
921
922 handle = FindFirstFileW(wpath, &data);
923 zbx_free(wpath);
924
925 if (INVALID_HANDLE_VALUE == handle)
926 {
927 if (0 < item->depth)
928 {
929 zabbix_log(LOG_LEVEL_DEBUG, "%s() cannot open directory listing '%s': %s",
930 __func__, item->path, zbx_strerror(errno));
931 goto skip;
932 }
933
934 SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain directory listing."));
935 list.values_num++;
936 goto err2;
937 }
938
939 do
940 {
941 char *path;
942 int match;
943
944 if (0 == wcscmp(data.cFileName, L".") || 0 == wcscmp(data.cFileName, L".."))
945 continue;
946
947 name = zbx_unicode_to_utf8(data.cFileName);
948 path = zbx_dsprintf(NULL, "%s/%s", item->path, name);
949
950 if (NULL != regex_excl_dir && 0 != (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
951 {
952 /* consider only path relative to path given in first parameter */
953 if (0 == zbx_regexp_match_precompiled(path + dir_len + 1, regex_excl_dir))
954 {
955 zbx_free(path);
956 zbx_free(name);
957 continue;
958 }
959 }
960
961 match = filename_matches(name, regex_incl, regex_excl);
962
963 if (min_size > DW2UI64(data.nFileSizeHigh, data.nFileSizeLow))
964 match = 0;
965
966 if (max_size < DW2UI64(data.nFileSizeHigh, data.nFileSizeLow))
967 match = 0;
968
969 if (min_time >= FT2UT(data.ftLastWriteTime))
970 match = 0;
971
972 if (max_time < FT2UT(data.ftLastWriteTime))
973 match = 0;
974
975 switch (data.dwFileAttributes & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY))
976 {
977 case FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY:
978 goto free_path;
979 case FILE_ATTRIBUTE_REPARSE_POINT:
980 /* not a symlink directory => symlink regular file*/
981 /* counting symlink files as MS explorer */
982 if (0 != (types & ZBX_FT_FILE) && 0 != match)
983 ++count;
984 break;
985 case FILE_ATTRIBUTE_DIRECTORY:
986 if (SUCCEED != queue_directory(&list, path, item->depth, max_depth))
987 zbx_free(path);
988
989 if (0 != (types & ZBX_FT_DIR) && 0 != match)
990 ++count;
991 break;
992 default: /* not a directory => regular file */
993 if (0 != (types & ZBX_FT_FILE) && 0 != match)
994 {
995 wpath = zbx_utf8_to_unicode(path);
996 if (FAIL == link_processed(data.dwFileAttributes, wpath, &descriptors,
997 path))
998 {
999 ++count;
1000 }
1001
1002 zbx_free(wpath);
1003 }
1004 free_path:
1005 zbx_free(path);
1006 }
1007
1008 zbx_free(name);
1009
1010 } while (0 != FindNextFile(handle, &data) && FALSE == has_timed_out(timeout_event));
1011
1012 if (0 == FindClose(handle))
1013 {
1014 zabbix_log(LOG_LEVEL_DEBUG, "%s() cannot close directory listing '%s': %s", __func__,
1015 item->path, zbx_strerror(errno));
1016 }
1017 skip:
1018 zbx_free(item->path);
1019 zbx_free(item);
1020 }
1021
1022 if (TRUE == has_timed_out(timeout_event))
1023 {
1024 goto err2;
1025 }
1026
1027 SET_UI64_RESULT(result, count);
1028 ret = SYSINFO_RET_OK;
1029 err2:
1030 list_vector_destroy(&list);
1031 descriptors_vector_destroy(&descriptors);
1032 err1:
1033 regex_incl_excl_free(regex_incl, regex_excl, regex_excl_dir);
1034
1035 return ret;
1036 }
1037 #else /* not _WINDOWS or __MINGW32__ */
vfs_dir_count(AGENT_REQUEST * request,AGENT_RESULT * result)1038 static int vfs_dir_count(AGENT_REQUEST *request, AGENT_RESULT *result)
1039 {
1040 char *dir = NULL;
1041 int types, max_depth, ret = SYSINFO_RET_FAIL;
1042 int count = 0;
1043 zbx_vector_ptr_t list;
1044 zbx_stat_t status;
1045 zbx_regexp_t *regex_incl = NULL, *regex_excl = NULL, *regex_excl_dir = NULL;
1046 zbx_uint64_t min_size = 0, max_size = __UINT64_C(0x7FFFffffFFFFffff);
1047 time_t min_time = 0, max_time = 0x7fffffff;
1048 size_t dir_len;
1049
1050 if (SUCCEED != prepare_count_parameters(request, result, &types, &min_size, &max_size, &min_time, &max_time))
1051 return ret;
1052
1053 if (SUCCEED != prepare_common_parameters(request, result, ®ex_incl, ®ex_excl, ®ex_excl_dir, &max_depth,
1054 &dir, &status, 5, 10, 11))
1055 {
1056 goto err1;
1057 }
1058
1059 zbx_vector_ptr_create(&list);
1060
1061 dir_len = strlen(dir); /* store this value before giving away pointer ownership */
1062
1063 if (SUCCEED != queue_directory(&list, dir, -1, max_depth)) /* put top directory into list */
1064 {
1065 zbx_free(dir);
1066 goto err2;
1067 }
1068
1069 while (0 < list.values_num)
1070 {
1071 zbx_directory_item_t *item;
1072 struct dirent *entry;
1073 DIR *directory;
1074
1075 item = (zbx_directory_item_t *)list.values[--list.values_num];
1076
1077 if (NULL == (directory = opendir(item->path)))
1078 {
1079 if (0 < item->depth) /* unreadable subdirectory - skip */
1080 {
1081 zabbix_log(LOG_LEVEL_DEBUG, "%s() cannot open directory listing '%s': %s",
1082 __func__, item->path, zbx_strerror(errno));
1083 goto skip;
1084 }
1085
1086 /* unreadable top directory - stop */
1087
1088 SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain directory listing."));
1089 list.values_num++;
1090 goto err2;
1091 }
1092
1093 while (NULL != (entry = readdir(directory)))
1094 {
1095 char *path;
1096
1097 if (0 == strcmp(entry->d_name, ".") || 0 == strcmp(entry->d_name, ".."))
1098 continue;
1099
1100 path = zbx_dsprintf(NULL, "%s/%s", item->path, entry->d_name);
1101
1102 if (0 == lstat(path, &status))
1103 {
1104 if (NULL != regex_excl_dir && 0 != S_ISDIR(status.st_mode))
1105 {
1106 /* consider only path relative to path given in first parameter */
1107 if (0 == zbx_regexp_match_precompiled(path + dir_len + 1, regex_excl_dir))
1108 {
1109 zbx_free(path);
1110 continue;
1111 }
1112 }
1113
1114 if (0 != filename_matches(entry->d_name, regex_incl, regex_excl) && (
1115 (S_ISREG(status.st_mode) && 0 != (types & ZBX_FT_FILE)) ||
1116 (S_ISDIR(status.st_mode) && 0 != (types & ZBX_FT_DIR)) ||
1117 (S_ISLNK(status.st_mode) && 0 != (types & ZBX_FT_SYM)) ||
1118 (S_ISSOCK(status.st_mode) && 0 != (types & ZBX_FT_SOCK)) ||
1119 (S_ISBLK(status.st_mode) && 0 != (types & ZBX_FT_BDEV)) ||
1120 (S_ISCHR(status.st_mode) && 0 != (types & ZBX_FT_CDEV)) ||
1121 (S_ISFIFO(status.st_mode) && 0 != (types & ZBX_FT_FIFO))) &&
1122 (min_size <= (zbx_uint64_t)status.st_size
1123 && (zbx_uint64_t)status.st_size <= max_size) &&
1124 (min_time < status.st_mtime &&
1125 status.st_mtime <= max_time))
1126 {
1127 ++count;
1128 }
1129
1130 if (!(0 != S_ISDIR(status.st_mode) && SUCCEED == queue_directory(&list, path,
1131 item->depth, max_depth)))
1132 {
1133 zbx_free(path);
1134 }
1135 }
1136 else
1137 {
1138 zabbix_log(LOG_LEVEL_DEBUG, "%s() cannot process directory entry '%s': %s",
1139 __func__, path, zbx_strerror(errno));
1140 zbx_free(path);
1141 }
1142 }
1143
1144 closedir(directory);
1145 skip:
1146 zbx_free(item->path);
1147 zbx_free(item);
1148 }
1149
1150 SET_UI64_RESULT(result, count);
1151 ret = SYSINFO_RET_OK;
1152 err2:
1153 list_vector_destroy(&list);
1154 err1:
1155 regex_incl_excl_free(regex_incl, regex_excl, regex_excl_dir);
1156
1157 return ret;
1158 }
1159 #endif
1160
VFS_DIR_COUNT(AGENT_REQUEST * request,AGENT_RESULT * result)1161 int VFS_DIR_COUNT(AGENT_REQUEST *request, AGENT_RESULT *result)
1162 {
1163 return zbx_execute_threaded_metric(vfs_dir_count, request, result);
1164 }
1165