#!/usr/bin/perl -w
#
# dh_installkpatches $Revision: 1.26 $
#
# Reads debian/$package.kpatches[.foo], installs all files necessary
# to have make-kpkg use the kernel patches described in there.
#
# (c) 2000-2002 Yann Dirson <dirson@debian.org>
# Some parts based on code from Adam Di Carlo and Joey Hess

use strict;
use Debian::Debhelper::Dh_Lib;
init();

my $pkgdeps = "bash (>= 2.0), patch";

my $tmpl_dir = '/usr/share/debhelper/dh-kpatches';

# This function slightly adapted from code in
# Adam Di Carlo's install-docs
sub read_control_file_section {
  my ($pfields) = @_;

  my $empty = 1;
  my ($cf,$v);
  while (<IN>) {
    chomp;

    # empty line?
    if (/^\s*$/o) {
      if ($empty) {
        next;
      } else {
        last;
      }
    }

    $empty = 0;

    # new field?
    if (/^(\S+)\s*:\s*(.*)$/) {
      ($cf,$v) = (lc $1,$2);
      #print STDERR "$cf -> $v\n";
      if (exists $pfields->{$cf}) {
        warn "warning: $cf: overwriting previous setting of control field";
      }
      $pfields->{$cf} = $v;
    } elsif (/^\s+(\S.*)$/) {
      $v = $1;
      defined($cf) or die "syntax error in control file: no field specified";
      #print STDERR "$cf -> $v (continued)\n";
      $pfields->{$cf} .= "\n$v";
    } else {
      die "syntax error in control file: $_";
    }
  }

  return not $empty;
}

sub read_control_file {
  my ($file) = @_;

  my %patchinfo = ('general' => {},
		   'files' => []);

  next PACKAGE unless open (IN, $file);
  read_control_file_section ($patchinfo{general});
  my $cf = {};
  while (read_control_file_section ($cf)) {
    push @{$patchinfo{files}}, $cf;
    $cf = {};
  }
  close IN;

  validate_control_data (\%patchinfo);
  return %patchinfo;
}

sub validate_control_data {
  my ($patchinfo) = @_;

  die "Patch-Id can only contain alphanumerics, hyphens, and underscores"
    if $patchinfo->{general}->{'patch-id'} =~ /[^\w-]/;
}

my $FIELD_MANDATORY = 0;
my $FIELD_INHERITS = 1;
my $FIELD_OPTIONAL = 2;
# Compute a field's value, according to given rules for inheritance
# and defaulting.  $inherits in $FIELD_*.  $default = something or undef.
sub field {
  my ($general, $patch, $name, $inherits, $default) = @_;

  my $value = $patch->{$name};
  if ($inherits == $FIELD_INHERITS) {
    $value = $general->{$name} unless defined $value;
  } elsif ($inherits == $FIELD_MANDATORY) {
    die "Patchfile info lacks $name field" unless defined $value;
  }
  if (defined $default) {
    $value = $default unless defined $value;
  }

  return $value;
}

# records a field value for a given patchfile on a given arch
sub record_patchfile_field {
  my ($hash, $arch, $value) = @_;

  if (defined $hash->{$arch}) {
    $hash->{$arch} .= " $value";
  } else {
    $hash->{$arch} = "$value";
  }
}


