1 # Functions for viewing and managing zones
2 # XXX proper pool selection field
4 BEGIN { push(@INC, ".."); };
7 &foreign_require("net", "net-lib.pl");
8 &foreign_require("mount", "mount-lib.pl");
10 %thing_key_map = ( "net" => "address",
12 "inherit-pkg-dir" => "dir",
13 "capped-cpu" => "ncpus",
14 "capped-memory" => "physical",
17 "device" => "match" );
19 # list_zones([global-too])
20 # Returns a list of all zones and their statuses (except global)
24 open(OUT, "zoneadm list -p -i -c |");
28 local @fields = split(/:/, $_);
29 next if ($fields[1] eq "global" && !$_[0]);
30 push(@rv, { 'id' => $fields[0],
32 'status' => $fields[2],
33 'zonepath' => $fields[3] });
40 # Returns the current zone name
43 local $zn = `zonename`;
49 # Returns a structure containing details of one 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'};
59 # Add zone-level variables. Failure is possible in some cases (like brand)
60 # if not supported on this Solaris version.
62 foreach $p ("zonepath", "autoboot", "pool", "brand") {
64 $main::error_must_die = 1;
65 local @lines = &get_zonecfg_output($zone, "info $p");
66 if ($lines[0] =~ /^$p:\s*(.*)/) {
73 foreach $r ("fs", "inherit-pkg-dir", "net", "device", "rctl", "attr",
74 "capped-cpu", "capped-memory") {
77 $main::error_must_die = 1;
78 @lines = &get_zonecfg_output($zone, "info $r");
83 # Start of a new thing
84 $thing = { 'keytype' => $r,
85 'keyfield' => $thing_key_map{$r},
87 push(@{$zinfo->{$r}}, $thing);
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})) {
97 $thing->{$1} .= "\0".$2;
103 if ($1 eq $thing->{'keyfield'}) {
104 $thing->{'key'} = $2;
109 # Save old values for later calls to modify_zone_object
110 $thing->{'keyoldvalue'} = $thing->{'value'};
116 # set_zone_variable(&zinfo, name, value)
117 # Updates zone variable like autoboot in the zone
118 sub set_zone_variable
120 local ($zone, $name, $value) = @_;
121 &get_zonecfg_output($zone->{'name'}, "set $name=\"$value\"\ncommit\nexit", 1);
124 # modify_zone_object(&zinfo, &object)
125 # Modifies some object like a network address or filesystem in a zone
126 sub modify_zone_object
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");
136 foreach $k (keys %$thing) {
138 foreach $v (split(/\0/, $thing->{$k})) {
139 if ($v =~ /^\(.*\)$/) {
140 push(@keys, "add $k $v\n");
143 push(@keys, "set $k=\"$v\"\n");
148 &get_zonecfg_output($zinfo->{'name'},
149 "select $thing->{'keytype'} $thing->{'keyfield'}=$thing->{'key'}\n".
150 join("", @removes).join("", @keys)."end\n", 1);
153 # create_zone_object(&zinfo, &object)
154 # Adds some object like a network interface to a zone
155 sub create_zone_object
157 local ($zinfo, $thing) = @_;
158 local (@keys, $k, $v);
159 foreach $k (keys %{$_[1]}) {
161 foreach $v (split(/\0/, $_[1]->{$k})) {
162 if ($v =~ /^\(.*\)$/) {
163 push(@keys, "add $k $v\n");
166 push(@keys, "set $k=\"$v\"\n");
169 if ($_[1]->{$k} eq "") {
170 push(@keys, "set $k=\"\"\n");
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);
181 # delete_zone_object(&zinfo, &object)
182 # Deletes some zone configuration object, like a network interface
183 sub delete_zone_object
185 local ($zinfo, $thing) = @_;
186 if ( !$thing->{'keyfield'}) {
187 &get_zonecfg_output($zinfo->{'name'}, "remove -F $thing->{'keytype'}", 1);
190 &get_zonecfg_output($zinfo->{'name'}, "remove $thing->{'keytype'} $thing->{'keyfield'}=$thing->{'key'}", 1);
194 # create_zone(name, path)
195 # Creates a new zone, and returns a zone info object for it
198 local ($name, $path) = @_;
199 &get_zonecfg_output($name, "create\nset zonepath=\"$path\"\nset autoboot=true", 1);
200 return &get_zone($name);
203 # delete_zone(&zinfo)
204 # Deletes an existing zone
208 &get_zonecfg_output($zinfo->{'name'}, "delete -F", 1);
209 rmdir($zinfo->{'zonepath'});
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
217 local ($zone, $cmd, $log) = @_;
218 local $temp = &transname();
219 open(TEMP, ">$temp");
220 print TEMP $cmd,"\n";
223 open(OUT, "zonecfg -z $zone -f $temp 2>&1 |");
231 local $lines = join("", map { "<tt>".&html_escape($_)."</tt><br>" } @lines);
232 $lines =~ s/$temp/input/g;
233 $cmd = &html_escape($cmd);
235 &error("<tt>zonecfg</tt> failed :<br>",
241 &additional_log("exec", undef, "zonecfg -z $zone", $cmd);
246 # print_zones_list(&zones)
250 local @tds = ( "width=30%", "width=10%", "width=20%", "width=20%",
251 "width=20% nowrap" );
252 print &ui_columns_start([ $text{'list_name'},
255 $text{'list_status'},
256 $text{'list_actions'} ], "100%", 0, \@tds);
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>");
263 print &ui_columns_row([
264 "<a href='edit_zone.cgi?zone=$z->{'name'}'>$z->{'name'}</a>",
267 &nice_status($z->{'status'}),
268 join(" | ", @actions),
271 print &ui_columns_end();
276 return $text{'status_'.$_[0]} || $_[0];
279 # pool_input(name, value)
280 # Returns HTML for selecting a pool
283 local ($name, $value) = @_;
284 return &ui_opt_textbox($name, $value, 10, $text{'pool_none'});
287 # get_active_interface(&zinfo, &net)
288 # Returns the active interface object for some zone's network object
289 sub get_active_interface
291 local ($zinfo, $net) = @_;
292 if (!scalar(@active_interfaces_cache)) {
293 @active_interfaces_cache = &net::active_interfaces();
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;
304 # get_active_mount(&zinfo, &fs)
305 # Returns the mount array ref for some zone's filesystem in the global zone
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();
313 local ($mount) = grep { $_->[0] eq $dir } @active_mounts_cache;
317 # get_zone_root(&zinfo)
318 # Returns the root directory for actual zone files
321 return $_[0]->{'zonepath'}."/root";
326 return &text('zone_in', "<tt>$_[0]</tt>");
329 # run_zone_command(&zinfo, command, [return-error])
330 # Executes some command within a zone, calling &error if it fails
333 local ($zinfo, $cmd, $re) = @_;
334 local $out = &backquote_logged("ctrun -l child zoneadm -z $zinfo->{'name'} $cmd 2>&1");
336 &error("<tt>zoneadm</tt> failed : <tt>$out</tt>");
338 return wantarray ? ($out, $?) : $out;
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
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);
353 &additional_log("exec", undef, "zoneadm -z $zinfo->{'name'} $cmd");
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
361 local ($zinfo, $cmd, $func, $args) = @_;
362 open(OUT, "zoneadm -z $zinfo->{'name'} $cmd 2>&1 |");
366 vec($rmask, fileno(OUT), 1) = 1;
367 local $sel = select($rmask, undef, undef, 60);
369 if (vec($rmask, fileno(OUT), 1)) {
370 # Got something to read
373 if ($line =~ /percent complete/) {
374 # Only show this every 10 seconds
376 if ($now - $last_percent > 10) {
377 &$func(@$args, $line);
378 $last_percent = $now;
382 &$func(@$args, $line);
386 # Nothing to read for 60 seconds
387 &$func(@$args, ".\n");
391 &additional_log("exec", undef, "zoneadm -z $zinfo->{'name'} $cmd");
395 # get_address_netmask(&net, &active)
396 # Returns the address and netmask for the interface
397 sub get_address_netmask
399 local ($net, $active) = @_;
400 local ($address, $netmask);
401 if ($net->{'address'} =~ /^(\S+)\/(\d+)$/) {
403 $netmask = &net::prefix_to_mask($2);
406 $address = $net->{'address'};
407 $netmask = $active ? $active->{'netmask'} : undef;
409 return ($address, $netmask);
412 # physical_input(name, value)
413 # Returns HTML for selecting a real interface
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);
423 # Returns a list of filesystems supported for Zones
427 opendir(FS, "/usr/lib/fs");
428 foreach (readdir(FS)) {
429 if ($_ ne "proc" && $_ ne "mntfs" && $_ ne "autofs" &&
430 $_ ne "cachefs" && $_ ne "nfs" && $_ !~ /^\./) {
439 #returns a list of valid brands
443 opendir(BRND, "/usr/lib/brand");
444 foreach (readdir(BRND)) {
454 # run_in_zone(&zinfo, command)
455 # Runs some command within a zone, and returns the output
458 local $zinfo = $_[0];
459 local $qc = quotemeta($_[1]);
460 local $out = &backquote_logged("zlogin $zinfo->{'name'} $qc 2>&1");
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
468 local $zinfo = $_[0];
469 local $qc = quotemeta($_[1]);
472 open(OUT, "zlogin $zinfo->{'name'} $qc 2>&1 |");
473 while($line = <OUT>) {
474 &$func(@$args, $line);
477 &additional_log("exec", undef, "zlogin $zinfo->{'name'} $qc");
482 # Returns a list of possible resource control names
486 open(RCTL, "rctladm -l |");
488 if (/^(\S+)\s+(\S+)=(\S+)/) {
496 # get_rctl_value(value)
497 # Returns the privilege, limit and action for an resource control
501 $value =~ s/^\((.*)\)$/$1/;
503 foreach $s (split(/,/, $value)) {
504 local ($sn, $sv) = split(/=/, $s);
507 return ($rv{'priv'}, $rv{'limit'}, $rv{'action'});
512 return ( "string", "int", "uint", "boolean" );
515 # find_clash(&zinfo, &thing)
516 # Returns an existing thing with the same key as the given one
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'}}};
526 # get_default_physical()
527 # Returns the default physical interface name (the first non-local interface)
528 sub get_default_physical
530 @ifaces = &net::active_interfaces();
531 ($nonlocal) = grep { $_->{'name'} ne "lo0" &&
532 $_->{'virtual'} eq "" } @ifaces;
533 return $nonlocal ? $nonlocal->{'fullname'} : "lo0";
536 # zone_status_actions(&zinfo, include-webmin)
537 # Returns possible actions for some status
538 sub zone_status_actions
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'} ] ) :
556 ( [ "boot", $text{'edit_boot'} ],
557 [ "halt", $text{'edit_halt'} ] ) :
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
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>");
573 local ($cat, $ex) = &run_in_zone($zinfo, "cat $root_directory/setup.sh");
575 return &text('webmin_eroot', "<tt>$root_directory</tt>");
578 &get_miniserv_config(\%miniserv);
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";
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";
606 chmod(0755, $script);
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
616 local $root = &get_zone_root($zinfo);
617 open(VERSION, $root.$config_directory."/version") || return 0;
618 local $version = <VERSION>;
621 return $version == &get_webmin_version() ? 2 : 1;
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
629 return undef if (!&zone_has_webmin($zinfo));
630 local $root = &get_zone_root($zinfo);
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'}/";
640 if ($zinfo->{'net'}) {
641 local $ip = $zinfo->{'net'}->[0]->{'address'};
643 if ($ip eq &to_ipaddress($zinfo->{'name'})) {
644 $ip = $zinfo->{'name'};
646 return "$prot://$ip:$miniserv{'port'}/";
651 # get_global_locale()
652 # Returns the locale for the global zone (defaults to C)
653 sub get_global_locale
656 &read_env_file("/etc/default/init", \%locale);
657 return $locale{'LC_CTYPE'} || "C";
660 # save_sysidcfg(&sysid, file)
661 # Writes out a sysidcfg array
664 local ($sysidcfg, $file) = @_;
665 open(FILE, ">$file");
666 local ($s, $k, $subs);
667 foreach $s (@$sysidcfg) {
668 local ($sk, $sv) = @$s;
671 local ($v, @v) = @$sv;
672 print FILE "$sk=$v {\n";
674 print FILE "\t$subs->[0]=$subs->[1]\n";
680 print FILE "$sk=$sv\n";
686 # zone_sysidcfg_file(zone)
687 # Returns a filename for storing a temporary zone sysidcfg file before the
689 sub zone_sysidcfg_file
691 return "$module_config_directory/$_[0].sysidcfg";
694 # config_zone_nfs(&zinfo)
695 # Setup the NFS configuration files for a zone. Should be called after installation
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");
704 # post_webmin_install(&zinfo)
705 # Called after Webmin is installed in a Zone, to perform extra setup (like
707 sub post_webmin_install
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");
713 if (-r "$config_directory/webmin.catnames") {
714 system("cp $config_directory/webmin.catnames $root/$config_directory/webmin.catnames");