#! /usr/bin/perl
#
# sbuild: build packages, obeying source dependencies
# Copyright (C) 1998-2000 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
#
# 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, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# $Id: sbuild,v 1.7 2005/03/31 16:42:48 mbanck Exp $
#

BEGIN {
        ($main::HOME = $ENV{'HOME'})
                or die "HOME not defined in environment!\n";
        push( @INC, "$main::HOME/lib" );
}

chomp( $main::HOSTNAME = `hostname` );

package conf;
$HOME = $main::HOME;
# defaults:
@dist_parts = qw(main contrib non-free);
$source_dependencies = "/etc/source-dependencies";
$mailprog = "/usr/sbin/sendmail";
$dpkg = "/usr/bin/dpkg";
$sudo = "/usr/bin/sudo";
$fakeroot = "/usr/bin/fakeroot";
$apt_get = "/usr/bin/apt-get";
$apt_cache = "/usr/bin/apt-cache";
$pgp_options = "-us -uc";
$log_dir = "$main::HOME/logs";
$mailto = "";
$mailfrom = "Source Builder <sbuild>";
$purge_build_directory = "successful";
$stalled_pkg_timeout = 90; # minutes
$srcdep_lock_wait = 1; # minutes
%individual_stalled_pkg_timeout = ();
# read conf files
require "/etc/sbuild.conf" if -r "/etc/sbuild.conf";
require "$HOME/.sbuildrc" if -r "$HOME/.sbuildrc";
# some checks
die "mailprog binary $conf::mailprog does not exist or isn't executable\n"
	if !-x $conf::mailprog;
die "sudo binary $conf::sudo does not exist or isn't executable\n"
	if !-x $conf::sudo;
die "apt-get binary $conf::apt_get does not exist or isn't executable\n"
	if !-x $conf::apt_get;
die "apt-cache binary $conf::apt_cache does not exist or isn't executable\n"
	if !-x $conf::apt_cache;
die "$conf::log_dir is not a directory\n" if ! -d $conf::log_dir;
die "$conf::srcdep_lock_dir is not a directory\n" if ! -d $conf::srcdep_lock_dir;
die "conf::mailto not set\n" if !$conf::mailto;
package main;

use strict;
use GDBM_File;
use POSIX;
use FileHandle;
use Cwd;

$ENV{'LC_ALL'} = "POSIX";

# avoid intermixing of stdout and stderr
$| = 1;
# in case the terminal disappears, the build should continue
$SIG{'HUP'} = 'IGNORE';

$main::distribution = "unstable";

%main::dist_order = ( 'oldstable' => 0, 'oldstable-security' => 0, stable => 1, 'stable-security' => 1, testing => 2, 'testing-security' => 2, unstable => 3 );

chomp( $main::arch = `dpkg --print-installation-architecture` );
$main::username = (getpwuid($<))[0] || $ENV{'LOGNAME'} || $ENV{'USER'};
$main::debug = 0;
$main::verbose = 0;
$main::batchmode = 0;
$main::auto_giveback = 0;
$main::nomail = 0;
$main::build_arch_all = 0;
$main::build_source = 0;
$main::jobs_file = "build-progress";
$main::max_lock_trys = 120;
$main::lock_interval = 5;
$main::cwd = cwd();
$main::ilock_file = "$conf::srcdep_lock_dir/install";
$main::srcdep_lock_cnt = 0;
$main::chroot_dir = "";
$main::chroot_build_dir = "";
$main::chroot_apt_options = "";
$main::override_distribution = 0;

$main::new_dpkg = 0;
check_dpkg_version();

