Index: frontends/php/include/classes/items/CHelpItems.php
===================================================================
--- frontends/php/include/classes/items/CHelpItems.php	(revision 74683)
+++ frontends/php/include/classes/items/CHelpItems.php	(working copy)
@@ -363,7 +363,7 @@
 					'description' => _('Log file monitoring. Returns log')
 				],
 				[
-					'key' => 'logrt[file_regexp,<regexp>,<encoding>,<maxlines>,<mode>,<output>]',
+					'key' => 'logrt[file_regexp,<regexp>,<encoding>,<maxlines>,<mode>,<output>,,<rotate (default) | copytruncate>]',
 					'description' => _('Log file monitoring with log rotation support. Returns log')
 				],
 				[
Index: src/zabbix_agent/active.c
===================================================================
--- src/zabbix_agent/active.c	(revision 74683)
+++ src/zabbix_agent/active.c	(working copy)
@@ -1022,9 +1022,10 @@
 		zbx_uint64_t *lastlogsize_sent, int *mtime_sent, char **error)
 {
 	AGENT_REQUEST	request;
-	const char	*filename, *pattern, *encoding, *maxlines_persec, *skip, *template;
+	const char	*filename, *pattern, *encoding, *maxlines_persec, *skip, *template, *options;
 	char		*encoding_uc = NULL;
 	int		rate, ret = FAIL, s_count, p_count;
+	int		max_parameter_num, rotation_type = ZBX_LOG_ROTATION_LOGRT;
 
 	init_request(&request);
 
@@ -1040,7 +1041,12 @@
 		goto out;
 	}
 
