summaryrefslogtreecommitdiffstats
path: root/dh_systemd_start
diff options
context:
space:
mode:
Diffstat (limited to 'dh_systemd_start')
-rwxr-xr-xdh_systemd_start288
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