/* alarm.c -- wakeup calls */
/* SPDX-License-Identifier: BSD-2-clause */

/*
-- Implementation of alarm.h
*/

#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <time.h>

static bool alarmPending = false; /* Nonzero when the alarm is set. */

static void ualarm();
static void AlarmHandler();

void AlarmSet(int time) {
	struct sigaction handler;

	alarmPending = true;
	handler.sa_handler = AlarmHandler;
	sigemptyset(&handler.sa_mask);
	handler.sa_flags = 0;
	sigaction(SIGALRM, &handler, NULL);
	ualarm(1000 * time, 0);
}

/*
-- If an alarm signal is lurking (due to a prior call to SetAlarm), then
-- pause until it arrives.  This procedure could have simply been written:
--   if (alarmPending) pause();
-- but that allows a potential race condition.
*/
void AlarmWait() {
	// Hold (block) the SIGALRM signal - was sighold(SIGALRM)
	sigset_t orig_mask, blocked_mask;
	sigemptyset(&blocked_mask);
	sigaddset(&blocked_mask, SIGALRM);
	sigprocmask(SIG_BLOCK, &blocked_mask, &orig_mask);

	// Was sigpause(SIGALRM)
	if (alarmPending) {
		// Suspend the process until a signal is received, but only if
		// SIGALRM is pending
		sigsuspend(&orig_mask);
	}

	// Was sigrelse(SIGALRM);
	// Release (unblock) the SIGALRM signal
	sigprocmask(SIG_SETMASK, &orig_mask, NULL);
}

static void ualarm(unsigned us) {
	struct itimerval rttimer, old_rttimer;

	rttimer.it_value.tv_sec = us / 1000000;
	rttimer.it_value.tv_usec = us % 1000000;
	rttimer.it_interval.tv_sec = 0;
	rttimer.it_interval.tv_usec = 0;
	if (setitimer(ITIMER_REAL, &rttimer, &old_rttimer)) {
		perror("ualarm");
		exit(1);
	}
}

static void AlarmHandler() { alarmPending = false; }
