diff options
Diffstat (limited to 'dh_systemd_start')
-rwxr-xr-x | dh_systemd_start | 288 |
1 files changed, 288 insertions, 0 deletions
diff --git a/dh_systemd_start b/dh_systemd_start new file mode 100755 index 0000000..af98809 --- /dev/null +++ b/dh_systemd_start @@ -0,0 +1,288 @@ +#!/usr/bin/perl -w + +=head1 NAME + +dh_systemd_start - start/stop/restart systemd unit files + +=cut + +use strict; +use warnings; +use Debian::Debhelper::Dh_Lib; +use File::Find; +use Cwd qw(getcwd abs_path); + +our $VERSION = DH_BUILTIN_VERSION; + +=head1 SYNOPSIS + +B<dh_systemd_start> [S<I<debhelper options>>] [B<--restart-after-upgrade>] [B<--no-stop-on-upgrade>] [S<I<unit file> ...>] + +=head1 DESCRIPTION + +B<dh_systemd_start> is a debhelper program that is responsible for +starting/stopping or restarting systemd unit files in case no corresponding +sysv init script is available. + +As with B<dh_installinit>, the unit file is stopped before +upgrades and started afterwards (unless B<--restart-after-upgrade> is +specified, in which case it will only be restarted after the upgrade). +This logic is not used when there is a corresponding SysV init script +because invoke-rc.d performs the stop/start/restart in that case. + +=head1 OPTIONS + +=over 4 + +=item B<--restart-after-upgrade> + +Do not stop the unit file until after the package upgrade has been completed. +This is the default behaviour in compat 10. + +In earlier compat levels the default was to stop the unit file in the +F<prerm>, and start it again in the F<postinst>. + +This can be useful for daemons that should not have a possibly long +downtime during upgrade. But you should make sure that the daemon will not +get confused by the package being upgraded while it's running before using +this option. + +=item B<--no-restart-after-upgrade> + +Undo a previous B<--restart-after-upgrade> (or the default of compat +10). If no other options are given, this will cause the service to be +stopped in the F<prerm> script and started again in the F<postinst> +script. + +=item B<-r>, B<--no-stop-on-upgrade>, B<--no-restart-on-upgrade> + +Do not stop service on upgrade. + +=item B<--no-start> + +Do not start the unit file after upgrades and after initial installation (the +latter is only relevant for services without a corresponding init script). + +=back + +=head1 NOTES + +Note that this command is not idempotent. L<dh_prep(1)> should be called +between invocations of this command (with the same arguments). Otherwise, it +may cause multiple instances of the same text to be added to maintainer +scripts. + +Note that B<dh_systemd_start> should be run after B<dh_installinit> so that it +can detect corresponding SysV init scripts. The default sequence in B<dh> does +the right thing, this note is only relevant when you are calling +B<dh_systemd_start> manually. + +=cut + +if (not compat(10)) { + error("dh_systemd_start is no longer used in compat >= 11, please use dh_installsystemd instead"); +} + +$dh{RESTART_AFTER_UPGRADE} = 1 if not compat(9); + +init(options => { + "r" => \$dh{R_FLAG}, + 'no-stop-on-upgrade' => \$dh{R_FLAG}, + "no-restart-on-upgrade" => \$dh{R_FLAG}, + "no-start" => \$dh{NO_START}, + "R|restart-after-upgrade!" => \$dh{RESTART_AFTER_UPGRADE}, + "no-also" => \$dh{NO_ALSO}, +}); + +# Extracts the Also= or Alias= line(s) from a unit file. +# In case this produces horribly wrong results, you can pass --no-also, but +# that should really not be necessary. Please report bugs to +# pkg-systemd-maintainers. +sub extract_key { + my ($unit_path, $key) = @_; + my @values; + + return if $dh{NO_ALSO}; + + open(my $fh, '<', $unit_path) or error("Cannot open($unit_path) for extracting the Also= line(s): $!"); + + while (my $line = <$fh>) { + chomp($line); + + # The keys parsed from the unit file below can only have + # unit names as values. Since unit names can't have + # whitespace in systemd, simply use split and strip any + # leading/trailing quotes. See systemd-escape(1) for + # examples of valid unit names. + if ($line =~ /^\s*$key=(.+)$/i) { + for my $value (split(/\s+/, $1)) { + $value =~ s/^(["'])(.*)\g1$/$2/; + push @values, $value; + } + } + } + close($fh); + return @values; +} + + +# PROMISE: DH NOOP WITHOUT tmp(lib/systemd/system) + +my %requested_files = map { basename($_) => 1 } @ARGV; +my %installed_files; + +foreach my $package (@{$dh{DOPACKAGES}}) { + my $tmpdir = tmpdir($package); + my @installed_units; + my @units; + my %aliases; + + my $oldcwd = getcwd(); + find({ + wanted => sub { + my $name = $File::Find::name; + return unless -f; + return unless $name =~ m,^\Q${tmpdir}\E/lib/systemd/system/[^/]+$,; + if (-l) { + my $target = abs_path(readlink()); + $target =~ s,^\Q${oldcwd}\E/,,g; + $aliases{$target} = [ $_ ]; + } else { + push @installed_units, $name; + } + }, + }, "${tmpdir}/lib/systemd/system") if -d "${tmpdir}/lib/systemd/system"; + chdir($oldcwd); + + # Handle either only the unit files which were passed as arguments or + # all unit files that are installed in this package. + my @args = @ARGV > 0 ? @ARGV : @installed_units; + + # support excluding units via -X + foreach my $x (@{$dh{EXCLUDE}}) { + @args = grep !/(^|\/)$x$/, @args; + } + + # This hash prevents us from looping forever in the following while loop. + # An actual real-world example of such a loop is systemd’s + # systemd-readahead-drop.service, which contains + # Also=systemd-readahead-collect.service, and that file in turn + # contains Also=systemd-readahead-drop.service, thus forming an endless + # loop. + my %seen; + + # We use while/shift because we push to the list in the body. + while (@args) { + my $name = shift @args; + my $base = basename($name); + + # Try to make the path absolute, so that the user can call + # dh_installsystemd bacula-fd.service + if ($base eq $name) { + # NB: This works because @installed_units contains + # files from precisely one directory. + my ($full) = grep { basename($_) eq $base } @installed_units; + if (defined($full)) { + $name = $full; + } elsif (not exists($requested_files{$base})) { + warning(qq|Could not find "$name" in the /lib/systemd/system directory of $package. | . + qq|This could be a typo, or using Also= with a service file from another package. | . + qq|Please check carefully that this message is harmless.|); + } else { + # Ignore an explicitly requested file that is missing; happens when we are acting on + # multiple packages and only a subset of them have the unit file. + next; + } + } + + $installed_files{$base} = 1 if exists($requested_files{$base}); + + # Skip template service files like e.g. getty@.service. + # Enabling, disabling, starting or stopping those services + # without specifying the instance (e.g. getty@ttyS0.service) is + # not useful. + if ($name =~ /\@/) { + next; + } + + # Handle all unit files specified via Also= explicitly. + # This is not necessary for enabling, but for disabling, as we + # cannot read the unit file when disabling (it was already + # deleted). + my @also = grep { !exists($seen{$_}) } extract_key($name, 'Also'); + $seen{$_} = 1 for @also; + @args = (@args, @also); + + push @{$aliases{$name}}, $_ for extract_key($name, 'Alias'); + my @sysv = grep { + my $base = $_; + $base =~ s/\.(?:mount|service|socket|target|path)$//g; + -f "$tmpdir/etc/init.d/$base" + } ($base, @{$aliases{$name}}); + if (@sysv == 0 && !grep { $_ eq $name } @units) { + push @units, $name; + } + } + + next if @units == 0; + + # Wrap the basenames in '' to preserve \x2d when the shell parses the + # name. (#764730) + my $unitargs = join(' ', sort map { q{'} . basename($_) . q{'} } @units); + # The $package and $sed parameters are always the same. + # This wrapper function makes the following logic easier to read. + my $sd_autoscript = sub { + my ($script, $filename, $restart_action) = @_; + my $replace = { + 'UNITFILES' => $unitargs, + 'RESTART_ACTION' => $restart_action // '', + }; + autoscript($package, $script, $filename, $replace); + }; + + if ($dh{RESTART_AFTER_UPGRADE}) { + my ($snippet, $restart_action); + if ($dh{NO_START}) { + $snippet = 'postinst-systemd-restartnostart'; + $restart_action = 'try-restart'; + } else { + $snippet = 'postinst-systemd-restart'; + $restart_action = 'restart'; + } + $sd_autoscript->("postinst", $snippet, $restart_action); + } elsif (!$dh{NO_START}) { + # We need to stop/start before/after the upgrade. + $sd_autoscript->("postinst", "postinst-systemd-start"); + } + + $sd_autoscript->("postrm", "postrm-systemd-reload-only"); + + if ($dh{R_FLAG} || $dh{RESTART_AFTER_UPGRADE}) { + # stop service only on remove + $sd_autoscript->("prerm", "prerm-systemd-restart"); + } elsif (!$dh{NO_START}) { + # always stop service + $sd_autoscript->("prerm", "prerm-systemd"); + } +} + +if (%requested_files) { + my $any_missing = 0; + for my $name (sort(keys(%requested_files))) { + if (not exists($installed_files{$name})) { + warning(qq{Requested unit "$name" but it was not found in any package acted on.}); + $any_missing = 1; + } + } + error("Could not handle all of the requested services") if $any_missing; +} + +=head1 SEE ALSO + +L<debhelper(7)> + +=head1 AUTHORS + +pkg-systemd-maintainers@lists.alioth.debian.org + +=cut |