#! /usr/bin/perl
# Run a test given as @ARGV, and then munge its exit code to 77 if it
# failed because of an "exec format error."
#
# Written by Zack Weinberg <zackw at panix.com> in 2017--2020.
# To the extent possible under law, Zack Weinberg has waived all
# copyright and related or neighboring rights to this work.
#
# See https://creativecommons.org/publicdomain/zero/1.0/ for further
# details.
use v5.14; # implicit use strict; use feature ':5.14';
use warnings FATAL => 'all';
use utf8;
use open qw(:std :utf8);
no if $] >= 5.022, warnings => 'experimental::re_strict';
use if $] >= 5.022, re => 'strict';
use FindBin ();
use IPC::Open3 qw(open3);
use Symbol qw(gensym);
use lib $FindBin::Bin;
use BuildCommon qw(subprocess_error);
# We cannot simply exec the program and then check $!{ENOEXEC} if that
# fails, because Perl's `exec` primitive calls execvp(3), and POSIX
# requires execvp(3) to retry after rewriting the command line as
# `/bin/sh PROGRAM ARGUMENTS...` if execve(2) fails with ENOEXEC.
# This is a backward compatibility quirk for the sake of code written
# before `#!` was a thing; if you ask me, on modern systems it's a
# misfeature, but the Austin Group seems unwilling to change anything
# (see https://austingroupbugs.net/view.php?id=1435). Even if they
# did, we would be stuck with build-system C libraries that
# implemented the old bad behavior for many years.
#
# There is no way to call execve(2) directly from Perl. We could
# implement this program in C, but then we would need $(CC_FOR_BUILD),
# which currently we do not. So what we do, is rely on /bin/sh to
# have some kind of heuristic to detect when it's being asked to
# interpret a machine-code executable, and to print a recognizable
# error message when this happens. This is the same thing the old
# shell-based implementation did, so we can live with it.
my $status = eval {
my $child_err = gensym;
my $pid = open3('<&STDIN', '>&STDOUT', $child_err, @ARGV);
my $saw_enoexec = 0;
local $_;
while (<$child_err>) {
$saw_enoexec = 1
if m{\b(?:
[Ex]ec \s+ format \s+ error
| cannot \s+ execute \s+ (?:ELF \s+)? binary
)\b}x;
print {*STDERR} $_;
}
close $child_err or die "read error: $!\n";
waitpid $pid, 0 or die "waitpid: $!\n";
if (my $sig = ($? & 0x7F)) {
# subprocess_error knows how to print symbolic names for signals.
subprocess_error($ARGV[0]);
} else {
return 77 if $saw_enoexec;
return $? >> 8;
}
};
exit $status if defined $status;
# We only get here if there was an error.
my $err = $@;
$err =~ s/\s+ at \s+ \S+ \s+ line \s+ \d+ \.? \n? \Z//x;
$err =~ s/^open3: //;
print {*STDERR} "${FindBin::Script}: $err\n";
exit(($err =~ /\bExec format error\b/) ? 77 : 99);