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 
22 #include "dbcache.h"
23 #include "log.h"
24 #include "zbxserver.h"
25 #include "zbxicmpping.h"
26 #include "daemon.h"
27 #include "zbxself.h"
28 #include "preproc.h"
29 
30 #include "pinger.h"
31 
32 /* defines for `fping' and `fping6' to successfully process pings */
33 #define MIN_COUNT	1
34 #define MAX_COUNT	10000
35 #define MIN_INTERVAL	20
36 #define MIN_SIZE	24
37 #define MAX_SIZE	65507
38 #define MIN_TIMEOUT	50
39 
40 extern unsigned char	process_type, program_type;
41 extern int		server_num, process_num;
42 
43 /******************************************************************************
44  *                                                                            *
45  * Function: process_value                                                    *
46  *                                                                            *
47  * Purpose: process new item value                                            *
48  *                                                                            *
49  * Parameters:                                                                *
50  *                                                                            *
51  * Return value:                                                              *
52  *                                                                            *
53  * Author: Alexei Vladishev, Alexander Vladishev                              *
54  *                                                                            *
55  * Comments:                                                                  *
56  *                                                                            *
57  ******************************************************************************/
process_value(zbx_uint64_t itemid,zbx_uint64_t * value_ui64,double * value_dbl,zbx_timespec_t * ts,int ping_result,char * error)58 static void	process_value(zbx_uint64_t itemid, zbx_uint64_t *value_ui64, double *value_dbl,	zbx_timespec_t *ts,
59 		int ping_result, char *error)
60 {
61 	DC_ITEM		item;
62 	int		errcode;
63 	AGENT_RESULT	value;
64 
65 	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
66 
67 	DCconfig_get_items_by_itemids(&item, &itemid, &errcode, 1);
68 
69 	if (SUCCEED != errcode)
70 		goto clean;
71 
72 	if (ITEM_STATUS_ACTIVE != item.status)
73 		goto clean;
74 
75 	if (HOST_STATUS_MONITORED != item.host.status)
76 		goto clean;
77 
78 	if (NOTSUPPORTED == ping_result)
79 	{
80 		item.state = ITEM_STATE_NOTSUPPORTED;
81 		zbx_preprocess_item_value(item.itemid, item.host.hostid, item.value_type, item.flags, NULL, ts,
82 				item.state, error);
83 	}
84 	else
85 	{
86 		init_result(&value);
87 
88 		if (NULL != value_ui64)
89 			SET_UI64_RESULT(&value, *value_ui64);
90 		else
91 			SET_DBL_RESULT(&value, *value_dbl);
92 
93 		item.state = ITEM_STATE_NORMAL;
94 		zbx_preprocess_item_value(item.itemid, item.host.hostid, item.value_type, item.flags, &value, ts,
95 				item.state, NULL);
96 
97 		free_result(&value);
98 	}
99 clean:
100 	DCrequeue_items(&item.itemid, &ts->sec, &errcode, 1);
101 
102 	DCconfig_clean_items(&item, &errcode, 1);
103 
104 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
105 }
106 
107 /******************************************************************************
108  *                                                                            *
109  * Function: process_values                                                   *
110  *                                                                            *
111  * Purpose: process new item values                                           *
112  *                                                                            *
113  * Parameters:                                                                *
114  *                                                                            *
115  * Return value:                                                              *
116  *                                                                            *
117  * Author: Alexei Vladishev, Alexander Vladishev                              *
118  *                                                                            *
119  * Comments:                                                                  *
120  *                                                                            *
121  ******************************************************************************/
process_values(icmpitem_t * items,int first_index,int last_index,ZBX_FPING_HOST * hosts,int hosts_count,zbx_timespec_t * ts,int ping_result,char * error)122 static void	process_values(icmpitem_t *items, int first_index, int last_index, ZBX_FPING_HOST *hosts,
123 		int hosts_count, zbx_timespec_t *ts, int ping_result, char *error)
124 {
125 	int		i, h;
126 	zbx_uint64_t	value_uint64;
127 	double		value_dbl;
128 
129 	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
130 
131 	for (h = 0; h < hosts_count; h++)
132 	{
133 		const ZBX_FPING_HOST	*host = &hosts[h];
134 
135 		if (NOTSUPPORTED == ping_result)
136 		{
137 			zabbix_log(LOG_LEVEL_DEBUG, "host [%s] %s", host->addr, error);
138 		}
139 		else
140 		{
141 			zabbix_log(LOG_LEVEL_DEBUG, "host [%s] cnt=%d rcv=%d"
142 					" min=" ZBX_FS_DBL " max=" ZBX_FS_DBL " sum=" ZBX_FS_DBL,
143 					host->addr, host->cnt, host->rcv, host->min, host->max, host->sum);
144 		}
145 
146 		for (i = first_index; i < last_index; i++)
147 		{
148 			const icmpitem_t	*item = &items[i];
149 
150 			if (0 != strcmp(item->addr, host->addr))
151 				continue;
152 
153 			if (NOTSUPPORTED == ping_result)
154 			{
155 				process_value(item->itemid, NULL, NULL, ts, NOTSUPPORTED, error);
156 				continue;
157 			}
158 
159 			if (0 == host->cnt)
160 			{
161 				process_value(item->itemid, NULL, NULL, ts, NOTSUPPORTED,
162 						(char *)"Cannot send ICMP ping packets to this host.");
163 				continue;
164 			}
165 
166 			switch (item->icmpping)
167 			{
168 				case ICMPPING:
169 					value_uint64 = (0 != host->rcv ? 1 : 0);
170 					process_value(item->itemid, &value_uint64, NULL, ts, SUCCEED, NULL);
171 					break;
172 				case ICMPPINGSEC:
173 					switch (item->type)
174 					{
175 						case ICMPPINGSEC_MIN:
176 							value_dbl = host->min;
177 							break;
178 						case ICMPPINGSEC_MAX:
179 							value_dbl = host->max;
180 							break;
181 						case ICMPPINGSEC_AVG:
182 							value_dbl = (0 != host->rcv ? host->sum / host->rcv : 0);
183 							break;
184 					}
185 
186 					if (0 < value_dbl && ZBX_FLOAT_PRECISION > value_dbl)
187 						value_dbl = ZBX_FLOAT_PRECISION;
188 
189 					process_value(item->itemid, NULL, &value_dbl, ts, SUCCEED, NULL);
190 					break;
191 				case ICMPPINGLOSS:
192 					value_dbl = (100 * (host->cnt - host->rcv)) / (double)host->cnt;
193 					process_value(item->itemid, NULL, &value_dbl, ts, SUCCEED, NULL);
194 					break;
195 			}
196 		}
197 	}
198 
199 	zbx_preprocessor_flush();
200 
201 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
202 }
203 
parse_key_params(const char * key,const char * host_addr,icmpping_t * icmpping,char ** addr,int * count,int * interval,int * size,int * timeout,icmppingsec_type_t * type,char * error,int max_error_len)204 static int	parse_key_params(const char *key, const char *host_addr, icmpping_t *icmpping, char **addr, int *count,
205 		int *interval, int *size, int *timeout, icmppingsec_type_t *type, char *error, int max_error_len)
206 {
207 	const char	*tmp;
208 	int		ret = NOTSUPPORTED;
209 	AGENT_REQUEST	request;
210 
211 	init_request(&request);
212 
213 	if (SUCCEED != parse_item_key(key, &request))
214 	{
215 		zbx_snprintf(error, max_error_len, "Invalid item key format.");
216 		goto out;
217 	}
218 
219 	if (0 == strcmp(get_rkey(&request), SERVER_ICMPPING_KEY))
220 	{
221 		*icmpping = ICMPPING;
222 	}
223 	else if (0 == strcmp(get_rkey(&request), SERVER_ICMPPINGLOSS_KEY))
224 	{
225 		*icmpping = ICMPPINGLOSS;
226 	}
227 	else if (0 == strcmp(get_rkey(&request), SERVER_ICMPPINGSEC_KEY))
228 	{
229 		*icmpping = ICMPPINGSEC;
230 	}
231 	else
232 	{
233 		zbx_snprintf(error, max_error_len, "Unsupported pinger key.");
234 		goto out;
235 	}
236 
237 	if (6 < get_rparams_num(&request) || (ICMPPINGSEC != *icmpping && 5 < get_rparams_num(&request)))
238 	{
239 		zbx_snprintf(error, max_error_len, "Too many arguments.");
240 		goto out;
241 	}
242 
243 	if (NULL == (tmp = get_rparam(&request, 1)) || '\0' == *tmp)
244 	{
245 		*count = 3;
246 	}
247 	else if (FAIL == is_uint31(tmp, count) || MIN_COUNT > *count || *count > MAX_COUNT)
248 	{
249 		zbx_snprintf(error, max_error_len, "Number of packets \"%s\" is not between %d and %d.",
250 				tmp, MIN_COUNT, MAX_COUNT);
251 		goto out;
252 	}
253 
254 	if (NULL == (tmp = get_rparam(&request, 2)) || '\0' == *tmp)
255 	{
256 		*interval = 0;
257 	}
258 	else if (FAIL == is_uint31(tmp, interval) || MIN_INTERVAL > *interval)
259 	{
260 		zbx_snprintf(error, max_error_len, "Interval \"%s\" should be at least %d.", tmp, MIN_INTERVAL);
261 		goto out;
262 	}
263 
264 	if (NULL == (tmp = get_rparam(&request, 3)) || '\0' == *tmp)
265 	{
266 		*size = 0;
267 	}
268 	else if (FAIL == is_uint31(tmp, size) || MIN_SIZE > *size || *size > MAX_SIZE)
269 	{
270 		zbx_snprintf(error, max_error_len, "Packet size \"%s\" is not between %d and %d.",
271 				tmp, MIN_SIZE, MAX_SIZE);
272 		goto out;
273 	}
274 
275 	if (NULL == (tmp = get_rparam(&request, 4)) || '\0' == *tmp)
276 	{
277 		*timeout = 0;
278 	}
279 	else if (FAIL == is_uint31(tmp, timeout) || MIN_TIMEOUT > *timeout)
280 	{
281 		zbx_snprintf(error, max_error_len, "Timeout \"%s\" should be at least %d.", tmp, MIN_TIMEOUT);
282 		goto out;
283 	}
284 
285 	if (NULL == (tmp = get_rparam(&request, 5)) || '\0' == *tmp)
286 	{
287 		*type = ICMPPINGSEC_AVG;
288 	}
289 	else
290 	{
291 		if (0 == strcmp(tmp, "min"))
292 		{
293 			*type = ICMPPINGSEC_MIN;
294 		}
295 		else if (0 == strcmp(tmp, "avg"))
296 		{
297 			*type = ICMPPINGSEC_AVG;
298 		}
299 		else if (0 == strcmp(tmp, "max"))
300 		{
301 			*type = ICMPPINGSEC_MAX;
302 		}
303 		else
304 		{
305 			zbx_snprintf(error, max_error_len, "Mode \"%s\" is not supported.", tmp);
306 			goto out;
307 		}
308 	}
309 
310 	if (NULL == (tmp = get_rparam(&request, 0)) || '\0' == *tmp)
311 		*addr = strdup(host_addr);
312 	else
313 		*addr = strdup(tmp);
314 
315 	ret = SUCCEED;
316 out:
317 	free_request(&request);
318 
319 	return ret;
320 }
321 
get_icmpping_nearestindex(icmpitem_t * items,int items_count,int count,int interval,int size,int timeout)322 static int	get_icmpping_nearestindex(icmpitem_t *items, int items_count, int count, int interval, int size, int timeout)
323 {
324 	int		first_index, last_index, index;
325 	icmpitem_t	*item;
326 
327 	if (items_count == 0)
328 		return 0;
329 
330 	first_index = 0;
331 	last_index = items_count - 1;
332 	while (1)
333 	{
334 		index = first_index + (last_index - first_index) / 2;
335 		item = &items[index];
336 
337 		if (item->count == count && item->interval == interval && item->size == size && item->timeout == timeout)
338 			return index;
339 		else if (last_index == first_index)
340 		{
341 			if (item->count < count ||
342 					(item->count == count && item->interval < interval) ||
343 					(item->count == count && item->interval == interval && item->size < size) ||
344 					(item->count == count && item->interval == interval && item->size == size && item->timeout < timeout))
345 				index++;
346 			return index;
347 		}
348 		else if (item->count < count ||
349 				(item->count == count && item->interval < interval) ||
350 				(item->count == count && item->interval == interval && item->size < size) ||
351 				(item->count == count && item->interval == interval && item->size == size && item->timeout < timeout))
352 			first_index = index + 1;
353 		else
354 			last_index = index;
355 	}
356 }
357 
add_icmpping_item(icmpitem_t ** items,int * items_alloc,int * items_count,int count,int interval,int size,int timeout,zbx_uint64_t itemid,char * addr,icmpping_t icmpping,icmppingsec_type_t type)358 static void	add_icmpping_item(icmpitem_t **items, int *items_alloc, int *items_count, int count, int interval,
359 		int size, int timeout, zbx_uint64_t itemid, char *addr, icmpping_t icmpping, icmppingsec_type_t type)
360 {
361 	int		index;
362 	icmpitem_t	*item;
363 	size_t		sz;
364 
365 	zabbix_log(LOG_LEVEL_DEBUG, "In %s() addr:'%s' count:%d interval:%d size:%d timeout:%d",
366 			__func__, addr, count, interval, size, timeout);
367 
368 	index = get_icmpping_nearestindex(*items, *items_count, count, interval, size, timeout);
369 
370 	if (*items_alloc == *items_count)
371 	{
372 		*items_alloc += 4;
373 		sz = *items_alloc * sizeof(icmpitem_t);
374 		*items = (icmpitem_t *)zbx_realloc(*items, sz);
375 	}
376 
377 	memmove(&(*items)[index + 1], &(*items)[index], sizeof(icmpitem_t) * (*items_count - index));
378 
379 	item = &(*items)[index];
380 	item->count	= count;
381 	item->interval	= interval;
382 	item->size	= size;
383 	item->timeout	= timeout;
384 	item->itemid	= itemid;
385 	item->addr	= addr;
386 	item->icmpping	= icmpping;
387 	item->type	= type;
388 
389 	(*items_count)++;
390 
391 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
392 }
393 
394 /******************************************************************************
395  *                                                                            *
396  * Function: get_pinger_hosts                                                 *
397  *                                                                            *
398  * Purpose: creates buffer which contains list of hosts to ping               *
399  *                                                                            *
400  * Parameters:                                                                *
401  *                                                                            *
402  * Return value: SUCCEED - the file was created successfully                  *
403  *               FAIL - otherwise                                             *
404  *                                                                            *
405  * Author: Alexei Vladishev, Alexander Vladishev                              *
406  *                                                                            *
407  * Comments:                                                                  *
408  *                                                                            *
409  ******************************************************************************/
get_pinger_hosts(icmpitem_t ** icmp_items,int * icmp_items_alloc,int * icmp_items_count)410 static void	get_pinger_hosts(icmpitem_t **icmp_items, int *icmp_items_alloc, int *icmp_items_count)
411 {
412 	DC_ITEM			item, *items;
413 	int			i, num, count, interval, size, timeout, rc, errcode = SUCCEED;
414 	char			error[MAX_STRING_LEN], *addr = NULL;
415 	icmpping_t		icmpping;
416 	icmppingsec_type_t	type;
417 
418 	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
419 
420 	items = &item;
421 	num = DCconfig_get_poller_items(ZBX_POLLER_TYPE_PINGER, &items);
422 
423 	for (i = 0; i < num; i++)
424 	{
425 		ZBX_STRDUP(items[i].key, items[i].key_orig);
426 		rc = substitute_key_macros(&items[i].key, NULL, &items[i], NULL, NULL, MACRO_TYPE_ITEM_KEY, error,
427 				sizeof(error));
428 
429 		if (SUCCEED == rc)
430 		{
431 			rc = parse_key_params(items[i].key, items[i].interface.addr, &icmpping, &addr, &count,
432 					&interval, &size, &timeout, &type, error, sizeof(error));
433 		}
434 
435 		if (SUCCEED == rc)
436 		{
437 			add_icmpping_item(icmp_items, icmp_items_alloc, icmp_items_count, count, interval, size,
438 				timeout, items[i].itemid, addr, icmpping, type);
439 		}
440 		else
441 		{
442 			zbx_timespec_t	ts;
443 
444 			zbx_timespec(&ts);
445 
446 			items[i].state = ITEM_STATE_NOTSUPPORTED;
447 			zbx_preprocess_item_value(items[i].itemid, items[i].host.hostid, items[i].value_type,
448 					items[i].flags, NULL, &ts, items[i].state, error);
449 
450 			DCrequeue_items(&items[i].itemid, &ts.sec, &errcode, 1);
451 		}
452 
453 		zbx_free(items[i].key);
454 	}
455 
456 	DCconfig_clean_items(items, NULL, num);
457 
458 	if (items != &item)
459 		zbx_free(items);
460 
461 	zbx_preprocessor_flush();
462 
463 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%d", __func__, *icmp_items_count);
464 }
465 
free_hosts(icmpitem_t ** items,int * items_count)466 static void	free_hosts(icmpitem_t **items, int *items_count)
467 {
468 	int	i;
469 
470 	for (i = 0; i < *items_count; i++)
471 		zbx_free((*items)[i].addr);
472 
473 	*items_count = 0;
474 }
475 
add_pinger_host(ZBX_FPING_HOST ** hosts,int * hosts_alloc,int * hosts_count,char * addr)476 static void	add_pinger_host(ZBX_FPING_HOST **hosts, int *hosts_alloc, int *hosts_count, char *addr)
477 {
478 	int		i;
479 	size_t		sz;
480 	ZBX_FPING_HOST	*h;
481 
482 	zabbix_log(LOG_LEVEL_DEBUG, "In %s() addr:'%s'", __func__, addr);
483 
484 	for (i = 0; i < *hosts_count; i++)
485 	{
486 		if (0 == strcmp(addr, (*hosts)[i].addr))
487 			return;
488 	}
489 
490 	(*hosts_count)++;
491 
492 	if (*hosts_alloc < *hosts_count)
493 	{
494 		*hosts_alloc += 4;
495 		sz = *hosts_alloc * sizeof(ZBX_FPING_HOST);
496 		*hosts = (ZBX_FPING_HOST *)zbx_realloc(*hosts, sz);
497 	}
498 
499 	h = &(*hosts)[*hosts_count - 1];
500 	memset(h, 0, sizeof(ZBX_FPING_HOST));
501 	h->addr = addr;
502 
503 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
504 }
505 
506 /******************************************************************************
507  *                                                                            *
508  * Function: process_pinger_hosts                                             *
509  *                                                                            *
510  * Purpose:                                                                   *
511  *                                                                            *
512  * Parameters:                                                                *
513  *                                                                            *
514  * Return value:                                                              *
515  *                                                                            *
516  * Author: Alexander Vladishev                                                *
517  *                                                                            *
518  * Comments:                                                                  *
519  *                                                                            *
520  ******************************************************************************/
process_pinger_hosts(icmpitem_t * items,int items_count)521 static void	process_pinger_hosts(icmpitem_t *items, int items_count)
522 {
523 	int			i, first_index = 0, ping_result;
524 	char			error[ITEM_ERROR_LEN_MAX];
525 	static ZBX_FPING_HOST	*hosts = NULL;
526 	static int		hosts_alloc = 4;
527 	int			hosts_count = 0;
528 	zbx_timespec_t		ts;
529 
530 	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
531 
532 	if (NULL == hosts)
533 		hosts = (ZBX_FPING_HOST *)zbx_malloc(hosts, sizeof(ZBX_FPING_HOST) * hosts_alloc);
534 
535 	for (i = 0; i < items_count && ZBX_IS_RUNNING(); i++)
536 	{
537 		add_pinger_host(&hosts, &hosts_alloc, &hosts_count, items[i].addr);
538 
539 		if (i == items_count - 1 || items[i].count != items[i + 1].count || items[i].interval != items[i + 1].interval ||
540 				items[i].size != items[i + 1].size || items[i].timeout != items[i + 1].timeout)
541 		{
542 			zbx_setproctitle("%s #%d [pinging hosts]", get_process_type_string(process_type), process_num);
543 
544 			zbx_timespec(&ts);
545 
546 			ping_result = zbx_ping(hosts, hosts_count,
547 						items[i].count, items[i].interval, items[i].size, items[i].timeout,
548 						error, sizeof(error));
549 
550 			if (FAIL != ping_result)
551 				process_values(items, first_index, i + 1, hosts, hosts_count, &ts, ping_result, error);
552 
553 			hosts_count = 0;
554 			first_index = i + 1;
555 		}
556 	}
557 
558 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
559 }
560 
561 /******************************************************************************
562  *                                                                            *
563  * Function: main_pinger_loop                                                 *
564  *                                                                            *
565  * Purpose: periodically perform ICMP pings                                   *
566  *                                                                            *
567  * Parameters:                                                                *
568  *                                                                            *
569  * Return value:                                                              *
570  *                                                                            *
571  * Author: Alexei Vladishev                                                   *
572  *                                                                            *
573  * Comments: never returns                                                    *
574  *                                                                            *
575  ******************************************************************************/
ZBX_THREAD_ENTRY(pinger_thread,args)576 ZBX_THREAD_ENTRY(pinger_thread, args)
577 {
578 	int			nextcheck, sleeptime, items_count = 0, itc;
579 	double			sec;
580 	static icmpitem_t	*items = NULL;
581 	static int		items_alloc = 4;
582 
583 	process_type = ((zbx_thread_args_t *)args)->process_type;
584 	server_num = ((zbx_thread_args_t *)args)->server_num;
585 	process_num = ((zbx_thread_args_t *)args)->process_num;
586 
587 	zabbix_log(LOG_LEVEL_INFORMATION, "%s #%d started [%s #%d]", get_program_type_string(program_type),
588 			server_num, get_process_type_string(process_type), process_num);
589 
590 	update_selfmon_counter(ZBX_PROCESS_STATE_BUSY);
591 
592 	if (NULL == items)
593 		items = (icmpitem_t *)zbx_malloc(items, sizeof(icmpitem_t) * items_alloc);
594 
595 	while (ZBX_IS_RUNNING())
596 	{
597 		sec = zbx_time();
598 		zbx_update_env(sec);
599 
600 		zbx_setproctitle("%s #%d [getting values]", get_process_type_string(process_type), process_num);
601 
602 		get_pinger_hosts(&items, &items_alloc, &items_count);
603 		process_pinger_hosts(items, items_count);
604 		sec = zbx_time() - sec;
605 		itc = items_count;
606 
607 		free_hosts(&items, &items_count);
608 
609 		nextcheck = DCconfig_get_poller_nextcheck(ZBX_POLLER_TYPE_PINGER);
610 		sleeptime = calculate_sleeptime(nextcheck, POLLER_DELAY);
611 
612 		zbx_setproctitle("%s #%d [got %d values in " ZBX_FS_DBL " sec, idle %d sec]",
613 				get_process_type_string(process_type), process_num, itc, sec, sleeptime);
614 
615 		zbx_sleep_loop(sleeptime);
616 	}
617 
618 	zbx_setproctitle("%s #%d [terminated]", get_process_type_string(process_type), process_num);
619 
620 	while (1)
621 		zbx_sleep(SEC_PER_MIN);
622 }
623