#!/usr/bin/perl

# Copyright (C) 2009  Neil Williams <codehelp@debian.org>
#
# This package 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 3 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/>.

use IO::File;
use Config::Auto;
use File::Basename;
use Parse::Debian::Packages;
use strict;
use warnings;
use vars qw/ $host $mirror $suite $config_str $dir
 $dpkgdir $etcdir $sourcesname $ourversion $progname @files
 $msg @touch @dirs @source_list $vendor /;

$progname = basename($0);
$ourversion = "0.0.1";
$dir = "/var/lib/apt-grip/";
$mirror = "http://ftp.uk.debian.org/debian/";
$suite  = "unstable";
$etcdir = "etc/apt-grip/"; # sources
# use the real system status information.
# we'd only have to copy it otherwise.
$dpkgdir = "/var/lib/dpkg/";        # state

while( @ARGV ) {
	$_= shift( @ARGV );
	last if m/^--$/;
	if (!/^-/) {
		unshift(@ARGV,$_);
		last;
	}
	elsif (/^(-\?|-h|--help|--version)$/) {
	&usageversion();
		exit( 0 );
	}
	elsif (/^(-M|--mirror)$/) {
		$mirror = shift(@ARGV);
	}
	elsif (/^(-S|--suite)$/) {
		$suite = shift(@ARGV);
	}
	elsif (/^(-V|--vendor)$/) {
		$vendor = shift(@ARGV);
	}
	else {
		die "$progname: Unknown option $_.\n";
	}
}
@files = @ARGV;

if (scalar @files == 0)
{
	warn ("ERROR: Please specify some packages for $progname to convert.\n");
	&usageversion;
	exit (0);
}

$host = `dpkg-architecture -qDEB_BUILD_ARCH`;
chomp ($host);

my $chk = system ("dpkg-vendor --is Debian");
$chk /= 256;
if ($chk == 0)
{
	warn ("ERROR: Using $progname on Debian will cause problems.\n");
	die ("See apt-grip (1) for more information.\n");
}

die ("ERROR: $progname: misconfiguration, '$dir' missing.\n")
	if (not -d "$dir");
system ("mkdir -p ${dir}${etcdir}sources.list.d/");

$sourcesname = "sources.list.d/apt-grip.sources.list";
unlink ("${dir}${etcdir}${sourcesname}")
	if (-f "${dir}${etcdir}${sourcesname}");

mkdir "$dir/lists" if (not -e "$dir/lists");
mkdir "$dir/lists/partial" if (not -e "$dir/lists/partial");
mkdir "$dir/archives" if (not -e "$dir/archives");
mkdir "$dir/output" if (not -e "$dir/output");
mkdir "$dir/archives/partial" if (not -e "$dir/archives/partial");
@dirs = qw/ alternatives info parts updates/;
@touch = qw/ available diversions statoverride status lock/;

&prepare_sources_list;
if (defined $mirror)
{
	push @source_list, "deb $mirror $suite main\n";
}
unlink "${dir}${etcdir}${sourcesname}";
open (SOURCES, ">${dir}${etcdir}${sourcesname}") 
	or die "Cannot open sources list $!";
