Subject: [PATCH v9 08/12] run-command: add pre-exec callback for child processes

From: "Paul Tarjan via GitGitGadget" <[email-masked]>
Date: Thu, 05 Mar 2026 00:51:56 +0000

From: Paul Tarjan <[email-masked]>

Add a pre_exec_cb function pointer to struct child_process that is
invoked in the child between fork and exec.  This gives callers a
place to perform setup that must happen in the child's context,
such as closing inherited file descriptors.

Provide close_fd_above_stderr() as a ready-made callback that
closes file descriptors 3 and above (skipping the child-notifier
pipe), capped at sysconf(_SC_OPEN_MAX) or 4096, whichever is
smaller.

Signed-off-by: Paul Tarjan <[email-masked]>
---
 run-command.c | 15 +++++++++++++++
 run-command.h | 15 +++++++++++++++
 2 files changed, 30 insertions(+)

diff --git a/run-command.c b/run-command.c
index e3e02475cc..b9bc84ca1b 100644
--- a/run-command.c
+++ b/run-command.c
@@ -674,6 +674,18 @@ static void trace_run_command(const struct child_process *cp)
 	strbuf_release(&buf);
 }
 
+void close_fd_above_stderr(void)
+{
+	long max_fd = sysconf(_SC_OPEN_MAX);
+	int fd;
+	if (max_fd < 0 || max_fd > 4096)
+		max_fd = 4096;
+	for (fd = 3; fd < max_fd; fd++) {
+		if (fd != child_notifier)
+			close(fd);
+	}
+}
+
 int start_command(struct child_process *cmd)
 {
 	int need_in, need_out, need_err;
@@ -832,6 +844,9 @@ fail_pipe:
 			child_close(cmd->out);
 		}
 
+		if (cmd->pre_exec_cb)
+			cmd->pre_exec_cb();
+
 		if (cmd->dir && chdir(cmd->dir))
 			child_die(CHILD_ERR_CHDIR);
 
diff --git a/run-command.h b/run-command.h
index 0df25e445f..7ea5c6e005 100644
--- a/run-command.h
+++ b/run-command.h
@@ -141,6 +141,14 @@ struct child_process {
 	unsigned stdout_to_stderr:1;
 	unsigned clean_on_exit:1;
 	unsigned wait_after_clean:1;
+
+	/**
+	 * If set, the callback is invoked in the child between fork and
+	 * exec.  It can be used, for example, to close inherited file
+	 * descriptors that the child should not keep open.
+	 */
+	void (*pre_exec_cb)(void);
+
 	void (*clean_on_exit_handler)(struct child_process *process);
 };
 
@@ -149,6 +157,13 @@ struct child_process {
 	.env = STRVEC_INIT, \
 }
 
+/**
+ * Close file descriptors 3 and above.  Suitable for use as a
+ * pre_exec_cb to prevent the child from inheriting pipe endpoints
+ * or other descriptors from the parent environment.
+ */
+void close_fd_above_stderr(void);
+
 /**
  * The functions: start_command, finish_command, run_command do the following:
  *
-- 
gitgitgadget