/* COPYING ******************************************************************
For copyright and licensing terms, see the file named COPYING.
// **************************************************************************
*/

#include <cerrno>
#include <csignal>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "wait.h"

/* Waiting for child processes **********************************************
// **************************************************************************
*/

#if defined(WEXITED)

static inline
void
make_status (
	const siginfo_t & si,
	int & status,
	int & code
) {
	switch (si.si_code) {
		case CLD_EXITED:	status = WAIT_STATUS_EXITED; code = si.si_status; break;
		case CLD_KILLED:	status = WAIT_STATUS_SIGNALLED; code = si.si_status; break;
		case CLD_DUMPED:	status = WAIT_STATUS_SIGNALLED_CORE; code = si.si_status; break;
		case CLD_STOPPED:	status = WAIT_STATUS_PAUSED; code = si.si_status; break;
		case CLD_TRAPPED:	status = WAIT_STATUS_PAUSED; code = si.si_status; break;
		default:
		case CLD_CONTINUED:	status = WAIT_STATUS_RUNNING; code = 0; break;
	}
}

static inline
int	/// \retval -1 error \retval 0 no child \retval >0 found child
wait_for_anychild (
	pid_t & child,
	int & status,
	int & code,
	int flags
) {
	for (;;) {
		siginfo_t si;
		// The Windows NT Linux subsystem needs this; actual Linux does not, despite the manual.
		si.si_pid = si.si_signo = 0;
		const int rc(waitid(P_ALL, static_cast<pid_t>(-1), &si, flags));
		if (0 > rc) {
			if (EINTR != errno) return -1;
		} else 
		if (0 == si.si_pid || 0 == si.si_signo) 
			return 0;
		else
		{
			make_status(si, status, code);
			child = si.si_pid;
			return 1;
		}
	}
}

int	/// \retval -1 error \retval 0 no child \retval >0 found child
wait_blocking_for_anychild_exit (
	pid_t & child,
	int & status,
	int & code
) {
	return wait_for_anychild(child, status, code, WEXITED);
}

#else

static inline
void
make_status (
	int s,
	int & status,
	int & code
) {
#if defined(WIFCONTINUED)
	if (WIFCONTINUED(s)) {
		status = WAIT_STATUS_RUNNING;
		code = 0;
	} else
#endif
	if (WIFSTOPPED(s)) {
		status = WAIT_STATUS_PAUSED;
		code = WSTOPSIG(s);
	} else
	if (WIFEXITED(s)) {
		status = WAIT_STATUS_EXITED;
		code = WEXITSTATUS(s);
	} else
	if (WIFSIGNALED(s)) {
		status = WCOREDUMP(s) ? WAIT_STATUS_SIGNALLED_CORE : WAIT_STATUS_SIGNALLED;
		code = WTERMSIG(s);
	} else
	{
		status = WAIT_STATUS_RUNNING;
		code = 0;
	}
}

static inline
int	/// \retval -1 error \retval 0 no child \retval >0 found child
wait_for_anychild (
	pid_t & child,
	int & status,
	int & code,
	int flags
) {
	for (;;) {
		int s;
		const pid_t rc(waitpid(-1, &s, flags));
		if (static_cast<pid_t>(-1) == rc) {
			if (EINTR != errno) return -1;
		} else
		if (static_cast<pid_t>(0) == rc) {
			return 0;
		} else
		{
			make_status(s, status, code);
			child = rc;
	       		return 1;
	 	}
	}
}

int	/// \retval -1 error \retval 0 no child \retval >0 found child
wait_blocking_for_anychild_exit (
	pid_t & child,
	int & status,
	int & code
) {
	return wait_for_anychild(child, status, code, 0);
}

#endif
