/*
 *  stress_sigfpe
 *	stress by generating floating point errors
 */
int stress_sigfpe(
	uint64_t *const counter,
	const uint32_t instance,
	const uint64_t max_ops,
	const char *name)
{
	(void)instance;

	for (;;) {
		struct sigaction new_action;
		int ret;

		memset(&new_action, 0, sizeof new_action);
		new_action.sa_handler = stress_fpehandler;
		sigemptyset(&new_action.sa_mask);
		new_action.sa_flags = 0;

		if (sigaction(SIGFPE, &new_action, NULL) < 0) {
			pr_failed_err(name, "sigfpe");
			return EXIT_FAILURE;
		}
		ret = sigsetjmp(jmp_env, 1);
		/*
		 * We return here if we get SIGFPE, so
		 * first check if we need to terminate
		 */
		if (!opt_do_run || (max_ops && *counter >= max_ops))
			break;

		if (ret)
			(*counter)++;	/* SIGFPE occurred */
		else
			uint64_put(1 / uint64_zero());	/* force division by zero */
	}

	return EXIT_SUCCESS;
}
/*
 *  stress_sigfpe
 *	stress by generating floating point errors
 */
static int stress_sigfpe(const args_t *args)
{
	struct sigaction action;
	static int i = 0;
	int ret;
	const uint64_t zero = stress_uint64_zero();

	typedef struct {
		int	exception;
		int	err_code;
	} fpe_err_t;

	/*
	 *  FPE errors to raise
	 */
	static const fpe_err_t fpe_errs[] = {
#if defined(FPE_INTDIV)
		{ SNG_INTDIV,	FPE_INTDIV },
#endif
#if defined(FPE_FLTDIV)
		{ SNG_FLTDIV,	FPE_FLTDIV },
#endif
#if defined(FE_DIVBYZERO) && defined(FPE_FLTDIV)
		{ FE_DIVBYZERO,	FPE_FLTDIV },
#endif
#if defined(FE_INEXACT) && defined(FPE_FLTRES)
		{ FE_INEXACT,	FPE_FLTRES },
#endif
#if defined(FE_INVALID) && defined(FPE_FLTINV)
		{ FE_INVALID,	FPE_FLTINV },
#endif
#if defined(FE_OVERFLOW) && defined(FPE_FLTOVF)
		{ FE_OVERFLOW,	FPE_FLTOVF },
#endif
#if defined(FE_UNDERFLOW) && defined(FPE_FLTUND)
		{ FE_UNDERFLOW,	FPE_FLTUND },
#endif
	};

	(void)memset(&action, 0, sizeof action);

#if defined(SA_SIGINFO)
	action.sa_sigaction = stress_fpehandler;
#else
	action.sa_handler = stress_fpehandler;
#endif
	(void)sigemptyset(&action.sa_mask);
#if defined(SA_SIGINFO)
	action.sa_flags = SA_SIGINFO;
#endif

	ret = sigaction(SIGFPE, &action, NULL);
	if (ret < 0) {
		pr_fail("%s: sigaction SIGFPE: errno=%d (%s)\n",
			args->name, errno, strerror(errno));
		return EXIT_FAILURE;
	}

	for (;;) {
#if defined(SA_SIGINFO)
		static int expected_err_code;
		int code;
#endif
		int exception;

#if defined(SA_SIGINFO)
		code = fpe_errs[i].err_code;
		expected_err_code = code;
#endif
		exception = fpe_errs[i].exception;

		ret = sigsetjmp(jmp_env, 1);
		/*
		 * We return here if we get SIGFPE, so
		 * first check if we need to terminate
		 */
		if (!keep_stressing())
			break;

		if (ret) {
			/*
			 *  A SIGFPE occurred, check the error code
			 *  matches the expected code
			 */
			(void)feclearexcept(FE_ALL_EXCEPT);

#if defined(SA_SIGINFO)
			if ((g_opt_flags & OPT_FLAGS_VERIFY) &&
			    (siginfo.si_code >= 0) &&
			    (siginfo.si_code != expected_err_code)) {
				pr_fail("%s: got SIGFPE error %d (%s), expecting %d (%s)\n",
					args->name,
					siginfo.si_code, stress_sigfpe_errstr(siginfo.si_code),
					expected_err_code, stress_sigfpe_errstr(expected_err_code));
			}
#endif
			inc_counter(args);
		} else {
#if defined(SA_SIGINFO)
			siginfo.si_code = 0;
#endif

			switch(exception) {
			case SNG_FLTDIV:
				float_put(1.0 / (float)zero);
				break;
			case SNG_INTDIV:
				uint64_put(1 / zero);
				break;
			default:
				/* Raise fault otherwise */
				(void)feraiseexcept(exception);
				break;
			}
		}
		i++;
		i %= SIZEOF_ARRAY(fpe_errs);
	}
	(void)feclearexcept(FE_ALL_EXCEPT);

	return EXIT_SUCCESS;
}