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

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