Blame SOURCES/rsyslog-8.37.0-rhbz1614179-imfile-symlink-support.patch

45f82e
From 3822da837e4d531e8a9cd78ae76359a410f8d98d Mon Sep 17 00:00:00 2001
45f82e
From: Jiri Vymazal <jvymazal@redhat.com>
45f82e
Date: Thu, 31 May 2018 16:30:08 +0200
45f82e
Subject: [PATCH] Symlink support for imfile
45f82e
45f82e
this introduces symlink detection and following as well
45f82e
as monitoring changes on them. Also added test for the new
45f82e
functionality and ensuring the original symlink behavior
45f82e
stays as well.
45f82e
---
45f82e
 plugins/imfile/imfile.c       | 182 +++++++++++++++++++++++++++----------
45f82e
 1 file changed, 133 insertions(+), 49 deletions(-)
45f82e
45f82e
diff --git a/plugins/imfile/imfile.c b/plugins/imfile/imfile.c
45f82e
index 3c9308bfe..4ca23d2ca 100644
45f82e
--- a/plugins/imfile/imfile.c
45f82e
+++ b/plugins/imfile/imfile.c
45f82e
@@ -152,6 +152,7 @@ struct act_obj_s {
45f82e
 	fs_edge_t *edge;	/* edge which this object belongs to */
45f82e
 	char *name;		/* full path name of active object */
45f82e
 	char *basename;		/* only basename */ //TODO: remove when refactoring rename support
45f82e
+	char *source_name;  /* if this object is target of a symlink, source_name is its name (else NULL) */
45f82e
 	//char *statefile;	/* base name of state file (for move operations) */
45f82e
 	int wd;
45f82e
 #if defined(OS_SOLARIS) && defined (HAVE_PORT_SOURCE_FILE)
45f82e
@@ -167,6 +168,7 @@ struct act_obj_s {
45f82e
 	int nRecords; /**< How many records did we process before persisting the stream? */
45f82e
 	ratelimit_t *ratelimiter;
45f82e
 	multi_submit_t multiSub;
45f82e
+	int is_symlink;
45f82e
 };
45f82e
 struct fs_edge_s {
45f82e
 	fs_node_t *parent;
45f82e
@@ -181,7 +182,8 @@ struct act_obj_s {
45f82e
 	instanceConf_t **instarr;
45f82e
 };
45f82e
 struct fs_node_s {
45f82e
-	fs_edge_t *edges;
45f82e
+	fs_edge_t *edges;	/* NULL in leaf nodes */
45f82e
+	fs_node_t *root;	/* node one level up (NULL for file system root) */
45f82e
 };
45f82e
 
45f82e
 
45f82e
@@ -189,7 +191,7 @@ static rsRetVal persistStrmState(act_obj_t *);
45f82e
 static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal);
45f82e
 static rsRetVal ATTR_NONNULL(1) pollFile(act_obj_t *act);
45f82e
 static int ATTR_NONNULL() getBasename(uchar *const __restrict__ basen, uchar *const __restrict__ path);
45f82e
-static void ATTR_NONNULL() act_obj_unlink(act_obj_t *const act);
45f82e
+static void ATTR_NONNULL() act_obj_unlink(act_obj_t *act);
45f82e
 static uchar * ATTR_NONNULL(1, 2) getStateFileName(const act_obj_t *, uchar *, const size_t);
45f82e
 static int ATTR_NONNULL() getFullStateFileName(const uchar *const, uchar *const pszout, const size_t ilenout);
45f82e
 
45f82e
@@ -483,14 +485,17 @@ in_setupWatch(act_obj_t *const act, const int is_file)
45f82e
 		goto done;
45f82e
 
45f82e
 	wd = inotify_add_watch(ino_fd, act->name,
45f82e
-		(is_file) ? IN_MODIFY : IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO);
45f82e
+		(is_file) ? IN_MODIFY|IN_DONT_FOLLOW : IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO);
45f82e
 	if(wd < 0) {
45f82e
-		LogError(errno, RS_RET_IO_ERROR, "imfile: cannot watch object '%s'",
45f82e
-			act->name);
45f82e
+		if (errno == EACCES) { /* There is high probability of selinux denial on top-level paths */
45f82e
+			DBGPRINTF("imfile: permission denied when adding watch for '%s'\n", act->name);
45f82e
+		} else {
45f82e
+			LogError(errno, RS_RET_IO_ERROR, "imfile: cannot watch object '%s'", act->name);
45f82e
+		}
45f82e
 		goto done;
45f82e
 	}
