/* This file is part of gacopyz.
   Copyright (C) 2006-2025 Sergey Poznyakoff

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>. */

#include <gacopyz_priv.h>

static int cleanup_needed;

#define PIDTAB_INCR 256

int
gacopyz_register_child(gacopyz_conn_t conn, pid_t pid)
{
	size_t i, newcount;
	pid_t *p;
	
	for (i = 0; i < conn->pidcount; i++)
		if (conn->pidtab[i] == 0) {
			conn->pidtab[i] = pid;
			return 0;
		}

	newcount = conn->pidcount + PIDTAB_INCR;
	p = realloc(conn->pidtab, newcount * sizeof(conn->pidtab[0]));
	if (!p)
		return 1;
	memset(p + i, 0, PIDTAB_INCR * sizeof(conn->pidtab[0]));
	conn->pidcount = newcount;
	conn->pidtab = p;

	conn->pidtab[i++] = pid;
	return 0;
}

void
gacopyz_unregister_child(gacopyz_conn_t conn, pid_t pid)
{
	size_t i;
	
	for (i = 0; i < conn->pidcount; i++)
		if (conn->pidtab[i] == pid) 
			conn->pidtab[i] = 0;
}

static void
print_status(gacopyz_conn_t conn, pid_t pid, int status, int expect_term)
{
	if (WIFEXITED(status)) {
		if (WEXITSTATUS(status) == 0) {
			if (GACOPYZ_CONN_LOG_MATCH(conn, SMI_LOG_DEBUG))
				gacopyz_log(SMI_LOG_DEBUG,
					    _("child %lu exited successfully"),
					    (unsigned long) pid);
		} else {
			if (GACOPYZ_CONN_LOG_MATCH(conn, SMI_LOG_ERR))
				gacopyz_log(SMI_LOG_ERR,
					  _("child %lu failed with status %d"),
					    (unsigned long) pid,
					    WEXITSTATUS(status));
		}
	} else if (WIFSIGNALED(status)) {
		int prio;
		if (expect_term && WTERMSIG(status) == SIGTERM)
			prio = SMI_LOG_DEBUG;
		else
			prio = SMI_LOG_ERR;
		if (GACOPYZ_CONN_LOG_MATCH(conn, prio))
			gacopyz_log(prio,
				    _("child %lu terminated on signal %d"),
				    (unsigned long) pid, WTERMSIG(status));
	} else if (WIFSTOPPED(status)) {
		if (GACOPYZ_CONN_LOG_MATCH(conn, SMI_LOG_ERR))
			gacopyz_log(SMI_LOG_ERR,
				    _("child %lu stopped on signal %d"),
				    (unsigned long) pid, WSTOPSIG(status));
	}
#ifdef WCOREDUMP
	else if (WCOREDUMP(status)) {
		if (GACOPYZ_CONN_LOG_MATCH(conn, SMI_LOG_ERR))
			gacopyz_log(SMI_LOG_ERR,
				    _("child %lu dumped core"),
				    (unsigned long) pid);
	}
#endif
	else if (GACOPYZ_CONN_LOG_MATCH(conn, SMI_LOG_ERR))
		gacopyz_log(SMI_LOG_ERR,
			    _("child %lu terminated with unrecognized status"),
			    (unsigned long) pid);
}

static void
cleanup_children(gacopyz_conn_t conn, int expect_term)
{
	if (!cleanup_needed)
		return;
	for (;;) {
		int status;
		pid_t pid = waitpid((pid_t)-1, &status, WNOHANG);
		if (pid <= 0)
			break;
		gacopyz_unregister_child(conn, pid);
		print_status(conn, pid, status, expect_term);
	}
	cleanup_needed = 0;
}

void
gacopyz_cleanup_children(gacopyz_conn_t conn)
{
	cleanup_children(conn, 0);
}

void
gacopyz_cleanup_conn(gacopyz_conn_t conn)
{
	size_t i;
	
	if (GACOPYZ_CONN_LOG_MATCH(conn, SMI_LOG_DEBUG))
		gacopyz_log(SMI_LOG_DEBUG, _("terminating subprocesses"));
	
	for (i = 0; i < conn->pidcount; i++)
		if (conn->pidtab[i])
			kill(conn->pidtab[i], SIGTERM);
	cleanup_children(conn, 1);

	if (conn->cleanup)
		conn->cleanup(conn, conn->cleanup_data);
}

static void
sig_child(int sig)
{
	cleanup_needed = 1;
	signal(sig, sig_child);
}

void
gacopyz_setup_signals()
{
	signal(SIGCHLD, sig_child);
	signal(SIGPIPE, SIG_IGN); /* FIXME */
}
