#!/usr/bin/perl

=head1 NAME

dh_66 - install 66 service registration into package maintainer scripts

=cut

use strict;
use warnings;
use Debian::Debhelper::Dh_Lib;

our $VERSION = '1.0.0';

=head1 SYNOPSIS

B<dh_66> [S<I<debhelper options>>] [B<-n>] [S<I<service options>> ...]

=head1 DESCRIPTION

B<dh_66> registers 66 services by enabling them in a 66 I<tree> and applying
live state changes through L<66(1)> in package maintainer scripts.

It is intended to be used with B<dh --with 66>.  In that mode,
L<dh_installinit(1)> still installs F<debian/I<package>.init> (and related
files) into F</etc/init.d/>, but does B<not> emit sysvinit
L<update-rc.d(8)> snippets.  B<dh_66> supplies the 66 equivalents
according to F<debian/I<package>.66>.

=head1 FILES

=over 4

=item debian/I<package>.66

Lists 66 services to register.  Each non-comment line has the form:

  I<service> [I<options>]

where I<options> is a comma-separated list (same style as L<dh_runit(1)>
and L<dh_s6(1)>).

If this file is absent, B<dh_66> does nothing for that package.

Frontend files are expected under F</usr/share/66/service/> (package)
and/or F</etc/66/service/> (sysadmin).  Enabling parses the frontend via
B<66 enable>.

=back

=head1 OPTIONS IN debian/I<package>.66

=over 4

=item B<defaults>

Explicit no-op placeholder.

=item B<tree=>I<tree>

66 tree that receives the service (default: B<global>).
B<bundle=>I<name> is accepted as a synonym for migration from dh-s6.

=item B<name=>I<name>

66 service name.  Defaults to the first field on the line.

=item B<disable>

Do not enable the service in the tree.

=item B<nostart>

Only manage tree membership (B<66 enable> / B<66 disable>); do not
start/stop/restart.

=item B<onupgrade=>B<restart>|B<stop>|B<nostop>

Upgrade policy (default B<restart>):

=over 4

=item B<restart>

Restart (or start on fresh install) after upgrade; do not stop in
F<preinst> during upgrade (like B<dh_installinit --restart-after-upgrade>).

=item B<stop>

Stop before upgrade and start after (classic prerm/postinst).

=item B<nostop>

Do not stop or restart on upgrade; still stop on remove
unless B<onremove=nostop> is also set.

=back

=item B<onremove=>B<stop>|B<nostop>

Remove policy (default B<stop>):

=over 4

=item B<stop>

Stop the service in F<prerm> on remove.

=item B<nostop>

Do not stop in F<prerm> on remove.  Useful for display managers so
the session stays up until dpkg finishes.

=back

=item B<noscripts>

Do not modify maintainer scripts for this entry.

=back

=head1 EXAMPLES

  # debian/rsyslog.66
  rsyslog tree=global

  # debian/openssh-server.66
  sshd

  # debian/netbase.66
  networking tree=boot,nostart

  # debian/foo.66
  foo defaults
  foo-extra name=foo-extra,tree=global,disable

=head1 OPTIONS

=over 4

=item B<-n>, B<--no-scripts>

Do not modify maintainer scripts (global).

=back

=cut

init(options => {});

sub parse_options {
	my ($opts) = @_;
	my $conf = {
		enable      => 1,
		nostart     => 0,
		noscripts   => 0,
		onupgrade   => 'restart',
		onremove    => 'stop',
		tree        => 'global',
	};
	$opts //= '';
	$opts =~ s/^\s+|\s+$//g;
	return $conf if $opts eq '';

	for my $opt (split(/,/, $opts)) {
		$opt =~ s/^\s+|\s+$//g;
		next if $opt eq '';
		if ($opt eq 'defaults') {
			# no-op
		} elsif ($opt eq 'disable') {
			$conf->{enable} = 0;
		} elsif ($opt eq 'nostart') {
			$conf->{nostart} = 1;
		} elsif ($opt eq 'noscripts') {
			$conf->{noscripts} = 1;
		} elsif ($opt =~ /^name=(.+)$/) {
			$conf->{name} = $1;
		} elsif ($opt =~ /^tree=(.+)$/) {
			$conf->{tree} = $1;
		} elsif ($opt =~ /^bundle=(.+)$/) {
			# synonym for dh-s6 migration
			$conf->{tree} = $1;
		} elsif ($opt =~ /^onupgrade=(.+)$/) {
			my $v = $1;
			error("unknown onupgrade value `$v' (want restart|stop|nostop)")
				unless $v =~ /^(?:restart|stop|nostop)$/;
			$conf->{onupgrade} = $v;
		} elsif ($opt =~ /^onremove=(.+)$/) {
			my $v = $1;
			error("unknown onremove value `$v' (want stop|nostop)")
				unless $v =~ /^(?:stop|nostop)$/;
			$conf->{onremove} = $v;
		} else {
			error("unknown option `$opt' in 66 config");
		}
	}
	return $conf;
}