45f82e
 	wdmapAdd(wd, act);
45f82e
-	DBGPRINTF("in_setupDirWatch: watch %d added for dir %s(%p)\n", wd, act->name, act);
45f82e
+	DBGPRINTF("in_setupWatch: watch %d added for %s(object %p)\n", wd, act->name, act);
45f82e
 done:	return wd;
45f82e
 }
45f82e
 
45f82e
@@ -605,7 +610,7 @@ done:	return;
45f82e
 static void ATTR_NONNULL()
45f82e
 fen_setupWatch(act_obj_t *const act __attribute__((unused)))
45f82e
 {
45f82e
-	DBGPRINTF("fen_setupWatch: DUMMY CALLED - not on Solaris?");
45f82e
+	DBGPRINTF("fen_setupWatch: DUMMY CALLED - not on Solaris?\n");
45f82e
 }
45f82e
 #endif /* FEN */
45f82e
 
45f82e
@@ -633,38 +638,48 @@ fs_node_print(const fs_node_t *const node, const int level)
45f82e
 	}
45f82e
 }
45f82e
 
45f82e
-
45f82e
 /* add a new file system object if it not yet exists, ignore call
45f82e
  * if it already does.
45f82e
  */
45f82e
-static rsRetVal ATTR_NONNULL()
45f82e
+static rsRetVal ATTR_NONNULL(1,2)
45f82e
 act_obj_add(fs_edge_t *const edge, const char *const name, const int is_file,
45f82e
-	const ino_t ino)
45f82e
+	const ino_t ino, const int is_symlink, const char *const source)
45f82e
 {
45f82e
 	act_obj_t *act;
45f82e
 	char basename[MAXFNAME];
45f82e
 	DEFiRet;
45f82e
 	
45f82e
-	DBGPRINTF("act_obj_add: edge %p, name '%s'\n", edge, name);
45f82e
+	DBGPRINTF("act_obj_add: edge %p, name '%s' (source '%s')\n", edge, name, source? source : "---");
45f82e
 	for(act = edge->active ; act != NULL ; act = act->next) {
45f82e
 		if(!strcmp(act->name, name)) {
45f82e
-			DBGPRINTF("active object '%s' already exists in '%s' - no need to add\n",
45f82e
-				name, edge->path);
45f82e
-			FINALIZE;
45f82e
+			if (!source || !act->source_name || !strcmp(act->source_name, source)) {
45f82e
+				DBGPRINTF("active object '%s' already exists in '%s' - no need to add\n",
45f82e
+					name, edge->path);
45f82e
+				FINALIZE;
45f82e
+			}
45f82e
 		}
45f82e
 	}
45f82e
 	DBGPRINTF("add new active object '%s' in '%s'\n", name, edge->path);
45f82e
 	CHKmalloc(act = calloc(sizeof(act_obj_t), 1));
45f82e
 	CHKmalloc(act->name = strdup(name));
45f82e
-	getBasename((uchar*)basename, (uchar*)name);
45f82e
-	CHKmalloc(act->basename = strdup(basename));
45f82e
+	if (-1 == getBasename((uchar*)basename, (uchar*)name)) {
45f82e
+		CHKmalloc(act->basename = strdup(name)); /* assume basename is same as name */
45f82e
+	} else {
45f82e
+		CHKmalloc(act->basename = strdup(basename));
45f82e
+	}
45f82e
 	act->edge = edge;
45f82e
 	act->ino = ino;
45f82e
+	act->is_symlink = is_symlink;
45f82e
+	if (source) { /* we are target of symlink */
45f82e
+		CHKmalloc(act->source_name = strdup(source));
45f82e
+	} else {
45f82e
+		act->source_name = NULL;
45f82e
+	}
45f82e
 	#ifdef HAVE_INOTIFY_INIT
45f82e
 	act->wd = in_setupWatch(act, is_file);
45f82e
 	#endif
45f82e
 	fen_setupWatch(act);
45f82e
-	if(is_file) {
45f82e
+	if(is_file && !is_symlink) {
45f82e
 		const instanceConf_t *const inst = edge->instarr[0];// TODO: same file, multiple instances?
45f82e
 		CHKiRet(ratelimitNew(&act->ratelimiter, "imfile", name));
45f82e
 		CHKmalloc(act->multiSub.ppMsgs = MALLOC(inst->nMultiSub * sizeof(smsg_t *)));
45f82e
@@ -702,27 +717,24 @@ detect_updates(fs_edge_t *const edge)
45f82e
 {
45f82e
 	act_obj_t *act;
45f82e
 	struct stat fileInfo;
45f82e
+	int restart = 0;
45f82e
 
45f82e
-	for(act = edge->active ; act != NULL ; ) {
45f82e
+	for(act = edge->active ; act != NULL ; act = act->next) {
45f82e
 		DBGPRINTF("detect_updates checking active obj '%s'\n", act->name);
45f82e
-		const int r = stat(act->name, &fileInfo);
45f82e
+		const int r = lstat(act->name, &fileInfo);
45f82e
 		if(r == -1) { /* object gone away? */
45f82e
 			DBGPRINTF("object gone away, unlinking: '%s'\n", act->name);
45f82e
-			act_obj_t *toDel = act;
45f82e
-			act = act->next;
45f82e
-			DBGPRINTF("new next act %p\n", act);
45f82e
-			act_obj_unlink(toDel);
45f82e
-			continue;
45f82e
+			act_obj_unlink(act);
45f82e
+			restart = 1;
45f82e
+			break;
45f82e
 		}
45f82e
 		// TODO: add inode check for change notification!
45f82e
 
45f82e
-		/* Note: active nodes may get deleted, so we need to do the
45f82e
-		 * pointer advancement at the end of the for loop!
45f82e
-		 */
45f82e
-		act = act->next;
45f82e
 	}
45f82e
 
45f82e
-
45f82e
+	if (restart) {
45f82e
+		detect_updates(edge);
45f82e
+	}
45f82e
 }
45f82e
 
45f82e
 
45f82e
@@ -746,14 +758,52 @@ poll_active_files(fs_edge_t *const edge)
45f82e
 	}
45f82e
 }
45f82e
 
45f82e
+static rsRetVal ATTR_NONNULL()
45f82e
+process_symlink(fs_edge_t *const chld, const char *symlink)
45f82e
+{
45f82e
+	DEFiRet;
45f82e
+	char *target = NULL;
45f82e
+	CHKmalloc(target = realpath(symlink, target));
45f82e
+	struct stat fileInfo;
45f82e
+	if(lstat(target, &fileInfo) != 0) {
45f82e
+		LogError(errno, RS_RET_ERR,	"imfile: process_symlink: cannot stat file '%s' - ignored", target);
45f82e
+		FINALIZE;
45f82e
+	}
45f82e
+	const int is_file = (S_ISREG(fileInfo.st_mode));
45f82e
+	DBGPRINTF("process_symlink:  found '%s', File: %d (config file: %d), symlink: %d\n",
45f82e
+		target, is_file, chld->is_file, 0);
45f82e
+	if (act_obj_add(chld, target, is_file, fileInfo.st_ino, 0, symlink) == RS_RET_OK) {
45f82e
+		/* need to watch parent target as well for proper rotation support */
45f82e
+		uint idx = ustrlen(chld->active->name) - ustrlen(chld->active->basename);
45f82e
+		if (idx) { /* basename is different from name */
45f82e
+			char parent[MAXFNAME];
45f82e
+			idx--; /* move past trailing slash */
45f82e
+			memcpy(parent, chld->active->name, idx);
45f82e
+			parent[idx] = '\0';
45f82e
+			if(lstat(parent, &fileInfo) != 0) {
45f82e
+				LogError(errno, RS_RET_ERR,
45f82e
+					"imfile: process_symlink: cannot stat directory '%s' - ignored", parent);
45f82e
+				FINALIZE;
45f82e
+			}
45f82e
+			if (chld->parent->root->edges) {
45f82e
+				DBGPRINTF("process_symlink: adding parent '%s' of target '%s'\n", parent, target);
45f82e
+				act_obj_add(chld->parent->root->edges, parent, 0, fileInfo.st_ino, 0, NULL);
45f82e
+			}
45f82e
+		}
45f82e
+	}
45f82e
+
45f82e
+finalize_it:
45f82e
+	free(target);
45f82e
+	RETiRet;
45f82e
+}
45f82e
 
45f82e
-static void ATTR_NONNULL() poll_tree(fs_edge_t *const chld);
45f82e
 static void ATTR_NONNULL()
45f82e
 poll_tree(fs_edge_t *const chld)
45f82e
 {
45f82e
 	struct stat fileInfo;
45f82e
 	glob_t files;
45f82e
 	int need_globfree = 0;
45f82e
+	int issymlink;
45f82e
 	DBGPRINTF("poll_tree: chld %p, name '%s', path: %s\n", chld, chld->name, chld->path);
45f82e
 	detect_updates(chld);
45f82e
 	const int ret = glob((char*)chld->path, runModConf->sortFiles|GLOB_BRACE, NULL, &files);
45f82e
@@ -766,18 +803,27 @@ poll_tree(fs_edge_t *const chld)
45f82e
 				goto done;
45f82e
 			}
45f82e
 			char *const file = files.gl_pathv[i];
45f82e
-			if(stat(file, &fileInfo) != 0) {
45f82e
+			if(lstat(file, &fileInfo) != 0) {
45f82e
 				LogError(errno, RS_RET_ERR,
45f82e
 					"imfile: poll_tree cannot stat file '%s' - ignored", file);
45f82e
 				continue;
45f82e
 			}
45f82e
 
45f82e
-			const int is_file = S_ISREG(fileInfo.st_mode);
45f82e
-			DBGPRINTF("poll_tree:  found '%s', File: %d (config file: %d)\n",
45f82e
-				file, is_file, chld->is_file);
45f82e
+			if (S_ISLNK(fileInfo.st_mode)) {
45f82e
+				rsRetVal slink_ret = process_symlink(chld, file);
45f82e
+				if (slink_ret != RS_RET_OK) {
45f82e
+					continue;
45f82e
+				}
45f82e
+				issymlink = 1;
45f82e
+			} else {
45f82e
+				issymlink = 0;
45f82e
+			}
45f82e
+			const int is_file = (S_ISREG(fileInfo.st_mode) || issymlink);
45f82e
+			DBGPRINTF("poll_tree:  found '%s', File: %d (config file: %d), symlink: %d\n",
45f82e
+				file, is_file, chld->is_file, issymlink);
45f82e
 			if(!is_file && S_ISREG(fileInfo.st_mode)) {
45f82e
 				LogMsg(0, RS_RET_ERR, LOG_WARNING,
45f82e
-					"imfile: '%s' is neither a regular file nor a "
45f82e
+					"imfile: '%s' is neither a regular file, symlink, nor a "
45f82e
 					"directory - ignored", file);
45f82e
 				continue;
45f82e
 			}
45f82e
@@ -788,7 +834,7 @@ poll_tree(fs_edge_t *const chld)
45f82e
 					(chld->is_file) ? "FILE" : "DIRECTORY");
45f82e
 				continue;
45f82e
 			}
45f82e
-			act_obj_add(chld, file, is_file, fileInfo.st_ino);
45f82e
+			act_obj_add(chld, file, is_file, fileInfo.st_ino, issymlink, NULL);
45f82e
 		}
