#!./perl

BEGIN{
	# Don't do anything if POSIX is missing, or sigaction missing.
	use Config;
	eval 'use POSIX';
	if($@ || $^O eq 'MSWin32' || $^O eq 'NetWare' || $^O eq 'dos' ||
	   $^O eq 'MacOS' || ($^O eq 'VMS' && !$Config{'d_sigaction'})) {
		print "1..0\n";
		exit 0;
	}
}

use Test::More tests => 36;

use strict;
use vars qw/$bad $bad7 $ok10 $bad18 $ok/;

$^W=1;

sub IGNORE {
    ++$bad7;
}

sub DEFAULT {
    ++$bad18;
}

sub foo {
    ++$ok;
}

my $newaction=POSIX::SigAction->new('::foo', new POSIX::SigSet(SIGUSR1), 0);
my $oldaction=POSIX::SigAction->new('::bar', new POSIX::SigSet(), 0);

{
	my $bad;
	local($SIG{__WARN__})=sub { $bad=1; };
	sigaction(SIGHUP, $newaction, $oldaction);
	is($bad, undef, "no warnings");
}

like($oldaction->{HANDLER}, qr/\A(?:DEFAULT|IGNORE)\z/, '$oldaction->{HANDLER}');

is($SIG{HUP}, '::foo');

sigaction(SIGHUP, $newaction, $oldaction);
is($oldaction->{HANDLER}, '::foo');

ok($oldaction->{MASK}->ismember(SIGUSR1), "SIGUSR1 ismember MASK");

SKIP: {
    skip("sigaction() thinks different in $^O", 1)
	if $^O eq 'linux' || $^O eq 'unicos';
    is($oldaction->{FLAGS}, 0);
}

$newaction=POSIX::SigAction->new('IGNORE');
sigaction(SIGHUP, $newaction);
kill 'HUP', $$;
is($bad, undef, "SIGHUP ignored");

is($SIG{HUP}, 'IGNORE');
sigaction(SIGHUP, POSIX::SigAction->new('DEFAULT'));
is($SIG{HUP}, 'DEFAULT');

$newaction=POSIX::SigAction->new(sub { ++$ok10; });
sigaction(SIGHUP, $newaction);
{
	local($^W)=0;
	kill 'HUP', $$;
}
is($ok10, 1, "SIGHUP handler called");

is(ref($SIG{HUP}), 'CODE');

sigaction(SIGHUP, POSIX::SigAction->new('::foo'));
# Make sure the signal mask gets restored after sigaction croak()s.
eval {
	my $act=POSIX::SigAction->new('::foo');
	delete $act->{HANDLER};
	sigaction(SIGINT, $act);
};
kill 'HUP', $$;
is($ok, 1, "signal mask gets restored after croak");

undef $ok;
# Make sure the signal mask gets restored after sigaction returns early.
my $x=defined sigaction(SIGKILL, $newaction, $oldaction);
kill 'HUP', $$;
is($x, '', "signal mask gets restored after early return");
is($ok, 1, "signal mask gets restored after early return");

$SIG{HUP}=sub {};
sigaction(SIGHUP, $newaction, $oldaction);
is(ref($oldaction->{HANDLER}), 'CODE');

eval {
	sigaction(SIGHUP, undef, $oldaction);
};
is($@, '', "undef for new action");

eval {
	sigaction(SIGHUP, 0, $oldaction);
};
is($@, '', "zero for new action");

eval {
	sigaction(SIGHUP, bless({},'Class'), $oldaction);
};
like($@, qr/\Aaction is not of type POSIX::SigAction/,
     'any object not good as new action');

SKIP: {
    skip("SIGCONT not trappable in $^O", 1)
	if ($^O eq 'VMS');
    $newaction=POSIX::SigAction->new(sub { ++$ok10; });
    if (eval { SIGCONT; 1 }) {
	sigaction(SIGCONT, POSIX::SigAction->new('DEFAULT'));
	{
	    local($^W)=0;
	    kill 'CONT', $$;
	}
    }
    is($bad18, undef, "SIGCONT trappable");
}

{
    local $SIG{__WARN__} = sub { }; # Just suffer silently.

    my $hup20;
    my $hup21;

    sub hup20 { $hup20++ }
    sub hup21 { $hup21++ }

    sigaction("FOOBAR", $newaction);
    pass("no coredump, still alive");

    $newaction = POSIX::SigAction->new("hup20");
    sigaction("SIGHUP", $newaction);
    kill "HUP", $$;
    is($hup20, 1);

    $newaction = POSIX::SigAction->new("hup21");
    sigaction("HUP", $newaction);
    kill "HUP", $$;
    is ($hup21, 1);
}

