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, &regex_incl, &regex_excl, &regex_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, &regex_incl, &regex_excl, &regex_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, &regex_incl, &regex_excl, &regex_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, &regex_incl, &regex_excl, &regex_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