/*
 * DF-2913 — RLIMIT_CPU soft-limit instant SIGKILL when hard limit is
 * RLIM_INFINITY (rlim_max + 5 signed overflow in plimit_testcpulimit,
 * sys/kern/kern_plimit.c:467).
 *
 * Fork a child that:
 *   1. installs a SIGXCPU handler that records delivery,
 *   2. setrlimit(RLIMIT_CPU, {cur = SECS, max = RLIM_INFINITY})
 *      (unprivileged-legal: cur <= existing max, max unchanged),
 *   3. busy-spins; if SIGXCPU is received it keeps running for a few
 *      more seconds (proving survival) then exits cleanly.
 *
 * Parent reports which signal killed the child / exit status + elapsed.
 *
 * BUG expectation (max=RLIM_INFINITY, the DEFAULT hard limit):
 *      child dies with SIGKILL at ~SECS cpu-seconds;
 *      "SIGXCPU received" is NEVER printed;
 *      kernel log shows killproc message "exceeded maximum CPU limit".
 * CORRECT behavior (control, max=finite):
 *      "SIGXCPU received" printed at ~SECS; child still alive ~3s
 *      later; exits 0 at SECS+3.
 *
 * Usage: cpulimit_kill [secs] [infinity|finite]
 */
#include <sys/resource.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static volatile sig_atomic_t got_xcpu;

static void
xcpu_handler(int sig)
{
	got_xcpu = 1;
}

static double
now(void)
{
	struct timeval tv;

	gettimeofday(&tv, NULL);
	return (tv.tv_sec + tv.tv_usec / 1e6);
}

int
main(int argc, char **argv)
{
	struct rlimit rl;
	double t_start, t_end;
	long secs = (argc > 1) ? atol(argv[1]) : 2;
	int infinite = (argc < 3 || strcmp(argv[2], "infinity") == 0);
	pid_t pid, wpid;
	int st;
	double t_xcpu = 0;

	t_start = now();

	pid = fork();
	if (pid == 0) {
		struct sigaction sa;
		double t0;

		memset(&sa, 0, sizeof(sa));
		sa.sa_handler = xcpu_handler;
		sigaction(SIGXCPU, &sa, NULL);

		rl.rlim_cur = (rlim_t)secs;
		rl.rlim_max = infinite ? RLIM_INFINITY : (rlim_t)(secs + 60);
		if (setrlimit(RLIMIT_CPU, &rl) < 0) {
			perror("setrlimit");
			_exit(9);
		}
		printf("child: limits set cur=%ld max=%s (0x%016llx)\n",
		       secs, infinite ? "RLIM_INFINITY" : "finite",
		       (unsigned long long)rl.rlim_max);
		fflush(stdout);

		t0 = now();
		for (;;) {
			if (got_xcpu && t_xcpu == 0) {
				t_xcpu = now();
				printf("child: SIGXCPU received at +%.1fs, "
				       "still running\n", t_xcpu - t0);
				fflush(stdout);
			}
			/* survived 3s past the soft limit -> success exit */
			if (got_xcpu && now() - t_xcpu > 3.0)
				_exit(0);
		}
		_exit(8);
	}

	wpid = waitpid(pid, &st, 0);
	t_end = now();

	printf("mode            : rlim_cur=%ld rlim_max=%s\n", secs,
	       infinite ? "RLIM_INFINITY" : "finite(cur+60)");
	printf("elapsed wall    : %.1f s\n", t_end - t_start);
	if (wpid < 0) {
		perror("waitpid");
		return (1);
	}
	if (WIFSIGNALED(st))
		printf("RESULT          : child killed by signal %d (%s)\n",
		       WTERMSIG(st),
		       WTERMSIG(st) == SIGKILL ? "SIGKILL" :
		       WTERMSIG(st) == SIGXCPU ? "SIGXCPU" : "?");
	else
		printf("RESULT          : child exited status %d "
		       "(survived soft limit)\n", WEXITSTATUS(st));
	return (0);
}
