#!/usr/bin/perl -w # This program is distributed under the terms of the GNU General Public License # # This file is part of sysv-rc-conf. # # sysv-rc-conf is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # sysv-rc-conf is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with sysv-rc-conf; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Copyright 2004 Joe Oppegaard # use strict; use Getopt::Long qw(:config no_ignore_case); use Curses; use Curses::UI; use constant { BOTTOM_LAB_HEIGHT => 2, BOTTOM_WIN_HEIGHT => 4, DEFAULT_K_PRI => 80, DEFAULT_S_PRI => 20, LABEL_WIDTH => 10, LIST_SN_LENGTH => 12, LIST_SN_PAD => 1, MAX_ROWS => 8, TOP_LABEL_HEIGHT => 2, }; my $VERSION = "0.98"; my %opts = ( cache_dir => "/var/lib/sysv-rc-conf", order => 'a', priority => '', root => '/', show => '', verbose => '', chkcfg_levels => '2345', # default runlevels to affect if not specified chkcfg_list => undef, chkcfg_sn => '', chkcfg_state => '', ); GetOptions("cache=s" => \$opts{cache_dir}, "level=s" => \$opts{chkcfg_levels}, "list:s" => \$opts{chkcfg_list}, "order=s" => \$opts{order}, "priority" => \$opts{priority}, "root=s" => \$opts{root}, "show=s" => \$opts{show}, "verbose=s" => \$opts{verbose}, "Version" => sub { print STDERR "$0 $VERSION\n"; exit; }, ); my $runlevel_cmd = '/sbin/runlevel'; $opts{verbose} ||= "/dev/null"; open VERBOSE, "> $opts{verbose}" or die "Can't open $opts{verbose} : $!"; my $etc_rc = $opts{root} . "/etc/rc.d/rc"; my $initd = $opts{root} . "/etc/init.d"; my @rls = qw/ 1 2 3 4 5 7 8 9 0 6 S /; check_args(); setup_cache_env(); my @show_rls = split //, $opts{show}; # Page index my $current_screen = 0; # Page screens my @s = (); # All the runlevel information my %runlevels = runlevel_status(); list_output(%runlevels) if defined $opts{chkcfg_list}; chkconfig_emulation(); my $cui = new Curses::UI( -clear_on_exit => 0, -color_support => 1, -default_colors => 1, ) or die "Can't create base Curses::UI object"; create_bottom_box(); # Get the service names for each screen my @snames_per_screen = split_services(); create_main_window(); my %box_pos = (x => '00', y => '00'); $cui->set_binding( \&toggle_help, "h" ); $cui->set_binding( \&revert_changes, "r") ; $cui->set_binding( sub { exit }, "q" ); $cui->set_binding( \&next_page, "\cN" ); $cui->set_binding( \&prev_page, "\cP" ); $s[$current_screen]->focus(); $cui->mainloop(); #--- rc access and modification subs ---# sub update_link { my ($sn, $rl, $sk, $pri) = @_; if (defined $sn && defined $rl && defined $sk && defined $pri) { if (-e "$etc_rc$rl.d/$sk$pri$sn") { # The symlink we are trying to make already exists return 'exists'; } } opendir (RL, "$etc_rc$rl.d") or die "$0: opendir $etc_rc$rl.d : $!"; foreach (grep { /[SK]\d\d$sn/i } readdir(RL)) { verbose("rm $etc_rc$rl.d/$_"); unlink "$etc_rc$rl.d/$_" or die "Can't unlink $etc_rc$rl.d/$_ : $!"; } # If in priority mode and are completely deleting the link, $sk will # be empty. return 1 if $sk eq ''; $pri = get_pri_cache($sn, $rl, $sk) unless $pri; unless ($pri =~ /^\d\d$/) { die "Priority isn't two numbers: $pri" } unless ($sk =~ /^[SK]$/) { die "You have to use S or K to start a link" } verbose("symlink $initd/$sn $etc_rc$rl.d/$sk$pri$sn"); # unlike ln relative symlinks are relative to the target file, not the cwd symlink "../init.d/$sn", "$etc_rc$rl.d/$sk$pri$sn" or die "Can't symlink $etc_rc$rl.d/$sk$pri$sn to ../init.d/$sn : $!\n"; } sub usage { print STDERR <] USAGE } sub chkconfig_emulation { $opts{chkcfg_sn} = $ARGV[0] if defined $ARGV[0]; $opts{chkcfg_state} = $ARGV[1] if defined $ARGV[1]; if ($opts{chkcfg_sn} && not $opts{chkcfg_state}) { # Check to see if service is configured to run in current rl # exit true if it is, false if not. # See chkconfig(8) my $current_rl = current_runlevel(); if (exists $runlevels{$opts{chkcfg_sn}}{$current_rl}) { if ($runlevels{$opts{chkcfg_sn}}{$current_rl} =~ /^S/) { # Service is configured to start, exit true exit 0; } } exit 1; } if ($opts{chkcfg_sn} && $opts{chkcfg_state}) { my $sk = ''; if ($opts{chkcfg_state} =~ /^on$/i) { $sk = 'S'; } elsif ($opts{chkcfg_state} =~ /^off$/i) { $sk = 'K'; } else { usage(); } foreach (split //, $opts{chkcfg_levels}) { update_link($opts{chkcfg_sn}, $_, $sk, undef); } exit 0; } # Program isn't being called like chkconfig, so return to normal # operation. return 1; } sub list_output { my (%runlevels) = @_; # There was an argument to --list my $opt_sn = $opts{chkcfg_list}; foreach my $sn (sort keys %runlevels) { my $output = substr $sn, 0, LIST_SN_LENGTH; $output .= " " until length $output >= LIST_SN_LENGTH + LIST_SN_PAD; foreach my $rl (sort keys %{$runlevels{$sn}}) { $output .= "$rl:"; if ($runlevels{$sn}{$rl} =~ /^[Ss]/) { $output .= "on"; } else { $output .= "off"; } $output .= "\t"; } chop($output); $output .= "\n"; if ($opt_sn) { print $output if $sn eq $opt_sn; } else { print $output; } } exit 0; } sub revert_changes { # Lookup table my %cache = (); foreach my $scr (@s) { for (my $i = 0; $i < max_services() ; $i++) { for (my $j = 0; $j <= $#show_rls; $j++) { my $obj = $scr->getobj(zero_pad($i).zero_pad($j)); my $ud = $obj->userdata(); $cache{ $ud->{sn} }{ $ud->{runlevel} } = $obj; } } } foreach my $sn (keys %runlevels) { foreach my $rl (keys %{$runlevels{$sn}}) { $runlevels{$sn}{$rl} =~ /^([SK])(\d\d)$/; my ($sk, $pri) = ($1, $2); next if update_link($sn, $rl, $sk, $pri) eq 'exists'; if (exists($cache{$sn}{$rl})) { my $box = $cache{$sn}{$rl}; if ($opts{priority}) { # Reset the text $box->text($sk.$pri); $box->draw(1); } else { # Simple layout, just toggle the box. $box->toggle(); $box->draw(1); } } } } $cui->dialog("Symlinks restored to original state!"); } sub start_service { my ($widget) = @_; my $ud = $widget->userdata(); st_service($ud->{sn}, 'start'); } sub stop_service { my ($widget) = @_; my $ud = $widget->userdata(); st_service($ud->{sn}, 'stop'); } sub st_service { my ($sn, $st) = @_; my $output = `$initd/$sn $st`; verbose("$initd/$sn $st : $output"); $cui->dialog("Results of $initd/$sn $st :\n" . $output); } sub get_pri_cache { my ($sn, $rl, $sk) = @_; # See if we can get an exact match from the cache, if not try to match # the S or K except in a different run level, if there still is not a match # get the opposite of S or K on another runlevel, if still no match return # the default. open CACHE, "< $opts{cache_dir}/services" or die "Can't open $opts{cache_dir}/services : $!"; chomp (my @cache = ); close CACHE; # Try an exact cache match foreach (@cache) { # $arg_rl $arg_sk $arg_pri $arg_sn next unless /^$rl\s+$sk\s+(\d\d)\s+$sn$/; verbose("Got exact cache match for priority: $_"); return $1; } # Try an $sk match, except on any runlevel foreach (@cache) { next unless /^[\dsS]\s+$sk\s+(\d\d)\s+$sn$/; verbose("Got differing runlevel cache for priority: $_"); return $1; } # Ok, try to match on any runlevel with either S or K foreach (@cache) { next unless /^[\dsS]\s+([SK])\s+(\d\d)\s+$sn$/; verbose("Returning difference of 100 and $2: $_"); # Sequence numbers are usually defined as stop = 100 - start # So that means that start = 100 - stop # Above we would have returned if $sk eq $1 so we know that $1 is # the opposite of $sk. So return 100 - $2. return zero_pad(100 - $2); } verbose("No cache found, returning default"); return DEFAULT_S_PRI if $sk eq 'S'; return DEFAULT_K_PRI if $sk eq 'K'; } sub pri_box_changed { my ($widget) = @_; my $ud = $widget->userdata(); my $new_link = $widget->get(); if ($new_link eq $ud->{last_good_text}) { # Text didn't actually change, just moved out of the box return 1; } if ($new_link =~ /^([KS])(\d\d)$/ or $new_link =~ /^$/) { my ($sk, $pri) = ('', ''); if (defined $1 and defined $2) { $sk = $1; $pri = $2; } update_link($ud->{sn}, $ud->{runlevel}, $sk, $pri); $ud->{last_good_text} = $new_link; } else { $cui->error("Incorrect format: $new_link\n" . "The correct format is a K or S followed by two digits!\n" . "Returning field back to original state." ); # Set the text in the box back to whatever the last good text was. $widget->{-text} = $ud->{last_good_text}; } } sub simple_box_changed { my ($box) = @_; my $userdata = $box->userdata(); $userdata->{changed}++; if ($box->get()) { update_link($userdata->{sn}, $userdata->{runlevel}, 'S', undef) } else { update_link($userdata->{sn}, $userdata->{runlevel}, 'K', undef) } } sub runlevel_status { my %runlevels = (); opendir (INITD, $initd) or die "$0: opendir $initd : $!"; while ( defined(my $service = readdir INITD) ) { next if $service =~ /\.sh$/; # see the debian policy manual next if $service =~ /^\.+$/; # ignore . and .. next unless -x "$initd/$service"; # ignore stuff like README $runlevels{$service} = { }; } closedir INITD or die "$0: closedir $initd : $!"; # While 7-9 usually aren't used, init supports it. foreach my $rl (@rls) { unless (opendir(DIR, "$etc_rc$rl.d")) { next if $rl =~ /^[789S]$/; die "$0: opendir $etc_rc$rl.d : $!"; } while ( defined(my $file = readdir DIR) ) { $file = "$etc_rc$rl.d/$file"; # Add the pathname to the file next unless -l $file; next if $file =~ /\.sh$/; next unless $file =~ /([SK])(\d\d)(.+)$/; my ($sk, $pri, $sn) = ($1, $2, $3); $runlevels{$sn}{$rl} = $sk.$pri; } closedir DIR or die "$0: closedir $etc_rc$rl.d : $!"; } update_cache(\%runlevels); return %runlevels; } sub setup_cache_env { unless (-e $opts{cache_dir}) { verbose("Creating non-existant cache directory: $opts{cache_dir}"); mkdir $opts{cache_dir} or die "Can't create $opts{cache_dir} : $!"; } unless (-e "$opts{cache_dir}/services") { # Later we need to open the file with +< which can't create a new file # so we'll emulate touch. verbose("Touching $opts{cache_dir}/services"); open CACHE, "> $opts{cache_dir}/services" or die "Can't touch $opts{cache_dir}/services : $!"; close CACHE; } } sub update_cache { my ($runlevels) = @_; open CACHE, "+< $opts{cache_dir}/services" or die "Can't open $opts{cache_dir}/services for rw access : $!"; # Check to see if this service & rl already exists somewhere in this file # and update the line if so. my %touched = (); while () { chomp; next unless /^([\dSs])\s+([SK])\s+(\d\d)\s+([^\s]+)$/; my ($rl, $sk, $pri, $sn) = ($1, $2, $3, $4); if (exists $runlevels->{$sn}{$rl}) { $runlevels->{$sn}{$rl} =~ /^([SK])(\d\d)$/; $touched{$sn}{$rl} = 1; my ($n_sk, $n_pri) = ($1, $2); next if $sk eq $n_sk && $pri eq $n_pri; s/^.+$/$rl $n_sk $n_pri $sn/; } } foreach my $sn (sort keys %{$runlevels}) { foreach my $rl (sort keys %{$runlevels->{$sn}}) { unless (exists $touched{$sn}{$rl}) { $runlevels->{$sn}{$rl} =~ /^([SK])(\d\d)$/; print CACHE "$rl $1 $2 $sn\n"; } } } close CACHE or die "Can't close $opts{cache_dir}/services : $!"; } #--- Misc subs ---# sub check_args { $opts{show} ||= get_default_show(); unless ($opts{show} =~ /^[S0-9]*$/) { die "$0: --show must match [S0-9]\n"; } if (length($opts{show}) > MAX_ROWS) { die "$0: can only show ". MAX_ROWS . "rows at a time\n"; } return 1; } sub current_runlevel { if (-e $runlevel_cmd) { my $rl_out = `$runlevel_cmd`; $rl_out = 1 if $rl_out =~ /unknown/; $rl_out =~ /^\S\s?([Ss\d])?$/ or die "Unknown return from $runlevel_cmd : $rl_out"; return $1; } else { return 1; } } sub split_services { # Figure out how many services can fit on the screen, then make # as many screens as needed to fit all the services. my @screens = (); my @services = (); my %o_opts = (); $o_opts{p} = 1 if $opts{order} =~ /p/; $o_opts{n} = 1 if $opts{order} =~ /n/; $o_opts{a} = 1 unless exists $o_opts{p}; if ($opts{order} =~ /([Ss\d])/) { $o_opts{rl} = $1; } else { # If the --order option didn't set a runlevel to sort by, then # use the current runlevel (from the output of /sbin/runlevel) or # sort by runlevel 1 if the runlevel command doesn't exsist on this # system. $o_opts{rl} = current_runlevel(); } # Process the opts we just set. if (exists $o_opts{a}) { if (exists $o_opts{n}) { # Include the priority num on an alpha sort foreach my $sn (sort keys %runlevels) { next unless exists $runlevels{$sn}{$o_opts{rl}}; next unless $runlevels{$sn}{$o_opts{rl}} =~ /^[SK](\d\d)$/; push @services, $1.$sn; } } else { # Standard alpha sort @services = sort keys %runlevels; } } elsif (exists $o_opts{p}) { # Sort by priority at runlevel specified or current runlevel my @tmp_order = ( [ ], [ ] ); # S is 0 and K is 1 foreach my $sn (keys %runlevels) { next unless exists $runlevels{$sn}{$o_opts{rl}}; next unless $runlevels{$sn}{$o_opts{rl}} =~ /^([SK])(\d\d)$/; if ($1 eq 'S') { push @{$tmp_order[0]}, $2.$sn } elsif ($1 eq 'K') { push @{$tmp_order[1]}, $2.$sn } } foreach (0, 1) { foreach my $ddsn (sort @{$tmp_order[$_]}) { $ddsn =~ /^(\d\d)(.+)$/; if (exists $o_opts{n}) { # Include the priority num on a pri sort push @services, $1.$2; } else { push @services, $2; } } } } { # We could be missing some services if they didn't have a link in the # runlevel we were sorting by. This happens in all circumstances except # the default of just 'a' being set. my %seen = (); foreach (@services) { next unless $_ =~ /^(\d\d)?(.+)$/; $seen{$2} = 1; } foreach (sort keys %runlevels) { unless (exists $seen{$_}) { push(@services, $_); } } } my $per_screen = max_services(); my $i = 0; do { $screens[$i] = [ splice(@services, 0, $per_screen) ]; $i++; } while @services; return @screens; } sub max_services { my $tmp_screen = $cui->add( undef, 'Window', -title => "N/A", -border => 1, -padtop => 1, -padbottom => 4, -ipad => 1, ); my $ms = $tmp_screen->canvasheight() - TOP_LABEL_HEIGHT; undef $tmp_screen; # Make sure the memory is cleaned up. return $ms; } sub get_default_show { my $show = ''; foreach (@rls) { if (-e "$etc_rc$_.d") { $show .= $_; } } return $show; } sub zero_pad { sprintf('%.2u', $_[0]); } sub verbose { print VERBOSE $_[0]."\n" if $opts{verbose}; } #--- Screen layout subs ---# sub create_main_window { create_help_window(); my $i = 0; foreach my $services (@snames_per_screen) { # First create the main window all of this page of services goes in my $id = "window_$i"; my $screen = $cui->add( $id, 'Window', -title => "SysV Runlevel Config -: stop service =/+: start service h: help q: quit", -border => 1, -padtop => 1, -padbottom => 4, -ipad => 1, ); # Can't set these globally (on $cui) or else it overrides the # keybindings on all other objects $screen->set_binding( \&move_up, KEY_UP(), ); $screen->set_binding( \&move_down, KEY_DOWN(), ); $screen->set_binding( \&move_left, KEY_LEFT(), ); $screen->set_binding( \&move_right, KEY_RIGHT(), ); create_top_label($screen); my $left_label = ''; for (my $i = 0; $i < scalar(@$services); $i++) { $left_label .= $services->[$i] . "\n"; if ($services->[$i] =~ /^\d\d(.+)$/) { # If the labels had numbers, we don't need them anymore. $services->[$i] = $1; } } my $row = TOP_LABEL_HEIGHT; $screen->add( undef, 'Label', -text => $left_label, -y => $row, -width => LABEL_WIDTH, -height => last_x() + 1, ); foreach my $sn (@$services) { if ($opts{priority}) { draw_priority_layout($screen, $sn, $row) } else { draw_simple_layout($screen, $sn, $row) } $row++; } $s[$i] = $screen; $i++; } } sub create_help_window { my $help_text = < EOF my $hw = $cui->add( 'help_window', 'Window', -title => "SysV Runlevel Config -: stop service =/+: start service h: help q: quit", -border => 1, -padtop => 1, -padbottom => 4, -ipad => 1, -userdata => 0, ); $hw->add( 'help_tv', 'TextViewer', -text => $help_text, -title => 'sysv-rc-conf help', -vscrollbar => 1, ); } sub draw_simple_layout { my ($screen, $sn, $row) = @_; for (my $i = 0, my $right_n = 12; $i <= $#show_rls; $i++, $right_n += 8) { my $on_or_off = 0; # We only want to show S\d\d services as selected. $on_or_off = 1 if exists $runlevels{$sn}{$show_rls[$i]} && $runlevels{$sn}{$show_rls[$i]} =~ /^S\d\d$/; my $box = $screen->add( zero_pad($row-2).zero_pad($i), 'Checkbox', -label => '', -checked => $on_or_off, -border => 0, -x => $right_n, -y => $row, -width => 5, #-height => 1, -userdata => { id => zero_pad($row-2).zero_pad($i), sn => $sn, changed => 0, runlevel => $show_rls[$i], }, -onchange => \&simple_box_changed, -onfocus => \&got_focus, ); $box->set_binding( \&start_service, "=", "+" ); $box->set_binding( \&stop_service, "-" ); } } sub draw_priority_layout { my ($screen, $sn, $row) = @_; for (my $i = 0, my $right_n = 11; $i <= $#show_rls; $i++, $right_n += 8) { my $text = exists $runlevels{$sn}{$show_rls[$i]} ? $runlevels{$sn}{$show_rls[$i]} : ''; my $box = $screen->add( zero_pad($row-2).zero_pad($i), 'TextEntry', -sbborder => 1, -x => $right_n, -y => $row, -width => 6, -maxlength => 3, -regexp => '/^[skSK\d]*$/', -toupper => 1, -showoverflow => 0, -text => $text, -userdata => { id => zero_pad($row-2).zero_pad($i), sn => $sn, changed => 0, runlevel => $show_rls[$i], last_good_text => $text, }, -onblur => \&pri_box_changed, -onfocus => \&got_focus, ); $box->set_binding( \&start_service, "=", "+" ); $box->set_binding( \&stop_service, "-" ); } } sub create_top_label { my ($window) = @_; my @label_rls = @show_rls; my $text = 'service ' . shift @label_rls; foreach (@label_rls) { $text .= " $_" }; $text .= "\n"; $text .= "-" x 76; $window->add( undef, 'Label', -text => $text, -y => 0, -x => 0, -height => TOP_LABEL_HEIGHT, -textalignment => 'left', ); } sub create_bottom_box { my $cmd_text = ''; if ($opts{priority}) { $cmd_text = "Editing: Backspace: bs ^d: delete ^b: backward ^f: forward"; } else { $cmd_text = " space: toggle service on / off ", } my $exp_window = $cui->add( 'exp_window', 'Window', -border => 1, -y => -1, -height => BOTTOM_WIN_HEIGHT, -ipadleft => 1, -ipadright => 1, ); $exp_window->add(undef, 'Label', -y => 0, -width => -1, -height => BOTTOM_LAB_HEIGHT, -text => "Use the arrow keys or mouse to move around. ^n: next pg ^p: prev pg\n$cmd_text", ); } sub toggle_help { my $hw = $cui->getobj('help_window'); my $hw_data = $hw->userdata(); if ($hw_data == 0) { $hw->userdata($cui->getfocusobj); $hw->focus(); } else { # The help window is up, so turn it off by focusing the last # object that was focused on before we pulled up the help window $hw_data->focus(); $hw->userdata(0); } } #--- Movement subs ---# sub next_page { $current_screen++; $current_screen = 0 if $current_screen > last_screen(); verbose("Going to screen $current_screen"); _move_focus(00, $box_pos{y}); } sub prev_page { $current_screen--; $current_screen = last_screen() if $current_screen < 0; verbose("Going to screen $current_screen"); $box_pos{x} = last_x(); _move_focus($box_pos{x}, $box_pos{y}); } sub got_focus { my $widget = shift; # Is there a better way to figure out my own id besides putting it # in userdata on creation and fetching it? my $userdata = $widget->userdata(); my $id = $userdata->{id}; $id =~ /^(\d\d)(\d\d)$/; $box_pos{x} = $1; $box_pos{y} = $2; } sub move_left { return if $box_pos{y} eq '00'; _move_focus($box_pos{x}, $box_pos{y} - 1); } sub move_right { return if $box_pos{y} == scalar(@show_rls)-1; _move_focus($box_pos{x}, $box_pos{y} + 1); } sub move_up { #return if $box_pos{x} eq '00'; return prev_page() if $box_pos{x} eq '00'; _move_focus($box_pos{x} - 1, $box_pos{y}); } sub move_down { # Index starts at 00, so we need one less then the max services that # are on the screen. return next_page() if $box_pos{x} == last_x(); _move_focus($box_pos{x} + 1, $box_pos{y}); } sub _move_focus { $box_pos{x} = $_[0]; $box_pos{y} = $_[1]; my $box = $s[$current_screen]->getobj(zero_pad($_[0]).zero_pad($_[1])); $box->focus(); } sub last_x { return scalar(@{$snames_per_screen[$current_screen]})-1; } sub first_screen { return 0 } sub last_screen { return scalar(@s)-1 } =pod =head1 NAME B - Run-level configuration for SysV like init script links =head1 SYNOPSIS B [ I ] B [ --level I ] I EIE =head1 DESCRIPTION B gives an easy to use interface for managing C symlinks. The interface comes in two different flavors, one that simply allows turning services on or off and another that allows for more fine tuned management of the symlinks. It's a replacement for programs like B or B. B can also be used at the command line when the desired changes to the symlinks are already known. The syntax is borrowed from B. =head1 GENERAL OPTIONS =over =item B<-c> DIRECTORY, B<--cache=>DIRECTORY The directory where the priority numbers, old runlevel configuration, etc. should be stored. This defaults to C. See the FILES section below. =item B<-r> DIRECTORY, B<--root=>DIRECTORY The root directory to use. This defaults to C. This comes in handy if the root file system is mounted somewhere else, such as when using a rescue disk. =item B<-v> FILE, B<--verbose=>FILE Print verbose information to C =item B<-V>, B<--Version> Print version information to STDOUT and exit =back =head1 GUI RELATED OPTIONS =over =item B<-o> [ see description ], B<--order=>[ see description ] Allows various sorting orders and ways to display the rows. The argument can be made up of any of the following: =over =item B Sort the rows Blphabetically. This is the default if the B<-o> option isn't specified. =item B Show the priority numbers along with the name of the service. =item B

