Rebuild init.d links according to LSB headers
An alternative to Debian insserv
[Description] [Perl script] [download zip]

It produces a shell script consisting of mv, ln, and rm commands, which can then be executed to abide by LSB headers while keeping the current ordering as much as possible.

Last update: Wed 03 Mar 2021. Fix multiple requirements of $all.

NAME


fix-init - rebuild init.d links according to LSB headers

SYNOPSIS


fix-init [-n|--dry-run [file]]
         [-r|--renum [step]]
         [--root root]
         [-v|--verbose [2|3|4]]

fix-init -h|--help [1|2]

OPTIONS

help
An optional value of 1 displays these options in addition to the synopsis. Use 2 for a man-like page.
root
Default to /. Use a different directory for testing.
dry-run
fix-init writes shell commands on a temporary file, then executes it. This option prevents that execution. It is implied if root is not writable. If an output file is specified, it will be used instead of a temporary one, overwriting its previous content if any. If file is "-" its content goes to stdout together with verbose output.
renum
Without this option, fix-init tries to respect the existing order of links. Otherwise, it renumbers them. The default step is 6, resulting in names like S06xyz, S12xyzzy, S18foo, ... This option is implied if dependecy based boot sequencing is in effect. Set it to 0 to prevent renum.
verbose
Minimal verbosity (-v) is recommended, otherwise fix-init is almost silent. At intermediate verbosity, unsatisfied required dependencies (-v 2), optional (``should'') and satisfied ones (-v 3) are displayed, as well as some skipped files and ``strange'' facts. Top verbosity (-v 4) dumps some internal structures, such as the initial and resulting order for each level.

DESCRIPTION

fix-init is designed to allow working with traditional init script links, also known as legacy boot ordering. It reads LSB blocks from the scripts it finds and tries to assign priorities so as to respect their well established order as well as LSB dependencies. File .legacy-bootordering is used as a timestamp to determine what is well established. Files newer than that are candidate to adjustmens. The order of older files is respected. Touching .legacy-bootordering before running fix-init has the same effect as setting renum to 0.

Especially after running update-rc.d in dependency based boot sequencing mode, priorities can be so tight as to not allow further insertions. In that case this script fails, asking for --renum. In either case, an attempt is made to leave gaps between subsequent priority orders.

Circular dependencies are broken respecting the high-priority well established scripts. That is, starting new scripts later. If verbose is enabled, loops are displayed with messages like, for example:

Circular dependencies (K level 0): umountnfs.sh -> foo -> rsyslog!

That means it is Kill at runlevel 0, and the loop is being broken giving the highest priority to the first script. That is, scripts will be stopped in the reverse order than displayed, i.e. rsyslog first. If it were an S, high priorities would imply low numbers, providing for scripts to be started in the order displayed. The initial order determines where to break any circular dependency loop. On building such initial order, non-linked scripts --whose priority orders are not defined--- go last. So, if you want the loop to break at foo rather than at umountnfs.sh, you can remove rc0.d/K??umountnfs.sh and/or create rc0.d/K22foo, then run fix-init again. Alternatively, use --dry-run and edit the resulting script before running it.

Although existing priorities are respected (unless --renum), existing runlevels are not. Except for scripts with no associated LSB info, fix-init always adds and removes files so as to honor the specified runlevels.

fix-init does not require that services bearing the same name don't coexist, as that's a package management job. It copes with missing LSBs and unsatisfied dependencies (but complains). It uses both directories of LSB overriding as defined by insserv; on top of that, it has a configuration file.

CONFIGURATION

LSB blocks specify runlevels in the Default-Start and Default-Stop directives. It is not advisable to customize those defaults by overriding the whole LSB, let alone editing the script itself. Therefore, one needs to save local customizations somewhere else, /etc/fix-init.conf is that file.

Each line specifies how to modify the list of runlevels for a given script. The syntax is like so:

script-name [ =|+|- [ level ]... ] [ / [ =|+|- [ level ]... ]]

The "=", "+", and "-" operators mean replace, increase by appending to, and decrease by removing from the list of runlevels, respectively. Start runlevels list before the "/", Kill after.

Comments ("#"), and empty lines work as usual. Spaces (or commas) around levels are optional. For example:

    # This too can solve the loop above, if the box mounts no NFS
    umountnfs.sh /=   # replace LSB's Default-Stop with an empty list
    gdm3 -2           # avoid starting X at runlevel 2
    gdm3 +34          # but make sure 3 and 4 are set

NOTE: Configuration for a script with no LSB starts with emtpy runlevels, not the actual runlevels that would be considered otherwise. On the other hand, setting levels for nonexistent scripts is a no-op.

BUGS

For wrong names, such as S31a-name -> ../init.d/another-name, the name of the file, ``a-name'', is used for overrides and configuration.

Hard links are not detected.

Plain files that ought to be deleted are renamed to DeleteMe.whatever instead.

No attempt is made to intelligently discriminate between Should- and Required- Start / Stop directives in LSB blocks.

FILES

/etc/fix-init.conf
/etc/init.d/.legacy-bootordering
/etc/insserv/overrides/
/usr/share/insserv/overrides/
/etc/insserv.conf
/etc/insserv.conf.d/

SEE ALSO

insserv(8), update-rc.d(8), chkconfig(8), sysv-rc-conf(8)

AUTHOR

Alessandro Vesely, vesely@tana.it

ACKNOWLEDGMENTS

Werner Fink and Petter Reinholdtsen provided the code for insserv and check-initd-order, which this script heavily draws on.

Perl script

