#!/usr/bin/perl
# Installs a package dependency. By and large the source package names are
# based on opensuse but the capacity exists to map the package names based
# on the distribution.
use strict;
use POSIX();
use POSIX qw(getuid);

if ($ENV{"ASSUME_PACKAGE_INSTALLED"} eq "yes") {
	exit 0;
}

$ENV{"ZYPP_LOCK_TIMEOUT"}=300;

# File::Which is not always available. Bodge it
sub which {
	my $binary = $_[0];

	open(BODGE, "command -v $binary 2>/dev/null |") || die("Failed to open pipe to which");
	chomp(my $line = <BODGE>);
	close(BODGE);
	return $line;
}

my %package_map = (
	"debian::acl-devel"			=> "libacl1-dev",
	"debian::attr-devel"			=> "libattr1-dev",
	"debian::binutils-devel"		=> "binutils-dev",
	"debian::blas-devel"			=> "libblas-dev",
	"debian::btrfsprogs"			=> "btrfs-progs",
	"debian::cpupower"			=> "linux-cpupower",
	"debian::diffutils"			=> "diff",
	"debian::expect-devel"			=> "expect-dev",
	"debian::gcc-32bit"			=> "gcc-multilib",
	"debian::gcc-c++"			=> "g++",
	"debian::gcc-fortran"			=> "gfortran",
	"debian::hwloc-devel"			=> "libhwloc-dev",
	"debian::hwloc"				=> "hwloc-nox",
	"debian::libaio-devel"			=> "libaio-dev",
	"debian::libcurl-devel"			=> "libcurl4-gnutls-dev",
	"debian::libelf-devel"			=> "libelf-dev",
	"debian::libevent-devel"		=> "libevent-dev",
	"debian::libhugetlbfs"			=> "libhugetlbfs0",
	"debian::libnuma-devel"			=> "libnuma-dev",
	"debian::libopenssl-devel"		=> "libssl-dev",
	"debian::libpsm_infinipath1"		=> "libpsm-infinipath1",
	"debian::libtirpc-devel"		=> "libtirpc-dev",
	"debian::libuuid-devel"			=> "uuid-dev",
	"debian::libxml2-devel"			=> "libxml2-dev",
	"debian::mono"				=> "mono-complete",
	"debian::MPlayer"			=> "mplayer",
	"debian::perl-Math-Round"		=> "libmath-round-perl",
	"debian::perl-File-Slurp"		=> "libfile-slurp-perl",
	"debian::perl-Time-HiRes"		=> "perl",
	"debian::popt-devel"			=> "libpopt-dev",
	"debian::python3-devel"			=> "python3-dev",
	"debian::rpcgen"			=> "rpcsvc-proto",
	"debian::which"				=> "debianutils",
	"debian::xfsprogs-devel"		=> "xfslibs-dev",
	"debian::xorg-x11-server"		=> "xserver-xorg",
	"debian::xz"				=> "xz-utils",
	"debian::zeromq-devel"			=> "libzmq3-dev",
	"debian::zlib-devel"			=> "zlib1g-dev",
	"debian::perl-List-BinarySearch"	=> "liblist-binarysearch-perl",
	"debian::perl-GD"			=> "libgd-perl",
	"debian::sysvinit-tools"		=> "sysvinit-utils",
	"debian::gettext-runtime"		=> "gettext",

	"redhat::blas-devel"			=> "openblas-devel",
	"redhat::blas-devel-static"		=> "openblas-static",
	"redhat::btrfsprogs"			=> "btrfs-progs",
	"redhat::libbz2-devel"			=> "bzip2-devel",
	"redhat::libexpat-devel"		=> "expat-devel",
	"redhat::cpupower"			=> "kernel-tools",
	"redhat::expect-devel"			=> "expect",
	"redhat::gcc-32bit"	 		=> "libgcc.i686",
	"redhat::gcc-fortran"			=> "gcc-gfortran",
	"redhat::glibc-devel-static-32bit"	=> "glibc-static.i686",
	"redhat::libblas3"			=> "openblas",
	"redhat::libelf-devel"			=> "elfutils-libelf-devel",
	"redhat::libnuma-devel"			=> "numactl-devel",
	"redhat::libnurses5"			=> "ncurses",
	"redhat::libopenssl-devel"		=> "openssl-devel",
	"redhat::openmpi-32bit"			=> "openmpi.i686",
	"redhat::openmpi-32bit"			=> "openmpi.i686",
	"redhat::tcsh"				=> "csh",
	"redhat::netcat-openbsd"		=> "netcat",
	"redhat::iproute2"			=> "iproute",
	"redhat::procps"			=> "procps-ng",
);

my %package_bin = (
	"suse"		=> "zypper install",
	"debian"	=> "apt-get install",
	"redhat"	=> "yum install",
);

my %force_install = (
	"suse" => "",
	"debian" => "",
	"redhat" => "",
);

my %noninteractive_install = (
	"suse" => "",
	"debian" => "",
	"redhat" => "",
);

my %allow_downgrade = (
	"suse" => "",
	"debian" => "",
	"redhat" => "",
);

