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