/*
 * DF-2792 PoC — sys_ioprio_get(PRIO_PGRP) compares p->p_nice but
 * assigns p->p_ionice (sys/kern/kern_resource.c:365-369):
 *
 *        LIST_FOREACH(p, &pg->pg_members, p_pglist) {
 *                if (PRISON_CHECK(curtd->td_ucred, p->p_ucred) &&
 *                    p->p_nice > high)          <-- WRONG field
 *                        high = p->p_ionice;
 *        }
 *
 * The PRIO_PROCESS (:340-352) and PRIO_USER (:399-413) paths compare
 * (and assign) p_ionice. The intent of the pgrp scan is to return the
 * maximum ionice among the group's members; instead it gates each
 * member on that member's CPU nice, so members whose nice does not
 * exceed the running "high" are skipped and the returned value is
 * order-dependent garbage (can also stay at the sentinel IOPRIO_MIN-2
 * and misreport ESRCH when all members have nice <= -1).
 *
 * Deterministic demo (no race): parent + 2 children in one pgrp.
 *   parent: nice 0, ionice 0  (joins pgrp first => iterated first)
 *   childA: nice 5, ionice 2
 *   childB: nice 1, ionice 8
 * buggy walk:  high=-1 -> parent(nice 0 > -1) high=0
 *              -> childA(nice 5 > 0) high=2
 *              -> childB(nice 1 > 2? NO) skipped   => returns 2
 * correct answer (max ionice) = 8, which ioprio_get(PRIO_USER) returns.
 */
#include <sys/types.h>
#include <sys/resource.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>

#ifndef PRIO_PGRP
#define PRIO_PGRP 1
#endif
#ifndef PRIO_USER
#define PRIO_USER 2
#endif

int
main(void)
{
	pid_t a, b, me = getpid();
	int pgid, ra, rb, rp, ru, pgrp_res;
	int nice_a = 5, nice_b = 1;
	int ion_a = 2, ion_b = 8;

	if (setpgid(0, 0) != 0) {
		perror("setpgid");
		return 1;
	}
	pgid = getpgrp();

	a = fork();
	if (a == 0) {
		setpriority(PRIO_PROCESS, 0, nice_a);
		pause();
		_exit(0);
	}
	b = fork();
	if (b == 0) {
		setpriority(PRIO_PROCESS, 0, nice_b);
		pause();
		_exit(0);
	}

	/* give the children time to apply their nice values */
	sleep(1);

	if (ioprio_set(PRIO_PROCESS, a, ion_a) != 0) {
		perror("ioprio_set A");
		return 1;
	}
	if (ioprio_set(PRIO_PROCESS, b, ion_b) != 0) {
		perror("ioprio_set B");
		return 1;
	}
	if (ioprio_set(PRIO_PROCESS, me, 0) != 0) {
		perror("ioprio_set parent");
		return 1;
	}

	ra = ioprio_get(PRIO_PROCESS, a);
	rb = ioprio_get(PRIO_PROCESS, b);
	rp = ioprio_get(PRIO_PROCESS, me);
	errno = 0;
	pgrp_res = ioprio_get(PRIO_PGRP, pgid);
	if (pgrp_res == -1 && errno)
		printf("ioprio_get(PGRP) FAILED: %s\n", strerror(errno));
	errno = 0;
	ru = ioprio_get(PRIO_USER, getuid());
	if (ru == -1 && errno)
		printf("ioprio_get(USER) FAILED: %s\n", strerror(errno));

	printf("members ionice: parent=%d childA(pid %d)=%d childB(pid %d)=%d\n",
	       rp, a, ra, b, rb);
	printf("ioprio_get(PRIO_PGRP, %d)  = %d   (correct max = %d)\n",
	       pgid, pgrp_res, ion_b);
	printf("ioprio_get(PRIO_USER, uid) = %d\n", ru);

	kill(a, SIGKILL);
	kill(b, SIGKILL);
	waitpid(a, NULL, 0);
	waitpid(b, NULL, 0);

	if (pgrp_res != ion_b) {
		printf("REPRODUCED: pgrp result %d != expected max %d "
		       "(copy-paste of p_nice into the p_ionice comparison)\n",
		       pgrp_res, ion_b);
		return 0;
	}
	printf("NOT reproduced (pgrp returned the correct max)\n");
	return 1;
}
