/*
 * DF-2784 - un_adjval (short) SEMAEM overflow in semundo_adjust().
 *
 * sys/kern/sysv_sem.c:244 accumulates undo adjustments into a 16-bit
 * signed field with no SEMAEM (kern.ipc.semaem = 16384) enforcement.
 * Two +30000 SEM_UNDO ops make un_adjval wrap from -60000 to +5536.
 * At process exit, semexit() (sysv_sem.c:1133-1140) then takes the
 * *positive* branch and skips the "clamp to 0" logic, leaving the
 * semaphore at a wrong value instead of 0.
 *
 * child:  +30000 UNDO, +30000 UNDO, -30000 (no undo) -> semval 30000,
 *         un_adjval stored as +5536 (wrapped)
 * exit:   adjval >= 0 -> semval += 5536 -> 35536
 * correct:adjval < 0, |adjval| 60000 > semval 30000 -> clamp to 0
 *
 * Success criterion: final GETVAL == 35536 (bug) instead of 0.
 */
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

union semun_u {
	int val;
	struct semid_ds *buf;
	unsigned short *array;
};

static union semun_u u;

int main(void)
{
	int id, st, v;
	pid_t pid;

	id = semget(IPC_PRIVATE, 1, 0600);
	if (id < 0) {
		perror("semget");
		exit(1);
	}

	pid = fork();
	if (pid == 0) {
		struct sembuf up[2] = {
			{ 0,  30000, SEM_UNDO },
			{ 0,  30000, SEM_UNDO },
		};
		struct sembuf down = { 0, -30000, 0 };

		if (semop(id, up, 2) < 0) {
			if (errno == ERANGE) {
				printf("child : semop rejected with ERANGE "
				       "(SEMAEM enforced)\n");
				_exit(42);	/* fixed behavior */
			}
			perror("semop(up x2)");
			_exit(9);
		}
		if (semop(id, &down, 1) < 0) {
			perror("semop(down)");
			_exit(9);
		}
		printf("child : semval before exit = %d (undo owed: -60000)\n",
		       semctl(id, 0, GETVAL, u));
		fflush(stdout);
		_exit(0);	/* semexit() applies wrapped adjval */
	}
	waitpid(pid, &st, 0);

	if (WIFEXITED(st) && WEXITSTATUS(st) == 42) {
		printf("parent: child rejected at semop (ERANGE) - "
		       "SEMAEM enforcement active, no corruption\n");
		semctl(id, 0, IPC_RMID, u);
		return 0;
	}
	v = semctl(id, 0, GETVAL, u);
	printf("parent: semval after child exit = %d\n", v);
	printf("expected (POSIX clamp semantics): 0\n");
	if (v == 35536)
		printf("BUG REPRODUCED: un_adjval wrapped to +5536, "
		       "positive branch skipped the clamp\n");
	else if (v == 0)
		printf("correct behavior observed\n");
	else
		printf("unexpected value: %d\n", v);

	semctl(id, 0, IPC_RMID, u);
	return (v == 35536) ? 0 : 1;
}
