git: 60ae4e52f33e - main - syslogd: Terminate pipe processes gracefully

From: Jake Freeland <jfree_at_FreeBSD.org>
Date: Mon, 12 Jan 2026 03:42:30 UTC
The branch main has been updated by jfree:

URL: https://cgit.FreeBSD.org/src/commit/?id=60ae4e52f33e3c67720b68a29e35f6c114a3386c

commit 60ae4e52f33e3c67720b68a29e35f6c114a3386c
Author:     Jake Freeland <jfree@FreeBSD.org>
AuthorDate: 2025-12-22 06:05:37 +0000
Commit:     Jake Freeland <jfree@FreeBSD.org>
CommitDate: 2026-01-12 03:40:23 +0000

    syslogd: Terminate pipe processes gracefully
    
    Pipe actions spawn a process based on the command provided in the
    syslogd configuration file. When a HUP signal is received, enter
    the process into the deadq instead of immediately killing it.
    This matches the behavior of syslogd prior to it being Capsicumized.
    
    Fixes: d2d180fb7736
---
 usr.sbin/syslogd/syslogd.c             | 94 +++++++++++++---------------------
 usr.sbin/syslogd/tests/syslogd_test.sh | 34 ++++++++++++
 2 files changed, 70 insertions(+), 58 deletions(-)

diff --git a/usr.sbin/syslogd/syslogd.c b/usr.sbin/syslogd/syslogd.c
index e06464c0e749..f109fcd02563 100644
--- a/usr.sbin/syslogd/syslogd.c
+++ b/usr.sbin/syslogd/syslogd.c
@@ -370,43 +370,19 @@ static void	increase_rcvbuf(int);
 static void
 close_filed(struct filed *f)
 {
-	switch (f->f_type) {
-	case F_FORW:
-		if (f->f_addr_fds != NULL) {
-			free(f->f_addrs);
-			for (size_t i = 0; i < f->f_num_addr_fds; ++i)
-				close(f->f_addr_fds[i]);
-			free(f->f_addr_fds);
-			f->f_addr_fds = NULL;
-			f->f_num_addr_fds = 0;
-		}
-		/* FALLTHROUGH */
-	case F_FILE:
-	case F_TTY:
-	case F_CONSOLE:
-		f->f_type = F_UNUSED;
-		break;
-	case F_PIPE:
-		if (f->f_procdesc != -1) {
-			/*
-			 * Close the procdesc, killing the underlying
-			 * process (if it is still alive).
-			 */
-			(void)close(f->f_procdesc);
-			f->f_procdesc = -1;
-			/*
-			 * The pipe process is guaranteed to be dead now,
-			 * so remove it from the deadq.
-			 */
-			if (f->f_dq != NULL) {
-				deadq_remove(f->f_dq);
-				f->f_dq = NULL;
-			}
-		}
-		break;
-	default:
-		break;
+	if (f->f_type == F_FORW && f->f_addr_fds != NULL) {
+		free(f->f_addrs);
+		for (size_t i = 0; i < f->f_num_addr_fds; ++i)
+			close(f->f_addr_fds[i]);
+		free(f->f_addr_fds);
+		f->f_addr_fds = NULL;
+		f->f_num_addr_fds = 0;
+	} else if (f->f_type == F_PIPE && f->f_procdesc != -1) {
+		f->f_dq = deadq_enter(f->f_procdesc);
 	}
+
+	f->f_type = F_UNUSED;
+
 	if (f->f_file != -1)
 		(void)close(f->f_file);
 	f->f_file = -1;
@@ -820,8 +796,23 @@ main(int argc, char *argv[])
 			break;
 		case EVFILT_PROCDESC:
 			if ((ev.fflags & NOTE_EXIT) != 0) {
-				log_deadchild(ev.ident, ev.data, ev.udata);
-				close_filed(ev.udata);
+				struct filed *f = ev.udata;
+
+				log_deadchild(f->f_procdesc, ev.data, f);
+				(void)close(f->f_procdesc);
+
+				f->f_procdesc = -1;
+				if (f->f_dq != NULL) {
+					deadq_remove(f->f_dq);
+					f->f_dq = NULL;
+				}
+
+				/*
+				 * If it is unused, then it was already closed.
+				 * Free the file data in this case.
+				 */
+				if (f->f_type == F_UNUSED)
+					free(f);
 			}
 			break;
 		}
@@ -2272,9 +2263,6 @@ die(int signo)
 		/* flush any pending output */
 		if (f->f_prevcount)
 			fprintlog_successive(f, 0);
-		/* terminate existing pipe processes */
-		if (f->f_type == F_PIPE)
-			close_filed(f);
 	}
 	if (signo) {
 		dprintf("syslogd: exiting on signal %d\n", signo);
@@ -2517,23 +2505,7 @@ closelogfiles(void)
 		case F_FORW:
 		case F_CONSOLE:
 		case F_TTY:
-			close_filed(f);
-			break;
 		case F_PIPE:
-			if (f->f_procdesc != -1) {
-				struct kevent ev;
-				/*
-				 * This filed is going to be freed.
-				 * Delete procdesc kevents that reference it.
-				 */
-				EV_SET(&ev, f->f_procdesc, EVFILT_PROCDESC,
-				    EV_DELETE, NOTE_EXIT, 0, f);
-				if (kevent(kq, &ev, 1, NULL, 0, NULL) == -1) {
-					logerror("failed to delete procdesc"
-					    "kevent");
-					exit(1);
-				}
-			}
 			close_filed(f);
 			break;
 		default:
@@ -2554,7 +2526,13 @@ closelogfiles(void)
 			}
 			free(f->f_prop_filter);
 		}
