summaryrefslogtreecommitdiffstats
path: root/script/update-rc.d
diff options
context:
space:
mode:
Diffstat (limited to 'script/update-rc.d')
-rwxr-xr-xscript/update-rc.d543
1 files changed, 543 insertions, 0 deletions
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");
+}