/*
 * Check decoding of waitid syscall.
 *
 * Copyright (c) 2015-2016 Dmitry V. Levin <ldv@altlinux.org>
 * Copyright (c) 2016-2020 The strace developers.
 * All rights reserved.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "tests.h"
#include <assert.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include "kernel_rusage.h"
#include "scno.h"

static const char *
sprint_rusage(const kernel_rusage_t *const ru)
{
	static char buf[1024];
	snprintf(buf, sizeof(buf),
		 "{ru_utime={tv_sec=%lld, tv_usec=%llu}"
		 ", ru_stime={tv_sec=%lld, tv_usec=%llu}"
#if VERBOSE
		 ", ru_maxrss=%llu"
		 ", ru_ixrss=%llu"
		 ", ru_idrss=%llu"
		 ", ru_isrss=%llu"
		 ", ru_minflt=%llu"
		 ", ru_majflt=%llu"
		 ", ru_nswap=%llu"
		 ", ru_inblock=%llu"
		 ", ru_oublock=%llu"
		 ", ru_msgsnd=%llu"
		 ", ru_msgrcv=%llu"
		 ", ru_nsignals=%llu"
		 ", ru_nvcsw=%llu"
		 ", ru_nivcsw=%llu}"
#else
		 ", ...}"
#endif
		 , (long long) ru->ru_utime.tv_sec
		 , zero_extend_signed_to_ull(ru->ru_utime.tv_usec)
		 , (long long) ru->ru_stime.tv_sec
		 , zero_extend_signed_to_ull(ru->ru_stime.tv_usec)
#if VERBOSE
		 , zero_extend_signed_to_ull(ru->ru_maxrss)
		 , zero_extend_signed_to_ull(ru->ru_ixrss)
		 , zero_extend_signed_to_ull(ru->ru_idrss)
		 , zero_extend_signed_to_ull(ru->ru_isrss)
		 , zero_extend_signed_to_ull(ru->ru_minflt)
		 , zero_extend_signed_to_ull(ru->ru_majflt)
		 , zero_extend_signed_to_ull(ru->ru_nswap)
		 , zero_extend_signed_to_ull(ru->ru_inblock)
		 , zero_extend_signed_to_ull(ru->ru_oublock)
		 , zero_extend_signed_to_ull(ru->ru_msgsnd)
		 , zero_extend_signed_to_ull(ru->ru_msgrcv)
		 , zero_extend_signed_to_ull(ru->ru_nsignals)
		 , zero_extend_signed_to_ull(ru->ru_nvcsw)
		 , zero_extend_signed_to_ull(ru->ru_nivcsw)
#endif
		 );
	return buf;
}

#define CASE(x) case x: return #x

static const char *
si_code_2_name(const int code)
{
	switch (code) {
#ifdef CLD_EXITED
	CASE(CLD_EXITED);
#endif
#ifdef CLD_KILLED
	CASE(CLD_KILLED);
#endif
#ifdef CLD_DUMPED
	CASE(CLD_DUMPED);
#endif
#ifdef CLD_TRAPPED
	CASE(CLD_TRAPPED);
#endif
#ifdef CLD_STOPPED
	CASE(CLD_STOPPED);
#endif
#ifdef CLD_CONTINUED
	CASE(CLD_CONTINUED);
#endif
	default:
		perror_msg_and_fail("unknown si_code %d", code);
	}
}

static const char *
sprint_siginfo(const siginfo_t *const si, const char *const status_text)
{
	static char buf[1024];
	snprintf(buf, sizeof(buf),
		 "{si_signo=SIGCHLD"
		 ", si_code=%s"
		 ", si_pid=%d"
		 ", si_uid=%d"
		 ", si_status=%s"
		 ", si_utime=%llu"
		 ", si_stime=%llu}",
		 si_code_2_name(si->si_code),
		 si->si_pid,
		 si->si_uid,
		 status_text,
		 zero_extend_signed_to_ull(si->si_utime),
		 zero_extend_signed_to_ull(si->si_stime));
	return buf;
}

static unsigned long
poison(unsigned int v)
{
	return (unsigned long) 0xfacefeed00000000ULL | v;
}

static long
do_waitid(const unsigned int idtype,
	  const unsigned int id,
	  const siginfo_t *const infop,
	  const unsigned int options,
	  const kernel_rusage_t *const rusage)
{
	sigset_t mask = {};
	sigaddset(&mask, SIGCHLD);

	assert(sigprocmask(SIG_BLOCK, &mask, NULL) == 0);
	long rc = syscall(__NR_waitid, poison(idtype), poison(id),
			  infop, poison(options), rusage);
	assert(sigprocmask(SIG_UNBLOCK, &mask, NULL) == 0);
	return rc;
}

int
main(void)
{
	tprintf("%s", "");

	int fds[2];
	if (pipe(fds))
		perror_msg_and_fail("pipe");

	pid_t pid;
	pid = fork();
	if (pid < 0)
		perror_msg_and_fail("fork");

	if (!pid) {
		char c;
		(void) close(1);
		assert(read(0, &c, sizeof(c)) == 1);
		return 42;
	}

	(void) close(0);

	if (do_waitid(P_PID, pid, 0, WNOHANG|WEXITED, 0))
		perror_msg_and_fail("waitid #1");
	tprintf("waitid(P_PID, %d, NULL, WNOHANG|WEXITED, NULL) = 0\n", pid);

	TAIL_ALLOC_OBJECT_CONST_PTR(siginfo_t, sinfo);
	memset(sinfo, 0, sizeof(*sinfo));
	TAIL_ALLOC_OBJECT_CONST_PTR(kernel_rusage_t, rusage);
	if (do_waitid(P_PID, pid, sinfo, WNOHANG|WEXITED|WSTOPPED, rusage))
		perror_msg_and_fail("waitid #2");
	tprintf("waitid(P_PID, %d, {}, WNOHANG|WEXITED|WSTOPPED, %s) = 0\n",
		pid, sprint_rusage(rusage));

	assert(write(1, "", 1) == 1);
	(void) close(1);

	if (do_waitid(P_PID, pid, sinfo, WEXITED, rusage))
		perror_msg_and_fail("waitid #3");
	tprintf("waitid(P_PID, %d, %s, WEXITED, %s) = 0\n",
		pid, sprint_siginfo(sinfo, "42"), sprint_rusage(rusage));

	pid = fork();
	if (pid < 0)
		perror_msg_and_fail("fork");

	if (!pid) {
		(void) raise(SIGUSR1);
		return 1;
	}

	if (do_waitid(P_PID, pid, sinfo, WEXITED, rusage))
		perror_msg_and_fail("waitid #4");
	tprintf("waitid(P_PID, %d, %s, WEXITED, %s) = 0\n",
		pid, sprint_siginfo(sinfo, "SIGUSR1"), sprint_rusage(rusage));

	if (pipe(fds))
		perror_msg_and_fail("pipe");
	pid = fork();
	if (pid < 0)
		perror_msg_and_fail("fork");

	if (!pid) {
		(void) close(1);
		raise(SIGSTOP);
		char c;
		assert(read(0, &c, sizeof(c)) == 1);
		return 0;
	}

	(void) close(0);

	if (do_waitid(P_PID, pid, sinfo, WSTOPPED, rusage))
		perror_msg_and_fail("waitid #5");
	tprintf("waitid(P_PID, %d, %s, WSTOPPED, %s) = 0\n",
		pid, sprint_siginfo(sinfo, "SIGSTOP"), sprint_rusage(rusage));

	if (kill(pid, SIGCONT))
		perror_msg_and_fail("kill(SIGCONT)");

#if defined WCONTINUED
	if (do_waitid(P_PID, pid, sinfo, WCONTINUED, rusage))
		perror_msg_and_fail("waitid #6");
	tprintf("waitid(P_PID, %d, %s, WCONTINUED, %s) = 0\n",
		pid, sprint_siginfo(sinfo, "SIGCONT"), sprint_rusage(rusage));
#endif /* WCONTINUED */

	assert(write(1, "", 1) == 1);
	(void) close(1);

	if (do_waitid(P_PID, pid, sinfo, WEXITED, rusage))
		perror_msg_and_fail("waitid #7");
	tprintf("waitid(P_PID, %d, %s, WEXITED, %s) = 0\n",
		pid, sprint_siginfo(sinfo, "0"), sprint_rusage(rusage));

	long rc = do_waitid(P_ALL, -1, sinfo, WEXITED|WSTOPPED, rusage);
	tprintf("waitid(P_ALL, -1, %p, WEXITED|WSTOPPED, %p)"
		" = %ld %s (%m)\n", sinfo, rusage, rc, errno2name());

	tprintf("%s\n", "+++ exited with 0 +++");
	return 0;
}