-		free(f);
+
+		/*
+		 * If a piped process is running, then defer the filed
+		 * cleanup until it exits.
+		 */
+		if (f->f_type != F_PIPE || f->f_procdesc == -1)
+			free(f);
 	}
 }
 
diff --git a/usr.sbin/syslogd/tests/syslogd_test.sh b/usr.sbin/syslogd/tests/syslogd_test.sh
index 2d093dd80c35..5422e78f6831 100644
--- a/usr.sbin/syslogd/tests/syslogd_test.sh
+++ b/usr.sbin/syslogd/tests/syslogd_test.sh
@@ -313,6 +313,39 @@ pipe_action_cleanup()
     syslogd_stop
 }
 
+atf_test_case "pipe_action_reload" "cleanup"
+pipe_action_reload_head()
+{
+    atf_set descr "Pipe processes terminate gracefully on reload"
+}
+pipe_action_reload_body()
+{
+    local logfile="${PWD}/pipe_reload.log"
+    local pipecmd="${PWD}/pipe_cmd.sh"
+
+    cat <<__EOF__ > "${pipecmd}"
+#!/bin/sh
+echo START > ${logfile}
+while read msg; do
+    echo \${msg} >> ${logfile}
+done
+echo END >> ${logfile}
+exit 0
+__EOF__
+    chmod +x "${pipecmd}"
+
+    printf "!pipe\nuser.debug\t| %s\n" "${pipecmd}" > "${SYSLOGD_CONFIG}"
+    syslogd_start
+
+    syslogd_log -p user.debug -t "pipe" -h "${SYSLOGD_LOCAL_SOCKET}" "MSG"
+    syslogd_reload
+    atf_check -s exit:0 -o match:"END" tail -n 1 "${logfile}"
+}
+pipe_action_reload_cleanup()
+{
+    syslogd_stop
+}
+
 atf_test_case "jail_noinet" "cleanup"
 jail_noinet_head()
 {
@@ -561,6 +594,7 @@ atf_init_test_cases()
     atf_add_test_case "prop_filter"
     atf_add_test_case "host_action"
     atf_add_test_case "pipe_action"
+    atf_add_test_case "pipe_action_reload"
     atf_add_test_case "jail_noinet"
     atf_add_test_case "allowed_peer"
     atf_add_test_case "allowed_peer_forwarding"