diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 17:29:54 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 17:29:54 +0000 |
commit | 618e47799afdfc2783d8469ca909aafa4acfa7b6 (patch) | |
tree | 6e12471be3cad7fb33c7f1b427b431bdf7dcf28d /script | |
parent | Initial commit. (diff) | |
download | init-system-helpers-upstream.tar.xz init-system-helpers-upstream.zip |
Adding upstream version 1.66.upstream/1.66upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'script')
-rwxr-xr-x | script/deb-systemd-helper | 691 | ||||
-rwxr-xr-x | script/deb-systemd-invoke | 187 | ||||
-rwxr-xr-x | script/invoke-rc.d | 574 | ||||
-rwxr-xr-x | script/service | 217 | ||||
-rwxr-xr-x | script/update-rc.d | 543 |
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"); +} |