0001: #! /usr/bin/perl
0002: # written by Alessandro Vesely in June 2013.  This is free software.
0003: 
0004: =head1 NAME
0005: 
0006: fix-init - rebuild F<init.d> links according to LSB headers
0007: 
0008: =head1 SYNOPSIS
0009: 
0010: fix-init [-n|--dry-run [I<file>]]
0011:          [-r|--renum [I<step>]]
0012:          [--root I<root>]
0013:          [-v|--verbose [2|3|4]]
0014: 
0015: fix-init -h|--help [1|2]
0016: 
0017: =head1 OPTIONS
0018: 
0019: =over
0020: 
0021: =item help
0022: 
0023: An optional value of I<1> displays these options in addition to the synopsis.
0024: Use I<2> for a man-like page.
0025: 
0026: =item root
0027: 
0028: Default to F</>.  Use a different directory for testing.
0029: 
0030: =item dry-run
0031: 
0032: B<fix-init> writes shell commands on a temporary file, then executes it.
0033: This option prevents that execution.  It is implied if I<root> is not
0034: writable.  If an output I<file> is specified, it will be used instead of a
0035: temporary one, overwriting its previous content if any.  If I<file> is
0036: C<-> its content goes to stdout together with verbose output.
0037: 
0038: =item renum
0039: 
0040: Without this option, B<fix-init> tries to respect the existing order of links.
0041: Otherwise, it renumbers them.  The default I<step> is 6, resulting in names
0042: like F<S06xyz>, F<S12xyzzy>, F<S18foo>, ...  This option is implied if
0043: dependecy based boot sequencing is in effect.  Set it to 0 to prevent
0044: renum.
0045: 
0046: =item verbose
0047: 
0048: Minimal verbosity (I<-v>) is recommended, otherwise B<fix-init> is almost
0049: silent.  At intermediate verbosity, unsatisfied required dependencies (I<-v 2>),
0050: optional ("should") and satisfied ones (I<-v 3>) are displayed, as well as some
0051: skipped files and "strange" facts.  Top verbosity (I<-v 4>) dumps some internal
0052: structures, such as the initial and resulting order for each level.
0053: 
0054: =back
0055: 
0056: =cut
0057: 
0058: use strict;
0059: use warnings;
0060: use feature "switch";
0061: 
0062: use File::Find;
0063: use File::Temp 'tempfile';
0064: use Getopt::Long;
0065: use List::Util qw(max min);
0066: use Data::Dumper;
0067: use Pod::Usage;
0068: 
0069: # 'hint' kind of file, set in sub check_file
0070: use constant {BROKEN_LINK => 1,
0071:    PLAIN_FILE => 2, STRANGE_LINK => 3, NEW_LINK => 4};
0072: 
0073: my ($dryrun, $renum, $help, $root, $verbose);
0074: 
0075: Getopt::Long::Configure('no_ignore_case');
0076: if (!GetOptions('verbose|v:i' => \$verbose,
0077:                 'dry-run|n:s' => \$dryrun,
0078:                 'renum|r:i'   => \$renum,
0079:                 'root=s'      => \$root,
0080:                 'help|h:i'    => \$help))
0081: {
0082:    pod2usage();
0083:    exit 1;
0084: }
0085: 
0086: if (defined($help))
0087: {
0088:    pod2usage(-verbose => $help);
0089: }
0090: 
0091: if (defined($verbose))
0092: {
0093:    $verbose = 1 unless ($verbose); # -v same as -v 1
0094: }
0095: else {$verbose = 0;}
0096: 
0097: $root = '/' unless defined($root);
0098: $root .= '/' unless (substr($root, -1) eq '/');
0099: my $initdir = $root . 'etc/init.d';
0100: my $init_link = "../init.d/";
0101: my $fix_flag = $initdir .'/.legacy-bootordering';
0102: my $overridepath = "/usr/share/insserv/overrides";
0103: my $hostoverridepath =  "/etc/insserv/overrides";
0104: 
0105: $Data::Dumper::Terse = 1;
0106: 
0107: my @levels = map "$_", ('S', 0.. 6);
0108: my %rc_d = map {$_ => $root ."etc/rc$_.d"} @levels;
0109: my $rc_d_length = length($rc_d{0}); # different from length($initdir)
0110: do {
0111:    my @invalid = grep ! -d, values %rc_d;
0112:    push(@invalid, $initdir) unless -d $initdir;
0113:    pod2usage(-msg => 'Invalid dir: '. join(', ', @invalid)) if (@invalid);
0114: };
0115: 
0116: # use timestamp to detect new links to be fixed
0117: my $timestamp = (stat($fix_flag))[9];
0118: if (!defined($timestamp))
0119: {
0120:    $timestamp = 0;
0121:    if (!defined($renum))
0122:    {
0123:       $renum = 999;
0124:       print "Dependecy based boot sequencing detected, --renum implied\n"
0125:          if ($verbose);
0126:    }
0127: }
0128: 
0129: my $defaultstep = 6;
0130: $renum = $defaultstep if (defined($renum) && $renum > 100);
0131: 
0132: # option -r 0, respect order irrespective of timestamp
0133: if (defined($renum) && $renum <= 0)
0134: {
0135:    $renum = undef;
0136:    $timestamp = time();
0137: }
0138: 
0139: # cannot do, because order becomes negative or larger than 99
0140: my $cannot;
0141: 
0142: # which files to skip in $initdir (after chkconfig, modified, to be revised)
0143: my %skip_rc = map {$_ => 1} qw {rc rcS rx skeleton powerfail boot
0144:    boot.local halt.local README};
0145: 
0146: # a hash of all data, keyed by script name
0147: # each services{$script} will have five entries:
0148: # 'lsb' from block, 'provides' 'S' 'K' ('reqS' 'shdS' 'revS' 'reqK' 'shdK' 'revK' 'lvlS' 'lvlK')
0149: # 'hint' kind of file constant
0150: # 'found' for each S/K and level, the order found
0151: # 'want' the order wanted
0152: # 'resp' the respected order
0153: my %services = ();
0154: 
0155: # a hash of script names, keyed by services each provides
0156: my %provides2name = ();
0157: 
0158: # a hash of priority orders found at each level
0159: my %foundpriority = ();
0160: 
0161: # a hash of config lines, keyed by script name
0162: my %configure = ();
0163: die ("Abort fix-init\n") if (load_config('/etc/fix-init.conf'));
0164: print "configure: ", Dumper(\%configure), "\n" if ($verbose > 3);
0165: 
0166: # a hash of virtual services to real ones, after insserv/check-initd-order
0167: my %sysmap;
0168: my $rcbase = "/etc";
0169: die ("Abort fix-init\n") if load_sysmap("$rcbase/insserv.conf");
0170: find(sub {load_sysmap($_) if (-f $_);}, "$rcbase/insserv.conf.d");
0171: print "sysmap: ", Dumper(\%sysmap), "\n" if ($verbose > 3);
0172: 
0173: 
0174: sub configured_lvl
0175: {
0176:    my ($orig, $conf) = @_;
0177:    $orig = [] unless defined($orig);
0178:    $conf = [] unless defined($conf);
0179: 
0180:    my @result = @{$orig};
0181:    while (@{$conf})
0182:    {
0183:       my $op = shift(@{$conf});
0184:       # switch
0185:       for (shift @{$op})
0186:       {
0187:          if ($_ eq '+')
0188:          {
0189:             push(@result, @{$op});
0190:             last;
0191:          }
0192: 
0193:          if ($_ eq '-')
0194:          {
0195:             @result = grep {my $el = $_; !grep {$_ eq $el} @{$op}} @result;
0196:             last;
0197:          }
0198: 
0199:          if ($_ eq '=')
0200:          {
0201:             @result = @{$op};
0202:             last;
0203:          }
0204:       }
0205:    }
0206:    return \@result
0207: }
0208: 
0209: sub set_services_rec
0210: {
0211:    my ($lname, $fname) = @_;
0212:    my $lsb = load_lsb_tags($fname, $lname);
0213:    if (defined($configure{$lname}))
0214:    {
0215:       my $start_conf = $configure{$lname}{'S'};
0216:       my $stop_conf = $configure{$lname}{'K'};
0217:       $lsb = {} unless $lsb;
0218:       ${$lsb}{'lvlS'} = configured_lvl(${$lsb}{'lvlS'}, $start_conf);
0219:       ${$lsb}{'lvlK'} = configured_lvl(${$lsb}{'lvlK'}, $stop_conf);
0220:       print 'Resulting runlevel Start: ', Dumper(${$lsb}{'lvlS'}), "\n",
0221:          'Resulting runlevel Stop: ', Dumper(${$lsb}{'lvlK'}), "\n"
0222:             if ($verbose > 3);
0223:    }
0224: 
0225:    if ($lsb)
0226:    {
0227:       $services{$lname} = {lsb => $lsb, found => {S => {}, K => {}}};
0228:    }
0229:    else
0230:    {
0231:       $services{$lname} = {found => {S => {}, K => {}}};
0232:       print 'No LSB record found for ', $fname, "\n" if ($verbose);
0233:    }
0234: }
0235: 
0236: sub skip_dir
0237: {
0238:    $File::Find::prune = 1;
0239:    print 'Skipping directory ', $File::Find::name, "\n" if ($verbose > 1);
0240: }
0241: 
0242: sub skip_file
0243: {
0244:    my $min_verbose = shift;
0245:    print 'Skipping ', $File::Find::name, "\n" if ($verbose > $min_verbose);
0246: }
0247: 
0248: # File::Find callback (cd and $_ = fname)
0249: sub check_file
0250: {
0251:    return if $_ eq '.';
0252:    return skip_dir() if (-d $_);
0253: 
0254:    my $fname = $_;
0255:    if ($File::Find::dir eq $initdir)
0256:    {
0257:       return skip_file(2) if ($skip_rc{$fname});
0258:       return skip_file(1) if (length($fname) <= 0 || ! -f $fname ||
0259:          substr($fname, -1) eq '~' ||
0260:          $fname =~ /^[\$.#%_\+\-\\\*\[\]\^:\(\)~]+/ ||
0261:          $fname =~ /\.(?:rpm|ba|dpkg)[^.]*$/ ||
0262:          $fname =~ /\.(?:old|new|org|orig|save|swp|core)$/);
0263: 
0264:       # symbolic link in the same directory, e.g. ups-monitor -> nut-client
0265:       return skip_file(0) if (-l $fname && readlink($fname) !~ qr(/));
0266: 
0267:       if (exists($services{$fname}))  # how come?
0268:       {
0269:          print 'Already found: ', $fname, ' ', Dumper($services{$fname}), "\n"
0270:             if ($verbose);
0271:       }
0272:       else
0273:       {
0274:          set_services_rec($fname, $fname);
0275:       }
0276:       return;
0277:    }
0278:    elsif (length($File::Find::dir) == $rc_d_length)
0279:    {
0280:       my $lvl = substr($File::Find::dir, -3, 1);
0281:       return skip_file(0) unless exists($rc_d{$lvl});  # how come?
0282: 
0283:       my $dir = $rc_d{$lvl};
0284:       return skip_file('README' eq $fname? 2: 1)
0285:          unless ($fname =~ /^([SK])(\d\d)([-.\w]+)$/); # same as in rc?
0286: 
0287:       my ($sk, $order, $lname) = ($1, $2, $3);
0288:       my $ftime = (lstat($fname))[9];
0289:       my $hint;
0290:       if (-l _)
0291:       {
0292:          my $l = readlink($fname);
0293:          if (! -e $fname)
0294:          {
0295:             $hint = BROKEN_LINK;
0296:             print 'Broken link ', $File::Find::name, ' -> ', $l, "\n"
0297:                if ($verbose);
0298:          }
0299:          elsif ($l ne $init_link . $lname)
0300:          {
0301:             $hint = STRANGE_LINK;
0302:             print 'Different link name in ',
0303:                $File::Find::name, ' -> ', $l, "\n"
0304:                   if ($verbose);
0305:          }
0306:          elsif ($ftime > $timestamp)
0307:          {
0308:             $hint = NEW_LINK;
0309:             print $lname, ' (', $File::Find::name, ') newer than timestamp', "\n"
0310:                if ($verbose && ($verbose > 3 ||
0311:                   $timestamp && !exists($services{$lname}{'hint'})));
0312:          }
0313:       }
0314:       else
0315:       {
0316:          $hint = PLAIN_FILE if (-f $fname);
0317:          print 'Not a symbolic link ', $fname, ' in ', $dir, "\n"
0318:             if ($verbose);
0319:       }
0320:       if (! exists($services{$lname})) # plain file, link to skipped file, ...
0321:       {
0322:          print 'Considering unexpected script ', $File::Find::name, "\n"
0323:             if ($verbose);
0324:          set_services_rec($lname, $fname);
0325:       }
0326: 
0327:       my $norder = $order + 0;
0328:       if (exists($services{$lname}))
0329:       {
0330:          $services{$lname}{'found'}{$sk}{$lvl} = $norder;
0331:          if ($hint)
0332:          {
0333:             $services{$lname}{'hint'} = {}
0334:                unless exists($services{$lname}{'hint'});
0335:             $services{$lname}{'hint'}{$lvl . $sk . $order} = $hint;
0336:          }
0337:       }
0338:       $norder = 99 - $norder if ($sk eq 'K');
0339:       $foundpriority{$sk} = {} unless exists($foundpriority{$sk});
0340:       $foundpriority{$sk}{$lvl} = [] unless exists($foundpriority{$sk}{$lvl});
0341:       push(@{$foundpriority{$sk}{$lvl}}, $norder)
0342:          unless (grep {$_ == $norder} @{$foundpriority{$sk}{$lvl}});
0343:       return;
0344:    }
0345: 
0346:    skip_file(1);
0347: }
0348: 
0349: sub load_config
0350: {
0351:    my $fname = shift;
0352:    my $fh;
0353:    my $errcount = 0;
0354:    unless (open($fh, "<", $fname))
0355:    {
0356:       unless ($!{ENOENT})
0357:       {
0358:          warn "Cannot open $fname: $!\n";
0359:          $errcount = 1;
0360:       }
0361:       return $errcount;
0362:    }
0363: 
0364:    my $line = 0;
0365:    my $config_parse = sub
0366:    {
0367:       my $expr = shift;
0368:       return undef unless $expr;
0369: 
0370:       my @op = ();
0371:       if ($expr !~ /^\s*([=\+\-])\s*(.*)/)
0372:       {
0373:          warn "no operator (=+-) in \"$expr\" at line $line of $fname\n";
0374:          ++$errcount;
0375:          return undef;
0376:       }
0377: 
0378:       push(@op, $1);
0379:       $expr = $2;
0380:       while ($expr =~ /\G\s*(\S)/cg)
0381:       {
0382:          my $el = $1;
0383:          if (!grep {$_ eq $el} @levels)
0384:          {
0385:             warn
0386:                "$el is not a runlevel, in \"$expr\" at line $line of $fname\n";
0387:             ++$errcount;
0388:             return undef;
0389:          }
0390:          push(@op, $el);
0391:       }
0392:       return \@op;
0393:    };
0394: 
0395:    while (<$fh>)
0396:    {
0397:       ++$line;
0398:       chomp;
0399:       s/#.*//;
0400:       if (/^\s*([^\s,;:]+)\s+([^\/]*)(?:(\/)\s*(.*)?)?/)
0401:       {
0402:          my ($script, $start, $slash, $stop) = ($1, $2, $3, $4);
0403:          $configure{$script} = {S => [], K => []}
0404:             unless defined($configure{$script});
0405: 
0406:          my $op = $config_parse->($start);
0407:          push(@{$configure{$script}{'S'}}, $op) if defined($op);
0408:          if ($slash)
0409:          {
0410:             $op = $config_parse->($stop? $stop: '');
0411:             push(@{$configure{$script}{'K'}}, $op) if defined($op);
0412:          }
0413:       }
0414:    }
0415: 
0416:    close($fh);
0417:    return $errcount;
0418: }
0419: 
0420: 
0421: #################################################################
0422: # from insserv/check-initd-order (modified)
0423: # Map system metapackages to packages. (opposite way around w.r.t. original)
0424: sub load_sysmap {
0425:     my $filename = shift;
0426:     unless (open(CONF, "<", "$filename")) {
0427:         print STDERR "error: unable to load $filename";
0428:         return 1;
0429:     }
0430:     while (<CONF>) {
0431:         chomp;
0432:         s/\#.*$//;
0433:         next if m/^\s*$/;
0434:         if (/^(\$\S+)\s+(\S.*)$/) {
0435:             my ($virt, $list) = ($1, $2);
0436:             $sysmap{$virt} = [] unless exists($sysmap{$virt});
0437:             push(@{$sysmap{$virt}}, split(/[\s,;]+/, $list));
0438:         }
0439:     }
0440:     close(CONF);
0441:     return 0;
0442: }
0443: 
0444: sub load_lsb_tags
0445: {
0446:    my ($initfile, $name) = @_;
0447:    my ($lsb, $fname);
0448: 
0449:    # First try the host override in $hostoverridepath.
0450:    $fname = "$hostoverridepath/$name";
0451:    if (-f $fname)
0452:    {
0453:       print "Override $fname\n" if $verbose > 3;
0454:       $lsb = load_lsb_tags_from_file($fname);
0455:       return $lsb if defined($lsb);
0456:    }
0457: 
0458:    # Next try shipped override file if given
0459:    $fname = "$overridepath/$name";
0460:    if (-f $fname)
0461:    {
0462:       print "Override $fname\n" if $verbose > 3;
0463:       $lsb = load_lsb_tags_from_file($fname);
0464:       return $lsb if defined($lsb);
0465:    }
0466: 
0467:    # usual case last
0468:    return load_lsb_tags_from_file($initfile);
0469: }
0470: 
0471: sub load_lsb_tags_from_file {
0472:    my ($file) = @_;
0473:    print "Loading $file\n" if $verbose > 3;
0474:    ### BEGIN INIT INFO
0475:    # Provides:          boot_facility_1 [ boot_facility_2 ...]
0476:    # Required-Start:    boot_facility_1 [ boot_facility_2 ...]
0477:    # Required-Stop:     boot_facility_1 [ boot_facility_2 ...]
0478:    # Should-Start:      boot_facility_1 [ boot_facility_2 ...]
0479:    # Should-Stop:       boot_facility_1 [ boot_facility_2 ...]
0480:    # X-Start-Before:    boot_facility_1 [ boot_facility_2 ...]
0481:    # X-Stop-After:      boot_facility_1 [ boot_facility_2 ...]
0482:    # Default-Start:     run_level_1 [ run_level_2 ...]
0483:    # Default-Stop:      run_level_1 [ run_level_2 ...]
0484:    # X-Interactive:     true
0485:    # Short-Description: single_line_description
0486:    # Description:       multiline_description
0487:    ### END INIT INFO
0488:    unless (open(FILE, "<$file")) {
0489:       warn "error: Unable to read $file:$!";
0490:       return undef;
0491:    }
0492:    my %lsb;
0493:    my $found = 0;
0494:    my $delim = qr/[\s,;]+/;
0495:    while (<FILE>)
0496:    {
0497:       chomp;
0498:       $found = 1 if (/### BEGIN INIT INFO/);
0499:       next unless $found;
0500:       last if (/### END INIT INFO/);
0501: 
0502:       if (/^#\s*(\S+)\s*:\s*(.*)/i)
0503:       {
0504:          my @items = split($delim, $2);
0505:          my $key = lc($1);
0506:          $key =~ s/[-_]//g;
0507: 
0508:          my $id;
0509:          $id = 'provides' if ($key eq 'provides');
0510:          $id = 'reqS' if ($key eq 'requiredstart');
0511:          $id = 'shdS' if ($key eq 'shouldstart');
0512:          $id = 'revS' if ($key eq 'xstartbefore');
0513:          $id = 'reqK' if ($key eq 'requiredstop');
0514:          $id = 'shdK' if ($key eq 'shouldstop');
0515:          $id = 'revK' if ($key eq 'xstopafter');
0516:          $id = 'lvlS' if ($key eq 'defaultstart');
0517:          $id = 'lvlK' if ($key eq 'defaultstop');
0518:          next unless $id;
0519: 
0520:          print "Duplicate definition of $key in $file\n"
0521:             if ($verbose && exists($lsb{$id}));
0522:          $lsb{$id} = \@items;
0523:       }
0524:    }
0525:    close(FILE);
0526: 
0527:    return undef unless(%lsb);
0528: 
0529:    normalize_req_shd(\%lsb, 'reqS', 'shdS');
0530:    normalize_req_shd(\%lsb, 'reqK', 'shdK');
0531: 
0532:    return \%lsb;
0533: }
0534: 
0535: sub normalize_req_shd
0536: {
0537:    my ($lsb, $req, $shd) = @_;
0538:    return unless (exists(${$lsb}{$req}) || exists(${$lsb}{$shd}));
0539: 
0540:    my @dep;
0541:    if (exists(${$lsb}{$req})) {@dep = @{${$lsb}{$req}}; delete(${$lsb}{$req})}
0542:    else {@dep = ();}
0543: 
0544:    # "should" requirements are represented by prefixing a '+'
0545:    if (exists(${$lsb}{$shd}))
0546:    {
0547:       push(@dep, map(substr($_, 0, 1) eq '+'? $_: "+$_", @{${$lsb}{$shd}}));
0548:       delete(${$lsb}{$shd});
0549:    }
0550: 
0551:    ${$lsb}{substr($req, 3)} = \@dep;
0552: }
0553: 
0554: #######################################################################
0555: # All records are loaded
0556: 
0557: # Given the array of requirements, call proc with each expanded element
0558: sub iterate_svc
0559: {
0560:    my ($ref, $proc) = @_;
0561:    my @array = @{$ref};
0562:    my %seen = ('$none' => 1);
0563: 
0564:    while (@array)
0565:    {
0566:       my $svc = shift(@array);
0567:       my $opt = 0;
0568:       if (substr($svc, 0, 1) eq '+')
0569:       {
0570:          $opt = 1;
0571:          $svc = substr($svc, 1);
0572:       }
0573: 
0574:       next if ($seen{$svc});
0575:       $seen{$svc} = 1;
0576: 
0577:       $proc->($opt, $svc);
0578: 
0579:       push(@array, @{$sysmap{$svc}})
0580:          if (substr($svc, 0, 1) eq '$' &&
0581:             $svc ne '$all' &&
0582:             exists($sysmap{$svc}));
0583:    }
0584: }
0585: 
0586: sub normalize_reverse
0587: {
0588:    my ($script, $xrev) = @_;
0589:    return if (!exists($services{$script}{'lsb'}{$xrev}));
0590: 
0591:    my $direct = substr($xrev, 3);
0592:    my $script_pro = $services{$script}{'lsb'}{'provides'};
0593:    iterate_svc($services{$script}{'lsb'}{$xrev}, sub
0594:    {
0595:       my ($opt, $svc) = @_;
0596:       if ($svc ne '$all' && exists($provides2name{$svc}))
0597:       {
0598:          foreach my $other (@{$provides2name{$svc}})
0599:          {
0600:             if (exists($services{$other}{'lsb'}))
0601:             {
0602:                $services{$other}{'lsb'}{$direct} = []
0603:                   unless exists($services{$other}{'lsb'}{$direct});
0604:                push(@{$services{$other}{'lsb'}{$direct}}, @{$script_pro});
0605:             }
0606:             elsif ($verbose > 1)
0607:             {
0608:                print "\n\n$script: How come $other has no LSB?!\n\n\n"
0609:             }
0610:          }
0611:       }
0612:    });
0613: 
0614:    delete $services{$script}{'lsb'}{$xrev};
0615: }
0616: 
0617: sub get_hint
0618: {
0619:    my ($script, $lvl, $sk, $order) = @_;
0620:    return 0 unless exists($services{$script}{'hint'});
0621:    $services{$script}{'hint'}{$lvl . $sk . sprintf('%02d', $order)} || 0;
0622: }
0623: 
0624: # define which services we want and priority orders we respect
0625: # order is set to -1 initially; find_sk_order(0 will adjust it
0626: # (resp is only needed because of how update-rc.d works)
0627: sub normalize_level
0628: {
0629:    my ($script, $lvlsk) = @_;
0630:    my $sk = substr($lvlsk, 3);
0631:    $services{$script}{'want'}{$sk} = ();
0632:    $services{$script}{'resp'}{$sk} = ();
0633: 
0634:    if (exists($services{$script}{'lsb'}{$lvlsk}))
0635:    {
0636:       # turn LSB array into a hash
0637:       foreach my $lvl (@{$services{$script}{'lsb'}{$lvlsk}})
0638:       {
0639:          $services{$script}{'want'}{$sk}{$lvl} = -1;
0640:          my $order = $services{$script}{'found'}{$sk}{$lvl};
0641:          if (defined($order))
0642:          {
0643:             my $hint = get_hint($script, $lvl, $sk, $order);
0644:             $services{$script}{'resp'}{$sk}{$lvl} =
0645:                $sk eq 'S'? $order: 99 - $order
0646:                   unless ($hint == BROKEN_LINK || $hint == NEW_LINK);
0647:          }
0648:       }
0649:       delete $services{$script}{'lsb'}{$lvlsk};
0650:    }
0651:    else
0652:    {
0653:       # levels stay as they are if not specified otherwise
0654:       foreach my $lvl (keys(%{$services{$script}{'found'}{$sk}}))
0655:       {
0656:          my $order = $services{$script}{'found'}{$sk}{$lvl};
0657:          my $hint = get_hint($script, $lvl, $sk, $order);
0658:          $services{$script}{'want'}{$sk}{$lvl} = -1
0659:             unless ($hint == BROKEN_LINK);
0660:          $services{$script}{'resp'}{$sk}{$lvl} =
0661:             $sk eq 'S'? $order: 99 - $order
0662:                unless ($hint == BROKEN_LINK || $hint == NEW_LINK);
0663:       }
0664:    }
0665: }
0666: 
0667: # give up
0668: sub set_cannot
0669: {
0670:    my $order = shift;
0671:    return if ($order >= 0 && $order < 100);
0672: 
0673:    if (!$cannot)
0674:    {
0675:       warn "Cannot complete sorting order, try using --renum\n";
0676:       $cannot = 1;
0677:    }
0678:    if ($verbose > 3)
0679:    {
0680:       my ($p, $f, $l) = caller;
0681:       print "Cannot order=$order at line $l\n";
0682:    }
0683: }
0684: 
0685: # recursively check dependencies
0686: my @path_array = ();
0687: sub find_sk_order
0688: {
0689:    my ($script, $sk, $lvl) = @_;
0690:    
0691:    return if ($services{$script}{'want'}{$sk}{$lvl} > 0);
0692: 
0693:    my $min_order = $services{$script}{'resp'}{$sk}{$lvl};
0694:    if (defined($min_order) && !$renum)
0695:    {
0696:       $min_order -= 1;
0697:    }
0698:    else
0699:    {
0700:       $min_order = 0;
0701:    }
0702: 
0703:    my $requires_all = 0;
0704: 
0705:    push(@path_array, $script);
0706:    if (exists($services{$script}{'lsb'}{$sk}))
0707:    {
0708:       iterate_svc($services{$script}{'lsb'}{$sk}, sub
0709:       {
0710:          my ($opt, $svc) = @_;
0711:          if ($svc eq '$all')
0712:          {
0713:             $min_order = 99;
0714:             $requires_all = 1;
0715:             if (!$renum && $foundpriority{$sk}{$lvl})
0716:             {
0717:                $min_order = $foundpriority{$sk}{$lvl}[0];
0718:             }
0719:             $min_order -= 1;
0720:             if ($verbose > 2)
0721:             {
0722:                print 'Script ', $script, $opt? ' optionally': '',
0723:                   ' requires $all, so min_order=', $min_order,
0724:                   ' at level ', $sk, $lvl,
0725:                   "\n";
0726:             }
0727:          }
0728:          elsif (exists($provides2name{$svc}))
0729:          {
0730:             foreach my $other (@{$provides2name{$svc}})
0731:             {
0732:                # skip dependencies not used at this level
0733:                if (!exists($services{$other}{'want'}{$sk}{$lvl}))
0734:                {
0735:                   print 'Script ', $script, $opt? ' optionally': '',
0736:                      ' requires ', $svc, ', provided by ', $other,
0737:                      ', which is not enabled at ', $sk, ' level ', $lvl, "\n"
0738:                         if ($verbose > 2);
0739:                   next;
0740:                }
0741: 
0742:                # check circular dependencies
0743:                my $seen;
0744:                for ($seen = 0; $seen < @path_array; ++$seen)
0745:                {
0746:                   last if ($path_array[$seen] eq $other);
0747:                }
0748: 
0749:                if ($seen < @path_array)
0750:                {
0751:                   print 'Circular dependencies (', $sk, ' level ', $lvl, '): ',
0752:                      join(' -> ', @path_array[$seen.. $#path_array]), "!\n",
0753:                         if ($verbose);
0754: 
0755:                   # Arbitrarily break the loop here!
0756:                   # This is the first element that triggered the loop,
0757:                   # so start from 0 if it is not in resp
0758:                   my $order = $services{$other}{'want'}{$sk}{$lvl};
0759:                   if ($order < 0)
0760:                   {
0761:                      if (!$renum)
0762:                      {
0763:                         $order = $services{$other}{'resp'}{$sk}{$lvl};
0764:                         $order = 1 if (!defined($order));
0765:                      }
0766:                      else
0767:                      {
0768:                         $order = 1;
0769:                      }
0770:                      $order -= 1;
0771:                   }
0772:                   print "have $other at order=$order at $sk level $lvl\n"
0773:                      if ($verbose > 3);
0774: 
0775:                   foreach my $s (@path_array[$seen.. $#path_array])
0776:                   {
0777:                      my $want = $services{$s}{'want'}{$sk}{$lvl};
0778:                      my $found;
0779:                      $found = $services{$s}{'resp'}{$sk}{$lvl} if (!$renum);
0780:                      if ($want < $order)
0781:                      {
0782:                         $order += 1;
0783:                         $order = $found
0784:                            if (defined($found) && $found >= $order);
0785:                         $services{$s}{'want'}{$sk}{$lvl} = $order;
0786:                         set_cannot($order);
0787:                      }
0788:                      else
0789:                      {
0790:                         $order = $services{$s}{'want'}{$sk}{$lvl};
0791:                      }
0792:                   }
0793:                   print "have ". join(', ', map
0794:                      {"$_=". $services{$_}{'want'}{$sk}{$lvl}}
0795:                         @path_array[$seen.. $#path_array]), "\n"
0796:                            if ($verbose > 3);;
0797:                   next;
0798:                }
0799: 
0800:                find_sk_order($other, $sk, $lvl);
0801:                my $other_ord = $services{$other}{'want'}{$sk}{$lvl};
0802:                $min_order = $other_ord if ($other_ord >= $min_order);
0803: 
0804:                if ($verbose > 2)
0805:                {
0806:                   print 'Script ', $script, $opt? ' optionally': '',
0807:                      ' requires ', $svc, ', provided by ', $other,
0808:                      $other_ord >= 0? ' (at order '. $other_ord .')': '',
0809:                      ' at level ', $sk, $lvl,
0810:                      "\n";
0811:                }
0812:             }
0813:          }
0814:          elsif ($verbose > $opt + 1)
0815:          {
0816:             my $direct = substr($svc, 0, 1) eq '$'? ' directly': '';
0817:             print 'Script ', $script, $opt? ' optionally': '',
0818:                ' requires ', $svc, ', which is not provided', $direct,
0819:                ' at level ', $sk, $lvl, "\n"
0820:                   if (!$direct || $verbose > 2);
0821:          }
0822:       });
0823:    }
0824: 
0825:    if ($services{$script}{'want'}{$sk}{$lvl} < 0)
0826:    {
0827:       my $x;
0828:       my $target = $min_order + 1;
0829:       if (!$renum)
0830:       {
0831:          my $found = $services{$script}{'resp'}{$sk}{$lvl};
0832:          if (defined($found))
0833:          {
0834:             # leave alone old scripts which require $all
0835:             if ($min_order < $found || $requires_all)
0836:             {
0837:                $x = $found;
0838:                print 'Script ', $script, ' left at ', $found,
0839:                   ' althought $all would imply ', $target, "\n"
0840:                      if ($verbose && $target > $found);
0841:             }
0842:          }
0843:       }
0844:       $x = $target unless defined($x);
0845:       $services{$script}{'want'}{$sk}{$lvl} = $x;
0846:       print "have $script with min_order=$min_order, set to $x\n"
0847:          if ($verbose > 3);
0848:       set_cannot($x);
0849:    }
0850: 
0851:    pop(@path_array);
0852: }
0853: 
0854: # initial sort, and final adjustments.  Return the array of all scripts
0855: # that we want to exist at the given sk/level.
0856: sub do_find_sk_order
0857: {
0858:    my ($sk, $lvl) = @_;
0859: 
0860:    # sort orders found at this level, in reverse order
0861:    if (exists($foundpriority{$sk}{$lvl}))
0862:    {
0863:       my @temp = sort {$b <=> $a} @{$foundpriority{$sk}{$lvl}};
0864:       $foundpriority{$sk}{$lvl} = \@temp;
0865:    }
0866:    else
0867:    {
0868:       $foundpriority{$sk}{$lvl} = [];
0869:    }  
0870:    
0871:    # sort keys according to the existing order, so that any circular
0872:    # dependency loop stays the way it was.  New services which are
0873:    # part of a loop will be started after existing ones.
0874:    my @scripts = sort
0875:    {
0876:       my $fa = $services{$a}{'resp'}{$sk}{$lvl};
0877:       my $fb = $services{$b}{'resp'}{$sk}{$lvl};
0878:       $fa = 111 unless defined($fa);
0879:       $fb = 111 unless defined($fb);
0880:       ($fa - $fb) || $a cmp $b;
0881:    } grep(exists($services{$_}{'want'}{$sk}{$lvl}), keys(%services));
0882: 
0883:    print "\n=============================\nRUNLEVEL ",
0884:       $lvl, ', ', $sk, ": Initial order:\n", Dumper(\@scripts), "\n"
0885:          if $verbose > 3;
0886: 
0887:    foreach (@scripts)
0888:    {
0889:       find_sk_order($_, $sk, $lvl);
0890:       warn 'INTERNAL ERROR: not empty: ', Dumper(\@path_array), "\n"
0891:          if @path_array > 0;
0892:    }
0893: 
0894:    my %order = ();
0895:    foreach my $s (@scripts)
0896:    {
0897:       if (exists($services{$s}{'want'}{$sk}{$lvl}))
0898:       {
0899:          my $n = $services{$s}{'want'}{$sk}{$lvl};
0900:          $order{$n} = [] unless exists($order{$n});
0901:          push(@{$order{$n}}, $s);
0902:       }
0903:    }
0904: 
0905:    my @want = sort {$a - $b} keys(%order);
0906:    if ($verbose > 3)
0907:    {
0908:       my $oref = \%order;
0909:       $Data::Dumper::Sortkeys = sub
0910:       {
0911:          my $ref = shift;
0912:          return \@want if ($ref eq $oref);
0913:       };
0914:       print "\nRUNLEVEL ", $lvl, ', ', $sk, ":\n",
0915:          '%order = ', Dumper($oref), "\n";
0916:       $Data::Dumper::Sortkeys = 0;
0917:    }
0918: 
0919:    if (scalar(@want) >= 100)
0920:    {
0921:       print "FAILED: unable to establish $sk order for level $lvl\n"
0922:          if ($verbose);
0923:       print 'Numbers for ', $sk, 'nn: ', Dumper(\@want), "\n"
0924:          if ($verbose > 3);
0925:       return undef;
0926:    }
0927: 
0928:    if ($renum)
0929:    {
0930:       my $ren = $renum;
0931:       if (scalar(@want) * $ren >= 100)
0932:       {
0933:          $ren = int(100.0/scalar(@want));
0934:          print "$renum is too hight for renum ($sk, $lvl), reduced to $ren\n"
0935:             if ($verbose);
0936:       }
0937: 
0938:       my $i;
0939:       for ($i = 0; $i < @want;)
0940:       {
0941:          my $n = $want[$i];
0942:          my $m = ++$i * $ren;
0943:          $services{$_}{'want'}{$sk}{$lvl} = $m foreach @{$order{$n}};
0944:       }
0945:    }
0946: 
0947:    elsif (scalar(@want) > 1)
0948:    # leave some gaps around newly inserted orders
0949:    {
0950:       my ($left, $first, $count, $right, $is_new, $i);
0951:       $left = 0;
0952:       $i = 0;
0953:       do
0954:       {
0955:          $count = 0;
0956:          $first = $i;
0957:          do
0958:          {
0959:             $right = $want[$i];
0960:             $is_new = !grep {$_ == $right} @{$foundpriority{$sk}{$lvl}};
0961: 
0962:             ++$count;
0963:             ++$i;
0964:          } while ($is_new && $i < @want);
0965: 
0966:          if ($is_new) {$right = 99;} else {--$count;}
0967:          my $step = int(($right - $left)/($count + 1));
0968: 
0969:          # we have $count newly inserted orders between $left and $right
0970:          if ($count > 0 && $step > 1)
0971:          {
0972:             $step = $defaultstep if ($step > $defaultstep);
0973:             while ($count-- > 0)
0974:             {
0975:                my $n = $want[$first++];
0976:                $left += $step;
0977:                $services{$_}{'want'}{$sk}{$lvl} = $left foreach @{$order{$n}};
0978:                print "Adjust $sk level $lvl: $n -> $left\n" if ($verbose > 3);
0979:             }
0980:          }
0981: 
0982:          $left = $right;
0983: 
0984:       } while ($i < @want);
0985:    }
0986: 
0987:    return \@scripts
0988: }
0989: 
0990: sub write_commands
0991: {
0992:    my ($scriptfh, $sk, $lvl, $scripts) = @_;
0993:    my $changes = 0;
0994:    my $renumbered = 0;
0995:    my $rc_dir = $rc_d{$lvl};
0996:    my $def_v = $renum? 3: 1;
0997: 
0998:    # sort by target for readability
0999:    foreach my $s (sort {$services{$a}{'want'}{$sk}{$lvl} <=>
1000:       $services{$b}{'want'}{$sk}{$lvl}} @{$scripts})
1001:    {
1002:       my $min_v = $def_v;
1003:       my $display;
1004:       my $dsk = $rc_dir .'/'. $sk;
1005:       my $new = $services{$s}{'want'}{$sk}{$lvl};
1006:       $new = 99 - $new if ($sk eq 'K');
1007:       my $new2 = sprintf('%02d', $new);
1008:       my $old = $services{$s}{'found'}{$sk}{$lvl};
1009:       if (defined($old))
1010:       {
1011:          if ($old != $new)
1012:          {
1013:             my $old2 = sprintf('%02d', $old);
1014:             print $scriptfh
1015:                'mv -T '. $dsk . $old2 . $s .' '. $dsk . $new2 . $s ." \n";
1016:             $display = 'moved from '. $old .' to ';
1017:             ++$changes;
1018:             ++$renumbered;
1019:             set_cannot($old);
1020:          }
1021:          else
1022:          {
1023:             $display = 'remains at ';
1024:             $min_v = 3;
1025:          }
1026:       }
1027:       else
1028:       {
1029:          print $scriptfh
1030:             'ln -s -T '. $init_link . $s .' '. $dsk . $new2 . $s ." \n";
1031:          $display = 'new, linked at ';
1032:          ++$changes;
1033:       }
1034:       print $s, ', ', $display, $new, "\n" if ($verbose > $min_v);
1035:       set_cannot($new);
1036:    }
1037:    print "$renumbered script(s) renumbered at RUNLEVEL $lvl, $sk\n"
1038:       if ($verbose && $renumbered && !$renum);
1039:    return $changes;
1040: }
1041: 
1042: sub write_rm
1043: {
1044:    my ($scriptfh, $sk, $lvl) = @_;
1045:    my $changes = 0;
1046: 
1047:    my $rc_dir = $rc_d{$lvl};
1048:    my @scripts = grep(exists($services{$_}{'found'}{$sk}{$lvl}) &&
1049:       !exists($services{$_}{'want'}{$sk}{$lvl}), keys(%services));
1050: 
1051:    foreach my $s (@scripts)
1052:    {
1053:       my $order = sprintf('%02d', $services{$s}{'found'}{$sk}{$lvl});
1054:       my $hint = get_hint($s, $lvl, $sk, $order);
1055:       my $fname = $rc_dir .'/'. $sk . $order . $s;
1056: 
1057:       if ($hint == PLAIN_FILE)
1058:       {
1059:          print $scriptfh 'mv -T '. $fname .' '.
1060:             $rc_dir .'/DeleteMe.'. $sk . $order . $s, "\n";
1061:       }
1062:       else
1063:       {
1064:          print $scriptfh 'rm -f '. $fname, "\n";
1065:       }
1066:       ++$changes;
1067:    }
1068:    return $changes;
1069: }
1070: #################################################################
1071: 
1072: # populate %services
1073: find(\&check_file, ($initdir, values %rc_d));
1074: 
1075: # populate %provides2name
1076: foreach my $script (keys(%services))
1077: {
1078:    if (!exists($services{$script}{'lsb'}{'provides'}))
1079:    {
1080:       $services{$script}{'lsb'}{'provides'} = [$script];
1081:       print "$script misses LSB's Provides\n" if ($verbose > 1);
1082:    }
1083: 
1084:    my @provides = @{$services{$script}{'lsb'}{'provides'}};
1085:    next unless @provides;
1086: 
1087:    foreach my $svc (@provides)
1088:    {
1089:       $provides2name{$svc} = [] unless exists($provides2name{$svc});
1090:       push(@{$provides2name{$svc}}, $script);
1091:    }
1092: }
1093: 
1094: # normalize reverse X-* dependencies
1095: foreach my $script (keys(%services))
1096: {
1097:    normalize_reverse($script, 'revS');
1098:    normalize_reverse($script, 'revK');
1099:    normalize_level($script, 'lvlS');
1100:    normalize_level($script, 'lvlK');
1101: }
1102: 
1103: # output file
1104: my ($scriptfh, $scriptname, $runscript);
1105: if ($dryrun)
1106: {
1107:    if ($dryrun eq '-')
1108:    {
1109:       $scriptname = 'stdout';
1110:       $scriptfh = *STDOUT;
1111:    }
1112:    else
1113:    {
1114:       $scriptname = $dryrun;
1115:       open($scriptfh, '>', $dryrun) or
1116:          die("Cannot open $dryrun for writing: $!\n");
1117:    }
1118: }
1119: else
1120: {
1121:    $runscript = (!defined($dryrun) && -w $root)? 1: 0;
1122:    ($scriptfh, $scriptname) =
1123:       tempfile("fix-init-XXXXX",
1124:          DIR => '/tmp', UNLINK => $runscript);
1125:    die unless $scriptname;
1126: }
1127: 
1128: my $shell = exists($ENV{'SHELL'})? $ENV{'SHELL'}: '/bin/sh';
1129: print $scriptfh '#! ', $shell, "\n",
1130:    '# automatically created by fix-init on '. localtime(), "\n";
1131: 
1132: # sort dependencies for each level
1133: my $changes = 0;
1134: foreach my $lvl (@levels)
1135: {
1136:    print "\n" if ($verbose > 1);
1137:    my $start = do_find_sk_order('S', $lvl);
1138:    my $stop = do_find_sk_order('K', $lvl);
1139: 
1140:    if (!defined($start) || !defined($stop))
1141:    {
1142:       warn "abort\n";
1143:       $runscript = 0;
1144:       last;
1145:    }
1146: 
1147:    print $scriptfh "\n", '# RUNLEVEL ', $lvl, "\n";
1148:    $changes += write_rm($scriptfh, 'S', $lvl);
1149:    $changes += write_rm($scriptfh, 'K', $lvl);
1150:    $changes += write_commands($scriptfh, 'S', $lvl, $start);
1151:    $changes += write_commands($scriptfh, 'K', $lvl, $stop);
1152: }
1153: 
1154: # stick to traditional boot and set timestamp
1155: print $scriptfh 'touch '. $fix_flag, "\n";
1156: 
1157: if ($verbose)
1158: {
1159:    if ($verbose > 3)
1160:    {
1161:       print '%services = ', Dumper(\%services), "\n";
1162:       print '%provides2name = ', Dumper(\%provides2name), "\n";
1163:       print '%foundpriority = ', Dumper(\%foundpriority), "\n";
1164:    }
1165:    print "$changes total change(s)\n";
1166: }
1167: 
1168: # run it
1169: if ($runscript)
1170: {
1171:    my $old = select($scriptfh);
1172:    $| = 1;
1173:    print "\n";
1174:    select($old);
1175:    if (!$cannot)
1176:    {
1177:       print "Running $shell -c \". $scriptname\"\n" if ($verbose > 1);
1178:       system($shell, '-c', '. '. $scriptname)
1179:    }
1180:    close($scriptfh);
1181:    if ($? == -1)
1182:    {
1183:       warn "failed to execute $shell: $!\n";
1184:    }
1185:    elsif ($? & 127)
1186:    {
1187:       warn sprintf("$shell killed by signal %d\n", ($? & 127));
1188:    }
1189:    elsif ($? >> 8)
1190:    {
1191:       warn sprintf("return code from $shell %d\n", ($? >> 8));
1192:    }
1193: }
1194: else
1195: {
1196:    close($scriptfh);
1197:    my $bye = "Commands saved to $scriptname\n";
1198:    $bye .= "($changes total change(s))\n" unless ($verbose);
1199:    if ($dryrun)
1200:    {
1201:       print $bye if ($verbose > 3);
1202:    }
1203:    else
1204:    {
1205:       warn $bye;
1206:    }
1207: }
1208: 
1209: 1;
1210: 
1211: __END__
1212: 
1213: 
1214: =head1 DESCRIPTION
1215: 
1216: B<fix-init> is designed to allow working with traditional init script links,
1217: also known as legacy boot ordering.  It reads LSB blocks from the scripts it
1218: finds and tries to assign priorities so as to respect their well established
1219: order as well as LSB dependencies.  File F<.legacy-bootordering> is used as a
1220: timestamp to determine what is well established.  Files newer than that
1221: are candidate to adjustmens.  The order of older files is respected.
1222: Touching F<.legacy-bootordering> before running B<fix-init> has the same
1223: effect as setting renum to 0.
1224: 
1225: Especially after running update-rc.d in dependency based boot sequencing mode,
1226: priorities can be so tight as to not allow further insertions.  In that case
1227: this script fails, asking for --renum.  In either case, an attempt is made to
1228: leave gaps between subsequent priority orders.
1229: 
1230: Circular dependencies are broken respecting the high-priority well established
1231: scripts.  That is, starting new scripts later.  If verbose is enabled, loops
1232: are displayed with messages like, for example:
1233: 
1234: =over
1235: 
1236: Circular dependencies (K level 0): umountnfs.sh -E<gt> foo -E<gt> rsyslog!
1237: 
1238: =back
1239: 
1240: That means it is I<K>ill at runlevel I<0>, and the loop is being broken giving
1241: the highest priority to the first script.  That is, scripts will be stopped in
1242: the reverse order than displayed, i.e. rsyslog first.  If it were an I<S>,
1243: high priorities would imply low numbers, providing for scripts to be started in
1244: the order displayed.  The initial order determines where to break any circular
1245: dependency loop.  On building such initial order, non-linked scripts --whose
1246: priority orders are not defined-- go last.  So, if you want the loop to break
1247: at foo rather than at umountnfs.sh, you can remove rc0.d/K??umountnfs.sh and/or
1248: create rc0.d/K22foo, then run fix-init again.  Alternatively, use --dry-run and
1249: edit the resulting script before running it.
1250: 
1251: Although existing priorities are respected (unless --renum), existing runlevels
1252: are not.  Except for scripts with no associated LSB info, B<fix-init> always
1253: adds and removes files so as to honor the specified runlevels.
1254: 
1255: B<fix-init> does not require that services bearing the same name don't coexist,
1256: as that's a package management job.  It copes with missing LSBs and unsatisfied
1257: dependencies (but complains).  It uses both directories of LSB overriding as
1258: defined by B<insserv>; on top of that, it has a configuration file.
1259: 
1260: =head1 CONFIGURATION
1261: 
1262: LSB blocks specify runlevels in the Default-Start and Default-Stop directives.
1263: It is not advisable to customize those defaults by overriding the whole LSB,
1264: let alone editing the script itself.  Therefore, one needs to save local
1265: customizations somewhere else, F</etc/fix-init.conf> is that file.
1266: 
1267: Each line specifies how to modify the list of runlevels for a given script.
1268: The syntax is like so:
1269: 
1270: =over
1271: 
1272: I<script-name> [ =|+|- [ I<level> ]... ] [ / [ =|+|- [ I<level> ]... ]]
1273: 
1274: =back
1275: 
1276: The C<=>, C<+>, and C<-> operators mean replace, increase by appending to,
1277: and decrease by removing from the list of runlevels, respectively.  Start
1278: runlevels list before the C</>, Kill after.
1279: 
1280: Comments (C<#>), and empty lines work as usual.  Spaces (or commas) around
1281: levels are optional.  For example:
1282: 
1283:     # This too can solve the loop above, if the box mounts no NFS
1284:     umountnfs.sh /=   # replace LSB's Default-Stop with an empty list
1285: 
1286:     gdm3 -2           # avoid starting X at runlevel 2
1287:     gdm3 +34          # but make sure 3 and 4 are set
1288: 
1289: B<NOTE>: Configuration for a script with no LSB starts with emtpy runlevels,
1290: not the actual runlevels that would be considered otherwise.  On the other
1291: hand, setting levels for nonexistent scripts is a no-op.
1292: 
1293: =head1 BUGS
1294: 
1295: For wrong names, such as F<S31a-name> -E<gt> F<../init.d/another-name>, the
1296: name of the file, "a-name", is used for overrides and configuration.
1297: 
1298: Hard links are not detected.
1299: 
1300: Plain files that ought to be deleted are renamed to F<DeleteMe.whatever>
1301: instead.
1302: 
1303: No attempt is made to intelligently discriminate between Should- and Required-
1304: Start / Stop directives in LSB blocks.
1305: 
1306: =head1 FILES
1307: 
1308: =over
1309: 
1310: =item F</etc/fix-init.conf>
1311: 
1312: =item F</etc/init.d/.legacy-bootordering>
1313: 
1314: =item F</etc/insserv/overrides/>
1315: 
1316: =item F</usr/share/insserv/overrides/>
1317: 
1318: =item F</etc/insserv.conf>
1319: 
1320: =item F</etc/insserv.conf.d/>
1321: 
1322: =back
1323: 
1324: =head1 SEE ALSO
1325: 
1326: B<insserv>(8), B<update-rc.d>(8), B<chkconfig>(8), B<sysv-rc-conf>(8)
1327: 
1328: =head1 AUTHOR
1329: 
1330: Alessandro Vesely, vesely@tana.it
1331: 
1332: =head1 ACKNOWLEDGMENTS
1333: 
1334: Werner Fink and Petter Reinholdtsen provided the code for F<insserv> and
1335: F<check-initd-order>, which this script heavily draws on.
1336: 
1337: =cut
1338: 
1339: 
zero rights

Updates

27 Oct 2018:
09 Jul 2021: