Handle hostnames with upper-case letters
[webmin.git] / init / init-lib.pl
1 =head1 init-lib.pl
2
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.
8
9 Example code :
10
11  foreign_require('init', 'init-lib.pl');
12  $ok = init::action_status('foo');
13  if ($ok == 0) {
14    init::enable_at_boot('foo', 'Start or stop the Foo server',
15                         '/etc/foo/start', '/etc/foo/stop');
16  }
17
18 =cut
19
20 BEGIN { push(@INC, ".."); };
21 use WebminCore;
22 &init_config();
23 @action_buttons = ( 'start', 'restart', 'condrestart', 'reload', 'status',
24                     'stop' );
25 %access = &get_module_acl();
26
27 =head2 init_mode
28
29 This variable is set based on the bootup system in use. Possible values are :
30
31 =item osx - MacOSX hostconfig files
32
33 =item rc - FreeBSD 6+ RC files
34
35 =item init - System V init.d files, seen on Linux and Solaris
36
37 =item local - A single rc.local file
38
39 =item win32 - Windows services
40
41 =cut
42 if ($config{'hostconfig'}) {
43         $init_mode = "osx";
44         }
45 elsif ($config{'rc_dir'}) {
46         $init_mode = "rc";
47         }
48 elsif ($config{'init_base'} && -d "/etc/init" &&
49        &has_command("insserv") && &has_command("initctl")) {
50         $init_mode = "upstart";
51         }
52 elsif ($config{'init_base'}) {
53         $init_mode = "init";
54         }
55 elsif ($config{'local_script'}) {
56         $init_mode = "local";
57         }
58 elsif ($gconfig{'os_type'} eq 'windows') {
59         $init_mode = "win32";
60         }
61
62 =head2 runlevel_actions(level, S|K)
63
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
66
67 =cut
68 sub runlevel_actions
69 {
70 local($dir, $f, @stbuf, @rv);
71 $dir = &runlevel_dir($_[0]);
72 opendir(DIR, $dir);
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]");
77         }
78 closedir(DIR);
79 @rv = sort { @a = split(/\s/,$a); @b = split(/\s/,$b); $a[0] <=> $b[0]; } @rv;
80 return $_[1] eq "S" ? @rv : reverse(@rv);
81 }
82
83
84 =head2 list_runlevels
85
86 Returns a list of known runlevels, such as : 2 3 5.
87
88 =cut
89 sub list_runlevels
90 {
91 local(@rv);
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; }
96                 push(@rv, $1);
97                 }
98         }
99 closedir(DIR);
100 return sort(@rv);
101 }
102
103
104 =head2 list_actions
105
106 List boot time action names from init.d, such as httpd and cron.
107
108 =cut
109 sub list_actions
110 {
111 local($dir, $f, @stbuf, @rv);
112 $dir = $config{init_dir};
113 opendir(DIR, $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]");
122                 }
123         }
124 closedir(DIR);
125 foreach $f (split(/\s+/, $config{'extra_init'})) {
126         if (@stbuf = stat($f)) {
127                 push(@rv, "$f $stbuf[1]");
128                 }
129         }
130 return @rv;
131 }
132
133
134 =head2 action_levels(S|K, action)
135
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
138
139 =cut
140 sub action_levels
141 {
142 local(@stbuf, $rl, $dir, $f, @stbuf2, @rv);
143 @stbuf = stat(&action_filename($_[1]));
144 foreach $rl (&list_runlevels()) {
145         $dir = &runlevel_dir($rl);
146         opendir(DIR, $dir);
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");
152                                 last;
153                                 }
154                         }
155                 }
156         closedir(DIR);
157         }
158 return @rv;
159 }
160
161
162 =head2 action_filename(name)
163
164 Returns the path to the file in init.d for some action, such as /etc/init.d/foo.
165
166 =cut
167 sub action_filename
168 {
169 return $_[0] =~ /^\// ? $_[0] : "$config{init_dir}/$_[0]";
170 }
171
172 =head2 runlevel_filename(level, S|K, order, name)
173
174 Returns the path to the actual script run at boot for some action, such as
175 /etc/rc3.d/S99foo.
176
177 =cut
178 sub runlevel_filename
179 {
180 local $n = $_[3];
181 $n =~ s/^(.*)\///;
182 return &runlevel_dir($_[0])."/$_[1]$_[2]$n";
183 }
184
185
186 =head2 add_rl_action(action, runlevel, S|K, order)
187
188 Add some existing action to a runlevel. The parameters are :
189
190 =item action - Name of the action, like foo
191
192 =item runlevel - A runlevel number, like 3
193
194 =item S|K - Either S for an action to run at boot, or K for shutdown
195
196 =item order - Numeric boot order, like 99
197
198 =cut
199 sub add_rl_action
200 {
201 $file = &runlevel_filename($_[1], $_[2], $_[3], $_[0]);
202 while(-r $file) {
203         if ($file =~ /^(.*)_(\d+)$/) { $file = "$1_".($2+1); }
204         else { $file = $file."_1"; }
205         }
206 &lock_file($file);
207 if ($config{soft_links}) {
208         &symlink_file(&action_filename($_[0]), $file);
209         }
210 else {
211         &link_file(&action_filename($_[0]), $file);
212         }
213 &unlock_file($file);
214 }
215
216
217 =head2 delete_rl_action(name, runlevel, S|K)
218
219 Delete some action from a runlevel. The parameters are :
220
221 =item action - Name of the action, like foo.
222
223 =item runlevel - A runlevel number, like 3.
224
225 =item S|K - Either S for an action to run at boot, or K for shutdown.
226
227 =cut
228 sub delete_rl_action
229 {
230 local(@stbuf, $dir, $f, @stbuf2);
231 @stbuf = stat(&action_filename($_[0]));
232 $dir = &runlevel_dir($_[1]);
233 opendir(DIR, $dir);
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");
240                         last;
241                         }
242                 }
243         }
244 closedir(DIR);
245 }
246
247
248 =head2 reorder_rl_action(name, runlevel, S|K, new_order)
249
250 Change the boot order of some existing runlevel action. The parameters are :
251
252 =item action - Name of the action, like foo.
253
254 =item runlevel - A runlevel number, like 3.
255
256 =item S|K - Either S for an action to run at boot, or K for shutdown.
257
258 =item new_order - New numeric boot order to use, like 99.
259
260 =cut
261 sub reorder_rl_action
262 {
263 local(@stbuf, $dir, $f, @stbuf2);
264 @stbuf = stat(&action_filename($_[0]));
265 $dir = &runlevel_dir($_[1]);
266 opendir(DIR, $dir);
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";
273                         while(-r $file) {
274                                 if ($file =~ /^(.*)_(\d+)$/)
275                                         { $file = "$1_".($2+1); }
276                                 else { $file = $file."_1"; }
277                                 }
278                         &rename_logged("$dir/$f", $file);
279                         last;
280                         }
281                 }
282         }
283 closedir(DIR);
284 }
285
286
287 =head2 rename_action(old, new)
288
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 :
291
292 =item old - Old action name.
293
294 =item new - New action name.
295
296 =cut
297 sub rename_action
298 {
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
305                 &lock_file($file);
306                 &unlink_file($file);
307                 &symlink_file("$config{init_dir}/$_[1]", $file);
308                 &unlock_file($file);
309                 }
310         if (($idx = index($file, $_[0])) != -1) {
311                 $old = $file;
312                 substr($file, $idx, length($_[0])) = $_[1];
313                 &rename_logged($old, $file);
314                 }
315         }
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
321                 &lock_file($file);
322                 &unlink_file($file);
323                 &symlink_file("$config{init_dir}/$_[1]", $file);
324                 &unlock_file($file);
325                 }
326         if (($idx = index($file, $_[0])) != -1) {
327                 $old = $file;
328                 substr($file, $idx, length($_[0])) = $_[1];
329                 &rename_logged($old, $file);
330                 }
331         }
332 &rename_logged("$config{init_dir}/$_[0]", "$config{init_dir}/$_[1]");
333 }
334
335
336 =head2 rename_rl_action(runlevel, S|K, order, old, new)
337
338 Change the name of a runlevel file. For internal use only.
339
340 =cut
341 sub rename_rl_action
342 {
343 &rename_logged(&runlevel_dir($_[0])."/$_[1]$_[2]$_[3]",
344                &runlevel_dir($_[0])."/$_[1]$_[2]$_[4]");
345 }
346
347 =head2 get_inittab_runlevel
348
349 Returns the runlevels entered at boot time. If more than one is returned,
350 actions from all of them are used.
351
352 =cut
353 sub get_inittab_runlevel
354 {
355 local %iconfig = &foreign_config("inittab");
356 local @rv;
357 local $id = $config{'inittab_id'};
358 if (open(TAB, $iconfig{'inittab_file'})) {
359         # Read the inittab file
360         while(<TAB>) {
361                 if (/^$id:(\d+):/ && $1) { @rv = ( $1 ); }
362                 }
363         close(TAB);
364         }
365
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+)/) {
370                 push(@rv, $2);
371                 }
372         }
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+)/) {
377                 push(@rv, $1);
378                 }
379         }
380
381 # Add statically configured runlevels
382 if ($config{"inittab_rl_$rv[0]"}) {
383         @rv = split(/,/, $config{"inittab_rl_$rv[0]"});
384         }
385 push(@rv, $config{'inittab_extra'});
386 return &unique(@rv);
387 }
388
389 =head2 init_description(file, [&hasargs])
390
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'.
394
395 =cut
396 sub init_description
397 {
398 # Read contents of script, extract start/stop commands
399 open(FILE, $_[0]);
400 local @lines = <FILE>;
401 close(FILE);
402 local $data = join("", @lines);
403 if ($_[1]) {
404         foreach (@lines) {
405                 if (/^\s*(['"]?)([a-z]+)\1\)/i) {
406                         $_[1]->{$2}++;
407                         }
408                 }
409         }
410
411 local $desc;
412 if ($config{'daemons_dir'}) {
413         # First try the daemons file
414         local %daemon;
415         if ($_[0] =~ /\/([^\/]+)$/ &&
416             &read_env_file("$config{'daemons_dir'}/$1", \%daemon) &&
417             $daemon{'DESCRIPTIVE'}) {
418                 return $daemon{'DESCRIPTIVE'};
419                 }
420         }
421 if ($config{'chkconfig'}) {
422         # Find the redhat-style description: section
423         foreach (@lines) {
424                 s/\r|\n//g;
425                 if (/^#+\s*description:(.*?)(\\?$)/) {
426                         $desc = $1;
427                         }
428                 elsif (/^#+\s*(.*?)(\\?$)/ && $desc && $1) {
429                         $desc .= "\n".$1;
430                         }
431                 if ($desc && !$2) {
432                         last;
433                         }
434                 }
435         }
436 elsif ($config{'init_info'} || $data =~ /BEGIN INIT INFO/) {
437         # Find the suse-style Description: line
438         foreach (@lines) {
439                 s/\r|\n//g;
440                 if (/^#\s*(Description|Short-Description):\s*(.*)/) {
441                         $desc = $2;
442                         }
443                 }
444         }
445 else {
446         # Use the first comments
447         foreach (@lines) {
448                 s/\r|\n//g;
449                 next if (/^#!\s*\/(bin|sbin|usr)\// || /\$id/i || /^#+\s+@/ ||
450                          /source function library/i || /^#+\s*copyright/i);
451                 if (/^#+\s*(.*)/) {
452                         last if ($desc && !$1);
453                         $desc .= $1."\n" if ($1);
454                         }
455                 elsif (/\S/) { last; }
456                 }
457         $_[0] =~ /\/([^\/]+)$/;
458         $desc =~ s/^Tag\s+(\S+)\s*//i;
459         $desc =~ s/^\s*$1\s+//;
460         }
461 return $desc;
462 }
463
464 =head2 chkconfig_info(file)
465
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.
469
470 =cut
471 sub chkconfig_info
472 {
473 local @rv;
474 local $desc;
475 open(FILE, $_[0]);
476 while(<FILE>) {
477         if (/^#\s*chkconfig:\s+(\S+)\s+(\d+)\s+(\d+)/) {
478                 @rv = ( $1 eq '-' ? [ ] : [ split(//, $1) ], $2, $3 );
479                 }
480         elsif (/^#\s*description:\s*(.*)/) {
481                 $desc = $1;
482                 }
483         }
484 close(FILE);
485 $rv[3] = $desc if ($desc && @rv);
486 return @rv;
487 }
488
489 =head2 action_status(action)
490
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.
494
495 =cut
496 sub action_status
497 {
498 if ($init_mode eq "upstart") {
499         # Check service status
500         local $out = &backquote_command("initctl status ".
501                                         quotemeta($_[0])." 2>&1");
502         if (!$?) {
503                 my $cfile = "/etc/init/$_[0].conf";
504                 open(CONF, $cfile);
505                 while(<CONF>) {
506                         if (/^(#*)\s*start/) {
507                                 return $1 ? 1 : 2;
508                                 }
509                         }
510                 close(CONF);
511                 return 1;       # Should never happen
512                 }
513         }
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]) {
520                         $exists++;
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);
525                                 }
526                         }
527                 }
528         if ($starting && $config{'daemons_dir'} &&
529             &read_env_file("$config{'daemons_dir'}/$_[0]", \%daemon)) {
530                 $starting = lc($daemon{'ONBOOT'}) eq 'yes' ? 1 : 0;
531                 }
532         return !$exists ? 0 : $starting ? 2 : 1;
533         }
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'});
539         while(<LOCAL>) {
540                 s/\r|\n//g;
541                 $found++ if ($_ eq $cmd);
542                 }
543         close(LOCAL);
544         return $found && -r $fn ? 2 : -r $fn ? 1 : 0;
545         }
546 elsif ($init_mode eq "win32") {
547         # Look for a win32 service, enabled at boot
548         local ($svc) = &list_win32_services($_[0]);
549         return !$svc ? 0 :
550                $svc->{'boot'} == 2 ? 2 : 1;
551         }
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;
556         return !$rc ? 0 :
557                $rc->{'enabled'} ? 2 : 1;
558         }
559 elsif ($init_mode eq "osx") {
560         # Look for a hostconfig entry
561         local $ucname = uc($_[0]);
562         local %hc;
563         &read_env_file($config{'hostconfig'}, \%hc);
564         return $hc{$ucname} eq '-YES-' ? 2 :
565                $hc{$ucname} eq '-NO-' ? 1 : 0;
566         }
567 }
568
569 =head2 enable_at_boot(action, description, startcode, stopcode, statuscode, &opts)
570
571 Makes some action start at boot time, creating the script by copying the
572 specified file if necessary. The parameters are :
573
574 =item action - Name of the action to create or enable.
575
576 =item description - A human-readable description for the action.
577
578 =item startcode - Shell commands to run at boot time.
579
580 =item stopcode - Shell commands to run at shutdown time.
581
582 =item statuscode - Shell code to output the action's status.
583
584 =item opts - Hash ref of additional options, like : fork -> server will fork into background
585
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.
588
589 =cut
590 sub enable_at_boot
591 {
592 local $st = &action_status($_[0]);
593 return if ($st == 2);   # already starting!
594 local ($daemon, %daemon);
595
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
599         # init script
600         my $cfile = "/etc/init/$_[0].conf";
601         if (-r $cfile) {
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);
605                 my $foundstart;
606                 foreach my $l (@$lref) {
607                         if ($l =~ /^#+start/) {
608                                 # Start of start block
609                                 $l =~ s/^#+//;
610                                 $foundstart = 1;
611                                 }
612                         elsif ($l =~ /^#+\s+\S/ && $foundstart) {
613                                 # Continuation line for start
614                                 $l =~ s/^#+//;
615                                 }
616                         elsif ($l =~ /^\S/ && $foundstart) {
617                                 # Some other directive after start
618                                 last;
619                                 }
620                         }
621                 &flush_file_lines($cfile);
622                 }
623         else {
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,
628                                         $_[5]->{'fork'});
629                 &system_logged("insserv ".quotemeta($_[0])." >/dev/null 2>&1");
630                 }
631         return;
632         }
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)) {
637                 $daemon++;
638                 }
639         local $fn;
640         if ($init_mode eq "init" || $init_mode eq "upstart") {
641                 # Normal init.d system
642                 $fn = &action_filename($_[0]);
643                 }
644         else {
645                 # Need to create hack init script
646                 $fn = "$module_config_directory/$_[0].sh";
647                 }
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'};
652         local @stop;
653         if (@chk) {
654                 local %starting = map { $_, 1 } @start;
655                 @stop = grep { !$starting{$_} && /^\d+$/ } &list_runlevels();
656                 }
657
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]");
665                 }
666         elsif ($st == 1) {
667                 # Just need to create links (later)
668                 $need_links++;
669                 }
670         elsif ($_[1]) {
671                 # Need to create the init script
672                 &lock_file($fn);
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");
680                         }
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",
688                                      "# Default-Stop:\n",
689                                      "# Description: $_[1]\n",
690                                      "### END INIT INFO\n");
691                         }
692                 else {
693                         &print_tempfile(ACTION, "# $_[1]\n");
694                         }
695                 &print_tempfile(ACTION, "\n");
696                 &print_tempfile(ACTION, "case \"\$1\" in\n");
697
698                 if ($_[2]) {
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");
706                                 }
707                         &print_tempfile(ACTION, "\t;;\n");
708                         }
709
710                 if ($_[3]) {
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");
718                                 }
719                         &print_tempfile(ACTION, "\t;;\n");
720                         }
721
722                 if ($_[4]) {
723                         &print_tempfile(ACTION, "'status')\n");
724                         &print_tempfile(ACTION, &tab_indent($_[4]));
725                         &print_tempfile(ACTION, "\t;;\n");
726                         }
727
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");
733                         }
734
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);
742                 chmod(0755, $fn);
743                 &unlock_file($fn);
744                 $need_links++;
745                 }
746
747         if ($need_links && ($init_mode eq "init" ||
748                             $init_mode eq "upstart")) {
749                 local $data = &read_file_contents($fn);
750                 my $done = 0;
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");
756                         $done = 1;
757                         }
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]).
762                                        " >/dev/null 2>&1");
763                         $done = 1 if (!$ex);
764                         }
765                 if (!$done) {
766                         # Just link up the init script
767                         local $s;
768                         foreach $s (@start) {
769                                 &add_rl_action($_[0], $s, "S", $start_order);
770                                 }
771                         local @klevels = &action_levels("K", $_[0]);
772                         if (!@klevels) {
773                                 # Only add K scripts if none exist
774                                 foreach $s (@stop) {
775                                         &add_rl_action($_[0], $s, "K", $stop_order);
776                                         }
777                                 }
778                         }
779                 }
780         elsif ($need_links) {
781                 # Just add rc.local entry
782                 local $lref = &read_file_lines($config{'local_script'});
783                 local $i;
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");
792                         }
793                 &flush_file_lines();
794                 }
795         }
796 elsif ($init_mode eq "win32") {
797         # Enable and/or create a win32 service
798         if ($st == 1) {
799                 # Just enable
800                 &enable_win32_service($_[0]);
801                 }
802         else {
803                 # Need to create service, which calls wrapper program
804                 eval "use Win32::Daemon";
805
806                 # modify the string handed over
807                 # so it does not contain backslashes ...
808                 $_[2] =~ s/\\/\//g;
809
810                 local $perl_path = &get_perl_path();
811                 local %svc = ( 'name' => $_[0],
812                          'display' => $_[1],
813                          'path' => $perl_path,
814                          'user' => '',
815                          'description' => "OCM Webmin Pro Service",
816                          'pwd' => $module_root_directory,
817                          'parameters' => "\"$module_root_directory/win32.pl\" $_[2]",
818                         );
819                 if (!Win32::Daemon::CreateService(\%svc)) {
820                         print STDERR "Failed to create Win32 service : ",
821                              Win32::FormatMessage(Win32::Daemon::GetLastError()),"\n";
822                         }
823                 }
824         }
825 elsif ($init_mode eq "rc") {
826         # Enable and/or create an RC script
827         &lock_rc_files();
828         if ($st == 1) {
829                 # Just enable
830                 &enable_rc_script($_[0]);
831                 }
832         else {
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";
836                 local $name = $_[0];
837                 $name =~ s/-/_/g;
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");
849                 if ($_[3]) {
850                         &print_tempfile(SCRIPT, "stop_cmd=\"$_[3]\"\n")
851                         }
852                 if ($_[4] && $_[4] !~ /\n/) {
853                         &print_tempfile(SCRIPT, "status_cmd=\"$_[4]\"\n")
854                         }
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]);
861                 }
862         &unlock_rc_files();
863         }
864 elsif ($init_mode eq "osx") {
865         # Add hostconfig file entry
866         local $ucname = uc($_[0]);
867         local %hc;
868         &lock_file($config{'hostconfig'});
869         &read_env_file($config{'hostconfig'}, \%hc);
870         if (!$hc{$ucname}) {
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";
876
877                 # Create dirs if missing
878                 if (!-d $config{'darwin_setup'}) {
879                         &make_dir($config{'darwin_setup'}, 0755);
880                         }
881                 if (!-d $dir) {
882                         &make_dir($dir, 0755);
883                         }
884
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);
899
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);
910                 }
911
912         # Update hostconfig file
913         $hc{$ucname} = '-YES-';
914         &write_env_file($config{'hostconfig'}, \%hc);
915         &unlock_file($config{'hostconfig'});
916         }
917 }
918
919 =head2 disable_at_boot(action)
920
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.
924
925 =cut
926 sub disable_at_boot
927 {
928 local $st = &action_status($_[0]);
929 return if ($st != 2);   # not currently starting
930
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";
935         if (-r $cfile) {
936                 my $lref = &read_file_lines($cfile);
937                 my $foundstart;
938                 foreach my $l (@$lref) {
939                         if ($l =~ /^start\s/) {
940                                 # Start of start block
941                                 $l = "#".$l;
942                                 $foundstart = 1;
943                                 }
944                         elsif ($l =~ /^\s+\S/ && $foundstart) {
945                                 # Continuation line for start
946                                 $l = "#".$l;
947                                 }
948                         elsif ($l =~ /^\S/ && $foundstart) {
949                                 # Some other directive after start
950                                 last;
951                                 }
952                         }
953                 &flush_file_lines($cfile);
954                 }
955         }
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);
962
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]");
970                 }
971         elsif (&has_command("chkconfig") && !$config{'no_chkconfig'} && @chk) {
972                 # Call chkconfig to remove the links
973                 &system_logged("chkconfig ".quotemeta($_[0])." off");
974                 }
975         else {
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');
980                         }
981
982                 if (@chk) {
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');
988                                 }
989                         }
990                 }
991         }
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";
996         local $i;
997         for($i=0; $i<@$lref; $i++) {
998                 if ($lref->[$i] eq $cmd) {
999                         splice(@$lref, $i, 1);
1000                         last;
1001                         }
1002                 }
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);
1010                                 last;
1011                                 }
1012                         }
1013                 }
1014         &flush_file_lines();
1015         }
1016 elsif ($init_mode eq "win32") {
1017         # Disable the service
1018         &disable_win32_service($_[0]);
1019         }
1020 elsif ($init_mode eq "rc") {
1021         # Disable an RC script
1022         &lock_rc_files();
1023         &disable_rc_script($_[0]);
1024         &unlock_rc_files();
1025         }
1026 elsif ($init_mode eq "osx") {
1027         # Disable in hostconfig
1028         local $ucname = uc($_[0]);
1029         local %hc;
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);
1035                 }
1036         &unlock_file($config{'hostconfig'});
1037         }
1038 }
1039
1040 =head2 start_action(name)
1041
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.
1045
1046 =cut
1047 sub start_action
1048 {
1049 local ($name) = @_;
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";
1054         if (!-x $fn) {
1055                 return (0, "$fn does not exist");
1056                 }
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);
1061         close(TEMP);
1062         local $ex = $?;
1063         local $out = &read_file_contents($temp);
1064         return (!$ex, $out);
1065         }
1066 elsif ($init_mode eq "rc") {
1067         # Run FreeBSD RC script
1068         return &start_rc_script($name);
1069         }
1070 elsif ($init_mode eq "win32") {
1071         # Start Windows service
1072         local $err = &start_win32_service($name);
1073         return (!$err, $err);
1074         }
1075 elsif ($init_mode eq "upstart") {
1076         # Run upstart action
1077         return &start_upstart_service($name);
1078         }
1079 else {
1080         return (0, "Bootup mode $init_mode not supported");
1081         }
1082 }
1083
1084 =head2 stop_action(name)
1085
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.
1089
1090 =cut
1091 sub stop_action
1092 {
1093 local ($name) = @_;
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";
1098         if (!-x $fn) {
1099                 return (0, "$fn does not exist");
1100                 }
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);
1105         close(TEMP);
1106         local $ex = $?;
1107         local $out = &read_file_contents($temp);
1108         return (!$ex, $out);
1109         }
1110 elsif ($init_mode eq "rc") {
1111         # Run FreeBSD RC script
1112         return &stop_rc_script($name);
1113         }
1114 elsif ($init_mode eq "win32") {
1115         # Start Windows service
1116         local $err = &stop_win32_service($name);
1117         return (!$err, $err);
1118         }
1119 elsif ($init_mode eq "upstart") {
1120         # Stop upstart action
1121         return &stop_upstart_service($name);
1122         }
1123 else {
1124         return (0, "Bootup mode $init_mode not supported");
1125         }
1126 }
1127
1128 =head2 restart_action(action)
1129
1130 Calls a stop then a start for some named action.
1131
1132 =cut
1133 sub restart_action
1134 {
1135 local ($name) = @_;
1136 &stop_action($name);
1137 &start_action($name);
1138 }
1139
1140 =head2 tab_indent(lines)
1141
1142 Given a string with multiple \n separated lines, returns the same string
1143 with lines prefixed by tabs.
1144
1145 =cut
1146 sub tab_indent
1147 {
1148 local ($rv, $l);
1149 foreach $l (split(/\n/, $_[0])) {
1150         $rv .= "\t$l\n";
1151         }
1152 return $rv;
1153 }
1154
1155 =head2 get_start_runlevels
1156
1157 Returns a list of runlevels that actions should be started in, either based
1158 on the module configuration or /etc/inittab.
1159
1160 =cut
1161 sub get_start_runlevels
1162 {
1163 if ($config{'boot_levels'}) {
1164         return split(/[ ,]+/, $config{'boot_levels'});
1165         }
1166 else {
1167         local @boot = &get_inittab_runlevel();
1168         return ( $boot[0] );
1169         }
1170 }
1171
1172 =head2 runlevel_dir(runlevel)
1173
1174 Given a runlevel like 3, returns the directory containing symlinks for it,
1175 like /etc/rc2.d.
1176
1177 =cut
1178 sub runlevel_dir
1179 {
1180 if ($_[0] eq "boot") {
1181         return "$config{init_base}/boot.d";
1182         }
1183 else {
1184         return "$config{init_base}/rc$_[0].d";
1185         }
1186 }
1187
1188 =head2 list_win32_services([name])
1189
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 :
1193
1194 =item name - A unique name for the service.
1195
1196 =item desc - A human-readable description.
1197
1198 =item boot - Set to 2 if started at boot, 3 if not, 4 if disabled.
1199
1200 =item state -Set to 4 if running now, 1 if stopped.
1201
1202 =cut
1203 sub list_win32_services
1204 {
1205 local ($name) = @_;
1206 local @rv;
1207 local $svc;
1208
1209 # Get the current statuses
1210 if ($name) {
1211         &open_execute_command(SC, "sc query $name", 1, 1);
1212         }
1213 else {
1214         &open_execute_command(SC, "sc query type= service state= all", 1, 1);
1215         }
1216 while(<SC>) {
1217         s/\r|\n//g;
1218         if (/^SERVICE_NAME:\s+(\S.*\S)/) {
1219                 $svc = { 'name' => $1 };
1220                 push(@rv, $svc);
1221                 }
1222         elsif (/^DISPLAY_NAME:\s+(\S.*)/ && $svc) {
1223                 $svc->{'desc'} = $1;
1224                 }
1225         elsif (/^\s+TYPE\s+:\s+(\d+)\s+(\S+)/ && $svc) {
1226                 $svc->{'type'} = $1;
1227                 $svc->{'type_desc'} = $2;
1228                 }
1229         elsif (/^\s+STATE\s+:\s+(\d+)\s+(\S+)/ && $svc) {
1230                 $svc->{'state'} = $1;
1231                 $svc->{'state_desc'} = $2;
1232                 }
1233         }
1234 close(SC);
1235
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);
1239         while(<SC>) {
1240                 s/\r|\n//g;
1241                 if (/^\s+START_TYPE\s+:\s+(\d+)\s+(\S+)/) {
1242                         $svc->{'boot'} = $1;
1243                         $svc->{'boot_desc'} = $2;
1244                         }
1245                 }
1246         close(SC);
1247         }
1248
1249 return @rv;
1250 }
1251
1252 =head2 start_win32_service(name)
1253
1254 Attempts to start a service, returning undef on success, or some error message.
1255
1256 =cut
1257 sub start_win32_service
1258 {
1259 local ($name) = @_;
1260 local $out = &backquote_command("sc start \"$name\" 2>&1");
1261 return $? ? $out : undef;
1262 }
1263
1264 =head2 stop_win32_service(name)
1265
1266 Attempts to stop a service, returning undef on success, or some error message.
1267
1268 =cut
1269 sub stop_win32_service
1270 {
1271 local ($name) = @_;
1272 local $out = &backquote_command("sc stop \"$name\" 2>&1");
1273 return $? ? $out : undef;
1274 }
1275
1276 =head2 enable_win32_service(name)
1277
1278 Marks some service as starting at boot time. Returns undef on success or an
1279 error message on failure.
1280
1281 =cut
1282 sub enable_win32_service
1283 {
1284 local ($name) = @_;
1285 local $out = &backquote_command("sc config \"$name\" start= auto 2>&1");
1286 return $? ? $out : undef;
1287 }
1288
1289 =head2 disable_win32_service(name)
1290
1291 Marks some service as disabled at boot time. Returns undef on success or an
1292 error message on failure.
1293
1294 =cut
1295 sub disable_win32_service
1296 {
1297 local ($name) = @_;
1298 local $out = &backquote_command("sc config \"$name\" start= demand 2>&1");
1299 return $? ? $out : undef;
1300 }
1301
1302 =head2 create_win32_service(name, command, desc)
1303
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.
1308
1309 =cut
1310 sub create_win32_service
1311 {
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;
1315 }
1316
1317 =head2 delete_win32_service(name)
1318
1319 Delete some existing service, identified by some name. Returns undef on
1320 success or an error message on failure.
1321
1322 =cut
1323 sub delete_win32_service
1324 {
1325 local ($name) = @_;
1326 local $out = &backquote_command("sc delete \"$name\" 2>&1");
1327 return $? ? $out : undef;
1328 }
1329
1330 =head2 list_rc_scripts
1331
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 :
1334
1335 =item name - A unique name for the script.
1336
1337 =item desc - A human-readable description.
1338
1339 =item enabled - Set to 1 if enabled, 0 if not, 2 if unknown.
1340
1341 =item file - Full path to the action script file.
1342
1343 =item standard - Set to 0 for user-defined actions, 1 for those supplied with FreeBSD.
1344
1345 =cut
1346 sub list_rc_scripts
1347 {
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$/) {
1353                 local $name = $1;
1354                 if (lc($r->{'value'}) eq 'yes') {
1355                         $enabled{$name} = 1;
1356                         }
1357                 $r->{'cmt'} =~ s/\s*\(\s*or\s+NO\)//i;
1358                 $r->{'cmt'} =~ s/\s*\(YES.*NO\)//i;
1359                 $cmt{$name} ||= $r->{'cmt'};
1360                 }
1361         }
1362
1363 # Scan the script dirs
1364 local @rv;
1365 foreach my $dir (split(/\s+/, $config{'rc_dir'})) {
1366         opendir(DIR, $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
1370                 local $name = $f;
1371                 $name =~ s/\.sh$//;
1372                 local $data = &read_file_contents("$dir/$f");
1373                 local $ename = $name;
1374                 $ename =~ s/-/_/g;
1375                 push(@rv, { 'name' => $name,
1376                             'file' => "$dir/$f",
1377                             'enabled' => $data !~ /rc\.subr/ ? 2 :
1378                                          $enabled{$ename},
1379                             'startstop' => $data =~ /rc\.subr/ ||
1380                                            $data =~ /start\)/,
1381                             'desc' => $cmt{$name},
1382                             'standard' => ($dir !~ /local/)
1383                           });
1384                 }
1385         closedir(DIR);
1386         }
1387 return sort { $a->{'name'} cmp $b->{'name'} } @rv;
1388 }
1389
1390 =head2 save_rc_conf(name, value)
1391
1392 Internal function to modify the value of a single entry in the FreeBSD
1393 rc.conf file.
1394
1395 =cut
1396 sub save_rc_conf
1397 {
1398 local $found;
1399 local @rcs = split(/\s+/, $config{'rc_conf'});
1400 local $rcfile = $rcs[$#rcs];
1401 &open_readfile(CONF, $rcfile);
1402 local @conf = <CONF>;
1403 close(CONF);
1404 &open_tempfile(CONF, ">$rcfile");
1405 foreach (@conf) {
1406         if (/^\s*([^=]+)\s*=\s*(.*)/ && $1 eq $_[0]) {
1407                 &print_tempfile(CONF, "$_[0]=\"$_[1]\"\n") if (@_ > 1);
1408                 $found++;
1409                 }
1410         else {
1411                 &print_tempfile(CONF, $_);
1412                 }
1413         }
1414 if (!$found && @_ > 1) {
1415         &print_tempfile(CONF, "$_[0]=\"$_[1]\"\n");
1416         }
1417 &close_tempfile(CONF);
1418 }
1419
1420 =head2 get_rc_conf
1421
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:
1424
1425 =item name - Name of this configuration parameter. May appear more than once, with the later one taking precedence.
1426
1427 =item value - Current value.
1428
1429 =item cmt - A human-readable comment about the parameter.
1430
1431 =cut
1432 sub get_rc_conf
1433 {
1434 local ($file, @rv);
1435 foreach $file (map { glob($_) } split(/\s+/, $config{'rc_conf'})) {
1436         local $lnum = 0;
1437         &open_readfile(FILE, $file);
1438         while(<FILE>) {
1439                 local $cmt;
1440                 s/\r|\n//g;
1441                 if (s/#(.*)$//) {
1442                         $cmt = $1;
1443                         }
1444                 if (/^\s*([^=\s]+)\s*=\s*"(.*)"/ ||
1445                     /^\s*([^=\s]+)\s*=\s*'(.*)'/ ||
1446                     /^\s*([^=\s]+)\s*=\s*(\S+)/) {
1447                         push(@rv, { 'name' => $1,
1448                                     'value' => $2,
1449                                     'line' => $lnum,
1450                                     'file' => $file,
1451                                     'cmt' => $cmt });
1452                         }
1453                 $lnum++;
1454                 }
1455         close(FILE);
1456         }
1457 return @rv;
1458 }
1459
1460 =head2 enable_rc_script(name)
1461
1462 Mark some RC script as enabled at boot.
1463
1464 =cut
1465 sub enable_rc_script
1466 {
1467 local ($name) = @_;
1468 $name =~ s/-/_/g;
1469 &save_rc_conf($name."_enable", "YES");
1470 }
1471
1472 =head2 disable_rc_script(name)
1473
1474 Mark some RC script as disabled at boot.
1475
1476 =cut
1477 sub disable_rc_script
1478 {
1479 local ($name) = @_;
1480 $name =~ s/-/_/g;
1481 local $enabled;
1482 foreach my $r (&get_rc_conf()) {
1483         if ($r->{'name'} eq $name."_enable" &&
1484             lc($r->{'value'}) eq 'yes') {
1485                 $enabled = 1;
1486                 }
1487         }
1488 &save_rc_conf($name."_enable", "NO") if ($enabled);
1489 }
1490
1491 =head2 start_rc_script(name)
1492
1493 Attempt to start some RC script, and returns 1 or 0 (for success or failure)
1494 and the output.
1495
1496 =cut
1497 sub start_rc_script
1498 {
1499 local ($name) = @_;
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");
1504 return (!$?, $out);
1505 }
1506
1507 =head2 stop_rc_script(name)
1508
1509 Attempts to stop some RC script, and returns 1 or 0 (for success or failure)
1510 and the output.
1511
1512 =cut
1513 sub stop_rc_script
1514 {
1515 local ($name) = @_;
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");
1520 return (!$?, $out);
1521 }
1522
1523 =head2 delete_rc_script(name)
1524
1525 Delete the FreeBSD RC script with some name
1526
1527 =cut
1528 sub delete_rc_script
1529 {
1530 local ($name) = @_;
1531 my @rcs = &list_rc_scripts();
1532 my ($rc) = grep { $_->{'name'} eq $name } @rcs;
1533 if ($rc) {
1534         &lock_rc_files();
1535         &disable_rc_script($in{'name'});
1536         &unlock_rc_files();
1537         &unlink_logged($rc->{'file'});
1538         }
1539 }
1540
1541 =head2 lock_rc_files
1542
1543 Internal function to lock all FreeBSD rc.conf files.
1544
1545 =cut
1546 sub lock_rc_files
1547 {
1548 foreach my $f (split(/\s+/, $config{'rc_conf'})) {
1549         &lock_file($f);
1550         }
1551 }
1552
1553 =head2 unlock_rc_files
1554
1555 Internal function to un-lock all FreeBSD rc.conf files.
1556
1557 =cut
1558 sub unlock_rc_files
1559 {
1560 foreach my $f (split(/\s+/, $config{'rc_conf'})) {
1561         &unlock_file($f);
1562         }
1563 }
1564
1565 =head2 list_upstart_services
1566
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.
1569
1570 =cut
1571 sub list_upstart_services
1572 {
1573 # Start with native upstart services
1574 my @rv;
1575 my $out = &backquote_command("initctl list");
1576 my %done;
1577 foreach my $l (split(/\r?\n/, $out)) {
1578         if ($l =~ /^(\S+)\s+(start|stop)\/([a-z]+)/) {
1579                 my $s = { 'name' => $1,
1580                           'goal' => $2,
1581                           'status' => $3 };
1582                 if ($l =~ /process\s+(\d+)/) {
1583                         $s->{'pid'} = $1;
1584                         }
1585                 open(CONF, "/etc/init/$s->{'name'}.conf");
1586                 while(<CONF>) {
1587                         if (/^description\s+"([^"]+)"/ && !$s->{'desc'}) {
1588                                 $s->{'desc'} = $1;
1589                                 }
1590                         elsif (/^(#*)\s*start/ && !$s->{'boot'}) {
1591                                 $s->{'boot'} = $1 ? 'stop' : 'start';
1592                                 }
1593                         }
1594                 close(CONF);
1595                 push(@rv, $s);
1596                 $done{$s->{'name'}} = 1;
1597                 }
1598         }
1599
1600 # Also add legacy init scripts
1601 my @rls = &get_inittab_runlevel();
1602 foreach my $a (&list_actions()) {
1603         $a =~ s/\s+\d+$//;
1604         next if ($done{$a});
1605         my $f = &action_filename($a);
1606         my $s = { 'name' => $a,
1607                   'legacy' => 1 };
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);
1612                 }
1613         $s->{'desc'} = &init_description($f);
1614         my $hasarg = &get_action_args($f);
1615         if ($hasarg->{'status'}) {
1616                 my $r = &action_running($f);
1617                 if ($r == 0) {
1618                         $s->{'status'} = 'waiting';
1619                         }
1620                 elsif ($r == 1) {
1621                         $s->{'status'} = 'running';
1622                         }
1623                 }
1624         push(@rv, $s);
1625         }
1626
1627 return sort { $a->{'name'} cmp $b->{'name'} } @rv;
1628 }
1629
1630 =head2 start_upstart_service(name)
1631
1632 Run the upstart service with some name, and return an OK flag and output
1633
1634 =cut
1635 sub start_upstart_service
1636 {
1637 my ($name) = @_;
1638 my $out = &backquote_logged(
1639         "service ".quotemeta($name)." start 2>&1 </dev/null");
1640 return (!$?, $out);
1641 }
1642
1643 =head2 stop_upstart_service(name)
1644
1645 Shut down the upstop service with some name, and return an OK flag and output
1646
1647 =cut
1648 sub stop_upstart_service
1649 {
1650 my ($name) = @_;
1651 my $out = &backquote_logged(
1652         "service ".quotemeta($name)." stop 2>&1 </dev/null");
1653 return (!$?, $out);
1654 }
1655
1656 =head2 restart_upstart_service(name)
1657
1658 Restart the upstart service with some name, and return an OK flag and output
1659
1660 =cut
1661 sub restart_upstart_service
1662 {
1663 my ($name) = @_;
1664 my $out = &backquote_logged(
1665         "service ".quotemeta($name)." restart 2>&1 </dev/null");
1666 return (!$?, $out);
1667 }
1668
1669 =head2 create_upstart_service(name, description, command, [pre-script], [fork])
1670
1671 Create a new upstart service with the given details.
1672
1673 =cut
1674 sub create_upstart_service
1675 {
1676 my ($name, $desc, $server, $prestart, $forks) = @_;
1677 my $cfile = "/etc/init/$name.conf";
1678 &open_lock_tempfile(CFILE, ">$cfile");
1679 &print_tempfile(CFILE,
1680   "# $name\n".
1681   "#\n".
1682   "# $desc\n".
1683   "\n".
1684   "description  \"$desc\"\n".
1685   "\n".
1686   "start on runlevel [2345]\n".
1687   "stop on runlevel [!2345]\n".
1688   "\n"
1689   );
1690 if ($forks) {
1691         &print_tempfile(CFILE,
1692           "expect fork\n".
1693           "\n"
1694           );
1695         }
1696 if ($prestart) {
1697         &print_tempfile(CFILE,
1698           "pre-start script\n".
1699           join("\n",
1700             map { "    ".$_."\n" }
1701                 split(/\n/, $prestart))."\n".
1702           "end script\n".
1703           "\n");
1704         }
1705 &print_tempfile(CFILE, "exec ".$server."\n");
1706 &close_tempfile(CFILE);
1707 }
1708
1709 =head2 delete_upstart_service(name)
1710
1711 Delete all traces of some upstart service
1712
1713 =cut
1714 sub delete_upstart_service
1715 {
1716 my ($name) = @_;
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);
1721 }
1722
1723 =head2 reboot_system
1724
1725 Immediately reboots the system.
1726
1727 =cut
1728 sub reboot_system
1729 {
1730 &system_logged("$config{'reboot_command'} >$null_file 2>$null_file");
1731 }
1732
1733 =head2 shutdown_system
1734
1735 Immediately shuts down the system.
1736
1737 =cut
1738 sub shutdown_system
1739 {
1740 &system_logged("$config{'shutdown_command'} >$null_file 2>$null_file");
1741 }
1742
1743 # get_action_args(filename)
1744 # Returns the args that this action script appears to support, like stop, start
1745 # and status.
1746 sub get_action_args
1747 {
1748 my ($file) = @_;
1749 my %hasarg;
1750 open(FILE, $file);
1751 while(<FILE>) {
1752         if (/^\s*(['"]?)([a-z]+)\1\)/i) {
1753                 $hasarg{$2}++;
1754                 }
1755         }
1756 close(FILE);
1757 return \%hasarg;
1758 }
1759
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
1763 sub action_running
1764 {
1765 my ($file) = @_;
1766 my $out = &backquote_command("$file status");
1767 if ($out =~ /not\s+running/i ||
1768     $out =~ /no\s+server\s+running/i) {
1769         return 0;
1770         }
1771 elsif ($out =~ /running/i) {
1772         return 1;
1773         }
1774 elsif ($out =~ /stopped/i) {
1775         return 0;
1776         }
1777 return -1;
1778 }
1779
1780 1;