Bug#793471: Perl system and $SIG{__WARN__} when exec fails

Ian Jackson ijackson at chiark.greenend.org.uk
Fri Jul 24 10:50:36 UTC 2015


Package: perl
Version: 5.20.2-6

See transcripts below.

Perl's system builtin needs to fork and exec.  If the exec fails it
needs to print a message to stderr and exit.  It appears that this is
implemented in Perl and that it does the latter with sysexit, but the
former with warn.

This has a number of strange effects, which are visible if a
$SIG{__WARN__} handler is installed.  Ones which can be observed
below include:

* If $SIG{__WARN__} = sub { die $_[0]; } (or something else that
  raises an exception) then the control flow escapes from the confines
  of system, and avoids the sysexit.

  The program's END blocks might be executed, for example.

* Worse, if the program has its own exception handler surrounding
  system, it will now start executing the main program both in the
  parent and in the child.

* During the $SIG{__WARN__} handler, the Perl interpreter is not
  properly set up in all versions of Perl.  For example in
  5.14.2-21+deb7u2, $$ is a lie - it still refers to the parent
  process.

* Something else weird seems to be going on.  I find these two
  messages in the transcript below hard to explain:
    24633 GOT UNDEF EVAL  at ./t.pl line 15.
    24633 BARE  at ./t.pl line 21.
  It appears that something is discarding or squashing an error.

  The latter is particularly puzzling because $@ is left as ""
  (indicating that the eval did not catch an exception) but $y is not
  set to 42 (indicating that the eval did not run to completion).


I suggest the following remedies, in combination:

* system() should reset $SIG{__WARN__} in the child.  There should be
  a way to set $SIG{__WARN__} within system's child, but it should be
  separate.  $SIG{__WARN_CHILD__} or something maybe.

* The behaviour should be documented.

* The fact that $$ was wrong should be written down somewhere in the
  manual so that users use the correct workaround (see below).

* And ideally someone familiar with the implementation should satisfy
  themselves that they understand the currently observed behaviour, so
  that we are confident that there aren't other lurking oddities.


As a workaround, people using unfixed versions of Perl can write:

  my $mainprogram = $$;
  $SIG{__WARN__} = sub { die $_[0] unless getppid == $mainprogram; };

Note that `die $_[0] if $$ == $mainprogram' is ineffective because $$
is wrong.


Thanks,
Ian.

(build)ian at zealot:~/junk$ cat t.pl
#!/usr/bin/perl -w
use strict;
use POSIX;

$SIG{__WARN__} = sub { die "DYING DUE TO WARNING $_[0]"; };

END { print STDERR "wheee!\n" if getppid == $$; }
END { print STDERR "getppid= ", getppid, "\n"; }
END { print STDERR "\$\$= $$\n"; }

my @bad = qw(/dev/enoent nothing);

print STDERR "---------- 1 ".getppid." $$ ----------\n";

my $y = eval { system @bad and die "EVAL $!"; 42; };
$y //= 'UNDEF';
print STDERR getppid." GOT $y $@\n";

print STDERR "---------- 2 ".getppid." ----------\n";

system @bad and die getppid." BARE $!";

print STDERR "NOTREACHED\n";
(build)ian at zealot:~/junk$ ./t.pl
---------- 1 24633 24948 ----------
24948 GOT UNDEF DYING DUE TO WARNING Can't exec "/dev/enoent": No such file or directory at ./t.pl line 15.

---------- 2 24948 ----------
DYING DUE TO WARNING Can't exec "/dev/enoent": No such file or directory at ./t.pl line 21.
$$= 24950
getppid= 24949
24948 BARE No such file or directory at ./t.pl line 21.
$$= 24949
getppid= 24948
24633 GOT UNDEF EVAL  at ./t.pl line 15.

---------- 2 24633 ----------
DYING DUE TO WARNING Can't exec "/dev/enoent": No such file or directory at ./t.pl line 21.
$$= 24951
getppid= 24948
24633 BARE  at ./t.pl line 21.
$$= 24948
getppid= 24633
(build)ian at zealot:~/junk$


zealot:~/junk> ./t.pl
---------- 1 23655 24976 ----------
24976 GOT UNDEF DYING DUE TO WARNING Can't exec "/dev/enoent": No such file or directory at ./t.pl line 15.

---------- 2 24976 ----------
DYING DUE TO WARNING Can't exec "/dev/enoent": No such file or directory at ./t.pl line 21.
$$= 24976
getppid= 24978
24976 BARE No such file or directory at ./t.pl line 21.
$$= 24976
getppid= 24976
wheee!
23655 GOT UNDEF EVAL  at ./t.pl line 15.

---------- 2 23655 ----------
DYING DUE TO WARNING Can't exec "/dev/enoent": No such file or directory at ./t.pl line 21.
$$= 24976
getppid= 24976
wheee!
23655 BARE  at ./t.pl line 21.
$$= 24976
getppid= 23655
zealot:~/junk>




More information about the Perl-maintainers mailing list