my %check_package = (
	"suse" => "rpm -q",
	"debian" => "dpkg-query -s",
	"redhat" => "rpm -q",
);

my $distro;
foreach my $release_file ("debian_version", "debian_release",
			  "redhat-release", "redhat_version",
			  "SuSE-release") {
	if (-r "/etc/$release_file" ) {
		$distro = $release_file;
		$distro =~ tr/[A-Z]/[a-z]/;
		$distro =~ s/[_-].*//;
		last;
	}
}

if (!defined($distro)) {
	if (-r "/etc/os-release") {
		open(OSRELEASE, "</etc/os-release");
		foreach (<OSRELEASE>) {
			# https://www.freedesktop.org/software/systemd/man/os-release.html
			# multiple values possible
			if (/ID_LIKE="(.*)"/) {
				$distro = $1;
				last;
			}
			if (/CPE_NAME="cpe:\/o:([a-z]*):/) {
				$distro = $1;
				last;
			}
		}
	}
}

if (!defined($distro)) {
	if (-r "/etc/lsb-release") {
		open(LSBRELEASE, "/etc/lsb-release");
		foreach (<LSBRELEASE>) {
			if (/DISTRIB_ID=(.*)/) {
				$distro = $1;
				$distro =~ tr/"//d;
				$distro =~ tr/[A-Z]/[a-z]/;
				last;
			}
		}
	}
}

if (!defined($distro)) {
	die("Failed to identify distribution\n");
}

# If we're "suse"-like (no matter whether "suse", "suse opensuse" or
# "opensuse suse"), we want zypper
$distro =~ s/(.*)(\bsuse\b)(.*)/$2/;

if (!defined($package_bin{$distro})) {
	die("Do not know how to invoke package manager for distro $distro");
}

my $sudo = "";
if (getuid() != 0) {
	$sudo = which("sudo");
	if (!defined $sudo) {
		print "ERROR: user is not root and sudo not available\n";
		exit(-1);
	}
}

if ($ENV{"MMTESTS_SESSION_ID"} ne "") {
	my $file = "/tmp/packages." . $ENV{"MMTESTS_SESSION_ID"};
	open(SESSION, ">>$file") || die("Failed to open $file");
}

my $exit_code = 0;
foreach my $package (@ARGV) {
	if ($ENV{"MMTESTS_SESSION_ID"} ne "") {
		print SESSION "$package\n";
	}

	# Map the package onto the distro-equivalent name
	if (defined($package_map{"$distro\::$package"})) {
		$package = $package_map{"$distro\::$package"};
	}

	# Check if the package is already installed
	if ($distro eq "debian") {
		if (system("dpkg-query -W --showformat='\${Status}\n' $package | grep 'install ok installed' >/dev/null 2>&1") == 0) {
			next;
		}
	} else {
		# Assume RPM based distro
		if (!defined(which("rpm"))) {
			die("Assumed rpm-based distro but no rpm");
		}
		if (system("rpm -q $package >& /dev/null") == 0) {
			next;
		}
	}

	if ($ENV{"AUTO_PACKAGE_INSTALL"} ne "yes" && ! -e $ENV{"HOME"} . "/.mmtests-auto-package-install") {
		if (! -e $ENV{"HOME"} . "/.mmtests-never-auto-package-install") {
			print "MMTests needs to install $package, should all packages be automatically installed (yes/no/never)? ";
			my $input = <STDIN>;
			chomp($input);
			if ($input eq "yes" || $input eq "y") {
				open(TOUCH, '>', $ENV{"HOME"} . "/.mmtests-auto-package-install");
				close TOUCH;
			}
			if ($input eq "never") {
				open(TOUCH, '>', $ENV{"HOME"} . "/.mmtests-never-auto-package-install");
				close TOUCH;
			}
		}
	}

	if ($ENV{"AUTO_PACKAGE_INSTALL"} eq "yes" || -e $ENV{"HOME"} . "/.mmtests-auto-package-install") {
		$force_install{"suse"} = "-y";
		$force_install{"debian"} = "-y";
		$force_install{"redhat"} = "-y";
		$noninteractive_install{"debian"} = "DEBIAN_FRONTEND=noninteractive";
	}

	if ($ENV{"AUTO_PACKAGE_DOWNGRADE"} eq "yes" || -e $ENV{"HOME"} . "/.mmtests-auto-package-downgrade") {
		$allow_downgrade{"suse"} = "--allow-downgrade";
	}

	if (system("$check_package{$distro} $package >/dev/null 2>&1") != 0) {
		if (system("$noninteractive_install{$distro} $sudo $package_bin{$distro} $force_install{$distro} $package") != 0) {
			if (system("$noninteractive_install{$distro} $sudo $package_bin{$distro} $allow_downgrade{$distro} $force_install{$distro} $package") != 0) {
				print("WARNING: Failed to cleanly install package $package for distro $distro\n");
				$exit_code = -1;
			}
		}
	}

	print "Installed $package\n";
}

if ($ENV{"MMTESTS_SESSION_ID"} ne "") {
	close(SESSION);
}

exit($exit_code);