while( @ARGV && $ARGV[0] =~ /^-/ ) {
	$_ = shift @ARGV;
	if (/^-v$/ || /^--verbose$/) {
		$main::verbose++;
	}
	elsif (/^-D$/ || /^--debug$/) {
		$main::debug++;
	}
	elsif (/^-b$/ || /^--batch$/) {
		$main::batchmode = 1;
	}
	elsif (/^-n$/ || /^--nolog$/) {
		$main::nolog = 1;
	}
	elsif (/^-A$/ || /^--arch-all$/) {
		$main::build_arch_all++;
	}
	elsif (/^-s$/ || /^--source$/) {
		$main::build_source++;
	}
	elsif (/^-d/ || /^--dist/) {
		if (/^-d(.)/ || /^--dist=(.)/) {
			$main::distribution = $1.$';
		}
		elsif (!@ARGV) {
			die "$_ option missing argument\n";
		}
		else {
			$main::distribution = shift @ARGV;
		}
		$main::distribution = "oldstable" if $main::distribution eq "o";
		$main::distribution = "stable"   if $main::distribution eq "s";
		$main::distribution = "testing"  if $main::distribution eq "t";
		$main::distribution = "unstable" if $main::distribution eq "u";
		$main::override_distribution = 1;
	}
	elsif (/^-p/ || /^--purge/) {
		if (/^-p(.)/ || /^--purge=(.)/) {
			$conf::purge_build_directory = $1.$';
		}
		elsif (!@ARGV) {
			die "$_ option missing argument\n";
		}
		else {
			$conf::purge_build_directory = shift @ARGV;
		}
		die "Bad purge mode\n"
			if !isin($conf::purge_build_directory, qw(always successful never));
	}
	elsif (/^-m/ || /^--maintainer/) {
		if (/^-m(.)/ || /^--maintainer=(.)/) {
			$conf::maintainer_name = $1.$';
		}
		elsif (!@ARGV) {
			die "$_ option missing argument\n";
		}
		else {
			$conf::maintainer_name = shift @ARGV;
		}
	}
	elsif (/^-k/ || /^--keyid/) {
		if (/^-k(.)/ || /^--keyid=(.)/) {
			$conf::key_id = $1.$';
		}
		elsif (!@ARGV) {
			die "$_ option missing argument\n";
		}
		else {
			$conf::key_id = shift @ARGV;
		}
	}
	elsif (/^-e/ || /^--uploader/) {
		if (/^-e(.)/ || /^--uploader=(.)/) {
			$conf::uploader_name = $1.$';
		}
		elsif (!@ARGV) {
			die "$_ option missing argument\n";
		}
		else {
			$conf::uploader_name = shift @ARGV;
		}
	}
	elsif (/^-f/ || /^--force-depends/) {
		if (/^-f(.)/ || /^--force-depends=(.)/) {
			push( @main::manual_srcdeps, "f".$1.$' );
		}
		elsif (!@ARGV) {
			die "$_ option missing argument\n";
		}
		else {
			push( @main::manual_srcdeps, "f".(shift @ARGV) );
		}
	}
	elsif (/^-a/ || /^--add-depends/) {
		if (/^-a(.)/ || /^--add-depends=(.)/) {
			push( @main::manual_srcdeps, "a".$1.$' );
		}
		elsif (!@ARGV) {
			die "$_ option missing argument\n";
		}
		else {
			push( @main::manual_srcdeps, "a".(shift @ARGV) );
		}
	}
	elsif (/^--auto-give-back(=(.*))?$/) {
		$main::auto_giveback = 1;
		if ($2) {
			my @parts = split( '@', $2 );
			$main::auto_giveback_wb_user = $parts[$#parts-2] if @parts > 2;
			$main::auto_giveback_user    = $parts[$#parts-1] if @parts > 1;
			$main::auto_giveback_host    = $parts[$#parts];
		}
	}
	elsif (/^--database=(.+)$/) {
		$main::database = $1;
	}
	elsif (/^--stats-dir=(.+)$/) {
		$main::stats_dir = $1;
	}
	elsif (/^--store-built-packages=(.+)$/) {
		$main::store_built_packages = $1;
	}
	elsif (/^--make-binNMU=(.+)$/) {
		$main::binNMU = $1;
	}
	else {
		die "Unknown option: $_\n";
	}
}

$conf::mailto = $conf::mailto{$main::distribution}
	if $conf::mailto{$main::distribution};

# see debsign for priorities, we will follow the same order
$main::dpkg_buildpackage_signopt="-m'".$conf::maintainer_name."'";
$main::dpkg_buildpackage_signopt="-e'".$conf::uploader_name."'" if defined $conf::uploader_name; 
$main::dpkg_buildpackage_signopt="-k'".$conf::key_id."'" if defined $conf::key_id; 
$conf::maintainer_name=$conf::uploader_name if defined $conf::uploader_name;
$conf::maintainer_name=$conf::key_id if defined $conf::key_id;

# variables for scripts:
open_log();
$SIG{'INT'} = \&shutdown;
$SIG{'TERM'} = \&shutdown;
$SIG{'ALRM'} = \&shutdown;
$SIG{'PIPE'} = \&shutdown;
read_deps( map { m,(?:.*/)?([^_/]+)[^/]*, } @ARGV );
if (-d "chroot-$main::distribution") {
	$main::chroot_dir = "chroot-$main::distribution";
	$main::chroot_build_dir = "$main::chroot_dir/build/$main::username/";
	$conf::srcdep_lock_dir = "$main::chroot_dir/$conf::srcdep_lock_dir";
	$main::ilock_file = "$conf::srcdep_lock_dir/install";
	my $absroot = "$main::cwd/$main::chroot_dir";
	$main::chroot_apt_options =
		"-o Dir::State=$absroot/var/".
			(-d "$absroot/var/lib/apt" ? "lib":"state")."/apt ".
		"-o Dir::State::status=$absroot/var/lib/dpkg/status ".
		"-o Dir::Cache=$absroot/var/cache/apt ".
		"-o Dir::Etc=$absroot/etc/apt ".
		"-o Dir::Etc::main=$absroot/etc/apt/apt.conf ".
		"-o Dir::Etc::parts=$absroot/etc/apt/apt.conf.d ".
		"-o DPkg::Options::=--root=$absroot ".
		"-o DPkg::Run-Directory=$absroot";
	$main::chroot_apt_op = '$CHROOT_OPTIONS';
} else {                                                                                                
	die "chroot-$main::distribution does not exist\n";
}
write_jobs_file();

my( $pkgv, $pkg );
foreach $pkgv (@ARGV) {
	my $urlbase;
	($urlbase, $pkgv) = ($1, $3) if $pkgv =~ m,^(\w+://(\S+/)?)([^/]+)$,;
	$pkgv =~ s/\.dsc$//;
	next if !open_pkg_log( $pkgv );
	(my $pkg = $pkgv) =~ s/_.*$//;
	$main::pkg_start_time = time;
	$main::pkg_status = "failed"; # assume for now
	$main::current_job = $main::binNMU_name || $pkgv;
	$main::additional_deps = [];
	write_jobs_file( "currently building" );
	if (should_skip( $pkgv )) {
		$main::pkg_status = "skipped";
		goto cleanup_close;
	}
	my $dscfile = $pkgv.".dsc";
	$main::pkg_fail_stage = "fetch-src";
	my @files_to_rm = fetch_source_files( \$dscfile );
	if (@files_to_rm && $files_to_rm[0] eq "ERROR") {
		shift @files_to_rm;
		goto cleanup_symlinks;
	}
		
	$main::pkg_fail_stage = "install-deps";
	if (!install_deps( $pkg )) {
		print PLOG "Source-dependencies not satisfied; skipping $pkg\n";
		goto cleanup_packages;
	}

	my $dscbase = basename( $dscfile );
	$main::pkg_status = "successful" if build( $dscbase, $pkgv );
	chdir( $main::cwd );
	write_jobs_file( $main::pkg_status );
	append_to_FINISHED( $main::current_job );

  cleanup_packages:
	undo_specials();
	uninstall_deps();
	remove_srcdep_lock_file();
  cleanup_symlinks:
	remove_files( @files_to_rm );
  cleanup_close:
	analyze_fail_stage( $pkgv );
	write_jobs_file( $main::pkg_status );
	close_pkg_log( $pkgv );
}
$main::current_job = "";
write_jobs_file();

close_log();
unlink( $main::jobs_file ) if $main::batchmode;
unlink( "SBUILD-FINISHED" ) if $main::batchmode;
exit 0;

sub fetch_source_files {
	my $dscfile_ref = shift;
	my $dscfile = $$dscfile_ref;
	my ($dir, $dscbase, $files, @other_files, $dscarchs, @made);
	my ($build_depends, $build_depends_indep, $build_conflicts,
		$build_conflicts_indep);
	local( *F );

	$dscfile =~ m,^(.*)/([^/]+)$,;
	($dir, $dscbase) = ($1, $2);
	my $urlbase;
	$urlbase = $1 if $dscfile =~ m,^(\w+://(\S+/)?)([^/]+)$,;
	(my $pkgv = $dscfile) =~ s,^(.*/)?([^/]+)\.dsc$,$2,;
	my ($pkg, $version) = split /_/, $pkgv;
	@main::have_dsc_build_deps = ();

	if (-d $dscfile) {
		if (-f "$dscfile/debian/.sbuild-build-deps") {
			open( F, "<$dscfile/debian/.sbuild-build-deps" );
			my $pkg;
			while( <F> ) {
				/^Package:\s*(.*)\s*$/i and $pkg = $1;
				/^Build-Depends:\s*(.*)\s*$/i and $build_depends = $1;
				/^Build-Depends-Indep:\s*(.*)\s*$/i and $build_depends_indep = $1;
				/^Build-Conflicts:\s*(.*)\s*$/i and $build_conflicts = $1;
				/^Build-Conflicts-Indep:\s*(.*)\s*$/i and $build_conflicts_indep = $1;
			}
			close( F );
			if ($build_depends || $build_depends_indep || $build_conflicts ||
				$build_conflicts_indep) {
				merge_pkg_build_deps( $pkg, $build_depends,
									  $build_depends_indep, $build_conflicts,
									  $build_conflicts_indep );
			}
		}
		return;
	}

	if ($dir ne ".") {
		{
			if (-f "${pkgv}.dsc") {
				print PLOG "${pkgv}.dsc exists in cwd\n";
			}
			else {
				my %entries;
				my $retried = 0;

			  retry:
				print PLOG "Checking available source versions...\n";
				if (!open( PIPE, "$conf::apt_cache $main::chroot_apt_options ".
						   "-q showsrc $pkg 2>&1 </dev/null |" )) {
					print PLOG "Can't open pipe to apt-cache: $!\n";
					return ("ERROR");
				}
				{ local($/) = "";
				  while( <PIPE> ) {
					  my $ver = $1 if /^Version:\s+(\S+)\s*$/mi;
					  my $tfile = $1 if /^Files:\s*\n((\s+.*\s*\n)+)/mi;
					  @{$entries{$ver}} = map { (split( /\s+/, $_ ))[3] }
						  split( "\n", $tfile );
				  }
			    }
				close( PIPE );
				if ($?) {
					print PLOG "$conf::apt_cache failed\n";
					return ("ERROR");
				}
				
				if (!defined($entries{$version})) {
					if (!$retried) {
						# try to update apt's cache if nothing found
						system "$conf::sudo $conf::apt_get ".
							   "$main::chroot_apt_options update >/dev/null";
						$retried = 1;
						goto retry;
					}
					print PLOG "Can't find source for $pkgv\n";
					print PLOG "(only different version(s) ",
							   join( ", ", sort keys %entries), " found)\n"
						if %entries;
					return( "ERROR" );
				}

				# XXX: Apt-get always fetches the latest version. We
				# need to make sure our version is the newest one and
				# hope that apt-get supports versioned fetches one
				# day.
				my $newest = $version;
				foreach my $entry (keys %entries) {
					$newest = $entry if version_cmp($entry, '>>', $newest);
				}
				if ($newest ne $version) {
					print PLOG "Version skew. Latest version is $newest, ".
							   "not $version.\n";
					return ("ERROR");
				}

				print PLOG "Fetching source files...\n";
				@made = @{$entries{$version}};
				if (!open( PIPE, "$conf::apt_get $main::chroot_apt_options ".
						   "--only-source -q -d source $pkg 2>&1 </dev/null |" )) {
					print PLOG "Can't open pipe to $conf::apt_get: $!\n";
					return ("ERROR", @made);
				}
				while( <PIPE> ) {
					print PLOG $_;
				}
				close( PIPE );
				if ($?) {
					print PLOG "$conf::apt_get for sources failed\n";
					return( "ERROR", @made );
				}
				$$dscfile_ref = $dscfile = (grep { /\.dsc$/ } @made)[0];
			}
		}
	}
	
	if (!open( F, "<$dscfile" )) {
		print PLOG "Can't open $dscfile: $!\n";
		return( "ERROR", @made );
	}
	my $dsctext;
	my $orig;
	{ local($/); $dsctext = <F>; }
	close( F );

	$dsctext =~ /^Build-Depends:\s*((.|\n\s+)*)\s*$/mi
		and $build_depends = $1;
	$dsctext =~ /^Build-Depends-Indep:\s*((.|\n\s+)*)\s*$/mi
		and $build_depends_indep = $1;
	$dsctext =~ /^Build-Conflicts:\s*((.|\n\s+)*)\s*$/mi
		and $build_conflicts = $1;
	$dsctext =~ /^Build-Conflicts-Indep:\s*((.|\n\s+)*)\s*$/mi
		and $build_conflicts_indep = $1;
	$build_depends =~ s/\n\s+/ /g if defined $build_depends;
	$build_depends_indep =~ s/\n\s+/ /g if defined $build_depends_indep;
	$build_conflicts =~ s/\n\s+/ /g if defined $build_conflicts;
	$build_conflicts_indep =~ s/\n\s+/ /g if defined $build_conflicts_indep;

	$dsctext =~ /^Architecture:\s*(.*)$/mi and $dscarchs = $1;

	$dsctext =~ /^Files:\s*\n((\s+.*\s*\n)+)/mi and $files = $1;
	@other_files = map { (split( /\s+/, $_ ))[3] } split( "\n", $files );
	$files =~ /(\Q$pkg\E.*orig.tar.gz)/mi and $orig = $1;

	if (!$dscarchs) {
		print PLOG "$dscbase has no Architecture: field -- skipping arch check!\n";
	}
	else {
		if ($dscarchs ne "any" && $dscarchs ne "all" && $dscarchs !~ /\b$main::arch\b/) {
			print PLOG "$dscbase: $main::arch not in arch list: $dscarchs -- ".
				 "skipping\n";
			$main::pkg_fail_stage = "arch-check";
			return( "ERROR", @made );
		}
	}
	print "Arch check ok ($main::arch included in $dscarchs)\n"
		if $main::debug;

	if ($build_depends || $build_depends_indep || $build_conflicts ||
		$build_conflicts_indep) {
		@main::have_dsc_build_deps = ($build_depends, $build_depends_indep,
									  $build_conflicts,$build_conflicts_indep);
		merge_pkg_build_deps( $pkg, $build_depends, $build_depends_indep,
							  $build_conflicts, $build_conflicts_indep );
	}

	if ($main::build_source) {
		@made = "";
		if ($orig && $main::chroot_build_dir) {
			system ("cp $orig $main::chroot_build_dir") 
				and print PLOG "ERROR: Could not copy $orig to $main::chroot_build_dir \n";
			push(@made, "$main::chroot_build_dir$orig");
		}
	}


	return @made;
}

sub build {
	my $dsc = shift;
	my $pkgv = shift;
	my( $dir, $rv, $changes );
	my $do_apply_patches = 1;
	local( *PIPE, *F, *F2 );

	fixup_pkgv( \$pkgv );
	print PLOG "-"x78, "\n";
	# count build time from now, ignoring the installation of source deps
	$main::pkg_start_time = time;
	$main::this_space = 0;
	$pkgv =~ /^([a-zA-Z\d.+-]+)_([a-zA-Z\d:.+-~]+)/;
	my ($pkg, $version) = ($1,$2);
	(my $sversion = $version) =~ s/^\d+://;
	my $tmpunpackdir = $dsc;
	$tmpunpackdir =~ s/-.*$/.orig.tmp-nest/;
	$tmpunpackdir =~ s/_/-/;
	$tmpunpackdir = "$main::chroot_build_dir$tmpunpackdir";
	
	if (-d "$main::chroot_build_dir$dsc" && -l "$main::chroot_build_dir$dsc") {
		# if the package dir already exists but is a symlink, complain
		print PLOG "Cannot unpack source: a symlink to a directory with the\n",
				   "same name already exists.\n";
		return 0;
	}
	if (! -d "$main::chroot_build_dir$dsc") {
		$main::pkg_fail_stage = "unpack";
		# dpkg-source refuses to remove the remanants of an
		# aborted dpkg-source extraction, so we will if necessary.
		if (-d $tmpunpackdir) {
		    system ("rm -fr $tmpunpackdir");
		}
		$main::sub_pid = open( PIPE, "-|" );
		if (!defined $main::sub_pid) {
			print PLOG "Can't spawn dpkg-source: $!\n";
			return 0;
		}
		if ($main::sub_pid == 0) {
			setpgrp( 0, $$ );
			if ($main::chroot_build_dir && !chdir( $main::chroot_build_dir )) {
				print PLOG "Couldn't cd to $main::chroot_build_dir: $!\n";
				system ("rm -fr $tmpunpackdir") if -d $tmpunpackdir;
				exit 1;
			}
			exec "dpkg-source -sn -x $main::cwd/$dsc 2>&1";
		}
		$main::sub_task = "dpkg-source";
		
		while( <PIPE> ) {
			print PLOG $_;
			$dir = $1 if /^dpkg-source: extracting \S+ in (\S+)/;
			$main::pkg_fail_stage = "unpack-check"
				if /^dpkg-source: error: file.*instead of expected/;
		}
		close( PIPE );
		undef $main::sub_pid;
		if ($?) {
			print PLOG "FAILED [dpkg-source died]\n";
		
		    system ("rm -fr $tmpunpackdir") if -d $tmpunpackdir;
			return 0;
		}
		if (!$dir) {
			print PLOG "Couldn't find directory of $dsc in dpkg-source output\n";
		    system ("rm -fr $tmpunpackdir") if -d $tmpunpackdir;
			return 0;
		}
		$dir = "$main::chroot_build_dir$dir";

		if (system( "chmod -R g-s,go+rX $dir" ) != 0) {
			print PLOG "chmod -R g-s,go+rX $dir failed.\n";
			return 0;
		}
		
		if (@main::have_dsc_build_deps && !defined $main::build_source) {
			my ($d, $di, $c, $ci) = @main::have_dsc_build_deps;
			open( F, ">$dir/debian/.sbuild-build-deps" );
			print F "Package: $pkg\n";
			print F "Build-Depends: $d\n" if $d;
			print F "Build-Depends-Indep: $di\n" if $di;
			print F "Build-Conflicts: $c\n" if $c;
			print F "Build-Conflicts-Indep: $ci\n" if $ci;
			close( F );
		}
	}
	else {
		$dir = "$main::chroot_build_dir$dsc";
		$do_apply_patches = 0;

		$main::pkg_fail_stage = "check-unpacked-version";
		# check if the unpacked tree is really the version we need
		$main::sub_pid = open( PIPE, "-|" );
		if (!defined $main::sub_pid) {
			print PLOG "Can't spawn dpkg-parsechangelog: $!\n";
			return 0;
		}
		if ($main::sub_pid == 0) {
			setpgrp( 0, $$ );
			chdir( $dir );
			exec "dpkg-parsechangelog 2>&1";
		}
		$main::sub_task = "dpkg-parsechangelog";

		my $clog = "";
		while( <PIPE> ) {
			$clog .= $_;
		}
		close( PIPE );
		undef $main::sub_pid;
		if ($?) {
			print PLOG "FAILED [dpkg-parsechangelog died]\n";
			return 0;
		}
		if ($clog !~ /^Version:\s*(.+)\s*$/mi) {
			print PLOG "dpkg-parsechangelog didn't print Version:\n";
			return 0;
		}
		my $tree_version = $1;
		my $cmp_version = ($main::binNMU && -f "$dir/debian/.sbuild-binNMU-done") ?
			binNMU_version($version) : $version;
		if ($tree_version ne $cmp_version) {
			print PLOG "The unpacked source tree $dir is version ".
					   "$tree_version, not wanted $cmp_version!\n";
			return 0;
		}
	}

	if (!chdir( $dir )) {
		print PLOG "Couldn't cd to $dir: $!\n";
		system ("rm -fr $tmpunpackdir") if -d $tmpunpackdir;
		return 0;
	}

	$main::pkg_fail_stage = "check-space";
	my $current_usage = `/usr/bin/du -s .`;
	$current_usage =~ /^(\d+)/;
	$current_usage = $1;
	if ($current_usage) {
		my $free = df( "." );
		if ($free < 2*$current_usage) {
			print PLOG "Disk space is propably not enough for building.\n".
					   "(Source needs $current_usage KB, free are $free KB.)\n";
			print PLOG "Purging $dir\n";
			chdir( $main::cwd );
			system "$conf::sudo rm -rf $dir";
			return 0;
		}
	}

	$main::pkg_fail_stage = "hack-binNMU";
	if ($main::binNMU && ! -f "debian/.sbuild-binNMU-done") {
		if (open( F, "<debian/changelog" )) {
			my($firstline, $text);
			$firstline = <F> while $firstline =~ /^$/;
			{ local($/); undef $/; $text = <F>; }
			close( F );
			$firstline =~ /^(\S+)\s+\((\S+)\)\s+([^;]+)\s*;\s*urgency=(\S+)\s*$/;
			my ($name, $version, $dists, $urgent) = ($1, $2, $3, $4);
			my $NMUversion = binNMU_version($version);
			chomp( my $date = `822-date` );
			if (!open( F, ">debian/changelog" )) {
				print PLOG "Can't open debian/changelog for binNMU hack: $!\n";
				chdir( $main::cwd );
				return 0;
			}
			$dists = $main::distribution;
			print F "$name ($NMUversion) $dists; urgency=low\n\n";
			print F "  * Binary-only non-maintainer upload for $main::arch; ",
					"no source changes.\n";
			print F "  * ", join( "    ", split( "\n", $main::binNMU )), "\n\n";
			print F " -- $conf::maintainer_name  $date\n\n";

			print F $firstline, $text;
			close( F );
			system "touch debian/.sbuild-binNMU-done";
			print PLOG "*** Created changelog entry for bin-NMU version $NMUversion\n";
		}
		else {
			print PLOG "Can't open debian/changelog -- no binNMU hack!\n";
		}
	}
	
	if ($do_apply_patches) {
		if (!apply_patches( $pkg )) {
			chdir( $main::cwd );
			return 0;
		}
	}
	
	if (-f "debian/files") {
		local( *FILES );
		my @lines;
		open( FILES, "<debian/files" );
		chomp( @lines = <FILES> );
		close( FILES );
		@lines = map { my $ind = 68-length($_);
					   $ind = 0 if $ind < 0;
					   "| $_".(" " x $ind)." |\n"; } @lines;
		
		print PLOG <<"EOF";

+----------------------------------------------------------------------+
| sbuild Warning:                                                      |
| ---------------                                                      |
| After unpacking, there exists a file debian/files with the contents: |
|                                                                      |
EOF
		print PLOG @lines;
		print PLOG <<"EOF";
|                                                                      |
| This should be reported as a bug.                                    |
| The file has been removed to avoid dpkg-genchanges errors.           |
+----------------------------------------------------------------------+

EOF
		unlink "debian/files";
	}

	$main::build_start_time = time;
	$main::pkg_fail_stage = "build";
	$main::sub_pid = open( PIPE, "-|" );
	if (!defined $main::sub_pid) {
		print PLOG "Can't spawn dpkg-buildpackage: $!\n";
		chdir( $main::cwd );
		return 0;
	}
	if ($main::sub_pid == 0) {
		setpgrp( 0, $$ );
		my $binopt = $main::build_source ? "" : $main::build_arch_all ? "-b" : "-B";
		if ($main::chroot_dir) {
			my $bdir = $dir;
			$bdir =~ s/^\Q$main::chroot_dir\E//;
			if (-f "$main::chroot_dir/etc/ld.so.conf" &&
			    ! -r "$main::chroot_dir/etc/ld.so.conf") {
				system "$conf::sudo chmod a+r $main::chroot_dir/etc/ld.so.conf";
				print PLOG "ld.so.conf was not readable! Fixed.\n";
			}
			print PLOG "$conf::sudo /usr/sbin/chroot $main::cwd/$main::chroot_diri ".
                                 "$conf::sudo -u $main::username -H /bin/sh -c cd $bdir && ".
				 "exec dpkg-buildpackage $conf::pgp_options ".
                                 "$binopt $main::dpkg_buildpackage_signopt -r$conf::fakeroot 2>&1\n" if $main::debug;

			exec "$conf::sudo", "/usr/sbin/chroot", "$main::cwd/$main::chroot_dir",
				 "$conf::sudo", "-u", $main::username, "-H", "/bin/sh", "-c",
				 "cd $bdir && exec dpkg-buildpackage $conf::pgp_options ".
				 "$binopt $main::dpkg_buildpackage_signopt -r$conf::fakeroot 2>&1";
		}
		else {
			if (-f "/etc/ld.so.conf" && ! -r "/etc/ld.so.conf") {
				system "$conf::sudo chmod a+r /etc/ld.so.conf";
				print PLOG "ld.so.conf was not readable! Fixed.\n";
			}
			print PLOG "dpkg-buildpackage $conf::pgp_options $binopt ".
                                "$main::dpkg_buildpackage_signopt -r$conf::fakeroot 2>&1\n" if $main::debug;

			exec "dpkg-buildpackage $conf::pgp_options $binopt ".
				"$main::dpkg_buildpackage_signopt -r$conf::fakeroot 2>&1";
		}
	}
	$main::sub_task = "dpkg-buildpackage";

	# We must send the signal as root, because some subprocesses of
	# dpkg-buildpackage could run as root. So we have to use a shell
	# command to send the signal... but /bin/kill can't send to
	# process groups :-( So start another Perl :-)
	my $timeout = $conf::individual_stalled_pkg_timeout{$pkg} ||
				  $conf::stalled_pkg_timeout;
	$timeout *= 60;
	my $timed_out = 0;
	my(@timeout_times, @timeout_sigs, $last_time);
	$SIG{'ALRM'} = sub {
		my $signal = ($timed_out > 0) ? 9 : 15;
		system "$conf::sudo perl -e 'kill( -$signal, $main::sub_pid )'";
		$timeout_times[$timed_out] = time - $last_time;
		$timeout_sigs[$timed_out] = $signal;
		$timed_out++;
		$timeout = 5*60; # only wait 5 minutes until next signal
	};

	alarm( $timeout );
	while( <PIPE> ) {
		alarm( $timeout );
		$last_time = time;
		print PLOG $_;
	}
	close( PIPE );
	undef $main::sub_pid;
	alarm( 0 );
	$rv = $?;

	my $i;
	for( $i = 0; $i < $timed_out; ++$i ) {
		print PLOG "Build killed with signal ", $timeout_sigs[$i],
				   " after ", int($timeout_times[$i]/60),
				   " minutes of inactivity\n";
	}
	$main::pkg_end_time = time;
	my $date = `date +%Y%m%d-%H%M`;
	print PLOG "*"x78, "\n";
	print PLOG "Build finished at $date";
	chdir( $main::cwd );

	my @space_files = ("$dir");
	my @checkinst_files;
	if ($rv) {
		print PLOG "FAILED [dpkg-buildpackage died]\n";
	}
	else {
		if (-r "$dir/debian/files") {
			my @debs;
			my @files;
			open( F, "<$dir/debian/files" );
			while( <F> ) {
				my $f = (split( /\s+/, $_ ))[0];
				push( @files, "$main::chroot_build_dir$f" );
				next if $f !~ /$main::arch\.[\w\d.-]*$/;
				push( @debs, "$main::chroot_build_dir$f" );
				push( @space_files, $f );
				push( @checkinst_files, $f ) if $main::store_built_packages;
			}
			close( F );
			my @debs2 = @debs;
			foreach (@debs) {
				print PLOG "\n$_:\n";
				if (!open( PIPE, "dpkg --info $_ 2>&1 |" )) {
					print PLOG "Can't spawn dpkg: $! -- can't dump infos\n";
				}
				else {
					print PLOG $_ while( <PIPE> );
					close( PIPE );
				}
			}
			foreach (@debs2) {
				print PLOG "\n$_:\n";
				if (!open( PIPE, "dpkg --contents $_ 2>&1 |" )) {
					print PLOG "Can't spawn dpkg: $! -- can't dump infos\n";
				}
				else {
					print PLOG $_ while( <PIPE> );
					close( PIPE );
				}
			}
		}

		$changes = "${pkg}_".
			($main::binNMU ? binNMU_version($sversion) : $sversion).
			"_$main::arch.changes";
		my @files;
		if (-r "$main::chroot_build_dir$changes") {
			my(@do_dists, @saved_dists);
			print PLOG "\n$changes:\n";
			open( F, "<$main::chroot_build_dir$changes" );
			if (open( F2, ">$changes.new" )) {
				while( <F> ) {
					if (/^Distribution:\s*(.*)\s*$/ and $main::override_distribution) {
						print PLOG "Distribution: $main::distribution\n";
						print F2 "Distribution: $main::distribution\n";
					}
					else {
						print PLOG $_;
						print F2 $_;
						if (/^ [a-z0-9]{32}/) {
							push(@files, (split( /\s+/, $_ ))[5] );
						}
					}
				}
				close( F2 );
				rename( "$changes.new", "$changes" )
					or print PLOG "$changes.new could not be renamed ".
								  "to $changes: $!\n";
				unlink( "$main::chroot_build_dir$changes" )
					if $main::chroot_build_dir;
			}
			else {
				print PLOG "Cannot create $changes.new: $!\n";
				print PLOG "Distribution field may be wrong!!!\n";
				if ($main::chroot_build_dir) {
					system "mv", "$main::chroot_build_dir$changes", "."
						and print PLOG "ERROR: Could not move $_ to .\n";
				}
			}
			close( F );
			print PLOG "\n";
		}
		else {
			print PLOG "Can't find $changes -- can't dump infos\n";
		}
		foreach (@files) {
			if ($main::chroot_build_dir) {
				system "mv", "$main::chroot_build_dir$_", "."
					and print PLOG "ERROR: Could not move $_ to .\n";
			}
		}

		print PLOG "*"x78, "\n";
		print PLOG "Built successfully\n";
	}

	check_watches();
	check_space( @space_files );

	if ($conf::purge_build_directory eq "always" ||
		($conf::purge_build_directory eq "successful" && $rv == 0)) {
		print PLOG "Purging $dir\n";
		system "$conf::sudo rm -rf $dir";
	}
	check_inst_packages( @checkinst_files )
		if $rv == 0 && $main::store_built_packages;
	
	print PLOG "-"x78, "\n";
	return $rv == 0 ? 1 : 0;
}

sub apply_patches {
	my $pkg = shift;
	my $name;
	
	$main::pkg_fail_stage = "apply-patch";
	foreach $name ((map { $_->{'Package'} } @{$main::deps{$pkg}}),
				   @main::global_patches) {
		if ($name =~ /^\*/ && exists $main::specials{$name}->{'patch'}) {
			if (exists $main::specials{$name}->{'patchcond'}) {
				print "Testing condition for $name patch:\n"
					if $main::debug;
				if (run_script("+e",$main::specials{$name}->{'patchcond'})!=0){
					print PLOG "Condition for $name patch not true -- ",
								"not applying\n" if $name !~ /^\*\*/;
					next;
				}
				print PLOG "Condition for $name patch ok\n";
			}
			print PLOG "Applying $name patch\n";
			$main::sub_pid = open( PIPE, "|-" );
			if (!defined $main::sub_pid) {
				print PLOG "Can't spawn patch: $! -- can't patch\n";
				return 0;
			}
			if ($main::sub_pid == 0) {
				setpgrp( 0, $$ );
				open( STDOUT, ">&PLOG" );
				open( STDERR, ">&PLOG" );
				exec "patch --batch --quiet -p1 -E -N --no-backup-if-mismatch";
			}
			$main::sub_task = "patch";

			print PIPE $main::specials{$name}->{'patch'};
			close( PIPE );
			undef $main::sub_pid;
			if ($name !~ /^\*\*/ && $?) {
				print PLOG "FAILED [patch died]\n";
				return 0;
			}
		}
	}
	return 1;
}

sub analyze_fail_stage {
	my $pkgv = shift;
	
	return if $main::pkg_status ne "failed";
	return if !$main::auto_giveback;
	if (isin( $main::pkg_fail_stage,
			  qw(find-dsc fetch-src unpack-check check-space))) {
		$main::pkg_status = "given-back";
		print PLOG "Giving back package $pkgv after failure in ".
			       "$main::pkg_fail_stage stage.\n";
		my $cmd = "";
		$cmd = "ssh -l$main::auto_giveback_user $main::auto_giveback_host "
			if $main::auto_giveback_host;
		$cmd .= "wanna-build --give-back --no-down-propagation ".
			    "--dist=$main::distribution";
		$cmd .= " --database=$main::database" if $main::database;
		$cmd .= " --user=$main::auto_giveback_wb_user "
			if $main::auto_giveback_wb_user;
		$cmd .= " $pkgv";
		system $cmd;
		if ($?) {
			print PLOG "wanna-build failed with status $?\n";
		}
		else {
			add_givenback( $pkgv, time );
			if ($main::stats_dir) {
				local( *F );
				lock_file( "$main::stats_dir" );
				open( F, ">>$main::stats_dir/give-back" );
				print F "1\n";
				close( F );
				unlock_file( "$main::stats_dir" );
			}
		}
	}
}

sub remove_files {

	foreach (@_) {
		unlink $_;
		print "Removed $_\n" if $main::debug;
	}
}


sub install_deps {
	my $pkg = shift;
	my( @positive, @negative, @special, @instd, @rmvd );

	if (!exists $main::deps{$pkg}) {
		prepare_watches( [] );
		return 1;
	}
	
	my $dep = $main::deps{$pkg};
	if ($main::debug) {
		print "Source dependencies of $pkg: ", format_deps(@$dep), "\n";
	}

  repeat:
	lock_file( "$main::ilock_file", 1 );
	
	print "Filtering dependencies\n" if $main::debug;
	if (!filter_dependencies( $dep, \@positive, \@negative, \@special )) {
		print PLOG "Package installation not possible\n";
		unlock_file( "$main::ilock_file" );
		return 0;
	}

	print PLOG "Checking for source dependency conflicts...\n";
	if (!run_apt( "-s", \@instd, \@rmvd, @positive )) {
		print PLOG "Test what should be installed failed.\n";
		unlock_file( "$main::ilock_file" );
		return 0;
	}
	# add negative deps as to be removed for checking srcdep conflicts
	push( @rmvd, @negative );
	my @confl;
	if (@confl = check_srcdep_conflicts( \@instd, \@rmvd, \@special )) {
		print PLOG "Waiting for job(s) @confl to finish\n";

		unlock_file( "$main::ilock_file" );
		wait_for_srcdep_conflicts( @confl );
		goto repeat;
	}
	
	write_srcdep_lock_file( $dep, \@special );
	
	foreach my $sp (@special) {
		next if $sp !~ /^\*/ || !exists $main::specials{$sp}->{'prepre'};
		print PLOG "Running prepre script for $sp\n";
		if (run_script( "-e", $main::specials{$sp}->{'prepre'} ) != 0) {
			print PLOG "prepre script of special dependency $sp failed\n";
			unlock_file( "$main::ilock_file" );
			return 0;
		}
	}

	print "Installing positive dependencies: @positive\n" if $main::debug;
	if (!run_apt( "-y", \@instd, \@rmvd, @positive )) {
		print PLOG "Package installation failed\n";
		# try to reinstall removed packages
		print PLOG "Trying to reinstall removed packages:\n";
		print "Reinstalling removed packages: @rmvd\n" if $main::debug;
		my (@instd2, @rmvd2);
		print PLOG "Failed to reinstall removed packages!\n"
			if !run_apt( "-y", \@instd2, \@rmvd2, @rmvd );
		print "Installed were: @instd2\n" if $main::debug;
		print "Removed were: @rmvd2\n" if $main::debug;
		# remove additional packages
		print PLOG "Trying to uninstall newly installed packages:\n";
		uninstall_debs( "remove", @instd );
		unlock_file( "$main::ilock_file" );
		return 0;
	}
	set_installed( @instd );
	set_removed( @rmvd );
	
	print "Removing negative dependencies: @negative\n" if $main::debug;
	if (!uninstall_debs( "remove", @negative )) {
		print PLOG "Removal of packages failed\n";
		unlock_file( "$main::ilock_file" );
		return 0;
	}
	set_removed( @negative );
	
	my $fail = check_dependencies( $dep );
	if ($fail) {
		print PLOG "After installing, the following source dependencies are ".
			 "still unsatisfied:\n$fail\n";
		unlock_file( "$main::ilock_file" );
		return 0;
	}

	foreach my $sp (@special) {
		next if $sp !~ /^\*/ ||
			    (!exists $main::specials{$sp}->{'pre'} &&
			     !exists $main::specials{$sp}->{'post'} &&
			     !exists $main::specials{$sp}->{'unpack'});
		if (exists $main::specials{$sp}->{'unpack'}) {
			my $s = $main::specials{$sp}->{'unpack'};
			$s =~ s/^\s+//mg;
			$s =~ s/\s+$//mg;
			my @s = split( /\s+/, $s );
			my @rem;
			print PLOG "Unpacking special sources $sp: @s\n";
			if (!(@rem = unpack_special_source( @s ))) {
				print PLOG "unpacking of special dependency sources for $sp failed\n";
				unlock_file( "$main::ilock_file" );
				return 0;
			}
			$main::changes->{'unpacked'}->{$sp} = \@rem;
		}
		if (exists $main::specials{$sp}->{'pre'}) {
			print PLOG "Running pre script for $sp\n";
			$main::changes->{'specials'}->{$sp} = 1;
			if (run_script( "-e", $main::specials{$sp}->{'pre'} ) != 0) {
				print PLOG "pre script of special dependency $sp failed\n";
				unlock_file( "$main::ilock_file" );
				return 0;
			}
		}
	}

	unlock_file( "$main::ilock_file" );

	prepare_watches( $dep, @instd );
	return 1;
}

sub unpack_special_source {
	my @s = @_;
	my (@files, @dirs);
	local (*PIPE);

	foreach my $s (@s) {
		my $dsc;

		{
			if (!open( PIPE, "$conf::apt_get $main::chroot_apt_options ".
                       "--only-source -q -d source $s 2>&1 </dev/null |" )) {
				print PLOG "Can't open pipe to apt-get: $!\n";
				goto failed;
			}
			while( <PIPE> ) {
				$dsc = "$1_$2.dsc" if /(\S+) (?:[^:]+:)?(\S+) \(dsc\)/;
				print PLOG $_;
			}
			close( PIPE );
			if ($?) {
				print PLOG "Apt-get of special unpack sources failed\n";
				goto failed;
			}
			push( @files, $dsc );
			if (!open( F, "<$dsc" )) {
				print PLOG "Can't open $dsc: $!\n";
				goto failed;
			}
			my $dsctext;
			{ local($/); $dsctext = <F>; }
			close( F );
			my $files;
			$dsctext =~ /^Files:\s*\n((\s+.*\s*\n)+)/mi and $files = $1;
			push(@files, map { (split( /\s+/, $_ ))[3] } split( "\n", $files ));
		}

		my $pid = open( PIPE, "-|" );
		if (!defined $pid) {
			print PLOG "Can't spawn dpkg-source: $! -- special unpack failed\n";
			goto failed;
		}
		if ($pid == 0) {
			setpgrp( 0, $$ );
			if ($main::chroot_build_dir && !chdir( $main::chroot_build_dir )) {
				print PLOG "Couldn't cd to $main::chroot_build_dir: $! -- special unpack failed\n";
				exit 1;
			}
			exec "dpkg-source -sn -x $main::cwd/$dsc 2>&1";
		}
		my $dir;
		while( <PIPE> ) {
			print PLOG $_;
			$dir = $1 if /^dpkg-source: extracting \S+ in (\S+)/;
		}
		close( PIPE );
		if ($?) {
			print PLOG "dpkg-source failure -- special unpack failed\n";
			goto failed;
		}
		push( @dirs, "$main::chroot_build_dir$dir" );
		unlink( @files );
	}

	return @dirs;
	
  failed:
	unlink( @files );
	system( "rm", "-rf", @dirs );
	return ();
}
	
sub wait_for_srcdep_conflicts {
	my @confl = @_;
	
	for(;;) {
		sleep( $conf::srcdep_lock_wait*60 );
		my $allgone = 1;
		for (@confl) {
			/^(\d+)-(\d+)$/;
			my $pid = $1;
			if (-f "$conf::srcdep_lock_dir/$_") {
				if (kill( 0, $pid ) == 0 && $! == ESRCH) {
					print PLOG "Ignoring stale src-dep lock $_\n";
					unlink( "$conf::srcdep_lock_dir/$_" ) or
						print PLOG "Cannot remove $conf::srcdep_lock_dir/$_: $!\n";
				}
				else {
					$allgone = 0;
					last;
				}
			}
		}
		last if $allgone;
	}
}

sub uninstall_deps {
	my( @pkgs, @instd, @rmvd );

	lock_file( "$main::ilock_file", 1 );

	@pkgs = keys %{$main::changes->{'removed'}};
	print "Reinstalling removed packages: @pkgs\n" if $main::debug;
	print PLOG "Failed to reinstall removed packages!\n"
		if !run_apt( "-y", \@instd, \@rmvd, @pkgs );
	print "Installed were: @instd\n" if $main::debug;
	print "Removed were: @rmvd\n" if $main::debug;
	unset_removed( @instd );
	unset_installed( @rmvd );

	@pkgs = keys %{$main::changes->{'installed'}};
	print "Removing installed packages: @pkgs\n" if $main::debug;
	print PLOG "Failed to remove installed packages!\n"
		if !uninstall_debs( "purge", @pkgs );
	unset_installed( @pkgs );

	unlock_file( "$main::ilock_file" );
}

sub uninstall_debs {
	my $mode = shift;
	my $output;
	
	return 1 if !@_;
	print "Uninstalling packages: @_\n" if $main::debug;
	my $chroot_opt =
		$main::chroot_dir ? "--root=$main::cwd/$main::chroot_dir" : "";
	print PLOG "  $conf::sudo dpkg $chroot_opt --$mode @_\n";
  repeat:
	$output = `cd $main::chroot_dir/ && $conf::sudo $conf::dpkg $chroot_opt --$mode @_ 2>&1 </dev/null`;
	print PLOG $output;
	if ($output =~ /status database area is locked/mi) {
		print PLOG "Another dpkg is running -- retrying later\n";
		sleep( 2*60 );
		goto repeat;
	}
	print PLOG "dpkg run to remove packages (@_) failed!\n" if $?;
	return $? == 0;
}

sub undo_specials {
	my $sp;

	print "Running post scripts of special dependencies:\n" if $main::debug;
	foreach $sp (keys %{$main::changes->{'specials'}}) {
		print PLOG "Running post script for $sp\n";
		if (run_script( "-e", $main::specials{$sp}->{'post'} ) != 0) {
			print PLOG "post script of special dependency $sp failed\n";
		}
		delete $main::changes->{'specials'}->{$sp};
	}
	foreach $sp (keys %{$main::changes->{'unpacked'}}) {
		my @dirs = @{$main::changes->{'unpacked'}->{$sp}};
		print PLOG "Removing special unpacked sources for $sp: @dirs\n";
		system "rm", "-rf", @dirs;
		delete $main::changes->{'unpacked'}->{$sp};
	}
}


sub run_apt {
	my $mode = shift;
	my $inst_ret = shift;
	my $rem_ret = shift;
	my @to_install = @_;
	my( $msgs, $status, $pkgs, $rpkgs );
	local (*PIPE);
	local (%ENV) = %ENV; # make local environment
	# hardwire frontent for debconf to non-interactive
	$ENV{'DEBIAN_FRONTEND'} = "noninteractive";

	@$inst_ret = ();
	@$rem_ret = ();
	return 1 if !@to_install;
  repeat:
	print PLOG "  $conf::sudo $conf::apt_get --purge $main::chroot_apt_op -q $mode install @to_install\n"
		if $mode ne "-s";
	$msgs = "";
	# redirection of stdin from /dev/null so that conffile question are
	# treated as if RETURN was pressed.
	# dpkg since 1.4.1.18 issues an error on the conffile question if it reads
	# EOF -- hardwire the new --force-confold option to avoid the questions.
	if (!open( PIPE, "$conf::sudo $conf::apt_get --purge $main::chroot_apt_options ".
			   ($main::new_dpkg ? "-o DPkg::Options::=--force-confold " : "").
			   "-q $mode install @to_install 2>&1 </dev/null |" )) {
		print PLOG "Can't open pipe to apt-get: $!\n";
		return 0;
	}
	while( <PIPE> ) {
		$msgs .= $_;
		print PLOG $_ if $mode ne "-s" || $main::debug;
	}
	close( PIPE );
	$status = $?;

	if ($status != 0 && $msgs =~ /^E: Packages file \S+ (has changed|is out of sync)/mi) {
		print PLOG "$conf::sudo $conf::apt_get $main::chroot_apt_op -q update\n";
		if (!open( PIPE, "$conf::sudo $conf::apt_get $main::chroot_apt_options -q update 2>&1 |" )) {
			print PLOG "Can't open pipe to apt-get: $!\n";
			return 0;
		}
		$msgs = "";
		while( <PIPE> ) {
			$msgs .= $_;
			print PLOG $_;
		}
		close( PIPE );
		print PLOG "apt-get update failed\n" if $?;
		$msgs = "";
		goto repeat;
	}

	if ($status != 0 && $msgs =~ /^Package (\S+) is a virtual package provided by:\n((^\s.*\n)*)/mi) {
		my $to_replace = $1;
		my @providers;
		foreach (split( "\n", $2 )) {
			s/^\s*//;
			push( @providers, (split( /\s+/, $_ ))[0] );
		}
		print PLOG "$to_replace is a virtual package provided by: @providers\n";
		my $selected;
		if (@providers == 1) {
			$selected = $providers[0];
			print PLOG "Using $selected (only possibility)\n";
		}
		elsif (exists $conf::alternatives{$to_replace}) {
			$selected = $conf::alternatives{$to_replace};
			print PLOG "Using $selected (selected in sbuildrc)\n";
		}
		else {
			$selected = $providers[0];
			print PLOG "Using $selected (no default, using first one)\n";
		}
		
		@to_install = grep { $_ ne $to_replace } @to_install;
		push( @to_install, $selected );
		
		goto repeat;
	}
	
	if ($status != 0 && ($msgs =~ /^E: Could( not get lock|n.t lock)/mi ||
						 $msgs =~ /^dpkg: status database area is locked/mi)) {
		print PLOG "Another apt-get or dpkg is running -- retrying later\n";
		sleep( 2*60 );
		goto repeat;
	}
	
	$pkgs = $rpkgs = "";
	if ($msgs =~ /NEW packages will be installed:\n((^[ 	].*\n)*)/mi) {
		($pkgs = $1) =~ s/^[ 	]*((.|\n)*)\s*$/$1/m;
	}
	if ($msgs =~ /packages will be REMOVED:\n((^[ 	].*\n)*)/mi) {
		($rpkgs = $1) =~ s/^[ 	]*((.|\n)*)\s*$/$1/m;
	}
	@$inst_ret = split( /\s+/, $pkgs );
	@$rem_ret = split( /\s+/, $rpkgs );

	print PLOG "apt-get failed.\n" if $status && $mode ne "-s";
	return $mode eq "-s" || $status == 0;
}

sub filter_dependencies {
	my $dependencies = shift;
	my $pos_list = shift;
	my $neg_list = shift;
	my $special_list = shift;
	my($dep, $d, $name, %names);

	print PLOG "Checking for already installed source dependencies...\n";
	
	@$pos_list = @$neg_list = @$special_list = ();
	foreach $d (@$dependencies) {
		my $name = $d->{'Package'};
		$names{$name} = 1 if $name !~ /^\*/;
		foreach (@{$d->{'Alternatives'}}) {
			my $name = $_->{'Package'};
			$names{$name} = 1 if $name !~ /^\*/;
		}
	}
	my $status = get_dpkg_status( keys %names );

	foreach $dep (@$dependencies) {
		$name = $dep->{'Package'};
		next if !$name;
		if ($name =~ /^\*/) {
			my $doit = 1;
			if (exists $main::specials{$name}->{'condition'}) {
				print "Testing condition for special dependency $name:\n"
					if $main::debug;
				if (run_script("+e",$main::specials{$name}->{'condition'})!=0){
					print "Condition false -> not running scripts\n"
						if $main::debug;
					$doit = 0;
				}
			}
			push( @$special_list, $name ) if $doit;
			next;
		}
		my $stat = $status->{$name};
		if ($dep->{'Neg'}) {
			if ($stat->{'Installed'}) {
				my ($rel, $vers) = ($dep->{'Rel'}, $dep->{'Version'});
				my $ivers = $stat->{'Version'};
				if (!$rel || version_cmp( $ivers, $rel, $vers )){
					print "$name: neg dep, installed, not versioned or ",
						  "version relation satisfied --> remove\n" if $main::debug;
					print PLOG "$name: installed (negative dependency)";
					print PLOG " (bad version $ivers $rel $vers)"
						if $rel;
					print PLOG "\n";
					push( @$neg_list, $name );
				}
				else {
					print PLOG "$name: installed (negative dependency)",
							   "(but version ok $ivers $rel $vers)\n";
				}
			}
			else {
				print "$name: neg dep, not installed\n" if $main::debug;
				print PLOG "$name: already deinstalled\n";
			}
			next;
		}
		
		my $is_satisfied = 0;
		my $installable = "";
		my $upgradeable = "";
		foreach $d ($dep, @{$dep->{'Alternatives'}}) {
			my ($name, $rel, $vers) =
				($d->{'Package'}, $d->{'Rel'}, $d->{'Version'});
			my $stat = $status->{$name};
			if (!$stat->{'Installed'}) {
				print "$name: pos dep, not installed\n" if $main::debug;
				print PLOG "$name: missing\n";
				$installable = $name if !$installable;
				next;
			}
			my $ivers = $stat->{'Version'};
			if (!$rel || version_cmp( $ivers, $rel, $vers )) {
				print "$name: pos dep, installed, no versioned dep or ",
					  "version ok\n" if $main::debug;
				print PLOG "$name: already installed";
				print PLOG " (in sufficient version $ivers $rel $vers)"
					if $rel;
				print PLOG "\n";
				$is_satisfied = 1;
				last;
			}
			print "$name: vers dep, installed $ivers ! $rel $vers\n"
				if $main::debug;
			print PLOG "$name: non-matching version installed ",
				  "($ivers ! $rel $vers)\n";
			if ($rel =~ /^</ ||
				($rel eq '=' && version_cmp($ivers, '>>', $vers))) {
				print "$name: would be a downgrade!\n" if $main::debug;
				print PLOG "$name: would have to downgrade!\n";
			}
			else {
				$upgradeable = $name if !$upgradeable;
			}
		}
		if (!$is_satisfied) {
			if ($upgradeable) {
				print "using $upgradeable for upgrade\n" if $main::debug;
				push( @$pos_list, $upgradeable );
			}
			elsif ($installable) {
				print "using $installable for install\n" if $main::debug;
				push( @$pos_list, $installable );
			}
			else {
				print PLOG "To satisfy this dependency the package(s) would ",
						   "have\n",
						   "to be downgraded; this is not implemented.\n";
				return 0;
			}
		}
	}

	return 1;
}

sub check_dependencies {
	my $dependencies = shift;
	my $fail = "";
	my($dep, $d, $name, %names);

	print PLOG "Checking correctness of source dependencies...\n";
	
	foreach $d (@$dependencies) {
		my $name = $d->{'Package'};
		$names{$name} = 1 if $name !~ /^\*/;
		foreach (@{$d->{'Alternatives'}}) {
			my $name = $_->{'Package'};
			$names{$name} = 1 if $name !~ /^\*/;
		}
	}
	my $status = get_dpkg_status( keys %names );

	foreach $dep (@$dependencies) {
		$name = $dep->{'Package'};
		next if $name =~ /^\*/;
		my $stat = $status->{$name};
		if ($dep->{'Neg'}) {
		    if ($stat->{'Installed'}) {
				if (!$dep->{'Rel'}) {
					$fail .= "$name(still installed) ";
				}
				elsif (version_cmp($stat->{'Version'}, $dep->{'Rel'},
								   $dep->{'Version'})) {
					$fail .= "$name(inst $stat->{'Version'} $dep->{'Rel'} ".
							 "conflicted $dep->{'Version'})\n";
				}
			}
		}
		else {
			my $is_satisfied = 0;
			my $f = "";
			foreach $d ($dep, @{$dep->{'Alternatives'}}) {
				my $name = $d->{'Package'};
				my $stat = $status->{$name};
				if (!$stat->{'Installed'}) {
					$f =~ s/ $/\|/ if $f;
					$f .= "$name(missing) ";
				}
				elsif ($d->{'Rel'} &&
					   !version_cmp( $stat->{'Version'}, $d->{'Rel'},
									 $d->{'Version'} )) {
					$f =~ s/ $/\|/ if $f;
					$f .= "$name(inst $stat->{'Version'} ! $d->{'Rel'} ".
						  "wanted $d->{'Version'}) ";
				}
				else {
					$is_satisfied = 1;
				}
			}
			if (!$is_satisfied) {
				$fail .= $f;
			}
		}
	}
	$fail =~ s/\s+$//;
	return $fail;
}

sub get_dpkg_status {
	my @interest = @_;
	my %result;
	local( *STATUS );

	return () if !@_;
	print "Requesting dpkg status for packages: @interest\n"
		if $main::debug;
	if (!open( STATUS, "<$main::chroot_dir/var/lib/dpkg/status" )) {
		print PLOG "Can't open $main::chroot_dir/var/lib/dpkg/status: $!\n";
		return ();
	}
	local( $/ ) = "";
	while( <STATUS> ) {
		my( $pkg, $status, $version, $provides );
		/^Package:\s*(.*)\s*$/mi and $pkg = $1;
		/^Status:\s*(.*)\s*$/mi and $status = $1;
		/^Version:\s*(.*)\s*$/mi and $version = $1;
		/^Provides:\s*(.*)\s*$/mi and $provides = $1;
		if (!$pkg) {
			print PLOG "sbuild: parse error in $main::chroot_dir/var/lib/dpkg/status: ",
					   "no Package: field\n";
			next;
		}
		print "$pkg ($version) status: $status\n" if $main::debug >= 2;
		if (!$status) {
			print PLOG "sbuild: parse error in $main::chroot_dir/var/lib/dpkg/status: ",
					   "no Status: field for package $pkg\n";
			next;
		}
		if ($status !~ /\sinstalled$/) {
			$result{$pkg}->{'Installed'} = 0
				if !(exists($result{$pkg}) &&
					 $result{$pkg}->{'Version'} eq '=*=PROVIDED=*=');
			next;
		}
		if ($version eq "") {
			print PLOG "sbuild: parse error in $main::chroot_dir/var/lib/dpkg/status: ",
					   "no Version: field for package $pkg\n";
			next;
		}
		$result{$pkg} = { Installed => 1, Version => $version }
			if isin( $pkg, @interest );
		if ($provides) {
			foreach (split( /\s*,\s*/, $provides )) {
				$result{$_} = { Installed => 1, Version => '=*=PROVIDED=*=' }
					if !$result{$_}{Installed} && isin( $_, @interest );					
			}
		}
	}
	close( STATUS );
	return \%result;
}

sub version_cmp {
	my $v1 = shift;
	my $rel = shift;
	my $v2 = shift;
	
	system "$conf::dpkg", "--compare-versions", $v1, $rel, $v2;
	return $? == 0;
}

sub run_script {
	my $e_mode = shift;
	my $x_mode = "";
	my $script = shift;

	if ($main::debug >= 2) {
		$x_mode = "set -x -v\n";
	}
	elsif ($main::debug)  {
		print "Running script:\n  ",
		join( "\n  ", split( "\n", "set $e_mode\n$script" )), "\n";
	}
	my $pid = fork();
	if (!defined $pid) {
		print PLOG "Can't fork: $! -- can't execute script\n";
		return 1;
	}
	if ($pid == 0) {
		setpgrp( 0, $$ );
		open( STDOUT, ">&PLOG" );
		open( STDERR, ">&PLOG" );
		if ($main::chroot_dir) {
			exec "$conf::sudo", "/usr/sbin/chroot", "$main::cwd/$main::chroot_dir",
				 "$conf::sudo", "-u", $main::username, "/bin/sh", "-c",
				 "cd /build/$main::username\nset $e_mode\n$x_mode$script";
		}
		else {
			exec "/bin/sh", "-c", "set $e_mode\n$x_mode$script";
		}
		die "Can't exec /bin/sh: $!\n";
	}
	wait;
	print "Script return value: $?\n" if $main::debug;
	return $?
}


sub read_deps {
	my @for_pkgs = @_;
	my $fname;
	local( *F );

	open( F, $fname = "<$conf::source_dependencies-$main::distribution" )
		or open( F, $fname = "<$conf::source_dependencies" )
		or die "Cannot open $conf::source_dependencies: $!\n";
	$fname = substr( $fname, 1 );
	print "Reading source dependencies from $fname\n"
		if $main::debug;
	while( <F> ) {
		chomp;
		next if /^\s*$/ || /^\s*#/;
		while( /\\$/ ) {
			chop;
			$_ .= <F>;
			chomp;
		}
		if (/^(\*\*?[\w\d.+-]+):\s*$/) {
			# is a special definition
			my $sp = $1;
			get_special( $fname, $sp, \*F );
			next;
		}
		if (/^abbrev\s+([\w\d.+-]+)\s*=\s*(.*)\s*$/) {
			my ($abbrev, $def) = ($1, $2);
			parse_one_srcdep( $abbrev, $def, \%main::abbrevs );
			next;
		}
		if (!/^([a-zA-Z\d.+-]+):\s*(.*)\s*$/) {
			warn "Syntax error in line $. in $fname\n";
			next;
		}
		my( $pkg, $deps ) = ($1, $2);
		if (exists $main::deps{$pkg}) {
			warn "Ignoring double entry for package $pkg at line $. ".
				 "in $fname\n";
			next;
		}
		next if !isin( $pkg, @for_pkgs );
		parse_one_srcdep( $pkg, $deps, \%main::deps );
	}
	close( F );

	foreach (@main::manual_srcdeps) {
		if (!/^([fa])([a-zA-Z\d.+-]+):\s*(.*)\s*$/) {
			warn "Syntax error in manual source dependency: ",
				 substr( $_, 1 ), "\n";
			next;
		}
		my ($mode, $pkg, $deps) = ($1, $2, $3);
		next if !isin( $pkg, @for_pkgs );
		@{$main::deps{$pkg}} = () if $mode eq 'f';
		parse_one_srcdep( $pkg, $deps, \%main::deps );
	}

	# substitute abbrevs and warn about undefined special deps
	my( $pkg, $i, %warned );
	foreach $pkg (keys %main::deps) {
	  repeat:
		my $dl = $main::deps{$pkg};
		for( $i = 0; $i < @$dl; ++$i ) {
			my $dep = $dl->[$i];
			my $name = $dep->{'Package'};
			if ($name =~ /^\*/) {
				if (!$warned{$name} && !exists $main::specials{$name}) {
					warn "Warning: $pkg: No definition for special ",
						 "dependency $name!\n";
					$warned{$name}++;
				}
			}
			elsif (defined $main::abbrevs{$name}) {
				my @l = @{$main::abbrevs{$name}};
				if (defined $dep->{'Alternatives'}) {
					warn "Warning: $pkg: abbrev $name not allowed ",
						 "in alternative\n";
					@l = ();
				}
				if ($dep->{'Neg'}) {
					warn "Warning: $pkg: Negation of abbrev $name ",
						 "not allowed\n";
					@l = ();
				}
				if ($dep->{'Rel'}) {
					warn "Warning: $pkg: No relation with abbrev $name ",
						 "allowed\n";
					@l = ();
				}
				if (my $ov = $dep->{'Override'}) {
					@l = map { my $x = copy($_);
							   $x->{'Override'} = $ov; $x } @l;
				}
				splice @$dl, $i, 1, @l;
				goto repeat;
			}
			elsif (defined $dep->{'Alternatives'}) {
				my $alt;
				foreach $alt (@{$dep->{'Alternatives'}}) {
					if (defined $main::abbrevs{$alt->{'Package'}}) {
						warn "Warning: $pkg: abbrev $alt->{'Package'} not ",
							 "allowed in alternative\n";
						splice @$dl, $i, 1;
					}
				}
			}
		}
	}

	if ($main::store_built_packages) {
		open(F, $fname = "<$conf::source_dependencies-$main::distribution.rev")
			or die "Cannot open $fname: $!\n";
		$fname = substr( $fname, 1 );
		print "Reading reverse source dependencies from $fname\n"
			if $main::debug;
		%main::revdeps = map { chomp; $_, 1 } <F>;
		close( F );
	}
}

sub copy {
	my $r = shift;
	my $new;

	if (ref($r) eq "HASH") {
		$new = { };
		foreach (keys %$r) {
			$new->{$_} = copy($r->{$_});
		}
	}
	elsif (ref($r) eq "ARRAY") {
		my $i;
		$new = [ ];
		for( $i = 0; $i < @$r; ++$i ) {
			$new->[$i] = copy($r->[$i]);
		}
	}
	elsif (!ref($r)) {
		$new = $r;
	}
	else {
		die "unknown ref type in copy\n";
	}
	
	return $new;
}

sub merge_pkg_build_deps {
	my $pkg = shift;
	my $depends = shift;
	my $dependsi = shift;
	my $conflicts = shift;
	my $conflictsi = shift;
	my (@l, $dep);

	print PLOG "** Using build dependencies supplied by package:\n";
	print PLOG "Build-Depends: $depends\n" if $depends;
	print PLOG "Build-Depends-Indep: $dependsi\n" if $dependsi;
	print PLOG "Build-Conflicts: $conflicts\n" if $conflicts;
	print PLOG "Build-Conflicts-Indep: $conflictsi\n" if $conflictsi;

	my $old_deps = copy($main::deps{$pkg});
	# keep deps from the central file marked as overrides (& prefix)
	foreach $dep (@{$main::deps{$pkg}}) {
		if ($dep->{'Override'}) {
			print PLOG "Added override: ",
				  (map { ($_->{'Neg'} ? "!" : "") .
					     $_->{'Package'} .
						 ($_->{'Rel'} ? " ($_->{'Rel'} $_->{'Version'})":"") }
				   	scalar($dep), @{$dep->{'Alternatives'}}), "\n";
			push( @l, $dep );
		}
	}

	$conflicts = join( ", ", map { "!$_" } split( /\s*,\s*/, $conflicts ));
	$conflictsi = join( ", ", map { "!$_" } split( /\s*,\s*/, $conflictsi ));
	
	my $deps = $depends . ", " . $conflicts;
	$deps .= "," . $dependsi . ", " . $conflictsi if $main::build_arch_all;
	@{$main::deps{$pkg}} = @l;
	print "Merging pkg deps: $deps\n" if $main::debug;
	parse_one_srcdep( $pkg, $deps, \%main::deps );

	my $missing = (cmp_dep_lists( $old_deps, $main::deps{$pkg} ))[1];
	return if !@$missing;

	# read list of build-essential packages (if not yet done) and expand their
	# dependencies (those are implicitly essential)
	if (!defined($main::deps{'ESSENTIAL'})) {
		my $ess = read_build_essential();
		parse_one_srcdep( 'ESSENTIAL', $ess, \%main::deps );
	}
	my ($exp_essential, $exp_pkgdeps, $filt_essential, $filt_pkgdeps);
	$exp_essential = expand_dependencies( $main::deps{'ESSENTIAL'} );
	print "Dependency-expanded build essential packages:\n",
		  format_deps(@$exp_essential), "\n" if $main::debug;

	# remove missing central deps that are essential
	($filt_essential, $missing) = cmp_dep_lists( $missing, $exp_essential );
	print PLOG "** Filtered missing central deps that are build-essential:\n",
			   format_deps(@$filt_essential), "\n"
				   if @$filt_essential;

	# if some build deps are virtual packages, replace them by an alternative
	# over all providing packages
	$exp_pkgdeps = expand_virtuals( $main::deps{$pkg} );
	print "Provided-expanded build deps:\n",
		  format_deps(@$exp_pkgdeps), "\n" if $main::debug;

	# now expand dependencies of package build deps
	$exp_pkgdeps = expand_dependencies( $exp_pkgdeps );
	print "Dependency-expanded build deps:\n",
		  format_deps(@$exp_pkgdeps), "\n" if $main::debug;
	$main::additional_deps = $exp_pkgdeps;

	# remove missing central deps that are dependencies of build deps
	($filt_pkgdeps, $missing) = cmp_dep_lists( $missing, $exp_pkgdeps );
	print PLOG "** Filtered missing central deps that are dependencies of ",
			   "or provide build-deps:\n",
			   format_deps(@$filt_pkgdeps), "\n"
				   if @$filt_pkgdeps;

	# remove comment package names
	push( @$main::additional_deps,
		  grep { $_->{'Neg'} && $_->{'Package'} =~ /^needs-no-/ } @$missing );
	$missing = [ grep { !($_->{'Neg'} &&
					      ($_->{'Package'} =~ /^this-package-does-not-exist/ ||
						   $_->{'Package'} =~ /^needs-no-/)) } @$missing ];

	print PLOG "**** Warning:\n",
			   "**** The following central src deps are ",
			   "(probably) missing:\n  ", format_deps(@$missing), "\n"
				   if @$missing;
}

sub cmp_dep_lists {
	my $list1 = shift;
	my $list2 = shift;
	my ($dep, @common, @missing);

	foreach $dep (@$list1) {
		my $found = 0;

		if ($dep->{'Neg'}) {
			foreach (@$list2) {
				if ($dep->{'Package'} eq $_->{'Package'} && $_->{'Neg'}) {
					$found = 1;
					last;
				}
			}
		}
		else {
			my $al = get_altlist($dep);
			foreach (@$list2) {
				if (is_superset( get_altlist($_), $al )) {
					$found = 1;
					last;
				}
			}
		}

		if ($found) {
			push( @common, $dep );
		}
		else {
			push( @missing, $dep );
		}
	}
	return (\@common, \@missing);
}

sub get_altlist {
	my $dep = shift;
	my %l;

	foreach (scalar($dep), @{$dep->{'Alternatives'}}) {
		$l{$_->{'Package'}} = 1 if !$_->{'Neg'};
	}
	return \%l;
}

sub is_superset {
	my $l1 = shift;
	my $l2 = shift;

	foreach (keys %$l2) {
		return 0 if !exists $l1->{$_};
	}
	return 1;
}

sub read_build_essential {
	my @essential;
	local (*F);

	if (open( F, "/usr/share/doc/build-essential/essential-packages-list" )) {
		while( <F> ) {
			last if $_ eq "\n";
		}
		while( <F> ) {
			chomp;
			push( @essential, $_ ) if $_ !~ /^\s*$/;
		}
		close( F );
	}
	else {
		warn "Cannot open /usr/share/doc/build-essential/essential-packages-list: $!\n";
	}

	if (open( F, "/usr/share/doc/build-essential/list" )) {
		while( <F> ) {
			last if $_ eq "BEGIN LIST OF PACKAGES\n";
		}
		while( <F> ) {
			chomp;
			last if $_ eq "END LIST OF PACKAGES";
			next if /^\s/ || /^$/;
			push( @essential, $_ );
		}
		close( F );
	}
	else {
		warn "Cannot open /usr/share/doc/build-essential/list: $!\n";
	}

	return join( ", ", @essential );
}

sub expand_dependencies {
	my $dlist = shift;
	my (@to_check, @result, %seen, $check, $dep);

	foreach $dep (@$dlist) {
		next if $dep->{'Neg'} || $dep->{'Package'} =~ /^\*/;
		foreach (scalar($dep), @{$dep->{'Alternatives'}}) {
			my $name = $_->{'Package'};
			push( @to_check, $name );
			$seen{$name} = 1;
		}
		push( @result, copy($dep) );
	}

	while( @to_check ) {
		my $deps = get_dependencies( @to_check );
		my @check = @to_check;
		@to_check = ();
		foreach $check (@check) {
			foreach (split( /\s*,\s*/, $deps->{$check} )) {
				foreach (split( /\s*\|\s*/, $_ )) {
					my $pkg = (/^([^\s([]+)/)[0];
					if (!$seen{$pkg}) {
						push( @to_check, $pkg );
						push( @result, { Package => $pkg, Neg => 0 } );
						$seen{$pkg} = 1;
					}
				}
			}
		}
	}
	
	return \@result;
}

sub expand_virtuals {
	my $dlist = shift;
	my ($dep, %names, @new_dlist);

	foreach $dep (@$dlist) {
		foreach (scalar($dep), @{$dep->{'Alternatives'}}) {
			$names{$_->{'Package'}} = 1;
		}
	}
	my $provided_by = get_virtuals( keys %names );

	foreach $dep (@$dlist) {
		my %seen;
		foreach (scalar($dep), @{$dep->{'Alternatives'}}) {
			my $name = $_->{'Package'};
			$seen{$name} = 1;
			if (exists $provided_by->{$name}) {
				foreach( keys %{$provided_by->{$name}} ) {
					$seen{$_} = 1;
				}
			}
		}
		my @l = map { { Package => $_, Neg => 0 } } keys %seen;
		my $l = shift @l;
		foreach (@l) {
			push( @{$l->{'Alternatives'}}, $_ );
		}
		push( @new_dlist, $l );
	}

	return \@new_dlist;
}
		
sub get_dependencies {
	local(*PIPE);
	my %deps;
	
	open( PIPE, "$conf::apt_cache $main::chroot_apt_options show @_ 2>&1 |" )
		or die "Cannot start $conf::apt_cache $main::chroot_apt_op: $!\n";
	local($/) = "";
	while( <PIPE> ) {
		my ($name, $dep, $predep);
		/^Package:\s*(.*)\s*$/mi and $name = $1;
		next if !$name || $deps{$name};
		/^Depends:\s*(.*)\s*$/mi and $dep = $1;
		/^Pre-Depends:\s*(.*)\s*$/mi and $predep = $1;
		$dep .= ", " if $dep && $predep;
		$dep .= $predep;
		$deps{$name} = $dep;
	}
	close( PIPE );
	die "$conf::apt_cache exit status $?\n" if $?;

	return \%deps;
}
		
sub get_virtuals {
	local(*PIPE);

	open( PIPE, "$conf::apt_cache $main::chroot_apt_options showpkg @_ 2>&1 |" )
		or die "Cannot start $conf::apt_cache $main::chroot_apt_op: $!\n";
	my $name;
	my $in_rprov = 0;
	my %provided_by;
	while( <PIPE> ) {
		if (/^Package:\s*(\S+)\s*$/) {
			$name = $1;
		}
		elsif (/^Reverse Provides: $/) {
			$in_rprov = 1;
		}
		elsif ($in_rprov && /^(\w+):\s/) {
			$in_rprov = 0;
		}
		elsif ($in_rprov && /^(\S+)\s*\S+\s*$/) {
			$provided_by{$name}->{$1} = 1;
		}
	}
	close( PIPE );
	die "$conf::apt_cache exit status $?\n" if $?;

	return \%provided_by;
}


sub parse_one_srcdep {
	my $pkg = shift;
	my $deps = shift;
	my $hash = shift;
	
	$deps =~ s/^\s*(.*)\s*$/$1/;
	foreach (split( /\s*,\s*/, $deps )) {
		my @l;
		my $override;
		if (/^\&/) {
			$override = 1;
			s/^\&\s+//;
		}
		my @alts = split( /\s*\|\s*/, $_ );
		my $special_seen = 0;
		my $neg_seen = 0;
		foreach (@alts) {
			if (!/^([^\s([]+)\s*(\(\s*([<=>]+)\s*(\S+)\s*\))?(\s*\[([^]]+)\])?/) {
				warn "Warning: syntax error in dependency '$_' of $pkg\n";
				next;
			}
			my( $dep, $rel, $relv, $archlist ) = ($1, $3, $4, $6);
			if ($archlist) {
				$archlist =~ s/^\s*(.*)\s*$/$1/;
				my @archs = split( /\s+/, $archlist );
				my ($use_it, $ignore_it, $include) = (0, 0, 0);
				foreach (@archs) {
					if (/^!/) {
						$ignore_it = 1 if substr($_, 1) eq $main::arch;
					}
					else {
						$use_it = 1 if $_ eq $main::arch;
						$include = 1;
					}
				}
				warn "Warning: inconsistent arch restriction on ",
					 "$pkg: $dep depedency\n"
						 if $ignore_it && $use_it;
				next if $ignore_it || ($include && !$use_it);
			}
			if ($dep =~ /^\*/) {
				warn "Warning: $pkg: ignoring version relation on ".
					 "special dependency $dep\n"
						 if $rel || $relv;
				push( @l, { Package => $dep, Override => 1 } );
				$special_seen = 1;
				next;
			}
			my $neg = 0;
			if ($dep =~ /^!/) {
				$dep =~ s/^!\s*//;
				$neg = 1;
				$neg_seen = 1;
			}
			if ($conf::srcdep_over{$dep}) {
				if ($main::verbose) {
					print PLOG "Replacing source dep $dep";
					print PLOG " ($rel $relv)" if $relv;
					print PLOG " with $conf::srcdep_over{$dep}[0]";
					print PLOG " ($conf::srcdep_over{$dep}[1] $conf::srcdep_over{$dep}[2])"
					  if $conf::srcdep_over{$dep}[1];
					print PLOG ".\n";
				}
				$dep = $conf::srcdep_over{$dep}[0];
				$rel = $conf::srcdep_over{$dep}[1];
				$relv = $conf::srcdep_over{$dep}[2];
			}
			my $h = { Package => $dep, Neg => $neg };
			if ($rel && $relv) {
				$h->{'Rel'} = $rel;
				$h->{'Version'} = $relv;
			}
			$h->{'Override'} = $override if $override;
			push( @l, $h );
		}
		if (@alts > 1 && $special_seen) {
			warn "Warning: $pkg: alternatives with special dependencies ",
				 "forbidden -- skipped\n";
		}
		elsif (@alts > 1 && $neg_seen) {
			warn "Warning: $pkg: alternatives with negative dependencies ",
				 "forbidden -- skipped\n";
		}
		elsif (@l) {
			my $l = shift @l;
			foreach (@l) {
				push( @{$l->{'Alternatives'}}, $_ );
			}
			push( @{$hash->{$pkg}}, $l );
		}
	}
}

sub get_special {
	my $fname = shift;
	my $sp = shift;
	my $sub = "";
	
	while( <F> ) {
		last if /^$/;
		if (/^\s*(\w+)\s*\{\s*$/) {
			if ($sub) {
				warn "Syntax error in line $. in $fname:\n";
				warn "  Start of special subsection inside ".
					"another one.\n";
			}
			else {
				$sub = $1;
				$main::specials{$sp}->{$sub} = "";
			}
		}
		elsif (/^\s*\}\s*$/) {
			if (!$sub) {
				warn "Syntax error in line $. in $fname:\n";
				warn "  }  outside of special subsection\n";
			}
			else {
				$sub = "";
			}
		}
		elsif ($sub) {
			$main::specials{$sp}->{$sub} .= $_;
		}
		else {
			warn "Syntax error in line $. in $fname:\n";
			warn "  Subsection start expected\n";
		}
	}
	if ($sub) {
		warn "Syntax error in line $. in $fname:\n";
		warn "  Subsection not finished with }\n";
	}

	push( @main::global_patches, $sp ) if $sp =~ /^\*\*/;
}


sub open_log {
	my $date = `date +%Y%m%d-%H%M`;
	chomp( $date );

	if ($main::nolog) {
		open( LOG, ">&STDOUT" );
		open( PLOG, ">&LOG" ) or warn "Can't redirect PLOG\n";
		select( LOG );
		return;
	}

	$main::main_logfile = "build-$date.log";

	if ($main::verbose) {
		my $pid;
		($pid = open( LOG, "|-")) || exec "tee $main::main_logfile";
		if (!defined $pid) {
			warn "Cannot open pipe to 'tee $main::main_logfile': $!\n";
		}
		else {
			$main::tee_pid = $pid;
		}
	}
	else {
		open( LOG, ">$main::main_logfile" )
			or warn "Cannot open log file $main::main_logfile: $!\n";
	}
	select( (select( LOG ), $| = 1)[0] );
	open( STDOUT, ">&LOG" ) or warn "Can't redirect stdout\n";
	open( STDERR, ">&LOG" ) or warn "Can't redirect stderr\n";
	open( PLOG, ">&LOG" ) or warn "Can't redirect PLOG\n";
}

sub close_log {
	my $date = `date +%Y%m%d-%H%M`;
	chomp( $date );

	kill( 15, $main::tee_pid ) if $main::verbose;
	close( LOG );
	if (!$main::nolog && !$main::verbose &&
		-s $main::main_logfile && $conf::mailto) {
		send_mail( $conf::mailto, "Log from sbuild $date",
				   $main::main_logfile ) if $conf::mailto;
	}
	elsif (!$main::nolog && !$main::verbose && ! -s $main::main_logfile) {
		unlink( $main::main_logfile );
	}
}

sub open_pkg_log {
	my $date = `date +%Y%m%d-%H%M`;
	chomp( $date );
	my $pkg = shift;

	if ($main::nolog) {
		open( PLOG, ">&STDOUT" );
	}
	else {
		$pkg = basename( $pkg );
		if ($main::binNMU) {
			$pkg =~ /^([^_]+)_([^_]+)(.*)$/;
			$pkg = $1."_".binNMU_version($2);
			$main::binNMU_name = $pkg;
			$pkg .= $3;
		}
		$main::pkg_logfile = "$conf::log_dir/${pkg}_$date";
		system "ln -sf $main::pkg_logfile current";
		if ($main::verbose) {
			my $pid;
			($pid = open( PLOG, "|-")) || exec "tee $main::pkg_logfile";
			if (!defined $pid) {
				warn "Cannot open pipe to 'tee $main::pkg_logfile': $!\n";
			}
			else {
				$main::pkg_tee_pid = $pid;
			}
		}
		else {
			if (!open( PLOG, ">$main::pkg_logfile" )) {
				warn "Can't open logfile $main::pkg_logfile: $!\n";
				return 0;
			}
		}
	}
	select( (select( PLOG ), $| = 1)[0] );

	my $revision = '$Revision: 1.7 $';
	$revision =~ /([\d.]+)/;
	$revision = $1;

	print PLOG "Automatic build of $pkg on $main::HOSTNAME by ".
			   "sbuild/$main::arch $revision\n";
	print PLOG "Build started at $date\n";
	print PLOG "*"x78, "\n";
	return 1;
}

sub close_pkg_log {
	my $date = `date +%Y%m%d-%H%M`;
	my $pkg = shift;
	my $t = $main::pkg_end_time - $main::pkg_start_time;
	
	$pkg = basename( $pkg );
	$t = 0 if $t < 0;
	if ($main::pkg_status eq "successful") {
		add_time_entry( $pkg, $t );
		add_space_entry( $pkg, $main::this_space );
	}
	print PLOG "*"x78, "\n";
	printf PLOG "Finished at ${date}Build needed %02d:%02d:%02d, %dk disk space\n",
		   int($t/3600), int(($t%3600)/60), int($t%60), $main::this_space;
	kill( 15, $main::pkg_tee_pid ) if $main::verbose && !$main::nolog;
	close( PLOG );
	open( PLOG, ">&LOG" ) or warn "Can't redirect PLOG\n";
	send_mail( $conf::mailto,
			   "Log for $main::pkg_status build of ".
			   ($main::binNMU_name || $pkg)." (dist=$main::distribution)",
			   $main::pkg_logfile ) if !$main::nolog && $conf::mailto;
}

sub add_time_entry {
	my $pkg = shift;
	my $t = shift;

	return if !$conf::avg_time_db;
	my %db;
	if (!tie %db, 'GDBM_File',$conf::avg_time_db,GDBM_WRCREAT,0664) {
		print "Can't open average time db $conf::avg_time_db\n";
		return;
	}
	$pkg =~ s/_.*//;
		
	if (exists $db{$pkg}) {
		my @times = split( /\s+/, $db{$pkg} );
		push( @times, $t );
		my $sum = 0;
		foreach (@times[1..$#times]) { $sum += $_; }
		$times[0] = $sum / (@times-1);
		$db{$pkg} = join( ' ', @times );
	}
	else {
		$db{$pkg} = "$t $t";
	}
	untie %db;
}

sub check_space {
	my @files = @_;
	local( *PIPE );

	if (!open( PIPE, "sudo /usr/bin/du -s @files |" )) {
		print PLOG "Cannot determine space needed (du failed): $!\n";
		return;
	}
	my $sum = 0;
	while( <PIPE> ) {
		next if !/^(\d+)/;
		$sum += $1;
	}
	close( PIPE );
	$main::this_space = $sum;
}

sub add_space_entry {
	my $pkg = shift;
	my $t = shift;

	my $keepvals = 4;
	
	return if !$conf::avg_space_db || $main::this_space == 0;
	my %db;
	if (!tie %db, 'GDBM_File',$conf::avg_space_db,GDBM_WRCREAT,0664) {
		print "Can't open average space db $conf::avg_space_db\n";
		return;
	}
	$pkg =~ s/_.*//;
		
	if (exists $db{$pkg}) {
		my @values = split( /\s+/, $db{$pkg} );
		shift @values;
		unshift( @values, $t );
		pop @values if @values > $keepvals;
		my ($sum, $n, $weight, $i) = (0, 0, scalar(@values));
		for( $i = 0; $i < @values; ++$i) {
			$sum += $values[$i] * $weight;
			$n += $weight;
		}
		unshift( @values, $sum/$n );
		$db{$pkg} = join( ' ', @values );
	}
	else {
		$db{$pkg} = "$t $t";
	}
	untie %db;
}

sub check_inst_packages {
	my @files = @_;
	my @names = map { /^([a-zA-Z\d.+-]+)_/ } @files;

	print "Checking if packages should be stored: @names\n" if $main::debug;
	
	# first take all packages on which a srcdep exists
	my (@inst, %provided_by, $name);
	foreach $name (@names) {
		next if !exists $main::revdeps{$name};
		push( @inst, $name );
		print "  $name is a srcdep and will be stored\n" if $main::debug;
		my $file = file_for_name( $name, @files );
		chomp( my $provides = `dpkg -f '$file' provides` );
		$provides =~ s/^\s*(.*)\s*$/$1/;
		foreach (split( /\s*,\s*/, $provides )) {
			$provided_by{$_} = $name;
			print "  $name provides $_\n" if $main::debug;
		}
	}
	return if !@inst;

	# then check dependencies of all choosen packages; if one of the deps has
	# just been built, too, add it also
	my @deps_inst;
	my @to_check = @inst;
	while( $name = shift @to_check ) {
		my $file = file_for_name( $name, @files );
		chomp( my $depends = `dpkg -f '$file' depends` );
		$depends =~ s/^\s*(.*)\s*$/$1/;
		print "  dependency line of $name: $depends\n" if $main::debug;
		my @d;
		foreach (split( /\s*,\s*/, $depends )) {
			foreach (split( /\s*\|\s*/, $_ )) {
				if (!/^([^\s([]+)\s*(\(\s*([<=>]+)\s*(\S+)\s*\))?/) {
					print PLOG "Cannot parse dependency from $file: $_\n";
					next;
				}
				push( @d, $1 );
			}
		}
		foreach (@d) {
			print "  dependency $_: " if $main::debug;
			if (isin( $_, @inst )) {
				print "already on store list\n" if $main::debug;
				next;
			}
			elsif (isin( $_, @names )) {
				print "also built, adding to store list\n" if $main::debug;
				if (!isin( $_, @inst, @deps_inst )) {
					push( @deps_inst, $_ );
					push( @to_check, $_ );
				}
			}
			elsif (exists $provided_by{$_}) {
				my $prov = $provided_by{$_};
				print "provided by $prov; " if $main::debug;
				if (isin( $prov, @inst )) {
					print "already on store list\n" if $main::debug;
				}
				else {
					print "adding to store list\n" if $main::debug;
					if (!isin( $prov, @inst, @deps_inst )) {
						push( @deps_inst, $prov );
						push( @to_check, $prov );
					}
				}
			}
			else {
				print "not built, ignoring\n" if $main::debug;
			}
		}
	}
	print "Final store list: @inst @deps_inst\n" if $main::debug;
	my @finst = map { file_for_name( $_, @files ); } @inst, @deps_inst;
	print PLOG "\nThe following packages are source dependencies and will ",
			   "be stored:\n  @inst\n";
	print PLOG "The following are dependencies of the above and will be ",
			   "be stored, too:\n  @deps_inst\n"
		if @deps_inst;

	my @chroots;
	if ($main::chroot_dir) {
		foreach (keys(%main::dist_order)) {
			push( @chroots, "--chroot=$main::cwd/chroot-$_" )
				  if !dist_gt( $_, $main::distribution ) &&
				     -d "$main::cwd/chroot-$_";
		}
	}
			
	# start program given by --store-built-packages option to make those
	# packages available for apt
	my $pid = fork();
	if (!defined $pid) {
		print PLOG "Can't spawn $main::store_built_packages: $!\n";
		return;
	}
	if ($pid == 0) {
		setpgrp( 0, $$ );
		open( STDOUT, ">&PLOG" );
		open( STDERR, ">&PLOG" );
		exec $main::store_built_packages, "--dist=$main::distribution",
			 @chroots, @finst;
	}
	wait;
	if ($?) {
		print PLOG "$main::store_built_packages returned error status $?\n";
		return;
	}

	# if the install went ok, run apt to install the packages that are already
	# installed.
	if (!$main::chroot_dir && $conf::system_level &&
		dist_gt( $main::distribution, $conf::system_level )) {
		print PLOG "Not installing because built for a newer ",
				   "distribution than $conf::system_level\n";
		return;
	}
			
	my $status = get_dpkg_status( @inst, @deps_inst );
	my @to_upgrade = grep { $status->{$_}->{'Installed'} } @inst, @deps_inst;
	if (!@to_upgrade) {
		print PLOG "None of the packages are installed -- no upgrades.\n";
		return;
	}
	my @to_upgrade2;
	foreach (@to_upgrade) {
		if (isin( $_, @conf::no_auto_upgrade )) {
			print PLOG "Auto-upgrade inhibited for $_\n";
		}
		else {
			push( @to_upgrade2, $_ );
		}
	}
	
	print PLOG "These packages are already installed and will be updated:\n",
			   "  @to_upgrade2\n";
	my (@dummy1, @dummy2);
	run_apt( "-y", \@dummy1, \@dummy2, @to_upgrade2 );
}

sub file_for_name {
	my $name = shift;
	my @x = grep { /^\Q$name\E_/ } @_;
	return $x[0];
}
				
sub write_jobs_file {
	my $news = shift;
	my $job;
	local( *F );
	
	$main::job_state{$main::current_job} = $news
		if $news && $main::current_job;

	return if !$main::batchmode;
	
	return if !open( F, ">$main::jobs_file" );
	foreach $job (@ARGV) {
		print F ($job eq $main::current_job) ? "" : "  ",
				$job,
				($main::job_state{$job} ? ": $main::job_state{$job}" : ""),
				"\n";
	}
	close( F );
}

sub append_to_FINISHED {
	my $pkg = shift;
	local( *F );

	return if !$main::batchmode;
	
	open( F, ">>SBUILD-FINISHED" );
	print F "$pkg\n";
	close( F );
}

sub write_srcdep_lock_file {
	my $deps = shift;
	my $specials = shift;
	local( *F );

	++$main::srcdep_lock_cnt;
	my $f = "$conf::srcdep_lock_dir/$$-$main::srcdep_lock_cnt";
	if (!open( F, ">$f" )) {
		print "Warning: cannot create srcdep lock file $f: $!";
		return;
	}
	print "Writing srcdep lock file $f:\n" if $main::debug;

	chomp( my $user = `/usr/bin/whoami` );
	print F "$main::current_job $$ $user\n";
	print "Job $main::current_job pid $$ user $user\n" if $main::debug;
	foreach (@$deps) {
		my $name = $_->{'Package'};
		# add special deps only if they affect global state ("global" sub)
		next if $name =~ /^\*/ &&
			    (!isin( $name, @$specials ) ||
				 $main::specials{$name}->{'global'} !~ /yes/m);
		print F ($_->{'Neg'} ? "!" : ""), "$name\n";
		print "  ", ($_->{'Neg'} ? "!" : ""), "$name\n" if $main::debug;
	}
	close( F );
}

sub check_srcdep_conflicts {
	my $to_inst = shift;
	my $to_remove = shift;
	my $special = shift;
	local( *F, *DIR );
	my $mypid = $$;
	my %conflict_builds;

	if (!opendir( DIR, $conf::srcdep_lock_dir )) {
		print PLOG "Cannot opendir $conf::srcdep_lock_dir: $!\n";
		return 1;
	}
	my @files = grep { !/^\.\.?$/ && !/^install\.lock/ && !/^$mypid-\d+$/ }
					 readdir(DIR);
	closedir(DIR);

	my $file;
	foreach $file (@files) {
		if (!open( F, "<$conf::srcdep_lock_dir/$file" )) {
			print PLOG "Cannot open $conf::srcdep_lock_dir/$file: $!\n";
			next;
		}
		<F> =~ /^(\S+)\s+(\S+)\s+(\S+)/;
		my ($job, $pid, $user) = ($1, $2, $3);

		# ignore (and remove) a lock file if associated process doesn't exist
		# anymore
		if (kill( 0, $pid ) == 0 && $! == ESRCH) {
			close( F );
			print PLOG "Found stale srcdep lock file $file -- removing it\n";
			print PLOG "Cannot remove: $!\n"
				if !unlink( "$conf::srcdep_lock_dir/$file" );
			next;
		}

		print "Reading srclock file $file by job $job user $user\n"
			if $main::debug;

		while( <F> ) {
			my ($neg, $pkg) = /^(!?)(\S+)/;
			print "Found ", ($neg ? "neg " : ""), "entry $pkg\n"
				if $main::debug;

			if ($pkg =~ /^\*/) {
				print PLOG "Build of $job by $user (pid $pid) has ",
						   "installed the global special dependency $pkg.\n";
				$conflict_builds{$file} = 1;
			}
			else {
				if (isin( $pkg, @$to_inst, @$to_remove )) {
					print PLOG "Source dependency conflict with build of ",
							   "$job by $user (pid $pid):\n";
					print PLOG "  $job ", ($neg ? "conflicts with" : "needs"),
							   " $pkg\n";
					print PLOG "  $main::current_job wants to ",
							   (isin( $pkg, @$to_inst ) ? "update" : "remove"),
							   " $pkg\n";
					$conflict_builds{$file} = 1;
				}
			}
		}
		close( F );
	}

	foreach (@$special) {
		if ($main::specials{$_}->{'global'} =~ /yes/m) {
			print PLOG "$main::current_job wants to apply global ",
					   "special dependency $_\n",
					   "Must wait for other builds to finish\n";
			foreach (@files) {
				$conflict_builds{$_} = 1;
			}
		}
	}

	my @conflict_builds = keys %conflict_builds;
	if (@conflict_builds) {
		print "Srcdep conflicts with: @conflict_builds\n" if $main::debug;
	}
	else {
		print "No srcdep conflicts\n" if $main::debug;
	}
	return @conflict_builds;
}

sub remove_srcdep_lock_file {
	my $f = "$conf::srcdep_lock_dir/$$-$main::srcdep_lock_cnt";

	print "Removing srcdep lock file $f\n" if $main::debug;
	if (!unlink( $f )) {
		print "Warning: cannot remove srcdep lock file $f: $!\n"
			if $! != ENOENT;
	}
}

sub prepare_watches {
	my $dependencies = shift;
	my @instd = @_;
	my(@dep_on, $dep, $pkg, $prg);

	@dep_on = @instd;
	foreach $dep (@$dependencies, @$main::additional_deps) {
		if ($dep->{'Neg'} && $dep->{'Package'} =~ /^needs-no-(\S+)/) {
			push( @dep_on, $1 );
		}
		elsif ($dep->{'Package'} !~ /^\*/ && !$dep->{'Neg'}) {
			foreach (scalar($dep), @{$dep->{'Alternatives'}}) {
				push( @dep_on, $_->{'Package'} );
			}
		}
	}
	# init %this_watches to names of packages which have not been installed as
	# source dependencies
	undef %main::this_watches;
	foreach $pkg (keys %conf::watches) {
		if (isin( $pkg, @dep_on )) {
			print "Excluding from watch: $pkg\n" if $main::debug;
			next;
		}
		foreach $prg (@{$conf::watches{$pkg}}) {
			$prg = "/usr/bin/$prg" if $prg !~ m,^/,;
			$main::this_watches{"$main::chroot_dir$prg"} = $pkg;
			print "Will watch for $prg ($pkg)\n" if $main::debug;
		}
	}
}

sub check_watches {
	my($prg, @st, %used);

	foreach $prg (keys %main::this_watches) {
		if (!(@st = stat( $prg ))) {
			print "Watch: $prg: stat failed\n" if $main::debug;
			next;
		}
		if ($st[8] > $main::build_start_time) {
			my $pkg = $main::this_watches{$prg};
			my $prg2 = $prg;
			$prg2 =~ s/^\Q$main::chroot_dir\E// if $main::chroot_dir;
			push( @{$used{$pkg}}, $prg2 )
				if @main::have_dsc_build_deps ||
				   !isin( $pkg, @conf::ignore_watches_no_build_deps );
		}
		else {
			print "Watch: $prg: untouched\n" if $main::debug;
		}
	}
	return if !%used;

	print PLOG <<EOF;

NOTE: The package could have used binaries from the following packages
(access time changed) without a source dependency:
EOF
	foreach (keys %used) {
		print PLOG "  $_: @{$used{$_}}\n";
	}
	print PLOG "\n";
}

sub should_skip {
	my $pkgv = shift;

	fixup_pkgv( \$pkgv );
	lock_file( "SKIP" );
	goto unlock if !open( F, "SKIP" );
	my @pkgs = <F>;
	close( F );

	if (!open( F, ">SKIP" )) {
		print "Can't open SKIP for writing: $!\n",
			  "Would write: @pkgs\nminus $pkgv\n";
		goto unlock;
	}
	my $found = 0;
	foreach (@pkgs) {
		if (/^\Q$pkgv\E$/) {
			++$found;
			print PLOG "$pkgv found in SKIP file -- skipping building it\n";
		}
		else {
			print F $_;
		}
	}
	close( F );
  unlock:
	unlock_file( "SKIP" );
	return $found;
}

sub add_givenback {
	my $pkgv = shift;
	my $time = shift;
	local( *F );

	lock_file( "SBUILD-GIVEN-BACK" );

	if (open( F, ">>SBUILD-GIVEN-BACK" )) {
		print F "$pkgv $time\n";
		close( F );
	}
	else {
		print PLOG "Can't open SBUILD-GIVEN-BACK: $!\n";
	}

  unlock:
	unlock_file( "SBUILD-GIVEN-BACK" );
}

sub send_mail {
	my $to = shift;
	my $subject = shift;
	my $file = shift;
	local( *MAIL, *F );

	if (!open( F, "<$file" )) {
		warn "Cannot open $file for mailing: $!\n";
		return 0;
	}
	local $SIG{'PIPE'} = 'IGNORE';
	
	if (!open( MAIL, "|$conf::mailprog -oem $to" )) {
		warn "Could not open pipe to $conf::mailprog: $!\n";
		close( F );
		return 0;
	}

	print MAIL "From: $conf::mailfrom\n";
	print MAIL "To: $to\n";
	print MAIL "Subject: $subject\n\n";
	while( <F> ) {
		print MAIL "." if $_ eq ".\n";
		print MAIL $_;
	}
	
	close( F );
	if (!close( MAIL )) {
		warn "$conf::mailprog failed (exit status $?)\n";
		return 0;
	}
	return 1;
}


sub set_installed {
	foreach (@_) {
		$main::changes->{'installed'}->{$_} = 1;
	}
	print "Added to installed list: @_\n" if $main::debug;
}

sub set_removed {
	foreach (@_) {
		$main::changes->{'removed'}->{$_} = 1;
		if (exists $main::changes->{'installed'}->{$_}) {
			delete $main::changes->{'installed'}->{$_};
			$main::changes->{'auto-removed'}->{$_} = 1;
			print "Note: $_ was installed\n" if $main::debug;
		}
	}
	print "Added to removed list: @_\n" if $main::debug;
}

sub unset_installed {
	foreach (@_) {
		delete $main::changes->{'installed'}->{$_};
	}
	print "Removed from installed list: @_\n" if $main::debug;
}

sub unset_removed {
	foreach (@_) {
		delete $main::changes->{'removed'}->{$_};
		if (exists $main::changes->{'auto-removed'}->{$_}) {
			delete $main::changes->{'auto-removed'}->{$_};
			$main::changes->{'installed'}->{$_} = 1;
			print "Note: revived $_ to installed list\n" if $main::debug;
		}
	}
	print "Removed from removed list: @_\n" if $main::debug;
}

sub basename {
	my $b = $_[0];
	$b =~ s,^.*/,,;
	return $b;
}

sub df {
	my $dir = shift;

	my $free = `/bin/df $dir | tail -n 1`;
	my @free = split( /\s+/, $free );
	return $free[3];
}

sub isin {
	my $val = shift;
	return grep( $_ eq $val, @_ );
}

sub fixup_pkgv {
	my $pkgv = shift;
	
	$$pkgv =~ s,^.*/,,; # strip path
	$$pkgv =~ s/\.(dsc|diff\.gz|tar\.gz|deb)$//; # strip extension
	$$pkgv =~ s/_[a-zA-Z\d+-]+\.(changes|deb)$//; # strip extension
}

sub format_deps {
	return join( ", ",
		   map { join( "|",
				 map { ($_->{'Neg'} ? "!" : "") .
					   $_->{'Package'} .
					   ($_->{'Rel'} ? " ($_->{'Rel'} $_->{'Version'})":"")}
				 scalar($_), @{$_->{'Alternatives'}}) } @_ );
}

sub lock_file {
	my $file = shift;
	my $for_srcdep = shift;
	my $lockfile = "$file.lock";
	my $try = 0;
	
  repeat:
	if (!sysopen( F, $lockfile, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL, 0644 )){
		if ($! == EEXIST) {
			# lock file exists, wait
			goto repeat if !open( F, "<$lockfile" );
			my $line = <F>;
			my ($pid, $user);
			close( F );
			if ($line !~ /^(\d+)\s+([\w\d.-]+)$/) {
				warn "Bad lock file contents ($lockfile) -- still trying\n";
			}
			else {
				($pid, $user) = ($1, $2);
				if (kill( 0, $pid ) == 0 && $! == ESRCH) {
					# process doesn't exist anymore, remove stale lock
					warn "Removing stale lock file $lockfile ".
						 " (pid $pid, user $user)\n";
					unlink( $lockfile );
					goto repeat;
				}
			}
			++$try;
			if (!$for_srcdep && $try > $main::max_lock_trys) {
				warn "Lockfile $lockfile still present after ".
				     $main::max_lock_trys*$main::lock_interval.
					 " seconds -- giving up\n";
				return;
			}
			print PLOG "Another sbuild process ($pid by $user) is currently ",
					   "installing or\n",
					   "removing packages -- waiting...\n"
						   if $for_srcdep && $try == 1;
			sleep $main::lock_interval;
			goto repeat;
		}
		warn "Can't create lock file $lockfile: $!\n";
	}
	F->print("$$ $ENV{'LOGNAME'}\n");
	F->close();
}

sub unlock_file {
	my $file = shift;
	my $lockfile = "$file.lock";

	unlink( $lockfile );
}

sub check_dpkg_version {
	my $t = `$conf::dpkg --version`;
	my $version = ($t =~ /version\s+(\S+)/)[0];

	$main::new_dpkg = 1
		if 0 == system "$conf::dpkg --compare-versions '$version' ge 1.4.1.18";
}

sub binNMU_version {
	my $v = shift;

	if ($v =~ /^(.*)-([^-]+)$/) {
		my ($upstream, $debian) = ($1, $2);
		my @parts = split( /\./, $debian );
		if (@parts == 1) {
			return "$upstream-$debian.0.1";
		}
		elsif (@parts == 2) {
			return "$upstream-$debian.1";
		}
		else {
			$parts[$#parts]++;
			return "$upstream-".join( ".", @parts );
		}
	}
	else {
		return "$v.0.1";
	}
}

sub dist_gt {
	my ($d1, $d2) = @_;
	die "Unknown dist '$d1' in dist_gt\n" if !exists($main::dist_order{$d1});
	die "Unknown dist '$d2' in dist_gt\n" if !exists($main::dist_order{$d2});
	return $main::dist_order{$d1} > $main::dist_order{$d2};
}

sub shutdown {
	my $signame = shift;
	my($job,@npkgs,@pkgs);
	local( *F );

	$SIG{'INT'} = 'IGNORE';
	$SIG{'QUIT'} = 'IGNORE';
	$SIG{'TERM'} = 'IGNORE';
	$SIG{'ALRM'} = 'IGNORE';
	$SIG{'PIPE'} = 'IGNORE';
	print PLOG "sbuild received SIG$signame -- shutting down\n";
	chdir( $main::cwd );

	goto not_ni_shutdown if !$main::batchmode;
	
	# most important: dump out names of unfinished jobs to REDO
	foreach $job (@ARGV) {
		my $job2 = $job;
		fixup_pkgv( \$job2 );
		push( @npkgs, $job2 )
			if !$main::job_state{$job} || $job eq $main::current_job;
	}
	print LOG "The following jobs were not finished: @npkgs\n";

	my $f = "REDO";
	if (-f "REDO.lock") {
		# if lock file exists, write to a different file -- timing may
		# be critical
		$f = "REDO2";
	}
	if (open( F, "<$f" )) {
		@pkgs = <F>;
		close( F );
	}
	if (open( F, ">>$f" )) {
		foreach $job (@npkgs) {
			next if grep( /^\Q$job\E\s/, @pkgs );
			print F "$job $main::distribution\n";
		}
		close( F );
	}
	else {
		print "Cannot open $f: $!\n";
	}
	open( F, ">SBUILD-REDO-DUMPED" );
	close( F );
	print LOG "SBUILD-REDO-DUMPED created\n";
	unlink( "SBUILD-FINISHED" );

	# next: say which packages should be uninstalled
	@pkgs = keys %{$main::changes->{'installed'}};
	if (@pkgs) {
		if (open( F, ">>NEED-TO-UNINSTALL" )) {
			print F "@pkgs\n";
			close( F );
		}
		print "The following packages still need to be uninstalled ",
			  "(--purge):\n@pkgs\n";
	}
	
  not_ni_shutdown:
	# next: kill currently running command (if one)
	if ($main::sub_pid) {
		print "Killing $main::sub_task subprocess $main::sub_pid\n";
		system "$conf::sudo perl -e 'kill( -15, $main::sub_pid )'";
	}
	remove_srcdep_lock_file();

	# close logs and send mails
	fixup_pkgv( \$main::current_job );
	close_pkg_log( $main::current_job );
	close_log();
	unlink( $main::jobs_file ) if $main::batchmode;
	$? = 0; $! = 0;
	exit 0;
}