foreach my $line (@source_list)
{
	chomp ($line);
	next if ($line =~ /^#/);
	next if ($line =~ /^$/);
	print SOURCES "$line\n";
}
close SOURCES;

$config_str = '';
$config_str .= " -o Apt::Get::Download-Only=true";
$config_str .= " -o Apt::Install-Recommends=false";
$config_str .= " -y --reinstall -o Dir=$dir";
$config_str .= " -o Dir::Etc=${dir}${etcdir}";
$sourcesname = "sources.list.d/apt-grip.sources.list";
$config_str .= " -o Dir::Etc::SourceList=${dir}${etcdir}$sourcesname";
$config_str .= " -o Dir::State=${dir}";
$config_str .= " -o Dir::State::Status=${dpkgdir}status";
$config_str .= " -o Dir::Cache=${dir}";

# bug - keeps complaining of a duplicate source list.
system ("apt-get $config_str update 2>/dev/null");
my $str = join (' ', @files);
system ("apt-get $config_str install $str");

opendir (DEBS, "${dir}archives/") or die ("Cannot read ${dir}archives/ : $!\n");
my @list = grep(/\.deb$/, readdir DEBS);
closedir (DEBS);
$vendor = $ENV{"DEB_VENDOR"}
	if (defined $ENV{"DEB_VENDOR"} and (not defined $vendor));
$vendor = "emdebian-grip" if (not defined $vendor);
foreach my $pkg (@list)
{
	system ("emgrip --vendor $vendor -o ${dir}output/ ${dir}archives/$pkg");
	unlink ("${dir}archives/$pkg");
}
system ("dpkg -i ${dir}output/*.deb");
system ("rm -rf ${dir}*");

sub prepare_sources_list
{
	# collate all available/configured sources into one list
	if (-e "/etc/apt/sources.list") {
		open (SOURCES, "/etc/apt/sources.list") or die "cannot open apt sources list. $!";
		@source_list = <SOURCES>;
		close (SOURCES);
	}
	if (-d "/etc/apt/sources.list.d/") {
		opendir (FILES, "/etc/apt/sources.list.d/")
			|| die "cannot open apt sources.list directory $!";
		my @files = grep(!/^\.\.?$/, readdir FILES);
		foreach my $f (@files) {
			next if ($f =~ /\.ucf-old$/);
			open (SOURCES, "/etc/apt/sources.list.d/$f") or
				die "cannot open /etc/apt/sources.list.d/$f $!";
			while(<SOURCES>) {
				push @source_list, $_;
			}
			close (SOURCES);
		}
		closedir (FILES);
	}
}

sub usageversion {
	print(STDERR <<END)
$progname version $ourversion

Usage:
 $progname [-M|--mirror] [-V|--vendor] [-S|--suite] PACKAGES ...
 $progname -?|-h|--help|--version

Options:
 -M|--mirror:         A Debian mirror with the requested package(s)
 -S|--suite:          Which Debian suite to use for the package(s)
 -V|--vendor:         Alternative to setting DEB_VENDOR

Emdebian Grip has a limited number of packages in the main repository,
principally to reduce the size of the apt cache data. On occassion,
individual packages from standard Debian need to be added to a single
machine running Emdebian Grip. apt-grip has been written with that
purpose in mind.

apt-grip requires DEB_VENDOR support in dpkg to determine how the
package should be gripped. In addition, if the default vendor is
not Emdebian, apt-grip will do nothing. To set this up, change the
default symlink in /etc/dpkg/origins to point to emdebian-grip or
set DEB_VENDOR for each call to apt-grip. Before you do this, read
the section in the apt-grip manpage on the limitations.

The usual case is that the system has already been upgraded to Emdebian
Grip before F<apt-grip> is used.

apt-grip tidies up after itself by removing all temporary data and
packages after installation.

END
}

=pod

=head1 NAME

apt-grip - extend Emdebian Grip to add Debian packages on-the-fly

=head1 Synopsis

 $ sudo apt-grip foo bar baz

 $ sudo apt-grip -M http://ftp.fr.debian.org/debian/ foo bar baz
 
=head1 Description

Emdebian Grip has a limited number of packages in the main repository,
principally to reduce the size of the apt cache data. On occassion,
individual packages from standard Debian need to be added to a single
machine running Emdebian Grip. F<apt-grip> has been written with that
purpose in mind.

F<apt-grip> requires DEB_VENDOR support in dpkg to determine how the
package should be C<gripped>. In addition, if the default vendor is
not Emdebian, C<apt-grip> will do nothing. To set this up, change the
F<default> symlink in F</etc/dpkg/origins> to point to
F<emdebian-grip> or set DEB_VENDOR for each call to F<apt-grip>. Before
you do this, read the next section on the limitations.

The usual case is that the system has already been upgraded to Emdebian
Grip before F<apt-grip> is used.

Contact the debian-embedded@lists.debian.org mailing list for requests
to add packages to Emdebian Grip repositories directly.

=head1 Limitations

Installing any package from repositories outside the normal apt sources
(especially if those packages are subsequently modified as in emgrip)
will list those packages as "local or obsolete" in package managers and
such packages cannot be upgraded without repeating the call to C<apt-grip>.

C<apt-grip> cannot currently track these packages - although it might
become possible in the future.

The net result is that using C<apt-grip> could make it difficult to
upgrade the rest of the system. If C<apt-get> complains about broken
dependencies (or more commonly, simply removes your extra packages)
use C<apt-grip> to reinstall or upgrade the relevant packages.

C<Gripping> a package means making a new version (with the em1 version
suffix) with less files in the new package. See emgrip (1) for detailed
information on that process. Changing the version string means that some
dependencies need to be changed - particularly strict dependencies. This
means that F<apt-grip> could fail with some combinations of packages.

C<apt-grip> uses the C<--reinstall> option to F<apt-get> to cope with
some of these situations.

=head1 Strict dependency issues

If a source package builds multiple binary packages, where at least one
package has a strict dependency on one of the other binary packages and
one of those binary packages is already installed from Debian,
it will be necessary to install Grip versions of both the binary
packages involved so that the strict dependency can be satisfied.

 Source: foo

 Package: foo
 Depends: bar (= 0.0.1)

 Package: bar

 $ sudo apt-grip foo bar

In the example above, F<foo_0.0.1_amd64.deb> will become 
F<foo_0.0.1em1_amd64.deb> and will be given a strict dependency on
C<bar (= 0.0.1em1)> by F<emgrip>.

=head1 Default mirror

F<apt-grip> uses C<http://ftp.uk.debian.org/debian/> as the default
Debian mirror. Use the -M|--mirrror option to change it.

=head1 Upgrading to Grip

Change your sources list from debian mirrors to the emdebian grip
mirror.

 deb http://www.emdebian.org/grip/ $suite main

(Emdebian Grip only supports Debian suites: unstable, sid, testing,
squeeze, stable, lenny.)

Then update and upgrade:

 $ sudo apt-get update
 $ sudo apt-get upgrade
 $ sudo apt-get dist-upgrade

(Note that F<apt-get> will usually report the ability to free several
hundred megabytes on your system when Debian packages are upgraded
to Emdebian Grip.)

=cut