# "safe" attribute.
# for this one, use the accessor instead of the attribute

# standard signal handling via %SIG is safe
$SIG{HUP} = \&foo;
$oldaction = POSIX::SigAction->new;
sigaction(SIGHUP, undef, $oldaction);
ok($oldaction->safe, "SIGHUP is safe");

# SigAction handling is not safe ...
sigaction(SIGHUP, POSIX::SigAction->new(\&foo));
sigaction(SIGHUP, undef, $oldaction);
ok(!$oldaction->safe, "SigAction not safe by default");

# ... unless we say so!
$newaction = POSIX::SigAction->new(\&foo);
$newaction->safe(1);
sigaction(SIGHUP, $newaction);
sigaction(SIGHUP, undef, $oldaction);
ok($oldaction->safe, "SigAction can be safe");

# And safe signal delivery must work
$ok = 0;
kill 'HUP', $$;
is($ok, 1, "safe signal delivery must work");

SKIP: {
    eval 'use POSIX qw(%SIGRT SIGRTMIN SIGRTMAX); scalar %SIGRT + SIGRTMIN() + SIGRTMAX()';
    $@					# POSIX did not exort
    || SIGRTMIN() < 0 || SIGRTMAX() < 0	# HP-UX 10.20 exports both as -1
    || SIGRTMIN() > $Config{sig_count}	# AIX 4.3.3 exports bogus 888 and 999
	and skip("no SIGRT signals", 4);
    cmp_ok(SIGRTMAX(), '>', SIGRTMIN(), "SIGRTMAX > SIGRTMIN");
    is(scalar %SIGRT, SIGRTMAX() - SIGRTMIN() + 1, "scalar SIGRT");
    my $sigrtmin;
    my $h = sub { $sigrtmin = 1 };
    $SIGRT{SIGRTMIN} = $h;
    is($SIGRT{SIGRTMIN}, $h, "handler set & get");
    kill 'SIGRTMIN', $$;
    is($sigrtmin, 1, "SIGRTMIN handler works");
}

SKIP: {
    my %siginfo = (
        signo => SIGHUP,
        pid => $$,
        uid => $<,
    );
    my %opt_val = ( code => 'SI_USER' );
    my %always = map +($_ => 1), qw(signo code);
    my %skip = ( code => { darwin => "not set to SI_USER for kill()" } );
    $skip{pid}{$^O} = $skip{uid}{$^O} = "not set for kill()"
        if (($^O.$Config{osvers}) =~ /^darwin[0-8]\./
            ||
            ($^O.$Config{osvers}) =~ /^openbsd[0-6]\./
            ||
            ($^O eq 'gnu')
            ||
            ($^O eq 'dragonfly'));
    my $tests = keys %{{ %siginfo, %opt_val }};
    eval 'use POSIX qw(SA_SIGINFO); SA_SIGINFO';
    skip("no SA_SIGINFO", $tests) if $@;
    skip("SA_SIGINFO is broken on AIX 4.2", $tests) if ($^O.$Config{osvers}) =~ m/^aix4\.2/;
    skip("SA_SIGINFO is broken on os390", $tests) if ($^O.$Config{osvers}) =~ m/os390/;
    eval "use POSIX qw($opt_val{$_}); \$siginfo{$_} = $opt_val{$_}"
        for keys %opt_val;
    sub hiphup {
        for my $field (sort keys %{{ %siginfo, %opt_val }}) {
            SKIP: {
                skip("siginfo_t has no $field field", 1)
                    unless %always{$field} or ($Config{"d_siginfo_si_$field"} || '') eq 'define';
                skip("no constant defined for SA_SIGINFO $field value $opt_val{$field}", 1)
                    unless defined $siginfo{$field};
                skip("SA_SIGINFO $field value is wrong on $^O: $skip{$field}{$^O}", 1)
                    if $skip{$field}{$^O};
                is($_[1]->{$field}, $siginfo{$field}, "SA_SIGINFO got right $field")
            }
        }
    }
    my $act = POSIX::SigAction->new('hiphup', 0, SA_SIGINFO);
    sigaction(SIGHUP, $act);
    kill 'HUP', $$;
}

eval { sigaction(-999, "foo"); };
like($@, qr/Negative signals/,
    "Prevent negative signals instead of core dumping");

# RT 77432 - assertion failure with POSIX::SigAction
{
  local *SIG = {};
  ok(sigaction(SIGHUP, POSIX::SigAction->new),
     "sigaction would crash/assert with a replaced %SIG");
}
