Handle hostnames with upper-case letters
[webmin.git] / zones / zones-lib.pl
1 # Functions for viewing and managing zones
2 # XXX proper pool selection field
3
4 BEGIN { push(@INC, ".."); };
5 use WebminCore;
6 &init_config();
7 &foreign_require("net", "net-lib.pl");
8 &foreign_require("mount", "mount-lib.pl");
9
10 %thing_key_map = ( "net" => "address",
11                    "fs" => "dir",
12                    "inherit-pkg-dir" => "dir",
13                    "capped-cpu" => "ncpus",
14                    "capped-memory" => "physical",
15                    "rctl" => "name",
16                    "attr" => "name",
17                    "device" => "match" );
18
19 # list_zones([global-too])
20 # Returns a list of all zones and their statuses (except global)
21 sub list_zones
22 {
23 local @rv;
24 open(OUT, "zoneadm list -p -i -c |");
25 while(<OUT>) {
26         s/\r|\n//g;
27         s/\s+$//;
28         local @fields = split(/:/, $_);
29         next if ($fields[1] eq "global" && !$_[0]);
30         push(@rv, { 'id' => $fields[0],
31                     'name' => $fields[1],
32                     'status' => $fields[2],
33                     'zonepath' => $fields[3] });
34         }
35 close(OUT);
36 return @rv;
37 }
38
39 # get_current_zone()
40 # Returns the current zone name
41 sub get_current_zone
42 {
43 local $zn = `zonename`;
44 chop($zn);
45 return $zn;
46 }
47
48 # get_zone(name)
49 # Returns a structure containing details of one zone
50 sub get_zone
51 {
52 local ($zone) = @_;
53 local $zinfo = { 'name' => $zone };
54 local ($status) = grep { $_->{'name'} eq $zone } &list_zones();
55 return undef if (!$status);
56 $zinfo->{'status'} = $status->{'status'};
57 $zinfo->{'id'} = $status->{'id'};
58
59 # Add zone-level variables. Failure is possible in some cases (like brand)
60 # if not supported on this Solaris version.
61 local ($p, $r);
62 foreach $p ("zonepath", "autoboot", "pool", "brand") {
63         eval {
64                 $main::error_must_die = 1;
65                 local @lines = &get_zonecfg_output($zone, "info $p");
66                 if ($lines[0] =~ /^$p:\s*(.*)/) {
67                         $zinfo->{$p} = $1;
68                         }
69                 };
70         }
71
72 # Add lists of things
73 foreach $r ("fs", "inherit-pkg-dir", "net", "device", "rctl", "attr",
74             "capped-cpu", "capped-memory") {
75         local @lines;
76         eval {
77                 $main::error_must_die = 1;
78                 @lines = &get_zonecfg_output($zone, "info $r");
79                 };
80         local ($l, $thing);
81         foreach $l (@lines) {
82                 if ($l =~ /^$r:/) {
83                         # Start of a new thing
84                         $thing = { 'keytype' => $r,
85                                    'keyfield' => $thing_key_map{$r},
86                                    'keyzone' => $zone };
87                         push(@{$zinfo->{$r}}, $thing);
88                         }
89                 elsif ($l =~ /^\s+\[([^:]+):\s*"(.*)"\]/ ||
90                        $l =~ /^\s+\[([^:]+):\s*(.*)\]/ ||
91                        $l =~ /^\s+([^:]+):\s*\[(.*)\]/ ||
92                        $l =~ /^\s+([^:]+):\s*"(.*)"/ ||
93                        $l =~ /^\s+([^:]+):\s*(.*)/) {
94                         # An attribute of a thing
95                         if (defined($thing->{$1})) {
96                                 # Multiple values!
97                                 $thing->{$1} .= "\0".$2;
98                                 }
99                         else {
100                                 # Just one
101                                 $thing->{$1} = $2;
102                                 }
103                         if ($1 eq $thing->{'keyfield'}) {
104                                 $thing->{'key'} = $2;
105                                 }
106                         }
107                 }
108         if ($r eq "rctl") {
109                 # Save old values for later calls to modify_zone_object
110                 $thing->{'keyoldvalue'} = $thing->{'value'};
111                 }
112         }
113 return $zinfo;
114 }
115
116 # set_zone_variable(&zinfo, name, value)
117 # Updates zone variable like autoboot in the zone
118 sub set_zone_variable
119 {
120 local ($zone, $name, $value) = @_;
121 &get_zonecfg_output($zone->{'name'}, "set $name=\"$value\"\ncommit\nexit", 1);
122 }
123
124 # modify_zone_object(&zinfo, &object)
125 # Modifies some object like a network address or filesystem in a zone
126 sub modify_zone_object
127 {
128 local ($zinfo, $thing) = @_;
129 local (@keys, @removes, $k, $v);
130 if ($thing->{'keytype'} eq "rctl") {
131         # Need to delete old values
132         foreach $v (split(/\0/, $thing->{'keyoldvalue'})) {
133                 push(@removes, "remove value $v\n");
134                 }
135         }
136 foreach $k (keys %$thing) {
137         if ($k !~ /^key/) {
138                 foreach $v (split(/\0/, $thing->{$k})) {
139                         if ($v =~ /^\(.*\)$/) {
140                                 push(@keys, "add $k $v\n");
141                                 }
142                         else {
143                                 push(@keys, "set $k=\"$v\"\n");
144                                 }
145                         }
146                 }
147         }
148 &get_zonecfg_output($zinfo->{'name'},
149         "select $thing->{'keytype'} $thing->{'keyfield'}=$thing->{'key'}\n".
150         join("", @removes).join("", @keys)."end\n", 1);
151 }
152
153 # create_zone_object(&zinfo, &object)
154 # Adds some object like a network interface to a zone
155 sub create_zone_object
156 {
157 local ($zinfo, $thing) = @_;
158 local (@keys, $k, $v);
159 foreach $k (keys %{$_[1]}) {
160         if ($k !~ /^key/) {
161                 foreach $v (split(/\0/, $_[1]->{$k})) {
162                         if ($v =~ /^\(.*\)$/) {
163                                 push(@keys, "add $k $v\n");
164                                 }
165                         else {
166                                 push(@keys, "set $k=\"$v\"\n");
167                                 }
168                         }
169                 if ($_[1]->{$k} eq "") {
170                         push(@keys, "set $k=\"\"\n");
171                         }
172                 }
173         }
174 &get_zonecfg_output($zinfo->{'name'},
175         "add $thing->{'keytype'}\n".
176         join("", @keys)."end\n", 1);
177 $thing->{'keyzone'} = $zinfo->{'name'};
178 push(@{$zinfo->{$thing->{'keytype'}}}, $thing);
179 }
180
181 # delete_zone_object(&zinfo, &object)
182 # Deletes some zone configuration object, like a network interface
183 sub delete_zone_object
184 {
185 local ($zinfo, $thing) = @_;
186 if ( !$thing->{'keyfield'}) {
187         &get_zonecfg_output($zinfo->{'name'}, "remove -F $thing->{'keytype'}", 1);
188         }
189   else {
190         &get_zonecfg_output($zinfo->{'name'}, "remove $thing->{'keytype'} $thing->{'keyfield'}=$thing->{'key'}", 1);
191         }
192 }
193
194 # create_zone(name, path)
195 # Creates a new zone, and returns a zone info object for it
196 sub create_zone
197 {
198 local ($name, $path) = @_;
199 &get_zonecfg_output($name, "create\nset zonepath=\"$path\"\nset autoboot=true", 1);
200 return &get_zone($name);
201 }
202
203 # delete_zone(&zinfo)
204 # Deletes an existing zone
205 sub delete_zone
206 {
207 local ($zinfo) = @_;
208 &get_zonecfg_output($zinfo->{'name'}, "delete -F", 1);
209 rmdir($zinfo->{'zonepath'});
210 }
211
212 # get_zonecfg_output(zone, command, log)
213 # Returns an array of lines output by zonecfg in response to some command. 
214 # If some error occurs, calls &error instead
215 sub get_zonecfg_output
216 {
217 local ($zone, $cmd, $log) = @_;
218 local $temp = &transname();
219 open(TEMP, ">$temp");
220 print TEMP $cmd,"\n";
221 close(TEMP);
222 local @lines;
223 open(OUT, "zonecfg -z $zone -f $temp 2>&1 |");
224 while(<OUT>) {
225         s/\r|\n//g;
226         push(@lines, $_);
227         }
228 close(OUT);
229 unlink($temp);
230 if ($?) {
231         local $lines = join("", map { "<tt>".&html_escape($_)."</tt><br>" } @lines);
232         $lines =~ s/$temp/input/g;
233         $cmd = &html_escape($cmd);
234         $cmd =~ s/\n/<br>/g;
235         &error("<tt>zonecfg</tt> failed :<br>",
236                $lines,
237                "for command :<br>",
238                "<tt>$cmd</tt>");
239         }
240 if ($log) {
241         &additional_log("exec", undef, "zonecfg -z $zone", $cmd);
242         }
243 return @lines;
244 }
245
246 # print_zones_list(&zones)
247 sub print_zones_list
248 {
249 local ($zones) = @_;
250 local @tds = ( "width=30%", "width=10%", "width=20%", "width=20%",
251                "width=20% nowrap" );
252 print &ui_columns_start([ $text{'list_name'},
253                           $text{'list_id'},
254                           $text{'list_path'},
255                           $text{'list_status'},
256                           $text{'list_actions'} ], "100%", 0, \@tds);
257 local $z;
258 foreach $z (@$zones) {
259         local ($a, @actions);
260         foreach $a (&zone_status_actions($z)) {
261                 push(@actions, "<a href='save_zone.cgi?zone=$z->{'name'}&$a->[0]=1&list=1'>$a->[1]</a>");
262                 }
263         print &ui_columns_row([
264                 "<a href='edit_zone.cgi?zone=$z->{'name'}'>$z->{'name'}</a>",
265                 $z->{'id'},
266                 $z->{'zonepath'},
267                 &nice_status($z->{'status'}),
268                 join(" | ", @actions),
269                 ], \@tds);
270         }
271 print &ui_columns_end();
272 }
273
274 sub nice_status
275 {
276 return $text{'status_'.$_[0]} || $_[0];
277 }
278
279 # pool_input(name, value)
280 # Returns HTML for selecting a pool
281 sub pool_input
282 {
283 local ($name, $value) = @_;
284 return &ui_opt_textbox($name, $value, 10, $text{'pool_none'});
285 }
286
287 # get_active_interface(&zinfo, &net)
288 # Returns the active interface object for some zone's network object
289 sub get_active_interface
290 {
291 local ($zinfo, $net) = @_;
292 if (!scalar(@active_interfaces_cache)) {
293         @active_interfaces_cache = &net::active_interfaces();
294         }
295 local $address = $net->{'address'};
296 $address =~ s/\/.*$//;
297 local ($iface) = grep { $_->{'zone'} eq $zinfo->{'name'} &&
298                         $_->{'address'} eq $address &&
299                         $_->{'name'} eq $net->{'physical'} }
300                       @active_interfaces_cache;
301 return $iface;
302 }
303
304 # get_active_mount(&zinfo, &fs)
305 # Returns the mount array ref for some zone's filesystem in the global zone
306 sub get_active_mount
307 {
308 local ($zinfo, $fs) = @_;
309 local $dir = &get_zone_root($zinfo).$fs->{'dir'};
310 if (!scalar(@active_mounts_cache)) {
311         @active_mounts_cache = &mount::list_mounted();
312         }
313 local ($mount) = grep { $_->[0] eq $dir } @active_mounts_cache;
314 return $mount;
315 }
316
317 # get_zone_root(&zinfo)
318 # Returns the root directory for actual zone files
319 sub get_zone_root
320 {
321 return $_[0]->{'zonepath'}."/root";
322 }
323
324 sub zone_title
325 {
326 return &text('zone_in', "<tt>$_[0]</tt>");
327 }
328
329 # run_zone_command(&zinfo, command, [return-error])
330 # Executes some command within a zone, calling &error if it fails
331 sub run_zone_command
332 {
333 local ($zinfo, $cmd, $re) = @_;
334 local $out = &backquote_logged("ctrun -l child zoneadm -z $zinfo->{'name'} $cmd 2>&1");
335 if ($? && !$re) {
336         &error("<tt>zoneadm</tt> failed : <tt>$out</tt>");
337         }
338 return wantarray ? ($out, $?) : $out;
339 }
340
341 # output_zone_command(&zinfo, command, filehandle, escape)
342 # Executes some command within a zone, sending output to a file handle
343 sub output_zone_command
344 {
345 local ($zinfo, $cmd, $fh, $escape) = @_;
346 open(OUT, "zoneadm -z $zinfo->{'name'} $cmd 2>&1 |");
347 while($line = <OUT>) {
348         next if ($line =~ /percent complete/);
349         $line = &html_escape($line) if ($escape);
350         print $line;
351         }
352 close(OUT);
353 &additional_log("exec", undef, "zoneadm -z $zinfo->{'name'} $cmd");
354 return $? ? 0 : 1;
355 }
356
357 # callback_zone_command(&zinfo, command, function, &args)
358 # Executes some command within a zone, sending output to a function
359 sub callback_zone_command
360 {
361 local ($zinfo, $cmd, $func, $args) = @_;
362 open(OUT, "zoneadm -z $zinfo->{'name'} $cmd 2>&1 |");
363 local $last_percent;
364 while(1) {
365         local $rmask;
366         vec($rmask, fileno(OUT), 1) = 1;
367         local $sel = select($rmask, undef, undef, 60);
368         next if ($sel < 0);
369         if (vec($rmask, fileno(OUT), 1)) {
370                 # Got something to read
371                 local $line = <OUT>;
372                 last if (!$line);
373                 if ($line =~ /percent complete/) {
374                         # Only show this every 10 seconds
375                         local $now = time();
376                         if ($now - $last_percent > 10) {
377                                 &$func(@$args, $line);
378                                 $last_percent = $now;
379                                 }
380                         }
381                 else {
382                         &$func(@$args, $line);
383                         }
384                 }
385         else {
386                 # Nothing to read for 60 seconds
387                 &$func(@$args, ".\n");
388                 }
389         }
390 close(OUT);
391 &additional_log("exec", undef, "zoneadm -z $zinfo->{'name'} $cmd");
392 return $? ? 0 : 1;
393 }
394
395 # get_address_netmask(&net, &active)
396 # Returns the address and netmask for the interface
397 sub get_address_netmask
398 {
399 local ($net, $active) = @_;
400 local ($address, $netmask);
401 if ($net->{'address'} =~ /^(\S+)\/(\d+)$/) {
402         $address = $1;
403         $netmask = &net::prefix_to_mask($2);
404         }
405 else {
406         $address = $net->{'address'};
407         $netmask = $active ? $active->{'netmask'} : undef;
408         }
409 return ($address, $netmask);
410 }
411
412 # physical_input(name, value)
413 # Returns HTML for selecting a real interface
414 sub physical_input
415 {
416 local ($name, $value) = @_;
417 return &ui_select($name, $value,
418        [ map { [ $_->{'name'} ] } grep { $_->{'virtual'} eq '' }
419              &net::active_interfaces() ], 0, 0, $value ? 1 : 0);
420 }
421
422 # list_filesystems()
423 # Returns a list of filesystems supported for Zones
424 sub list_filesystems
425 {
426 local @rv;
427 opendir(FS, "/usr/lib/fs");
428 foreach (readdir(FS)) {
429         if ($_ ne "proc" && $_ ne "mntfs" && $_ ne "autofs" &&
430             $_ ne "cachefs" && $_ ne "nfs" && $_ !~ /^\./) {
431                 push(@rv, $_);
432                 }
433         }
434 close(FS);
435 return @rv;
436 }
437
438 #list_brands()
439 #returns a list of valid brands
440 sub list_brands
441 {
442         local @rv;
443         opendir(BRND, "/usr/lib/brand");
444         foreach (readdir(BRND)) {
445                 if ($_ !~ /^\./){
446                         push(@rv, $_);
447                 }
448         }
449         close(BRND);
450 return @rv;
451 }
452
453
454 # run_in_zone(&zinfo, command)
455 # Runs some command within a zone, and returns the output
456 sub run_in_zone
457 {
458 local $zinfo = $_[0];
459 local $qc = quotemeta($_[1]);
460 local $out = &backquote_logged("zlogin $zinfo->{'name'} $qc 2>&1");
461 return ($out, $?);
462 }
463
464 # run_in_zone_callback(&zinfo, command, &func, &args)
465 # Runs some command within a zone, calling back for each line output
466 sub run_in_zone_callback
467 {
468 local $zinfo = $_[0];
469 local $qc = quotemeta($_[1]);
470 local $func = $_[2];
471 local $args = $_[3];
472 open(OUT, "zlogin $zinfo->{'name'} $qc 2>&1 |");
473 while($line = <OUT>) {
474         &$func(@$args, $line);
475         }
476 close(OUT);
477 &additional_log("exec", undef, "zlogin $zinfo->{'name'} $qc");
478 return $?;
479 }
480
481 # list_rctls()
482 # Returns a list of possible resource control names
483 sub list_rctls
484 {
485 local @rv;
486 open(RCTL, "rctladm -l |");
487 while(<RCTL>) {
488         if (/^(\S+)\s+(\S+)=(\S+)/) {
489                 push(@rv, $1);
490                 }
491         }
492 close(RCTL);
493 return @rv;
494 }
495
496 # get_rctl_value(value)
497 # Returns the privilege, limit and action for an resource control
498 sub get_rctl_value
499 {
500 local ($value) = @_;
501 $value =~ s/^\((.*)\)$/$1/;
502 local ($s, %rv);
503 foreach $s (split(/,/, $value)) {
504         local ($sn, $sv) = split(/=/, $s);
505         $rv{$sn} = $sv;
506         }
507 return ($rv{'priv'}, $rv{'limit'}, $rv{'action'});
508 }
509
510 sub list_attr_types
511 {
512 return ( "string", "int", "uint", "boolean" );
513 }
514
515 # find_clash(&zinfo, &thing)
516 # Returns an existing thing with the same key as the given one
517 sub find_clash
518 {
519 local ($zinfo, $thing) = @_;
520 local $kf = $thing_key_map{$thing->{'keytype'}};
521 local ($clash) = grep { $_ ne $thing && $_->{$kf} eq $thing->{$kf} }
522                       @{$zinfo->{$thing->{'keytype'}}};
523 return $clash;
524 }
525
526 # get_default_physical()
527 # Returns the default physical interface name (the first non-local interface)
528 sub get_default_physical
529 {
530 @ifaces = &net::active_interfaces();
531 ($nonlocal) = grep { $_->{'name'} ne "lo0" &&
532                      $_->{'virtual'} eq "" } @ifaces;
533 return $nonlocal ? $nonlocal->{'fullname'} : "lo0";
534 }
535
536 # zone_status_actions(&zinfo, include-webmin)
537 # Returns possible actions for some status
538 sub zone_status_actions
539 {
540 local ($zinfo, $inc) = @_;
541 local $status = $zinfo->{'status'};
542 local $w = &zone_has_webmin($zinfo);
543 local $wr = &zone_running_webmin($zinfo);
544 return $status eq 'running' ?
545         ( [ "reboot", $text{'edit_reboot'} ],
546           [ "halt", $text{'edit_halt'} ],
547           $w == 1 && $inc ? ( [ "wupgrade", $text{'edit_wupgrade'} ] ) :
548           $w == 0 && $inc ? ( [ "winstall", $text{'edit_winstall'} ] ) : ( ),
549           $wr ? ( [ "webmin", $text{'edit_webmin'} ] ) : ( ) ) :
550        $status eq 'installed' ?
551         ( [ "boot", $text{'edit_boot'} ],
552           [ "uninstall", $text{'edit_uninstall'} ] ) :
553        $status eq 'configured' ?
554         ( [ "install", $text{'edit_install'} ] ) :
555        $status eq 'ready' ?
556         ( [ "boot", $text{'edit_boot'} ],
557           [ "halt", $text{'edit_halt'} ] ) :
558         ( );
559
560 }
561
562 # create_webmin_install_script(&zinfo, file)
563 # Creates a shell script to install Webmin in a zone. Returns undef on success,
564 # or an error message if something would prevent Webmin from working.
565 sub create_webmin_install_script
566 {
567 local ($zinfo, $script) = @_;
568 local $perl_path = &get_perl_path();
569 local $root = &get_zone_root($zinfo);
570 if (!-x $root.$perl_path) {
571         return &text('webmin_eperl', "<tt>$perl_path</tt>");
572         }
573 local ($cat, $ex) = &run_in_zone($zinfo, "cat $root_directory/setup.sh");
574 if ($ex || !$cat) {
575         return &text('webmin_eroot', "<tt>$root_directory</tt>");
576         }
577 local %miniserv;
578 &get_miniserv_config(\%miniserv);
579
580 open(SCRIPT, ">$script");
581 print SCRIPT "#!/bin/sh\n";
582 print SCRIPT "config_dir=$config_directory\n";
583 print SCRIPT "var_dir=$var_directory\n";
584 print SCRIPT "perl=$perl_path\n";
585 print SCRIPT "autoos=3\n";
586 print SCRIPT "port=$miniserv{'port'}\n";
587 print SCRIPT "login=root\n";
588 print SCRIPT "crypt=x\n";
589 print SCRIPT "$perl_path -e 'use Net::SSLeay' >/dev/null 2>&1\n";
590 print SCRIPT "if [ \$? = 0 ]; then\n";
591 print SCRIPT "    ssl=1\n";
592 print SCRIPT "else\n";
593 print SCRIPT "    ssl=0\n";
594 print SCRIPT "fi\n";
595 print SCRIPT "atboot=1\n";
596 print SCRIPT "nochown=1\n";
597 print SCRIPT "autothird=1\n";
598 print SCRIPT "noperlpath=1\n";
599 print SCRIPT "nouninstall=1\n";
600 print SCRIPT "nostart=1\n";
601 print SCRIPT "export config_dir var_dir perl autoos port login crypt ssl atboot nochown autothird noperlpath nouninstall nostart\n";
602 print SCRIPT "cd $root_directory\n";
603 print SCRIPT "./setup.sh || exit 1\n";
604 print SCRIPT "$config_directory/start >/dev/null 2>&1 </dev/null &\n";
605 close(SCRIPT);
606 chmod(0755, $script);
607 return undef;
608 }
609
610 # zone_has_webmin(&zinfo)
611 # Returns 2 if Webmin is installed in the zone and is the same version, 1 if
612 # installed but older version, 0 if not installed at all
613 sub zone_has_webmin
614 {
615 local ($zinfo) = @_;
616 local $root = &get_zone_root($zinfo);
617 open(VERSION, $root.$config_directory."/version") || return 0;
618 local $version = <VERSION>;
619 close(VERSION);
620 chop($version);
621 return $version == &get_webmin_version() ? 2 : 1;
622 }
623
624 # zone_running_webmin(&zinfo)
625 # If a zone has Webmin installed and it is running, returns a URL for it
626 sub zone_running_webmin
627 {
628 local ($zinfo) = @_;
629 return undef if (!&zone_has_webmin($zinfo));
630 local $root = &get_zone_root($zinfo);
631 local %miniserv;
632 &read_file("$root$config_directory/miniserv.conf", \%miniserv);
633 local $pid = &check_pid_file($root.$miniserv{'pidfile'});
634 return undef if (!$pid);
635 local $prot = $miniserv{'ssl'} ? "https" : "http";
636 if (gethostbyname($zinfo->{'name'}) && !$zinfo->{'net'}) {
637         # The zone name appears to resolve .. use it
638         return "$prot://$zinfo->{'name'}:$miniserv{'port'}/";
639         }
640 if ($zinfo->{'net'}) {
641         local $ip = $zinfo->{'net'}->[0]->{'address'};
642         $ip =~ s/\/\d+$//;
643         if ($ip eq &to_ipaddress($zinfo->{'name'})) {
644                 $ip = $zinfo->{'name'};
645                 }
646         return "$prot://$ip:$miniserv{'port'}/";
647         }
648 return undef;
649 }
650
651 # get_global_locale()
652 # Returns the locale for the global zone (defaults to C)
653 sub get_global_locale
654 {
655 local %locale;
656 &read_env_file("/etc/default/init", \%locale);
657 return $locale{'LC_CTYPE'} || "C";
658 }
659
660 # save_sysidcfg(&sysid, file)
661 # Writes out a sysidcfg array
662 sub save_sysidcfg
663 {
664 local ($sysidcfg, $file) = @_;
665 open(FILE, ">$file");
666 local ($s, $k, $subs);
667 foreach $s (@$sysidcfg) {
668         local ($sk, $sv) = @$s;
669         if (ref($sv)) {
670                 # A sub-structure
671                 local ($v, @v) = @$sv;
672                 print FILE "$sk=$v {\n";
673                 foreach $subs (@v) {
674                         print FILE "\t$subs->[0]=$subs->[1]\n";
675                         }
676                 print FILE "}\n";
677                 }
678         else {
679                 # A single value
680                 print FILE "$sk=$sv\n";
681                 }
682         }
683 close(FILE);
684 }
685
686 # zone_sysidcfg_file(zone)
687 # Returns a filename for storing a temporary zone sysidcfg file before the
688 # zone is installed
689 sub zone_sysidcfg_file
690 {
691 return "$module_config_directory/$_[0].sysidcfg";
692 }
693
694 # config_zone_nfs(&zinfo)
695 # Setup the NFS configuration files for a zone. Should be called after installation
696 sub config_zone_nfs
697 {
698 local ($zinfo) = @_;
699 local $root = &get_zone_root($zinfo);
700 &system_logged("cp /etc/default/nfs $root/etc/default/nfs");
701 &system_logged("touch $root/etc/.NFS4inst_state.domain");
702 }
703
704 # post_webmin_install(&zinfo)
705 # Called after Webmin is installed in a Zone, to perform extra setup (like
706 # copying users/etc)
707 sub post_webmin_install
708 {
709 local $root = &get_zone_root($zinfo);
710 if (-r "$config_directory/webmin.cats") {
711         system("cp $config_directory/webmin.cats $root/$config_directory/webmin.cats");
712         }
713 if (-r "$config_directory/webmin.catnames") {
714         system("cp $config_directory/webmin.catnames $root/$config_directory/webmin.catnames");
715         }
716 }
717
718 1;
719