-	if (6 < get_rparams_num(&request))
+	if (0 != (ZBX_METRIC_FLAG_LOG_LOGRT & metric->flags))
+		max_parameter_num = 8;	/* logrt[] */
+	else
+		max_parameter_num = 6;	/* log[] */
+
+	if (max_parameter_num < get_rparams_num(&request))
 	{
 		*error = zbx_strdup(*error, "Too many parameters.");
 		goto out;
@@ -1096,6 +1102,29 @@
 	if (NULL == (template = get_rparam(&request, 5)))
 		template = "";
 
+	if (0 != (ZBX_METRIC_FLAG_LOG_LOGRT & metric->flags))
+	{
+		const char	*maxdelay;
+
+		/* logrt[] 7th parameter must be empty in 3.0 */
+		if (NULL != (maxdelay = get_rparam(&request, 6)) && '\0' != *maxdelay)
+		{
+			*error = zbx_strdup(*error, "Invalid seventh parameter.");
+			goto out;
+		}
+
+		if (NULL != (options = get_rparam(&request, 7)))
+		{
+			if (0 == strcmp(options, "copytruncate"))
+				rotation_type = ZBX_LOG_ROTATION_LOGCPT;
+			else if (0 != strcmp(options, "rotate"))
+			{
+				*error = zbx_strdup(*error, "Invalid eighth parameter.");
+				goto out;
+			}
+		}
+	}
+
 	/* do not flood Zabbix server if file grows too fast */
 	s_count = rate * metric->refresh;
 
@@ -1105,7 +1134,7 @@
 	ret = process_logrt(metric->flags, filename, &metric->lastlogsize, &metric->mtime, lastlogsize_sent, mtime_sent,
 			&metric->skip_old_data, &metric->big_rec, &metric->use_ino, error, &metric->logfiles,
 			&metric->logfiles_num, encoding, &regexps, pattern, template, &p_count, &s_count, process_value,
-			server, port, CONFIG_HOSTNAME, metric->key_orig);
+			server, port, CONFIG_HOSTNAME, metric->key_orig, rotation_type);
 
 	if (SUCCEED == ret)
 	{
Index: src/zabbix_agent/logfiles.c
===================================================================
--- src/zabbix_agent/logfiles.c	(revision 74683)
+++ src/zabbix_agent/logfiles.c	(working copy)
@@ -33,7 +33,12 @@
 #define ZBX_SAME_FILE_NO	0
 #define ZBX_SAME_FILE_YES	1
 #define ZBX_SAME_FILE_RETRY	2
+#define ZBX_SAME_FILE_COPY	3
 
+#define ZBX_FILE_PLACE_UNKNOWN	-1	/* cannot compare file device and inode numbers */
+#define ZBX_FILE_PLACE_OTHER	0	/* both files have different device or inode numbers */
+#define ZBX_FILE_PLACE_SAME	1	/* both files have the same device and inode numbers */
+
 /******************************************************************************
  *                                                                            *
  * Function: split_string                                                     *
@@ -443,26 +448,40 @@
 	for (i = 0; i < logfiles_num; i++)
 	{
 		zabbix_log(LOG_LEVEL_DEBUG, "   nr:%d filename:'%s' mtime:%d size:" ZBX_FS_UI64 " processed_size:"
-				ZBX_FS_UI64 " seq:%d incomplete:%d dev:" ZBX_FS_UI64 " ino_hi:" ZBX_FS_UI64 " ino_lo:"
-				ZBX_FS_UI64
+				ZBX_FS_UI64 " seq:%d copy_of:%d incomplete:%d dev:" ZBX_FS_UI64 " ino_hi:" ZBX_FS_UI64
+				" ino_lo:" ZBX_FS_UI64
 				" md5size:%d md5buf:%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
 				i, logfiles[i].filename, logfiles[i].mtime, logfiles[i].size,
-				logfiles[i].processed_size, logfiles[i].seq, logfiles[i].incomplete, logfiles[i].dev,
-				logfiles[i].ino_hi, logfiles[i].ino_lo, logfiles[i].md5size, logfiles[i].md5buf[0],
-				logfiles[i].md5buf[1], logfiles[i].md5buf[2], logfiles[i].md5buf[3],
-				logfiles[i].md5buf[4], logfiles[i].md5buf[5], logfiles[i].md5buf[6],
-				logfiles[i].md5buf[7], logfiles[i].md5buf[8], logfiles[i].md5buf[9],
-				logfiles[i].md5buf[10], logfiles[i].md5buf[11], logfiles[i].md5buf[12],
-				logfiles[i].md5buf[13], logfiles[i].md5buf[14], logfiles[i].md5buf[15]);
+				logfiles[i].processed_size, logfiles[i].seq, logfiles[i].copy_of,
+				logfiles[i].incomplete, logfiles[i].dev, logfiles[i].ino_hi, logfiles[i].ino_lo,
+				logfiles[i].md5size, logfiles[i].md5buf[0], logfiles[i].md5buf[1],
+				logfiles[i].md5buf[2], logfiles[i].md5buf[3], logfiles[i].md5buf[4],
+				logfiles[i].md5buf[5], logfiles[i].md5buf[6], logfiles[i].md5buf[7],
+				logfiles[i].md5buf[8], logfiles[i].md5buf[9], logfiles[i].md5buf[10],
+				logfiles[i].md5buf[11], logfiles[i].md5buf[12], logfiles[i].md5buf[13],
+				logfiles[i].md5buf[14], logfiles[i].md5buf[15]);
 	}
 }
 
+static int	compare_file_places(const struct st_logfile *old, const struct st_logfile *new, int use_ino)
+{
+	if (1 == use_ino || 2 == use_ino)
+	{
+		if (old->ino_lo != new->ino_lo || old->dev != new->dev || (2 == use_ino && old->ino_hi != new->ino_hi))
+			return ZBX_FILE_PLACE_OTHER;
+		else
+			return ZBX_FILE_PLACE_SAME;
+	}
+
+	return ZBX_FILE_PLACE_UNKNOWN;
+}
+
 /******************************************************************************
  *                                                                            *
- * Function: is_same_file                                                     *
+ * Function: is_same_file_logrt                                               *
  *                                                                            *
- * Purpose: find out wheter a file from the old list and a file from the new  *
- *          list could be the same file                                       *
+ * Purpose: find out if a file from the old list and a file from the new list *
+ *          could be the same file in case of simple rotation                 *
  *                                                                            *
  * Parameters:                                                                *
  *          old     - [IN] file from the old list                             *
@@ -483,22 +502,14 @@
  *           truncated and replaced with a similar one.                       *
  *                                                                            *
  ******************************************************************************/
-static int	is_same_file(const struct st_logfile *old, const struct st_logfile *new, int use_ino, char **err_msg)
+static int	is_same_file_logrt(const struct st_logfile *old, const struct st_logfile *new, int use_ino,
+		char **err_msg)
 {
 	int	ret = ZBX_SAME_FILE_NO;
 
-	if (1 == use_ino || 2 == use_ino)
+	if (ZBX_FILE_PLACE_OTHER == compare_file_places(old, new, use_ino))
 	{
-		if (old->ino_lo != new->ino_lo || old->dev != new->dev)
-		{
-			/* file inode and device id cannot differ */
-			goto out;
-		}
-	}
-
-	if (2 == use_ino && old->ino_hi != new->ino_hi)
-	{
-		/* file inode (older 64-bits) cannot differ */
+		/* files cannot reside on different devices or occupy different inodes */
 		goto out;
 	}
 
@@ -604,34 +615,139 @@
 	return ret;
 }
 
+static int	examine_md5_and_place(const md5_byte_t *buf1, const md5_byte_t *buf2, size_t size, int is_same_place)
+{
+	if (0 == memcmp(buf1, buf2, size))
+	{
+		switch (is_same_place)
+		{
+			case ZBX_FILE_PLACE_UNKNOWN:
+			case ZBX_FILE_PLACE_SAME:
+				return ZBX_SAME_FILE_YES;
+			case ZBX_FILE_PLACE_OTHER:
+				return ZBX_SAME_FILE_COPY;
+		}
+	}
+
+	return ZBX_SAME_FILE_NO;
+}
+
 /******************************************************************************
  *                                                                            *
- * Function: setup_old2new                                                    *
+ * Function: is_same_file_logcpt                                              *
  *                                                                            *
+ * Purpose: find out if a file from the old list and a file from the new list *
+ *          could be the same file or copy in case of copy/truncate rotation  *
+ *                                                                            *
+ * Parameters:                                                                *
+ *          old     - [IN] file from the old list                             *
+ *          new     - [IN] file from the new list                             *
+ *          use_ino - [IN] 0 - do not use inodes in comparison,               *
+ *                         1 - use up to 64-bit inodes in comparison,         *
+ *                         2 - use 128-bit inodes in comparison.              *
+ *          err_msg - [IN/OUT] error message why an item became               *
+ *                    NOTSUPPORTED                                            *
+ *                                                                            *
+ * Return value: ZBX_SAME_FILE_NO - it is not the same file                   *
+ *               ZBX_SAME_FILE_YES - it could be the same file                *
+ *               ZBX_SAME_FILE_COPY - it is a copy                            *
+ *               ZBX_SAME_FILE_ERROR - error                                  *
+ *                                                                            *
+ * Comments: In some cases we can say that it IS NOT the same file.           *
+ *           In other cases it COULD BE the same file or copy.                *
+ *                                                                            *
+ ******************************************************************************/
+static int	is_same_file_logcpt(const struct st_logfile *old, const struct st_logfile *new, int use_ino,
+		char **err_msg)
+{
+	int	is_same_place;
+
+	if (old->mtime > new->mtime)
+		return ZBX_SAME_FILE_NO;
+
+	if (old->size > new->size)
+		return ZBX_SAME_FILE_NO;
+
+	if (-1 == old->md5size || -1 == new->md5size)
+	{
+		/* Cannot compare MD5 sums. Assume two different files - reporting twice is better than skipping. */
+		return ZBX_SAME_FILE_NO;
+	}
+
+	if (old->md5size > new->md5size)
+		return ZBX_SAME_FILE_NO;
+
+	is_same_place = compare_file_places(old, new, use_ino);
+
+	if (old->md5size == new->md5size)
+		return examine_md5_and_place(old->md5buf, new->md5buf, sizeof(new->md5buf), is_same_place);
+
+	if (0 < old->md5size)
+	{
+		/* MD5 for the old file has been calculated from a smaller block than for the new file */
+
+		int		f, ret;
+		md5_byte_t	md5tmp[MD5_DIGEST_SIZE];
+
+		if (-1 == (f = zbx_open(new->filename, O_RDONLY)))
+		{
+			*err_msg = zbx_dsprintf(*err_msg, "Cannot open file \"%s\": %s", new->filename,
+					zbx_strerror(errno));
+			return ZBX_SAME_FILE_ERROR;
+		}
+
+		if (SUCCEED == file_start_md5(f, old->md5size, md5tmp, new->filename, err_msg))
+			ret = examine_md5_and_place(old->md5buf, md5tmp, sizeof(md5tmp), is_same_place);
+		else
+			ret = ZBX_SAME_FILE_ERROR;
+
+		if (0 != close(f))
+		{
+			if (ZBX_SAME_FILE_ERROR != ret)
+			{
+				*err_msg = zbx_dsprintf(*err_msg, "Cannot close file \"%s\": %s", new->filename,
+						zbx_strerror(errno));
+				ret = ZBX_SAME_FILE_ERROR;
+			}
+		}
+
+		return ret;
+	}
+
+	return ZBX_SAME_FILE_NO;
+}
+
+/******************************************************************************
+ *                                                                            *
+ * Function: setup_old2new_and_copy_of                                        *
+ *                                                                            *
  * Purpose: fill an array of possible mappings from the old log files to the  *
  *          new log files.                                                    *
  *                                                                            *
  * Parameters:                                                                *
- *          old2new - [IN] two dimensional array of possible mappings         *
- *          old     - [IN] old file list                                      *
- *          num_old - [IN] number of elements in the old file list            *
- *          new     - [IN] new file list                                      *
- *          num_new - [IN] number of elements in the new file list            *
- *          use_ino - [IN] how to use inodes in is_same_file()                *
- *          err_msg - [IN/OUT] error message why an item became NOTSUPPORTED  *
+ *     rotation_type - [IN] file rotation type                                *
+ *     old2new       - [IN] two dimensional array of possible mappings        *
+ *     old           - [IN] old file list                                     *
+ *     num_old       - [IN] number of elements in the old file list           *
+ *     new           - [IN] new file list                                     *
+ *     num_new       - [IN] number of elements in the new file list           *
+ *     use_ino       - [IN] how to use inodes in is_same_file()               *
+ *     err_msg       - [IN/OUT] error message why an item became NOTSUPPORTED *
  *                                                                            *
  * Return value: SUCCEED or FAIL                                              *
  *                                                                            *
  * Comments:                                                                  *
- *    The array is filled with '0' and '1' which mean:                        *
+ *    The array is filled with '0', '1' and '2'  which mean:                  *
  *       old2new[i][j] = '0' - the i-th old file IS NOT the j-th new file     *
  *       old2new[i][j] = '1' - the i-th old file COULD BE the j-th new file   *
+ *       old2new[i][j] = '2' - the j-th new file is a copy of the i-th old    *
+ *                             file                                           *
  *                                                                            *
  ******************************************************************************/
-static int	setup_old2new(char *old2new, struct st_logfile *old, int num_old,
-		const struct st_logfile *new, int num_new, int use_ino, char **err_msg)
+static int	setup_old2new_and_copy_of(int rotation_type, char *old2new, struct st_logfile *old,
+		int num_old, struct st_logfile *new, int num_new, int use_ino, char **err_msg)
 {
-	int	i, j, rc;
+	int	i, j;
 	char	*p = old2new;
 
 	for (i = 0; i < num_old; i++)
@@ -638,8 +754,13 @@
 	{
 		for (j = 0; j < num_new; j++)
 		{
-			rc = is_same_file(old + i, new + j, use_ino, err_msg);
+			int	rc;
 
+			if (ZBX_LOG_ROTATION_LOGRT == rotation_type)
+				rc = is_same_file_logrt(old + i, new + j, use_ino, err_msg);
+			else
+				rc = is_same_file_logcpt(old + i, new + j, use_ino, err_msg);
+
 			switch (rc)
 			{
 				case ZBX_SAME_FILE_NO:
@@ -655,9 +776,13 @@
 					}
 					p[j] = '1';
 					break;
+				case ZBX_SAME_FILE_COPY:
+					p[j] = '2';
+					new[j].copy_of = i;
+					break;
 				case ZBX_SAME_FILE_RETRY:
 					old[i].retry = 1;
-					/* break; is not missing here */
+					return FAIL;
 				case ZBX_SAME_FILE_ERROR:
 					return FAIL;
 			}
@@ -664,7 +789,7 @@
 
 			if (SUCCEED == zabbix_check_log_level(LOG_LEVEL_DEBUG))
 			{
-				zabbix_log(LOG_LEVEL_DEBUG, "setup_old2new: is_same_file(%s, %s) = %c",
+				zabbix_log(LOG_LEVEL_DEBUG, "setup_old2new_and_copy_of: is_same_file(%s, %s) = %c",
 						old[i].filename, new[j].filename, p[j]);
 			}
 		}
@@ -734,7 +859,7 @@
  *                                                                            *
  * Function: is_uniq_row                                                      *
  *                                                                            *
- * Purpose: check if there is only one element '1' in the given row           *
+ * Purpose: check if there is only one element '1' or '2' in the given row    *
  *                                                                            *
  * Parameters:                                                                *
  *          arr    - [IN] two dimensional array                               *
@@ -741,13 +866,14 @@
  *          n_cols - [IN] number of columns in the array                      *
  *          row    - [IN] number of row to search                             *
  *                                                                            *
- * Return value: number of column where the '1' element was found or          *
- *               -1 if there are zero or multiple '1' elements in the row     *
+ * Return value: number of column where the element '1' or '2' was found or   *
+ *               -1 if there are zero or multiple elements '1' or '2' in the  *
+ *               row                                                          *
  *                                                                            *
  ******************************************************************************/
 static int	is_uniq_row(const char *arr, int n_cols, int row)
 {
-	int		i, ones = 0, ret = -1;
+	int		i, mappings = 0, ret = -1;
 	const char	*p;
 
 	p = arr + row * n_cols;			/* point to the first element of the 'row' */
@@ -754,9 +880,9 @@
 
 	for (i = 0; i < n_cols; i++)
 	{
-		if ('1' == *p++)
+		if ('1' == *p || '2' == *p)
 		{
-			if (2 == ++ones)
+			if (2 == ++mappings)
 			{
 				ret = -1;	/* non-unique mapping in the row */
 				break;
@@ -764,6 +890,8 @@
 
 			ret = i;
 		}
+
+		p++;
 	}
 
 	return ret;
@@ -773,7 +901,7 @@
  *                                                                            *
  * Function: is_uniq_col                                                      *
  *                                                                            *
- * Purpose: check if there is only one element '1' in the given column        *
+ * Purpose: check if there is only one element '1' or '2' in the given column *
  *                                                                            *
  * Parameters:                                                                *
  *          arr    - [IN] two dimensional array                               *
@@ -781,13 +909,14 @@
  *          n_cols - [IN] number of columns in the array                      *
  *          col    - [IN] number of column to search                          *
  *                                                                            *
- * Return value: number of row where the '1' element was found or             *
- *               -1 if there are zero or multiple '1' elements in the column  *
+ * Return value: number of column where the element '1' or '2 ' was found or  *
+ *               -1 if there are zero or multiple elements '1' or '2' in the  *
+ *               row                                                          *
  *                                                                            *
  ******************************************************************************/
 static int	is_uniq_col(const char *arr, int n_rows, int n_cols, int col)
 {
-	int		i, ones = 0, ret = -1;
+	int		i, mappings = 0, ret = -1;
 	const char	*p;
 
 	p = arr + col;				/* point to the top element of the 'col' */
@@ -794,9 +923,9 @@
 
 	for (i = 0; i < n_rows; i++)
 	{
-		if ('1' == *p)
+		if ('1' == *p || '2' == *p)
 		{
-			if (2 == ++ones)
+			if (2 == ++mappings)
 			{
 				ret = -1;	/* non-unique mapping in the column */
 				break;
@@ -825,11 +954,11 @@
  ******************************************************************************/
 static void	resolve_old2new(char *old2new, int num_old, int num_new)
 {
-	int	i, j, ones;
+	int	i, j, mappings;
 	char	*p, *protected_rows = NULL, *protected_cols = NULL;
 
 	/* Is there 1:1 mapping in both directions between files in the old and the new list ? */
-	/* In this case every row and column has not more than one element '1'. */
+	/* In this case every row and column has not more than one element '1' or '2', others are '0'. */
 	/* This is expected on UNIX (using inode numbers) and MS Windows (using FileID on NTFS, ReFS) */
 
 	p = old2new;
@@ -836,15 +965,17 @@
 
 	for (i = 0; i < num_old; i++)		/* loop over rows (old files) */
 	{
-		ones = 0;
+		mappings = 0;
 
 		for (j = 0; j < num_new; j++)	/* loop over columns (new files) */
 		{
-			if ('1' == *p++)
+			if ('1' == *p || '2' == *p)
 			{
-				if (2 == ++ones)
+				if (2 == ++mappings)
 					goto non_unique;
 			}
+
+			p++;
 		}
 	}
 
@@ -851,13 +982,13 @@
 	for (i = 0; i < num_new; i++)		/* loop over columns */
 	{
 		p = old2new + i;
-		ones = 0;
+		mappings = 0;
 
 		for (j = 0; j < num_old; j++)	/* loop over rows */
 		{
-			if ('1' == *p)
+			if ('1' == *p || '2' == *p)
 			{
-				if (2 == ++ones)
+				if (2 == ++mappings)
 					goto non_unique;
 			}
 			p += num_new;
@@ -928,7 +1059,7 @@
 
 			for (j = 0; j < num_new; j++)
 			{
-				if ('1' == p[j] && '1' != protected_cols[j])
+				if (('1' == p[j] || '2' == p[j]) && '1' != protected_cols[j])
 				{
 					cross_out(old2new, num_old, num_new, i, j, protected_rows, protected_cols);
 					break;
@@ -980,7 +1111,7 @@
 
 			for (j = num_new - 1; j >= 0; j--)
 			{
-				if ('1' == p[j] && '1' != protected_cols[j])
+				if (('1' == p[j] || '2' == p[j]) && '1' != protected_cols[j])
 				{
 					cross_out(old2new, num_old, num_new, i, j, protected_rows, protected_cols);
 					break;
@@ -1011,14 +1142,14 @@
 static int	find_old2new(char *old2new, int num_new, int i_old)
 {
 	int	i;
-	char	*p;
+	char	*p = old2new + i_old * num_new;
 
-	p = old2new + i_old * num_new;
-
 	for (i = 0; i < num_new; i++)		/* loop over columns (new files) on i_old-th row */
 	{
-		if ('1' == *p++)
+		if ('1' == *p || '2' == *p)
 			return i;
+
+		p++;
 	}
 
 	return -1;
@@ -1036,8 +1167,6 @@
  *             filename - name of a logfile (with full path)                  *
  *             st - structure returned by stat()                              *
  *                                                                            *
- * Return value: none                                                         *
- *                                                                            *
  * Author: Dmitry Borovikov                                                   *
  *                                                                            *
  ******************************************************************************/
@@ -1050,7 +1179,6 @@
 	zabbix_log(LOG_LEVEL_DEBUG, "In %s() filename:'%s' mtime:%d size:" ZBX_FS_UI64, __function_name, filename,
 			(int)st->st_mtime, (zbx_uint64_t)st->st_size);
 
-	/* must be done in any case */
 	if (*logfiles_alloc == *logfiles_num)
 	{
 		*logfiles_alloc += 64;
@@ -1113,6 +1241,7 @@
 	(*logfiles)[i].md5size = -1;
 	(*logfiles)[i].seq = 0;
 	(*logfiles)[i].incomplete = 0;
+	(*logfiles)[i].copy_of = -1;
 #ifndef _WINDOWS
 	(*logfiles)[i].dev = (zbx_uint64_t)st->st_dev;
 	(*logfiles)[i].ino_lo = (zbx_uint64_t)st->st_ino;
@@ -1296,6 +1425,78 @@
 #endif
 }
 
+static int	compile_filename_regexp(regex_t *re, const char *filename_regexp, char **err_msg)
+{
+	int	err_code;
+
+	if (0 != (err_code = regcomp(re, filename_regexp, REG_EXTENDED | REG_NEWLINE | REG_NOSUB)))
+	{
+		char	err_buf[MAX_STRING_LEN];
+
+		regerror(err_code, re, err_buf, sizeof(err_buf));
+
+		*err_msg = zbx_dsprintf(*err_msg, "Cannot compile a regular expression describing filename pattern: %s",
+				err_buf);
+#ifdef _WINDOWS
+		/* the Windows gnuregex implementation does not correctly clean up */
+		/* allocated memory after regcomp() failure                        */
+		regfree(re);
+#endif
+		return FAIL;
+	}
+
+	return SUCCEED;
+}
+
+#ifdef _WINDOWS
+static int	fill_file_details(struct st_logfile **logfiles, int logfiles_num, int use_ino, char **err_msg)
+#else
+static int	fill_file_details(struct st_logfile **logfiles, int logfiles_num, char **err_msg)
+#endif
+{
+	int	i, ret = SUCCEED;
+
+	/* Fill in MD5 sums and file indexes in the logfile list. */
+	/* These operations require opening of file, therefore we group them together. */
+
+	for (i = 0; i < logfiles_num; i++)
+	{
+		int			f;
+		struct st_logfile	*p = *logfiles + i;
+
+		if (-1 == (f = zbx_open(p->filename, O_RDONLY)))
+		{
+			*err_msg = zbx_dsprintf(*err_msg, "Cannot open file \"%s\": %s", p->filename,
+					zbx_strerror(errno));
+			return FAIL;
+		}
+
+		p->md5size = (zbx_uint64_t)MAX_LEN_MD5 > p->size ? (int)p->size : MAX_LEN_MD5;
+
+		if (SUCCEED != file_start_md5(f, p->md5size, p->md5buf, p->filename, err_msg))
+		{
+			ret = FAIL;
+			goto clean;
+		}
+#ifdef _WINDOWS
+		if (SUCCEED != file_id(f, use_ino, &p->dev, &p->ino_lo, &p->ino_hi, p->filename, err_msg))
+			ret = FAIL;
+#endif	/*_WINDOWS*/
+clean:
+		if (0 != close(f))
+		{
+			*err_msg = zbx_dsprintf(*err_msg, "Cannot close file \"%s\": %s", p->filename,
+					zbx_strerror(errno));
+			return FAIL;
+		}
+
+		if (FAIL == ret)
+			return FAIL;
+	}
+
+	return ret;
+}
+
 /******************************************************************************
  *                                                                            *
  * Function: make_logfile_list                                                *
@@ -1304,7 +1505,7 @@
  *          parameter                                                         *
  *                                                                            *
  * Parameters:                                                                *
- *     flags          - [IN] metric flags to check item type: log or logrt    *
+ *     flags          - [IN] bit flags with item type: log or logrt           *
  *     filename       - [IN] logfile name (regular expression with a path)    *
  *     mtime          - [IN] last modification time of the file               *
  *     logfiles       - [IN/OUT] pointer to the list of logfiles              *
@@ -1320,11 +1521,12 @@
 static int	make_logfile_list(unsigned char flags, const char *filename, const int *mtime,
 		struct st_logfile **logfiles, int *logfiles_alloc, int *logfiles_num, int *use_ino, char **err_msg)
 {
-	int		ret = SUCCEED, i;
-	zbx_stat_t	file_buf;
+	int	ret = SUCCEED;
 
 	if (0 != (ZBX_METRIC_FLAG_LOG_LOG & flags))	/* log[] item */
 	{
+		zbx_stat_t	file_buf;
+
 		if (0 != zbx_stat(filename, &file_buf))
 		{
 			*err_msg = zbx_dsprintf(*err_msg, "Cannot obtain information for file \"%s\": %s", filename,
@@ -1352,32 +1554,21 @@
 		*use_ino = 1;
 #endif
 	}
-	else if (0 != (ZBX_METRIC_FLAG_LOG_LOGRT & flags))	/* logrt[] item */
+	else if (0 != (ZBX_METRIC_FLAG_LOG_LOGRT & flags))	/* logrt[] */
 	{
-		char	*directory = NULL, *format = NULL;
-		int	reg_error;
+		char	*directory = NULL, *filename_regexp = NULL;
 		regex_t	re;
 
-		/* split a filename into directory and file mask (regular expression) parts */
-		if (SUCCEED != split_filename(filename, &directory, &format, err_msg))
+		/* split a filename into directory and file name regular expression parts */
+		if (SUCCEED != split_filename(filename, &directory, &filename_regexp, err_msg))
 		{
 			ret = FAIL;
 			goto clean;
 		}
 
-		if (0 != (reg_error = regcomp(&re, format, REG_EXTENDED | REG_NEWLINE | REG_NOSUB)))
+		if (SUCCEED != compile_filename_regexp(&re, filename_regexp, err_msg))
 		{
-			char	err_buf[MAX_STRING_LEN];
-
-			regerror(reg_error, &re, err_buf, sizeof(err_buf));
-			*err_msg = zbx_dsprintf(*err_msg, "Cannot compile a regular expression describing filename"
-					" pattern: %s", err_buf);
 			ret = FAIL;
-#ifdef _WINDOWS
-			/* the Windows gnuregex implementation does not correctly clean up */
-			/* allocated memory after regcomp() failure                        */
-			regfree(&re);
-#endif
 			goto clean1;
 		}
 
@@ -1390,11 +1581,11 @@
 
 		if (0 == *logfiles_num)
 		{
-			/* Do not make a logrt[] item NOTSUPPORTED if there are no matching log files or they are not */
-			/* accessible (can happen during a rotation), just log the problem. */
+			/* do not make a logrt[] item NOTSUPPORTED if there are no matching log files or */
+			/* they are not accessible (can happen during a rotation), just log the problem */
 #ifdef _WINDOWS
 			zabbix_log(LOG_LEVEL_WARNING, "there are no files matching \"%s\" in \"%s\" or insufficient "
-					"access rights", format, directory);
+					"access rights", filename_regexp, directory);
 #else
 			if (0 != access(directory, X_OK))
 			{
@@ -1403,8 +1594,8 @@
 			}
 			else
 			{
-				zabbix_log(LOG_LEVEL_WARNING, "there are no files matching \"%s\" in \"%s\"", format,
-						directory);
+				zabbix_log(LOG_LEVEL_WARNING, "there are no files matching \"%s\" in \"%s\"",
+						filename_regexp, directory);
 			}
 #endif
 		}
@@ -1412,7 +1603,7 @@
 		regfree(&re);
 clean1:
 		zbx_free(directory);
-		zbx_free(format);
+		zbx_free(filename_regexp);
 
 		if (FAIL == ret)
 			goto clean;
@@ -1420,43 +1611,11 @@
 	else
 		THIS_SHOULD_NEVER_HAPPEN;
 
-	/* Fill in MD5 sums and file indexes in the logfile list. */
-	/* These operations require opening of file, therefore we group them together. */
-	for (i = 0; i < *logfiles_num; i++)
-	{
-		int			f;
-		struct st_logfile	*p;
-
-		p = *logfiles + i;
-
-		if (-1 == (f = zbx_open(p->filename, O_RDONLY)))
-		{
-			*err_msg = zbx_dsprintf(*err_msg, "Cannot open file \"%s\": %s", p->filename,
-					zbx_strerror(errno));
-			ret = FAIL;
-			break;
-		}
-
-		p->md5size = (zbx_uint64_t)MAX_LEN_MD5 > p->size ? (int)p->size : MAX_LEN_MD5;
-
-		if (SUCCEED != file_start_md5(f, p->md5size, p->md5buf, p->filename, err_msg))
-		{
-			ret = FAIL;
-			goto clean3;
-		}
 #ifdef _WINDOWS
-		if (SUCCEED != file_id(f, *use_ino, &p->dev, &p->ino_lo, &p->ino_hi, p->filename, err_msg))
-			ret = FAIL;
-#endif	/*_WINDOWS*/
-clean3:
-		if (0 != close(f))
-		{
-			*err_msg = zbx_dsprintf(*err_msg, "Cannot close file \"%s\": %s", p->filename,
-					zbx_strerror(errno));
-			ret = FAIL;
-			break;
-		}
-	}
+	ret = fill_file_details(logfiles, *logfiles_num, *use_ino, err_msg);
+#else
+	ret = fill_file_details(logfiles, *logfiles_num, err_msg);
+#endif
 clean:
 	if (FAIL == ret && NULL != *logfiles)
 		destroy_logfile_list(logfiles, logfiles_alloc, logfiles_num);
@@ -1772,7 +1931,7 @@
  *          records to Zabbix server                                          *
  *                                                                            *
  * Parameters:                                                                *
- *     flags           - [IN] metric flags to check item type: log or logrt   *
+ *     flags           - [IN] bit flags with item type: log or logrt          *
  *     filename        - [IN] logfile name                                    *
  *     lastlogsize     - [IN/OUT] offset from the beginning of the file       *
  *     mtime           - [IN] file modification time for reporting to server  *
@@ -1888,6 +2047,138 @@
 	return ret;
 }
 
+static void	adjust_mtime_to_clock(int *mtime)
+{
+	time_t	now;
+
+	/* Adjust 'mtime' if the system clock has been set back in time. */
+	/* Setting the clock ahead of time is harmless in our case. */
+
+	if (*mtime > (now = time(NULL)))
+	{
+		int	old_mtime;
+
+		old_mtime = *mtime;
+		*mtime = (int)now;
+
+		zabbix_log(LOG_LEVEL_WARNING, "System clock has been set back in time. Setting agent mtime %d "
+				"seconds back.", (int)(old_mtime - now));
+	}
+}
+
+static int	is_swap_required(const struct st_logfile *old, struct st_logfile *new, int use_ino, int idx)
+{
+	int	is_same_place;
+
+	/* if the 1st file is not processed at all while the 2nd file was processed (at least partially) */
+	/* then swap them */
+	if (0 == new[idx].seq && 0 < new[idx + 1].seq)
+		return SUCCEED;
+
+	/* if the 2nd file is not a copy of some other file then no need to swap */
+	if (-1 == new[idx + 1].copy_of)
+		return FAIL;
+
+	/* The 2nd file is a copy. But is it a copy of the 1st file ? */
+
+	/* On file systems with inodes or file indices if a file is copied and truncated, we assume that */
+	/* there is a high possibility that the truncated file has the same inode (index) as before. */
+
+	if (NULL == old)	/* cannot consult the old file list */
+		return FAIL;
+
+	is_same_place = compare_file_places(old + new[idx + 1].copy_of, new + idx, use_ino);
+
+	if (ZBX_FILE_PLACE_SAME == is_same_place && new[idx].seq <= new[idx + 1].seq)
+		return SUCCEED;
+
+	/* The last attempt - compare file names. It is less reliable as file rotation can change file names. */
+	if (ZBX_FILE_PLACE_OTHER == is_same_place || ZBX_FILE_PLACE_UNKNOWN == is_same_place)
+	{
+		if (0 == strcmp((old + new[idx + 1].copy_of)->filename, (new + idx)->filename))
+			return SUCCEED;
+	}
+
+	return FAIL;
+}
+
+static void	swap_logfile_array_elements(struct st_logfile *array, int idx1, int idx2)
+{
+	struct st_logfile	*p1 = array + idx1;
+	struct st_logfile	*p2 = array + idx2;
+	struct st_logfile	tmp;
+
+	memcpy(&tmp, p1, sizeof(struct st_logfile));
+	memcpy(p1, p2, sizeof(struct st_logfile));
+	memcpy(p2, &tmp, sizeof(struct st_logfile));
+}
+
+static void	ensure_order_if_mtimes_equal(const struct st_logfile *logfiles_old, struct st_logfile *logfiles,
+		int logfiles_num, int use_ino, int *start_idx)
+{
+	int	i;
+
+	/* There is a special case when within 1 second of time:       */
+	/*   1. a log file ORG.log is copied to other file COPY.log,   */
+	/*   2. the original file ORG.log is truncated,                */
+	/*   3. new records are appended to the original file ORG.log, */
+	/*   4. both files ORG.log and COPY.log have the same 'mtime'. */
+	/* Now in the list 'logfiles' the file ORG.log precedes the COPY.log because if 'mtime' is the same   */
+	/* then add_logfile() function sorts files by name in descending order. This would lead to an error - */
+	/* processing ORG.log before COPY.log. We need to correct the order by swapping ORG.log and COPY.log  */
+	/* elements in the 'logfiles' list. */
+
+	for (i = 0; i < logfiles_num - 1; i++)
+	{
+		if (logfiles[i].mtime == logfiles[i + 1].mtime &&
+				SUCCEED == is_swap_required(logfiles_old, logfiles, use_ino, i))
+		{
+			zabbix_log(LOG_LEVEL_DEBUG, "ensure_order_if_mtimes_equal() swapping files '%s' and '%s'",
+					logfiles[i].filename, logfiles[i + 1].filename);
+
+			swap_logfile_array_elements(logfiles, i, i + 1);
+
+			if (*start_idx == i + 1)
+				*start_idx = i;
+		}
+	}
+}
+
+static void	handle_multiple_copies(struct st_logfile *logfiles, int logfiles_num, int i)
+{
+	/* There is a special case when the latest log file is copied to other file but not yet truncated. */
+	/* So there are two files and we don't know which one will stay as the copy and which one will be  */
+	/* truncated. Similar cases: the latest log file is copied but not truncated or is copied multiple */
+	/* times. */
+
+	int	j;
+
+	for (j = i + 1; j < logfiles_num; j++)
+	{
+		if (-1 != logfiles[i].md5size && -1 != logfiles[j].md5size &&
+				logfiles[i].md5size == logfiles[j].md5size &&
+				0 == memcmp(logfiles[i].md5buf, logfiles[j].md5buf, sizeof(logfiles[i].md5buf)))
+		{
+			/* logfiles[i] and logfiles[j] are original and copy (or vice versa). */
+			/* If logfiles[i] has been at least partially processed then transfer its */
+			/* processed size to logfiles[j], too. */
+
+			if (0 < logfiles[i].processed_size)
+			{
+				if (logfiles[i].processed_size <= logfiles[j].size)
+					logfiles[j].processed_size = logfiles[i].processed_size;
+				else
+					logfiles[j].processed_size = logfiles[j].size;
+
+				zabbix_log(LOG_LEVEL_DEBUG, "handle_multiple_copies() file '%s' processed_size:"
+						ZBX_FS_UI64 " transferred to" " file '%s' processed_size:" ZBX_FS_UI64,
+						logfiles[i].filename, logfiles[i].processed_size,
+						logfiles[j].filename, logfiles[j].processed_size);
+			}
+		}
+	}
+}
+
 /******************************************************************************
  *                                                                            *
  * Function: process_logrt                                                    *
@@ -1895,7 +2186,7 @@
  * Purpose: Find new records in logfiles                                      *
  *                                                                            *
  * Parameters:                                                                *
- *     flags            - [IN] metric flags to check item type: log or logrt  *
+ *     flags            - [IN] bit flags with item type: log or logrt         *
  *     filename         - [IN] logfile name (regular expression with a path)  *
  *     lastlogsize      - [IN/OUT] offset from the beginning of the file      *
  *     mtime            - [IN/OUT] last modification time of the file         *
@@ -1925,6 +2216,7 @@
  *     port             - [IN] port to send data to                           *
  *     hostname         - [IN] hostname the data comes from                   *
  *     key              - [IN] item key the data belongs to                   *
+ *     rotation_type    - [IN] simple rotation or copy/truncate rotation      *
  *                                                                            *
  * Return value: returns SUCCEED on successful reading,                       *
  *               FAIL on other cases                                          *
@@ -1937,7 +2229,7 @@
 		int *use_ino, char **err_msg, struct st_logfile **logfiles_old, int *logfiles_num_old,
 		const char *encoding, zbx_vector_ptr_t *regexps, const char *pattern, const char *output_template,
 		int *p_count, int *s_count, zbx_process_value_func_t process_value, const char *server,
-		unsigned short port, const char *hostname, const char *key)
+		unsigned short port, const char *hostname, const char *key, int rotation_type)
 {
 	const char		*__function_name = "process_logrt";
 	int			i, j, start_idx, ret = FAIL, logfiles_num = 0, logfiles_alloc = 0, seq = 1,
@@ -1944,24 +2236,12 @@
 				max_old_seq = 0, old_last, from_first_file = 1;
 	char			*old2new = NULL;
 	struct st_logfile	*logfiles = NULL;
-	time_t			now;
 
-	zabbix_log(LOG_LEVEL_DEBUG, "In %s() is_logrt:%d filename:'%s' lastlogsize:" ZBX_FS_UI64 " mtime:%d",
-			__function_name, ZBX_METRIC_FLAG_LOG_LOGRT & flags, filename, *lastlogsize, *mtime);
+	zabbix_log(LOG_LEVEL_DEBUG, "In %s() flags:0x%02x filename:'%s' lastlogsize:" ZBX_FS_UI64 " mtime:%d",
+			__function_name, (unsigned int)flags, filename, *lastlogsize, *mtime);
 
-	/* Minimize data loss if the system clock has been set back in time. */
-	/* Setting the clock ahead of time is harmless in our case. */
-	if (*mtime > (now = time(NULL)))
-	{
-		int	old_mtime;
+	adjust_mtime_to_clock(mtime);
 
-		old_mtime = *mtime;
-		*mtime = (int)now;
-
-		zabbix_log(LOG_LEVEL_WARNING, "System clock has been set back in time. Setting agent mtime %d "
-				"seconds back.", (int)(old_mtime - now));
-	}
-
 	if (SUCCEED != make_logfile_list(flags, filename, mtime, &logfiles, &logfiles_alloc, &logfiles_num, use_ino,
 			err_msg))
 	{
@@ -1976,14 +2256,19 @@
 		goto out;
 	}
 
-	start_idx = (1 == *skip_old_data ? logfiles_num - 1 : 0);
+	if (1 == *skip_old_data)
+	{
+		start_idx = logfiles_num - 1;
 
-	/* mark files to be skipped as processed (in case of 'skip_old_data' was set) */
-	for (i = 0; i < start_idx; i++)
-	{
-		logfiles[i].processed_size = logfiles[i].size;
-		logfiles[i].seq = seq++;
+		/* mark files to be skipped as processed (except the last one) */
+		for (i = 0; i < start_idx; i++)
+		{
+			logfiles[i].processed_size = logfiles[i].size;
+			logfiles[i].seq = seq++;
+		}
 	}
+	else
+		start_idx = 0;
 
 	if (0 < *logfiles_num_old && 0 < logfiles_num)
 	{
@@ -1990,8 +2275,8 @@
 		/* set up a mapping array from old files to new files */
 		old2new = zbx_malloc(old2new, (size_t)logfiles_num * (size_t)(*logfiles_num_old) * sizeof(char));
 
-		if (SUCCEED != setup_old2new(old2new, *logfiles_old, *logfiles_num_old, logfiles, logfiles_num,
-				*use_ino, err_msg))
+		if (SUCCEED != setup_old2new_and_copy_of(rotation_type, old2new, *logfiles_old, *logfiles_num_old,
+				logfiles, logfiles_num, *use_ino, err_msg))
 		{
 			destroy_logfile_list(&logfiles, &logfiles_alloc, &logfiles_num);
 			zbx_free(old2new);
@@ -2054,6 +2339,9 @@
 		}
 	}
 
+	if (ZBX_LOG_ROTATION_LOGCPT == rotation_type && 1 < logfiles_num)
+		ensure_order_if_mtimes_equal(*logfiles_old, logfiles, logfiles_num, *use_ino, &start_idx);
+
 	zbx_free(old2new);
 
 	if (SUCCEED == zabbix_check_log_level(LOG_LEVEL_DEBUG))
@@ -2088,6 +2376,14 @@
 	/* from now assume success - it could be that there is nothing to do */
 	ret = SUCCEED;
 
+	if (ZBX_LOG_ROTATION_LOGCPT == rotation_type && 1 < logfiles_num)
+	{
+		int	k;
+
+		for (k = 0; k < logfiles_num - 1; k++)
+			handle_multiple_copies(logfiles, logfiles_num, k);
+	}
+
 	while (i < logfiles_num)
 	{
 		if (0 == logfiles[i].incomplete && (logfiles[i].size != logfiles[i].processed_size ||
@@ -2111,6 +2407,9 @@
 			/* and we will know where we left off. */
 			logfiles[i].seq = seq++;
 
+			if (ZBX_LOG_ROTATION_LOGCPT == rotation_type && 1 < logfiles_num)
+				handle_multiple_copies(logfiles, logfiles_num, i);
+
 			if (SUCCEED != ret)
 				break;
 
Index: src/zabbix_agent/logfiles.h
===================================================================
--- src/zabbix_agent/logfiles.h	(revision 74683)
+++ src/zabbix_agent/logfiles.h	(working copy)
@@ -23,6 +23,9 @@
 #include "zbxregexp.h"
 #include "md5.h"
 
+#define ZBX_LOG_ROTATION_LOGRT	0	/* pure rotation model */
+#define ZBX_LOG_ROTATION_LOGCPT	1	/* copy-truncate rotation model */
+
 struct	st_logfile
 {
 	char		*filename;
@@ -32,6 +35,8 @@
 	int		retry;
 	int		incomplete;	/* 0 - the last record ends with a newline, 1 - the last record contains */
 					/* no newline at the end */
+	int		copy_of;	/* '-1' - the file is not a copy. '0 <= copy_of' - this file is a copy of */
+					/* the file with index 'copy_of' in the old log file list. */
 	zbx_uint64_t	dev;		/* ID of device containing file */
 	zbx_uint64_t	ino_lo;		/* UNIX: inode number. Microsoft Windows: nFileIndexLow or FileId.LowPart */
 	zbx_uint64_t	ino_hi;		/* Microsoft Windows: nFileIndexHigh or FileId.HighPart */
@@ -45,6 +50,6 @@
 		int *use_ino, char **err_msg, struct st_logfile **logfiles_old, int *logfiles_num_old,
 		const char *encoding, zbx_vector_ptr_t *regexps, const char *pattern, const char *output_template,
 		int *p_count, int *s_count, zbx_process_value_func_t process_value, const char *server,
-		unsigned short port, const char *hostname, const char *key);
+		unsigned short port, const char *hostname, const char *key, int rotation_type);
 
 #endif
Index: .
===================================================================
--- .	(revision 74683)
+++ .	(working copy)

Property changes on: .
___________________________________________________________________
Modified: svn:mergeinfo
## -0,0 +0,1 ##
   Merged /branches/dev/DEV-653-3:r74169-74683
