From 1d6d8671262dc6b2a4e02e4a1878d91b86647054 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 06:42:34 +0200 Subject: Adding upstream version 1.7. Signed-off-by: Daniel Baumann --- Makefile | 9 + README.md | 25 ++ autoscripts/postinst-nss | 6 + autoscripts/postinst-nss-db | 5 + autoscripts/postrm-nss | 8 + autoscripts/postrm-nss-db | 6 + autoscripts/preinst-nss | 4 + dh_installnss | 459 ++++++++++++++++++++ installnss.pm | 7 + t/Test/DH.pm | 205 +++++++++ t/dh_installnss/debian/changelog | 5 + t/dh_installnss/debian/compat | 1 + t/dh_installnss/debian/control | 89 ++++ t/dh_installnss/debian/foo-after1.nss | 1 + t/dh_installnss/debian/foo-after2.nss | 1 + t/dh_installnss/debian/foo-before1.nss | 1 + t/dh_installnss/debian/foo-before2.nss | 1 + t/dh_installnss/debian/foo-empty1.nss | 0 t/dh_installnss/debian/foo-empty2.nss | 3 + t/dh_installnss/debian/foo-example.nss | 3 + t/dh_installnss/debian/foo-first1.nss | 2 + t/dh_installnss/debian/foo-first2.nss | 1 + t/dh_installnss/debian/foo-first3.nss | 2 + t/dh_installnss/debian/foo-last1.nss | 1 + t/dh_installnss/debian/foo-last2.nss | 1 + t/dh_installnss/debian/foo-last3.nss | 2 + t/dh_installnss/debian/foo-multidb1.nss | 4 + t/dh_installnss/debian/foo-newdb1.nss | 1 + t/dh_installnss/debian/foo-newdb2.nss | 3 + t/dh_installnss/debian/foo-newdb3.nss | 3 + t/dh_installnss/debian/foo-other.nss | 1 + t/dh_installnss/debian/foo-remove-only1.nss | 2 + t/dh_installnss/debian/foo-skip.nss | 2 + t/dh_installnss/debian/foo-substring.nss | 2 + t/dh_installnss/dh_installnss.t | 652 ++++++++++++++++++++++++++++ t/dh_installnss/nsswitch.conf.template | 17 + 36 files changed, 1535 insertions(+) create mode 100644 Makefile create mode 100644 README.md create mode 100644 autoscripts/postinst-nss create mode 100644 autoscripts/postinst-nss-db create mode 100644 autoscripts/postrm-nss create mode 100644 autoscripts/postrm-nss-db create mode 100644 autoscripts/preinst-nss create mode 100755 dh_installnss create mode 100644 installnss.pm create mode 100644 t/Test/DH.pm create mode 100644 t/dh_installnss/debian/changelog create mode 100644 t/dh_installnss/debian/compat create mode 100644 t/dh_installnss/debian/control create mode 100644 t/dh_installnss/debian/foo-after1.nss create mode 100644 t/dh_installnss/debian/foo-after2.nss create mode 100644 t/dh_installnss/debian/foo-before1.nss create mode 100644 t/dh_installnss/debian/foo-before2.nss create mode 100644 t/dh_installnss/debian/foo-empty1.nss create mode 100644 t/dh_installnss/debian/foo-empty2.nss create mode 100644 t/dh_installnss/debian/foo-example.nss create mode 100644 t/dh_installnss/debian/foo-first1.nss create mode 100644 t/dh_installnss/debian/foo-first2.nss create mode 100644 t/dh_installnss/debian/foo-first3.nss create mode 100644 t/dh_installnss/debian/foo-last1.nss create mode 100644 t/dh_installnss/debian/foo-last2.nss create mode 100644 t/dh_installnss/debian/foo-last3.nss create mode 100644 t/dh_installnss/debian/foo-multidb1.nss create mode 100644 t/dh_installnss/debian/foo-newdb1.nss create mode 100644 t/dh_installnss/debian/foo-newdb2.nss create mode 100644 t/dh_installnss/debian/foo-newdb3.nss create mode 100644 t/dh_installnss/debian/foo-other.nss create mode 100644 t/dh_installnss/debian/foo-remove-only1.nss create mode 100644 t/dh_installnss/debian/foo-skip.nss create mode 100644 t/dh_installnss/debian/foo-substring.nss create mode 100755 t/dh_installnss/dh_installnss.t create mode 100644 t/dh_installnss/nsswitch.conf.template diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f7d8e14 --- /dev/null +++ b/Makefile @@ -0,0 +1,9 @@ +all: dh_installnss.1 + +dh_installnss.1: dh_installnss + pod2man --utf8 $< > $@ + +update-version: + sed -E -i dh_installnss -e '/^our \$$VERSION = .*;$$/ s/= .*;$$/= "'"$$(dpkg-parsechangelog -S Version)"'";/' + +.PHONY: all update-version diff --git a/README.md b/README.md new file mode 100644 index 0000000..229841a --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# `dh_installnss` - enable NSS services + +`dh_installnss` is a debhelper program that is responsible for injecting +NSS (Name Service Switch) services into `/etc/nsswitch.conf`. + +## Example + +An example `debian/nss` file could look like this: + + hosts before=dns mdns4 + hosts before=mdns4 mdns4_minimal [NOTFOUND=return] + hosts remove-only mdns # In case the user manually added it + +After the installation of this package, the original configuration of +the `hosts` database in `/etc/nsswitch.conf` will change from: + + hosts: files dns + +to: + + hosts: files mdns4_minimal [NOTFOUND=return] mdns4 dns + +## Documentation + +Detailed documentation can be found in `dh_installnss(1)`. diff --git a/autoscripts/postinst-nss b/autoscripts/postinst-nss new file mode 100644 index 0000000..9f67154 --- /dev/null +++ b/autoscripts/postinst-nss @@ -0,0 +1,6 @@ +if [ "$1" = "configure" ] && [ -f "${DPKG_ROOT}/etc/nsswitch.conf.nss.${DPKG_MAINTSCRIPT_PACKAGE}-will-install" ] && [ -e "${DPKG_ROOT}/etc/nsswitch.conf" ] ; then + if ! grep -q -E #SERVICE_PATTERNS# "${DPKG_ROOT}/etc/nsswitch.conf" ; then + #OPERATIONS# + fi + rm "${DPKG_ROOT}/etc/nsswitch.conf.nss.${DPKG_MAINTSCRIPT_PACKAGE}-will-install" +fi diff --git a/autoscripts/postinst-nss-db b/autoscripts/postinst-nss-db new file mode 100644 index 0000000..3814202 --- /dev/null +++ b/autoscripts/postinst-nss-db @@ -0,0 +1,5 @@ +if [ "$1" = "configure" ] && [ -e "${DPKG_ROOT}/etc/nsswitch.conf" ] ; then + if ! grep -q -E '^#DB#:' "${DPKG_ROOT}/etc/nsswitch.conf" ; then + echo "#DB#: " >> "${DPKG_ROOT}/etc/nsswitch.conf" + fi +fi diff --git a/autoscripts/postrm-nss b/autoscripts/postrm-nss new file mode 100644 index 0000000..da94344 --- /dev/null +++ b/autoscripts/postrm-nss @@ -0,0 +1,8 @@ +: "${DPKG_MAINTSCRIPT_PACKAGE_INSTCOUNT:=$(dpkg-query -f '${db:Status-Abbrev}\n' -W "$DPKG_MAINTSCRIPT_PACKAGE" | grep -c '^i')}" +if { [ "$1" = "remove" ] || [ "$1" = "purge" ] ; } && [ -e "${DPKG_ROOT}/etc/nsswitch.conf" ] && [ "$DPKG_MAINTSCRIPT_PACKAGE_INSTCOUNT" -eq 0 ] ; then + sed -E -i "${DPKG_ROOT}/etc/nsswitch.conf" \ + -e ':a /^#DB#:\s.*#/ s/(^[^#]+)\s(#SERVICE_NAMES#)\s+\[!?[A-Za-z]+=[A-Za-z]+\](\s|#)/\1\3/g ; t a' \ + -e ':b /^#DB#:\s.*#/ s/(^[^#]+)\s(#SERVICE_NAMES#)(\s|#)/\1\3/g ; t b' \ + -e ':c /^#DB#:\s[^#]*$/ s/\s(#SERVICE_NAMES#)\s+\[!?[A-Za-z]+=[A-Za-z]+\](\s|$)/\2/g ; t c' \ + -e ':d /^#DB#:\s[^#]*$/ s/\s(#SERVICE_NAMES#)(\s|$)/\2/g ; t d' +fi diff --git a/autoscripts/postrm-nss-db b/autoscripts/postrm-nss-db new file mode 100644 index 0000000..c5a9e70 --- /dev/null +++ b/autoscripts/postrm-nss-db @@ -0,0 +1,6 @@ +: "${DPKG_MAINTSCRIPT_PACKAGE_INSTCOUNT:=$(dpkg-query -f '${db:Status-Abbrev}\n' -W "$DPKG_MAINTSCRIPT_PACKAGE" | grep -c '^i')}" +if { [ "$1" = "remove" ] || [ "$1" = "purge" ] ; } && [ -e "${DPKG_ROOT}/etc/nsswitch.conf" ] && [ "$DPKG_MAINTSCRIPT_PACKAGE_INSTCOUNT" -eq 0 ] ; then + if grep -q -E '^#DB#:\s*$' "${DPKG_ROOT}/etc/nsswitch.conf" ; then + sed -E -i "${DPKG_ROOT}/etc/nsswitch.conf" -e '/^#DB#:/d' + fi +fi diff --git a/autoscripts/preinst-nss b/autoscripts/preinst-nss new file mode 100644 index 0000000..a21bc7a --- /dev/null +++ b/autoscripts/preinst-nss @@ -0,0 +1,4 @@ +if [ "$1" = install ]; then + # Signal to postinst that the NSS services should be installed, even if the package has been removed but not purged. + touch "${DPKG_ROOT}/etc/nsswitch.conf.nss.${DPKG_MAINTSCRIPT_PACKAGE}-will-install" +fi diff --git a/dh_installnss b/dh_installnss new file mode 100755 index 0000000..6ae712a --- /dev/null +++ b/dh_installnss @@ -0,0 +1,459 @@ +#!/usr/bin/perl + +=head1 NAME + +dh_installnss - enable NSS services + +=cut + +use strict; +use warnings; +use Debian::Debhelper::Dh_Lib; + +our $VERSION = "1.7"; + +=head1 SYNOPSIS + +B [S>] + +=head1 DESCRIPTION + +B is a debhelper program that is responsible for injecting +NSS (Name Service Switch) services into F. + +=head1 FILES + +=over 4 + +=item debian/I.nss + +Lists the services to inject into F when a package is +configured and to remove when a package is removed or purged. + +Each line in that file (a I) should be of the form + +I I I I I + +where the fields contain the following pieces of information: + +=over + +=item I + +The NSS database in which the service will be added. +Usually C. + +=item I + +Where to add the NSS service. + +Possible values are C, C, C>, +C>, plus the pseudo-positions C, +C and C. + +The pseudo-position C is used to mark services that are not +going to be added during the installation of the package, but that will +be removed during its deinstallation (e.g., legacy services). + +The pseudo-position C is used to request the addition of a +non-standard NSS database to F during the installation +of the package and its removal during the deinstallation of the package. +When C is used, all other fields in the directive should +be left empty. + +The pseudo-position C is used to announce that one +or more services in the I file will be installed under a +non-standard NSS database added by another package. +When C is used, all other fields in the directive +should be left empty. + +=item I + +The name of the NSS service to add. + +=item I + +Optional action specification C<[STATE=ACTION]>. + +=item I + +Optional set of conditions to better define when +a service should (or should not) be installed. + +Only one kind of condition is currently defined: +C>. + +=back + +Additionally, text between a C<#> character and the end of line is ignored. + +=back + +=head1 EXAMPLES + +An example F file could look like this: + + hosts before=dns mdns4 + hosts before=mdns4 mdns4_minimal [NOTFOUND=return] + hosts remove-only mdns # In case the user manually added it + +After the installation of this package, the original configuration of +the B database in B will change from: + + hosts: files dns + +to: + + hosts: files mdns4_minimal [NOTFOUND=return] mdns4 dns + +=head1 CAVEATS + +=head2 Non-standard NSS databases + +Directives in a F.nss> file can reference a non-standard +NSS database only if that database has been first declared with a +C or C directive in the same file. + +Non-standard NSS databases are all databases that are not defined in +F as shipped by the package B. + +If a directive references an undeclared non-standard NSS database +(e.g., C), B will exit with the error message +C. + +A non-standard NSS database can be declared by at most one installed +package. +In other words, if the directives in the B files of two packages +A and B reference the same non-standard NSS database C and +both packages can be installed at the same time, one of the following +two solutions must be implemented: + +=over + +=item 1. + +Package A supplies the C directive, package B +Is on A and uses a C directive. + +=item 2. + +The shared package C supplies the C directive, +both package A and B I on C and use a +C directive. + +=back + +=cut + +use constant NSS_DATABASES => qw(passwd group shadow gshadow hosts networks protocols services ethers rpc netgroup); +use constant POSITIONS => qw(first last before after remove-only database database-add database-require); # TODO: remove 'database' in v2 +use constant SERVICE_NAME_RE => qr/^[a-zA-Z0-9+:\@_-]+$/; +use constant ACTION_RE => qr/^\[!?[A-Za-z]+=[A-Za-z]+\]$/; +use constant CONDITION_TESTS => qw(skip-if-present); + +init(); + +# PROMISE: DH NOOP WITHOUT nss cli-options() + +sub process { + foreach my $package (getpackages()) { + my @dbs_extra_add = (); + my @dbs_extra_require = (); + my @service_names = (); + my @inst_lines = (); + my %db_services = (); + + my $nss = pkgfile($package, "nss") or next; + open(my $fd, $nss) or die("open($nss): $!"); + foreach my $line (<$fd>) { + $line =~ s/#.*$//; # Remove comments. + chomp($line); + next if ($line eq ""); + + my ($db, $service_name, $position, $inst_line) = process_line($line, $package, $nss, $., \@dbs_extra_add, \@dbs_extra_require); + + if (($position eq "database-add") || ($position eq "database")) { # TODO: remove 'database' in v2 + # Collect names of extra NSS databases to be added. + if (! grep { $_ eq $db } @dbs_extra_add) { + push(@dbs_extra_add, $db); + } + next; + } + + if (($position eq "database-require")) { + # Collect names of known non-standard NSS databases. + if (! grep { $_ eq $db } @dbs_extra_require) { + push(@dbs_extra_require, $db); + } + next; + } + + # Collect the names of NSS services installed by this package, + # as well as their installation command lines. + if (! grep { $_ eq $service_name } @service_names) { + push(@service_names, $service_name); + } + push(@inst_lines, $inst_line) unless ($position eq "remove-only"); + + # Collect the names of the NSS services that will be removed + # when this package will be uninstalled. + # The services are grouped by NSS DB. + if (! exists $db_services{$db}) { @{$db_services{$db}} = (); } + if (! grep { $_ eq $service_name } @{$db_services{$db}}) { + push(@{$db_services{$db}}, $service_name); + } + } + close($fd); + + if (!@dbs_extra_add && !@inst_lines && !%db_services) { + warning("$nss exists but contains no actionable directives."); + } + + # Generate the required debhelper snippets. + output_autoscripts($package, \@dbs_extra_add, \@inst_lines, \%db_services); + } +} + +sub process_line { + my ($line, $package, $nss_file, $line_num, $dbs_extra_add, $dbs_extra_require) = @_; + + my ($db, $position, $service, $action, $condition) = split(" ", $line); + $service //= ""; + $action //= ""; + $condition //= ""; + + # Turn before=service into pos=before, anchor=service. + ($position, my $anchors) = split("=", $position); + $anchors //= ""; + + # Use $action as condition if it does not look like a proper action. + if (substr($action, 0, 1) ne "[") { + $condition = $action; + $action = ""; + } + + validate_line($nss_file, $line_num, $db, $position, $service, $action, $condition, $dbs_extra_add, $dbs_extra_require); + + my $comment = comment_for_line($package, $db, $service, $position, $anchors, $action, $condition); + my $inst_operation = inst_operation_expr($package, $db, $service, $position, $anchors, $action, $condition); + my $inst_line = "$comment\n\t\t$inst_operation"; + + return $db, $service, $position, $inst_line; +} + +sub validate_line { + my ($nss_file, $line_num, $db, $position, $service, $action, $condition, $dbs_extra_add, $dbs_extra_require) = @_; + my @dbs_extra_add = @{$dbs_extra_add}; + my @dbs_extra_require = @{$dbs_extra_require}; + + if (! grep({ $_ eq $db } NSS_DATABASES)) { + if (!($position eq "database-add" || $position eq "database-require" || $position eq "database") # TODO: remove 'database' in v2 + && ! grep({$_ eq $db } (@dbs_extra_add, @dbs_extra_require))) { + error("Unknown NSS database '$db' [$nss_file:$line_num]"); + } + } + + if (! grep({ $_ eq $position } POSITIONS)) { + error("Unknown position '$position' [$nss_file:$line_num]"); + } + + if ($position eq "database-add" || $position eq "database-require" || $position eq "database") { # TODO: remove database' in v2 + if ($position eq "database") { # TODO: remove check in v2 + warning("Outdated 'database' directive will be removed in dh-nss v2. Use 'database-add' instead. [$nss_file:$line_num]"); + } + return; + } + + if ($service !~ SERVICE_NAME_RE) { + warning("Malformed service name '$service' [$nss_file:$line_num]"); + } + + if ($action && $action !~ ACTION_RE) { + warning("Malformed action specification '$action' [$nss_file:$line_num]"); + } + + if ($condition) { + my ($cond_test, $cond_value) = split("=", $condition); + if ($cond_test && !grep({ $_ eq $cond_test } CONDITION_TESTS)) { + error("Invalid condition test '$cond_test' (part of '$condition') [$nss_file:$line_num]"); + } + if ($cond_test eq "skip-if-present") { + foreach my $cond_service (split(",", $cond_value)) { + if ($cond_service !~ SERVICE_NAME_RE) { + warning("Invalid condition value '$cond_value' (part of '$condition') [$nss_file:$line_num]"); + last; + } + } + } + } +} + +sub comment_for_line { + my ($package, $db, $service, $position, $anchors, $action, $condition) = @_; + + my $comment = "# Installing $db/$service$action from $package in position $position"; + if ($anchors ne "") { $comment .= "=$anchors"; } + if ($condition ne "") { $comment .= " ($condition)"; } + + return $comment; +} + +sub inst_operation_expr { + my ($package, $db, $service, $position, $anchors, $action, $condition) = @_; + + my $anchors_expr = anchors_expr($anchors); + + my $condition_expr = cond_expr($db, $condition); + + # Prepare a sed-ready string with the service name. + my $service_complete = $service; + $service_complete .= " $action" if ($action ne ""); + $service_complete =~ s/(\[|\])/\\$1/g; # Escape `[` and `]`. + + # Choose the right sed invocation for the required position. + my $sed_cmd = 'sed -E -i "${DPKG_ROOT}/etc/nsswitch.conf"'; + if ($position eq "first") { + $sed_cmd .= " -e '/^$db:\\s/ s/(:\\s+)/\\1$service_complete /'"; + } elsif ($position eq "last") { + $sed_cmd .= " -e '/^$db:\\s[^#]*\$/ s/\$/ $service_complete/'"; + $sed_cmd .= " -e '/^$db:\\s.*#/ s/#/ $service_complete #/'"; + } elsif ($position eq "before") { + $sed_cmd .= " -e '/^$db:\\s[^#]*\$/ s/(\\s)($anchors_expr)(\\s|\$)/\\1$service_complete \\2 /'" ; + $sed_cmd .= " -e '/^$db:\\s.*#/ s/(\\s)($anchors_expr)(\\s|#)/\\1$service_complete \\2 /'" ; + $sed_cmd .= " -e 's/ \$//'"; + } elsif ($position eq "after") { + $sed_cmd .= " -e '/^$db:\\s[^#]*\$/ s/(\\s)($anchors_expr)(\\s|\$)/\\1\\2 $service_complete /'"; + $sed_cmd .= " -e '/^$db:\\s.*#/ s/(\\s)($anchors_expr)(\\s|#)/\\1\\2 $service_complete \\3/'"; + $sed_cmd .= " -e 's/ \$//'"; + } elsif ($position eq "remove-only") { + $sed_cmd = ""; + } + + if ($condition_expr ne "") { + $sed_cmd = "if $condition_expr ; then\n\t\t\t$sed_cmd\n\t\tfi"; + } + + return $sed_cmd; +} + +sub anchors_expr { + my ($anchors_str) = @_; + if (! defined($anchors_str)) { return "" }; + + my @anchors_res; + foreach my $anchor (split(",", $anchors_str)) { + my $anchor_re = "$anchor(\\s+\\[[^]]+\\])?"; + push(@anchors_res, $anchor_re) ; + } + + my $anchors_expr = join("|", @anchors_res); + + return $anchors_expr; +} + +sub cond_expr { + my ($db, $cond_str) = @_; + if ($cond_str eq "") { return "" }; + + my $cond_expr = "grep -q -E "; + if ($cond_str =~ /^skip-if-present=/ ) { + my $services = $cond_str =~ s/^skip-if-present=//r; + $services =~ s/,/|/g; + $cond_expr = "! $cond_expr"; + $cond_expr .= "'^$db:.*\\s($services)(\\s|\$)'" + } else { + error("Cannot parse condition $cond_str"); + } + + $cond_expr .= ' "${DPKG_ROOT}/etc/nsswitch.conf"'; + return $cond_expr +} + +sub service_patterns_expr { + my ($db_services) = @_; + my %db_services = %{$db_services}; + + my @exprs = ""; + my @dbs = sort(keys %db_services); + foreach my $db (@dbs) { + my $db_services_expr = $db_services{$db}; + my $expr = "-e '^$db:[^#]*\\s($db_services_expr)(\\s|#|\$)'"; + push(@exprs, $expr); + } + + my $service_patterns_expr = join(" ", @exprs); + + return $service_patterns_expr; +} + +sub output_autoscripts { + my ($package, $dbs_extra_add, $inst_lines, $db_services) = @_; + my @dbs_extra_add = @{$dbs_extra_add}; + my @inst_lines = @{$inst_lines}; + my %db_services = %{$db_services}; + + # Turn the lists of database-specific services into regular expressions. + my @dbs = sort(keys %db_services); + foreach my $db (@dbs) { + my @db_services = @{$db_services{$db}}; + my $db_services_expr = join("|", @db_services); + $db_services{$db} = $db_services_expr; + } + + # Aggregate the target databases and service regular expressions into + # grep-compatible guard expressions. + my $service_patterns_expr = service_patterns_expr(\%db_services); + + # Add a snippet in preinst to detect installation of non-purged packages. + autoscript($package, "preinst", "preinst-nss"); + + # Generate one snippet for each extra NSS databases to be added. + foreach my $db_extra (@dbs_extra_add) { + autoscript($package, "postinst", "postinst-nss-db", { + "DB" => $db_extra, + }); + } + + # Generate a single snippet with a sequence of installation instructions. + if (@inst_lines) { + my $inst_lines_expr = join("\n\t\t", @inst_lines); + autoscript($package, "postinst", "postinst-nss", { + "SERVICE_PATTERNS" => $service_patterns_expr, + "OPERATIONS" => $inst_lines_expr, + }); + } + + # Generate one snippet for each extra NSS databases to be removed. + # NOTE: DB removal must happen after service removal, however debhelper + # adds the `postrm` snippets in reverse order. For this reason the + # snippets for the extra DBs must be added first. + foreach my $db_extra (@dbs_extra_add) { + autoscript($package, "postrm", "postrm-nss-db", { + "DB" => $db_extra, + }); + } + + # Generate one snippet for each NSS DB, removing only the services + # related to that DB. + foreach my $db (@dbs) { + my $db_services_expr = $db_services{$db}; + autoscript($package, "postrm", "postrm-nss", { + "DB" => $db, + "SERVICE_NAMES" => $db_services_expr, + }); + } +} + +process(); + +=head1 SEE ALSO + +L + +This program is a debhelper addon. + +=head1 AUTHOR + +Gioele Barabucci + +=cut diff --git a/installnss.pm b/installnss.pm new file mode 100644 index 0000000..dc2e044 --- /dev/null +++ b/installnss.pm @@ -0,0 +1,7 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use Debian::Debhelper::Dh_Lib; + +insert_after("dh_install", "dh_installnss"); diff --git a/t/Test/DH.pm b/t/Test/DH.pm new file mode 100644 index 0000000..7fa30bf --- /dev/null +++ b/t/Test/DH.pm @@ -0,0 +1,205 @@ +package Test::DH; + +use strict; +use warnings; + +use Test::More; + +use Cwd qw(getcwd realpath); +use Errno qw(EEXIST); +use Exporter qw(import); + +use File::Temp qw(tempdir); +use File::Path qw(remove_tree make_path); +use File::Basename qw(dirname); + +our $ROOT_DIR; + +BEGIN { + my $res = realpath(__FILE__) or die('Cannot resolve ' . __FILE__ . ": $!"); + $ROOT_DIR = dirname(dirname(dirname($res))); +}; + +use lib "$ROOT_DIR/lib"; + +# These should be done before Dh_lib is loaded. +BEGIN { + $ENV{PATH} = "$ROOT_DIR:$ENV{PATH}" if $ENV{PATH} !~ m{\Q$ROOT_DIR\E/?:}; + $ENV{PERL5LIB} = join(':', "${ROOT_DIR}/lib", (grep {defined} $ENV{PERL5LIB})) + if not $ENV{PERL5LIB} or $ENV{PERL5LIB} !~ m{\Q$ROOT_DIR\E(?:/lib)?/?:}; + $ENV{DH_DATAFILES} = "${ROOT_DIR}/t/fixtures:${ROOT_DIR}"; + # Nothing in the tests requires root. + $ENV{DEB_RULES_REQUIRES_ROOT} = 'no'; + # Disable colors for good measure + $ENV{DH_COLORS} = 'never'; + $ENV{DPKG_COLORS} = 'never'; + + # Drop DEB_BUILD_PROFILES and DEB_BUILD_OPTIONS so they don't interfere + delete($ENV{DEB_BUILD_PROFILES}); + delete($ENV{DEB_BUILD_OPTIONS}); +}; + +use Debian::Debhelper::Dh_Lib qw(!dirname); + +our @EXPORT = qw( + each_compat_up_to_and_incl_subtest each_compat_subtest + each_compat_from_and_above_subtest run_dh_tool + create_empty_file readlines + error find_script non_deprecated_compat_levels + each_compat_from_x_to_and_incl_y_subtest +); + +our ($TEST_DH_COMPAT); + +my $START_DIR = getcwd(); +my $TEST_DIR; + +sub run_dh_tool { + my (@cmd) = @_; + my $compat = $TEST_DH_COMPAT; + my $options = ref($cmd[0]) ? shift(@cmd) : {}; + my $pid; + + $pid = fork() // BAIL_OUT("fork failed: $!"); + if (not $pid) { + $ENV{DH_COMPAT} = $compat; + $ENV{DH_INTERNAL_TESTSUITE_SILENT_WARNINGS} = 1; + if (defined(my $env = $options->{env})) { + for my $k (sort(keys(%{$env}))) { + if (defined($env->{$k})) { + $ENV{$k} = $env->{$k}; + } else { + delete($ENV{$k}); + } + } + } + if ($options->{quiet}) { + open(STDOUT, '>', '/dev/null') or error("Reopen stdout: $!"); + open(STDERR, '>', '/dev/null') or error("Reopen stderr: $!"); + } else { + # If run under prove/TAP, we don't want to confuse the test runner. + open(STDOUT, '>&', *STDERR) or error("Redirect stdout to stderr: $!"); + } + exec(@cmd); + } + waitpid($pid, 0) == $pid or BAIL_OUT("waitpid($pid) failed: $!"); + return 1 if not $?; + return 0; +} + +sub _prepare_test_root { + my $dir = tempdir(CLEANUP => 1); + if (not mkdir("$dir/debian", 0777)) { + error("mkdir $dir/debian failed: $!") + if $! != EEXIST; + } else { + # auto seed it + my @files = qw( + debian/control + debian/compat + debian/changelog + ); + for my $file (@files) { + install_file($file, "${dir}/${file}"); + } + if (@::TEST_DH_EXTRA_TEMPLATE_FILES) { + my $test_dir = ($TEST_DIR //= dirname($0)); + my $fixture_dir = $::TEST_DH_FIXTURE_DIR // '.'; + my $actual_dir = "$test_dir/$fixture_dir"; + for my $file (@::TEST_DH_EXTRA_TEMPLATE_FILES) { + if (index($file, '/') > -1) { + my $install_dir = dirname($file); + install_dir($install_dir); + } + install_file("${actual_dir}/${file}", "${dir}/${file}"); + } + } + } + return $dir; +} + +sub each_compat_from_x_to_and_incl_y_subtest($$&) { + my ($compat, $high_compat, $code) = @_; + my $lowest = Debian::Debhelper::Dh_Lib::MIN_COMPAT_LEVEL; + my $highest = Debian::Debhelper::Dh_Lib::MAX_COMPAT_LEVEL; + error("compat $compat is no longer support! Min compat $lowest") + if $compat < $lowest; + error("$high_compat is from the future! Max known is $highest") + if $high_compat > $highest; + subtest '' => sub { + # Keep $dir alive until the test is over + my $dir = _prepare_test_root; + chdir($dir) or error("chdir($dir): $!"); + while ($compat <= $high_compat) { + local $TEST_DH_COMPAT = $compat; + $code->($compat); + ++$compat; + } + chdir($START_DIR) or error("chdir($START_DIR): $!"); + }; + return; +} + +sub each_compat_up_to_and_incl_subtest($&) { + unshift(@_, Debian::Debhelper::Dh_Lib::MIN_COMPAT_LEVEL); + goto \&each_compat_from_x_to_and_incl_y_subtest; +} + +sub each_compat_from_and_above_subtest($&) { + splice(@_, 1, 0, Debian::Debhelper::Dh_Lib::MAX_COMPAT_LEVEL); + goto \&each_compat_from_x_to_and_incl_y_subtest; +} + +sub each_compat_subtest(&) { + unshift(@_, + Debian::Debhelper::Dh_Lib::MIN_COMPAT_LEVEL, + Debian::Debhelper::Dh_Lib::MAX_COMPAT_LEVEL); + goto \&each_compat_from_x_to_and_incl_y_subtest; +} + +sub create_empty_file { + my ($file, $chmod) = @_; + open(my $fd, '>', $file) or die("open($file): $!\n"); + close($fd) or die("close($file): $!\n"); + if (defined($chmod)) { + chmod($chmod, $file) + or die(sprintf('chmod(%04o, %s): %s', $chmod, $file, $!)); + } + return 1; +} + +sub readlines { + my ($h) = @_; + my @lines = <$h>; + close $h; + chop @lines; + return \@lines; +} + +# In *inst order (find_script will shuffle them around for *rm order) +my @SNIPPET_FILE_TEMPLATES = ( + 'debian/#PACKAGE#.#SCRIPT#.debhelper', + 'debian/.debhelper/generated/#PACKAGE#/#SCRIPT#.service', +); + +sub find_script { + my ($package, $script) = @_; + my @files; + for my $template (@SNIPPET_FILE_TEMPLATES) { + my $file = ($template =~ s/#PACKAGE#/$package/r); + $file =~ s/#SCRIPT#/$script/; + push(@files, $file) if -f $file; + } + if ($script eq 'postrm' or $script eq 'prerm') { + @files = reverse(@files); + } + return @files; +} + +sub non_deprecated_compat_levels() { + my $start = Debian::Debhelper::Dh_Lib::LOWEST_NON_DEPRECATED_COMPAT_LEVEL; + my $end = Debian::Debhelper::Dh_Lib::MAX_COMPAT_LEVEL; + return ($start..$end); +} + +1; diff --git a/t/dh_installnss/debian/changelog b/t/dh_installnss/debian/changelog new file mode 100644 index 0000000..5850f0e --- /dev/null +++ b/t/dh_installnss/debian/changelog @@ -0,0 +1,5 @@ +foo (1.0-1) unstable; urgency=low + + * Initial release. (Closes: #XXXXXX) + + -- Test Mon, 11 Jul 2016 18:10:59 +0200 diff --git a/t/dh_installnss/debian/compat b/t/dh_installnss/debian/compat new file mode 100644 index 0000000..60d3b2f --- /dev/null +++ b/t/dh_installnss/debian/compat @@ -0,0 +1 @@ +15 diff --git a/t/dh_installnss/debian/control b/t/dh_installnss/debian/control new file mode 100644 index 0000000..8e137f7 --- /dev/null +++ b/t/dh_installnss/debian/control @@ -0,0 +1,89 @@ +Source: foo +Section: misc +Priority: optional +Maintainer: Test +Standards-Version: 3.9.8 + +Package: foo-before1 +Architecture: all +Description: package foo-before1 + +Package: foo-before2 +Architecture: all +Description: package foo-before2 + +Package: foo-after1 +Architecture: all +Description: package foo-after1 + +Package: foo-after2 +Architecture: all +Description: package foo-after2 + +Package: foo-empty1 +Architecture: all +Description: package foo-empty1 + +Package: foo-empty2 +Architecture: all +Description: package foo-empty2 + +Package: foo-first1 +Architecture: all +Description: package foo-first1 + +Package: foo-first2 +Architecture: all +Description: package foo-first2 + +Package: foo-first3 +Architecture: all +Description: package foo-first3 + +Package: foo-last1 +Architecture: all +Description: package foo-last1 + +Package: foo-last2 +Architecture: all +Description: package foo-last2 + +Package: foo-last3 +Architecture: all +Description: package foo-last3 + +Package: foo-multidb1 +Architecture: all +Description: package foo-multidb1 + +Package: foo-newdb1 +Architecture: all +Description: package foo-newdb1 + +Package: foo-newdb2 +Architecture: all +Description: package foo-newdb2 + +Package: foo-newdb3 +Architecture: all +Description: package foo-newdb3 + +Package: foo-remove-only1 +Architecture: all +Description: package foo-remove-only1 + +Package: foo-other +Architecture: all +Description: package foo-other + +Package: foo-example +Architecture: all +Description: package foo-example + +Package: foo-skip +Architecture: all +Description: package foo-skip + +Package: foo-substring +Architecture: all +Description: package foo-substring diff --git a/t/dh_installnss/debian/foo-after1.nss b/t/dh_installnss/debian/foo-after1.nss new file mode 100644 index 0000000..aa65c17 --- /dev/null +++ b/t/dh_installnss/debian/foo-after1.nss @@ -0,0 +1 @@ +hosts after=dns foo diff --git a/t/dh_installnss/debian/foo-after2.nss b/t/dh_installnss/debian/foo-after2.nss new file mode 100644 index 0000000..0c0d053 --- /dev/null +++ b/t/dh_installnss/debian/foo-after2.nss @@ -0,0 +1 @@ +hosts after=dns,files foo diff --git a/t/dh_installnss/debian/foo-before1.nss b/t/dh_installnss/debian/foo-before1.nss new file mode 100644 index 0000000..d0d65a9 --- /dev/null +++ b/t/dh_installnss/debian/foo-before1.nss @@ -0,0 +1 @@ +hosts before=dns foo diff --git a/t/dh_installnss/debian/foo-before2.nss b/t/dh_installnss/debian/foo-before2.nss new file mode 100644 index 0000000..cdfd74d --- /dev/null +++ b/t/dh_installnss/debian/foo-before2.nss @@ -0,0 +1 @@ +hosts before=dns,resolve foo diff --git a/t/dh_installnss/debian/foo-empty1.nss b/t/dh_installnss/debian/foo-empty1.nss new file mode 100644 index 0000000..e69de29 diff --git a/t/dh_installnss/debian/foo-empty2.nss b/t/dh_installnss/debian/foo-empty2.nss new file mode 100644 index 0000000..40afa03 --- /dev/null +++ b/t/dh_installnss/debian/foo-empty2.nss @@ -0,0 +1,3 @@ +# only comments here + +# and empty lines diff --git a/t/dh_installnss/debian/foo-example.nss b/t/dh_installnss/debian/foo-example.nss new file mode 100644 index 0000000..7708ddd --- /dev/null +++ b/t/dh_installnss/debian/foo-example.nss @@ -0,0 +1,3 @@ +hosts before=dns mdns4 +hosts before=mdns4 mdns4_minimal [NOTFOUND=return] +hosts remove-only mdns # In case the user manually added it diff --git a/t/dh_installnss/debian/foo-first1.nss b/t/dh_installnss/debian/foo-first1.nss new file mode 100644 index 0000000..8e29dd7 --- /dev/null +++ b/t/dh_installnss/debian/foo-first1.nss @@ -0,0 +1,2 @@ +#DB POS MODULE (ACTION?) (CONDITION?) +hosts first foo diff --git a/t/dh_installnss/debian/foo-first2.nss b/t/dh_installnss/debian/foo-first2.nss new file mode 100644 index 0000000..10a78de --- /dev/null +++ b/t/dh_installnss/debian/foo-first2.nss @@ -0,0 +1 @@ +hosts first foo [NOTFOUND=return] diff --git a/t/dh_installnss/debian/foo-first3.nss b/t/dh_installnss/debian/foo-first3.nss new file mode 100644 index 0000000..9c80174 --- /dev/null +++ b/t/dh_installnss/debian/foo-first3.nss @@ -0,0 +1,2 @@ +hosts first foo2 +hosts first foo2 [NOTFOUND=return] diff --git a/t/dh_installnss/debian/foo-last1.nss b/t/dh_installnss/debian/foo-last1.nss new file mode 100644 index 0000000..defa945 --- /dev/null +++ b/t/dh_installnss/debian/foo-last1.nss @@ -0,0 +1 @@ +hosts last foo diff --git a/t/dh_installnss/debian/foo-last2.nss b/t/dh_installnss/debian/foo-last2.nss new file mode 100644 index 0000000..e6ad245 --- /dev/null +++ b/t/dh_installnss/debian/foo-last2.nss @@ -0,0 +1 @@ +hosts last foo [NOTFOUND=return] diff --git a/t/dh_installnss/debian/foo-last3.nss b/t/dh_installnss/debian/foo-last3.nss new file mode 100644 index 0000000..cb85368 --- /dev/null +++ b/t/dh_installnss/debian/foo-last3.nss @@ -0,0 +1,2 @@ +hosts last foo2 +hosts last foo2 [NOTFOUND=return] diff --git a/t/dh_installnss/debian/foo-multidb1.nss b/t/dh_installnss/debian/foo-multidb1.nss new file mode 100644 index 0000000..8dc899c --- /dev/null +++ b/t/dh_installnss/debian/foo-multidb1.nss @@ -0,0 +1,4 @@ +shadow first foo +shadow last foo2 +group first foo +passwd last baz diff --git a/t/dh_installnss/debian/foo-newdb1.nss b/t/dh_installnss/debian/foo-newdb1.nss new file mode 100644 index 0000000..606d085 --- /dev/null +++ b/t/dh_installnss/debian/foo-newdb1.nss @@ -0,0 +1 @@ +mydb database-add diff --git a/t/dh_installnss/debian/foo-newdb2.nss b/t/dh_installnss/debian/foo-newdb2.nss new file mode 100644 index 0000000..2d38f54 --- /dev/null +++ b/t/dh_installnss/debian/foo-newdb2.nss @@ -0,0 +1,3 @@ +mydb database-add + +mydb first foo diff --git a/t/dh_installnss/debian/foo-newdb3.nss b/t/dh_installnss/debian/foo-newdb3.nss new file mode 100644 index 0000000..7bd7149 --- /dev/null +++ b/t/dh_installnss/debian/foo-newdb3.nss @@ -0,0 +1,3 @@ +mydb database-require + +mydb first bar diff --git a/t/dh_installnss/debian/foo-other.nss b/t/dh_installnss/debian/foo-other.nss new file mode 100644 index 0000000..6f3c135 --- /dev/null +++ b/t/dh_installnss/debian/foo-other.nss @@ -0,0 +1 @@ +hosts last foo diff --git a/t/dh_installnss/debian/foo-remove-only1.nss b/t/dh_installnss/debian/foo-remove-only1.nss new file mode 100644 index 0000000..816ad1e --- /dev/null +++ b/t/dh_installnss/debian/foo-remove-only1.nss @@ -0,0 +1,2 @@ +hosts first foo +hosts remove-only resolve diff --git a/t/dh_installnss/debian/foo-skip.nss b/t/dh_installnss/debian/foo-skip.nss new file mode 100644 index 0000000..0002201 --- /dev/null +++ b/t/dh_installnss/debian/foo-skip.nss @@ -0,0 +1,2 @@ +hosts first foo skip-if-present=other1,other2 +hosts first foo2 skip-if-present=other3 diff --git a/t/dh_installnss/debian/foo-substring.nss b/t/dh_installnss/debian/foo-substring.nss new file mode 100644 index 0000000..5843e2b --- /dev/null +++ b/t/dh_installnss/debian/foo-substring.nss @@ -0,0 +1,2 @@ +hosts before=dns foo +hosts first foo_extra diff --git a/t/dh_installnss/dh_installnss.t b/t/dh_installnss/dh_installnss.t new file mode 100755 index 0000000..95cfecd --- /dev/null +++ b/t/dh_installnss/dh_installnss.t @@ -0,0 +1,652 @@ +#!/usr/bin/perl +use strict; +use warnings; + +use Test::More; +use Cwd qw(abs_path); +use File::Basename qw(dirname); +use File::Path qw(make_path); +use lib dirname(dirname(abs_path(__FILE__))); +use Test::DH; +use Debian::Debhelper::Dh_Lib qw(!dirname); + +plan(tests => 1); + +my $test_dir = abs_path(dirname(__FILE__)); +my $dpkg_root = "debian/foo"; + +our @TEST_DH_EXTRA_TEMPLATE_FILES = (qw( + debian/changelog + debian/control + debian/foo-after1.nss + debian/foo-after2.nss + debian/foo-before1.nss + debian/foo-before2.nss + debian/foo-empty1.nss + debian/foo-empty2.nss + debian/foo-example.nss + debian/foo-first1.nss + debian/foo-first2.nss + debian/foo-first3.nss + debian/foo-last1.nss + debian/foo-last2.nss + debian/foo-last3.nss + debian/foo-multidb1.nss + debian/foo-newdb1.nss + debian/foo-newdb2.nss + debian/foo-newdb3.nss + debian/foo-other.nss + debian/foo-remove-only1.nss + debian/foo-skip.nss + debian/foo-substring.nss +)); + +each_compat_subtest { + make_path(qw(debian/foo debian/foo/etc)); + ok(run_dh_tool("dh_installnss")); + + $ENV{"DPKG_ROOT"} = "$dpkg_root"; + $ENV{"DPKG_MAINTSCRIPT_PACKAGE_INSTCOUNT"} = 0; + + subtest "skips installation if no /etc/nsswitch.conf" => sub { + unlink("$dpkg_root/etc/nsswitch.conf"); + exec_script_ok("foo-first1", "preinst", "install"); + exec_script_ok("foo-first1", "postinst", "configure"); + ok(!-e "$dpkg_root/etc/nsswitch.conf"); + }; + + subtest "respects skip-if-present" => sub { + place_nsswitch_file("hosts", "files other2 dns"); + exec_script_ok("foo-skip", "preinst", "install"); + exec_script_ok("foo-skip", "postinst", "configure"); + is(db_line("hosts"), "hosts: foo2 files other2 dns"); + exec_script_ok("foo-skip", "postrm", "remove"); + is(db_line("hosts"), "hosts: files other2 dns"); + }; + + subtest "position=first" => sub { + subtest "adds in first position" => sub { + place_nsswitch_file("hosts", "files other2 dns"); + exec_script_ok("foo-first1", "preinst", "install"); + exec_script_ok("foo-first1", "postinst", "configure"); + is(db_line("hosts"), "hosts: foo files other2 dns"); + exec_script_ok("foo-first1", "postrm", "remove"); + is(db_line("hosts"), "hosts: files other2 dns"); + }; + + subtest "adds in first position (in presence of comments)" => sub { + place_nsswitch_file("hosts", "files other2 dns # comment"); + exec_script_ok("foo-first1", "preinst", "install"); + exec_script_ok("foo-first1", "postinst", "configure"); + is(db_line("hosts"), "hosts: foo files other2 dns # comment"); + exec_script_ok("foo-first1", "postrm", "remove"); + is(db_line("hosts"), "hosts: files other2 dns # comment"); + }; + + subtest "adds in first position (in presence of service in comment)" => sub { + place_nsswitch_file("hosts", "files other2 dns # foo in comment"); + exec_script_ok("foo-first1", "preinst", "install"); + exec_script_ok("foo-first1", "postinst", "configure"); + is(db_line("hosts"), "hosts: foo files other2 dns # foo in comment"); + exec_script_ok("foo-first1", "postrm", "remove"); + is(db_line("hosts"), "hosts: files other2 dns # foo in comment"); + }; + + subtest "adds in first position (with action)" => sub { + place_nsswitch_file("hosts", "files other2 dns"); + exec_script_ok("foo-first2", "preinst", "install"); + exec_script_ok("foo-first2", "postinst", "configure"); + is(db_line("hosts"), "hosts: foo [NOTFOUND=return] files other2 dns"); + exec_script_ok("foo-first2", "postrm", "remove"); + is(db_line("hosts"), "hosts: files other2 dns"); + }; + + subtest "adds in first position (multiple times)" => sub { + place_nsswitch_file("hosts", "files other2 dns"); + exec_script_ok("foo-first3", "preinst", "install"); + exec_script_ok("foo-first3", "postinst", "configure"); + is(db_line("hosts"), "hosts: foo2 [NOTFOUND=return] foo2 files other2 dns"); + exec_script_ok("foo-first3", "postrm", "remove"); + is(db_line("hosts"), "hosts: files other2 dns"); + }; + + subtest "does not add if already present in line (service name)" => sub { + place_nsswitch_file("hosts", "files foo dns"); + exec_script_ok("foo-first1", "preinst", "install"); + exec_script_ok("foo-first1", "postinst", "configure"); + is(db_line("hosts"), "hosts: files foo dns"); + exec_script_ok("foo-first1", "postrm", "remove"); + is(db_line("hosts"), "hosts: files dns"); + }; + + subtest "does not add if already present in line (service first)" => sub { + place_nsswitch_file("hosts", "foo files dns"); + exec_script_ok("foo-first1", "preinst", "install"); + exec_script_ok("foo-first1", "postinst", "configure"); + is(db_line("hosts"), "hosts: foo files dns"); + exec_script_ok("foo-first1", "postrm", "remove"); + is(db_line("hosts"), "hosts: files dns"); + }; + + subtest "does not add if already present in line (service last)" => sub { + place_nsswitch_file("hosts", "files dns foo"); + exec_script_ok("foo-first1", "preinst", "install"); + exec_script_ok("foo-first1", "postinst", "configure"); + is(db_line("hosts"), "hosts: files dns foo"); + exec_script_ok("foo-first1", "postrm", "remove"); + is(db_line("hosts"), "hosts: files dns"); + }; + + subtest "does not add if already present in line (service last before comment)" => sub { + place_nsswitch_file("hosts", "files dns foo# comment"); + exec_script_ok("foo-first1", "preinst", "install"); + exec_script_ok("foo-first1", "postinst", "configure"); + is(db_line("hosts"), "hosts: files dns foo# comment"); + exec_script_ok("foo-first1", "postrm", "remove"); + is(db_line("hosts"), "hosts: files dns# comment"); + }; + + subtest "does not add if already present in line (service name with action)" => sub { + place_nsswitch_file("hosts", "files foo [NOTFOUND=return] dns"); + exec_script_ok("foo-first2", "preinst", "install"); + exec_script_ok("foo-first2", "postinst", "configure"); + is(db_line("hosts"), "hosts: files foo [NOTFOUND=return] dns"); + exec_script_ok("foo-first2", "postrm", "remove"); + is(db_line("hosts"), "hosts: files dns"); + }; + + subtest "does not if already present in line (service name without action)" => sub { + place_nsswitch_file("hosts", "files foo dns"); + exec_script_ok("foo-first2", "preinst", "install"); + exec_script_ok("foo-first2", "postinst", "configure"); + is(db_line("hosts"), "hosts: files foo dns"); + exec_script_ok("foo-first2", "postrm", "remove"); + is(db_line("hosts"), "hosts: files dns"); + }; + }; + + subtest "position=last" => sub { + subtest "adds in last position" => sub { + place_nsswitch_file("hosts", "files other2 dns"); + exec_script_ok("foo-last1", "preinst", "install"); + exec_script_ok("foo-last1", "postinst", "configure"); + is(db_line("hosts"), "hosts: files other2 dns foo"); + exec_script_ok("foo-last1", "postrm", "remove"); + is(db_line("hosts"), "hosts: files other2 dns"); + }; + + subtest "adds in last position (with action)" => sub { + place_nsswitch_file("hosts", "files other2 dns"); + exec_script_ok("foo-last2", "preinst", "install"); + exec_script_ok("foo-last2", "postinst", "configure"); + is(db_line("hosts"), "hosts: files other2 dns foo [NOTFOUND=return]"); + exec_script_ok("foo-last2", "postrm", "remove"); + is(db_line("hosts"), "hosts: files other2 dns"); + }; + + subtest "adds in last position (multiple times)" => sub { + place_nsswitch_file("hosts", "files other2 dns"); + exec_script_ok("foo-last3", "preinst", "install"); + exec_script_ok("foo-last3", "postinst", "configure"); + is(db_line("hosts"), "hosts: files other2 dns foo2 foo2 [NOTFOUND=return]"); + exec_script_ok("foo-last3", "postrm", "remove"); + is(db_line("hosts"), "hosts: files other2 dns"); + }; + + subtest "adds before a comment" => sub { + place_nsswitch_file("hosts", "files other2 dns #a long comment"); + exec_script_ok("foo-last1", "preinst", "install"); + exec_script_ok("foo-last1", "postinst", "configure"); + is(db_line("hosts"), "hosts: files other2 dns foo #a long comment"); + exec_script_ok("foo-last1", "postrm", "remove"); + is(db_line("hosts"), "hosts: files other2 dns #a long comment"); + }; + + subtest "adds before a comment (no space)" => sub { + place_nsswitch_file("hosts", "files other2 dns#short comment"); + exec_script_ok("foo-last1", "preinst", "install"); + exec_script_ok("foo-last1", "postinst", "configure"); + # The script unconditionally adds a space before the comment. + is(db_line("hosts"), "hosts: files other2 dns foo #short comment"); + exec_script_ok("foo-last1", "postrm", "remove"); + is(db_line("hosts"), "hosts: files other2 dns #short comment"); + }; + }; + + subtest "position=before" => sub { + subtest "adds before another" => sub { + place_nsswitch_file("hosts", "files other2 dns"); + exec_script_ok("foo-before1", "preinst", "install"); + exec_script_ok("foo-before1", "postinst", "configure"); + is(db_line("hosts"), "hosts: files other2 foo dns"); + exec_script_ok("foo-before1", "postrm", "remove"); + is(db_line("hosts"), "hosts: files other2 dns"); + }; + + subtest "adds before another (in presence of comments)" => sub { + place_nsswitch_file("hosts", "files other2 dns # comment"); + exec_script_ok("foo-before1", "preinst", "install"); + exec_script_ok("foo-before1", "postinst", "configure"); + is(db_line("hosts"), "hosts: files other2 foo dns # comment"); + exec_script_ok("foo-before1", "postrm", "remove"); + is(db_line("hosts"), "hosts: files other2 dns # comment"); + }; + + subtest "adds before another with action" => sub { + place_nsswitch_file("hosts", "files other2 dns [NOTFOUND=return]"); + exec_script_ok("foo-before1", "preinst", "install"); + exec_script_ok("foo-before1", "postinst", "configure"); + is(db_line("hosts"), "hosts: files other2 foo dns [NOTFOUND=return]"); + exec_script_ok("foo-before1", "postrm", "remove"); + is(db_line("hosts"), "hosts: files other2 dns [NOTFOUND=return]"); + }; + + subtest "does not add before another if missing" => sub { + place_nsswitch_file("hosts", "files other2"); + exec_script_ok("foo-before1", "preinst", "install"); + exec_script_ok("foo-before1", "postinst", "configure"); + is(db_line("hosts"), "hosts: files other2"); + exec_script_ok("foo-before1", "postrm", "remove"); + is(db_line("hosts"), "hosts: files other2"); + }; + + subtest "does not add if another missing (but in comment)" => sub { + place_nsswitch_file("hosts", "files other2 # dns"); + exec_script_ok("foo-before1", "preinst", "install"); + exec_script_ok("foo-before1", "postinst", "configure"); + is(db_line("hosts"), "hosts: files other2 # dns"); + exec_script_ok("foo-before1", "postrm", "remove"); + is(db_line("hosts"), "hosts: files other2 # dns"); + }; + + subtest "adds before another (multiple possibilites, one found)" => sub { + place_nsswitch_file("hosts", "files resolve other2"); + exec_script_ok("foo-before2", "preinst", "install"); + exec_script_ok("foo-before2", "postinst", "configure"); + is(db_line("hosts"), "hosts: files foo resolve other2"); + exec_script_ok("foo-before2", "postrm", "remove"); + is(db_line("hosts"), "hosts: files resolve other2"); + }; + + subtest "adds before another (multiple possibilites, same order)" => sub { + place_nsswitch_file("hosts", "files dns resolve other2"); + exec_script_ok("foo-before2", "preinst", "install"); + exec_script_ok("foo-before2", "postinst", "configure"); + is(db_line("hosts"), "hosts: files foo dns resolve other2"); + exec_script_ok("foo-before2", "postrm", "remove"); + is(db_line("hosts"), "hosts: files dns resolve other2"); + }; + + subtest "adds before another (multiple possibilites, inverted order)" => sub { + place_nsswitch_file("hosts", "files resolve other2 dns"); + exec_script_ok("foo-before2", "preinst", "install"); + exec_script_ok("foo-before2", "postinst", "configure"); + is(db_line("hosts"), "hosts: files foo resolve other2 dns"); + exec_script_ok("foo-before2", "postrm", "remove"); + is(db_line("hosts"), "hosts: files resolve other2 dns"); + }; + + subtest "adds before another (multiple possibilites, with action)" => sub { + place_nsswitch_file("hosts", "files resolve [!NOTFOUND=return] other2"); + exec_script_ok("foo-before2", "preinst", "install"); + exec_script_ok("foo-before2", "postinst", "configure"); + is(db_line("hosts"), "hosts: files foo resolve [!NOTFOUND=return] other2"); + exec_script_ok("foo-before2", "postrm", "remove"); + is(db_line("hosts"), "hosts: files resolve [!NOTFOUND=return] other2"); + }; + }; + + subtest "position=after" => sub { + subtest "adds after another" => sub { + place_nsswitch_file("hosts", "files other2 dns"); + exec_script_ok("foo-after1", "preinst", "install"); + exec_script_ok("foo-after1", "postinst", "configure"); + is(db_line("hosts"), "hosts: files other2 dns foo"); + exec_script_ok("foo-after1", "postrm", "remove"); + is(db_line("hosts"), "hosts: files other2 dns"); + }; + + subtest "adds after another (in presence of comments)" => sub { + place_nsswitch_file("hosts", "files other2 dns # a comment"); + exec_script_ok("foo-after1", "preinst", "install"); + exec_script_ok("foo-after1", "postinst", "configure"); + is(db_line("hosts"), "hosts: files other2 dns foo # a comment"); + exec_script_ok("foo-after1", "postrm", "remove"); + is(db_line("hosts"), "hosts: files other2 dns # a comment"); + }; + + subtest "adds after another with action" => sub { + place_nsswitch_file("hosts", "dns [NOTFOUND=return] other2 files"); + exec_script_ok("foo-after1", "preinst", "install"); + exec_script_ok("foo-after1", "postinst", "configure"); + is(db_line("hosts"), "hosts: dns [NOTFOUND=return] foo other2 files"); + exec_script_ok("foo-after1", "postrm", "remove"); + is(db_line("hosts"), "hosts: dns [NOTFOUND=return] other2 files"); + }; + + subtest "does not add after another if missing (but in comment)" => sub { + place_nsswitch_file("hosts", "files other2 # dns"); + exec_script_ok("foo-after1", "preinst", "install"); + exec_script_ok("foo-after1", "postinst", "configure"); + is(db_line("hosts"), "hosts: files other2 # dns"); + exec_script_ok("foo-after1", "postrm", "remove"); + is(db_line("hosts"), "hosts: files other2 # dns"); + }; + + subtest "does not add after another if missing" => sub { + place_nsswitch_file("hosts", "files other2"); + exec_script_ok("foo-after1", "preinst", "install"); + exec_script_ok("foo-after1", "postinst", "configure"); + is(db_line("hosts"), "hosts: files other2"); + exec_script_ok("foo-after1", "postrm", "remove"); + is(db_line("hosts"), "hosts: files other2"); + }; + + subtest "adds after another (multiple possibilites)" => sub { + place_nsswitch_file("hosts", "files resolve other2"); + exec_script_ok("foo-after2", "preinst", "install"); + exec_script_ok("foo-after2", "postinst", "configure"); + is(db_line("hosts"), "hosts: files foo resolve other2"); + exec_script_ok("foo-after2", "postrm", "remove"); + is(db_line("hosts"), "hosts: files resolve other2"); + }; + }; + + subtest "position=remove-only" => sub { + subtest "does not add remove-only services" => sub { + place_nsswitch_file("hosts", "files dns"); + exec_script_ok("foo-remove-only1", "preinst", "install"); + exec_script_ok("foo-remove-only1", "postinst", "configure"); + is(db_line("hosts"), "hosts: foo files dns"); + exec_script_ok("foo-remove-only1", "postrm", "remove"); + is(db_line("hosts"), "hosts: files dns"); + }; + + subtest "removes both added and already present services" => sub { + place_nsswitch_file("hosts", "foo files resolve dns"); + exec_script_ok("foo-remove-only1", "postrm", "remove"); + is(db_line("hosts"), "hosts: files dns"); + }; + }; + + subtest "addition" => sub { + subtest "skips if any service is already mentioned in a target database (1)" => sub { + place_nsswitch_file("shadow", "files foo dns"); + exec_script_ok("foo-multidb1", "preinst", "install"); + exec_script_ok("foo-multidb1", "postinst", "configure"); + is(db_line("shadow"), "shadow: files foo dns"); + exec_script_ok("foo-multidb1", "postrm", "remove"); + is(db_line("shadow"), "shadow: files dns"); + }; + + subtest "skips if any service is already mentioned in a target database (2)" => sub { + place_nsswitch_file("passwd", "baz files"); + exec_script_ok("foo-multidb1", "preinst", "install"); + exec_script_ok("foo-multidb1", "postinst", "configure"); + is(db_line("passwd"), "passwd: baz files"); + exec_script_ok("foo-multidb1", "postrm", "remove"); + is(db_line("passwd"), "passwd: files"); + }; + + subtest "adds if the service is mentioned in an unrelated database" => sub { + place_nsswitch_file("passwd", "files foo"); + is(db_line("shadow"), "shadow: files"); + exec_script_ok("foo-multidb1", "preinst", "install"); + exec_script_ok("foo-multidb1", "postinst", "configure"); + is(db_line("shadow"), "shadow: foo files foo2"); + is(db_line("passwd"), "passwd: files foo baz"); + exec_script_ok("foo-multidb1", "postrm", "remove"); + is(db_line("shadow"), "shadow: files"); + is(db_line("passwd"), "passwd: files foo"); + }; + }; + + subtest "removal" => sub { + subtest "succeeds if service has already been removed" => sub { + place_nsswitch_file("hosts", "files dns"); + exec_script_ok("foo-other", "postrm", "remove"); + is(db_line("hosts"), "hosts: files dns"); + }; + + subtest "removes all instances of service (consecutive 1)" => sub { + place_nsswitch_file("hosts", "files foo foo [NOTFOUND=return] foo dns"); + exec_script_ok("foo-other", "postrm", "remove"); + is(db_line("hosts"), "hosts: files dns"); + }; + + subtest "removes all instances of service (consecutive 2)" => sub { + place_nsswitch_file("hosts", "files foo [NOTFOUND=return] foo foo dns"); + exec_script_ok("foo-other", "postrm", "remove"); + is(db_line("hosts"), "hosts: files dns"); + }; + + subtest "removes all instances of service (split 1)" => sub { + place_nsswitch_file("hosts", "files foo [NOTFOUND=return] foo dns foo"); + exec_script_ok("foo-other", "postrm", "remove"); + is(db_line("hosts"), "hosts: files dns"); + }; + + subtest "removes all instances of service (split 2)" => sub { + place_nsswitch_file("hosts", "foo [NOTFOUND=return] files foo [!UNAVAIL=return] foo dns"); + exec_script_ok("foo-other", "postrm", "remove"); + is(db_line("hosts"), "hosts: files dns"); + }; + + subtest "removes all instances of service (also during purge)" => sub { + place_nsswitch_file("hosts", "files foo foo [NOTFOUND=return] foo dns"); + exec_script_ok("foo-other", "postrm", "purge"); + is(db_line("hosts"), "hosts: files dns"); + }; + + subtest "removes services with substring names" => sub { + place_nsswitch_file("hosts", "foo_extra files foo dns"); + exec_script_ok("foo-substring", "postrm", "remove"); + is(db_line("hosts"), "hosts: files dns"); + }; + + subtest "removes services with substring names (in presence of comments)" => sub { + place_nsswitch_file("hosts", "foo_extra files foo dns# comment: foo is ok"); + exec_script_ok("foo-substring", "postrm", "remove"); + is(db_line("hosts"), "hosts: files dns# comment: foo is ok"); + }; + }; + + subtest "position=database-add" => sub { + subtest "adds a database if not already present (without service)" => sub { + place_nsswitch_file(); + is(num_occurrences("mydb", dbs()), 0); + exec_script_ok("foo-newdb1", "preinst", "install"); + exec_script_ok("foo-newdb1", "postinst", "configure"); + is(db_line("mydb"), "mydb: "); + is(num_occurrences("mydb", dbs()), 1); + exec_script_ok("foo-newdb1", "postrm", "remove"); + is(num_occurrences("mydb", dbs()), 0); + }; + + subtest "does not add the database if already present (without service)" => sub { + place_nsswitch_file(); + is(num_occurrences("mydb", dbs()), 0); + # first install + exec_script_ok("foo-newdb1", "preinst", "install"); + exec_script_ok("foo-newdb1", "postinst", "configure"); + is(db_line("mydb"), "mydb: "); + is(num_occurrences("mydb", dbs()), 1); + # second install + exec_script_ok("foo-newdb1", "preinst", "install"); + exec_script_ok("foo-newdb1", "postinst", "configure"); + is(db_line("mydb"), "mydb: "); + is(num_occurrences("mydb", dbs()), 1); + exec_script_ok("foo-newdb1", "postrm", "remove"); + is(num_occurrences("mydb", dbs()), 0); + }; + + subtest "adds a database if not already present (with service)" => sub { + place_nsswitch_file(); + is(num_occurrences("mydb", dbs()), 0); + exec_script_ok("foo-newdb2", "preinst", "install"); + exec_script_ok("foo-newdb2", "postinst", "configure"); + is(db_line("mydb"), "mydb: foo "); # TODO: fix trailing space in v2 + is(num_occurrences("mydb", dbs()), 1); + exec_script_ok("foo-newdb2", "postrm", "remove"); + is(num_occurrences("mydb", dbs()), 0); + }; + + subtest "does not add the database if already present (with service)" => sub { + place_nsswitch_file(); + is(num_occurrences("mydb", dbs()), 0); + # first install + exec_script_ok("foo-newdb2", "preinst", "install"); + exec_script_ok("foo-newdb2", "postinst", "configure"); + is(db_line("mydb"), "mydb: foo "); # TODO: fix trailing space in v2 + is(num_occurrences("mydb", dbs()), 1); + # second install + exec_script_ok("foo-newdb2", "preinst", "install"); + exec_script_ok("foo-newdb2", "postinst", "configure"); + is(db_line("mydb"), "mydb: foo "); # TODO: fix trailing space in v2 + is(num_occurrences("mydb", dbs()), 1); + # removal + exec_script_ok("foo-newdb2", "postrm", "remove"); + is(num_occurrences("mydb", dbs()), 0); + }; + }; + + subtest "position=detabase-require" => sub { + subtest "adds a service to the the database if already present" => sub { + place_nsswitch_file("mydb", "other"); + is(num_occurrences("mydb", dbs()), 1); + exec_script_ok("foo-newdb3", "preinst", "install"); + exec_script_ok("foo-newdb3", "postinst", "configure"); + is(db_line("mydb"), "mydb: bar other"); + is(num_occurrences("mydb", dbs()), 1); + exec_script_ok("foo-newdb3", "postrm", "remove"); + is(db_line("mydb"), "mydb: other"); + is(num_occurrences("mydb", dbs()), 1); + }; + + subtest "does not add a service if the database is missing" => sub { + place_nsswitch_file(); + is(num_occurrences("mydb", dbs()), 0); + exec_script_ok("foo-newdb3", "preinst", "install"); + exec_script_ok("foo-newdb3", "postinst", "configure"); + is(num_occurrences("mydb", dbs()), 0); + exec_script_ok("foo-newdb3", "postrm", "remove"); + is(num_occurrences("mydb", dbs()), 0); + }; + + subtest "does not remove the database during package removal" => sub { + place_nsswitch_file("mydb", " "); + is(num_occurrences("mydb", dbs()), 1); + exec_script_ok("foo-newdb3", "preinst", "install"); + exec_script_ok("foo-newdb3", "postinst", "configure"); + is(db_line("mydb"), "mydb: bar "); + is(num_occurrences("mydb", dbs()), 1); + exec_script_ok("foo-newdb3", "postrm", "remove"); + is(db_line("mydb"), "mydb: "); + is(num_occurrences("mydb", dbs()), 1); + }; + }; + + subtest "validation" => sub { + subtest "does not add debhelper snippets if .nss is empty" => sub { + my ($postinst_path) = find_script("foo-empty1", "postinst"); + my ($postrm_path) = find_script("foo-empty1", "postrm"); + is($postinst_path, undef); + is($postrm_path, undef); + }; + + subtest "does not add debhelper snippets if .nss is empty (with comments)" => sub { + my ($postinst_path) = find_script("foo-empty2", "postinst"); + my ($postrm_path) = find_script("foo-empty2", "postrm"); + is($postinst_path, undef); + is($postrm_path, undef); + }; + }; + + subtest "example in docs" => sub { + place_nsswitch_file("hosts", "files dns"); + exec_script_ok("foo-example", "preinst", "install"); + exec_script_ok("foo-example", "postinst", "configure"); + is(db_line("hosts"), "hosts: files mdns4_minimal [NOTFOUND=return] mdns4 dns"); + exec_script_ok("foo-example", "postrm", "remove"); + is(db_line("hosts"), "hosts: files dns"); + }; + + ok(run_dh_tool('dh_clean')); +}; + +sub place_nsswitch_file { + my ($db, $line) = @_; + my $template_file = "$test_dir/nsswitch.conf.template"; + my $conf_file = "debian/foo/etc/nsswitch.conf"; + + my $fd; + open($fd, '<', $template_file) or error("open($template_file): $!"); + read($fd, my $conf, -s $fd); + close($fd); + + if ($db && $line) { + my $db_line_re = "^$db:.*\$"; + my $newline = "$db: $line"; + if ($conf =~ /$db_line_re/m) { + $conf =~ s/$db_line_re/$newline/m; + } else { + $conf .= "\n$newline\n"; + } + } + + open($fd, '>', $conf_file) or error("open($conf_file): $!"); + print($fd $conf); + close($fd); +} + +sub exec_script_ok { + is(exec_script(@_), 0); +} + +sub exec_script { + my ($package, $script, @args) = @_; + my ($script_path) = find_script($package, $script); + (defined $script_path && $script_path ne "") or error("No maintscript of type $script found"); + system("sh", "-e", $script_path, @args); + return $? >> 8; +} + +sub db_line { + my ($db) = @_; + + my @lines = nsswitch_file_lines(); + + my $db_line_re = "^$db:"; + my ($line) = grep({ m/$db_line_re/ } @lines); + $line //= ""; + # Normalize spaces to simplify comparison. + $line =~ s/\s+/ /g; + + return $line; +} + +sub dbs { + my @lines = nsswitch_file_lines(); + + my @dbs = (); + foreach my $line (@lines) { + my ($db) = $line =~ m/^([a-zA-Z]+):/g; + push @dbs, $db if $db; + } + + return @dbs; +} + +sub nsswitch_file_lines { + open(my $fd, '<', "$dpkg_root/etc/nsswitch.conf"); + my @lines = @{readlines($fd)}; + close($fd); + + return @lines; +} + +sub num_occurrences { + my ($item, @array) = @_; + + return scalar grep({$_ eq $item} @array); +} diff --git a/t/dh_installnss/nsswitch.conf.template b/t/dh_installnss/nsswitch.conf.template new file mode 100644 index 0000000..8ecf84c --- /dev/null +++ b/t/dh_installnss/nsswitch.conf.template @@ -0,0 +1,17 @@ +# A sample /etc/nsswitch.conf file + +passwd: files systemd +group: files systemd +shadow: files +gshadow: files + +#hosts: files mdns4_minimal [NOTFOUND=return] dns +hosts: files mdns4 [NOTFOUND=return] dns mymachines +networks: files + +protocols: db files +services: db files +ethers: db files +rpc: db files + +netgroup: nis -- cgit v1.2.3