3 # update-rc.d Update the links in /etc/rc[0-9S].d/
9 my $initd = "/etc/init.d";
13 # Save last action to this directory
14 my $archive = "/var/lib/update-rc.d";
16 # Print usage message and die.
19 print STDERR "update-rc.d: error: @_\n" if ($#_ >= 0);
21 usage: update-rc.d [-n] [-f] <basename> remove
22 update-rc.d [-n] <basename> defaults [NN | SS KK]
23 update-rc.d [-n] <basename> start|stop NN runlvl [runlvl] [...] .
24 update-rc.d [-n] <basename> disable|enable [S|2|3|4|5]
28 The disable|enable API is not stable and might change in the future.
33 # Dependency based boot sequencing is the default, but upgraded
34 # systems might keep the legacy ordering until the sysadm choose to
35 # migrate to the new ordering method. sysv-rc version 2.87dsf-2 will
36 # remove /var/lib/insserv/using-insserv and this divert, thus transfering
37 # the responsibility for dependency based update-rc.d to sysv-rc.
38 if ( -f "/var/lib/insserv/using-insserv" && ! -f "/etc/init.d/.legacy-bootordering" ) {
39 info("using dependency based boot sequencing");
40 exit insserv_updatercd(@ARGV);
46 my @orig_argv = @ARGV;
48 while($#ARGV >= 0 && ($_ = $ARGV[0]) =~ /^-/) {
50 if (/^-n$/) { $notreally++; next }
51 if (/^-f$/) { $force++; next }
52 if (/^-h|--help$/) { &usage; }
53 &usage("unknown option");
56 sub save_last_action {
57 my ($script, @arguments) = @_;
61 open(FILE, ">", "$archive/${script}.new") || die "unable to write to $archive/${script}.new";
62 print FILE join(" ","update-rc.d",@arguments), "\n";
64 rename "$archive/${script}.new", "$archive/${script}";
67 sub remove_last_action {
69 unlink "$archive/$script";
74 &usage() if ($#ARGV < 1);
77 unless ($bn =~ m/[a-zA-Z0-9+.-]+/) {
78 print STDERR "update-rc.d: illegal character in name '$bn'\n";
82 if ($ARGV[0] ne 'remove') {
83 if (! -f "$initd/$bn") {
84 print STDERR "update-rc.d: $initd/$bn: file does not exist\n";
87 &parse_lsb_header("$initd/$bn");
88 &cmp_args_with_defaults($bn, $ARGV[0], @ARGV);
89 } elsif (-f "$initd/$bn") {
91 printf STDERR "update-rc.d: $initd/$bn exists during rc.d purge (use -f to force)\n";
100 if (/^remove$/) { &checklinks ("remove"); remove_last_action($bn); }
101 elsif (/^defaults$/) { &defaults (@ARGV); &makelinks; save_last_action($bn, @orig_argv); }
102 elsif (/^(start|stop)$/) { &startstop (@ARGV); &makelinks; save_last_action($bn, @orig_argv); }
103 elsif (/^(dis|en)able$/) { &toggle (@ARGV); &makelinks; save_last_action($bn, @orig_argv); }
109 print STDOUT "update-rc.d: @_\n";
113 print STDERR "update-rc.d: warning: @_\n";
117 print STDERR "update-rc.d: error: @_\n";
123 print STDERR "update-rc.d: error: @_\n";
127 # Check if there are links in /etc/rc[0-9S].d/
128 # Remove if the first argument is "remove" and the links
132 my ($op, $fn, $bn) = @_;
134 warning "$fn is not a symbolic link\n";
137 my $linkdst = readlink ($fn);
138 if (! defined $linkdst) {
139 die ("update-rc.d: error reading symbolic link: $!\n");
141 if (($linkdst ne "../init.d/$bn") && ($linkdst ne "$initd/$bn")) {
142 warning "$fn is not a link to ../init.d/$bn or $initd/$bn\n";
150 my ($i, $found, $fn, $islnk);
152 print " Removing any system startup links for $initd/$bn ...\n"
153 if (defined $_[0] && $_[0] eq 'remove');
157 foreach $i (0..9, 'S') {
158 unless (chdir ("$etcd$i.d")) {
159 next if ($i =~ m/^[789S]$/);
160 die("update-rc.d: chdir $etcd$i.d: $!\n");
164 $saveBN =~ s/\+/\\+/g;
165 foreach $_ (readdir(DIR)) {
166 next unless (/^[SK]\d\d$saveBN$/);
167 $fn = "$etcd$i.d/$_";
169 $islnk = &is_link ($_[0], $fn, $bn);
170 next unless (defined $_[0] and $_[0] eq 'remove');
172 print " $fn is not a link to ../init.d/$bn; not removing\n";
175 print " $etcd$i.d/$_\n";
176 next if ($notreally);
177 unlink ("$etcd$i.d/$_") ||
178 die("update-rc.d: unlink: $!\n");
185 sub parse_lsb_header {
186 my $initdscript = shift;
188 my $lsbheaders = "Provides|Required-Start|Required-Stop|Default-Start|Default-Stop";
189 open(INIT, "<$initdscript") || die "error: unable to read $initdscript";
192 $lsbinfo{'found'} = 1 if (m/^\#\#\# BEGIN INIT INFO\s*$/);
193 last if (m/\#\#\# END INIT INFO\s*$/);
194 if (m/^\# ($lsbheaders):\s*(\S?.*)$/i) {
195 $lsbinfo{lc($1)} = $2;
200 # Check that all the required headers are present
201 if (!$lsbinfo{found}) {
202 printf STDERR "update-rc.d: warning: $initdscript missing LSB information\n";
203 printf STDERR "update-rc.d: see <http://wiki.debian.org/LSBInitScripts>\n";
205 for my $key (split(/\|/, lc($lsbheaders))) {
206 if (!exists $lsbinfo{$key}) {
207 warning "$initdscript missing LSB keyword '$key'\n";
214 # Process the arguments after the "enable" or "disable" keyword.
218 my ($action, %lvls, @start, @stop, @xstartlinks);
221 print " System start/stop links for $initd/$bn do not exist.\n";
227 while ($#argv > 0 && shift @argv) {
228 if ($argv[0] =~ /^[S2-5]$/) {
231 &usage ("expected 'S' '2' '3' '4' or '5'");
235 $lvls{$_}++ for ('S', '2', '3', '4', '5');
238 push(@start, glob($etcd . '[2-5S].d/[KS][0-9][0-9]' . $bn));
241 my $islink = &is_link (undef, $_, $bn);
244 next unless my ($lvl, $sk, $seq) = m/^$etcd([2-5S])\.d\/([SK])([0-9]{2})$bn$/;
245 $startlinks[$lvl] = $sk . $seq;
247 if ($action eq 'disable' and $sk eq 'S' and $lvls{$lvl}) {
248 $xstartlinks[$lvl] = 'K' . sprintf "%02d", (100 - $seq);
249 } elsif ($action eq 'enable' and $sk eq 'K' and $lvls{$lvl}) {
250 $xstartlinks[$lvl] = 'S' . sprintf "%02d", -($seq - 100);
252 $xstartlinks[$lvl] = $sk . $seq;
256 push(@stop, glob($etcd . '[016].d/[KS][0-9][0-9]' . $bn));
259 my $islink = &is_link (undef, $_, $bn);
262 next unless my ($lvl, $sk, $seq) = m/^$etcd([016])\.d\/([SK])([0-9]{2})$bn$/;
263 $stoplinks[$lvl] = $sk . $seq;
266 if ($action eq 'disable') {
267 print " Disabling system startup links for $initd/$bn ...\n";
268 } elsif ($action eq 'enable') {
269 print " Enabling system startup links for $initd/$bn ...\n";
272 &checklinks ("remove");
273 @startlinks = @xstartlinks;
278 # Process the arguments after the "defaults" keyword.
282 my ($start, $stop) = (20, 20);
284 &usage ("defaults takes only one or two codenumbers") if ($#argv > 2);
285 $start = $stop = $argv[1] if ($#argv >= 1);
286 $stop = $argv[2] if ($#argv >= 2);
287 &usage ("codenumber must be a number between 0 and 99")
288 if ($start !~ /^\d\d?$/ || $stop !~ /^\d\d?$/);
290 $start = sprintf("%02d", $start);
291 $stop = sprintf("%02d", $stop);
293 $stoplinks[$_] = "K$stop" for (0, 1, 6);
294 $startlinks[$_] = "S$start" for (2, 3, 4, 5);
299 # Process the arguments after the start or stop keyword.
303 my($letter, $NN, $level);
305 while ($#argv >= 0) {
306 if ($argv[0] eq 'start') { $letter = 'S'; }
307 elsif ($argv[0] eq 'stop') { $letter = 'K'; }
309 &usage("expected start|stop");
312 if ($argv[1] !~ /^\d\d?$/) {
313 &usage("expected NN after $argv[0]");
315 $NN = sprintf("%02d", $argv[1]);
317 if ($argv[-1] ne '.') {
318 &usage("start|stop arguments not terminated by \".\"");
321 shift @argv; shift @argv;
322 $level = shift @argv;
324 if ($level !~ m/^[0-9S]$/) {
326 "expected runlevel [0-9S] (did you forget \".\" ?)");
328 if (! -d "$etcd$level.d") {
330 "update-rc.d: $etcd$level.d: no such directory\n";
333 $level = 99 if ($level eq 'S');
334 $startlinks[$level] = "$letter$NN" if ($letter eq 'S');
335 $stoplinks[$level] = "$letter$NN" if ($letter eq 'K');
336 } while (($level = shift @argv) ne '.');
348 print " System start/stop links for $initd/$bn already exist.\n";
351 print " Adding system startup for $initd/$bn ...\n";
353 # nice unreadable perl mess :)
355 for($t = 0; $t < 2; $t++) {
356 @links = $t ? @startlinks : @stoplinks;
357 for($i = 0; $i <= $#links; $i++) {
359 $lvl = 'S' if ($i == 99);
360 next if (!defined $links[$i] or $links[$i] eq '');
361 print " $etcd$lvl.d/$links[$i]$bn -> ../init.d/$bn\n";
362 next if ($notreally);
363 symlink("../init.d/$bn", "$etcd$lvl.d/$links[$i]$bn")
364 || die("update-rc.d: symlink: $!\n");
372 sub insserv_updatercd {
379 my @orig_argv = @args;
381 while($#args >= 0 && ($_ = $args[0]) =~ /^-/) {
383 if (/^-n$/) { push(@opts, $_); $notreally++; next }
384 if (/^-f$/) { push(@opts, $_); next }
385 if (/^-h|--help$/) { &usage; }
386 usage("unknown option");
389 usage("not enough arguments") if ($#args < 1);
391 $scriptname = shift @args;
392 $action = shift @args;
393 if ("remove" eq $action) {
394 if ( -f "/etc/init.d/$scriptname" ) {
395 my $rc = system("insserv", @opts, "-r", $scriptname) >> 8;
396 if (0 == $rc && !$notreally) {
397 remove_last_action($scriptname);
399 error_code($rc, "insserv rejected the script header") if $rc;
402 # insserv removes all dangling symlinks, no need to tell it
404 my $rc = system("insserv", @opts) >> 8;
405 if (0 == $rc && !$notreally) {
406 remove_last_action($scriptname);
408 error_code($rc, "insserv rejected the script header") if $rc;
411 } elsif ("defaults" eq $action || "start" eq $action ||
413 # All start/stop/defaults arguments are discarded so emit a
414 # message if arguments have been given and are in conflict
415 # with Default-Start/Default-Stop values of LSB comment.
416 cmp_args_with_defaults($scriptname, $action, @args);
418 if ( -f "/etc/init.d/$scriptname" ) {
419 my $rc = system("insserv", @opts, $scriptname) >> 8;
420 if (0 == $rc && !$notreally) {
421 save_last_action($scriptname, @orig_argv);
423 error_code($rc, "insserv rejected the script header") if $rc;
426 error("initscript does not exist: /etc/init.d/$scriptname");
428 } elsif ("disable" eq $action || "enable" eq $action) {
429 insserv_toggle($notreally, $action, $scriptname, @args);
430 # Call insserv to resequence modified links
431 my $rc = system("insserv", @opts, $scriptname) >> 8;
432 if (0 == $rc && !$notreally) {
433 save_last_action($scriptname, @orig_argv);
435 error_code($rc, "insserv rejected the script header") if $rc;
442 sub parse_def_start_stop {
444 my (%lsb, @def_start_lvls, @def_stop_lvls);
446 open my $fh, '<', $script or error("unable to read $script");
449 if (m/^### BEGIN INIT INFO$/) {
452 elsif (m/^### END INIT INFO$/) {
456 elsif ($lsb{'begin'} and not $lsb{'end'}) {
457 if (m/^# Default-Start:\s*(\S?.*)$/) {
458 @def_start_lvls = split(' ', $1);
460 if (m/^# Default-Stop:\s*(\S?.*)$/) {
461 @def_stop_lvls = split(' ', $1);
467 return (\@def_start_lvls, \@def_stop_lvls);
470 sub lsb_header_for_script {
473 foreach my $file ("/etc/insserv/overrides/$name", "/etc/init.d/$name",
474 "/usr/share/insserv/overrides/$name") {
475 return $file if -s $file;
478 error("cannot find a LSB script for $name");
481 sub cmp_args_with_defaults {
482 my ($name, $act) = (shift, shift);
483 my ($lsb_start_ref, $lsb_stop_ref, $arg_str, $lsb_str);
484 my (@arg_start_lvls, @arg_stop_lvls, @lsb_start_lvls, @lsb_stop_lvls);
486 ($lsb_start_ref, $lsb_stop_ref) = parse_def_start_stop("/etc/init.d/$name");
487 @lsb_start_lvls = @$lsb_start_ref;
488 @lsb_stop_lvls = @$lsb_stop_ref;
489 return if (!@lsb_start_lvls and !@lsb_stop_lvls);
491 if ($act eq 'defaults') {
492 @arg_start_lvls = (2, 3, 4, 5);
493 @arg_stop_lvls = (0, 1, 6);
494 } elsif ($act eq 'start' or $act eq 'stop') {
495 my $start = $act eq 'start' ? 1 : 0;
496 my $stop = $act eq 'stop' ? 1 : 0;
498 # The legacy part of this program passes arguments starting with
499 # "start|stop NN x y z ." but the insserv part gives argument list
500 # starting with sequence number (ie. strips off leading "start|stop")
501 # Start processing arguments immediately after the first seq number.
502 my $argi = $_[0] eq $act ? 2 : 1;
504 while (defined $_[$argi]) {
507 # Runlevels 0 and 6 are always stop runlevels
508 if ($arg eq 0 or $arg eq 6) {
509 $start = 0; $stop = 1;
510 } elsif ($arg eq 'start') {
511 $start = 1; $stop = 0; $argi++; next;
512 } elsif ($arg eq 'stop') {
513 $start = 0; $stop = 1; $argi++; next;
514 } elsif ($arg eq '.') {
517 push(@arg_start_lvls, $arg) if $start;
518 push(@arg_stop_lvls, $arg) if $stop;
524 if ($#arg_start_lvls != $#lsb_start_lvls or
525 join("\0", sort @arg_start_lvls) ne join("\0", sort @lsb_start_lvls)) {
526 $arg_str = @arg_start_lvls ? "@arg_start_lvls" : "none";
527 $lsb_str = @lsb_start_lvls ? "@lsb_start_lvls" : "none";
528 warning "$name start runlevel arguments ($arg_str) do not match",
529 "LSB Default-Start values ($lsb_str)";
531 if ($#arg_stop_lvls != $#lsb_stop_lvls or
532 join("\0", sort @arg_stop_lvls) ne join("\0", sort @lsb_stop_lvls)) {
533 $arg_str = @arg_stop_lvls ? "@arg_stop_lvls" : "none";
534 $lsb_str = @lsb_stop_lvls ? "@lsb_stop_lvls" : "none";
535 warning "$name stop runlevel arguments ($arg_str) do not match",
536 "LSB Default-Stop values ($lsb_str)";
541 my ($dryrun, $act, $name) = (shift, shift, shift);
542 my (@toggle_lvls, $start_lvls, $stop_lvls, @symlinks);
543 my $lsb_header = lsb_header_for_script($name);
545 # Extra arguments to disable|enable action are runlevels. If none
546 # given parse LSB info for Default-Start value.
550 ($start_lvls, $stop_lvls) = parse_def_start_stop($lsb_header);
551 @toggle_lvls = @$start_lvls;
552 if ($#toggle_lvls < 0) {
553 error("$name Default-Start contains no runlevels, aborting.");
557 # Find symlinks in rc.d directories. Refuse to modify links in runlevels
558 # not used for normal system start sequence.
559 for my $lvl (@toggle_lvls) {
560 if ($lvl !~ /^[S2345]$/) {
561 warning("$act action will have no effect on runlevel $lvl");
564 push(@symlinks, $_) for glob("/etc/rc$lvl.d/[SK][0-9][0-9]$name");
568 error("no runlevel symlinks to modify, aborting!");
571 # Toggle S/K bit of script symlink.
572 for my $cur_lnk (@symlinks) {
574 my @new_lnk = split(//, $cur_lnk);
576 if ("disable" eq $act) {
577 $sk = rindex($cur_lnk, '/S') + 1;
581 $sk = rindex($cur_lnk, '/K') + 1;
587 printf("rename(%s, %s)\n", $cur_lnk, join('', @new_lnk));
591 rename($cur_lnk, join('', @new_lnk)) or error($!);