summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xscript/deb-systemd-helper46
-rw-r--r--t/002-deb-systemd-helper-update.t155
2 files changed, 200 insertions, 1 deletions
diff --git a/script/deb-systemd-helper b/script/deb-systemd-helper
index 02d5027..5d8466b 100755
--- a/script/deb-systemd-helper
+++ b/script/deb-systemd-helper
@@ -35,7 +35,7 @@ deb-systemd-helper - subset of systemctl for machines not running systemd
=head1 SYNOPSIS
-B<deb-systemd-helper> enable|disable|is-enabled|reenable S<I<unit file> ...>
+B<deb-systemd-helper> enable|disable|is-enabled|was-enabled|reenable S<I<unit file> ...>
=head1 DESCRIPTION
@@ -47,6 +47,10 @@ package). On the first "enable", an state file is created which will be deleted
upon "disable", but only when _DEB_SYSTEMD_HELPER_PURGE=1 to distinguish purge
from remove.
+The "was-enabled" action is not present in systemctl, but is required in Debian
+so that we can figure out whether a service was enabled before we installed an
+updated service file. See http://bugs.debian.org/717603 for details.
+
B<deb-systemd-helper> is intended to be used from maintscripts to enable
systemd unit files. It is specifically NOT intended to be used interactively by
users. Instead, users should run systemd and use systemctl, or not bother about
@@ -203,6 +207,31 @@ sub make_systemd_links {
return $already_enabled;
}
+sub was_enabled {
+ my ($service_path) = @_;
+
+ my $statefile = "$state_dir/" . basename($service_path) . '.dsh-also';
+ debug "Reading state file $statefile";
+ my $fh;
+ my @other;
+ if (open($fh, '<', $statefile)) {
+ @other = map { chomp; $_ } <$fh>;
+ close($fh);
+ }
+
+ debug "Contents: " . Dumper(\@other);
+
+ for my $link (@other) {
+ if (! -l $link) {
+ debug "Link $link is missing, considering $service_path was-disabled.";
+ return 0;
+ }
+ }
+
+ debug "All links present, considering $service_path was-enabled.";
+ return 1;
+}
+
sub remove_links {
my ($service_path) = @_;
@@ -305,6 +334,21 @@ for my $scriptname (@ARGV) {
$rc = 0 if $enabled;
}
+ # was-enabled is the same as is-enabled, but only considers links recorded
+ # in the state file. This is useful after package upgrades, to determine
+ # whether the unit file was enabled before upgrading, even if the unit file
+ # has changed and is not entirely enabled currently (due to a new Alias=
+ # line for example).
+ #
+ # If all machines were running systemd, this issue would not be present
+ # because is-enabled would query systemd, which would not have picked up
+ # the new unit file yet.
+ if ($action eq 'was-enabled') {
+ my $enabled = was_enabled($service_path);
+ print STDERR ($enabled ? "enabled\n" : "disabled\n");
+ $rc = 0 if $enabled;
+ }
+
if ($action eq 'reenable') {
remove_links($service_path);
make_systemd_links($scriptname, $service_path, $action, undef);
diff --git a/t/002-deb-systemd-helper-update.t b/t/002-deb-systemd-helper-update.t
new file mode 100644
index 0000000..97621d2
--- /dev/null
+++ b/t/002-deb-systemd-helper-update.t
@@ -0,0 +1,155 @@
+#!perl
+# vim:ts=4:sw=4:et
+
+use strict;
+use warnings;
+use Test::More;
+use File::Temp qw(tempfile tempdir); # in core since perl 5.6.1
+use File::Path qw(make_path); # in core since Perl 5.001
+use File::Basename; # in core since Perl 5
+use FindBin; # in core since Perl 5.00307
+use Linux::Clone; # neither in core nor in Debian :-/
+
+# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+# ┃ SETUP: in a new mount namespace, bindmount tmpdirs on /etc/systemd and ┃
+# ┃ /var/lib/systemd to start with clean directories yet use the actual ┃
+# ┃ locations and code paths. ┃
+# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
+
+my $dsh = "$FindBin::Bin/../script/deb-systemd-helper";
+
+# reads in a whole file
+sub slurp {
+ open my $fh, '<', shift;
+ local $/;
+ <$fh>;
+}
+
+sub state_file_entries {
+ my ($path) = @_;
+ my $bytes = slurp($path);
+ return split("\n", $bytes);
+}
+
+sub _unit_enabled {
+ my ($unit_file, $cb, $verb) = @_;
+
+ my $retval = system("DPKG_MAINTSCRIPT_PACKAGE=test $dsh is-enabled $unit_file");
+ isnt($retval, -1, 'deb-systemd-helper could be executed');
+ ok(!($retval & 127), 'deb-systemd-helper did not exit due to a signal');
+ $cb->($retval >> 8, 0, "random unit file $verb enabled");
+}
+
+sub is_enabled { _unit_enabled($_[0], \&is, 'is') }
+sub isnt_enabled { _unit_enabled($_[0], \&isnt, 'isnt') }
+
+my $retval = Linux::Clone::unshare Linux::Clone::NEWNS;
+BAIL_OUT("Cannot unshare(NEWNS): $!") if $retval != 0;
+
+sub bind_mount_tmp {
+ my ($dir) = @_;
+ my $tmp = tempdir(CLEANUP => 1);
+ system("mount -n --bind $tmp $dir") == 0
+ or BAIL_OUT("bind-mounting $tmp to $dir failed: $!");
+ return $tmp;
+}
+
+my $etc_systemd = bind_mount_tmp('/etc/systemd');
+my $lib_systemd = bind_mount_tmp('/lib/systemd');
+my $var_lib_systemd = bind_mount_tmp('/var/lib/systemd');
+
+# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+# ┃ Verify “is-enabled” is not true for a random, non-existing unit file. ┃
+# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
+
+my ($fh, $random_unit) = tempfile('unitXXXXX',
+ SUFFIX => '.service',
+ TMPDIR => 1,
+ UNLINK => 1);
+close($fh);
+$random_unit = basename($random_unit);
+
+my $statefile = "/var/lib/systemd/deb-systemd-helper-enabled/$random_unit.dsh-also";
+my $servicefile_path = "/lib/systemd/system/$random_unit";
+make_path('/lib/systemd/system');
+open($fh, '>', $servicefile_path);
+print $fh <<'EOT';
+[Unit]
+Description=test unit
+
+[Service]
+ExecStart=/bin/sleep 1
+
+[Install]
+WantedBy=multi-user.target
+EOT
+close($fh);
+
+# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+# ┃ Verify “enable” creates the requested symlinks. ┃
+# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
+
+$retval = system("DPKG_MAINTSCRIPT_PACKAGE=test $dsh enable $random_unit");
+my $symlink_path = "/etc/systemd/system/multi-user.target.wants/$random_unit";
+ok(-l $symlink_path, "$random_unit was enabled");
+is(readlink($symlink_path), $servicefile_path,
+ "symlink points to $servicefile_path");
+
+# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+# ┃ Verify “is-enabled” now returns true. ┃
+# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
+
+is_enabled($random_unit);
+
+# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+# ┃ Modify the unit file and verify that “is-enabled” is no longer true. ┃
+# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
+
+open($fh, '>', $servicefile_path);
+print $fh "Alias=newalias.service\n";
+close($fh);
+
+isnt_enabled($random_unit);
+
+# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+# ┃ Verify “was-enabled” is still true (operates on the state file). ┃
+# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
+
+$retval = system("DPKG_MAINTSCRIPT_PACKAGE=test $dsh was-enabled $random_unit");
+isnt($retval, -1, 'deb-systemd-helper could be executed');
+ok(!($retval & 127), 'deb-systemd-helper did not exit due to a signal');
+is($retval >> 8, 0, "random unit file was-enabled");
+
+# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+# ┃ Verify the new symlink is not yet in the state file. ┃
+# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
+
+is_deeply(
+ [ state_file_entries($statefile) ],
+ [ $symlink_path ],
+ 'state file does not contain the new link yet');
+
+# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+# ┃ Verify “enable” creates the new symlinks. ┃
+# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
+
+my $new_symlink_path = '/etc/systemd/system/newalias.service';
+ok(! -l $new_symlink_path, 'new symlink does not exist yet');
+
+$retval = system("DPKG_MAINTSCRIPT_PACKAGE=test $dsh enable $random_unit");
+ok(-l $new_symlink_path, 'new symlink was created');
+is(readlink($new_symlink_path), $servicefile_path,
+ "symlink points to $servicefile_path");
+
+is_enabled($random_unit);
+
+# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+# ┃ Verify the new symlink was recorded in the state file. ┃
+# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
+
+is_deeply(
+ [ state_file_entries($statefile) ],
+ [ $symlink_path, $new_symlink_path ],
+ 'state file updated');
+
+done_testing;