#!/usr/bin/perl
#
# check-config -- check the current config for issues
#
use strict;
use File::Basename;
use File::Spec;

my $P = 'check-config';

my $test = -1;
if ($ARGV[0] eq '--test') {
	$test = $ARGV[1] + 0;
} elsif ($#ARGV != 5) {
	die "Usage: $P <config> <arch> <flavour> <commonconfig> <warn-only> <enforce-all>\n";
}

my ($configfile, $arch, $flavour, $commonconfig, $warn_only, $enforce_all) = @ARGV;

my %values = ();

# If we are in overridden then still perform the checks and emit the messages
# but do not return failure.  Those items marked FATAL will alway trigger
# failure.
my $fail_exit = 1;
$fail_exit = 0 if ($warn_only eq 'true' || $warn_only eq '1');
my $exit_val = 0;

$enforce_all = 0 if $enforce_all eq "no" or $enforce_all eq "false";

# Load up the current configuration values -- FATAL if this fails
print "$P: $configfile: loading config\n";
open(CONFIG, "<$configfile") || die "$P: $configfile: open failed -- $! -- aborting\n";
while (<CONFIG>) {
	# Pull out values.
	/^#*\s*(CONFIG_\w+)[\s=](.*)$/ or next;
	if ($2 eq 'is not set') {
		$values{$1} = 'n';
	} else {
		$values{$1} = $2;
	}
}
close(CONFIG);

sub read_annotations {
    my ($filename) = @_;
    my %annot;
    my $form = 1;
    my ($config, $value, $options);

    # Keep track of the configs that shouldn't be appended because
    # they were include_annot from another annotations file.
    # That's a hash of undefs, aka a set.
    my %noappend;

    print "$P: $filename loading annotations\n";
    open(my $fd, "<$filename") ||
	die "$P: $filename: open failed -- $! -- aborting\n";
    while (<$fd>) {
	if (/^# FORMAT: (\S+)/) {
	    die "$P: $1: unknown annotations format\n" if ($1 < 2 || $1 > 4);
	    $form = $1;
	}

	# Format #3 and #4 add the include directive on top of format #2:
	if ($form >= 3 && /^\s*include(\s|$)/) {
	    # Include quoted or unquoted files:
	    if (/^\s*include\s+"(.*)"\s*$/ || /^\s*include\s+(.*)$/) {
		# The include is relative to the current file
		my $include_filename = File::Spec->join(dirname($filename), $1);
		# Append the include files
		my %include_annot = read_annotations($include_filename);
		%annot = ( %annot, %include_annot );
		# And marked them to not be appended:
		my %included_noappend;
		# Discard the values and keep only the keys
		@included_noappend{keys %include_annot} = ();
		%noappend = ( %noappend, %included_noappend );
		next;
	    } else {
		die "$P: Invalid include: $_";
	    }
	}

	/^#/ && next;
	chomp;
	/^$/ && next;
	/^CONFIG_/ || next;

	if ($form == 1) {
	    ($config, $value, $options) = split(' ', $_, 3);
	} elsif ($form >= 2) {
	    ($config, $options) = split(' ', $_, 2);
	}

	if (exists $noappend{$config}) {
	    delete $annot{$config};
	    delete $noappend{$config};
	}
	$annot{$config} = $annot{$config} . ' ' . $options;
    }
    close($fd);
    return %annot;
}

# ANNOTATIONS: check any annotations marked for enforcement
my $annotations = "$commonconfig/annotations";
my %annot = read_annotations($annotations);

my $pass = 0;
my $total = 0;
my ($config, $value, $options, $option, $check, $policy);
for $config (keys %annot) {
	$check = $enforce_all;
	$options = $annot{$config};

	$policy = undef;
	while ($options =~ /\s*([^\s<]+)<(.*?)?>/g) {
		($option, $value) = ($1, $2);

		if ($option eq 'mark' && $value eq 'ENFORCED') {
			$check = 1;

		} elsif ($option eq 'policy') {
			if ($value =~ /^{/) {
				$value =~ s/:/=>/g;
				$policy = eval($value);
				warn "$config: $@" if ($@);
			} else {
				$policy = undef;
			}
		}
	}
	if ($check == 1 && !defined($policy)) {
		print "$P: INVALID POLICY (use policy<{...}>) $config$options\n";
		$total++;
		$check = 0;
	}
	if ($check) {
		# CONFIG_VERSION_SIGNATURE is dynamically set during the build
		next if ($config eq "CONFIG_VERSION_SIGNATURE");
		my $is = '-';
		$is = $values{$config} if (defined $values{$config});

		my $value = '-';
		for my $which ("$arch-$flavour", "$arch-*", "*-$flavour", "$arch", "*") {
			if (defined $policy->{$which}) {
				$value = $policy->{$which};
				last;
			}
		}
		if ($is eq $value) {
			$pass++;
		} else {
			print "$P: FAIL ($is != $value): $config$options\n";
			$exit_val = $fail_exit;
		}
		$total++;
	}
}

print "$P: $pass/$total checks passed -- exit $exit_val\n";
exit $exit_val;
