The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#ifndef CopHINTHASH_get
#define CopHINTHASH_get(c) ((c)->cop_hints_hash)
#endif

#ifndef cophh_fetch_pvs
#define cophh_fetch_pvs(cophh, key, flags) Perl_refcounted_he_fetch(aTHX_ cophh, NULL, STR_WITH_LEN(key), 0, flags)
#endif

#if PERL_VERSION < 15 || PERL_VERSION == 15 && PERL_SUBVERSION < 8
#define PL_delaymagic_uid PL_uid
#define PL_delaymagic_euid PL_euid
#define PL_delaymagic_gid PL_gid
#define PL_delaymagic_egid PL_egid
#endif

#if defined(HAS_GETGROUPS) || defined(HAS_SETGROUPS)
#  ifdef I_GRP
#    include <grp.h>
#  endif
#endif

#if defined(HAS_SETGROUPS)
#  ifndef NGROUPS
#    define NGROUPS 32
#  endif
#endif

static int autodie_variables(pTHX) {
	SV* val = cophh_fetch_pvs(CopHINTHASH_get(PL_curcop), "autodie_variables", 0);
	if (val != &PL_sv_placeholder)
		return SvIV(val);
	return 0;
}

static int new_magic_set(pTHX_ SV *sv, MAGIC *mg) {
	dVAR;
	register const char *s;
	I32 i;
	STRLEN len;
	MAGIC *tmg;
	int ret = 0;

#ifdef PERL_ARGS_ASSERT_MAGIC_SET
	PERL_ARGS_ASSERT_MAGIC_SET;
#endif

	if (!autodie_variables(aTHX))
		return Perl_magic_set(aTHX_ sv, mg);

	switch (*mg->mg_ptr) {
	case '<':
		{
		const Uid_t new_uid = SvIV(sv);
		if (PL_delaymagic) {
			PL_delaymagic_uid = new_uid;
			PL_delaymagic |= DM_RUID;
			break;								/* don't do magic till later */
		}
#ifdef HAS_SETRESUID
		ret = setresuid((Uid_t)new_uid, (Uid_t)-1, (Uid_t)-1);
#else
#ifdef HAS_SETRUID
		ret = setruid((Uid_t)new_uid);
#else
#ifdef HAS_SETREUID
		ret = setreuid((Uid_t)new_uid, (Uid_t)-1);
#else
		if (new_uid == geteuid()) {				/* special case $< = $> */
#ifdef PERL_DARWIN
			/* workaround for Darwin's setuid peculiarity, cf [perl #24122] */
			if (new_uid != 0 && PerlProc_getuid() == 0)
				(void)PerlProc_setuid(0);
#endif
			ret = PerlProc_setuid(new_uid);
		} else {
			Perl_croak(aTHX_ "setruid() not implemented");
		}
#endif
#endif
#endif
		if (ret < 0)
			Perl_croak(aTHX_ "setruid(%d) failed: %s", new_uid, Strerror(errno));
#ifdef PL_uid
		PL_uid = PerlProc_getuid();
#endif
		break;
		}
	case '>':
		{
		const Uid_t new_euid = SvIV(sv);
		if (PL_delaymagic) {
			PL_delaymagic_euid = new_euid;
			PL_delaymagic |= DM_EUID;
			break;								/* don't do magic till later */
		}
#ifdef HAS_SETRESUID
		ret = setresuid((Uid_t)-1, (Uid_t)new_euid, (Uid_t)-1);
#else
#ifdef HAS_SETEUID
		ret = seteuid((Uid_t)new_euid);
#else
#ifdef HAS_SETREUID
		(void)setreuid((Uid_t)-1, (Uid_t)new_euid);
#else
		if (new_euid == PerlProc_getuid())				/* special case $> = $< */
			ret = PerlProc_setuid(new_euid);
		else {
			Perl_croak(aTHX_ "seteuid() not implemented");
		}
#endif
#endif
#endif
		if (ret < 0)
			Perl_croak(aTHX_ "seteuid(%d) failed: %s", new_euid, Strerror(errno));
#ifdef PL_euid
		PL_euid = PerlProc_geteuid();
#endif
		break;
		}
	case '(':
		{
		const Gid_t new_gid = SvIV(sv);
		if (PL_delaymagic) {
			PL_delaymagic_gid = new_gid;
			PL_delaymagic |= DM_RGID;
			break;								/* don't do magic till later */
		}
#ifdef HAS_SETRESGID
		ret = setresgid((Gid_t)new_gid, (Gid_t)-1, (Gid_t) -1);
#else
#ifdef HAS_SETRGID
		ret = setrgid((Gid_t)new_gid);
#else
#ifdef HAS_SETREGID
		ret = setregid((Gid_t)new_gid, (Gid_t)-1);
#else
		if (new_gid == PerlProc_getegid())						/* special case $( = $) */
			ret = PerlProc_setgid(new_gid);
		else {
			Perl_croak(aTHX_ "setrgid() not implemented");
		}
#endif
#endif
#endif
		if (ret < 0)
			Perl_croak(aTHX_ "setrgid(%d) failed: %s", new_gid, Strerror(errno));
#ifdef PL_gid
		PL_gid = PerlProc_getgid();
#endif
		break;
		}
	case ')':
		{
		gid_t new_egid;
#ifdef HAS_SETGROUPS
		{
			const char *p = SvPV_const(sv, len);
			const char *additional = NULL;
			Groups_t *gary = NULL;
#ifdef _SC_NGROUPS_MAX
			int maxgrp = sysconf(_SC_NGROUPS_MAX);

			if (maxgrp < 0)
				maxgrp = NGROUPS;
#else
			int maxgrp = NGROUPS;
#endif

			while (isSPACE(*p))
				++p;
			new_egid = Atol(p);
			for (i = 0; i < maxgrp; ++i) {
				while (*p && !isSPACE(*p))
					++p;
				while (isSPACE(*p))
					++p;
				if (!additional)
					additional = p;
				if (!*p)
					break;
				if(!gary)
					Newx(gary, i + 1, Groups_t);
				else
					Renew(gary, i + 1, Groups_t);
				gary[i] = Atol(p);
			}
			if (i) {
				if (setgroups(i, gary) < 0) {
					Perl_croak(aTHX_ "setgroups(%s) failed: %s", additional, Strerror(errno));
				}
			}
			
			Safefree(gary);
		}
#else  /* HAS_SETGROUPS */
		new_egid = SvIV(sv);
#endif /* HAS_SETGROUPS */
		if (PL_delaymagic) {
			PL_delaymagic_egid = new_egid;
			PL_delaymagic |= DM_EGID;
			break;								/* don't do magic till later */
		}
#ifdef HAS_SETRESGID
		ret = setresgid((Gid_t)-1, (Gid_t)new_egid, (Gid_t)-1);
#else
#ifdef HAS_SETEGID
		ret = setegid((Gid_t)new_egid);
#else
#ifdef HAS_SETREGID
		ret = setregid((Gid_t)-1, (Gid_t)new_egid);
#else
		if (new_egid == PerlProc_getgid())						/* special case $) = $( */
			ret = PerlProc_setgid(new_egid);
		else {
			Perl_croak(aTHX_ "setegid() not implemented");
		}
#endif
#endif
#endif
		if (ret < 0)
			Perl_croak(aTHX_ "setegid(%d) failed: %s", new_egid, Strerror(errno));
#ifdef PL_egid
		PL_egid = PerlProc_getegid();
#endif
		}
		break;
		default:
			return Perl_magic_set(aTHX_ sv, mg);
	}
}

const MGVTBL new_vtable = { Perl_magic_get, new_magic_set };

MODULE = autodie::variables                PACKAGE = autodie::variables

void
_reset_global(var)
	SV* var;
    CODE:
		MAGIC* magic = mg_find(var, PERL_MAGIC_sv);
		magic->mg_virtual = (MGVTBL*)&new_vtable;