my %snippet_options = ('snippet-order' => 'service');

foreach my $package (@{$dh{DOPACKAGES}}) {
	next if is_udeb($package);

	my $tmp = tmpdir($package);
	my $conffiles = "$tmp/DEBIAN/conffiles";

	# Second pass (after dh_installdeb): drop /etc/66/service and conf
	# from conffiles so frontends disappear on remove, not only on purge.
	if (-f $conffiles) {
		open(my $in, '<', $conffiles) or error("cannot read $conffiles: $!");
		my @keep;
		my $changed = 0;
		while (my $line = <$in>) {
			chomp $line;
			next unless length $line;
			if ($line =~ m{^/etc/66/(?:service|conf|seed)(?:/|$)}) {
				$changed = 1;
				next;
			}
			push @keep, $line;
		}
		close $in;
		if ($changed) {
			if (@keep) {
				open(my $out, '>', $conffiles) or error("cannot write $conffiles: $!");
				print {$out} map { "$_\n" } @keep;
				close $out;
			} else {
				doit('rm', '-f', $conffiles);
			}
			verbose_print("removed /etc/66/ service entries from $package conffiles");
		}
		next;
	}

	my $pkgfile = pkgfile($package, '66');
	next unless $pkgfile;

	my @entries = filedoublearray($pkgfile);
	next unless @entries;

	my $added_scripts = 0;

	for my $entry (@entries) {
		my ($svc_field, $opts) = @$entry;
		error("empty service name in $pkgfile") unless defined $svc_field && length $svc_field;
		if ($svc_field =~ /,/) {
			error("$pkgfile: '$svc_field' looks like options, not a service name (want: NAME [options])");
		}
		if ($svc_field =~ /^(?:defaults|disable|nostart|noscripts)$/) {
			error("$pkgfile: '$svc_field' is an option, not a service name (want: NAME [options])");
		}

		my $conf = parse_options($opts // '');
		my $script = $conf->{name} // $svc_field;

		my $frontend = "$tmp/usr/share/66/service/$script";
		my $frontend_etc = "$tmp/etc/66/service/$script";
		my $init_path = "$tmp/etc/init.d/$script";
		warning("$package: neither $frontend nor $frontend_etc found in staging; 66 snippets will still be generated")
			unless (-e $frontend || -e $frontend_etc || -e $init_path);

		next if $conf->{noscripts} || $dh{NOSCRIPTS};

		my $tree = $conf->{enable} ? $conf->{tree} : '';
		my $replace = {
			'SCRIPT'         => $script,
			'TREE'           => $tree,
			'ERROR_HANDLER'  => $dh{ERROR_HANDLER} // 'exit 1',
		};

		autoscript($package, 'preinst', 'preinst-66-chmod', $replace, \%snippet_options);

		if (!$conf->{nostart}) {
			if ($conf->{onupgrade} eq 'restart') {
				autoscript($package, 'postinst', 'postinst-66-restart', $replace, \%snippet_options);
			} elsif ($conf->{onupgrade} eq 'stop') {
				autoscript($package, 'postinst', 'postinst-66', $replace, \%snippet_options);
				autoscript($package, 'preinst', 'preinst-66-stop', $replace, \%snippet_options);
			} else {  # nostop
				autoscript($package, 'postinst', 'postinst-66', $replace, \%snippet_options);
			}
			if ($conf->{onremove} eq 'stop') {
				autoscript($package, 'prerm', 'prerm-66-norestart', $replace, \%snippet_options);
			}
		} else {
			autoscript($package, 'postinst', 'postinst-66-nostart', $replace, \%snippet_options);
		}

		autoscript($package, 'postrm', 'postrm-66',
			{ 'SCRIPT' => $script, 'ERROR_HANDLER' => $dh{ERROR_HANDLER} // 'exit 1' },
			\%snippet_options);

		$added_scripts = 1;
	}

	if ($added_scripts) {
		addsubstvar($package, 'misc:Depends', '66-helper', '>= 1.0.0~');
	}
}

=head1 SEE ALSO

L<debhelper(7)>, L<dh_installinit(1)>, L<66(1)>, L<dh_s6(1)>,
L<dh_openrc(1)>, L<dh_runit(1)>

=head1 AUTHOR

66 packaging helpers for Genuen-style systems.

=cut