45f82e
 	}
45f82e
 
45f82e
@@ -829,8 +875,20 @@ act_obj_destroy(act_obj_t *const act, const int is_deleted)
45f82e
 	if(act == NULL)
45f82e
 		return;
45f82e
 
45f82e
-	DBGPRINTF("act_obj_destroy: act %p '%s', wd %d, pStrm %p, is_deleted %d, in_move %d\n",
45f82e
-		act, act->name, act->wd, act->pStrm, is_deleted, act->in_move);
45f82e
+	DBGPRINTF("act_obj_destroy: act %p '%s' (source '%s'), wd %d, pStrm %p, is_deleted %d, in_move %d\n",
45f82e
+		act, act->name, act->source_name? act->source_name : "---", act->wd, act->pStrm, is_deleted,
45f82e
+		act->in_move);
45f82e
+	if(act->is_symlink && is_deleted) {
45f82e
+		act_obj_t *target_act;
45f82e
+		for(target_act = act->edge->active ; target_act != NULL ; target_act = target_act->next) {
45f82e
+			if(target_act->source_name && !strcmp(target_act->source_name, act->name)) {
45f82e
+				DBGPRINTF("act_obj_destroy: unlinking slink target %s of %s "
45f82e
+						"symlink\n", target_act->name, act->name);
45f82e
+				act_obj_unlink(target_act);
45f82e
+				break;
45f82e
+			}
45f82e
+		}
45f82e
+	}
45f82e
 	if(act->ratelimiter != NULL) {
45f82e
 		ratelimitDestruct(act->ratelimiter);
45f82e
 	}
