3 Common functions for SYSV-style boot/shutdown sequences, MacOS, FreeBSD
4 and Windows. Because each system uses a different format and semantics for
5 bootup actions, there are separate functions for listing and managing each
6 type. However, some functions like enable_at_boot and disable_at_boot can
7 creation actions regardless of the underlying boot system.
11 foreign_require('init', 'init-lib.pl');
12 $ok = init::action_status('foo');
14 init::enable_at_boot('foo', 'Start or stop the Foo server',
15 '/etc/foo/start', '/etc/foo/stop');
20 BEGIN { push(@INC, ".."); };
23 @action_buttons = ( 'start', 'restart', 'condrestart', 'reload', 'status',
25 %access = &get_module_acl();
29 This variable is set based on the bootup system in use. Possible values are :
31 =item osx - MacOSX hostconfig files
33 =item rc - FreeBSD 6+ RC files
35 =item init - System V init.d files, seen on Linux and Solaris
37 =item local - A single rc.local file
39 =item win32 - Windows services
42 if ($config{'hostconfig'}) {
45 elsif ($config{'rc_dir'}) {
48 elsif ($config{'init_base'} && -d "/etc/init" &&
49 &has_command("insserv") && &has_command("initctl")) {
50 $init_mode = "upstart";
52 elsif ($config{'init_base'}) {
55 elsif ($config{'local_script'}) {
58 elsif ($gconfig{'os_type'} eq 'windows') {
62 =head2 runlevel_actions(level, S|K)
64 Return a list of init.d actions started or stopped in some run-level, each of
65 which is a space-separated string in the format : number name inode
70 local($dir, $f, @stbuf, @rv);
71 $dir = &runlevel_dir($_[0]);
73 foreach $f (readdir(DIR)) {
74 if ($f !~ /^([A-Z])(\d+)(.*)$/ || $1 ne $_[1]) { next; }
75 if (!(@stbuf = stat("$dir/$f"))) { next; }
76 push(@rv, "$2 $3 $stbuf[1]");
79 @rv = sort { @a = split(/\s/,$a); @b = split(/\s/,$b); $a[0] <=> $b[0]; } @rv;
80 return $_[1] eq "S" ? @rv : reverse(@rv);
86 Returns a list of known runlevels, such as : 2 3 5.
92 opendir(DIR, $config{init_base});
93 foreach (readdir(DIR)) {
94 if (/^rc([A-z0-9])\.d$/ || /^(boot)\.d$/) {
95 #if (!$config{show_opts} && $1 < 1) { next; }
106 List boot time action names from init.d, such as httpd and cron.
111 local($dir, $f, @stbuf, @rv);
112 $dir = $config{init_dir};
114 foreach $f (sort { lc($a) cmp lc($b) } readdir(DIR)) {
115 if ($f eq "." || $f eq ".." || $f =~ /\.bak$/ || $f eq "functions" ||
116 $f eq "core" || $f eq "README" || $f eq "rc" || $f eq "rcS" ||
117 -d "$dir/$f" || $f =~ /\.swp$/ || $f eq "skeleton" ||
118 $f =~ /\.lock$/ || $f =~ /\.dpkg-(old|dist)$/ ||
119 $f =~ /^\.depend\./ || $f eq '.legacy-bootordering') { next; }
120 if (@stbuf = stat("$dir/$f")) {
121 push(@rv, "$f $stbuf[1]");
125 foreach $f (split(/\s+/, $config{'extra_init'})) {
126 if (@stbuf = stat($f)) {
127 push(@rv, "$f $stbuf[1]");
134 =head2 action_levels(S|K, action)
136 Return a list of run levels in which some action (from init.d) is started
137 or stopped. Each item is a space-separated string in the format : level order name
142 local(@stbuf, $rl, $dir, $f, @stbuf2, @rv);
143 @stbuf = stat(&action_filename($_[1]));
144 foreach $rl (&list_runlevels()) {
145 $dir = &runlevel_dir($rl);
147 foreach $f (readdir(DIR)) {
148 if ($f =~ /^([A-Z])(\d+)(.*)$/ && $1 eq $_[0]) {
149 @stbuf2 = stat("$dir/$f");
150 if ($stbuf[1] == $stbuf2[1]) {
151 push(@rv, "$rl $2 $3");
162 =head2 action_filename(name)
164 Returns the path to the file in init.d for some action, such as /etc/init.d/foo.
169 return $_[0] =~ /^\// ? $_[0] : "$config{init_dir}/$_[0]";
172 =head2 runlevel_filename(level, S|K, order, name)
174 Returns the path to the actual script run at boot for some action, such as
178 sub runlevel_filename
182 return &runlevel_dir($_[0])."/$_[1]$_[2]$n";
186 =head2 add_rl_action(action, runlevel, S|K, order)
188 Add some existing action to a runlevel. The parameters are :
190 =item action - Name of the action, like foo
192 =item runlevel - A runlevel number, like 3
194 =item S|K - Either S for an action to run at boot, or K for shutdown
196 =item order - Numeric boot order, like 99
201 $file = &runlevel_filename($_[1], $_[2], $_[3], $_[0]);
203 if ($file =~ /^(.*)_(\d+)$/) { $file = "$1_".($2+1); }
204 else { $file = $file."_1"; }
207 if ($config{soft_links}) {
208 &symlink_file(&action_filename($_[0]), $file);
211 &link_file(&action_filename($_[0]), $file);
217 =head2 delete_rl_action(name, runlevel, S|K)
219 Delete some action from a runlevel. The parameters are :
221 =item action - Name of the action, like foo.
223 =item runlevel - A runlevel number, like 3.
225 =item S|K - Either S for an action to run at boot, or K for shutdown.
230 local(@stbuf, $dir, $f, @stbuf2);
231 @stbuf = stat(&action_filename($_[0]));
232 $dir = &runlevel_dir($_[1]);
234 foreach $f (readdir(DIR)) {
235 if ($f =~ /^([A-Z])(\d+)(.+)$/ && $1 eq $_[2]) {
236 @stbuf2 = stat("$dir/$f");
237 if ($stbuf[1] == $stbuf2[1]) {
238 # found file to delete.. unlink
239 &unlink_logged("$dir/$f");
248 =head2 reorder_rl_action(name, runlevel, S|K, new_order)
250 Change the boot order of some existing runlevel action. The parameters are :
252 =item action - Name of the action, like foo.
254 =item runlevel - A runlevel number, like 3.
256 =item S|K - Either S for an action to run at boot, or K for shutdown.
258 =item new_order - New numeric boot order to use, like 99.
261 sub reorder_rl_action
263 local(@stbuf, $dir, $f, @stbuf2);
264 @stbuf = stat(&action_filename($_[0]));
265 $dir = &runlevel_dir($_[1]);
267 foreach $f (readdir(DIR)) {
268 if ($f =~ /^([A-Z])(\d+)(.+)$/ && $1 eq $_[2]) {
269 @stbuf2 = stat("$dir/$f");
270 if ($stbuf[1] == $stbuf2[1]) {
271 # Found file that needs renaming
272 $file = &runlevel_dir($_[1])."/$1$_[3]$3";
274 if ($file =~ /^(.*)_(\d+)$/)
275 { $file = "$1_".($2+1); }
276 else { $file = $file."_1"; }
278 &rename_logged("$dir/$f", $file);
287 =head2 rename_action(old, new)
289 Change the name of an action in init.d, and re-direct all soft links
290 to it from the runlevel directories. Parameters are :
292 =item old - Old action name.
294 =item new - New action name.
299 local($file, $idx, $old);
300 foreach (&action_levels('S', $_[0])) {
301 /^(\S+)\s+(\S+)\s+(\S+)$/;
302 $file = &runlevel_dir($1)."/S$2$3";
303 if (readlink($file)) {
304 # File is a symbolic link.. change it
307 &symlink_file("$config{init_dir}/$_[1]", $file);
310 if (($idx = index($file, $_[0])) != -1) {
312 substr($file, $idx, length($_[0])) = $_[1];
313 &rename_logged($old, $file);
316 foreach (&action_levels('K', $_[0])) {
317 /^(\S+)\s+(\S+)\s+(\S+)$/;
318 $file = &runlevel_dir($1)."/K$2$3";
319 if (readlink($file)) {
320 # File is a symbolic link.. change it
323 &symlink_file("$config{init_dir}/$_[1]", $file);
326 if (($idx = index($file, $_[0])) != -1) {
328 substr($file, $idx, length($_[0])) = $_[1];
329 &rename_logged($old, $file);
332 &rename_logged("$config{init_dir}/$_[0]", "$config{init_dir}/$_[1]");
336 =head2 rename_rl_action(runlevel, S|K, order, old, new)
338 Change the name of a runlevel file. For internal use only.
343 &rename_logged(&runlevel_dir($_[0])."/$_[1]$_[2]$_[3]",
344 &runlevel_dir($_[0])."/$_[1]$_[2]$_[4]");
347 =head2 get_inittab_runlevel
349 Returns the runlevels entered at boot time. If more than one is returned,
350 actions from all of them are used.
353 sub get_inittab_runlevel
355 local %iconfig = &foreign_config("inittab");
357 local $id = $config{'inittab_id'};
358 if (open(TAB, $iconfig{'inittab_file'})) {
359 # Read the inittab file
361 if (/^$id:(\d+):/ && $1) { @rv = ( $1 ); }
366 if (&has_command("runlevel")) {
367 # Use runlevel command to get current level
368 local $out = &backquote_command("runlevel");
369 if ($out =~ /^(\S+)\s+(\S+)/) {
373 elsif (&has_command("who")) {
374 # Use who -r command to get runlevel
375 local $out = &backquote_command("who -r 2>/dev/null");
376 if (!$? && $out =~ /run-level\s+(\d+)/) {
381 # Add statically configured runlevels
382 if ($config{"inittab_rl_$rv[0]"}) {
383 @rv = split(/,/, $config{"inittab_rl_$rv[0]"});
385 push(@rv, $config{'inittab_extra'});
389 =head2 init_description(file, [&hasargs])
391 Given a full path to an init.d file, returns a description from the comments
392 about what it does. If the hasargs hash ref parameter is given, it is filled
393 in with supported parameters to the action, like 'start' and 'stop'.
398 # Read contents of script, extract start/stop commands
400 local @lines = <FILE>;
402 local $data = join("", @lines);
405 if (/^\s*(['"]?)([a-z]+)\1\)/i) {
412 if ($config{'daemons_dir'}) {
413 # First try the daemons file
415 if ($_[0] =~ /\/([^\/]+)$/ &&
416 &read_env_file("$config{'daemons_dir'}/$1", \%daemon) &&
417 $daemon{'DESCRIPTIVE'}) {
418 return $daemon{'DESCRIPTIVE'};
421 if ($config{'chkconfig'}) {
422 # Find the redhat-style description: section
425 if (/^#+\s*description:(.*?)(\\?$)/) {
428 elsif (/^#+\s*(.*?)(\\?$)/ && $desc && $1) {
436 elsif ($config{'init_info'} || $data =~ /BEGIN INIT INFO/) {
437 # Find the suse-style Description: line
440 if (/^#\s*(Description|Short-Description):\s*(.*)/) {
446 # Use the first comments
449 next if (/^#!\s*\/(bin|sbin|usr)\// || /\$id/i || /^#+\s+@/ ||
450 /source function library/i || /^#+\s*copyright/i);
452 last if ($desc && !$1);
453 $desc .= $1."\n" if ($1);
455 elsif (/\S/) { last; }
457 $_[0] =~ /\/([^\/]+)$/;
458 $desc =~ s/^Tag\s+(\S+)\s*//i;
459 $desc =~ s/^\s*$1\s+//;
464 =head2 chkconfig_info(file)
466 If a file has a chkconfig: section specifying the runlevels to start in and
467 the orders to use, return an array containing the levels (as array ref),
468 start order, stop order and description.
477 if (/^#\s*chkconfig:\s+(\S+)\s+(\d+)\s+(\d+)/) {
478 @rv = ( $1 eq '-' ? [ ] : [ split(//, $1) ], $2, $3 );
480 elsif (/^#\s*description:\s*(.*)/) {
485 $rv[3] = $desc if ($desc && @rv);
489 =head2 action_status(action)
491 Returns 0 if some action doesn't exist, 1 if it does but is not enabled,
492 or 2 if it exists and is enabled. This works for all supported boot systems,
493 such as init.d, OSX and FreeBSD.
498 if ($init_mode eq "upstart") {
499 # Check service status
500 local $out = &backquote_command("initctl status ".
501 quotemeta($_[0])." 2>&1");
503 my $cfile = "/etc/init/$_[0].conf";
506 if (/^(#*)\s*start/) {
511 return 1; # Should never happen
514 if ($init_mode eq "init" || $init_mode eq "upstart") {
515 # Look for init script
516 local ($a, $exists, $starting, %daemon);
517 foreach $a (&list_actions()) {
518 local @a = split(/\s+/, $a);
519 if ($a[0] eq $_[0]) {
521 local @boot = &get_inittab_runlevel();
522 foreach $s (&action_levels("S", $a[0])) {
523 local ($l, $p) = split(/\s+/, $s);
524 $starting++ if (&indexof($l, @boot) >= 0);
528 if ($starting && $config{'daemons_dir'} &&
529 &read_env_file("$config{'daemons_dir'}/$_[0]", \%daemon)) {
530 $starting = lc($daemon{'ONBOOT'}) eq 'yes' ? 1 : 0;
532 return !$exists ? 0 : $starting ? 2 : 1;
534 elsif ($init_mode eq "local") {
535 # Look for entry in rc.local
536 local $fn = "$module_config_directory/$_[0].sh";
537 local $cmd = "$fn start";
538 open(LOCAL, $config{'local_script'});
541 $found++ if ($_ eq $cmd);
544 return $found && -r $fn ? 2 : -r $fn ? 1 : 0;
546 elsif ($init_mode eq "win32") {
547 # Look for a win32 service, enabled at boot
548 local ($svc) = &list_win32_services($_[0]);
550 $svc->{'boot'} == 2 ? 2 : 1;
552 elsif ($init_mode eq "rc") {
553 # Look for an RC script
554 local @rcs = &list_rc_scripts();
555 local ($rc) = grep { $_->{'name'} eq $_[0] } @rcs;
557 $rc->{'enabled'} ? 2 : 1;
559 elsif ($init_mode eq "osx") {
560 # Look for a hostconfig entry
561 local $ucname = uc($_[0]);
563 &read_env_file($config{'hostconfig'}, \%hc);
564 return $hc{$ucname} eq '-YES-' ? 2 :
565 $hc{$ucname} eq '-NO-' ? 1 : 0;
569 =head2 enable_at_boot(action, description, startcode, stopcode, statuscode, &opts)
571 Makes some action start at boot time, creating the script by copying the
572 specified file if necessary. The parameters are :
574 =item action - Name of the action to create or enable.
576 =item description - A human-readable description for the action.
578 =item startcode - Shell commands to run at boot time.
580 =item stopcode - Shell commands to run at shutdown time.
582 =item statuscode - Shell code to output the action's status.
584 =item opts - Hash ref of additional options, like : fork -> server will fork into background
586 If this is called for a named action that already exists (even if it isn't
587 enabled), only the first parameter needs to be given.
592 local $st = &action_status($_[0]);
593 return if ($st == 2); # already starting!
594 local ($daemon, %daemon);
596 if ($init_mode eq "upstart" && (!-r "$config{'init_dir'}/$_[0]" ||
597 -r "/etc/init/$_[0].conf")) {
598 # Create upstart action if missing, as long as this isn't an old-style
600 my $cfile = "/etc/init/$_[0].conf";
602 # Config file exists, make sure it is enabled
603 &system_logged("insserv ".quotemeta($_[0])." >/dev/null 2>&1");
604 my $lref = &read_file_lines($cfile);
606 foreach my $l (@$lref) {
607 if ($l =~ /^#+start/) {
608 # Start of start block
612 elsif ($l =~ /^#+\s+\S/ && $foundstart) {
613 # Continuation line for start
616 elsif ($l =~ /^\S/ && $foundstart) {
617 # Some other directive after start
621 &flush_file_lines($cfile);
624 # Need to create config
625 $_[2] || &error("Upstart service $_[0] cannot be created ".
626 "unless a command is given");
627 &create_upstart_service($_[0], $_[1], $_[2], undef,
629 &system_logged("insserv ".quotemeta($_[0])." >/dev/null 2>&1");
633 if ($init_mode eq "init" || $init_mode eq "local" || $init_mode eq "upstart") {
634 # In these modes, we create a script to run
635 if ($config{'daemons_dir'} &&
636 &read_env_file("$config{'daemons_dir'}/$_[0]", \%daemon)) {
640 if ($init_mode eq "init" || $init_mode eq "upstart") {
641 # Normal init.d system
642 $fn = &action_filename($_[0]);
645 # Need to create hack init script
646 $fn = "$module_config_directory/$_[0].sh";
648 local @chk = &chkconfig_info($fn);
649 local @start = @{$chk[0]} ? @{$chk[0]} : &get_start_runlevels();
650 local $start_order = $chk[1] || "9" x $config{'order_digits'};
651 local $stop_order = $chk[2] || "9" x $config{'order_digits'};
654 local %starting = map { $_, 1 } @start;
655 @stop = grep { !$starting{$_} && /^\d+$/ } &list_runlevels();
658 local $need_links = 0;
659 if ($st == 1 && $daemon) {
660 # Just update daemons file
661 $daemon{'ONBOOT'} = 'yes';
662 &lock_file("$config{'daemons_dir'}/$_[0]");
663 &write_env_file("$config{'daemons_dir'}/$_[0]", \%daemon);
664 &unlock_file("$config{'daemons_dir'}/$_[0]");
667 # Just need to create links (later)
671 # Need to create the init script
673 &open_tempfile(ACTION, ">$fn");
674 &print_tempfile(ACTION, "#!/bin/sh\n");
675 if ($config{'chkconfig'}) {
676 # Redhat-style description: and chkconfig: lines
677 &print_tempfile(ACTION, "# description: $_[1]\n");
678 &print_tempfile(ACTION, "# chkconfig: $config{'chkconfig'} ",
679 "$start_order $stop_order\n");
681 elsif ($config{'init_info'}) {
682 # Suse-style init info section
683 &print_tempfile(ACTION, "### BEGIN INIT INFO\n",
684 "# Provides: $_[0]\n",
685 "# Required-Start: \$network \$syslog\n",
686 "# Required-Stop: \$network\n",
687 "# Default-Start: ",join(" ", @start),"\n",
689 "# Description: $_[1]\n",
690 "### END INIT INFO\n");
693 &print_tempfile(ACTION, "# $_[1]\n");
695 &print_tempfile(ACTION, "\n");
696 &print_tempfile(ACTION, "case \"\$1\" in\n");
699 &print_tempfile(ACTION, "'start')\n");
700 &print_tempfile(ACTION, &tab_indent($_[2]));
701 &print_tempfile(ACTION, "\tRETVAL=\$?\n");
702 if ($config{'subsys'}) {
703 &print_tempfile(ACTION, "\tif [ \"\$RETVAL\" = \"0\" ]; then\n");
704 &print_tempfile(ACTION, "\t\ttouch $config{'subsys'}/$_[0]\n");
705 &print_tempfile(ACTION, "\tfi\n");
707 &print_tempfile(ACTION, "\t;;\n");
711 &print_tempfile(ACTION, "'stop')\n");
712 &print_tempfile(ACTION, &tab_indent($_[3]));
713 &print_tempfile(ACTION, "\tRETVAL=\$?\n");
714 if ($config{'subsys'}) {
715 &print_tempfile(ACTION, "\tif [ \"\$RETVAL\" = \"0\" ]; then\n");
716 &print_tempfile(ACTION, "\t\trm -f $config{'subsys'}/$_[0]\n");
717 &print_tempfile(ACTION, "\tfi\n");
719 &print_tempfile(ACTION, "\t;;\n");
723 &print_tempfile(ACTION, "'status')\n");
724 &print_tempfile(ACTION, &tab_indent($_[4]));
725 &print_tempfile(ACTION, "\t;;\n");
728 if ($_[2] && $_[3]) {
729 &print_tempfile(ACTION, "'restart')\n");
730 &print_tempfile(ACTION, "\t\$0 stop ; \$0 start\n");
731 &print_tempfile(ACTION, "\tRETVAL=\$?\n");
732 &print_tempfile(ACTION, "\t;;\n");
735 &print_tempfile(ACTION, "*)\n");
736 &print_tempfile(ACTION, "\techo \"Usage: \$0 { start | stop }\"\n");
737 &print_tempfile(ACTION, "\tRETVAL=1\n");
738 &print_tempfile(ACTION, "\t;;\n");
739 &print_tempfile(ACTION, "esac\n");
740 &print_tempfile(ACTION, "exit \$RETVAL\n");
741 &close_tempfile(ACTION);
747 if ($need_links && ($init_mode eq "init" ||
748 $init_mode eq "upstart")) {
749 local $data = &read_file_contents($fn);
751 if (&has_command("chkconfig") && !$config{'no_chkconfig'} &&
752 (@chk && $chk[3] || $data =~ /Default-Start:/i)) {
753 # Call the chkconfig command to link up
754 &system_logged("chkconfig --add ".quotemeta($_[0]));
755 &system_logged("chkconfig ".quotemeta($_[0])." on");
758 elsif (&has_command("insserv") && !$config{'no_chkconfig'} &&
759 $data =~ /Default-Start:/i) {
760 # Call the insserv command to enable
761 my $ex = &system_logged("insserv ".quotemeta($_[0]).
766 # Just link up the init script
768 foreach $s (@start) {
769 &add_rl_action($_[0], $s, "S", $start_order);
771 local @klevels = &action_levels("K", $_[0]);
773 # Only add K scripts if none exist
775 &add_rl_action($_[0], $s, "K", $stop_order);
780 elsif ($need_links) {
781 # Just add rc.local entry
782 local $lref = &read_file_lines($config{'local_script'});
784 for($i=0; $i<@$lref && $lref->[$i] !~ /^exit\s/; $i++) { }
785 splice(@$lref, $i, 0, "$fn start");
786 if ($config{'local_down'}) {
787 # Also add to shutdown script
788 $lref = &read_file_lines($config{'local_down'});
789 for($i=0; $i<@$lref &&
790 $lref->[$i] !~ /^exit\s/; $i++) { }
791 splice(@$lref, $i, 0, "$fn stop");
796 elsif ($init_mode eq "win32") {
797 # Enable and/or create a win32 service
800 &enable_win32_service($_[0]);
803 # Need to create service, which calls wrapper program
804 eval "use Win32::Daemon";
806 # modify the string handed over
807 # so it does not contain backslashes ...
810 local $perl_path = &get_perl_path();
811 local %svc = ( 'name' => $_[0],
813 'path' => $perl_path,
815 'description' => "OCM Webmin Pro Service",
816 'pwd' => $module_root_directory,
817 'parameters' => "\"$module_root_directory/win32.pl\" $_[2]",
819 if (!Win32::Daemon::CreateService(\%svc)) {
820 print STDERR "Failed to create Win32 service : ",
821 Win32::FormatMessage(Win32::Daemon::GetLastError()),"\n";
825 elsif ($init_mode eq "rc") {
826 # Enable and/or create an RC script
830 &enable_rc_script($_[0]);
833 # Need to create a local rc script, and enable
834 local @dirs = split(/\s+/, $config{'rc_dir'});
835 local $file = $dirs[$#dirs]."/".$_[0].".sh";
838 &open_lock_tempfile(SCRIPT, ">$file");
839 &print_tempfile(SCRIPT, "#!/bin/sh\n");
840 &print_tempfile(SCRIPT, "#\n");
841 &print_tempfile(SCRIPT, "# PROVIDE: $_[0]\n");
842 &print_tempfile(SCRIPT, "# REQUIRE: LOGIN\n");
843 &print_tempfile(SCRIPT, "\n");
844 &print_tempfile(SCRIPT, ". /etc/rc.subr\n");
845 &print_tempfile(SCRIPT, "\n");
846 &print_tempfile(SCRIPT, "name=$name\n");
847 &print_tempfile(SCRIPT, "rcvar=`set_rcvar`\n");
848 &print_tempfile(SCRIPT, "start_cmd=\"$_[2]\"\n");
850 &print_tempfile(SCRIPT, "stop_cmd=\"$_[3]\"\n")
852 if ($_[4] && $_[4] !~ /\n/) {
853 &print_tempfile(SCRIPT, "status_cmd=\"$_[4]\"\n")
855 &print_tempfile(SCRIPT, "\n");
856 &print_tempfile(SCRIPT, "load_rc_config \${name}\n");
857 &print_tempfile(SCRIPT, "run_rc_command \"\$1\"\n");
858 &close_tempfile(SCRIPT);
859 &set_ownership_permissions(undef, undef, 0755, $file);
860 &enable_rc_script($_[0]);
864 elsif ($init_mode eq "osx") {
865 # Add hostconfig file entry
866 local $ucname = uc($_[0]);
868 &lock_file($config{'hostconfig'});
869 &read_env_file($config{'hostconfig'}, \%hc);
871 # Need to create action
872 local $ucfirst = ucfirst($_[0]);
873 local $dir = "$config{'darwin_setup'}/$ucfirst";
874 local $paramlist = "$dir/$config{'plist'}";
875 local $scriptfile = "$dir/$ucfirst";
877 # Create dirs if missing
878 if (!-d $config{'darwin_setup'}) {
879 &make_dir($config{'darwin_setup'}, 0755);
882 &make_dir($dir, 0755);
885 # Make params list file
886 &open_lock_tempfile(PLIST, ">$paramlist");
887 &print_tempfile(PLIST, "{\n");
888 &print_tempfile(PLIST, "\t\tDescription\t\t= \"$_[1]\";\n");
889 &print_tempfile(PLIST, "\t\tProvides\t\t= (\"$ucfirst\");\n");
890 &print_tempfile(PLIST, "\t\tRequires\t\t= (\"Resolver\");\n");
891 &print_tempfile(PLIST, "\t\tOrderPreference\t\t= \"None\";\n");
892 &print_tempfile(PLIST, "\t\tMessages =\n");
893 &print_tempfile(PLIST, "\t\t{\n");
894 &print_tempfile(PLIST, "\t\t\tstart\t= \"Starting $ucfirst\";\n");
895 &print_tempfile(PLIST, "\t\t\tstop\t= \"Stopping $ucfirst\";\n");
896 &print_tempfile(PLIST, "\t\t};\n");
897 &print_tempfile(PLIST, "}\n");
898 &close_tempfile(PLIST);
900 # Create Bootup Script
901 &open_lock_tempfile(STARTUP, ">$scriptfile");
902 &print_tempfile(STARTUP, "#!/bin/sh\n\n");
903 &print_tempfile(STARTUP, ". /etc/rc.common\n\n");
904 &print_tempfile(STARTUP, "if [ \"\${$ucname:=-NO-}\" = \"-YES-\" ]; then\n");
905 &print_tempfile(STARTUP, "\tConsoleMessage \"Starting $ucfirst\"\n");
906 &print_tempfile(STARTUP, "\t$_[2]\n");
907 &print_tempfile(STARTUP, "fi\n");
908 &close_tempfile(STARTUP);
909 &set_ownership_permissions(undef, undef, 0750, $scriptfile);
912 # Update hostconfig file
913 $hc{$ucname} = '-YES-';
914 &write_env_file($config{'hostconfig'}, \%hc);
915 &unlock_file($config{'hostconfig'});
919 =head2 disable_at_boot(action)
921 Disabled some action from starting at boot, identified by the action
922 parameter. The config files that define what commands the action runs are not
923 touched, so it can be re-enabled with the enable_at_boot function.
928 local $st = &action_status($_[0]);
929 return if ($st != 2); # not currently starting
931 if ($init_mode eq "upstart") {
932 # Just use insserv to disable, and comment out start line in .conf file
933 &system_logged("insserv -r ".quotemeta($_[0])." >/dev/null 2>&1");
934 my $cfile = "/etc/init/$_[0].conf";
936 my $lref = &read_file_lines($cfile);
938 foreach my $l (@$lref) {
939 if ($l =~ /^start\s/) {
940 # Start of start block
944 elsif ($l =~ /^\s+\S/ && $foundstart) {
945 # Continuation line for start
948 elsif ($l =~ /^\S/ && $foundstart) {
949 # Some other directive after start
953 &flush_file_lines($cfile);
956 if ($init_mode eq "init" || $init_mode eq "upstart") {
957 # Unlink or disable init script
958 local ($daemon, %daemon);
959 local $file = &action_filename($_[0]);
960 local @chk = &chkconfig_info($file);
961 local $data = &read_file_contents($file);
963 if ($config{'daemons_dir'} &&
964 &read_env_file("$config{'daemons_dir'}/$_[0]", \%daemon)) {
965 # Update daemons file
966 $daemon{'ONBOOT'} = 'no';
967 &lock_file("$config{'daemons_dir'}/$_[0]");
968 &write_env_file("$config{'daemons_dir'}/$_[0]", \%daemon);
969 &unlock_file("$config{'daemons_dir'}/$_[0]");
971 elsif (&has_command("chkconfig") && !$config{'no_chkconfig'} && @chk) {
972 # Call chkconfig to remove the links
973 &system_logged("chkconfig ".quotemeta($_[0])." off");
976 # Just unlink the S links
977 foreach my $a (&action_levels('S', $_[0])) {
978 $a =~ /^(\S+)\s+(\S+)\s+(\S+)$/;
979 &delete_rl_action($_[0], $1, 'S');
983 # Take out the K links as well, since we know how to put
984 # them back from the chkconfig info
985 foreach my $a (&action_levels('K', $_[0])) {
986 $a =~ /^(\S+)\s+(\S+)\s+(\S+)$/;
987 &delete_rl_action($_[0], $1, 'K');
992 elsif ($init_mode eq "local") {
993 # Take out of rc.local file
994 local $lref = &read_file_lines($config{'local_script'});
995 local $cmd = "$module_config_directory/$_[0].sh start";
997 for($i=0; $i<@$lref; $i++) {
998 if ($lref->[$i] eq $cmd) {
999 splice(@$lref, $i, 1);
1003 if ($config{'local_down'}) {
1004 # Take out of shutdown script
1005 $lref = &read_file_lines($config{'local_down'});
1006 local $cmd = "$module_config_directory/$_[0].sh stop";
1007 for($i=0; $i<@$lref; $i++) {
1008 if ($lref->[$i] eq $cmd) {
1009 splice(@$lref, $i, 1);
1014 &flush_file_lines();
1016 elsif ($init_mode eq "win32") {
1017 # Disable the service
1018 &disable_win32_service($_[0]);
1020 elsif ($init_mode eq "rc") {
1021 # Disable an RC script
1023 &disable_rc_script($_[0]);
1026 elsif ($init_mode eq "osx") {
1027 # Disable in hostconfig
1028 local $ucname = uc($_[0]);
1030 &lock_file($config{'hostconfig'});
1031 &read_env_file($config{'hostconfig'}, \%hc);
1032 if ($hc{$ucname} eq '-YES-' || $hc{$ucname} eq '-AUTOMATIC-') {
1033 $hc{$ucname} = '-NO-';
1034 &write_env_file($config{'hostconfig'}, \%hc);
1036 &unlock_file($config{'hostconfig'});
1040 =head2 start_action(name)
1042 Start the action with the given name, using whatever method is appropriate
1043 for this operating system. Returns a status code (0 or 1 for failure or
1044 success) and all output from the action script.
1050 if ($init_mode eq "init" || $init_mode eq "local") {
1051 # Run the init script or Webmin-created wrapper
1052 local $fn = $init_mode eq "init" ? &action_filename($name) :
1053 "$module_config_directory/$name.sh";
1055 return (0, "$fn does not exist");
1057 local $temp = &transname();
1058 &foreign_require("proc", "proc-lib.pl");
1059 open(TEMP, ">$temp");
1060 &proc::safe_process_exec_logged("$fn start", 0, 0, TEMP);
1063 local $out = &read_file_contents($temp);
1064 return (!$ex, $out);
1066 elsif ($init_mode eq "rc") {
1067 # Run FreeBSD RC script
1068 return &start_rc_script($name);
1070 elsif ($init_mode eq "win32") {
1071 # Start Windows service
1072 local $err = &start_win32_service($name);
1073 return (!$err, $err);
1075 elsif ($init_mode eq "upstart") {
1076 # Run upstart action
1077 return &start_upstart_service($name);
1080 return (0, "Bootup mode $init_mode not supported");
1084 =head2 stop_action(name)
1086 Stop the action with the given name, using whatever method is appropriate
1087 for this operating system. Returns a status code (0 or 1 for failure or
1088 success) and all output from the action script.
1094 if ($init_mode eq "init" || $init_mode eq "local") {
1095 # Run the init script or Webmin-created wrapper
1096 local $fn = $init_mode eq "init" ? &action_filename($name) :
1097 "$module_config_directory/$name.sh";
1099 return (0, "$fn does not exist");
1101 local $temp = &transname();
1102 &foreign_require("proc", "proc-lib.pl");
1103 open(TEMP, ">$temp");
1104 &proc::safe_process_exec_logged("$fn stop", 0, 0, TEMP);
1107 local $out = &read_file_contents($temp);
1108 return (!$ex, $out);
1110 elsif ($init_mode eq "rc") {
1111 # Run FreeBSD RC script
1112 return &stop_rc_script($name);
1114 elsif ($init_mode eq "win32") {
1115 # Start Windows service
1116 local $err = &stop_win32_service($name);
1117 return (!$err, $err);
1119 elsif ($init_mode eq "upstart") {
1120 # Stop upstart action
1121 return &stop_upstart_service($name);
1124 return (0, "Bootup mode $init_mode not supported");
1128 =head2 restart_action(action)
1130 Calls a stop then a start for some named action.
1136 &stop_action($name);
1137 &start_action($name);
1140 =head2 tab_indent(lines)
1142 Given a string with multiple \n separated lines, returns the same string
1143 with lines prefixed by tabs.
1149 foreach $l (split(/\n/, $_[0])) {
1155 =head2 get_start_runlevels
1157 Returns a list of runlevels that actions should be started in, either based
1158 on the module configuration or /etc/inittab.
1161 sub get_start_runlevels
1163 if ($config{'boot_levels'}) {
1164 return split(/[ ,]+/, $config{'boot_levels'});
1167 local @boot = &get_inittab_runlevel();
1168 return ( $boot[0] );
1172 =head2 runlevel_dir(runlevel)
1174 Given a runlevel like 3, returns the directory containing symlinks for it,
1180 if ($_[0] eq "boot") {
1181 return "$config{init_base}/boot.d";
1184 return "$config{init_base}/rc$_[0].d";
1188 =head2 list_win32_services([name])
1190 Returns a list of known Win32 services, each of which is a hash ref. If the
1191 name parameter is given, only details of that service are returned. Useful
1192 keys for each hash are :
1194 =item name - A unique name for the service.
1196 =item desc - A human-readable description.
1198 =item boot - Set to 2 if started at boot, 3 if not, 4 if disabled.
1200 =item state -Set to 4 if running now, 1 if stopped.
1203 sub list_win32_services
1209 # Get the current statuses
1211 &open_execute_command(SC, "sc query $name", 1, 1);
1214 &open_execute_command(SC, "sc query type= service state= all", 1, 1);
1218 if (/^SERVICE_NAME:\s+(\S.*\S)/) {
1219 $svc = { 'name' => $1 };
1222 elsif (/^DISPLAY_NAME:\s+(\S.*)/ && $svc) {
1223 $svc->{'desc'} = $1;
1225 elsif (/^\s+TYPE\s+:\s+(\d+)\s+(\S+)/ && $svc) {
1226 $svc->{'type'} = $1;
1227 $svc->{'type_desc'} = $2;
1229 elsif (/^\s+STATE\s+:\s+(\d+)\s+(\S+)/ && $svc) {
1230 $svc->{'state'} = $1;
1231 $svc->{'state_desc'} = $2;
1236 # For each service, see if it starts at boot or not
1237 foreach $svc (@rv) {
1238 &open_execute_command(SC, "sc qc \"$svc->{'name'}\"", 1, 1);
1241 if (/^\s+START_TYPE\s+:\s+(\d+)\s+(\S+)/) {
1242 $svc->{'boot'} = $1;
1243 $svc->{'boot_desc'} = $2;
1252 =head2 start_win32_service(name)
1254 Attempts to start a service, returning undef on success, or some error message.
1257 sub start_win32_service
1260 local $out = &backquote_command("sc start \"$name\" 2>&1");
1261 return $? ? $out : undef;
1264 =head2 stop_win32_service(name)
1266 Attempts to stop a service, returning undef on success, or some error message.
1269 sub stop_win32_service
1272 local $out = &backquote_command("sc stop \"$name\" 2>&1");
1273 return $? ? $out : undef;
1276 =head2 enable_win32_service(name)
1278 Marks some service as starting at boot time. Returns undef on success or an
1279 error message on failure.
1282 sub enable_win32_service
1285 local $out = &backquote_command("sc config \"$name\" start= auto 2>&1");
1286 return $? ? $out : undef;
1289 =head2 disable_win32_service(name)
1291 Marks some service as disabled at boot time. Returns undef on success or an
1292 error message on failure.
1295 sub disable_win32_service
1298 local $out = &backquote_command("sc config \"$name\" start= demand 2>&1");
1299 return $? ? $out : undef;
1302 =head2 create_win32_service(name, command, desc)
1304 Creates a new win32 service, enabled at boot time. The required parameters are:
1305 name - A unique name for the service
1306 command - The DOS command to run at boot time
1307 desc - A human-readable description.
1310 sub create_win32_service
1312 local ($name, $cmd, $desc) = @_;
1313 local $out = &backquote_command("sc create \"$name\" DisplayName= \"$desc\" type= share start= auto binPath= \"$cmd\" 2>&1");
1314 return $? ? $out : undef;
1317 =head2 delete_win32_service(name)
1319 Delete some existing service, identified by some name. Returns undef on
1320 success or an error message on failure.
1323 sub delete_win32_service
1326 local $out = &backquote_command("sc delete \"$name\" 2>&1");
1327 return $? ? $out : undef;
1330 =head2 list_rc_scripts
1332 Returns a list of known BSD RC scripts, and their enabled statuses. Each
1333 element of the return list is a hash ref, with the following keys :
1335 =item name - A unique name for the script.
1337 =item desc - A human-readable description.
1339 =item enabled - Set to 1 if enabled, 0 if not, 2 if unknown.
1341 =item file - Full path to the action script file.
1343 =item standard - Set to 0 for user-defined actions, 1 for those supplied with FreeBSD.
1348 # Build a list of those that are enabled in the rc.conf files
1349 local @rc = &get_rc_conf();
1350 local (%enabled, %cmt);
1351 foreach my $r (@rc) {
1352 if ($r->{'name'} =~ /^(\S+)_enable$/) {
1354 if (lc($r->{'value'}) eq 'yes') {
1355 $enabled{$name} = 1;
1357 $r->{'cmt'} =~ s/\s*\(\s*or\s+NO\)//i;
1358 $r->{'cmt'} =~ s/\s*\(YES.*NO\)//i;
1359 $cmt{$name} ||= $r->{'cmt'};
1363 # Scan the script dirs
1365 foreach my $dir (split(/\s+/, $config{'rc_dir'})) {
1367 foreach my $f (readdir(DIR)) {
1368 next if ($f =~ /^\./ || $f =~ /\.(bak|tmp)/i);
1369 next if (uc($f) eq $f); # Dummy actions are upper-case
1372 local $data = &read_file_contents("$dir/$f");
1373 local $ename = $name;
1375 push(@rv, { 'name' => $name,
1376 'file' => "$dir/$f",
1377 'enabled' => $data !~ /rc\.subr/ ? 2 :
1379 'startstop' => $data =~ /rc\.subr/ ||
1381 'desc' => $cmt{$name},
1382 'standard' => ($dir !~ /local/)
1387 return sort { $a->{'name'} cmp $b->{'name'} } @rv;
1390 =head2 save_rc_conf(name, value)
1392 Internal function to modify the value of a single entry in the FreeBSD
1399 local @rcs = split(/\s+/, $config{'rc_conf'});
1400 local $rcfile = $rcs[$#rcs];
1401 &open_readfile(CONF, $rcfile);
1402 local @conf = <CONF>;
1404 &open_tempfile(CONF, ">$rcfile");
1406 if (/^\s*([^=]+)\s*=\s*(.*)/ && $1 eq $_[0]) {
1407 &print_tempfile(CONF, "$_[0]=\"$_[1]\"\n") if (@_ > 1);
1411 &print_tempfile(CONF, $_);
1414 if (!$found && @_ > 1) {
1415 &print_tempfile(CONF, "$_[0]=\"$_[1]\"\n");
1417 &close_tempfile(CONF);
1422 Reads the default and system-specific FreeBSD rc.conf files, and parses
1423 them into a list of hash refs. Each element in the list has the following keys:
1425 =item name - Name of this configuration parameter. May appear more than once, with the later one taking precedence.
1427 =item value - Current value.
1429 =item cmt - A human-readable comment about the parameter.
1435 foreach $file (map { glob($_) } split(/\s+/, $config{'rc_conf'})) {
1437 &open_readfile(FILE, $file);
1444 if (/^\s*([^=\s]+)\s*=\s*"(.*)"/ ||
1445 /^\s*([^=\s]+)\s*=\s*'(.*)'/ ||
1446 /^\s*([^=\s]+)\s*=\s*(\S+)/) {
1447 push(@rv, { 'name' => $1,
1460 =head2 enable_rc_script(name)
1462 Mark some RC script as enabled at boot.
1465 sub enable_rc_script
1469 &save_rc_conf($name."_enable", "YES");
1472 =head2 disable_rc_script(name)
1474 Mark some RC script as disabled at boot.
1477 sub disable_rc_script
1482 foreach my $r (&get_rc_conf()) {
1483 if ($r->{'name'} eq $name."_enable" &&
1484 lc($r->{'value'}) eq 'yes') {
1488 &save_rc_conf($name."_enable", "NO") if ($enabled);
1491 =head2 start_rc_script(name)
1493 Attempt to start some RC script, and returns 1 or 0 (for success or failure)
1500 local @rcs = &list_rc_scripts();
1501 local ($rc) = grep { $_->{'name'} eq $name } @rcs;
1502 $rc || return "No script found for $name";
1503 local $out = &backquote_logged("$rc->{'file'} forcestart 2>&1 </dev/null");
1507 =head2 stop_rc_script(name)
1509 Attempts to stop some RC script, and returns 1 or 0 (for success or failure)
1516 local @rcs = &list_rc_scripts();
1517 local ($rc) = grep { $_->{'name'} eq $name } @rcs;
1518 $rc || return "No script found for $name";
1519 local $out = &backquote_logged("$rc->{'file'} forcestop 2>&1 </dev/null");
1523 =head2 delete_rc_script(name)
1525 Delete the FreeBSD RC script with some name
1528 sub delete_rc_script
1531 my @rcs = &list_rc_scripts();
1532 my ($rc) = grep { $_->{'name'} eq $name } @rcs;
1535 &disable_rc_script($in{'name'});
1537 &unlink_logged($rc->{'file'});
1541 =head2 lock_rc_files
1543 Internal function to lock all FreeBSD rc.conf files.
1548 foreach my $f (split(/\s+/, $config{'rc_conf'})) {
1553 =head2 unlock_rc_files
1555 Internal function to un-lock all FreeBSD rc.conf files.
1560 foreach my $f (split(/\s+/, $config{'rc_conf'})) {
1565 =head2 list_upstart_services
1567 Returns a list of all known upstart services, each of which is a hash ref
1568 with 'name', 'desc', 'boot', 'status' and 'pid' keys.
1571 sub list_upstart_services
1573 # Start with native upstart services
1575 my $out = &backquote_command("initctl list");
1577 foreach my $l (split(/\r?\n/, $out)) {
1578 if ($l =~ /^(\S+)\s+(start|stop)\/([a-z]+)/) {
1579 my $s = { 'name' => $1,
1582 if ($l =~ /process\s+(\d+)/) {
1585 open(CONF, "/etc/init/$s->{'name'}.conf");
1587 if (/^description\s+"([^"]+)"/ && !$s->{'desc'}) {
1590 elsif (/^(#*)\s*start/ && !$s->{'boot'}) {
1591 $s->{'boot'} = $1 ? 'stop' : 'start';
1596 $done{$s->{'name'}} = 1;
1600 # Also add legacy init scripts
1601 my @rls = &get_inittab_runlevel();
1602 foreach my $a (&list_actions()) {
1604 next if ($done{$a});
1605 my $f = &action_filename($a);
1606 my $s = { 'name' => $a,
1608 $s->{'boot'} = 'stop';
1609 foreach my $rl (@rls) {
1610 my $l = glob("/etc/rc$rl.d/S*$a");
1611 $s->{'boot'} = 'start' if ($l);
1613 $s->{'desc'} = &init_description($f);
1614 my $hasarg = &get_action_args($f);
1615 if ($hasarg->{'status'}) {
1616 my $r = &action_running($f);
1618 $s->{'status'} = 'waiting';
1621 $s->{'status'} = 'running';
1627 return sort { $a->{'name'} cmp $b->{'name'} } @rv;
1630 =head2 start_upstart_service(name)
1632 Run the upstart service with some name, and return an OK flag and output
1635 sub start_upstart_service
1638 my $out = &backquote_logged(
1639 "service ".quotemeta($name)." start 2>&1 </dev/null");
1643 =head2 stop_upstart_service(name)
1645 Shut down the upstop service with some name, and return an OK flag and output
1648 sub stop_upstart_service
1651 my $out = &backquote_logged(
1652 "service ".quotemeta($name)." stop 2>&1 </dev/null");
1656 =head2 restart_upstart_service(name)
1658 Restart the upstart service with some name, and return an OK flag and output
1661 sub restart_upstart_service
1664 my $out = &backquote_logged(
1665 "service ".quotemeta($name)." restart 2>&1 </dev/null");
1669 =head2 create_upstart_service(name, description, command, [pre-script], [fork])
1671 Create a new upstart service with the given details.
1674 sub create_upstart_service
1676 my ($name, $desc, $server, $prestart, $forks) = @_;
1677 my $cfile = "/etc/init/$name.conf";
1678 &open_lock_tempfile(CFILE, ">$cfile");
1679 &print_tempfile(CFILE,
1684 "description \"$desc\"\n".
1686 "start on runlevel [2345]\n".
1687 "stop on runlevel [!2345]\n".
1691 &print_tempfile(CFILE,
1697 &print_tempfile(CFILE,
1698 "pre-start script\n".
1701 split(/\n/, $prestart))."\n".
1705 &print_tempfile(CFILE, "exec ".$server."\n");
1706 &close_tempfile(CFILE);
1709 =head2 delete_upstart_service(name)
1711 Delete all traces of some upstart service
1714 sub delete_upstart_service
1717 &system_logged("insserv -r ".quotemeta($name)." >/dev/null 2>&1");
1718 my $cfile = "/etc/init/$name.conf";
1719 my $ifile = "/etc/init.d/$name";
1720 &unlink_logged($cfile, $ifile);
1723 =head2 reboot_system
1725 Immediately reboots the system.
1730 &system_logged("$config{'reboot_command'} >$null_file 2>$null_file");
1733 =head2 shutdown_system
1735 Immediately shuts down the system.
1740 &system_logged("$config{'shutdown_command'} >$null_file 2>$null_file");
1743 # get_action_args(filename)
1744 # Returns the args that this action script appears to support, like stop, start
1752 if (/^\s*(['"]?)([a-z]+)\1\)/i) {
1760 # action_running(filename)
1761 # Assuming some init.d action supports the status parameter, returns a 1 if
1762 # running, 0 if not, or -1 if unknown
1766 my $out = &backquote_command("$file status");
1767 if ($out =~ /not\s+running/i ||
1768 $out =~ /no\s+server\s+running/i) {
1771 elsif ($out =~ /running/i) {
1774 elsif ($out =~ /stopped/i) {