PACKAGE: foreach my $package (@{$dh{DOPACKAGES}}) {
  my $tmp = tmpdir($package);
  my $ext = pkgext($package);

  # There are two filename formats, the usual
  # plus an extended format (debian/package.*).

  opendir(DEB,"debian/") || error("can't read debian directory: $!");
  # If this is the main package, we need to handle unprefixed filenames.
  # For all packages, we must support both the usual filename format plus
  # that format with a period an something appended.
  my $regexp="\Q$package\E\.";
  if ($package eq $dh{MAINPACKAGE}) {
    $regexp="(|$regexp)";
  }
  my @files = grep { /^${regexp}kpatches(\..*)?$/ } readdir(DEB);
  closedir(DEB);

  # next package if there are no patches in there
  next PACKAGE if $#files < 0;

  foreach my $file (@files) {
    my %patchinfo = read_control_file ("debian/$file");

    #   use Data::Dumper;
    #   print Dumper (%patchinfo);

    my $patchid = $patchinfo{general}->{'patch-id'};

    # transformation of the ID to be acceptable as part of an envvar's name
    $patchinfo{general}->{'clean-patch-id'} = $patchinfo{general}->{'patch-id'};
    $patchinfo{general}->{'clean-patch-id'} =~ s/-/_/g;

    # protect pipes and dquotes for sed command-line
    $patchinfo{general}->{'patch-name'} =~ s,([|\"]),\\$1,g;

    my %kversions=();
    my %patchfiles=();
    my %striplevels=();
    my %depends=();
    my @archs=();

    # put the right files in the right places
    foreach my $patchentry (@{$patchinfo{files}}) {

      ### field extraction

      my $arch = field ($patchinfo{general}, $patchentry,
			'architecture', $FIELD_INHERITS, 'all');
      push @archs, $arch unless grep { $_ eq $arch } @archs;

      ##

      my $striplevel = field ($patchinfo{general}, $patchentry,
			      'path-strip-level', $FIELD_INHERITS, 1);

      ##

      my $kversion = field ($patchinfo{general}, $patchentry,
			    'kernel-version', $FIELD_MANDATORY);
      {
	# parse "2.4.5 - 2.4.7" and "2.5.4 -" syntaxes

	my @kv = split (/\s+/, $kversion);
	if ($#kv > 0) {
	  # FIXME: validity check is really too strict, but we need a
	  # good kversion comparison algorithm to attempt any better
	  # (ie. "-pre" and "-test" at least are special)
	  $kv[0] =~ m/^(\d+\.\d+)\.(\d+)$/ or die "Malformed kernel version: `$kv[0]'";

	  my ($branch, $first) = ($1, $2);
	  die "Malformed kernel-version range \`$kversion'"
	    unless ($kv[1] eq '-') && ($#kv <= 2);
	  if ($#kv == 1) {
	    die "Unbounded ranges not supported yet: \`$kversion'";
	    $kversion = [ $branch, $first ];
	  } else {
	    $kv[2] =~ m/^(\d+\.\d+)\.(\d+)$/ or die "Malformed kernel version: `$kv[2]'";
	    die "Cross-branch ranges are not allowed: `$kversion'"
	      unless $1 == $branch;
	    die "Reverse-ordered range: `$kversion'" if $2 < $first;
	    $kversion = [ $branch, $first, $2 ];
	  }
	} else {
	  $kv[0] =~ m/^(\d+\.\d+)\.(\d+)/ or die "Malformed kernel version: `$kv[0]'";
	}
      }

      ##

      my $patchfile = field ($patchinfo{general}, $patchentry,
			     'patch-file', $FIELD_MANDATORY);
      # recording of file name delayed until installed and maybe compressed

      ##

      my $depends = field ($patchinfo{general}, $patchentry,
			   'depends', $FIELD_OPTIONAL, "");
      $depends =~ s/, */ /g;
      # We must quote the content of each dep sequence, so that the
      # lists don't get merged when concatenated

      ### install the patch file

      my $pdir = "/usr/src/kernel-patches/$arch";
      my $srcdir = "$pdir/$patchid";
      doit ("mkdir",  "-p", "$tmp$srcdir") unless -d "$tmp$srcdir";
      my $instpatchfile = "$srcdir/" . basename($patchfile);
      doit ("cp", $patchfile, "$tmp$instpatchfile");
      doit ("gzip", "-9f", "$tmp$instpatchfile");
      $instpatchfile = "$instpatchfile.gz" if -r "$tmp$instpatchfile.gz";


      # record the patch entry, duplicating it when using version ranges

      if ((ref $kversion) eq 'ARRAY') {
	for (my $v = $kversion->[1]; $v <= $kversion->[2]; $v++) {
	  record_patchfile_field (\%kversions, $arch, $kversion->[0] . '.' . $v);
	  record_patchfile_field (\%striplevels, $arch, $striplevel);
	  record_patchfile_field (\%depends, $arch, '"' . $depends . '"');
	  record_patchfile_field (\%patchfiles, $arch, $instpatchfile);
	}
      } else {
	record_patchfile_field (\%kversions, $arch, $kversion);
	record_patchfile_field (\%striplevels, $arch, $striplevel);
	record_patchfile_field (\%depends, $arch, '"' . $depends . '"');
	record_patchfile_field (\%patchfiles, $arch, $instpatchfile);
      }
    }

    #
    # generation of apply/unpatch scripts
    #

    foreach my $arch (@archs) {
      my $pdir = "/usr/src/kernel-patches/$arch";
      foreach my $script ('apply', 'unpatch') {
	doit ("mkdir", "-p", "$tmp$pdir/$script");
	complex_doit ("sed <$tmpl_dir/$script.tmpl >$tmp$pdir/$script/$patchid" .
		      ' -e \'s/#PATCHID#/' . $patchinfo{general}->{'patch-id'} . '/g\'' .
		      ' -e \'s/#CLEANPATCHID#/' . $patchinfo{general}->{'clean-patch-id'} . '/g\'' .
		      ' -e \'s|#PATCHNAME#|"' . $patchinfo{general}->{'patch-name'} . '"|g\'' .
		      " -e 's/#DEPENDS#/$depends{$arch}/g'" .
		      " -e 's/#KVERSIONS#/$kversions{$arch}/g'" .
		      " -e 's|#PATCHFILES#|$patchfiles{$arch}|g'" .
		      " -e 's/#PATCHARCH#/$arch/g'" .
		      " -e 's/#STRIPLEVELS#/$striplevels{$arch}/g'"
		     );
	doit ("chmod", "0755", "$tmp$pdir/$script/$patchid");

	doit ("mkdir", "-p", "$tmp/usr/share/doc/$package");
	doit ("cp",
	      "$tmpl_dir/README-kernelpatch.Debian",
	      "$tmp/usr/share/doc/$package/");
      }
    }
  }

  #
  # kpatch:Depends substvar
  # (modified from similar functionnality in dh_perl v3.4.1

  # For idempotency, remove anything this program might have
  # previously added to the substvars file.
  if (-e "debian/${ext}substvars") {
    complex_doit("grep -v ^kpatch:Depends= debian/${ext}substvars > debian/${ext}substvars.new || true");
    doit("mv", "debian/${ext}substvars.new","debian/${ext}substvars");
  }

  complex_doit("echo 'kpatch:Depends=$pkgdeps' >> debian/${ext}substvars");
}