45f82e
@@ -862,6 +920,7 @@ act_obj_destroy(act_obj_t *const act, const int is_deleted)
45f82e
 	}
45f82e
 	#endif
45f82e
 	free(act->basename);
45f82e
+	free(act->source_name);
45f82e
 	//free(act->statefile);
45f82e
 	free(act->multiSub.ppMsgs);
45f82e
 	#if defined(OS_SOLARIS) && defined (HAVE_PORT_SOURCE_FILE)
45f82e
@@ -909,7 +968,7 @@ chk_active(const act_obj_t *act, const act_obj_t *const deleted)
45f82e
  * destruct it.
45f82e
  */
45f82e
 static void //ATTR_NONNULL()
45f82e
-act_obj_unlink(act_obj_t *const act)
45f82e
+act_obj_unlink(act_obj_t *act)
45f82e
 {
45f82e
 	DBGPRINTF("act_obj_unlink %p: %s\n", act, act->name);
45f82e
 	if(act->prev == NULL) {
45f82e
@@ -921,6 +980,7 @@ act_obj_unlink(act_obj_t *const act)
45f82e
 		act->next->prev = act->prev;
45f82e
 	}
45f82e
 	act_obj_destroy(act, 1);
45f82e
+	act = NULL;
45f82e
 //dbgprintf("printout of fs tree post unlink\n");
45f82e
 //fs_node_print(runModConf->conf_tree, 0);
45f82e
 //dbg_wdmapPrint("wdmap after");
45f82e
@@ -1025,6 +1038,7 @@ fs_node_walk(fs_node_t *const node,
45f82e
  */
45f82e
 static rsRetVal
45f82e
 fs_node_add(fs_node_t *const node,
45f82e
+	fs_node_t *const source,
45f82e
 	const uchar *const toFind,
45f82e
 	const size_t pathIdx,
45f82e
 	instanceConf_t *const inst)
45f82e
@@ -1053,6 +1067,7 @@ fs_node_add(fs_node_t *const node,
45f82e
 	memcpy(name, toFind+pathIdx, len);
45f82e
 	name[len] = '\0';
45f82e
 	DBGPRINTF("fs_node_add: name '%s'\n", name);
45f82e
+	node->root = source;
45f82e
 
45f82e
 	fs_edge_t *chld;
45f82e
 	for(chld = node->edges ; chld != NULL ; chld = chld->next) {
45f82e
@@ -1064,7 +1079,7 @@ fs_node_add(fs_node_t *const node,
45f82e
 			chld->instarr[chld->ninst-1] = inst;
45f82e
 			/* recurse */
45f82e
 			if(!isFile) {
45f82e
-				CHKiRet(fs_node_add(chld->node, toFind, nextPathIdx, inst));
45f82e
+				CHKiRet(fs_node_add(chld->node, node, toFind, nextPathIdx, inst));
45f82e
 			}
45f82e
 			FINALIZE;
45f82e
 		}
45f82e
@@ -1086,7 +1101,7 @@ fs_node_add(fs_node_t *const node,
45f82e
 	DBGPRINTF("fs_node_add(%p, '%s') returns %p\n", node, toFind, newchld->node);
45f82e
 
45f82e
 	if(!isFile) {
45f82e
-		CHKiRet(fs_node_add(newchld->node, toFind, nextPathIdx, inst));
45f82e
+		CHKiRet(fs_node_add(newchld->node, node, toFind, nextPathIdx, inst));
45f82e
 	}
45f82e
 
45f82e
 	/* link to list */
45f82e
@@ -1162,7 +1222,11 @@ enqLine(act_obj_t *const act,
45f82e
 	msgSetPRI(pMsg, inst->iFacility | inst->iSeverity);
45f82e
 	MsgSetRuleset(pMsg, inst->pBindRuleset);
45f82e
 	if(inst->addMetadata) {
45f82e
-		metadata_values[0] = (const uchar*)act->name;
45f82e
+		if (act->source_name) {
45f82e
+			metadata_values[0] = (const uchar*)act->source_name;
45f82e
+		} else {
45f82e
+			metadata_values[0] = (const uchar*)act->name;
45f82e
+		}
45f82e
 		snprintf((char *)file_offset, MAX_OFFSET_REPRESENTATION_NUM_BYTES+1, "%lld", strtOffs);
45f82e
 		metadata_values[1] = file_offset;
45f82e
 		msgAddMultiMetadata(pMsg, metadata_names, metadata_values, 2);
45f82e
@@ -1389,13 +1453,16 @@ pollFile(act_obj_t *const act)
45f82e
 {
45f82e
 	cstr_t *pCStr = NULL;
45f82e
 	DEFiRet;
45f82e
+	if (act->is_symlink) {
45f82e
+		FINALIZE;    /* no reason to poll symlink file */
45f82e
+	}
45f82e
 	/* Note: we must do pthread_cleanup_push() immediately, because the POSIX macros
45f82e
 	 * otherwise do not work if I include the _cleanup_pop() inside an if... -- rgerhards, 2008-08-14
45f82e
 	 */
45f82e
 	pthread_cleanup_push(pollFileCancelCleanup, &pCStr);
45f82e
 	iRet = pollFileReal(act, &pCStr);
45f82e
 	pthread_cleanup_pop(0);
45f82e
-	RETiRet;
45f82e
+finalize_it: RETiRet;
45f82e
 }
45f82e
 
45f82e
 
45f82e
@@ -1931,7 +1946,7 @@ CODESTARTactivateCnf
45f82e
 					"be processed. Reason", inst->pszFileName);
45f82e
 			}
45f82e
 		}
45f82e
-		fs_node_add(runModConf->conf_tree, inst->pszFileName, 0, inst);
45f82e
+		fs_node_add(runModConf->conf_tree, NULL, inst->pszFileName, 0, inst);
45f82e
 	}
45f82e
 
45f82e
 	if(Debug) {
45f82e
@@ -2031,6 +2113,9 @@ flag_in_move(fs_edge_t *const edge, const char *name_moved)
45f82e
 			DBGPRINTF("name check fails, '%s' != '%s'\n", act->basename, name_moved);
45f82e
 		}
45f82e
 	}
45f82e
+	if (!act && edge->next) {
45f82e
+		flag_in_move(edge->next, name_moved);
45f82e
+	}
45f82e
 }
45f82e
 
45f82e
 static void ATTR_NONNULL(1)
45f82e
@@ -2057,7 +2145,7 @@ in_processEvent(struct inotify_event *ev)
45f82e
 	}
45f82e
 	if(ev->mask & (IN_MOVED_FROM | IN_MOVED_TO))  {
45f82e
 		fs_node_walk(etry->act->edge->node, poll_tree);
45f82e
-	} else if(etry->act->edge->is_file) {
45f82e
+	} else if(etry->act->edge->is_file && !(etry->act->is_symlink)) {
45f82e
 		in_handleFileEvent(ev, etry); // esentially poll_file()!
45f82e
 	} else {
45f82e
 		fs_node_walk(etry->act->edge->node, poll_tree);