#!/usr/bin/perl -w =head1 NAME dh_systemd_start - start/stop/restart systemd unit files =cut use strict; use Debian::Debhelper::Dh_Lib; use File::Find; use Text::ParseWords qw(shellwords); # in core since Perl 5 use Cwd qw(getcwd abs_path); my $deb_host_arch_os = dpkg_architecture_value('DEB_HOST_ARCH_OS'); if ($deb_host_arch_os ne 'linux') { warning("systemd is unsupported on $deb_host_arch_os, doing nothing"); exit(0); } =head1 SYNOPSIS B [S>] [B<--restart-after-upgrade>] [B<--no-restart-on-upgrade>] [S ...>] =head1 DESCRIPTION B 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, 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 different than the default behavior, which stops the unit file in the F and starts it again in the F maintscript. 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<-r>, 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 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 should be run after B so that it can detect corresponding SysV init scripts. The default sequence in B does the right thing, this note is only relevant when you are calling B manually. This command currently works for Linux only, for other Debian archs it does nothing and returns 0; =cut init(options => { "r" => \$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; my $fh; if ($dh{NO_ALSO}) { return @values; } if (!open($fh, '<', $unit_path)) { warning("Cannot open($unit_path) for extracting the Also= line(s)"); return; } while (my $line = <$fh>) { chomp($line); if ($line =~ /^\s*$key=(.+)$/i) { @values = (@values, shellwords($1)); } } close($fh); return @values; } 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,^$tmpdir/lib/systemd/system/[^/]+$,; if (-l) { my $target = abs_path(readlink()); $target =~ s,^$oldcwd/,,g; $aliases{$target} = [ $_ ]; } else { push @installed_units, $name; } }, }, $tmpdir); 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; # 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; } else { warning(qq|Could not find "$name" in the /lib/systemd/system 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.|); } } # 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/\.(?:service|socket)$//g; -f "$tmpdir/etc/init.d/$base" } ($base, @{$aliases{$name}}); if (@sysv == 0 && !grep { $_ eq $name } @units) { push @units, $name; } } next if @units == 0; # 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) = @_; my $unitargs = join(" ", map { basename($_) } @units); autoscript($package, $script, $filename, "s/#UNITFILES#/$unitargs/"); }; if ($dh{RESTART_AFTER_UPGRADE}) { $sd_autoscript->("postinst", "postinst-systemd-restart"); } 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"); } } =head1 SEE ALSO L =head1 AUTHORS pkg-systemd-maintainers@lists.alioth.debian.org =cut