#
# Conf.pm: configuration library for buildd
# Copyright © 1998 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
# Copyright © 2005 Ryan Murray <rmurray@debian.org>
# Copyright © 2006-2009 Roger Leigh <rleigh@debian.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see
# <http://www.gnu.org/licenses/>.
#
#######################################################################

package Buildd::Conf;

use strict;
use warnings;

use Buildd::DistConf        qw();
use Buildd::UploadQueueConf qw();
use Sbuild::ConfBase;
use Sbuild::Sysconfig;
use Buildd::ClientConf qw();

BEGIN {
	use Exporter ();
	our (@ISA, @EXPORT);

	@ISA = qw(Exporter);

	@EXPORT = qw($reread_config new setup read);
}

our $reread_config = 0;

sub setup ($);
sub read ($);

sub new {
	my $conf = Sbuild::ConfBase->new(@_);
	Buildd::Conf::setup($conf);
	Buildd::Conf::read($conf);

	return $conf;
}

sub setup ($) {
	my $conf = shift;

	my $validate_program = sub {
		my $conf    = shift;
		my $entry   = shift;
		my $key     = $entry->{'NAME'};
		my $program = $conf->get($key);

		die "$key binary is not defined"
		  if !defined($program) || !$program;

		# Emulate execvp behaviour by searching the binary in the PATH.
		my @paths = split(/:/, $conf->get('PATH'));
		# Also consider the empty path for absolute locations.
		push(@paths, '');
		my $found = 0;
		foreach my $path (@paths) {
			$found = 1 if (-x File::Spec->catfile($path, $program));
		}

		die "$key binary '$program' does not exist or is not executable"
		  if !$found;
	};

	my $validate_directory = sub {
		my $conf      = shift;
		my $entry     = shift;
		my $key       = $entry->{'NAME'};
		my $directory = $conf->get($key);

		die "$key directory is not defined"
		  if !defined($directory) || !$directory;

		die "$key directory '$directory' does not exist"
		  if !-d $directory;
	};

	our $HOME = $conf->get('HOME');
	$main::HOME = $HOME;    # TODO: Remove once Buildd.pm uses $conf
	my $arch = $conf->get('ARCH');

	my @buildd_keys = (
		'ADMIN_MAIL' => {
			TYPE    => 'STRING',
			VARNAME => 'admin_mail',
			GROUP   => 'Mail',
			DEFAULT => 'root',
			HELP    => 'email address for admin'
		},
		'APT_GET' => {
			TYPE    => 'STRING',
			VARNAME => 'apt_get',
			GROUP   => 'Programs',
			CHECK   => $validate_program,
			DEFAULT => 'apt-get',
			HELP    => 'Path to apt-get binary'
		},
		'DPKG_FILE_SUFFIX' => {
			TYPE    => 'STRING',
			VARNAME => 'dpkg_file_suffix',
			GROUP   => 'Programs',
			DEFAULT => '',
			HELP    =>
'Value for the sbuild dpkg-file-suffix option, to be passed on to sbuild',
		},
		'BUILD_LOG_KEEP' => {
			TYPE    => 'NUMERIC',
			VARNAME => 'build_log_keep',
			GROUP   => 'Watcher',
			DEFAULT => 2,
			HELP    => 'Number of days until build logs are archived'
		},
		'DAEMON_LOG_FILE' => {
			TYPE           => 'STRING',
			VARNAME        => 'daemon_log_file',
			GROUP          => 'Daemon',
			IGNORE_DEFAULT => 1,                  # Don't dump the current home
			DEFAULT        => "$HOME/daemon.log",
			HELP           => 'Main buildd daemon log file'
		},
		'DAEMON_LOG_KEEP' => {
			TYPE    => 'NUMERIC',
			VARNAME => 'daemon_log_keep',
			GROUP   => 'Watcher',
			DEFAULT => 7,
			HELP    =>
'Number of days until old daemon logs are archived in a .tar.gz file'
		},
		'DAEMON_LOG_ROTATE' => {
			TYPE    => 'NUMERIC',
			VARNAME => 'daemon_log_rotate',
			GROUP   => 'Watcher',
			DEFAULT => 1,
			HELP    =>
'Number how many days until daemon logs are rotated (one is kept as daemon.log.old, others are moved to old-logs and gzipped)'
		},
		'DAEMON_LOG_SEND' => {
			TYPE    => 'BOOL',
			VARNAME => 'daemon_log_send',
			GROUP   => 'Watcher',
			DEFAULT => 1,
			HELP    => 'email rotated daemon logs to the admin?'
		},
		'DELAY_AFTER_GIVE_BACK' => {
			TYPE    => 'NUMERIC',
			VARNAME => 'delay_after_give_back',
			GROUP   => 'Daemon',
			DEFAULT => 8 * 60,                    # 8 hours
			HELP    =>
'Time to avoid packages that have automatically been given back by sbuild (in minutes)'
		},
		'ERROR_MAIL_WINDOW' => {
			TYPE    => 'NUMERIC',
			VARNAME => 'error_mail_window',
			GROUP   => 'Mail',
			DEFAULT => 8 * 60 * 60,
			HELP    =>
'If more than five error mails are received within the specified time (in seconds), do not forward (to avoid possible mail loops)'
		},
		'IDLE_SLEEP_TIME' => {
			TYPE    => 'NUMERIC',
			VARNAME => 'idle_sleep_time',
			GROUP   => 'Daemon',
			DEFAULT => 5 * 60,
			HELP    =>
'Time to sleep when idle (in seconds) between wanna-build --list=needs-build calls)'
		},
		'LOG_QUEUED_MESSAGES' => {
			TYPE    => 'BOOL',
			VARNAME => 'log_queued_messages',
			GROUP   => 'Mail',
			DEFAULT => 0,
			HELP    => 'Log success messages from upload queue daemon?'
		},
		'MAX_SBUILD_FAILS' => {
			TYPE    => 'NUMERIC',
			VARNAME => 'max_sbuild_fails',
			GROUP   => 'Daemon',
			DEFAULT => 2,
			HELP => 'Maximum number of times sbuild can fail before sleeping'
		},
		'MIN_FREE_SPACE' => {
			TYPE    => 'NUMERIC',
			VARNAME => 'min_free_space',
			GROUP   => 'Daemon',
			DEFAULT => 50 * 1024,
			HELP    => 'Minimum free space (in KiB) on build filesystem'
		},
		'NICE_LEVEL' => {
			TYPE    => 'NUMERIC',
			VARNAME => 'nice_level',
			GROUP   => 'Build options',
			DEFAULT => 10,
			HELP    =>
'Nice level to run sbuild.  Dedicated build daemons should not be niced.'
		},
		'NO_DETACH' => {
			TYPE    => 'BOOL',
			VARNAME => 'no_detach',
			GROUP   => 'Daemon',
			DEFAULT => 0,
			HELP    =>
'Disable becoming a daemon, for debugging purposes.  Set to 1 to stop daemonising, otherwise set to 0 to become a daemon.'
		},
		'NO_WARN_PATTERN' => {
			TYPE    => 'STRING',
			VARNAME => 'no_warn_pattern',
			GROUP   => 'Watcher',
			DEFAULT =>
'^build/(SKIP|REDO|SBUILD-GIVEN-BACK|buildd\.pid|[^/]*.ssh|chroot-[^/]*|current-[^/]*)$',
			HELP => 'Don\'t complain about old files if they match the regexp.'
		},
		'PIDFILE' => {
			TYPE    => 'STRING',
			VARNAME => 'pidfile',
			GROUP   => 'Daemon',
# Set once running as a system service.
#          DEFAULT => "${Sbuild::Sysconfig::paths{'LOCALSTATEDIR'}/run/buildd.pid"
			IGNORE_DEFAULT => 1,    # Don't dump the current home
			DEFAULT        => "$HOME/build/buildd.pid",
			HELP           => 'PID file to identify running daemon.'
		},
		'PKG_LOG_KEEP' => {
			TYPE    => 'NUMERIC',
			VARNAME => 'pkg_log_keep',
			GROUP   => 'Watcher',
			DEFAULT => 7,
			HELP    => 'Number of days until to package logs are archived'
		},
		'SHOULD_BUILD_MSGS' => {
			TYPE    => 'BOOL',
			VARNAME => 'should_build_msgs',
			GROUP   => 'Daemon',
			DEFAULT => 1,
			HELP    => 'Should buildd send "Should I build" messages?'
		},
		'STATISTICS_MAIL' => {
			TYPE    => 'STRING',
			VARNAME => 'statistics_mail',
			GROUP   => 'Watcher',
			DEFAULT => 'root',
			HELP    => 'email address for statistics summaries'
		},
		'STATISTICS_PERIOD' => {
			TYPE    => 'NUMERIC',
			VARNAME => 'statistics_period',
			GROUP   => 'Watcher',
			DEFAULT => 7,
			HELP    => 'Period for statistic summaries (days)'
		},
		'SUDO' => {
			TYPE    => 'STRING',
			VARNAME => 'sudo',
			GROUP   => 'Programs',
			CHECK   => $validate_program,
			DEFAULT => 'sudo',
			HELP    => 'Path to sudo binary'
		},
		'WARNING_AGE' => {
			TYPE    => 'NUMERIC',
			VARNAME => 'warning_age',
			GROUP   => 'Watcher',
			DEFAULT => 7,
			HELP    =>
'Age (in days) after which a warning is issued for files in upload and dirs in build'
		},
		'CONFIG_TIME' => {
			TYPE    => 'NUMERIC',
			VARNAME => 'config_time',
			GROUP   => '__INTERNAL',
			DEFAULT => {},
			HELP    => 'Time configuration was last read'
		},
		'DISTRIBUTIONS' => {
			TYPE           => 'ARRAY:HASH:SCALAR',
			VARNAME        => 'distributions',
			GROUP          => 'Build options',
			DEFAULT        => [],
			IGNORE_DEFAULT => 1,                   # Don't dump class to config
			HELP           =>
			  'List of distributions that buildd should take packages from',
			EXAMPLE => '$distributions = [
	{
		# name of the suite to build (also used to query wanna-build)
		dist_name => ["unstable", "testing"],

		# architecture to be built (will be passed to sbuild and can be
		# used to compute wanna_build_db_name)
		built_architecture => undef,

		# host on which wanna-build is run
		wanna_build_ssh_host => "buildd.debian.org",

		# user as who we are going to connect to the host running wanna-build
		wanna_build_ssh_user => "buildd_arch",

		# SSH control socket path for ssh -S option
		wanna_build_ssh_socket => "",

		# Additional SSH options used when connecting
		wanna_build_ssh_options => [],

		# database used for wanna-build
		wanna_build_db_name => "arch/build-db",

		# Username to use for wanna-build.
		wanna_build_db_user => $Buildd::username,

		# Local queue directory where binaries are stored before uploaded
		# by dupload. You need to configure this directory in
		# @upload_queues to get packages uploaded from there.
		dupload_local_queue_dir => "upload",

		# list of packages which shouldn\'t be picked up by buildd
		no_auto_build => [],

		# list of packages which should only be taken if there absolutely
		# nothing else to do (probably packages included in no_auto_build
		# because they take too long)
		weak_no_auto_build => [],

		# regex used to filter out unwanted packages:
		#no_build_regex => "^(contrib/|non-free/)?non-US/",

		# regex used to filter packages to build:
		#build_regex => "",

		# mail addr of buildd admin handling packages from this distribution
		logs_mailed_to => $admin_mail,

		# schroot name (or alias) of the chrooted environment to use for
		# building (will be passed to sbuild). sbuild\'s default is
		# the first of $distribution-$arch-sbuild, $distribution-sbuild,
		# $distribution-$arch and $distribution.
		sbuild_chroot => undef,

	}
];'
		},
		'UPLOAD_QUEUES' => {
			TYPE           => 'ARRAY:HASH:SCALAR',
			VARNAME        => 'upload_queues',
			GROUP          => 'Uploader',
			DEFAULT        => [],
			IGNORE_DEFAULT => 1,                   # Don't dump class to config
			HELP           => 'Package upload queues',
			EXAMPLE        => '$upload_queues = [
	{
		# Local queue directory where binaries are stored before uploaded
		# by dupload.
		dupload_local_queue_dir => "upload",

		# Upload site for buildd-upload to pass to dupload(1); see
		# /etc/dupload.conf for possible values.
		dupload_archive_name => "debian-ftp",
	},

	{
		# Local queue directory where binaries are stored before uploaded
		# by dupload.
		dupload_local_queue_dir => "upload-security",

		# Upload site for buildd-upload to pass to dupload(1); see
		# /etc/dupload.conf for possible values.
		dupload_archive_name => "debian-ssh-security",
	}
];'
		});

	$conf->set_allowed_keys(@buildd_keys);
	Buildd::ClientConf::setup($conf);
}

