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.

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.
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: It is necessary to touch that file after considered priority adjustments.

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

Updates

27 Oct 2018: