Git init
[external/insserv.git] / packaging / update-rc.d-insserv
1 #! /usr/bin/perl
2 #
3 # update-rc.d   Update the links in /etc/rc[0-9S].d/
4 #
5
6 use strict;
7 use warnings;
8
9 my $initd = "/etc/init.d";
10 my $etcd  = "/etc/rc";
11 my $notreally = 0;
12
13 # Save last action to this directory
14 my $archive = "/var/lib/update-rc.d";
15
16 # Print usage message and die.
17
18 sub usage {
19         print STDERR "update-rc.d: error: @_\n" if ($#_ >= 0);
20         print STDERR <<EOF;
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]
25                 -n: not really
26                 -f: force
27
28 The disable|enable API is not stable and might change in the future.
29 EOF
30         exit (1);
31 }
32
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);
41 }
42
43 # Check out options.
44 my $force;
45
46 my @orig_argv = @ARGV;
47
48 while($#ARGV >= 0 && ($_ = $ARGV[0]) =~ /^-/) {
49         shift @ARGV;
50         if (/^-n$/) { $notreally++; next }
51         if (/^-f$/) { $force++; next }
52         if (/^-h|--help$/) { &usage; }
53         &usage("unknown option");
54 }
55
56 sub save_last_action {
57     my ($script, @arguments) = @_;
58
59     return if $notreally;
60
61     open(FILE, ">", "$archive/${script}.new") || die "unable to write to $archive/${script}.new";
62     print FILE join(" ","update-rc.d",@arguments), "\n";
63     close(FILE);
64     rename "$archive/${script}.new", "$archive/${script}";
65 }
66
67 sub remove_last_action {
68     my ($script) = @_;
69     unlink "$archive/$script";
70 }
71
72 # Action.
73
74 &usage() if ($#ARGV < 1);
75 my $bn = shift @ARGV;
76
77 unless ($bn =~ m/[a-zA-Z0-9+.-]+/) {
78     print STDERR "update-rc.d: illegal character in name '$bn'\n";
79     exit (1);
80 }
81
82 if ($ARGV[0] ne 'remove') {
83     if (! -f "$initd/$bn") {
84         print STDERR "update-rc.d: $initd/$bn: file does not exist\n";
85         exit (1);
86     }
87     &parse_lsb_header("$initd/$bn");
88     &cmp_args_with_defaults($bn, $ARGV[0], @ARGV);
89 } elsif (-f "$initd/$bn") {
90     if (!$force) {
91         printf STDERR "update-rc.d: $initd/$bn exists during rc.d purge (use -f to force)\n";
92         exit (1);
93     }
94 }
95
96 my @startlinks;
97 my @stoplinks;
98
99 $_ = $ARGV[0];
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); }
104 else                     { &usage; }
105
106 exit (0);
107
108 sub info {
109     print STDOUT "update-rc.d: @_\n";
110 }
111
112 sub warning {
113     print STDERR "update-rc.d: warning: @_\n";
114 }
115
116 sub error {
117     print STDERR "update-rc.d: error: @_\n";
118     exit (1);
119 }
120
121 sub error_code {
122     my $rc = shift;
123     print STDERR "update-rc.d: error: @_\n";
124     exit ($rc);
125 }
126
127 # Check if there are links in /etc/rc[0-9S].d/ 
128 # Remove if the first argument is "remove" and the links 
129 # point to $bn.
130
131 sub is_link () {
132     my ($op, $fn, $bn) = @_;
133     if (! -l $fn) {
134         warning "$fn is not a symbolic link\n";
135         return 0;
136     } else {
137         my $linkdst = readlink ($fn);
138         if (! defined $linkdst) {
139             die ("update-rc.d: error reading symbolic link: $!\n");
140         }
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";
143             return 0;
144         }
145     }
146     return 1;
147 }
148
149 sub checklinks {
150     my ($i, $found, $fn, $islnk);
151
152     print " Removing any system startup links for $initd/$bn ...\n"
153         if (defined $_[0] && $_[0] eq 'remove');
154
155     $found = 0;
156
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");
161         }
162         opendir(DIR, ".");
163         my $saveBN=$bn;
164         $saveBN =~ s/\+/\\+/g;
165         foreach $_ (readdir(DIR)) {
166             next unless (/^[SK]\d\d$saveBN$/);
167             $fn = "$etcd$i.d/$_";
168             $found = 1;
169             $islnk = &is_link ($_[0], $fn, $bn);
170             next unless (defined $_[0] and $_[0] eq 'remove');
171             if (! $islnk) {
172                 print "   $fn is not a link to ../init.d/$bn; not removing\n"; 
173                 next;
174             }
175             print "   $etcd$i.d/$_\n";
176             next if ($notreally);
177             unlink ("$etcd$i.d/$_") ||
178                 die("update-rc.d: unlink: $!\n");
179         }
180         closedir(DIR);
181     }
182     $found;
183 }
184
185 sub parse_lsb_header {
186     my $initdscript = shift;
187     my %lsbinfo;
188     my $lsbheaders = "Provides|Required-Start|Required-Stop|Default-Start|Default-Stop";
189     open(INIT, "<$initdscript") || die "error: unable to read $initdscript";
190     while (<INIT>) {
191         chomp;
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;
196         }
197     }
198     close(INIT);
199
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";
204     } else {
205         for my $key (split(/\|/, lc($lsbheaders))) {
206             if (!exists $lsbinfo{$key}) {
207                 warning "$initdscript missing LSB keyword '$key'\n";
208             }
209         }
210     }
211 }
212
213
214 # Process the arguments after the "enable" or "disable" keyword.
215
216 sub toggle {
217     my @argv = @_;
218     my ($action, %lvls, @start, @stop, @xstartlinks);
219
220     if (!&checklinks) {
221         print " System start/stop links for $initd/$bn do not exist.\n";
222         exit (0);
223     }
224
225     $action = $argv[0];
226     if ($#argv > 1) {
227         while ($#argv > 0 && shift @argv) {
228             if ($argv[0] =~ /^[S2-5]$/) {
229                 $lvls{$argv[0]}++;
230             } else {
231                 &usage ("expected 'S' '2' '3' '4' or '5'");
232             }
233         }
234     } else {
235         $lvls{$_}++ for ('S', '2', '3', '4', '5');
236     }
237
238     push(@start, glob($etcd . '[2-5S].d/[KS][0-9][0-9]' . $bn));
239
240     foreach (@start) {
241         my $islink = &is_link (undef, $_, $bn);
242         next if !$islink;
243
244         next unless my ($lvl, $sk, $seq) = m/^$etcd([2-5S])\.d\/([SK])([0-9]{2})$bn$/;
245         $startlinks[$lvl] = $sk . $seq;
246
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);
251         } else {
252             $xstartlinks[$lvl] = $sk . $seq;
253         }
254     }
255
256     push(@stop, glob($etcd . '[016].d/[KS][0-9][0-9]' . $bn));
257
258     foreach (@stop) {
259         my $islink = &is_link (undef, $_, $bn);
260         next if !$islink;
261
262         next unless my ($lvl, $sk, $seq) = m/^$etcd([016])\.d\/([SK])([0-9]{2})$bn$/;
263         $stoplinks[$lvl] = $sk . $seq;
264     }
265
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";
270     }
271
272     &checklinks ("remove");
273     @startlinks = @xstartlinks;
274
275     1;
276 }
277
278 # Process the arguments after the "defaults" keyword.
279
280 sub defaults {
281     my @argv = @_;
282     my ($start, $stop) = (20, 20);
283
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?$/);
289
290     $start = sprintf("%02d", $start);
291     $stop  = sprintf("%02d", $stop);
292
293     $stoplinks[$_]  = "K$stop"  for (0, 1, 6);
294     $startlinks[$_] = "S$start" for (2, 3, 4, 5);
295
296     1;
297 }
298
299 # Process the arguments after the start or stop keyword.
300
301 sub startstop {
302     my @argv = @_;
303     my($letter, $NN, $level);
304
305     while ($#argv >= 0) {
306         if    ($argv[0] eq 'start') { $letter = 'S'; }
307         elsif ($argv[0] eq 'stop')  { $letter = 'K'; }
308         else {
309             &usage("expected start|stop");
310         }
311
312         if ($argv[1] !~ /^\d\d?$/) {
313             &usage("expected NN after $argv[0]");
314         }
315         $NN = sprintf("%02d", $argv[1]);
316
317         if ($argv[-1] ne '.') {
318             &usage("start|stop arguments not terminated by \".\"");
319         }
320
321         shift @argv; shift @argv;
322         $level = shift @argv;
323         do {
324             if ($level !~ m/^[0-9S]$/) {
325                 &usage(
326                        "expected runlevel [0-9S] (did you forget \".\" ?)");
327             }
328             if (! -d "$etcd$level.d") {
329                 print STDERR
330                     "update-rc.d: $etcd$level.d: no such directory\n";
331                 exit(1);
332             }
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 '.');
337     }
338     1;
339 }
340
341 # Create the links.
342
343 sub makelinks {
344     my($t, $i);
345     my @links;
346
347     if (&checklinks) {
348         print " System start/stop links for $initd/$bn already exist.\n";
349         return 0;
350     }
351     print " Adding system startup for $initd/$bn ...\n";
352
353     # nice unreadable perl mess :)
354
355     for($t = 0; $t < 2; $t++) {
356         @links = $t ? @startlinks : @stoplinks;
357         for($i = 0; $i <= $#links; $i++) {
358             my $lvl = $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");
365         }
366     }
367
368     1;
369 }
370
371 ## Dependency based
372 sub insserv_updatercd {
373     my @args = @_;
374     my @opts;
375     my $scriptname;
376     my $action;
377     my $notreally = 0;
378
379     my @orig_argv = @args;
380
381     while($#args >= 0 && ($_ = $args[0]) =~ /^-/) {
382         shift @args;
383         if (/^-n$/) { push(@opts, $_); $notreally++; next }
384         if (/^-f$/) { push(@opts, $_); next }
385         if (/^-h|--help$/) { &usage; }
386         usage("unknown option");
387     }
388
389     usage("not enough arguments") if ($#args < 1);
390
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);
398             }
399             error_code($rc, "insserv rejected the script header") if $rc;
400             exit $rc;
401         } else {
402             # insserv removes all dangling symlinks, no need to tell it
403             # what to look for.
404             my $rc = system("insserv", @opts) >> 8;
405             if (0 == $rc && !$notreally) {
406                 remove_last_action($scriptname);
407             }
408             error_code($rc, "insserv rejected the script header") if $rc;
409             exit $rc;
410         }
411     } elsif ("defaults" eq $action || "start" eq $action ||
412              "stop" 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);
417
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);
422             }
423             error_code($rc, "insserv rejected the script header") if $rc;
424             exit $rc;
425         } else {
426             error("initscript does not exist: /etc/init.d/$scriptname");
427         }
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);
434         }
435         error_code($rc, "insserv rejected the script header") if $rc;
436         exit $rc;
437     } else {
438         usage();
439     }
440 }
441
442 sub parse_def_start_stop {
443     my $script = shift;
444     my (%lsb, @def_start_lvls, @def_stop_lvls);
445
446     open my $fh, '<', $script or error("unable to read $script");
447     while (<$fh>) {
448         chomp;
449         if (m/^### BEGIN INIT INFO$/) {
450             $lsb{'begin'}++;
451         }
452         elsif (m/^### END INIT INFO$/) {
453             $lsb{'end'}++;
454             last;
455         }
456         elsif ($lsb{'begin'} and not $lsb{'end'}) {
457             if (m/^# Default-Start:\s*(\S?.*)$/) {
458                 @def_start_lvls = split(' ', $1);
459             }
460             if (m/^# Default-Stop:\s*(\S?.*)$/) {
461                 @def_stop_lvls = split(' ', $1);
462             }
463         }
464     }
465     close($fh);
466
467     return (\@def_start_lvls, \@def_stop_lvls);
468 }
469
470 sub lsb_header_for_script {
471     my $name = shift;
472
473     foreach my $file ("/etc/insserv/overrides/$name", "/etc/init.d/$name",
474                       "/usr/share/insserv/overrides/$name") {
475         return $file if -s $file;
476     }
477
478     error("cannot find a LSB script for $name");
479 }
480
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);
485
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);
490
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;
497
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;
503
504         while (defined $_[$argi]) {
505             my $arg = $_[$argi];
506
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 '.') {
515                 next;
516             }
517             push(@arg_start_lvls, $arg) if $start;
518             push(@arg_stop_lvls, $arg) if $stop;
519         } continue {
520             $argi++;
521         }
522     }
523
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)";
530     }
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)";
537     }
538 }
539
540 sub insserv_toggle {
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);
544
545     # Extra arguments to disable|enable action are runlevels. If none
546     # given parse LSB info for Default-Start value.
547     if ($#_ >= 0) {
548         @toggle_lvls = @_;
549     } else {
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.");
554         }
555     }
556
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");
562             next;
563         }
564         push(@symlinks, $_) for glob("/etc/rc$lvl.d/[SK][0-9][0-9]$name");
565     }
566
567     if (!@symlinks) {
568         error("no runlevel symlinks to modify, aborting!");
569     }
570
571     # Toggle S/K bit of script symlink.
572     for my $cur_lnk (@symlinks) {
573         my $sk;
574         my @new_lnk = split(//, $cur_lnk);
575
576         if ("disable" eq $act) {
577             $sk = rindex($cur_lnk, '/S') + 1;
578             next if $sk < 1;
579             $new_lnk[$sk] = 'K';
580         } else {
581             $sk = rindex($cur_lnk, '/K') + 1;
582             next if $sk < 1;
583             $new_lnk[$sk] = 'S';
584         }
585
586         if ($dryrun) {
587             printf("rename(%s, %s)\n", $cur_lnk, join('', @new_lnk));
588             next;
589         }
590
591         rename($cur_lnk, join('', @new_lnk)) or error($!);
592     }
593 }