sub read ($) {
	my $conf = shift;

	my $HOME = $conf->get('HOME');

	my $global      = $Sbuild::Sysconfig::paths{'BUILDD_CONF'};
	my $user        = "$HOME/.builddrc";
	my %config_time = ();
	my $user_time   = 0;

	my $reread = 0;

	sub ST_MTIME () { 9 }

	my @config_files = ($global, $user);

	$reread = 1 if $reread_config;

	foreach (@config_files) {
		if (-r $_) {
			$config_time{$_} = 0;
			my @stat = stat($_);
			if (!defined($conf->get('CONFIG_TIME')->{$_})
				|| $conf->get('CONFIG_TIME')->{$_} < $stat[ST_MTIME]) {
				$config_time{$_} = $stat[ST_MTIME];
				$reread = 1;
			}
		}
	}

	# For compatibility only.  Non-scalars are deprecated.
	my $deprecated_init = <<END;
# Variables are undefined, so config will default to DEFAULT if unset.
my \$defaults;
my \@distributions;
undef \@distributions;
my \@upload_queues;
undef \@upload_queues;

#legacy fields:
my \@weak_no_auto_build;
undef \@weak_no_auto_build;
my \$build_regex = undef; # Should this be user settable?
my \@no_auto_build;
undef \@no_auto_build;
my \$no_build_regex = undef;
my \@take_from_dists;
undef \@take_from_dists;
my \$sshcmd = undef;
my \$sshsocket = undef;
my \$wanna_build_user = undef;
my \$wanna_build_dbbase = undef;
END

	my $deprecated_setup = '';

	my $custom_setup = <<END;
if (\$sshcmd && \$sshcmd =~ /^\\s*(\\S+)\\s+(.+)/) {
    my \$rest = \$2;
    \$conf->set('SSH', \$1);

    #Try to pry the user out:
    if (\$rest =~ /(-l\\s*(\\S+))\\s+/) {
	\$wanna_build_ssh_user = \$2;
	#purge this from the rest:
	\$rest =~ s/\\Q\$1//;
    } elsif (\$rest =~ /\\s+(\\S+)\@/) {
	\$wanna_build_ssh_user = \$1;
	\$rest =~ s/\\Q\$1\\E\@//;
    }

    #Hope that the last argument is the host:
    if (\$rest =~ /\\s+(\\S+)\\s*\$/) {
	\$wanna_build_ssh_host = \$1;
	\$rest =~ s/\\Q\$1//;
    }

    #rest should be options:
    if (\$rest !~ /\\s*/) {
	\$wanna_build_ssh_options = [split \$rest];
    }
}

if (\$sshsocket) {
    \$wanna_build_ssh_socket = \$sshsocket;
}

if (\$wanna_build_user) {
    \$wanna_build_db_user = \$wanna_build_user;
}

if (\$wanna_build_dbbase) {
    \$wanna_build_db_name = \$wanna_build_dbbase;
}

#Convert old config, if needed:
my \@distributions_info;
if (\@take_from_dists) {
    for my \$dist (\@take_from_dists) {
	my \%entry;

	\$entry{DIST_NAME} = \$dist;
	\$entry{SSH} = \$ssh;

	if (\$dist =~ /security/) {
	    \$entry{DUPLOAD_LOCAL_QUEUE_DIR} = 'upload-security';
	}
	if (\$build_regex) {
	    \$entry{BUILD_REGEX} = \$build_regex;
	}
	if (\$no_build_regex) {
	    \$entry{NO_BUILD_REGEX} = \$build_regex;
	}
	if (\@no_auto_build) {
	    \$entry{NO_AUTO_BUILD} = \\\@no_auto_build;
	}
	if (\@weak_no_auto_build) {
	    \$entry{WEAK_NO_AUTO_BUILD} = \\\@weak_no_auto_build;
	}

	\$entry{WANNA_BUILD_DB_NAME} = \$wanna_build_db_name;
	\$entry{WANNA_BUILD_DB_USER} = \$wanna_build_db_user;
	\$entry{WANNA_BUILD_SSH_HOST} = \$wanna_build_ssh_host;
	\$entry{WANNA_BUILD_SSH_USER} = \$wanna_build_ssh_user;
	\$entry{WANNA_BUILD_SSH_SOCKET} = \$wanna_build_ssh_socket;
	\$entry{WANNA_BUILD_SSH_OPTIONS} = \$wanna_build_ssh_options;
                \$entry{WANNA_BUILD_API} = 0;

	my \$dist_config = Buildd::DistConf::new_hash(CHECK=>$conf->{'CHECK'},
						      HASH=>\\\%entry);

	push \@distributions_info, \$dist_config;
    }
} else {
    my \@dists = ();
    push \@dists, \@{\$distributions} if defined \$distributions;

    if (\@distributions) {
	warn 'W: \@distributions is deprecated; please use the array reference \$distributions[]\n';
	push \@dists, \@distributions;
    }

    for my \$raw_entry (\@dists) {
	my \%entry;
	my \@dist_names;

	#Find out for which distributions this entry is intended:
	for my \$key (keys \%\$raw_entry) {
	    if (uc(\$key) eq "DIST_NAME") {
		if (ref(\$raw_entry->{\$key}) eq "ARRAY") {
		    push \@dist_names, \@{\$raw_entry->{\$key}};
		} else {
		    push \@dist_names, \$raw_entry->{\$key};
		}
	    }
	}

	for my \$key (keys \%\$raw_entry) {
	    if (uc(\$key) ne "DIST_NAME") {
		\$entry{uc(\$key)} = \$raw_entry->{\$key};
	    }
	}

                for my \$key (keys \%\$defaults) {
                    if (uc(\$key) ne "DIST_NAME" && not defined \$entry{uc(\$key)}) {
                        \$entry{uc(\$key)} = \$defaults->{\$key};
                    }
                }

                \$entry{WANNA_BUILD_API} //= 1;


	#We need this to pass this to Buildd::Client:
                \$entry{SSH} = \$ssh;

	#Make one entry per distribution, it's easier later on:
	for my \$dist (\@dist_names) {
	    \$entry{'DIST_NAME'} = \$dist;
                    my \$dist_config = Buildd::DistConf::new_hash(HASH=>\\\%entry);
                    push \@distributions_info, \$dist_config;
	}
    }
}

\$conf->set('DISTRIBUTIONS', \\\@distributions_info);

my \@queues = ();
push \@queues, \@{\$upload_queues} if defined \$upload_queues;

if (\@upload_queues) {
    warn 'W: \@upload_queues is deprecated; please use the array reference \$upload_queues[]\n';
    push \@queues, \@upload_queues;
}

if (\@queues) {
    my \@upload_queue_configs;
    for my \$raw_entry (\@queues) {
	my \%entry;
	for my \$key (keys \%\$raw_entry) {
	    \$entry{uc(\$key)} = \$raw_entry->{\$key};
	}

	my \$queue_config = Buildd::UploadQueueConf::new_hash(CHECK=>$conf->{'CHECK'},
							      HASH=>\\\%entry);

	push \@upload_queue_configs, \$queue_config;
    }
    \$conf->set('UPLOAD_QUEUES', \\\@upload_queue_configs);
} else {
    push \@{\$conf->get('UPLOAD_QUEUES')},
	Buildd::UploadQueueConf::new_hash(CHECK=>$conf->{'CHECK'},
					  HASH=>
	    {
		DUPLOAD_LOCAL_QUEUE_DIR => 'upload',
		DUPLOAD_ARCHIVE_NAME    => 'anonymous-ftp-master'
	    }
	),
	Buildd::UploadQueueConf::new_hash(CHECK=>$conf->{'CHECK'},
					  HASH=>
	    {
		DUPLOAD_LOCAL_QUEUE_DIR => 'upload-security',
		DUPLOAD_ARCHIVE_NAME    => 'security'
	    }
	);
}

# Set here to allow user to override.
if (-t STDIN && -t STDOUT && \$conf->get('NO_DETACH')) {
    \$conf->_set_default('VERBOSE', 1);
} else {
    \$conf->_set_default('VERBOSE', 0);
}
END

	$conf->read(\@config_files, $deprecated_init, $deprecated_setup,
		$custom_setup);

	# Update times
	if ($reread) {
		foreach (@config_files) {
			if (-r $_) {
				$conf->get('CONFIG_TIME')->{$_} = $config_time{$_};
			}
		}
	}
}

1;
