123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227 |
- #!/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 <joe@pidone.org>
- #
- 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;
- sysv-rc-conf command line usage:
-
- sysv-rc-conf --list [service name]
- sysv-rc-conf [--level <runlevels>] <service name> <on|off>
- 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 = <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 (<CACHE>) {
- 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;
- Quick key reference:
- Arrow keys: Move around
- CTRL-n: Next Page
- CTRL-p: Previous Page
- r: Restore all symlinks back to original state
- -: Stop service
- = or +: Start service
- h: Toggle help on / off
- q: Quit
- Checkbox Layout:
- Space: Toggle service on / off
- Priority Layout:
- Backspace: Delete text behind cursor
- CTRL-d: Delete text in front of cursor
- CTRL-f: Move cursor forward through text
- CTRL-b: Move cursor backwards through text
- See the man page for information on how the system uses the init
- script links, how to get new version of the program, how to submit
- bug reports, etc.
- sysv-rc-conf is released under the GNU GPL.
- Version: $VERSION
- (c) 2004 Joe Oppegaard <joe\@pidone.org>
- 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<sysv-rc-conf> - Run-level configuration for SysV like init script links
- =head1 SYNOPSIS
- B<sysv-rc-conf> [ I<options> ]
- B<sysv-rc-conf> [ --level I<levels> ] I<service> E<lt>I<on|off>E<gt>
- =head1 DESCRIPTION
- B<sysv-rc-conf> gives an easy to use interface for managing
- C</etc/rc{runlevel}.d/> 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<ntsysv(8)> or B<rcconf(8)>.
- B<sysv-rc-conf> can also be used at the command line when the desired changes
- to the symlinks are already known. The syntax is borrowed from B<chkconfig(8)>.
- =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</var/lib/sysv-rc-conf>. 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<FILE>
- =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<a>
- Sort the rows B<a>lphabetically. This is the default if the B<-o> option isn't
- specified.
- =item B<n>
- Show the priority numbers along with the name of the service.
- =item B<p>
- Sorts by the B<p>riority numbers.
- =item B<level>
- I<level> 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<p>. 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<levels>, B<--show=>I<levels>
- 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<levels>
- The runlevels this operation will affect. I<levels> 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<name>]
- This option will list all of the services and if they are stopped or started
- when entering each runlevel. If I<name> 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<init.d> and which runlevels they are turned on at. For example, where
- the C<ssh> 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<S08iptables> would
- start before C<S20ssh>. 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<sysv-rc-conf> was run,
- press the B<r> key. The B<h> 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</etc/init.d/service start> or C</etc/init.d/service stop>.
- =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<sysv-rc-conf>.
- 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<Note:> Feel free to skip this section
- B<sysv-rc-conf> stores a cache of all the symlink information from
- C</etc/rc{runlevel}.d/> in C</var/lib/sysv-rc-conf/services> (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<sysv-rc-conf> will first see if it can get an exact match from the cache.
- For example, if the symlink for C<cron> in runlevel 3 is S89cron and you
- uncheck it, B<sysv-rc-conf> 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<sysv-rc-conf> 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<i K nn cron>, where i is any runlevel. If found,
- the link will use nn.
- If there still wasn't a match, B<sysv-rc-conf> will look for the opposite of S
- or K in any run level, and use 100 - that priority. So in our example,
- C<i S nn cron>. 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<sysv-rc-conf> should work on any Unix like system that manages services
- when changing runlevels by using symlinks in C</etc/rc{runlevel}.d/>. Refer
- to your system documentation to see if that's the case (usually there's a
- C</etc/init.d/README>).
- =head1 CAVEATS
- B<sysv-rc-conf> only manages the symlinks in the C<rc{runlevel}.d>
- 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<gt> err.out
- =head1 REPORTING BUGS
- Report bugs to Joe Oppegaard E<lt>joe@pidone.orgE<gt>
- =head1 SEE ALSO
- B<init(8)>, B<runlevel(8)>, B<chkconfig(8)>, C</etc/init.d/README>
- www: http://sysv-rc-conf.sourceforge.net
- ftp: ftp://ftp.pidone.org/sysv-rc-conf
- =head1 AUTHOR
- Joe Oppegaard E<lt>joe@pidone.orgE<gt>
|