Sorts by the B

riority numbers. =item B I can be any runlevel, 0-9 or S. This controls which runlevel the priority numbers are sorted at. It only makes sense to use this in conjuntion with B

. If omitted the priority numbers are sorted by the current runlevel the system is in. =back =item B<-p>, B<--priority> Alternate layout. Instead of just showing a checkbox, the priority of the service and the S or K are allowed to be edited. This is for more fine tuned control then the default layout allows. =item B<-s> I, B<--show=>I Which runlevels to show. This defaults to up to 8 of the runlevels available on the system. Usually this means it will show 1, 2, 3, 4, 5, 0, 6, and S. The syntax calls for the runlevels to be allruntogether. For instance, to show runlevels 3, 4, and 5 the syntax would be C<--show=345>. Also see B<--order>. =back =head1 CLI RELATED OPTIONS =over =item B<--level> I The runlevels this operation will affect. I can be any number from 0-9 or S. For example, B<--level 135> will affect runlevels 1, 3, and 5. If B<--level> is not set, the default is to affect runlevels 2, 3, 4, and 5. This option is only used for the command line interface, see the section below labled USING THE CLI for more information. =item B<--list> [I] This option will list all of the services and if they are stopped or started when entering each runlevel. If I is specified, only the information for that service is displayed. =back =head1 USING THE GUI =head2 Using the Default layout The default (simple) layout shows in a grid fashion all of the services that are in C and which runlevels they are turned on at. For example, where the C row and C<3> column intersect, if there is a checkbox there that means the service will be turned on when entering runlevel 3. If there is no checkbox it can mean that either there are no links to the service in that specific runlevel, or that the service is turned off when entering that runlevel. If more configuration detail is needed, see the next paragraph and the B<--priority> option. =head2 Using the Priority layout The priority (advanced) layout also uses a grid fashion, but instead of checkboxes there are text boxes that can have a few different values. If the text box is blank, that means there isn't a symlink in that runlevel for that service. This means that when changing into that runlevel that the service will not be started or stopped, which is significant. If the text box starts with the letter K that means that the service will be stopped when entering that runlevel. If the text box starts with the letter S that means the service will be started when entering that runlevel. The two digits following is the order in which the services are started. That means that C would start before C. For more information see your system documentation. =head2 Controls To move around use the arrow keys, or if the terminal support it, the mouse. Typically there is more then one page of services (unless the terminal screen is large), to move between the pages use CTRL-n or CTRL-p, or simply arrow key down or up at the bottom or top of the screen, respectively. The bottom of the screen also shows these movement commands for quick reference. To restore the symlinks back to their original state before the B was run, press the B key. The B key will display a quick reference help screen. =head2 Default layout When using the default layout use the space bar to toggle the service on / off. =head2 Priority layout The priority layout uses the default movement keys. In order to edit the fields you can use CTRL-d to delete the character in front of the cursor or backspace to backspace. Use CTRL-b or CTRL-f to move the cursor backwards or forwards within the field. Note that only S, K, or any digit is allowed to be entered into the field. =head2 Starting / Stopping Services To start a service, press the C<+> or C<=> key. To stop a service, press the C<-> key. This will call C or C. =head1 USING THE CLI If the desired modifications to the symlinks are known and only one quick change is needed, then you can use a CLI interface to B. Examples: # sysv-rc-conf --level 35 ssh off # sysv-rc-conf atd on The first example will turn ssh off on levels 3 and 5. The second example turns atd on for runlevels 2, 3, 4, and 5. =head1 FILES B Feel free to skip this section B stores a cache of all the symlink information from C in C (See the --cache option to change the location of this file). It uses this cache to make an intelligent decision on what priority number to give the K or S link when they are changed in the simple layout. This cache is updated/created everytime the program is launched. The format of the file is as follows: RUNLEVEL S|K PRIORITY SERVICE Here's a few examples: 2 K 74 ntpd 2 K 50 xinetd 3 S 08 iptables 3 S 80 sendmail B will first see if it can get an exact match from the cache. For example, if the symlink for C in runlevel 3 is S89cron and you uncheck it, B will first see if there is an entry in the cache that looks like C<3 K nn cron>, if so it will use nn for the priority number. If there wasn't a match, B will then see if there is another S or K (whichever you're switching to, so in this example, K) entry on a different runlevel - so an entry like C, where i is any runlevel. If found, the link will use nn. If there still wasn't a match, B will look for the opposite of S or K in any run level, and use 100 - that priority. So in our example, C. If nn is 20, then it will use 80 (100 - 20), since that is typically the way that the priority numbers are used. If there still isn't a match, the default priority of 20 for S links is used, and the default priority of 80 for K links is used. =head1 COMPATIBILITY B should work on any Unix like system that manages services when changing runlevels by using symlinks in C. Refer to your system documentation to see if that's the case (usually there's a C). =head1 CAVEATS B only manages the symlinks in the C directories. It's possible that pacakages may have other ways of being disabled or enabled. Because Curses takes over the screen sometimes error messages won't be seen on the terminal. If you run across any weird problems try redirecting STDERR to a file when you execute the program. For example: # sysv-rc-conf 2E err.out =head1 REPORTING BUGS Report bugs to Joe Oppegaard Ejoe@pidone.orgE =head1 SEE ALSO B, B, B, C www: http://sysv-rc-conf.sourceforge.net ftp: ftp://ftp.pidone.org/sysv-rc-conf =head1 AUTHOR Joe Oppegaard Ejoe@pidone.orgE