sysv-rc-conf.pl 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227
  1. #!/usr/bin/perl -w
  2. # This program is distributed under the terms of the GNU General Public License
  3. #
  4. # This file is part of sysv-rc-conf.
  5. #
  6. # sysv-rc-conf is free software; you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation; either version 2 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # sysv-rc-conf is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with sysv-rc-conf; if not, write to the Free Software
  18. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  19. #
  20. # Copyright 2004 Joe Oppegaard <joe@pidone.org>
  21. #
  22. use strict;
  23. use Getopt::Long qw(:config no_ignore_case);
  24. use Curses;
  25. use Curses::UI;
  26. use constant {
  27. BOTTOM_LAB_HEIGHT => 2,
  28. BOTTOM_WIN_HEIGHT => 4,
  29. DEFAULT_K_PRI => 80,
  30. DEFAULT_S_PRI => 20,
  31. LABEL_WIDTH => 10,
  32. LIST_SN_LENGTH => 12,
  33. LIST_SN_PAD => 1,
  34. MAX_ROWS => 8,
  35. TOP_LABEL_HEIGHT => 2,
  36. };
  37. my $VERSION = "0.98";
  38. my %opts = (
  39. cache_dir => "/var/lib/sysv-rc-conf",
  40. order => 'a',
  41. priority => '',
  42. root => '/',
  43. show => '',
  44. verbose => '',
  45. chkcfg_levels => '2345', # default runlevels to affect if not specified
  46. chkcfg_list => undef,
  47. chkcfg_sn => '',
  48. chkcfg_state => '',
  49. );
  50. GetOptions("cache=s" => \$opts{cache_dir},
  51. "level=s" => \$opts{chkcfg_levels},
  52. "list:s" => \$opts{chkcfg_list},
  53. "order=s" => \$opts{order},
  54. "priority" => \$opts{priority},
  55. "root=s" => \$opts{root},
  56. "show=s" => \$opts{show},
  57. "verbose=s" => \$opts{verbose},
  58. "Version" => sub { print STDERR "$0 $VERSION\n"; exit; },
  59. );
  60. my $runlevel_cmd = '/sbin/runlevel';
  61. $opts{verbose} ||= "/dev/null";
  62. open VERBOSE, "> $opts{verbose}" or die "Can't open $opts{verbose} : $!";
  63. my $etc_rc = $opts{root} . "/etc/rc.d/rc";
  64. my $initd = $opts{root} . "/etc/init.d";
  65. my @rls = qw/ 1 2 3 4 5 7 8 9 0 6 S /;
  66. check_args();
  67. setup_cache_env();
  68. my @show_rls = split //, $opts{show};
  69. # Page index
  70. my $current_screen = 0;
  71. # Page screens
  72. my @s = ();
  73. # All the runlevel information
  74. my %runlevels = runlevel_status();
  75. list_output(%runlevels) if defined $opts{chkcfg_list};
  76. chkconfig_emulation();
  77. my $cui = new Curses::UI( -clear_on_exit => 0,
  78. -color_support => 1,
  79. -default_colors => 1,
  80. ) or die "Can't create base Curses::UI object";
  81. create_bottom_box();
  82. # Get the service names for each screen
  83. my @snames_per_screen = split_services();
  84. create_main_window();
  85. my %box_pos = (x => '00', y => '00');
  86. $cui->set_binding( \&toggle_help, "h" );
  87. $cui->set_binding( \&revert_changes, "r") ;
  88. $cui->set_binding( sub { exit }, "q" );
  89. $cui->set_binding( \&next_page, "\cN" );
  90. $cui->set_binding( \&prev_page, "\cP" );
  91. $s[$current_screen]->focus();
  92. $cui->mainloop();
  93. #--- rc access and modification subs ---#
  94. sub update_link
  95. {
  96. my ($sn, $rl, $sk, $pri) = @_;
  97. if (defined $sn && defined $rl && defined $sk && defined $pri) {
  98. if (-e "$etc_rc$rl.d/$sk$pri$sn") {
  99. # The symlink we are trying to make already exists
  100. return 'exists';
  101. }
  102. }
  103. opendir (RL, "$etc_rc$rl.d") or die "$0: opendir $etc_rc$rl.d : $!";
  104. foreach (grep { /[SK]\d\d$sn/i } readdir(RL)) {
  105. verbose("rm $etc_rc$rl.d/$_");
  106. unlink "$etc_rc$rl.d/$_"
  107. or die "Can't unlink $etc_rc$rl.d/$_ : $!";
  108. }
  109. # If in priority mode and are completely deleting the link, $sk will
  110. # be empty.
  111. return 1 if $sk eq '';
  112. $pri = get_pri_cache($sn, $rl, $sk) unless $pri;
  113. unless ($pri =~ /^\d\d$/) { die "Priority isn't two numbers: $pri" }
  114. unless ($sk =~ /^[SK]$/) { die "You have to use S or K to start a link" }
  115. verbose("symlink $initd/$sn $etc_rc$rl.d/$sk$pri$sn");
  116. # unlike ln relative symlinks are relative to the target file, not the cwd
  117. symlink "../init.d/$sn", "$etc_rc$rl.d/$sk$pri$sn"
  118. or die "Can't symlink $etc_rc$rl.d/$sk$pri$sn to ../init.d/$sn : $!\n";
  119. }
  120. sub usage
  121. {
  122. print STDERR <<USAGE;
  123. sysv-rc-conf command line usage:
  124. sysv-rc-conf --list [service name]
  125. sysv-rc-conf [--level <runlevels>] <service name> <on|off>
  126. USAGE
  127. }
  128. sub chkconfig_emulation
  129. {
  130. $opts{chkcfg_sn} = $ARGV[0] if defined $ARGV[0];
  131. $opts{chkcfg_state} = $ARGV[1] if defined $ARGV[1];
  132. if ($opts{chkcfg_sn} && not $opts{chkcfg_state}) {
  133. # Check to see if service is configured to run in current rl
  134. # exit true if it is, false if not.
  135. # See chkconfig(8)
  136. my $current_rl = current_runlevel();
  137. if (exists $runlevels{$opts{chkcfg_sn}}{$current_rl}) {
  138. if ($runlevels{$opts{chkcfg_sn}}{$current_rl} =~ /^S/) {
  139. # Service is configured to start, exit true
  140. exit 0;
  141. }
  142. }
  143. exit 1;
  144. }
  145. if ($opts{chkcfg_sn} && $opts{chkcfg_state}) {
  146. my $sk = '';
  147. if ($opts{chkcfg_state} =~ /^on$/i) {
  148. $sk = 'S';
  149. }
  150. elsif ($opts{chkcfg_state} =~ /^off$/i) {
  151. $sk = 'K';
  152. }
  153. else {
  154. usage();
  155. }
  156. foreach (split //, $opts{chkcfg_levels}) {
  157. update_link($opts{chkcfg_sn}, $_, $sk, undef);
  158. }
  159. exit 0;
  160. }
  161. # Program isn't being called like chkconfig, so return to normal
  162. # operation.
  163. return 1;
  164. }
  165. sub list_output
  166. {
  167. my (%runlevels) = @_;
  168. # There was an argument to --list
  169. my $opt_sn = $opts{chkcfg_list};
  170. foreach my $sn (sort keys %runlevels) {
  171. my $output = substr $sn, 0, LIST_SN_LENGTH;
  172. $output .= " " until length $output >= LIST_SN_LENGTH + LIST_SN_PAD;
  173. foreach my $rl (sort keys %{$runlevels{$sn}}) {
  174. $output .= "$rl:";
  175. if ($runlevels{$sn}{$rl} =~ /^[Ss]/) {
  176. $output .= "on";
  177. }
  178. else {
  179. $output .= "off";
  180. }
  181. $output .= "\t";
  182. }
  183. chop($output);
  184. $output .= "\n";
  185. if ($opt_sn) {
  186. print $output if $sn eq $opt_sn;
  187. }
  188. else {
  189. print $output;
  190. }
  191. }
  192. exit 0;
  193. }
  194. sub revert_changes
  195. {
  196. # Lookup table
  197. my %cache = ();
  198. foreach my $scr (@s) {
  199. for (my $i = 0; $i < max_services() ; $i++) {
  200. for (my $j = 0; $j <= $#show_rls; $j++) {
  201. my $obj = $scr->getobj(zero_pad($i).zero_pad($j));
  202. my $ud = $obj->userdata();
  203. $cache{ $ud->{sn} }{ $ud->{runlevel} } = $obj;
  204. }
  205. }
  206. }
  207. foreach my $sn (keys %runlevels) {
  208. foreach my $rl (keys %{$runlevels{$sn}}) {
  209. $runlevels{$sn}{$rl} =~ /^([SK])(\d\d)$/;
  210. my ($sk, $pri) = ($1, $2);
  211. next if update_link($sn, $rl, $sk, $pri) eq 'exists';
  212. if (exists($cache{$sn}{$rl})) {
  213. my $box = $cache{$sn}{$rl};
  214. if ($opts{priority}) {
  215. # Reset the text
  216. $box->text($sk.$pri);
  217. $box->draw(1);
  218. }
  219. else {
  220. # Simple layout, just toggle the box.
  221. $box->toggle();
  222. $box->draw(1);
  223. }
  224. }
  225. }
  226. }
  227. $cui->dialog("Symlinks restored to original state!");
  228. }
  229. sub start_service
  230. {
  231. my ($widget) = @_;
  232. my $ud = $widget->userdata();
  233. st_service($ud->{sn}, 'start');
  234. }
  235. sub stop_service
  236. {
  237. my ($widget) = @_;
  238. my $ud = $widget->userdata();
  239. st_service($ud->{sn}, 'stop');
  240. }
  241. sub st_service
  242. {
  243. my ($sn, $st) = @_;
  244. my $output = `$initd/$sn $st`;
  245. verbose("$initd/$sn $st : $output");
  246. $cui->dialog("Results of $initd/$sn $st :\n" . $output);
  247. }
  248. sub get_pri_cache
  249. {
  250. my ($sn, $rl, $sk) = @_;
  251. # See if we can get an exact match from the cache, if not try to match
  252. # the S or K except in a different run level, if there still is not a match
  253. # get the opposite of S or K on another runlevel, if still no match return
  254. # the default.
  255. open CACHE, "< $opts{cache_dir}/services"
  256. or die "Can't open $opts{cache_dir}/services : $!";
  257. chomp (my @cache = <CACHE>);
  258. close CACHE;
  259. # Try an exact cache match
  260. foreach (@cache) {
  261. # $arg_rl $arg_sk $arg_pri $arg_sn
  262. next unless /^$rl\s+$sk\s+(\d\d)\s+$sn$/;
  263. verbose("Got exact cache match for priority: $_");
  264. return $1;
  265. }
  266. # Try an $sk match, except on any runlevel
  267. foreach (@cache) {
  268. next unless /^[\dsS]\s+$sk\s+(\d\d)\s+$sn$/;
  269. verbose("Got differing runlevel cache for priority: $_");
  270. return $1;
  271. }
  272. # Ok, try to match on any runlevel with either S or K
  273. foreach (@cache) {
  274. next unless /^[\dsS]\s+([SK])\s+(\d\d)\s+$sn$/;
  275. verbose("Returning difference of 100 and $2: $_");
  276. # Sequence numbers are usually defined as stop = 100 - start
  277. # So that means that start = 100 - stop
  278. # Above we would have returned if $sk eq $1 so we know that $1 is
  279. # the opposite of $sk. So return 100 - $2.
  280. return zero_pad(100 - $2);
  281. }
  282. verbose("No cache found, returning default");
  283. return DEFAULT_S_PRI if $sk eq 'S';
  284. return DEFAULT_K_PRI if $sk eq 'K';
  285. }
  286. sub pri_box_changed
  287. {
  288. my ($widget) = @_;
  289. my $ud = $widget->userdata();
  290. my $new_link = $widget->get();
  291. if ($new_link eq $ud->{last_good_text}) {
  292. # Text didn't actually change, just moved out of the box
  293. return 1;
  294. }
  295. if ($new_link =~ /^([KS])(\d\d)$/ or $new_link =~ /^$/) {
  296. my ($sk, $pri) = ('', '');
  297. if (defined $1 and defined $2) {
  298. $sk = $1;
  299. $pri = $2;
  300. }
  301. update_link($ud->{sn}, $ud->{runlevel}, $sk, $pri);
  302. $ud->{last_good_text} = $new_link;
  303. }
  304. else {
  305. $cui->error("Incorrect format: $new_link\n" .
  306. "The correct format is a K or S followed by two digits!\n" .
  307. "Returning field back to original state."
  308. );
  309. # Set the text in the box back to whatever the last good text was.
  310. $widget->{-text} = $ud->{last_good_text};
  311. }
  312. }
  313. sub simple_box_changed
  314. {
  315. my ($box) = @_;
  316. my $userdata = $box->userdata();
  317. $userdata->{changed}++;
  318. if ($box->get()) {
  319. update_link($userdata->{sn}, $userdata->{runlevel}, 'S', undef)
  320. }
  321. else {
  322. update_link($userdata->{sn}, $userdata->{runlevel}, 'K', undef)
  323. }
  324. }
  325. sub runlevel_status
  326. {
  327. my %runlevels = ();
  328. opendir (INITD, $initd) or die "$0: opendir $initd : $!";
  329. while ( defined(my $service = readdir INITD) ) {
  330. next if $service =~ /\.sh$/; # see the debian policy manual
  331. next if $service =~ /^\.+$/; # ignore . and ..
  332. next unless -x "$initd/$service"; # ignore stuff like README
  333. $runlevels{$service} = { };
  334. }
  335. closedir INITD or die "$0: closedir $initd : $!";
  336. # While 7-9 usually aren't used, init supports it.
  337. foreach my $rl (@rls) {
  338. unless (opendir(DIR, "$etc_rc$rl.d")) {
  339. next if $rl =~ /^[789S]$/;
  340. die "$0: opendir $etc_rc$rl.d : $!";
  341. }
  342. while ( defined(my $file = readdir DIR) ) {
  343. $file = "$etc_rc$rl.d/$file"; # Add the pathname to the file
  344. next unless -l $file;
  345. next if $file =~ /\.sh$/;
  346. next unless $file =~ /([SK])(\d\d)(.+)$/;
  347. my ($sk, $pri, $sn) = ($1, $2, $3);
  348. $runlevels{$sn}{$rl} = $sk.$pri;
  349. }
  350. closedir DIR or die "$0: closedir $etc_rc$rl.d : $!";
  351. }
  352. update_cache(\%runlevels);
  353. return %runlevels;
  354. }
  355. sub setup_cache_env
  356. {
  357. unless (-e $opts{cache_dir}) {
  358. verbose("Creating non-existant cache directory: $opts{cache_dir}");
  359. mkdir $opts{cache_dir} or die "Can't create $opts{cache_dir} : $!";
  360. }
  361. unless (-e "$opts{cache_dir}/services") {
  362. # Later we need to open the file with +< which can't create a new file
  363. # so we'll emulate touch.
  364. verbose("Touching $opts{cache_dir}/services");
  365. open CACHE, "> $opts{cache_dir}/services"
  366. or die "Can't touch $opts{cache_dir}/services : $!";
  367. close CACHE;
  368. }
  369. }
  370. sub update_cache
  371. {
  372. my ($runlevels) = @_;
  373. open CACHE, "+< $opts{cache_dir}/services"
  374. or die "Can't open $opts{cache_dir}/services for rw access : $!";
  375. # Check to see if this service & rl already exists somewhere in this file
  376. # and update the line if so.
  377. my %touched = ();
  378. while (<CACHE>) {
  379. chomp;
  380. next unless /^([\dSs])\s+([SK])\s+(\d\d)\s+([^\s]+)$/;
  381. my ($rl, $sk, $pri, $sn) = ($1, $2, $3, $4);
  382. if (exists $runlevels->{$sn}{$rl}) {
  383. $runlevels->{$sn}{$rl} =~ /^([SK])(\d\d)$/;
  384. $touched{$sn}{$rl} = 1;
  385. my ($n_sk, $n_pri) = ($1, $2);
  386. next if $sk eq $n_sk && $pri eq $n_pri;
  387. s/^.+$/$rl $n_sk $n_pri $sn/;
  388. }
  389. }
  390. foreach my $sn (sort keys %{$runlevels}) {
  391. foreach my $rl (sort keys %{$runlevels->{$sn}}) {
  392. unless (exists $touched{$sn}{$rl}) {
  393. $runlevels->{$sn}{$rl} =~ /^([SK])(\d\d)$/;
  394. print CACHE "$rl $1 $2 $sn\n";
  395. }
  396. }
  397. }
  398. close CACHE or die "Can't close $opts{cache_dir}/services : $!";
  399. }
  400. #--- Misc subs ---#
  401. sub check_args
  402. {
  403. $opts{show} ||= get_default_show();
  404. unless ($opts{show} =~ /^[S0-9]*$/) {
  405. die "$0: --show must match [S0-9]\n";
  406. }
  407. if (length($opts{show}) > MAX_ROWS) {
  408. die "$0: can only show ". MAX_ROWS . "rows at a time\n";
  409. }
  410. return 1;
  411. }
  412. sub current_runlevel
  413. {
  414. if (-e $runlevel_cmd) {
  415. my $rl_out = `$runlevel_cmd`;
  416. $rl_out = 1 if $rl_out =~ /unknown/;
  417. $rl_out =~ /^\S\s?([Ss\d])?$/ or
  418. die "Unknown return from $runlevel_cmd : $rl_out";
  419. return $1;
  420. }
  421. else {
  422. return 1;
  423. }
  424. }
  425. sub split_services
  426. {
  427. # Figure out how many services can fit on the screen, then make
  428. # as many screens as needed to fit all the services.
  429. my @screens = ();
  430. my @services = ();
  431. my %o_opts = ();
  432. $o_opts{p} = 1 if $opts{order} =~ /p/;
  433. $o_opts{n} = 1 if $opts{order} =~ /n/;
  434. $o_opts{a} = 1 unless exists $o_opts{p};
  435. if ($opts{order} =~ /([Ss\d])/) {
  436. $o_opts{rl} = $1;
  437. }
  438. else {
  439. # If the --order option didn't set a runlevel to sort by, then
  440. # use the current runlevel (from the output of /sbin/runlevel) or
  441. # sort by runlevel 1 if the runlevel command doesn't exsist on this
  442. # system.
  443. $o_opts{rl} = current_runlevel();
  444. }
  445. # Process the opts we just set.
  446. if (exists $o_opts{a}) {
  447. if (exists $o_opts{n}) {
  448. # Include the priority num on an alpha sort
  449. foreach my $sn (sort keys %runlevels) {
  450. next unless exists $runlevels{$sn}{$o_opts{rl}};
  451. next unless $runlevels{$sn}{$o_opts{rl}} =~ /^[SK](\d\d)$/;
  452. push @services, $1.$sn;
  453. }
  454. }
  455. else {
  456. # Standard alpha sort
  457. @services = sort keys %runlevels;
  458. }
  459. }
  460. elsif (exists $o_opts{p}) {
  461. # Sort by priority at runlevel specified or current runlevel
  462. my @tmp_order = ( [ ], [ ] ); # S is 0 and K is 1
  463. foreach my $sn (keys %runlevels) {
  464. next unless exists $runlevels{$sn}{$o_opts{rl}};
  465. next unless $runlevels{$sn}{$o_opts{rl}} =~ /^([SK])(\d\d)$/;
  466. if ($1 eq 'S') { push @{$tmp_order[0]}, $2.$sn }
  467. elsif ($1 eq 'K') { push @{$tmp_order[1]}, $2.$sn }
  468. }
  469. foreach (0, 1) {
  470. foreach my $ddsn (sort @{$tmp_order[$_]}) {
  471. $ddsn =~ /^(\d\d)(.+)$/;
  472. if (exists $o_opts{n}) {
  473. # Include the priority num on a pri sort
  474. push @services, $1.$2;
  475. }
  476. else {
  477. push @services, $2;
  478. }
  479. }
  480. }
  481. }
  482. {
  483. # We could be missing some services if they didn't have a link in the
  484. # runlevel we were sorting by. This happens in all circumstances except
  485. # the default of just 'a' being set.
  486. my %seen = ();
  487. foreach (@services) {
  488. next unless $_ =~ /^(\d\d)?(.+)$/;
  489. $seen{$2} = 1;
  490. }
  491. foreach (sort keys %runlevels) {
  492. unless (exists $seen{$_}) {
  493. push(@services, $_);
  494. }
  495. }
  496. }
  497. my $per_screen = max_services();
  498. my $i = 0;
  499. do {
  500. $screens[$i] = [ splice(@services, 0, $per_screen) ];
  501. $i++;
  502. } while @services;
  503. return @screens;
  504. }
  505. sub max_services
  506. {
  507. my $tmp_screen = $cui->add(
  508. undef, 'Window',
  509. -title => "N/A",
  510. -border => 1,
  511. -padtop => 1,
  512. -padbottom => 4,
  513. -ipad => 1,
  514. );
  515. my $ms = $tmp_screen->canvasheight() - TOP_LABEL_HEIGHT;
  516. undef $tmp_screen; # Make sure the memory is cleaned up.
  517. return $ms;
  518. }
  519. sub get_default_show
  520. {
  521. my $show = '';
  522. foreach (@rls) {
  523. if (-e "$etc_rc$_.d") {
  524. $show .= $_;
  525. }
  526. }
  527. return $show;
  528. }
  529. sub zero_pad { sprintf('%.2u', $_[0]); }
  530. sub verbose { print VERBOSE $_[0]."\n" if $opts{verbose}; }
  531. #--- Screen layout subs ---#
  532. sub create_main_window
  533. {
  534. create_help_window();
  535. my $i = 0;
  536. foreach my $services (@snames_per_screen) {
  537. # First create the main window all of this page of services goes in
  538. my $id = "window_$i";
  539. my $screen = $cui->add(
  540. $id, 'Window',
  541. -title =>
  542. "SysV Runlevel Config -: stop service =/+: start service h: help q: quit",
  543. -border => 1,
  544. -padtop => 1,
  545. -padbottom => 4,
  546. -ipad => 1,
  547. );
  548. # Can't set these globally (on $cui) or else it overrides the
  549. # keybindings on all other objects
  550. $screen->set_binding( \&move_up, KEY_UP(), );
  551. $screen->set_binding( \&move_down, KEY_DOWN(), );
  552. $screen->set_binding( \&move_left, KEY_LEFT(), );
  553. $screen->set_binding( \&move_right, KEY_RIGHT(), );
  554. create_top_label($screen);
  555. my $left_label = '';
  556. for (my $i = 0; $i < scalar(@$services); $i++) {
  557. $left_label .= $services->[$i] . "\n";
  558. if ($services->[$i] =~ /^\d\d(.+)$/) {
  559. # If the labels had numbers, we don't need them anymore.
  560. $services->[$i] = $1;
  561. }
  562. }
  563. my $row = TOP_LABEL_HEIGHT;
  564. $screen->add(
  565. undef, 'Label',
  566. -text => $left_label,
  567. -y => $row,
  568. -width => LABEL_WIDTH,
  569. -height => last_x() + 1,
  570. );
  571. foreach my $sn (@$services) {
  572. if ($opts{priority}) { draw_priority_layout($screen, $sn, $row) }
  573. else { draw_simple_layout($screen, $sn, $row) }
  574. $row++;
  575. }
  576. $s[$i] = $screen;
  577. $i++;
  578. }
  579. }
  580. sub create_help_window
  581. {
  582. my $help_text = <<EOF;
  583. Quick key reference:
  584. Arrow keys: Move around
  585. CTRL-n: Next Page
  586. CTRL-p: Previous Page
  587. r: Restore all symlinks back to original state
  588. -: Stop service
  589. = or +: Start service
  590. h: Toggle help on / off
  591. q: Quit
  592. Checkbox Layout:
  593. Space: Toggle service on / off
  594. Priority Layout:
  595. Backspace: Delete text behind cursor
  596. CTRL-d: Delete text in front of cursor
  597. CTRL-f: Move cursor forward through text
  598. CTRL-b: Move cursor backwards through text
  599. See the man page for information on how the system uses the init
  600. script links, how to get new version of the program, how to submit
  601. bug reports, etc.
  602. sysv-rc-conf is released under the GNU GPL.
  603. Version: $VERSION
  604. (c) 2004 Joe Oppegaard <joe\@pidone.org>
  605. EOF
  606. my $hw = $cui->add(
  607. 'help_window', 'Window',
  608. -title =>
  609. "SysV Runlevel Config -: stop service =/+: start service h: help q: quit",
  610. -border => 1,
  611. -padtop => 1,
  612. -padbottom => 4,
  613. -ipad => 1,
  614. -userdata => 0,
  615. );
  616. $hw->add(
  617. 'help_tv', 'TextViewer',
  618. -text => $help_text,
  619. -title => 'sysv-rc-conf help',
  620. -vscrollbar => 1,
  621. );
  622. }
  623. sub draw_simple_layout
  624. {
  625. my ($screen, $sn, $row) = @_;
  626. for (my $i = 0, my $right_n = 12; $i <= $#show_rls; $i++, $right_n += 8) {
  627. my $on_or_off = 0;
  628. # We only want to show S\d\d services as selected.
  629. $on_or_off = 1 if exists $runlevels{$sn}{$show_rls[$i]}
  630. && $runlevels{$sn}{$show_rls[$i]} =~ /^S\d\d$/;
  631. my $box = $screen->add(
  632. zero_pad($row-2).zero_pad($i), 'Checkbox',
  633. -label => '',
  634. -checked => $on_or_off,
  635. -border => 0,
  636. -x => $right_n,
  637. -y => $row,
  638. -width => 5,
  639. #-height => 1,
  640. -userdata => { id => zero_pad($row-2).zero_pad($i),
  641. sn => $sn,
  642. changed => 0,
  643. runlevel => $show_rls[$i],
  644. },
  645. -onchange => \&simple_box_changed,
  646. -onfocus => \&got_focus,
  647. );
  648. $box->set_binding( \&start_service, "=", "+" );
  649. $box->set_binding( \&stop_service, "-" );
  650. }
  651. }
  652. sub draw_priority_layout
  653. {
  654. my ($screen, $sn, $row) = @_;
  655. for (my $i = 0, my $right_n = 11; $i <= $#show_rls; $i++, $right_n += 8) {
  656. my $text = exists $runlevels{$sn}{$show_rls[$i]}
  657. ? $runlevels{$sn}{$show_rls[$i]}
  658. : '';
  659. my $box = $screen->add(
  660. zero_pad($row-2).zero_pad($i), 'TextEntry',
  661. -sbborder => 1,
  662. -x => $right_n,
  663. -y => $row,
  664. -width => 6,
  665. -maxlength => 3,
  666. -regexp => '/^[skSK\d]*$/',
  667. -toupper => 1,
  668. -showoverflow => 0,
  669. -text => $text,
  670. -userdata => { id => zero_pad($row-2).zero_pad($i),
  671. sn => $sn,
  672. changed => 0,
  673. runlevel => $show_rls[$i],
  674. last_good_text => $text,
  675. },
  676. -onblur => \&pri_box_changed,
  677. -onfocus => \&got_focus,
  678. );
  679. $box->set_binding( \&start_service, "=", "+" );
  680. $box->set_binding( \&stop_service, "-" );
  681. }
  682. }
  683. sub create_top_label
  684. {
  685. my ($window) = @_;
  686. my @label_rls = @show_rls;
  687. my $text = 'service ' . shift @label_rls;
  688. foreach (@label_rls) { $text .= " $_" };
  689. $text .= "\n";
  690. $text .= "-" x 76;
  691. $window->add(
  692. undef, 'Label',
  693. -text => $text,
  694. -y => 0,
  695. -x => 0,
  696. -height => TOP_LABEL_HEIGHT,
  697. -textalignment => 'left',
  698. );
  699. }
  700. sub create_bottom_box
  701. {
  702. my $cmd_text = '';
  703. if ($opts{priority}) {
  704. $cmd_text =
  705. "Editing: Backspace: bs ^d: delete ^b: backward ^f: forward";
  706. }
  707. else {
  708. $cmd_text =
  709. " space: toggle service on / off ",
  710. }
  711. my $exp_window = $cui->add(
  712. 'exp_window', 'Window',
  713. -border => 1,
  714. -y => -1,
  715. -height => BOTTOM_WIN_HEIGHT,
  716. -ipadleft => 1,
  717. -ipadright => 1,
  718. );
  719. $exp_window->add(undef, 'Label',
  720. -y => 0,
  721. -width => -1,
  722. -height => BOTTOM_LAB_HEIGHT,
  723. -text =>
  724. "Use the arrow keys or mouse to move around. ^n: next pg ^p: prev pg\n$cmd_text",
  725. );
  726. }
  727. sub toggle_help
  728. {
  729. my $hw = $cui->getobj('help_window');
  730. my $hw_data = $hw->userdata();
  731. if ($hw_data == 0) {
  732. $hw->userdata($cui->getfocusobj);
  733. $hw->focus();
  734. }
  735. else {
  736. # The help window is up, so turn it off by focusing the last
  737. # object that was focused on before we pulled up the help window
  738. $hw_data->focus();
  739. $hw->userdata(0);
  740. }
  741. }
  742. #--- Movement subs ---#
  743. sub next_page
  744. {
  745. $current_screen++;
  746. $current_screen = 0 if $current_screen > last_screen();
  747. verbose("Going to screen $current_screen");
  748. _move_focus(00, $box_pos{y});
  749. }
  750. sub prev_page
  751. {
  752. $current_screen--;
  753. $current_screen = last_screen() if $current_screen < 0;
  754. verbose("Going to screen $current_screen");
  755. $box_pos{x} = last_x();
  756. _move_focus($box_pos{x}, $box_pos{y});
  757. }
  758. sub got_focus
  759. {
  760. my $widget = shift;
  761. # Is there a better way to figure out my own id besides putting it
  762. # in userdata on creation and fetching it?
  763. my $userdata = $widget->userdata();
  764. my $id = $userdata->{id};
  765. $id =~ /^(\d\d)(\d\d)$/;
  766. $box_pos{x} = $1;
  767. $box_pos{y} = $2;
  768. }
  769. sub move_left
  770. {
  771. return if $box_pos{y} eq '00';
  772. _move_focus($box_pos{x}, $box_pos{y} - 1);
  773. }
  774. sub move_right
  775. {
  776. return if $box_pos{y} == scalar(@show_rls)-1;
  777. _move_focus($box_pos{x}, $box_pos{y} + 1);
  778. }
  779. sub move_up
  780. {
  781. #return if $box_pos{x} eq '00';
  782. return prev_page() if $box_pos{x} eq '00';
  783. _move_focus($box_pos{x} - 1, $box_pos{y});
  784. }
  785. sub move_down
  786. {
  787. # Index starts at 00, so we need one less then the max services that
  788. # are on the screen.
  789. return next_page() if $box_pos{x} == last_x();
  790. _move_focus($box_pos{x} + 1, $box_pos{y});
  791. }
  792. sub _move_focus
  793. {
  794. $box_pos{x} = $_[0];
  795. $box_pos{y} = $_[1];
  796. my $box = $s[$current_screen]->getobj(zero_pad($_[0]).zero_pad($_[1]));
  797. $box->focus();
  798. }
  799. sub last_x { return scalar(@{$snames_per_screen[$current_screen]})-1; }
  800. sub first_screen { return 0 }
  801. sub last_screen { return scalar(@s)-1 }
  802. =pod
  803. =head1 NAME
  804. B<sysv-rc-conf> - Run-level configuration for SysV like init script links
  805. =head1 SYNOPSIS
  806. B<sysv-rc-conf> [ I<options> ]
  807. B<sysv-rc-conf> [ --level I<levels> ] I<service> E<lt>I<on|off>E<gt>
  808. =head1 DESCRIPTION
  809. B<sysv-rc-conf> gives an easy to use interface for managing
  810. C</etc/rc{runlevel}.d/> symlinks. The interface comes in two different
  811. flavors, one that simply allows turning services on or off and another that
  812. allows for more fine tuned management of the symlinks. It's a replacement for
  813. programs like B<ntsysv(8)> or B<rcconf(8)>.
  814. B<sysv-rc-conf> can also be used at the command line when the desired changes
  815. to the symlinks are already known. The syntax is borrowed from B<chkconfig(8)>.
  816. =head1 GENERAL OPTIONS
  817. =over
  818. =item B<-c> DIRECTORY, B<--cache=>DIRECTORY
  819. The directory where the priority numbers, old runlevel configuration, etc.
  820. should be stored. This defaults to C</var/lib/sysv-rc-conf>. See the FILES
  821. section below.
  822. =item B<-r> DIRECTORY, B<--root=>DIRECTORY
  823. The root directory to use. This defaults to C</>. This comes in handy if the
  824. root file system is mounted somewhere else, such as when using a rescue disk.
  825. =item B<-v> FILE, B<--verbose=>FILE
  826. Print verbose information to C<FILE>
  827. =item B<-V>, B<--Version>
  828. Print version information to STDOUT and exit
  829. =back
  830. =head1 GUI RELATED OPTIONS
  831. =over
  832. =item B<-o> [ see description ], B<--order=>[ see description ]
  833. Allows various sorting orders and ways to display the rows. The argument can be
  834. made up of any of the following:
  835. =over
  836. =item B<a>
  837. Sort the rows B<a>lphabetically. This is the default if the B<-o> option isn't
  838. specified.
  839. =item B<n>
  840. Show the priority numbers along with the name of the service.
  841. =item B<p>
  842. Sorts by the B<p>riority numbers.
  843. =item B<level>
  844. I<level> can be any runlevel, 0-9 or S. This controls which runlevel the
  845. priority numbers are sorted at. It only makes sense to use this in conjuntion
  846. with B<p>. If omitted the priority numbers are sorted by the current runlevel
  847. the system is in.
  848. =back
  849. =item B<-p>, B<--priority>
  850. Alternate layout. Instead of just showing a checkbox, the priority of the
  851. service and the S or K are allowed to be edited. This is for more fine tuned
  852. control then the default layout allows.
  853. =item B<-s> I<levels>, B<--show=>I<levels>
  854. Which runlevels to show. This defaults to up to 8 of the runlevels available
  855. on the system. Usually this means it will show 1, 2, 3, 4, 5, 0, 6, and S.
  856. The syntax calls for the runlevels to be allruntogether. For instance, to
  857. show runlevels 3, 4, and 5 the syntax would be C<--show=345>. Also see
  858. B<--order>.
  859. =back
  860. =head1 CLI RELATED OPTIONS
  861. =over
  862. =item B<--level> I<levels>
  863. The runlevels this operation will affect. I<levels> can be any number from
  864. 0-9 or S. For example, B<--level 135> will affect runlevels 1, 3, and 5.
  865. If B<--level> is not set, the default is to affect runlevels 2, 3, 4, and 5.
  866. This option is only used for the command line interface, see the section
  867. below labled USING THE CLI for more information.
  868. =item B<--list> [I<name>]
  869. This option will list all of the services and if they are stopped or started
  870. when entering each runlevel. If I<name> is specified, only the information
  871. for that service is displayed.
  872. =back
  873. =head1 USING THE GUI
  874. =head2 Using the Default layout
  875. The default (simple) layout shows in a grid fashion all of the services that
  876. are in C<init.d> and which runlevels they are turned on at. For example, where
  877. the C<ssh> row and C<3> column intersect, if there is a checkbox there that
  878. means the service will be turned on when entering runlevel 3. If there is no
  879. checkbox it can mean that either there are no links to the service in that
  880. specific runlevel, or that the service is turned off when entering that
  881. runlevel. If more configuration detail is needed, see the next paragraph and
  882. the B<--priority> option.
  883. =head2 Using the Priority layout
  884. The priority (advanced) layout also uses a grid fashion, but instead of
  885. checkboxes there are text boxes that can have a few different values. If the
  886. text box is blank, that means there isn't a symlink in that runlevel for that
  887. service. This means that when changing into that runlevel that the service
  888. will not be started or stopped, which is significant. If the text box starts
  889. with the letter K that means that the service will be stopped when entering
  890. that runlevel. If the text box starts with the letter S that means the service
  891. will be started when entering that runlevel. The two digits following is the
  892. order in which the services are started. That means that C<S08iptables> would
  893. start before C<S20ssh>. For more information see your system documentation.
  894. =head2 Controls
  895. To move around use the arrow keys, or if the terminal support it, the mouse.
  896. Typically there is more then one page of services (unless the terminal screen
  897. is large), to move between the pages use CTRL-n or CTRL-p, or simply arrow key
  898. down or up at the bottom or top of the screen, respectively. The bottom of the
  899. screen also shows these movement commands for quick reference. To restore the
  900. symlinks back to their original state before the B<sysv-rc-conf> was run,
  901. press the B<r> key. The B<h> key will display a quick reference help screen.
  902. =head2 Default layout
  903. When using the default layout use the space bar to toggle the service on / off.
  904. =head2 Priority layout
  905. The priority layout uses the default movement keys. In order to edit the fields
  906. you can use CTRL-d to delete the character in front of the cursor or backspace
  907. to backspace. Use CTRL-b or CTRL-f to move the cursor backwards or forwards
  908. within the field. Note that only S, K, or any digit is allowed to be entered
  909. into the field.
  910. =head2 Starting / Stopping Services
  911. To start a service, press the C<+> or C<=> key.
  912. To stop a service, press the C<-> key.
  913. This will call C</etc/init.d/service start> or C</etc/init.d/service stop>.
  914. =head1 USING THE CLI
  915. If the desired modifications to the symlinks are known and only one quick
  916. change is needed, then you can use a CLI interface to B<sysv-rc-conf>.
  917. Examples:
  918. # sysv-rc-conf --level 35 ssh off
  919. # sysv-rc-conf atd on
  920. The first example will turn ssh off on levels 3 and 5. The second example
  921. turns atd on for runlevels 2, 3, 4, and 5.
  922. =head1 FILES
  923. B<Note:> Feel free to skip this section
  924. B<sysv-rc-conf> stores a cache of all the symlink information from
  925. C</etc/rc{runlevel}.d/> in C</var/lib/sysv-rc-conf/services> (See the --cache
  926. option to change the location of this file). It uses this cache to make an
  927. intelligent decision on what priority number to give the K or S link when they
  928. are changed in the simple layout. This cache is updated/created everytime the
  929. program is launched. The format of the file is as follows:
  930. RUNLEVEL S|K PRIORITY SERVICE
  931. Here's a few examples:
  932. 2 K 74 ntpd
  933. 2 K 50 xinetd
  934. 3 S 08 iptables
  935. 3 S 80 sendmail
  936. B<sysv-rc-conf> will first see if it can get an exact match from the cache.
  937. For example, if the symlink for C<cron> in runlevel 3 is S89cron and you
  938. uncheck it, B<sysv-rc-conf> will first see if there is an entry in the cache
  939. that looks like C<3 K nn cron>, if so it will use nn for the priority number.
  940. If there wasn't a match, B<sysv-rc-conf> will then see if there is another S or
  941. K (whichever you're switching to, so in this example, K) entry on a different
  942. runlevel - so an entry like C<i K nn cron>, where i is any runlevel. If found,
  943. the link will use nn.
  944. If there still wasn't a match, B<sysv-rc-conf> will look for the opposite of S
  945. or K in any run level, and use 100 - that priority. So in our example,
  946. C<i S nn cron>. If nn is 20, then it will use 80 (100 - 20), since that is
  947. typically the way that the priority numbers are used.
  948. If there still isn't a match, the default priority of 20 for S links is used,
  949. and the default priority of 80 for K links is used.
  950. =head1 COMPATIBILITY
  951. B<sysv-rc-conf> should work on any Unix like system that manages services
  952. when changing runlevels by using symlinks in C</etc/rc{runlevel}.d/>. Refer
  953. to your system documentation to see if that's the case (usually there's a
  954. C</etc/init.d/README>).
  955. =head1 CAVEATS
  956. B<sysv-rc-conf> only manages the symlinks in the C<rc{runlevel}.d>
  957. directories. It's possible that pacakages may have other ways of being
  958. disabled or enabled.
  959. Because Curses takes over the screen sometimes error messages won't be
  960. seen on the terminal. If you run across any weird problems try redirecting
  961. STDERR to a file when you execute the program.
  962. For example:
  963. # sysv-rc-conf 2E<gt> err.out
  964. =head1 REPORTING BUGS
  965. Report bugs to Joe Oppegaard E<lt>joe@pidone.orgE<gt>
  966. =head1 SEE ALSO
  967. B<init(8)>, B<runlevel(8)>, B<chkconfig(8)>, C</etc/init.d/README>
  968. www: http://sysv-rc-conf.sourceforge.net
  969. ftp: ftp://ftp.pidone.org/sysv-rc-conf
  970. =head1 AUTHOR
  971. Joe Oppegaard E<lt>joe@pidone.orgE<gt>