summaryrefslogtreecommitdiffstats
path: root/script
diff options
context:
space:
mode:
Diffstat (limited to 'script')
-rwxr-xr-xscript/deb-systemd-helper691
-rwxr-xr-xscript/deb-systemd-invoke187
-rwxr-xr-xscript/invoke-rc.d574
-rwxr-xr-xscript/service217
-rwxr-xr-xscript/update-rc.d543
5 files changed, 2212 insertions, 0 deletions
diff --git a/script/deb-systemd-helper b/script/deb-systemd-helper
new file mode 100755
index 0000000..5e7e167
--- /dev/null
+++ b/script/deb-systemd-helper
@@ -0,0 +1,691 @@
+#!/usr/bin/perl
+# vim:ts=4:sw=4:expandtab
+# © 2013-2014 Michael Stapelberg <stapelberg@debian.org>
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * Neither the name of Michael Stapelberg nor the
+# names of contributors may be used to endorse or promote products
+# derived from this software without specific prior written permission.
+# .
+# THIS SOFTWARE IS PROVIDED BY Michael Stapelberg ''AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL Michael Stapelberg BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+=head1 NAME
+
+deb-systemd-helper - subset of systemctl for machines not running systemd
+
+=head1 SYNOPSIS
+
+B<deb-systemd-helper> enable | disable | purge | mask | unmask | is-enabled | was-enabled | debian-installed | update-state | reenable S<I<unit file> ...>
+
+=head1 DESCRIPTION
+
+B<deb-systemd-helper> is a Debian-specific helper script which re-implements
+the enable, disable, is-enabled and reenable commands from systemctl.
+
+The "enable" action will only be performed once (when first installing the
+package). On the first "enable", a state file is created which will be deleted
+upon "purge".
+
+The "mask" action will keep state on whether the service was enabled/disabled
+before and will properly return to that state on "unmask".
+
+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.
+
+The "debian-installed" action is also not present in systemctl. It returns 0 if
+the state file of at least one of the given units is present.
+
+The "update-state" action is also not present in systemctl. It updates
+B<deb-systemd-helper>'s state file, removing obsolete entries (e.g. service
+files that are no longer shipped by the package) and adding new entries (e.g.
+new service files shipped by the package) without enabling them.
+
+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
+the systemd enabled state in case they are not running systemd.
+
+=head1 ENVIRONMENT
+
+=over 4
+
+=item B<_DEB_SYSTEMD_HELPER_DEBUG>
+
+If you export _DEB_SYSTEMD_HELPER_DEBUG=1, deb-systemd-helper will print debug
+messages to stderr (thus visible in dpkg runs). Please include these when
+filing a bugreport.
+
+=item B<DPKG_ROOT>
+
+Instead of working on the filesystem root /, perform all operations on a chroot
+system in the directory given by DPKG_ROOT.
+
+=back
+
+=cut
+
+use strict;
+use warnings;
+use File::Path qw(make_path); # in core since Perl 5.001
+use File::Basename; # in core since Perl 5
+use File::Temp qw(tempfile); # in core since Perl 5.6.1
+use Getopt::Long; # in core since Perl 5
+# Make Data::Dumper::Dumper available if present (not present on systems that
+# only have perl-base, not perl).
+eval { require Data::Dumper; } or *Data::Dumper::Dumper = sub { "no Data::Dumper" };
+
+my $dpkg_root = $ENV{DPKG_ROOT} // '';
+
+use constant {
+ SYSTEM_INSTANCE_ENABLED_STATE_DIR => '/var/lib/systemd/deb-systemd-helper-enabled',
+ USER_INSTANCE_ENABLED_STATE_DIR => '/var/lib/systemd/deb-systemd-user-helper-enabled',
+ SYSTEM_INSTANCE_MASKED_STATE_DIR => '/var/lib/systemd/deb-systemd-helper-masked',
+ USER_INSTANCE_MASKED_STATE_DIR => '/var/lib/systemd/deb-systemd-user-helper-masked',
+};
+
+my $quiet = 0;
+my $instance = 'system';
+my $enabled_state_dir = $dpkg_root . SYSTEM_INSTANCE_ENABLED_STATE_DIR;
+my $masked_state_dir = $dpkg_root . SYSTEM_INSTANCE_MASKED_STATE_DIR;
+
+# Globals are bad, but in this specific case, it really makes things much
+# easier to write and understand.
+my $changed_sth;
+my $has_systemctl = -x "$dpkg_root/bin/systemctl" || -x "$dpkg_root/usr/bin/systemctl";
+
+sub assertdpkgroot {
+ my ($path, $msg) = @_;
+ if (length $ENV{DPKG_ROOT}) {
+ if ($path !~ /^\Q$dpkg_root\E/) {
+ error("doesn't start with dpkg_root: $path $msg");
+ }
+ if ($path =~ /^\Q$dpkg_root$dpkg_root\E/) {
+ error("double dpkg_root: $path $msg");
+ }
+ }
+}
+
+sub assertnotdpkgroot {
+ my ($path, $msg) = @_;
+ if (length $ENV{DPKG_ROOT}) {
+ if ($path =~ /^\Q$dpkg_root\E/) {
+ error("starts with dpkg_root: $path $msg");
+ }
+ }
+}
+
+sub error {
+ print STDERR "$0: error: @_\n";
+ exit (1);
+}
+
+sub debug {
+ my ($msg) = @_;
+ return if !defined($ENV{_DEB_SYSTEMD_HELPER_DEBUG}) || $ENV{_DEB_SYSTEMD_HELPER_DEBUG} != 1;
+ print STDERR "(deb-systemd-helper DEBUG) $msg\n";
+}
+
+sub is_purge {
+ return (defined($ENV{_DEB_SYSTEMD_HELPER_PURGE}) && $ENV{_DEB_SYSTEMD_HELPER_PURGE} == 1)
+}
+
+sub find_unit {
+ my ($scriptname) = @_;
+
+ my $service_path = $scriptname;
+
+ if (-f "$dpkg_root/etc/systemd/$instance/$scriptname") {
+ $service_path = "/etc/systemd/$instance/$scriptname";
+ } elsif (-f "$dpkg_root/lib/systemd/$instance/$scriptname") {
+ $service_path = "/lib/systemd/$instance/$scriptname";
+ } elsif (-f "$dpkg_root/usr/lib/systemd/$instance/$scriptname") {
+ $service_path = "/usr/lib/systemd/$instance/$scriptname";
+ }
+
+ return $service_path;
+}
+
+sub dsh_state_path {
+ my ($scriptname) = @_;
+ return $enabled_state_dir . '/' . basename($scriptname) . '.dsh-also';
+}
+
+sub state_file_entries {
+ my ($dsh_state) = @_;
+ debug "Reading state file $dsh_state";
+ my @entries;
+ if (open(my $fh, '<', $dsh_state)) {
+ @entries = map { chomp; "$dpkg_root$_" } <$fh>;
+ close($fh);
+ }
+ return @entries;
+}
+
+# Writes $service_link into $dsh_state unless it’s already in there.
+sub record_in_statefile {
+ my ($dsh_state, $service_link) = @_;
+
+ assertdpkgroot($dsh_state, "record_in_statefile");
+ assertnotdpkgroot($service_link, "record_in_statefile");
+
+ # Appending a newline makes the following code simpler; we can skip
+ # chomp()ing and appending newlines in every print.
+ $service_link .= "\n";
+
+ make_path(dirname($dsh_state));
+ my $line_exists;
+ my ($outfh, $tmpname) = tempfile('.stateXXXXX',
+ DIR => dirname($dsh_state),
+ SUFFIX => '.tmp',
+ UNLINK => 0);
+ chmod(0644, $tmpname);
+ if (-e $dsh_state) {
+ open(my $infh, '<', $dsh_state) or error("unable to read from $dsh_state");
+ while (<$infh>) {
+ $line_exists = 1 if $_ eq $service_link;
+ print $outfh $_;
+ }
+ close($infh);
+ }
+ print $outfh $service_link unless $line_exists;
+ close($outfh) or error("unable to close $tmpname");
+
+ debug "Renaming temp file $tmpname to state file $dsh_state";
+ rename($tmpname, $dsh_state) or
+ error("Unable to move $tmpname to $dsh_state");
+}
+
+# Gets the transitive closure of links, i.e. all links that need to be created
+# when enabling this service file. Not straight-forward because service files
+# can refer to other service files using Also=.
+sub get_link_closure {
+ my ($scriptname, $service_path, @visited) = @_;
+ assertnotdpkgroot($service_path, "get_link_closure");
+
+ my @links;
+ my @wants_dirs;
+
+ my $unit_name = basename($service_path);
+ my $wanted_target = $unit_name;
+
+ # 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.
+ open my $fh, '<', "$dpkg_root$service_path" or error("unable to read $dpkg_root$service_path");
+ while (my $line = <$fh>) {
+ chomp($line);
+ my $service_link;
+
+ if ($line =~ /^\s*(WantedBy|RequiredBy)=(.+)$/i) {
+ for my $value (split(/\s+/, $2)) {
+ $value =~ s/^(["'])(.*)\g1$/$2/;
+ my $wants_dir = "/etc/systemd/$instance/$value";
+ $wants_dir .= '.wants' if $1 eq 'WantedBy';
+ $wants_dir .= '.requires' if $1 eq 'RequiredBy';
+ push @wants_dirs, "$wants_dir/";
+ }
+ }
+
+ if ($line =~ /^\s*Also=(.+)$/i) {
+ for my $value (split(/\s+/, $1)) {
+ $value =~ s/^(["'])(.*)\g1$/$2/;
+ if ($value ne $unit_name and not grep $_ eq $value, @visited) {
+ # We can end up in an infinite recursion, so remember what units we
+ # already processed to break it
+ push @visited, $value;
+ push @links, get_link_closure($value, find_unit($value), @visited);
+ }
+ }
+ }
+
+ if ($line =~ /^\s*Alias=(.+)$/i) {
+ for my $value (split(/\s+/, $1)) {
+ $value =~ s/^(["'])(.*)\g1$/$2/;
+ if ($value ne $unit_name) {
+ push @links, { dest => $service_path, src => "/etc/systemd/$instance/$1" };
+ }
+ }
+ }
+
+ if ($line =~ /^\s*DefaultInstance=\s*(["']?+)(.+)\g1\s*$/i) {
+ $wanted_target = $2;
+ $wanted_target = $unit_name =~ s/^(.*\@)(\.\w+)$/$1$wanted_target$2/r;
+ }
+ }
+ close($fh);
+
+ for my $wants_dir (@wants_dirs) {
+ push @links, { dest => $service_path, src => $wants_dir . $wanted_target };
+ }
+
+ return @links;
+}
+
+sub all_links_installed {
+ my ($scriptname, $service_path) = @_;
+
+ my @links = get_link_closure($scriptname, $service_path);
+ foreach my $link (@links) {
+ assertnotdpkgroot($link->{src}, "all_links_installed");
+ }
+ my @missing_links = grep { ! -l "$dpkg_root$_->{src}" } @links;
+
+ return (@missing_links == 0);
+}
+
+sub no_link_installed {
+ my ($scriptname, $service_path) = @_;
+
+ my @links = get_link_closure($scriptname, $service_path);
+ foreach my $link (@links) {
+ assertnotdpkgroot($link->{src}, "all_links_installed");
+ }
+ my @existing_links = grep { -l "$dpkg_root$_->{src}" } @links;
+
+ return (@existing_links == 0);
+}
+
+sub enable {
+ my ($scriptname, $service_path) = @_;
+ if ($has_systemctl) {
+ # We use 'systemctl preset' on the initial installation only.
+ # On upgrade, we manually add the missing symlinks only if the
+ # service already has some links installed. Using 'systemctl
+ # preset' allows administrators and downstreams to alter the
+ # enable policy using systemd-native tools.
+ my $create_links = 0;
+ if (debian_installed($scriptname)) {
+ $create_links = 1 unless no_link_installed($scriptname, $service_path);
+ } else {
+ debug "Using systemctl preset to enable $scriptname";
+ my $systemd_root = '/';
+ if ($dpkg_root ne '') {
+ $systemd_root = $dpkg_root;
+ }
+ system("systemctl",
+ "--root=$systemd_root",
+ $instance eq "user" ? "--global" : "--system",
+ "--preset-mode=enable-only",
+ "preset", $scriptname) == 0
+ or error("systemctl preset failed on $scriptname: $!");
+ }
+ make_systemd_links($scriptname, $service_path, create_links => $create_links);
+ } else {
+ # We create all the symlinks ourselves
+ make_systemd_links($scriptname, $service_path);
+ }
+}
+
+sub make_systemd_links {
+ my ($scriptname, $service_path, %opts) = @_;
+ $opts{'create_links'} //= 1;
+
+ my $dsh_state = dsh_state_path($scriptname);
+
+ my @links = get_link_closure($scriptname, $service_path);
+ for my $link (@links) {
+ my $service_path = $link->{dest};
+ my $service_link = $link->{src};
+
+ record_in_statefile($dsh_state, $service_link);
+
+ my $statefile = $service_link;
+ $statefile =~ s,^/etc/systemd/$instance/,$enabled_state_dir/,;
+ $service_link = "$dpkg_root$service_link";
+ assertdpkgroot($statefile, "make_systemd_links");
+ assertdpkgroot($service_link, "make_systemd_links");
+ assertnotdpkgroot($service_path, "make_systemd_links");
+ next if -e $statefile;
+
+ if ($opts{'create_links'} && ! -l $service_link) {
+ make_path(dirname($service_link));
+ symlink($service_path, $service_link) or
+ error("unable to link $service_link to $service_path: $!");
+ $changed_sth = 1;
+ }
+
+ # Store the fact that we ran enable for this service_path,
+ # so that we can skip enable the next time.
+ # This allows us to call deb-systemd-helper unconditionally
+ # and still only enable unit files on the initial installation
+ # of a package.
+ make_path(dirname($statefile));
+ open(my $fh, '>>', $statefile) or error("Failed to create/touch $statefile");
+ close($fh) or error("Failed to create/touch $statefile");
+ }
+
+}
+
+# In contrary to make_systemd_links(), which only modifies the state file in an
+# append-only fashion, update_state() can also remove entries from the state
+# file.
+#
+# The distinction is important because update_state() should only be called
+# when the unit file(s) are guaranteed to be on-disk, e.g. on package updates,
+# but not on package removals.
+sub update_state {
+ my ($scriptname, $service_path) = @_;
+
+ my $dsh_state = dsh_state_path($scriptname);
+ my @links = get_link_closure($scriptname, $service_path);
+ assertdpkgroot($dsh_state, "update_state");
+
+ debug "Old state file contents: " .
+ Data::Dumper::Dumper([ state_file_entries($dsh_state) ]);
+
+ make_path(dirname($dsh_state));
+ my ($outfh, $tmpname) = tempfile('.stateXXXXX',
+ DIR => dirname($dsh_state),
+ SUFFIX => '.tmp',
+ UNLINK => 0);
+ chmod(0644, $tmpname);
+ for my $link (@links) {
+ assertnotdpkgroot($link->{src}, "update_state");
+ print $outfh $link->{src} . "\n";
+ }
+ close($outfh) or error("Failed to close $tmpname");
+
+ debug "Renaming temp file $tmpname to state file $dsh_state";
+ rename($tmpname, $dsh_state) or
+ error("Unable to move $tmpname to $dsh_state");
+
+ debug "New state file contents: " .
+ Data::Dumper::Dumper([ state_file_entries($dsh_state) ]);
+}
+
+sub was_enabled {
+ my ($scriptname) = @_;
+
+ my @entries = state_file_entries(dsh_state_path($scriptname));
+ debug "Contents: " . Data::Dumper::Dumper(\@entries);
+
+ for my $link (@entries) {
+ assertdpkgroot($link, "was_enabled");
+ if (! -l $link) {
+ debug "Link $link is missing, considering $scriptname was-disabled.";
+ return 0;
+ }
+ }
+
+ debug "All links present, considering $scriptname was-enabled.";
+ return 1;
+}
+
+sub debian_installed {
+ my ($scriptname) = @_;
+ return -f dsh_state_path($scriptname);
+}
+
+sub remove_links {
+ my ($service_path) = @_;
+
+ my $dsh_state = dsh_state_path($service_path);
+ my @entries = state_file_entries($dsh_state);
+ debug "Contents: " . Data::Dumper::Dumper(\@entries);
+ assertdpkgroot($dsh_state, "remove_links");
+ assertnotdpkgroot($service_path, "remove_links");
+
+ if (is_purge()) {
+ unlink($dsh_state) if -e $dsh_state;
+ }
+
+ # Also disable all the units which were enabled when this one was enabled.
+ for my $link (@entries) {
+ # Delete the corresponding state file:
+ # • Always when purging
+ # • If the user did not disable (= link still exists) the service.
+ # If we don’t do this, the link will be deleted a few lines down,
+ # but not re-created when re-installing the package.
+ assertdpkgroot($link, "remove_links");
+ if (is_purge() || -l $link) {
+ my $link_state = $link;
+ $link_state =~ s,^\Q$dpkg_root\E/etc/systemd/$instance/,$enabled_state_dir/,;
+ unlink($link_state);
+ }
+
+ next unless -l $link;
+ unlink($link) or
+ print STDERR "$0: unable to remove '$link': $!\n";
+
+ $changed_sth = 1;
+ }
+
+ # Read $service_path, recurse for all Also= units.
+ # This might not work when $service_path was already deleted,
+ # i.e. after apt-get remove. In this case we just return
+ # silently in order to not confuse the user about whether
+ # disabling actually worked or not — the case is handled by
+ # dh_installsystemd generating an appropriate disable
+ # command by parsing the service file at debhelper-time.
+ open(my $fh, '<', "$dpkg_root$service_path") or return;
+ while (my $line = <$fh>) {
+ chomp($line);
+ my $service_link;
+
+ if ($line =~ /^\s*Also=(.+)$/i) {
+ remove_links(find_unit($1));
+ }
+ }
+ close($fh);
+}
+
+# Recursively deletes a directory structure, if all (!) components are empty,
+# e.g. to clean up after purging.
+sub rmdir_if_empty {
+ my ($dir) = @_;
+
+ debug "rmdir_if_empty $dir";
+
+ rmdir_if_empty($_) for (grep { -d } <$dir/*>);
+
+ if (!rmdir($dir)) {
+ debug "rmdir($dir) failed ($!)";
+ }
+}
+
+sub mask_service {
+ my ($scriptname, $service_path) = @_;
+
+ my $mask_link = "$dpkg_root/etc/systemd/$instance/" . basename($service_path);
+
+ if (-e $mask_link) {
+ # If the link already exists, don’t do anything.
+ return if -l $mask_link && readlink($mask_link) eq '/dev/null';
+
+ # If the file already exists, the user most likely copied the .service
+ # file to /etc/ to change it in some way. In this case we don’t need to
+ # mask the .service in the first place, since it will not be removed by
+ # dpkg.
+ debug "$mask_link already exists, not masking.";
+ return;
+ }
+
+ make_path(dirname($mask_link));
+ # clean up after possible leftovers from Alias= to self (LP#1439793)
+ unlink($mask_link);
+ symlink('/dev/null', $mask_link) or
+ error("unable to link $mask_link to /dev/null: $!");
+ $changed_sth = 1;
+
+ my $statefile = $mask_link;
+ $statefile =~ s,^\Q$dpkg_root\E/etc/systemd/$instance/,$masked_state_dir/,;
+
+ # Store the fact that we masked this service, so that we can unmask it on
+ # installation time. We cannot unconditionally unmask because that would
+ # interfere with the user’s decision to mask a service.
+ make_path(dirname($statefile));
+ open(my $fh, '>>', $statefile) or error("Failed to create/touch $statefile");
+ close($fh) or error("Failed to create/touch $statefile");
+}
+
+sub unmask_service {
+ my ($scriptname, $service_path) = @_;
+
+ my $mask_link = "$dpkg_root/etc/systemd/$instance/" . basename($service_path);
+
+ # Not masked? Nothing to do.
+ return unless -e $mask_link;
+
+ if (! -l $mask_link || readlink($mask_link) ne '/dev/null') {
+ debug "Not unmasking $mask_link because it is not a link to /dev/null";
+ return;
+ }
+
+ my $statefile = $mask_link;
+ $statefile =~ s,^\Q$dpkg_root\E/etc/systemd/$instance/,$masked_state_dir/,;
+
+ if (! -e $statefile) {
+ debug "Not unmasking $mask_link because the state file $statefile does not exist";
+ return;
+ }
+
+ unlink($mask_link) or
+ error("unable to remove $mask_link: $!");
+ $changed_sth = 1;
+ unlink($statefile);
+}
+
+my $result = GetOptions(
+ "quiet" => \$quiet,
+ "user" => sub { $instance = 'user'; },
+ "system" => sub { $instance = 'system'; }, # default
+);
+
+if ($instance eq 'user') {
+ debug "is user unit = yes";
+ $enabled_state_dir = $dpkg_root . USER_INSTANCE_ENABLED_STATE_DIR;
+ $masked_state_dir = $dpkg_root . USER_INSTANCE_MASKED_STATE_DIR;
+}
+
+my $action = shift;
+if (!defined($action)) {
+ # Called without arguments. Explain that this script should not be run interactively.
+ print "$0 is a program which should be called by dpkg maintscripts only.\n";
+ print "Please do not run it interactively, ever. Also see the manpage deb-systemd-helper(1).\n";
+ exit 0;
+}
+
+if (!$ENV{DPKG_MAINTSCRIPT_PACKAGE}) {
+ print STDERR "$0 was not called from dpkg. Exiting.\n";
+ exit 1;
+}
+
+if ($action eq 'purge') {
+ $ENV{_DEB_SYSTEMD_HELPER_PURGE} = 1;
+ $action = 'disable';
+}
+
+debug "is purge = " . (is_purge() ? "yes" : "no");
+
+my $rc = 0;
+if ($action eq 'is-enabled' ||
+ $action eq 'was-enabled' ||
+ $action eq 'debian-installed') {
+ $rc = 1;
+}
+for my $scriptname (@ARGV) {
+ my $service_path = find_unit($scriptname);
+
+ debug "action = $action, scriptname = $scriptname, service_path = $service_path";
+
+ if ($action eq 'is-enabled') {
+ my $enabled = all_links_installed($scriptname, $service_path);
+ print STDERR ($enabled ? "enabled\n" : "disabled\n") unless $quiet;
+ $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($scriptname);
+ print STDERR ($enabled ? "enabled\n" : "disabled\n") unless $quiet;
+ $rc = 0 if $enabled;
+ }
+
+ if ($action eq 'update-state') {
+ update_state($scriptname, $service_path);
+ }
+
+ if ($action eq 'debian-installed') {
+ $rc = 0 if debian_installed($scriptname);
+ }
+
+ if ($action eq 'reenable') {
+ remove_links($service_path);
+ make_systemd_links($scriptname, $service_path);
+ }
+
+ if ($action eq 'disable') {
+ remove_links($service_path);
+ # Clean up the state dir if it’s empty, or at least clean up all empty
+ # subdirectories. Necessary to cleanly pass a piuparts run.
+ rmdir_if_empty($dpkg_root . SYSTEM_INSTANCE_ENABLED_STATE_DIR);
+ rmdir_if_empty($dpkg_root . USER_INSTANCE_ENABLED_STATE_DIR);
+
+ # Same with directories below /etc/systemd, where we create symlinks.
+ # If systemd is not installed (and no other package shipping service
+ # files), this would make piuparts fail, too.
+ rmdir_if_empty($_) for (grep { -d } <$dpkg_root/etc/systemd/system/*>);
+ rmdir_if_empty($_) for (grep { -d } <$dpkg_root/etc/systemd/user/*>);
+ }
+
+ if ($action eq 'enable') {
+ enable($scriptname, $service_path);
+ }
+
+ if ($action eq 'mask') {
+ mask_service($scriptname, $service_path);
+ }
+
+ if ($action eq 'unmask') {
+ unmask_service($scriptname, $service_path);
+ # Clean up the state dir if it’s empty, or at least clean up all empty
+ # subdirectories. Necessary to cleanly pass a piuparts run.
+ rmdir_if_empty($dpkg_root . SYSTEM_INSTANCE_MASKED_STATE_DIR);
+ rmdir_if_empty($dpkg_root . USER_INSTANCE_MASKED_STATE_DIR);
+ }
+}
+
+# If we changed anything and this machine is running systemd, tell
+# systemd to reload so that it will immediately pick up our
+# changes.
+if (!length $ENV{DPKG_ROOT} && $changed_sth && $instance eq 'system' && -d "/run/systemd/system") {
+ system("systemctl", "daemon-reload");
+}
+
+exit $rc;
+
+=head1 AUTHOR
+
+Michael Stapelberg <stapelberg@debian.org>
+
+=cut
diff --git a/script/deb-systemd-invoke b/script/deb-systemd-invoke
new file mode 100755
index 0000000..6d49249
--- /dev/null
+++ b/script/deb-systemd-invoke
@@ -0,0 +1,187 @@
+#!/usr/bin/perl
+# vim:ts=4:sw=4:expandtab
+# © 2013 Michael Stapelberg <stapelberg@debian.org>
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * Neither the name of Michael Stapelberg nor the
+# names of contributors may be used to endorse or promote products
+# derived from this software without specific prior written permission.
+# .
+# THIS SOFTWARE IS PROVIDED BY Michael Stapelberg ''AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL Michael Stapelberg BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+=head1 NAME
+
+deb-systemd-invoke - wrapper around systemctl, respecting policy-rc.d
+
+=head1 SYNOPSIS
+
+B<deb-systemd-invoke> [B<--user>] start|stop|restart S<I<unit file> ...>
+B<deb-systemd-invoke> [B<--user>] [B<--no-dbus>] daemon-reload|daemon-reexec
+
+=head1 DESCRIPTION
+
+B<deb-systemd-invoke> is a Debian-specific helper script which asks
+/usr/sbin/policy-rc.d before performing a systemctl call.
+
+B<deb-systemd-invoke> is intended to be used from maintscripts to manage
+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
+the systemd enabled state in case they are not running systemd.
+
+=cut
+
+use strict;
+use warnings;
+use Getopt::Long; # in core since Perl 5
+
+if (@ARGV < 2) {
+ print STDERR "Syntax: $0 <action> [<unit file> [<unit file> ...]]\n";
+ exit 1;
+}
+
+my $is_system = 1;
+my $use_dbus = 1;
+my @instances = ();
+my $result = GetOptions(
+ "user" => sub { $is_system = 0; },
+ "system" => sub { $is_system = 1; }, # default
+ "no-dbus" => sub { $use_dbus = 0; },
+);
+
+my $policyhelper = '/usr/sbin/policy-rc.d';
+if (length $ENV{DPKG_ROOT}) {
+ $policyhelper = $ENV{DPKG_ROOT} . $policyhelper;
+}
+my @units = @ARGV;
+my $action = shift @units;
+if (-x $policyhelper) {
+ for my $unit (@units) {
+ system(qq|$policyhelper $unit "$action"|);
+
+ # 0 or 104 means run
+ # 101 means do not run
+ my $exitcode = ($? >> 8);
+ if ($exitcode == 101) {
+ print STDERR "$policyhelper returned 101, not running '" . join(' ', @ARGV) . "'\n";
+ exit 0;
+ } elsif ($exitcode != 104 && $exitcode != 0) {
+ print STDERR "deb-systemd-invoke only supports $policyhelper return codes 0, 101, and 104!\n";
+ print STDERR "Got return code $exitcode, ignoring.\n";
+ }
+ }
+}
+
+if (!$is_system) {
+ # '--machine <ID>@' was added in v250 and v249.10, before that we can't talk to arbitrary user instances
+ my $systemctl_version = `systemctl --version --quiet | sed -n -r "s/systemd ([0-9]+) \\(.*/\\1/p"`;
+ chomp ($systemctl_version);
+ if (system('dpkg', '--compare-versions', $systemctl_version, 'ge', '249') != 0) {
+ print STDERR "systemctl version $systemctl_version does not support acting on user instance, skipping\n";
+ exit 0;
+ }
+
+ # Each user instance of the manager has a corresponding user@<id<.service unit.
+ # Get the full list of IDs, so that we can talk to each user instance to start/stop
+ # user units.
+ @instances = `systemctl --no-legend --quiet list-units 'user@*' | sed -n -r 's/.*user@([0-9]+).service.*/\\1/p'`;
+} else {
+ push @instances, 'system';
+}
+
+# If the job is disabled and is not currently running, the job is not started or restarted.
+# However, if the job is disabled but has been forced into the running state, we *do* stop
+# and restart it since this is expected behaviour for the admin who forced the start.
+# We don't autostart static units either.
+if ($action eq "start" || $action eq "restart") {
+ my $global_exit_code = 0;
+ my @start_units = ();
+
+ for my $instance (@instances) {
+ my @instance_args = ();
+
+ if ($instance eq 'system') {
+ push @instance_args, '--system';
+ } else {
+ chomp ($instance);
+ push @instance_args, '--user', '--machine', "$instance@";
+ }
+
+ for my $unit (@units) {
+ my $unit_installed = 0;
+ my $enabled_output = `systemctl @instance_args is-enabled -- '$unit'`;
+ # matching enabled and enabled-runtime as an installed non static unit
+ if ($enabled_output =~ /enabled/) {
+ $unit_installed = 1;
+ }
+ system('systemctl', @instance_args, '--quiet', 'is-active', '--', $unit);
+ my $unit_active = $?>>8 == 0 ? 1 : 0;
+ if (!$unit_installed && $action eq "start") {
+ print STDERR "$unit is a disabled or a static unit, not starting it.\n";
+ } elsif (!$unit_installed && !$unit_active && $action eq "restart") {
+ print STDERR "$unit is a disabled or a static unit not running, not starting it.\n";
+ }
+ else {
+ push @start_units, $unit;
+ }
+ }
+ if (@start_units) {
+ system('systemctl', '--quiet', @instance_args, $action, @start_units) == 0 or die("Could not execute systemctl: $!");
+ }
+ }
+ exit(0);
+} elsif ($action eq "stop" && !$is_system) {
+ my $global_exit_code = 0;
+
+ for my $instance (@instances) {
+ chomp ($instance);
+ system('systemctl', '--quiet', '--user', '--machine', "$instance@", $action, @units);
+ }
+ exit(0);
+} elsif (($action eq "daemon-reload" || $action eq "daemon-reexec") && !$is_system && $use_dbus) {
+ my $global_exit_code = 0;
+
+ for my $instance (@instances) {
+ chomp ($instance);
+ system('systemctl', '--quiet', '--user', '--machine', "$instance@", $action);
+ }
+ exit(0);
+} elsif (($action eq "daemon-reload" || $action eq "daemon-reexec") && !$use_dbus) {
+ my $global_exit_code = 0;
+ my $signal;
+
+ if ($action eq "daemon-reload") {
+ $signal = 'SIGHUP';
+ } else {
+ $signal = 'SIGRTMIN+25';
+ }
+
+ if ($is_system) {
+ system('kill', '-s', $signal, '1');
+ } else {
+ system('systemctl', '--quiet', 'kill', '--kill-whom=main', '--signal', $signal, 'user@*.service');
+ }
+
+ exit(0);
+} else {
+ exec('systemctl', @ARGV);
+}
diff --git a/script/invoke-rc.d b/script/invoke-rc.d
new file mode 100755
index 0000000..ca1bdbe
--- /dev/null
+++ b/script/invoke-rc.d
@@ -0,0 +1,574 @@
+#!/bin/sh
+# vim: ft=sh
+#
+# invoke-rc.d.sysvinit - Executes initscript actions
+#
+# SysVinit /etc/rc?.d version for Debian's sysvinit package
+#
+# Copyright (C) 2000,2001 Henrique de Moraes Holschuh <hmh@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation; either version 2 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+
+# Constants
+RUNLEVELHELPER=/sbin/runlevel
+POLICYHELPER=$DPKG_ROOT/usr/sbin/policy-rc.d
+INITDPREFIX=/etc/init.d/
+RCDPREFIX=/etc/rc
+
+# Options
+BEQUIET=
+MODE=
+ACTION=
+FALLBACK=
+NOFALLBACK=
+FORCE=
+RETRY=
+RETURNFAILURE=
+RC=
+is_systemd=
+is_openrc=
+SKIP_SYSTEMD_NATIVE=
+
+# Shell options
+set +e
+
+dohelp () {
+ #
+ # outputs help and usage
+ #
+cat <<EOF
+
+invoke-rc.d, Debian/SysVinit (/etc/rc?.d) initscript subsystem.
+Copyright (c) 2000,2001 Henrique de Moraes Holschuh <hmh@debian.org>
+
+Usage:
+ invoke-rc.d [options] <basename> <action> [extra parameters]
+
+ basename - Initscript ID, as per update-rc.d(8)
+ action - Initscript action. Known actions are:
+ start, [force-]stop, [try-]restart,
+ [force-]reload, status
+ WARNING: not all initscripts implement all of the above actions.
+
+ extra parameters are passed as is to the initscript, following
+ the action (first initscript parameter).
+
+Options:
+ --quiet
+ Quiet mode, no error messages are generated.
+ --force
+ Try to run the initscript regardless of policy and subsystem
+ non-fatal errors.
+ --try-anyway
+ Try to run init script even if a non-fatal error is found.
+ --disclose-deny
+ Return status code 101 instead of status code 0 if
+ initscript action is denied by local policy rules or
+ runlevel constrains.
+ --query
+ Returns one of status codes 100-106, does not run
+ the initscript. Implies --disclose-deny and --no-fallback.
+ --no-fallback
+ Ignores any fallback action requests by the policy layer.
+ Warning: this is usually a very *bad* idea for any actions
+ other than "start".
+ --skip-systemd-native
+ Exits before doing anything if a systemd environment is detected
+ and the requested service is a native systemd unit.
+ This is useful for maintainer scripts that want to defer systemd
+ actions to deb-systemd-invoke
+ --help
+ Outputs help message to stdout
+
+EOF
+}
+
+printerror () {
+ #
+ # prints an error message
+ # $* - error message
+ #
+if test x${BEQUIET} = x ; then
+ echo `basename $0`: "$*" >&2
+fi
+}
+
+formataction () {
+ #
+ # formats a list in $* into $printaction
+ # for human-friendly printing to stderr
+ # and sets $naction to action or actions
+ #
+printaction=`echo $* | sed 's/ /, /g'`
+if test $# -eq 1 ; then
+ naction=action
+else
+ naction=actions
+fi
+}
+
+querypolicy () {
+ #
+ # queries policy database
+ # returns: $RC = 104 - ok, run
+ # $RC = 101 - ok, do not run
+ # other - exit with status $RC, maybe run if $RETRY
+ # initial status of $RC is taken into account.
+ #
+
+policyaction="${ACTION}"
+if test x${RC} = "x101" ; then
+ if test "${ACTION}" = "start" || test "${ACTION}" = "restart" || test "${ACTION}" = "try-restart"; then
+ policyaction="(${ACTION})"
+ fi
+fi
+
+if test "x${POLICYHELPER}" != x && test -x "${POLICYHELPER}" ; then
+ FALLBACK=`${POLICYHELPER} ${BEQUIET} ${INITSCRIPTID} "${policyaction}" ${RL}`
+ RC=$?
+ formataction ${ACTION}
+ case ${RC} in
+ 0) RC=104
+ ;;
+ 1) RC=105
+ ;;
+ 101) if test x${FORCE} != x ; then
+ printerror Overriding policy-rc.d denied execution of ${printaction}.
+ RC=104
+ else
+ printerror policy-rc.d denied execution of ${printaction}.
+ fi
+ ;;
+ esac
+ if test x${MODE} != xquery ; then
+ case ${RC} in
+ 105) printerror policy-rc.d query returned \"behaviour undefined\",
+ printerror assuming \"${printaction}\" is allowed.
+ RC=104
+ ;;
+ 106) formataction ${FALLBACK}
+ if test x${FORCE} = x ; then
+ if test x${NOFALLBACK} = x ; then
+ ACTION="${FALLBACK}"
+ printerror executing ${naction} \"${printaction}\" instead due to policy-rc.d request.
+ RC=104
+ else
+ printerror ignoring policy-rc.d fallback request: ${printaction}.
+ RC=101
+ fi
+ else
+ printerror ignoring policy-rc.d fallback request: ${printaction}.
+ RC=104
+ fi
+ ;;
+ esac
+ fi
+ case ${RC} in
+ 100|101|102|103|104|105|106) ;;
+ *) printerror WARNING: policy-rc.d returned unexpected error status ${RC}, 102 used instead.
+ RC=102
+ ;;
+ esac
+else
+ if test ! -e "/sbin/init" ; then
+ if test x${FORCE} != x ; then
+ printerror "WARNING: No init system and policy-rc.d missing, but force specified so proceeding."
+ else
+ printerror "WARNING: No init system and policy-rc.d missing! Defaulting to block."
+ RC=101
+ fi
+ fi
+
+ if test x${RC} = x ; then
+ RC=104
+ fi
+fi
+return
+}
+
+verifyparameter () {
+ #
+ # Verifies if $1 is not null, and $# = 1
+ #
+if test $# -eq 0 ; then
+ printerror syntax error: invalid empty parameter
+ exit 103
+elif test $# -ne 1 ; then
+ printerror syntax error: embedded blanks are not allowed in \"$*\"
+ exit 103
+fi
+return
+}
+
+##
+## main
+##
+
+## Verifies command line arguments
+
+if test $# -eq 0 ; then
+ printerror syntax error: missing required parameter, --help assumed
+ dohelp
+ exit 103
+fi
+
+state=I
+while test $# -gt 0 && test ${state} != III ; do
+ case "$1" in
+ --help) dohelp
+ exit 0
+ ;;
+ --quiet) BEQUIET=--quiet
+ ;;
+ --force) FORCE=yes
+ RETRY=yes
+ ;;
+ --try-anyway)
+ RETRY=yes
+ ;;
+ --disclose-deny)
+ RETURNFAILURE=yes
+ ;;
+ --query) MODE=query
+ RETURNFAILURE=yes
+ ;;
+ --no-fallback)
+ NOFALLBACK=yes
+ ;;
+ --skip-systemd-native) SKIP_SYSTEMD_NATIVE=yes
+ ;;
+ --*) printerror syntax error: unknown option \"$1\"
+ exit 103
+ ;;
+ *) case ${state} in
+ I) verifyparameter $1
+ INITSCRIPTID=$1
+ ;;
+ II) verifyparameter $1
+ ACTION=$1
+ ;;
+ esac
+ state=${state}I
+ ;;
+ esac
+ shift
+done
+
+if test ${state} != III ; then
+ printerror syntax error: missing required parameter
+ exit 103
+fi
+
+#NOTE: It may not be obvious, but "$@" from this point on must expand
+#to the extra initscript parameters, except inside functions.
+
+if test -d /run/systemd/system ; then
+ is_systemd=1
+ UNIT="${INITSCRIPTID%.sh}.service"
+elif test -f /run/openrc/softlevel ; then
+ is_openrc=1
+elif test ! -f "${INITDPREFIX}${INITSCRIPTID}" ; then
+ ## Verifies if the given initscript ID is known
+ ## For sysvinit, this error is critical
+ printerror unknown initscript, ${INITDPREFIX}${INITSCRIPTID} not found.
+fi
+
+## Queries sysvinit for the current runlevel
+if [ ! -x ${RUNLEVELHELPER} ] || ! RL=`${RUNLEVELHELPER}`; then
+ if [ -n "$is_systemd" ] && systemctl is-active --quiet sysinit.target; then
+ # under systemd, the [2345] runlevels are only set upon reaching them;
+ # if we are past sysinit.target (roughly equivalent to rcS), consider
+ # this as runlevel 5 (this is only being used for validating rcN.d
+ # symlinks, so the precise value does not matter much)
+ RL=5
+ else
+ printerror "could not determine current runlevel"
+ # this usually fails in schroots etc., ignore failure (#823611)
+ RL=
+ fi
+fi
+# strip off previous runlevel
+RL=${RL#* }
+
+## Running ${RUNLEVELHELPER} to get current runlevel do not work in
+## the boot runlevel (scripts in /etc/rcS.d/), as /var/run/utmp
+## contains runlevel 0 or 6 (written at shutdown) at that point.
+if test x${RL} = x0 || test x${RL} = x6 ; then
+ if ps -fp 1 | grep -q 'init boot' ; then
+ RL=S
+ fi
+fi
+
+## Handles shutdown sequences VERY safely
+## i.e.: forget about policy, and do all we can to run the script.
+## BTW, why the heck are we being run in a shutdown runlevel?!
+if test x${RL} = x0 || test x${RL} = x6 ; then
+ FORCE=yes
+ RETRY=yes
+ POLICYHELPER=
+ BEQUIET=
+ printerror "-----------------------------------------------------"
+ printerror "WARNING: 'invoke-rc.d ${INITSCRIPTID} ${ACTION}' called"
+ printerror "during shutdown sequence."
+ printerror "enabling safe mode: initscript policy layer disabled"
+ printerror "-----------------------------------------------------"
+fi
+
+## Verifies the existance of proper S??initscriptID and K??initscriptID
+## *links* in the proper /etc/rc?.d/ directory
+verifyrclink () {
+ #
+ # verifies if parameters are non-dangling symlinks
+ # all parameters are verified
+ #
+ doexit=
+ while test $# -gt 0 ; do
+ if test ! -L "$1" ; then
+ printerror not a symlink: $1
+ doexit=102
+ fi
+ if test ! -f "$1" ; then
+ printerror dangling symlink: $1
+ doexit=102
+ fi
+ shift
+ done
+ if test x${doexit} != x && test x${RETRY} = x; then
+ exit ${doexit}
+ fi
+ return 0
+}
+
+testexec () {
+ #
+ # returns true if any of the parameters is
+ # executable (after following links)
+ #
+ while test $# -gt 0 ; do
+ if test -x "$1" ; then
+ return 0
+ fi
+ shift
+ done
+ return 1
+}
+
+RC=
+
+###
+### LOCAL POLICY: Enforce that the script/unit is enabled. For SysV init
+### scripts, this needs a start entry in either runlevel S or current runlevel
+### to allow start or restart.
+if [ -n "$is_systemd" ]; then
+ case ${ACTION} in
+ start|restart|try-restart)
+ # If a package ships both init script and systemd service file, the
+ # systemd unit will not be enabled by the time invoke-rc.d is called
+ # (with current debhelper sequence). This would make systemctl is-enabled
+ # report the wrong status, and then the service would not be started.
+ # This check cannot be removed as long as we support not passing --skip-systemd-native
+
+ if systemctl --quiet is-enabled "${UNIT}" 2>/dev/null || \
+ ls ${RCDPREFIX}[S2345].d/S[0-9][0-9]${INITSCRIPTID} >/dev/null 2>&1; then
+ RC=104
+ elif systemctl --quiet is-active "${UNIT}" 2>/dev/null; then
+ RC=104
+ else
+ RC=101
+ fi
+ ;;
+ esac
+else
+ # we do handle multiple links per runlevel
+ # but we don't handle embedded blanks in link names :-(
+ if test x${RL} != x ; then
+ SLINK=`ls -d -Q ${RCDPREFIX}${RL}.d/S[0-9][0-9]${INITSCRIPTID} 2>/dev/null | xargs`
+ KLINK=`ls -d -Q ${RCDPREFIX}${RL}.d/K[0-9][0-9]${INITSCRIPTID} 2>/dev/null | xargs`
+ SSLINK=`ls -d -Q ${RCDPREFIX}S.d/S[0-9][0-9]${INITSCRIPTID} 2>/dev/null | xargs`
+
+ verifyrclink ${SLINK} ${KLINK} ${SSLINK}
+ fi
+
+ case ${ACTION} in
+ start|restart|try-restart)
+ if testexec ${SLINK} ; then
+ RC=104
+ elif testexec ${KLINK} ; then
+ RC=101
+ elif testexec ${SSLINK} ; then
+ RC=104
+ else
+ RC=101
+ fi
+ ;;
+ esac
+fi
+
+# test if /etc/init.d/initscript is actually executable
+_executable=
+if [ -n "$is_systemd" ]; then
+ _executable=1
+elif testexec "${INITDPREFIX}${INITSCRIPTID}"; then
+ _executable=1
+fi
+if [ "$_executable" = "1" ]; then
+ if test x${RC} = x && test x${MODE} = xquery ; then
+ RC=105
+ fi
+
+ # call policy layer
+ querypolicy
+ case ${RC} in
+ 101|104)
+ ;;
+ *) if test x${MODE} != xquery ; then
+ printerror policy-rc.d returned error status ${RC}
+ if test x${RETRY} = x ; then
+ exit ${RC}
+ else
+ RC=102
+ fi
+ fi
+ ;;
+ esac
+else
+ ###
+ ### LOCAL INITSCRIPT POLICY: non-executable initscript; deny exec.
+ ### (this is common sense, actually :^P )
+ ###
+ RC=101
+fi
+
+## Handles --query
+if test x${MODE} = xquery ; then
+ exit ${RC}
+fi
+
+
+setechoactions () {
+ if test $# -gt 1 ; then
+ echoaction=true
+ else
+ echoaction=
+ fi
+}
+getnextaction () {
+ saction=$1
+ shift
+ ACTION="$@"
+}
+
+## Executes initscript
+## note that $ACTION is a space-separated list of actions
+## to be attempted in order until one suceeds.
+if test x${FORCE} != x || test ${RC} -eq 104 ; then
+ if [ -n "$is_systemd" ] || testexec "${INITDPREFIX}${INITSCRIPTID}" ; then
+ RC=102
+ setechoactions ${ACTION}
+ while test ! -z "${ACTION}" ; do
+ getnextaction ${ACTION}
+ if test ! -z ${echoaction} ; then
+ printerror executing initscript action \"${saction}\"...
+ fi
+
+ if [ -n "$is_systemd" ]; then
+ if [ "$SKIP_SYSTEMD_NATIVE" = yes ] ; then
+ case $(systemctl show --value --property SourcePath "${UNIT}") in
+ /etc/init.d/*)
+ ;;
+ *)
+ # We were asked to skip native systemd units, and this one was not generated by the sysv generator
+ # exit cleanly
+ exit 0
+ ;;
+ esac
+
+ fi
+ _state=$(systemctl -p LoadState show "${UNIT}" 2>/dev/null)
+
+ case $saction in
+ start|restart|try-restart)
+ [ "$_state" != "LoadState=masked" ] || exit 0
+ systemctl $sctl_args "${saction}" "${UNIT}" && exit 0
+ ;;
+ stop|status)
+ systemctl $sctl_args "${saction}" "${UNIT}" && exit 0
+ ;;
+ reload)
+ [ "$_state" != "LoadState=masked" ] || exit 0
+ _canreload="$(systemctl -p CanReload show ${UNIT} 2>/dev/null)"
+ # Don't block on reload requests during bootup and shutdown
+ # from units/hooks and simply schedule the task.
+ if ! systemctl --quiet is-system-running; then
+ sctl_args="--no-block"
+ fi
+ if [ "$_canreload" = "CanReload=no" ]; then
+ "${INITDPREFIX}${INITSCRIPTID}" "${saction}" "$@" && exit 0
+ else
+ systemctl $sctl_args reload "${UNIT}" && exit 0
+ fi
+ ;;
+ force-stop)
+ systemctl --signal=KILL kill "${UNIT}" && exit 0
+ ;;
+ force-reload)
+ [ "$_state" != "LoadState=masked" ] || exit 0
+ _canreload="$(systemctl -p CanReload show ${UNIT} 2>/dev/null)"
+ if [ "$_canreload" = "CanReload=no" ]; then
+ systemctl $sctl_args restart "${UNIT}" && exit 0
+ else
+ systemctl $sctl_args reload "${UNIT}" && exit 0
+ fi
+ ;;
+ *)
+ # We try to run non-standard actions by running
+ # the init script directly.
+ "${INITDPREFIX}${INITSCRIPTID}" "${saction}" "$@" && exit 0
+ ;;
+ esac
+ elif [ -n "$is_openrc" ]; then
+ rc-service "${INITSCRIPTID}" "${saction}" && exit 0
+ else
+ "${INITDPREFIX}${INITSCRIPTID}" "${saction}" "$@" && exit 0
+ fi
+ RC=$?
+
+ if test ! -z "${ACTION}" ; then
+ printerror action \"${saction}\" failed, trying next action...
+ fi
+ done
+ printerror initscript ${INITSCRIPTID}, action \"${saction}\" failed.
+ if [ -n "$is_systemd" ] && [ "$saction" = start -o "$saction" = restart -o "$saction" = "try-restart" ]; then
+ systemctl status --full --no-pager "${UNIT}" || true
+ fi
+ exit ${RC}
+ fi
+ exit 102
+fi
+
+## Handles --disclose-deny and denied "status" action (bug #381497)
+if test ${RC} -eq 101 && test x${RETURNFAILURE} = x ; then
+ if test "x${ACTION%% *}" = "xstatus"; then
+ printerror emulating initscript action \"status\", returning \"unknown\"
+ RC=4
+ else
+ RC=0
+ fi
+else
+ formataction ${ACTION}
+ printerror initscript ${naction} \"${printaction}\" not executed.
+fi
+
+exit ${RC}
diff --git a/script/service b/script/service
new file mode 100755
index 0000000..08f69bb
--- /dev/null
+++ b/script/service
@@ -0,0 +1,217 @@
+#!/bin/sh
+
+###########################################################################
+# /usr/bin/service
+#
+# A convenient wrapper for the /etc/init.d init scripts.
+#
+# This script is a modified version of the /sbin/service utility found on
+# Red Hat/Fedora systems (licensed GPLv2+).
+#
+# Copyright (C) 2006 Red Hat, Inc. All rights reserved.
+# Copyright (C) 2008 Canonical Ltd.
+# * August 2008 - Dustin Kirkland <kirkland@canonical.com>
+# Copyright (C) 2013 Michael Stapelberg <stapelberg@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# On Debian GNU/Linux systems, the complete text of the GNU General
+# Public License can be found in `/usr/share/common-licenses/GPL-2'.
+###########################################################################
+
+
+is_ignored_file() {
+ case "$1" in
+ skeleton | README | *.dpkg-dist | *.dpkg-old | rc | rcS | single | reboot | bootclean.sh)
+ return 0
+ ;;
+ esac
+ return 1
+}
+
+VERSION="`basename $0` ver. __VERSION__"
+USAGE="Usage: `basename $0` < option > | --status-all | \
+[ service_name [ command | --full-restart ] ]"
+SERVICE=
+ACTION=
+SERVICEDIR="/etc/init.d"
+OPTIONS=
+is_systemd=
+
+
+if [ $# -eq 0 ]; then
+ echo "${USAGE}" >&2
+ exit 1
+fi
+
+if [ -d /run/systemd/system ]; then
+ is_systemd=1
+fi
+
+cd /
+while [ $# -gt 0 ]; do
+ case "${1}" in
+ --help | -h | --h* )
+ echo "${USAGE}" >&2
+ exit 0
+ ;;
+ --version | -V )
+ echo "${VERSION}" >&2
+ exit 0
+ ;;
+ *)
+ if [ -z "${SERVICE}" -a $# -eq 1 -a "${1}" = "--status-all" ]; then
+ cd ${SERVICEDIR}
+ for SERVICE in * ; do
+ case "${SERVICE}" in
+ functions | halt | killall | single| linuxconf| kudzu)
+ ;;
+ *)
+ if ! is_ignored_file "${SERVICE}" \
+ && [ -x "${SERVICEDIR}/${SERVICE}" ]; then
+ out=$(env -i LANG="$LANG" LANGUAGE="$LANGUAGE" LC_CTYPE="$LC_CTYPE" LC_NUMERIC="$LC_NUMERIC" LC_TIME="$LC_TIME" LC_COLLATE="$LC_COLLATE" LC_MONETARY="$LC_MONETARY" LC_MESSAGES="$LC_MESSAGES" LC_PAPER="$LC_PAPER" LC_NAME="$LC_NAME" LC_ADDRESS="$LC_ADDRESS" LC_TELEPHONE="$LC_TELEPHONE" LC_MEASUREMENT="$LC_MEASUREMENT" LC_IDENTIFICATION="$LC_IDENTIFICATION" LC_ALL="$LC_ALL" PATH="$PATH" TERM="$TERM" "$SERVICEDIR/$SERVICE" status 2>&1)
+ retval=$?
+ if echo "$out" | grep -Fiq "usage:"; then
+ #printf " %s %-60s %s\n" "[?]" "$SERVICE:" "unknown" 1>&2
+ echo " [ ? ] $SERVICE" 1>&2
+ continue
+ else
+ if [ "$retval" = "0" -a -n "$out" ]; then
+ #printf " %s %-60s %s\n" "[+]" "$SERVICE:" "running"
+ echo " [ + ] $SERVICE"
+ continue
+ else
+ #printf " %s %-60s %s\n" "[-]" "$SERVICE:" "NOT running"
+ echo " [ - ] $SERVICE"
+ continue
+ fi
+ fi
+ #env -i LANG="$LANG" LANGUAGE="$LANGUAGE" LC_CTYPE="$LC_CTYPE" LC_NUMERIC="$LC_NUMERIC" LC_TIME="$LC_TIME" LC_COLLATE="$LC_COLLATE" LC_MONETARY="$LC_MONETARY" LC_MESSAGES="$LC_MESSAGES" LC_PAPER="$LC_PAPER" LC_NAME="$LC_NAME" LC_ADDRESS="$LC_ADDRESS" LC_TELEPHONE="$LC_TELEPHONE" LC_MEASUREMENT="$LC_MEASUREMENT" LC_IDENTIFICATION="$LC_IDENTIFICATION" LC_ALL="$LC_ALL" PATH="$PATH" TERM="$TERM" "$SERVICEDIR/$SERVICE" status
+ fi
+ ;;
+ esac
+ done
+ exit 0
+ elif [ $# -eq 2 -a "${2}" = "--full-restart" ]; then
+ SERVICE="${1}"
+ # On systems using systemd, we just perform a normal restart:
+ # A restart with systemd is already a full restart.
+ if [ -n "$is_systemd" ]; then
+ ACTION="restart"
+ else
+ if [ -x "${SERVICEDIR}/${SERVICE}" ]; then
+ env -i LANG="$LANG" LANGUAGE="$LANGUAGE" LC_CTYPE="$LC_CTYPE" LC_NUMERIC="$LC_NUMERIC" LC_TIME="$LC_TIME" LC_COLLATE="$LC_COLLATE" LC_MONETARY="$LC_MONETARY" LC_MESSAGES="$LC_MESSAGES" LC_PAPER="$LC_PAPER" LC_NAME="$LC_NAME" LC_ADDRESS="$LC_ADDRESS" LC_TELEPHONE="$LC_TELEPHONE" LC_MEASUREMENT="$LC_MEASUREMENT" LC_IDENTIFICATION="$LC_IDENTIFICATION" LC_ALL="$LC_ALL" PATH="$PATH" TERM="$TERM" "$SERVICEDIR/$SERVICE" stop
+ env -i LANG="$LANG" LANGUAGE="$LANGUAGE" LC_CTYPE="$LC_CTYPE" LC_NUMERIC="$LC_NUMERIC" LC_TIME="$LC_TIME" LC_COLLATE="$LC_COLLATE" LC_MONETARY="$LC_MONETARY" LC_MESSAGES="$LC_MESSAGES" LC_PAPER="$LC_PAPER" LC_NAME="$LC_NAME" LC_ADDRESS="$LC_ADDRESS" LC_TELEPHONE="$LC_TELEPHONE" LC_MEASUREMENT="$LC_MEASUREMENT" LC_IDENTIFICATION="$LC_IDENTIFICATION" LC_ALL="$LC_ALL" PATH="$PATH" TERM="$TERM" "$SERVICEDIR/$SERVICE" start
+ exit $?
+ fi
+ fi
+ elif [ -z "${SERVICE}" ]; then
+ SERVICE="${1}"
+ elif [ -z "${ACTION}" ]; then
+ ACTION="${1}"
+ else
+ OPTIONS="${OPTIONS} ${1}"
+ fi
+ shift
+ ;;
+ esac
+done
+
+run_via_sysvinit() {
+ # Otherwise, use the traditional sysvinit
+ if [ -x "${SERVICEDIR}/${SERVICE}" ]; then
+ exec env -i LANG="$LANG" LANGUAGE="$LANGUAGE" LC_CTYPE="$LC_CTYPE" LC_NUMERIC="$LC_NUMERIC" LC_TIME="$LC_TIME" LC_COLLATE="$LC_COLLATE" LC_MONETARY="$LC_MONETARY" LC_MESSAGES="$LC_MESSAGES" LC_PAPER="$LC_PAPER" LC_NAME="$LC_NAME" LC_ADDRESS="$LC_ADDRESS" LC_TELEPHONE="$LC_TELEPHONE" LC_MEASUREMENT="$LC_MEASUREMENT" LC_IDENTIFICATION="$LC_IDENTIFICATION" LC_ALL="$LC_ALL" PATH="$PATH" TERM="$TERM" "$SERVICEDIR/$SERVICE" ${ACTION} ${OPTIONS}
+ else
+ echo "${SERVICE}: unrecognized service" >&2
+ exit 1
+ fi
+}
+
+update_openrc_started_symlinks() {
+ # maintain the symlinks of /run/openrc/started so that
+ # rc-status works with the service command as well
+ if [ -d /run/openrc/started ] ; then
+ case "${ACTION}" in
+ start)
+ if [ ! -h /run/openrc/started/$SERVICE ] ; then
+ ln -s $SERVICEDIR/$SERVICE /run/openrc/started/$SERVICE || true
+ fi
+ ;;
+ stop)
+ rm /run/openrc/started/$SERVICE || true
+ ;;
+ esac
+ fi
+}
+
+# When this machine is running systemd, standard service calls are turned into
+# systemctl calls.
+if [ -n "$is_systemd" ]
+then
+ UNIT="${SERVICE%.sh}.service"
+
+ case "${ACTION}" in
+ restart|status|try-restart)
+ exec systemctl $sctl_args ${ACTION} ${UNIT}
+ ;;
+ start|stop)
+ # Follow the principle of least surprise for SysV people:
+ # When running "service foo stop" and foo happens to be a service that
+ # has one or more .socket files, we also stop the .socket units.
+ # Users who need more control will use systemctl directly.
+ for unit in $(systemctl list-unit-files --full --type=socket 2>/dev/null | sed -ne 's/\.socket\s*[a-z]*\s*$/.socket/p'); do
+ if [ "$(systemctl -p Triggers show $unit)" = "Triggers=${UNIT}" ]; then
+ systemctl $sctl_args ${ACTION} $unit
+ fi
+ done
+ exec systemctl $sctl_args ${ACTION} ${UNIT}
+ ;;
+ reload)
+ _canreload="$(systemctl -p CanReload show ${UNIT} 2>/dev/null)"
+ # Don't block on reload requests during bootup and shutdown
+ # from units/hooks and simply schedule the task.
+ if ! systemctl --quiet is-system-running; then
+ sctl_args="--no-block"
+ fi
+ if [ "$_canreload" = "CanReload=no" ]; then
+ # The reload action falls back to the sysv init script just in case
+ # the systemd service file does not (yet) support reload for a
+ # specific service.
+ run_via_sysvinit
+ else
+ exec systemctl $sctl_args reload "${UNIT}"
+ fi
+ ;;
+ force-stop)
+ exec systemctl --signal=KILL kill "${UNIT}"
+ ;;
+ force-reload)
+ _canreload="$(systemctl -p CanReload show ${UNIT} 2>/dev/null)"
+ if [ "$_canreload" = "CanReload=no" ]; then
+ exec systemctl $sctl_args restart "${UNIT}"
+ else
+ exec systemctl $sctl_args reload "${UNIT}"
+ fi
+ ;;
+ *)
+ # We try to run non-standard actions by running
+ # the init script directly.
+ run_via_sysvinit
+ ;;
+ esac
+fi
+
+update_openrc_started_symlinks
+run_via_sysvinit
diff --git a/script/update-rc.d b/script/update-rc.d
new file mode 100755
index 0000000..264757b
--- /dev/null
+++ b/script/update-rc.d
@@ -0,0 +1,543 @@
+#! /usr/bin/perl
+# vim: ft=perl
+#
+# update-rc.d Update the links in /etc/rc[0-9S].d/
+#
+
+use strict;
+use warnings;
+# NB: All Perl modules used here must be in perl-base. Specifically, depending
+# on modules in perl-modules is not okay! See bug #716923
+
+my $initd = "/etc/init.d";
+my $etcd = "/etc/rc";
+my $dpkg_root = $ENV{DPKG_ROOT} // '';
+
+# Print usage message and die.
+
+sub usage {
+ print STDERR "update-rc.d: error: @_\n" if ($#_ >= 0);
+ print STDERR <<EOF;
+usage: update-rc.d [-f] <basename> remove
+ update-rc.d [-f] <basename> defaults
+ update-rc.d [-f] <basename> defaults-disabled
+ update-rc.d <basename> disable|enable [S|2|3|4|5]
+ -f: force
+
+The disable|enable API is not stable and might change in the future.
+EOF
+ exit (1);
+}
+
+exit main(@ARGV);
+
+sub info {
+ print STDOUT "update-rc.d: @_\n";
+}
+
+sub warning {
+ print STDERR "update-rc.d: warning: @_\n";
+}
+
+sub error {
+ print STDERR "update-rc.d: error: @_\n";
+ exit (1);
+}
+
+sub error_code {
+ my $rc = shift;
+ print STDERR "update-rc.d: error: @_\n";
+ exit ($rc);
+}
+
+sub make_path {
+ my ($path) = @_;
+ my @dirs = ();
+ my @path = split /\//, $path;
+ map { push @dirs, $_; mkdir join('/', @dirs), 0755; } @path;
+}
+
+# Given a script name, return any runlevels except 0 or 6 in which the
+# script is enabled. If that gives nothing and the script is not
+# explicitly disabled, return 6 if the script is disabled in runlevel
+# 0 or 6.
+sub script_runlevels {
+ my ($scriptname) = @_;
+ my @links=<"$dpkg_root/etc/rc[S12345].d/S[0-9][0-9]$scriptname">;
+ if (@links) {
+ return map(substr($_, 7, 1), @links);
+ } elsif (! <"$dpkg_root/etc/rc[S12345].d/K[0-9][0-9]$scriptname">) {
+ @links=<"$dpkg_root/etc/rc[06].d/K[0-9][0-9]$scriptname">;
+ return ("6") if (@links);
+ } else {
+ return ;
+ }
+}
+
+# Map the sysvinit runlevel to that of openrc.
+sub openrc_rlconv {
+ my %rl_table = (
+ "S" => "sysinit",
+ "1" => "recovery",
+ "2" => "default",
+ "3" => "default",
+ "4" => "default",
+ "5" => "default",
+ "6" => "off" );
+
+ my %seen; # return unique runlevels
+ return grep !$seen{$_}++, map($rl_table{$_}, @_);
+}
+
+sub systemd_reload {
+ if (length $ENV{DPKG_ROOT}) {
+ # if we operate on a chroot from the outside, do not attempt to reload
+ return;
+ }
+ if (-d "/run/systemd/system") {
+ system("systemctl", "daemon-reload");
+ }
+}
+
+# Creates the necessary links to enable/disable a SysV init script (fallback if
+# no insserv/rc-update exists)
+sub make_sysv_links {
+ my ($scriptname, $action) = @_;
+
+ # for "remove" we cannot rely on the init script still being present, as
+ # this gets called in postrm for purging. Just remove all symlinks.
+ if ("remove" eq $action) { unlink($_) for
+ glob("$dpkg_root/etc/rc?.d/[SK][0-9][0-9]$scriptname"); return; }
+
+ # if the service already has any links, do not touch them
+ # numbers we don't care about, but enabled/disabled state we do
+ return if glob("$dpkg_root/etc/rc?.d/[SK][0-9][0-9]$scriptname");
+
+ # for "defaults", parse Default-{Start,Stop} and create these links
+ my ($lsb_start_ref, $lsb_stop_ref) = parse_def_start_stop("$dpkg_root/etc/init.d/$scriptname");
+ my $start = $action eq "defaults-disabled" ? "K" : "S";
+ foreach my $lvl (@$lsb_start_ref) {
+ make_path("$dpkg_root/etc/rc$lvl.d");
+ my $l = "$dpkg_root/etc/rc$lvl.d/${start}01$scriptname";
+ symlink("../init.d/$scriptname", $l);
+ }
+
+ foreach my $lvl (@$lsb_stop_ref) {
+ make_path("$dpkg_root/etc/rc$lvl.d");
+ my $l = "$dpkg_root/etc/rc$lvl.d/K01$scriptname";
+ symlink("../init.d/$scriptname", $l);
+ }
+}
+
+# Creates the necessary links to enable/disable the service (equivalent of an
+# initscript) in systemd.
+sub make_systemd_links {
+ my ($scriptname, $action) = @_;
+
+ # If called by systemctl (via systemd-sysv-install), do nothing to avoid
+ # an endless loop.
+ if (defined($ENV{_SKIP_SYSTEMD_NATIVE}) && $ENV{_SKIP_SYSTEMD_NATIVE} == 1) {
+ return;
+ }
+
+ # If systemctl is available, let's use that to create the symlinks.
+ if (-x "$dpkg_root/bin/systemctl" || -x "$dpkg_root/usr/bin/systemctl") {
+ my $systemd_root = '/';
+ if ($dpkg_root ne '') {
+ $systemd_root = $dpkg_root;
+ }
+ # Set this env var to avoid loop in systemd-sysv-install.
+ local $ENV{SYSTEMCTL_SKIP_SYSV} = 1;
+ # Use --quiet to mimic the old update-rc.d behaviour.
+ system("systemctl", "--root=$systemd_root", "--quiet", "$action", "$scriptname");
+ return;
+ }
+
+ # In addition to the insserv call we also enable/disable the service
+ # for systemd by creating the appropriate symlink in case there is a
+ # native systemd service. In case systemd is not installed we do this
+ # on our own instead of using systemctl.
+ my $service_path;
+ if (-f "/etc/systemd/system/$scriptname.service") {
+ $service_path = "/etc/systemd/system/$scriptname.service";
+ } elsif (-f "/lib/systemd/system/$scriptname.service") {
+ $service_path = "/lib/systemd/system/$scriptname.service";
+ } elsif (-f "/usr/lib/systemd/system/$scriptname.service") {
+ $service_path = "/usr/lib/systemd/system/$scriptname.service";
+ }
+ if (defined($service_path)) {
+ my $changed_sth;
+ open my $fh, '<', $service_path or error("unable to read $service_path");
+ while (<$fh>) {
+ chomp;
+ if (/^\s*WantedBy=(.+)$/i) {
+ my $wants_dir = "/etc/systemd/system/$1.wants";
+ my $service_link = "$wants_dir/$scriptname.service";
+ if ("enable" eq $action) {
+ make_path($wants_dir);
+ symlink($service_path, $service_link);
+ } else {
+ unlink($service_link) if -e $service_link;
+ }
+ }
+ }
+ close($fh);
+ }
+}
+
+sub create_sequence {
+ my $force = (@_);
+ my $insserv = "$dpkg_root/usr/lib/insserv/insserv";
+ # Fallback for older insserv package versions [2014-04-16]
+ $insserv = "/sbin/insserv" if ( -x "$dpkg_root/sbin/insserv");
+ # If insserv is not configured it is not fully installed
+ my $insserv_installed = -x "$dpkg_root$insserv" && -e "$dpkg_root/etc/insserv.conf";
+ my @opts;
+ push(@opts, '-f') if $force;
+ # Add force flag if initscripts is not installed
+ # This enables inistcripts-less systems to not fail when a facility is missing
+ unshift(@opts, '-f') unless is_initscripts_installed();
+ if ( $dpkg_root ne '' ) {
+ push( @opts,
+ '--path', "$dpkg_root/etc/init.d",
+ '--override', "$dpkg_root/etc/insserv/overrides/",
+ '--insserv-dir', "$dpkg_root/etc/init.d",
+ '--config', "$dpkg_root/etc/insserv.conf" );
+ }
+
+ my $openrc_installed = -x "$dpkg_root/sbin/openrc";
+
+ my $sysv_insserv ={};
+ $sysv_insserv->{remove} = sub {
+ my ($scriptname) = @_;
+ if ( -f "$dpkg_root/etc/init.d/$scriptname" ) {
+ return system($insserv, @opts, "-r", $scriptname) >> 8;
+ } else {
+ # insserv removes all dangling symlinks, no need to tell it
+ # what to look for.
+ my $rc = system($insserv, @opts) >> 8;
+ error_code($rc, "insserv rejected the script header") if $rc;
+ }
+ };
+ $sysv_insserv->{defaults} = sub {
+ my ($scriptname) = @_;
+ if ( -f "$dpkg_root/etc/init.d/$scriptname" ) {
+ my $rc = system($insserv, @opts, $scriptname) >> 8;
+ error_code($rc, "insserv rejected the script header") if $rc;
+ } else {
+ error("initscript does not exist: /etc/init.d/$scriptname");
+ }
+ };
+ $sysv_insserv->{defaults_disabled} = sub {
+ my ($scriptname) = @_;
+ return if glob("$dpkg_root/etc/rc?.d/[SK][0-9][0-9]$scriptname");
+ if ( -f "$dpkg_root/etc/init.d/$scriptname" ) {
+ my $rc = system($insserv, @opts, $scriptname) >> 8;
+ error_code($rc, "insserv rejected the script header") if $rc;
+ } else {
+ error("initscript does not exist: /etc/init.d/$scriptname");
+ }
+ sysv_toggle("disable", $scriptname);
+ };
+ $sysv_insserv->{toggle} = sub {
+ my ($action, $scriptname) = (shift, shift);
+ sysv_toggle($action, $scriptname, @_);
+
+ # Call insserv to resequence modified links
+ my $rc = system($insserv, @opts, $scriptname) >> 8;
+ error_code($rc, "insserv rejected the script header") if $rc;
+ };
+
+ my $sysv_plain = {};
+ $sysv_plain->{remove} = sub {
+ my ($scriptname) = @_;
+ make_sysv_links($scriptname, "remove");
+ };
+ $sysv_plain->{defaults} = sub {
+ my ($scriptname) = @_;
+ make_sysv_links($scriptname, "defaults");
+ };
+ $sysv_plain->{defaults_disabled} = sub {
+ my ($scriptname) = @_;
+ make_sysv_links($scriptname, "defaults-disabled");
+ };
+ $sysv_plain->{toggle} = sub {
+ my ($action, $scriptname) = (shift, shift);
+ sysv_toggle($action, $scriptname, @_);
+ };
+
+ my $systemd = {};
+ $systemd->{remove} = sub {
+ systemd_reload;
+ };
+ $systemd->{defaults} = sub {
+ systemd_reload;
+ };
+ $systemd->{defaults_disabled} = sub {
+ systemd_reload;
+ };
+ $systemd->{toggle} = sub {
+ my ($action, $scriptname) = (shift, shift);
+ make_systemd_links($scriptname, $action);
+ systemd_reload;
+ };
+
+ # Should we check exit codeS?
+ my $openrc = {};
+ $openrc->{remove} = sub {
+ my ($scriptname) = @_;
+ system("rc-update", "-qqa", "delete", $scriptname);
+
+ };
+ $openrc->{defaults} = sub {
+ my ($scriptname) = @_;
+ # OpenRC does not distinguish halt and reboot. They are handled
+ # by /etc/init.d/transit instead.
+ return if ("halt" eq $scriptname || "reboot" eq $scriptname);
+ # no need to consider default disabled runlevels
+ # because everything is disabled by openrc by default
+ my @rls=script_runlevels($scriptname);
+ if ( @rls ) {
+ system("rc-update", "add", $scriptname, openrc_rlconv(@rls));
+ }
+ };
+ $openrc->{defaults_disabled} = sub {
+ # In openrc everything is disabled by default
+ };
+ $openrc->{toggle} = sub {
+ my ($action, $scriptname) = (shift, shift);
+ my (@toggle_lvls, $start_lvls, $stop_lvls, @symlinks);
+ my $lsb_header = lsb_header_for_script($scriptname);
+
+ # Extra arguments to disable|enable action are runlevels. If none
+ # given parse LSB info for Default-Start value.
+ if ($#_ >= 0) {
+ @toggle_lvls = @_;
+ } else {
+ ($start_lvls, $stop_lvls) = parse_def_start_stop($lsb_header);
+ @toggle_lvls = @$start_lvls;
+ if ($#toggle_lvls < 0) {
+ error("$scriptname Default-Start contains no runlevels, aborting.");
+ }
+ }
+ my %openrc_act = ( "disable" => "del", "enable" => "add" );
+ system("rc-update", $openrc_act{$action}, $scriptname,
+ openrc_rlconv(@toggle_lvls))
+ };
+
+ my @sequence;
+ if ($insserv_installed) {
+ push @sequence, $sysv_insserv;
+ }
+ else {
+ push @sequence, $sysv_plain;
+ }
+ # OpenRC has to be after sysv_{insserv,plain} because it depends on them to synchronize
+ # states.
+ if ($openrc_installed) {
+ push @sequence, $openrc;
+ }
+ push @sequence, $systemd;
+
+ return @sequence;
+}
+
+## Dependency based
+sub main {
+ my @args = @_;
+ my $scriptname;
+ my $action;
+ my $force = 0;
+
+ while($#args >= 0 && ($_ = $args[0]) =~ /^-/) {
+ shift @args;
+ if (/^-f$/) { $force = 1; next }
+ if (/^-h|--help$/) { usage(); }
+ usage("unknown option");
+ }
+
+ usage("not enough arguments") if ($#args < 1);
+
+ my @sequence = create_sequence($force);
+
+ $scriptname = shift @args;
+ $action = shift @args;
+ if ("remove" eq $action) {
+ foreach my $init (@sequence) {
+ $init->{remove}->($scriptname);
+ }
+ } elsif ("defaults" eq $action || "start" eq $action ||
+ "stop" eq $action) {
+ # All start/stop/defaults arguments are discarded so emit a
+ # message if arguments have been given and are in conflict
+ # with Default-Start/Default-Stop values of LSB comment.
+ if ("start" eq $action || "stop" eq $action) {
+ cmp_args_with_defaults($scriptname, $action, @args);
+ }
+ foreach my $init (@sequence) {
+ $init->{defaults}->($scriptname);
+ }
+ } elsif ("defaults-disabled" eq $action) {
+ foreach my $init (@sequence) {
+ $init->{defaults_disabled}->($scriptname);
+ }
+ } elsif ("disable" eq $action || "enable" eq $action) {
+ foreach my $init (@sequence) {
+ $init->{toggle}->($action, $scriptname, @args);
+ }
+ } else {
+ usage();
+ }
+}
+
+sub parse_def_start_stop {
+ my $script = shift;
+ my (%lsb, @def_start_lvls, @def_stop_lvls);
+
+ open my $fh, '<', $script or error("unable to read $script");
+ while (<$fh>) {
+ chomp;
+ if (m/^### BEGIN INIT INFO\s*$/) {
+ $lsb{'begin'}++;
+ }
+ elsif (m/^### END INIT INFO\s*$/) {
+ $lsb{'end'}++;
+ last;
+ }
+ elsif ($lsb{'begin'} and not $lsb{'end'}) {
+ if (m/^# Default-Start:\s*(\S?.*)$/) {
+ @def_start_lvls = split(' ', $1);
+ }
+ if (m/^# Default-Stop:\s*(\S?.*)$/) {
+ @def_stop_lvls = split(' ', $1);
+ }
+ }
+ }
+ close($fh);
+
+ return (\@def_start_lvls, \@def_stop_lvls);
+}
+
+sub lsb_header_for_script {
+ my $name = shift;
+
+ foreach my $file ("/etc/insserv/overrides/$name", "/etc/init.d/$name",
+ "/usr/share/insserv/overrides/$name") {
+ return $file if -s $file;
+ }
+
+ error("cannot find a LSB script for $name");
+}
+
+sub cmp_args_with_defaults {
+ my ($name, $act) = (shift, shift);
+ my ($lsb_start_ref, $lsb_stop_ref, $arg_str, $lsb_str);
+ my (@arg_start_lvls, @arg_stop_lvls, @lsb_start_lvls, @lsb_stop_lvls);
+
+ ($lsb_start_ref, $lsb_stop_ref) = parse_def_start_stop("/etc/init.d/$name");
+ @lsb_start_lvls = @$lsb_start_ref;
+ @lsb_stop_lvls = @$lsb_stop_ref;
+ return if (!@lsb_start_lvls and !@lsb_stop_lvls);
+
+ warning "start and stop actions are no longer supported; falling back to defaults";
+ my $start = $act eq 'start' ? 1 : 0;
+ my $stop = $act eq 'stop' ? 1 : 0;
+
+ # The legacy part of this program passes arguments starting with
+ # "start|stop NN x y z ." but the insserv part gives argument list
+ # starting with sequence number (ie. strips off leading "start|stop")
+ # Start processing arguments immediately after the first seq number.
+ my $argi = $_[0] eq $act ? 2 : 1;
+
+ while (defined $_[$argi]) {
+ my $arg = $_[$argi];
+
+ # Runlevels 0 and 6 are always stop runlevels
+ if ($arg eq 0 or $arg eq 6) {
+ $start = 0; $stop = 1;
+ } elsif ($arg eq 'start') {
+ $start = 1; $stop = 0; $argi++; next;
+ } elsif ($arg eq 'stop') {
+ $start = 0; $stop = 1; $argi++; next;
+ } elsif ($arg eq '.') {
+ next;
+ }
+ push(@arg_start_lvls, $arg) if $start;
+ push(@arg_stop_lvls, $arg) if $stop;
+ } continue {
+ $argi++;
+ }
+
+ if ($#arg_start_lvls != $#lsb_start_lvls or
+ join("\0", sort @arg_start_lvls) ne join("\0", sort @lsb_start_lvls)) {
+ $arg_str = @arg_start_lvls ? "@arg_start_lvls" : "none";
+ $lsb_str = @lsb_start_lvls ? "@lsb_start_lvls" : "none";
+ warning "start runlevel arguments ($arg_str) do not match",
+ "$name Default-Start values ($lsb_str)";
+ }
+ if ($#arg_stop_lvls != $#lsb_stop_lvls or
+ join("\0", sort @arg_stop_lvls) ne join("\0", sort @lsb_stop_lvls)) {
+ $arg_str = @arg_stop_lvls ? "@arg_stop_lvls" : "none";
+ $lsb_str = @lsb_stop_lvls ? "@lsb_stop_lvls" : "none";
+ warning "stop runlevel arguments ($arg_str) do not match",
+ "$name Default-Stop values ($lsb_str)";
+ }
+}
+
+sub sysv_toggle {
+ my ($act, $name) = (shift, shift);
+ my (@toggle_lvls, $start_lvls, $stop_lvls, @symlinks);
+ my $lsb_header = lsb_header_for_script($name);
+
+ # Extra arguments to disable|enable action are runlevels. If none
+ # given parse LSB info for Default-Start value.
+ if ($#_ >= 0) {
+ @toggle_lvls = @_;
+ } else {
+ ($start_lvls, $stop_lvls) = parse_def_start_stop($lsb_header);
+ @toggle_lvls = @$start_lvls;
+ if ($#toggle_lvls < 0) {
+ error("$name Default-Start contains no runlevels, aborting.");
+ }
+ }
+
+ # Find symlinks in rc.d directories. Refuse to modify links in runlevels
+ # not used for normal system start sequence.
+ for my $lvl (@toggle_lvls) {
+ if ($lvl !~ /^[S2345]$/) {
+ warning("$act action will have no effect on runlevel $lvl");
+ next;
+ }
+ push(@symlinks, $_) for glob("$dpkg_root/etc/rc$lvl.d/[SK][0-9][0-9]$name");
+ }
+
+ if (!@symlinks) {
+ error("no runlevel symlinks to modify, aborting!");
+ }
+
+ # Toggle S/K bit of script symlink.
+ for my $cur_lnk (@symlinks) {
+ my $sk;
+ my @new_lnk = split(//, $cur_lnk);
+
+ if ("disable" eq $act) {
+ $sk = rindex($cur_lnk, '/S') + 1;
+ next if $sk < 1;
+ $new_lnk[$sk] = 'K';
+ } else {
+ $sk = rindex($cur_lnk, '/K') + 1;
+ next if $sk < 1;
+ $new_lnk[$sk] = 'S';
+ }
+
+ rename($cur_lnk, join('', @new_lnk)) or error($!);
+ }
+}
+
+# Try to determine if initscripts is installed
+sub is_initscripts_installed {
+ # Check if mountkernfs is available. We cannot make inferences
+ # using the running init system because we may be running in a
+ # chroot
+ return glob("$dpkg_root/etc/rcS.d/S??mountkernfs.sh");
+}