10 use PVE
::Cluster
qw(cfs_register_file cfs_read_file);
14 use PVE
::JSONSchema
qw(get_standard_option);
15 use PVE
::Tools
qw($IPV6RE $IPV4RE dir_glob_foreach);
17 use PVE
::AccessControl
;
22 my $nodename = PVE
::INotify
::nodename
();
24 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
26 PVE
::JSONSchema
::register_format
('pve-lxc-network', \
&verify_lxc_network
);
27 sub verify_lxc_network
{
28 my ($value, $noerr) = @_;
30 return $value if parse_lxc_network
($value);
32 return undef if $noerr;
34 die "unable to parse network setting\n";
37 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', \
&verify_ct_mountpoint
);
38 sub verify_ct_mountpoint
{
39 my ($value, $noerr) = @_;
41 return $value if parse_ct_mountpoint
($value);
43 return undef if $noerr;
45 die "unable to parse CT mountpoint options\n";
48 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
49 type
=> 'string', format
=> 'pve-ct-mountpoint',
50 typetext
=> '[volume=]volume,] [,backup=yes|no] [,size=\d+]',
51 description
=> "Use volume as container root.",
55 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
56 description
=> "The name of the snapshot.",
57 type
=> 'string', format
=> 'pve-configid',
65 description
=> "Lock/unlock the VM.",
66 enum
=> [qw(migrate backup snapshot rollback)],
71 description
=> "Specifies whether a VM will be started during system bootup.",
74 startup
=> get_standard_option
('pve-startup-order'),
78 description
=> "Enable/disable Template.",
84 enum
=> ['amd64', 'i386'],
85 description
=> "OS architecture type.",
91 enum
=> ['debian', 'ubuntu', 'centos', 'archlinux'],
92 description
=> "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
97 description
=> "Attach a console device (/dev/console) to the container.",
103 description
=> "Specify the number of tty available to the container",
111 description
=> "Limit of CPU usage. Note if the computer has 2 CPUs, it has total of '2' CPU time. Value '0' indicates no CPU limit.",
119 description
=> "CPU weight for a VM. Argument is used in the kernel fair scheduler. The larger the number is, the more CPU time this VM gets. Number is relative to weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.",
127 description
=> "Amount of RAM for the VM in MB.",
134 description
=> "Amount of SWAP for the VM in MB.",
140 description
=> "Set a host name for the container.",
147 description
=> "Container description. Only used on the configuration web interface.",
152 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
157 description
=> "Sets DNS server IP address for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
159 rootfs
=> get_standard_option
('pve-ct-rootfs'),
162 type
=> 'string', format
=> 'pve-configid',
164 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
168 description
=> "Timestamp for snapshots.",
174 description
=> "Console mode. By default, the console command tries to open a connection to one of the available tty devices. By setting cmode to 'console' it tries to attach to /dev/console instead. If you set cmode to 'shell', it simply invokes a shell inside the container (no login).",
176 enum
=> ['shell', 'console', 'tty'],
182 description
=> "Sets the protection flag of the container. This will prevent the remove operation.",
187 my $valid_lxc_conf_keys = {
191 'lxc.haltsignal' => 1,
192 'lxc.rebootsignal' => 1,
193 'lxc.stopsignal' => 1,
195 'lxc.network.type' => 1,
196 'lxc.network.flags' => 1,
197 'lxc.network.link' => 1,
198 'lxc.network.mtu' => 1,
199 'lxc.network.name' => 1,
200 'lxc.network.hwaddr' => 1,
201 'lxc.network.ipv4' => 1,
202 'lxc.network.ipv4.gateway' => 1,
203 'lxc.network.ipv6' => 1,
204 'lxc.network.ipv6.gateway' => 1,
205 'lxc.network.script.up' => 1,
206 'lxc.network.script.down' => 1,
208 'lxc.console.logfile' => 1,
211 'lxc.devttydir' => 1,
212 'lxc.hook.autodev' => 1,
216 'lxc.mount.entry' => 1,
217 'lxc.mount.auto' => 1,
219 'lxc.rootfs.mount' => 1,
220 'lxc.rootfs.options' => 1,
224 'lxc.aa_profile' => 1,
225 'lxc.aa_allow_incomplete' => 1,
226 'lxc.se_context' => 1,
229 'lxc.hook.pre-start' => 1,
230 'lxc.hook.pre-mount' => 1,
231 'lxc.hook.mount' => 1,
232 'lxc.hook.start' => 1,
233 'lxc.hook.post-stop' => 1,
234 'lxc.hook.clone' => 1,
235 'lxc.hook.destroy' => 1,
238 'lxc.start.auto' => 1,
239 'lxc.start.delay' => 1,
240 'lxc.start.order' => 1,
242 'lxc.environment' => 1,
249 my $MAX_LXC_NETWORKS = 10;
250 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
251 $confdesc->{"net$i"} = {
253 type
=> 'string', format
=> 'pve-lxc-network',
254 description
=> "Specifies network interfaces for the container.\n\n".
255 "The string should have the follow format:\n\n".
256 "-net<[0-9]> bridge=<vmbr<Nummber>>[,hwaddr=<MAC>]\n".
257 "[,mtu=<Number>][,name=<String>][,ip=<IPv4Format/CIDR>]\n".
258 ",ip6=<IPv6Format/CIDR>][,gw=<GatwayIPv4>]\n".
259 ",gw6=<GatwayIPv6>][,firewall=<[1|0]>][,tag=<VlanNo>]",
263 my $MAX_MOUNT_POINTS = 10;
264 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
265 $confdesc->{"mp$i"} = {
267 type
=> 'string', format
=> 'pve-ct-mountpoint',
268 typetext
=> '[volume=]volume,] [,backup=yes|no] [,size=\d+] [,mp=mountpoint]',
269 description
=> "Use volume as container mount point (experimental feature).",
274 sub write_pct_config
{
275 my ($filename, $conf) = @_;
277 delete $conf->{snapstate
}; # just to be sure
279 my $generate_raw_config = sub {
284 # add description as comment to top of file
285 my $descr = $conf->{description
} || '';
286 foreach my $cl (split(/\n/, $descr)) {
287 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
290 foreach my $key (sort keys %$conf) {
291 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
292 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
293 $raw .= "$key: $conf->{$key}\n";
296 if (my $lxcconf = $conf->{lxc
}) {
297 foreach my $entry (@$lxcconf) {
298 my ($k, $v) = @$entry;
306 my $raw = &$generate_raw_config($conf);
308 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
309 $raw .= "\n[$snapname]\n";
310 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
317 my ($key, $value) = @_;
319 die "unknown setting '$key'\n" if !$confdesc->{$key};
321 my $type = $confdesc->{$key}->{type
};
323 if (!defined($value)) {
324 die "got undefined value\n";
327 if ($value =~ m/[\n\r]/) {
328 die "property contains a line feed\n";
331 if ($type eq 'boolean') {
332 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
333 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
334 die "type check ('boolean') failed - got '$value'\n";
335 } elsif ($type eq 'integer') {
336 return int($1) if $value =~ m/^(\d+)$/;
337 die "type check ('integer') failed - got '$value'\n";
338 } elsif ($type eq 'number') {
339 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
340 die "type check ('number') failed - got '$value'\n";
341 } elsif ($type eq 'string') {
342 if (my $fmt = $confdesc->{$key}->{format
}) {
343 PVE
::JSONSchema
::check_format
($fmt, $value);
352 sub parse_pct_config
{
353 my ($filename, $raw) = @_;
355 return undef if !defined($raw);
358 digest
=> Digest
::SHA
::sha1_hex
($raw),
362 $filename =~ m
|/lxc/(\d
+).conf
$|
363 || die "got strange filename '$filename'";
371 my @lines = split(/\n/, $raw);
372 foreach my $line (@lines) {
373 next if $line =~ m/^\s*$/;
375 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
377 $conf->{description
} = $descr if $descr;
379 $conf = $res->{snapshots
}->{$section} = {};
383 if ($line =~ m/^\#(.*)\s*$/) {
384 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
388 if ($line =~ m/^(lxc\.[a-z0-9_\.]+)(:|\s*=)\s*(.*?)\s*$/) {
391 if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
392 push @{$conf->{lxc
}}, [$key, $value];
394 warn "vm $vmid - unable to parse config: $line\n";
396 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
397 $descr .= PVE
::Tools
::decode_text
($2);
398 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
399 $conf->{snapstate
} = $1;
400 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S+)\s*$/) {
403 eval { $value = check_type
($key, $value); };
404 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
405 $conf->{$key} = $value;
407 warn "vm $vmid - unable to parse config: $line\n";
411 $conf->{description
} = $descr if $descr;
413 delete $res->{snapstate
}; # just to be sure
419 my $vmlist = PVE
::Cluster
::get_vmlist
();
421 return $res if !$vmlist || !$vmlist->{ids
};
422 my $ids = $vmlist->{ids
};
424 foreach my $vmid (keys %$ids) {
425 next if !$vmid; # skip CT0
426 my $d = $ids->{$vmid};
427 next if !$d->{node
} || $d->{node
} ne $nodename;
428 next if !$d->{type
} || $d->{type
} ne 'lxc';
429 $res->{$vmid}->{type
} = 'lxc';
434 sub cfs_config_path
{
435 my ($vmid, $node) = @_;
437 $node = $nodename if !$node;
438 return "nodes/$node/lxc/$vmid.conf";
442 my ($vmid, $node) = @_;
444 my $cfspath = cfs_config_path
($vmid, $node);
445 return "/etc/pve/$cfspath";
449 my ($vmid, $node) = @_;
451 $node = $nodename if !$node;
452 my $cfspath = cfs_config_path
($vmid, $node);
454 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
455 die "container $vmid does not exists\n" if !defined($conf);
461 my ($vmid, $conf) = @_;
463 my $dir = "/etc/pve/nodes/$nodename/lxc";
466 write_config
($vmid, $conf);
472 unlink config_file
($vmid, $nodename);
476 my ($vmid, $conf) = @_;
478 my $cfspath = cfs_config_path
($vmid);
480 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
483 # flock: we use one file handle per process, so lock file
484 # can be called multiple times and succeeds for the same process.
486 my $lock_handles = {};
487 my $lockdir = "/run/lock/lxc";
492 return "$lockdir/pve-config-${vmid}.lock";
496 my ($vmid, $timeout) = @_;
498 $timeout = 10 if !$timeout;
501 my $filename = lock_filename
($vmid);
503 mkdir $lockdir if !-d
$lockdir;
505 my $lock_func = sub {
506 if (!$lock_handles->{$$}->{$filename}) {
507 my $fh = new IO
::File
(">>$filename") ||
508 die "can't open file - $!\n";
509 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
512 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
513 print STDERR
"trying to aquire lock...";
516 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
517 # try again on EINTR (see bug #273)
518 if ($success || ($! != EINTR
)) {
523 print STDERR
" failed\n";
524 die "can't aquire lock - $!\n";
527 print STDERR
" OK\n";
530 $lock_handles->{$$}->{$filename}->{refcount
}++;
533 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
536 die "can't lock file '$filename' - $err";
543 my $filename = lock_filename
($vmid);
545 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
546 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
547 if ($refcount <= 0) {
548 $lock_handles->{$$}->{$filename} = undef;
555 my ($vmid, $timeout, $code, @param) = @_;
559 lock_aquire
($vmid, $timeout);
560 eval { $res = &$code(@param) };
572 return defined($confdesc->{$name});
575 # add JSON properties for create and set function
576 sub json_config_properties
{
579 foreach my $opt (keys %$confdesc) {
580 next if $opt eq 'parent' || $opt eq 'snaptime';
581 next if $prop->{$opt};
582 $prop->{$opt} = $confdesc->{$opt};
588 sub json_config_properties_no_rootfs
{
591 foreach my $opt (keys %$confdesc) {
592 next if $prop->{$opt};
593 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
594 $prop->{$opt} = $confdesc->{$opt};
600 # container status helpers
602 sub list_active_containers
{
604 my $filename = "/proc/net/unix";
606 # similar test is used by lcxcontainers.c: list_active_containers
609 my $fh = IO
::File-
>new ($filename, "r");
612 while (defined(my $line = <$fh>)) {
613 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
615 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
626 # warning: this is slow
630 my $active_hash = list_active_containers
();
632 return 1 if defined($active_hash->{$vmid});
637 sub get_container_disk_usage
{
640 my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/'];
650 if (my ($fsid, $total, $used, $avail) = $line =~
651 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
659 eval { PVE
::Tools
::run_command
($cmd, timeout
=> 1, outfunc
=> $parser); };
668 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
670 my $active_hash = list_active_containers
();
672 foreach my $vmid (keys %$list) {
673 my $d = $list->{$vmid};
675 my $running = defined($active_hash->{$vmid});
677 $d->{status
} = $running ?
'running' : 'stopped';
679 my $cfspath = cfs_config_path
($vmid);
680 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
682 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
683 $d->{name
} =~ s/[\s]//g;
685 $d->{cpus
} = $conf->{cpulimit
} // 0;
688 my $res = get_container_disk_usage
($vmid);
689 $d->{disk
} = $res->{used
};
690 $d->{maxdisk
} = $res->{total
};
693 # use 4GB by default ??
694 if (my $rootfs = $conf->{rootfs
}) {
695 my $rootinfo = parse_ct_mountpoint
($rootfs);
696 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
698 $d->{maxdisk
} = 4*1024*1024*1024;
704 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
705 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
716 $d->{template
} = is_template
($conf);
719 foreach my $vmid (keys %$list) {
720 my $d = $list->{$vmid};
721 next if $d->{status
} ne 'running';
723 $d->{uptime
} = 100; # fixme:
725 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
726 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
728 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
729 my @bytes = split(/\n/, $blkio_bytes);
730 foreach my $byte (@bytes) {
731 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
732 $d->{diskread
} = $2 if $key eq 'Read';
733 $d->{diskwrite
} = $2 if $key eq 'Write';
741 my $parse_size = sub {
744 return undef if $value !~ m/^(\d+(\.\d+)?)([KMG])?$/;
745 my ($size, $unit) = ($1, $3);
748 $size = $size * 1024;
749 } elsif ($unit eq 'M') {
750 $size = $size * 1024 * 1024;
751 } elsif ($unit eq 'G') {
752 $size = $size * 1024 * 1024 * 1024;
758 sub parse_ct_mountpoint
{
765 foreach my $p (split (/,/, $data)) {
766 next if $p =~ m/^\s*$/;
768 if ($p =~ m/^(volume|backup|size|mp)=(.+)$/) {
769 my ($k, $v) = ($1, $2);
770 return undef if defined($res->{$k});
773 if (!$res->{volume
} && $p !~ m/=/) {
781 return undef if !defined($res->{volume
});
783 return undef if $res->{backup
} && $res->{backup
} !~ m/^(yes|no)$/;
786 return undef if !defined($res->{size
} = &$parse_size($res->{size
}));
792 sub print_ct_mountpoint
{
793 my ($info, $nomp) = @_;
797 die "missing volume\n" if !$info->{volume
};
799 foreach my $o ('size', 'backup') {
800 $opts .= ",$o=$info->{$o}" if defined($info->{$o});
802 $opts .= ",mp=$info->{mp}" if !$nomp;
804 return "$info->{volume}$opts";
807 sub print_lxc_network
{
810 die "no network name defined\n" if !$net->{name
};
812 my $res = "name=$net->{name}";
814 foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
815 next if !defined($net->{$k});
816 $res .= ",$k=$net->{$k}";
822 sub parse_lxc_network
{
827 return $res if !$data;
829 foreach my $pv (split (/,/, $data)) {
830 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
837 $res->{type
} = 'veth';
838 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
843 sub read_cgroup_value
{
844 my ($group, $vmid, $name, $full) = @_;
846 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
848 return PVE
::Tools
::file_get_contents
($path) if $full;
850 return PVE
::Tools
::file_read_firstline
($path);
853 sub write_cgroup_value
{
854 my ($group, $vmid, $name, $value) = @_;
856 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
857 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
861 sub find_lxc_console_pids
{
865 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
868 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
871 my @args = split(/\0/, $cmdline);
873 # serach for lxc-console -n <vmid>
874 return if scalar(@args) != 3;
875 return if $args[1] ne '-n';
876 return if $args[2] !~ m/^\d+$/;
877 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
881 push @{$res->{$vmid}}, $pid;
893 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
895 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid], outfunc
=> $parser);
897 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
902 my $ipv4_reverse_mask = [
938 # Note: we cannot use Net:IP, because that only allows strict
940 sub parse_ipv4_cidr
{
941 my ($cidr, $noerr) = @_;
943 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
944 return { address
=> $1, netmask
=> $ipv4_reverse_mask->[$2] };
947 return undef if $noerr;
949 die "unable to parse ipv4 address/mask\n";
955 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
958 sub update_lxc_config
{
959 my ($storage_cfg, $vmid, $conf) = @_;
961 my $dir = "/var/lib/lxc/$vmid";
963 if ($conf->{template
}) {
965 unlink "$dir/config";
972 die "missing 'arch' - internal error" if !$conf->{arch
};
973 $raw .= "lxc.arch = $conf->{arch}\n";
975 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
976 if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
977 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
982 if (!has_dev_console
($conf)) {
983 $raw .= "lxc.console = none\n";
984 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
987 my $ttycount = get_tty_count
($conf);
988 $raw .= "lxc.tty = $ttycount\n";
990 my $utsname = $conf->{hostname
} || "CT$vmid";
991 $raw .= "lxc.utsname = $utsname\n";
993 my $memory = $conf->{memory
} || 512;
994 my $swap = $conf->{swap
} // 0;
996 my $lxcmem = int($memory*1024*1024);
997 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
999 my $lxcswap = int(($memory + $swap)*1024*1024);
1000 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1002 if (my $cpulimit = $conf->{cpulimit
}) {
1003 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1004 my $value = int(100000*$cpulimit);
1005 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1008 my $shares = $conf->{cpuunits
} || 1024;
1009 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1011 my $mountpoint = parse_ct_mountpoint
($conf->{rootfs
});
1012 $mountpoint->{mp
} = '/';
1013 my $volid = $mountpoint->{volume
};
1014 my $path = mountpoint_mount_path
($mountpoint, $storage_cfg);
1016 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1019 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
1020 $path = "loop:$path" if $scfg->{path
};
1023 $raw .= "lxc.rootfs = $path\n";
1026 foreach my $k (keys %$conf) {
1027 next if $k !~ m/^net(\d+)$/;
1029 my $d = parse_lxc_network
($conf->{$k});
1031 $raw .= "lxc.network.type = veth\n";
1032 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1033 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1034 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1035 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1038 if (my $lxcconf = $conf->{lxc
}) {
1039 foreach my $entry (@$lxcconf) {
1040 my ($k, $v) = @$entry;
1041 $netcount++ if $k eq 'lxc.network.type';
1042 $raw .= "$k = $v\n";
1046 $raw .= "lxc.network.type = empty\n" if !$netcount;
1048 File
::Path
::mkpath
("$dir/rootfs");
1050 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1053 # verify and cleanup nameserver list (replace \0 with ' ')
1054 sub verify_nameserver_list
{
1055 my ($nameserver_list) = @_;
1058 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1059 PVE
::JSONSchema
::pve_verify_ip
($server);
1060 push @list, $server;
1063 return join(' ', @list);
1066 sub verify_searchdomain_list
{
1067 my ($searchdomain_list) = @_;
1070 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1071 # todo: should we add checks for valid dns domains?
1072 push @list, $server;
1075 return join(' ', @list);
1078 sub update_pct_config
{
1079 my ($vmid, $conf, $running, $param, $delete) = @_;
1087 my $pid = find_lxc_pid
($vmid);
1088 $rootdir = "/proc/$pid/root";
1091 if (defined($delete)) {
1092 foreach my $opt (@$delete) {
1093 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1094 die "unable to delete required option '$opt'\n";
1095 } elsif ($opt eq 'swap') {
1096 delete $conf->{$opt};
1097 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1098 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1099 delete $conf->{$opt};
1100 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1101 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1102 delete $conf->{$opt};
1103 push @nohotplug, $opt;
1105 } elsif ($opt =~ m/^net(\d)$/) {
1106 delete $conf->{$opt};
1109 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1110 } elsif ($opt eq 'protection') {
1111 delete $conf->{$opt};
1112 } elsif ($opt =~ m/^mp(\d+)$/) {
1113 delete $conf->{$opt};
1114 push @nohotplug, $opt;
1116 } elsif ($opt eq 'rootfs') {
1121 write_config
($vmid, $conf) if $running;
1125 # There's no separate swap size to configure, there's memory and "total"
1126 # memory (iow. memory+swap). This means we have to change them together.
1127 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1128 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1129 if (defined($wanted_memory) || defined($wanted_swap)) {
1131 $wanted_memory //= ($conf->{memory
} || 512);
1132 $wanted_swap //= ($conf->{swap
} || 0);
1134 my $total = $wanted_memory + $wanted_swap;
1136 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1137 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1139 $conf->{memory
} = $wanted_memory;
1140 $conf->{swap
} = $wanted_swap;
1142 write_config
($vmid, $conf) if $running;
1145 foreach my $opt (keys %$param) {
1146 my $value = $param->{$opt};
1147 if ($opt eq 'hostname') {
1148 $conf->{$opt} = $value;
1149 } elsif ($opt eq 'onboot') {
1150 $conf->{$opt} = $value ?
1 : 0;
1151 } elsif ($opt eq 'startup') {
1152 $conf->{$opt} = $value;
1153 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1154 $conf->{$opt} = $value;
1155 push @nohotplug, $opt;
1157 } elsif ($opt eq 'nameserver') {
1158 my $list = verify_nameserver_list
($value);
1159 $conf->{$opt} = $list;
1160 push @nohotplug, $opt;
1162 } elsif ($opt eq 'searchdomain') {
1163 my $list = verify_searchdomain_list
($value);
1164 $conf->{$opt} = $list;
1165 push @nohotplug, $opt;
1167 } elsif ($opt eq 'cpulimit') {
1168 $conf->{$opt} = $value;
1169 push @nohotplug, $opt; # fixme: hotplug
1171 } elsif ($opt eq 'cpuunits') {
1172 $conf->{$opt} = $value;
1173 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1174 } elsif ($opt eq 'description') {
1175 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1176 } elsif ($opt =~ m/^net(\d+)$/) {
1178 my $net = parse_lxc_network
($value);
1180 $conf->{$opt} = print_lxc_network
($net);
1182 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1184 } elsif ($opt eq 'protection') {
1185 $conf->{$opt} = $value ?
1 : 0;
1186 } elsif ($opt =~ m/^mp(\d+)$/) {
1187 $conf->{$opt} = $value;
1188 push @$new_disks, $opt;
1189 push @nohotplug, $opt;
1191 } elsif ($opt eq 'rootfs') {
1192 die "implement me: $opt";
1194 die "implement me: $opt";
1196 write_config
($vmid, $conf) if $running;
1199 if ($running && scalar(@nohotplug)) {
1200 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1204 my $storage_cfg = PVE
::Storage
::config
();
1205 create_disks
($storage_cfg, $vmid, $conf, $conf);
1206 mount_all
($vmid, $storage_cfg, $conf, $new_disks, 1);
1207 umount_all
($vmid, $storage_cfg, $conf, 0);
1211 sub has_dev_console
{
1214 return !(defined($conf->{console
}) && !$conf->{console
});
1220 return $conf->{tty
} // $confdesc->{tty
}->{default};
1226 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1229 sub get_console_command
{
1230 my ($vmid, $conf) = @_;
1232 my $cmode = get_cmode
($conf);
1234 if ($cmode eq 'console') {
1235 return ['lxc-console', '-n', $vmid, '-t', 0];
1236 } elsif ($cmode eq 'tty') {
1237 return ['lxc-console', '-n', $vmid];
1238 } elsif ($cmode eq 'shell') {
1239 return ['lxc-attach', '--clear-env', '-n', $vmid];
1241 die "internal error";
1245 sub get_primary_ips
{
1248 # return data from net0
1250 return undef if !defined($conf->{net0
});
1251 my $net = parse_lxc_network
($conf->{net0
});
1253 my $ipv4 = $net->{ip
};
1255 if ($ipv4 =~ /^(dhcp|manual)$/) {
1261 my $ipv6 = $net->{ip6
};
1263 if ($ipv6 =~ /^(dhcp|manual)$/) {
1270 return ($ipv4, $ipv6);
1274 sub destroy_lxc_container
{
1275 my ($storage_cfg, $vmid, $conf) = @_;
1277 foreach_mountpoint
($conf, sub {
1278 my ($ms, $mountpoint) = @_;
1279 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $mountpoint->{volume
});
1280 PVE
::Storage
::vdisk_free
($storage_cfg, $mountpoint->{volume
}) if $vmid == $owner;
1283 rmdir "/var/lib/lxc/$vmid/rootfs";
1284 unlink "/var/lib/lxc/$vmid/config";
1285 rmdir "/var/lib/lxc/$vmid";
1286 destroy_config
($vmid);
1288 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1289 #PVE::Tools::run_command($cmd);
1292 sub vm_stop_cleanup
{
1293 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1298 my $vollist = get_vm_volumes
($conf);
1299 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1302 warn $@ if $@; # avoid errors - just warn
1305 my $safe_num_ne = sub {
1308 return 0 if !defined($a) && !defined($b);
1309 return 1 if !defined($a);
1310 return 1 if !defined($b);
1315 my $safe_string_ne = sub {
1318 return 0 if !defined($a) && !defined($b);
1319 return 1 if !defined($a);
1320 return 1 if !defined($b);
1326 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1328 if ($newnet->{type
} ne 'veth') {
1329 # for when there are physical interfaces
1330 die "cannot update interface of type $newnet->{type}";
1333 my $veth = "veth${vmid}i${netid}";
1334 my $eth = $newnet->{name
};
1336 if (my $oldnetcfg = $conf->{$opt}) {
1337 my $oldnet = parse_lxc_network
($oldnetcfg);
1339 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1340 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1342 PVE
::Network
::veth_delete
($veth);
1343 delete $conf->{$opt};
1344 write_config
($vmid, $conf);
1346 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1348 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1349 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1350 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1352 if ($oldnet->{bridge
}) {
1353 PVE
::Network
::tap_unplug
($veth);
1354 foreach (qw(bridge tag firewall)) {
1355 delete $oldnet->{$_};
1357 $conf->{$opt} = print_lxc_network
($oldnet);
1358 write_config
($vmid, $conf);
1361 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1362 foreach (qw(bridge tag firewall)) {
1363 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1365 $conf->{$opt} = print_lxc_network
($oldnet);
1366 write_config
($vmid, $conf);
1369 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1372 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1376 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1378 my $veth = "veth${vmid}i${netid}";
1379 my $vethpeer = $veth . "p";
1380 my $eth = $newnet->{name
};
1382 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1383 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1385 # attach peer in container
1386 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1387 PVE
::Tools
::run_command
($cmd);
1389 # link up peer in container
1390 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1391 PVE
::Tools
::run_command
($cmd);
1393 my $done = { type
=> 'veth' };
1394 foreach (qw(bridge tag firewall hwaddr name)) {
1395 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1397 $conf->{$opt} = print_lxc_network
($done);
1399 write_config
($vmid, $conf);
1402 sub update_ipconfig
{
1403 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1405 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1407 my $optdata = parse_lxc_network
($conf->{$opt});
1411 my $cmdargs = shift;
1412 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1414 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1416 my $change_ip_config = sub {
1417 my ($ipversion) = @_;
1419 my $family_opt = "-$ipversion";
1420 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1421 my $gw= "gw$suffix";
1422 my $ip= "ip$suffix";
1424 my $newip = $newnet->{$ip};
1425 my $newgw = $newnet->{$gw};
1426 my $oldip = $optdata->{$ip};
1428 my $change_ip = &$safe_string_ne($oldip, $newip);
1429 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1431 return if !$change_ip && !$change_gw;
1433 # step 1: add new IP, if this fails we cancel
1434 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1435 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1442 # step 2: replace gateway
1443 # If this fails we delete the added IP and cancel.
1444 # If it succeeds we save the config and delete the old IP, ignoring
1445 # errors. The config is then saved.
1446 # Note: 'ip route replace' can add
1449 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1452 # the route was not replaced, the old IP is still available
1453 # rollback (delete new IP) and cancel
1455 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1456 warn $@ if $@; # no need to die here
1461 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1462 # if the route was not deleted, the guest might have deleted it manually
1468 # from this point on we save the configuration
1469 # step 3: delete old IP ignoring errors
1470 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1471 # We need to enable promote_secondaries, otherwise our newly added
1472 # address will be removed along with the old one.
1475 if ($ipversion == 4) {
1476 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1477 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1478 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1480 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1482 warn $@ if $@; # no need to die here
1484 if ($ipversion == 4) {
1485 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1489 foreach my $property ($ip, $gw) {
1490 if ($newnet->{$property}) {
1491 $optdata->{$property} = $newnet->{$property};
1493 delete $optdata->{$property};
1496 $conf->{$opt} = print_lxc_network
($optdata);
1497 write_config
($vmid, $conf);
1498 $lxc_setup->setup_network($conf);
1501 &$change_ip_config(4);
1502 &$change_ip_config(6);
1506 # Internal snapshots
1508 # NOTE: Snapshot create/delete involves several non-atomic
1509 # action, and can take a long time.
1510 # So we try to avoid locking the file and use 'lock' variable
1511 # inside the config file instead.
1513 my $snapshot_copy_config = sub {
1514 my ($source, $dest) = @_;
1516 foreach my $k (keys %$source) {
1517 next if $k eq 'snapshots';
1518 next if $k eq 'snapstate';
1519 next if $k eq 'snaptime';
1520 next if $k eq 'vmstate';
1521 next if $k eq 'lock';
1522 next if $k eq 'digest';
1523 next if $k eq 'description';
1525 $dest->{$k} = $source->{$k};
1529 my $snapshot_prepare = sub {
1530 my ($vmid, $snapname, $comment) = @_;
1534 my $updatefn = sub {
1536 my $conf = load_config
($vmid);
1538 die "you can't take a snapshot if it's a template\n"
1539 if is_template
($conf);
1543 $conf->{lock} = 'snapshot';
1545 die "snapshot name '$snapname' already used\n"
1546 if defined($conf->{snapshots
}->{$snapname});
1548 my $storecfg = PVE
::Storage
::config
();
1549 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1551 $snap = $conf->{snapshots
}->{$snapname} = {};
1553 &$snapshot_copy_config($conf, $snap);
1555 $snap->{'snapstate'} = "prepare";
1556 $snap->{'snaptime'} = time();
1557 $snap->{'description'} = $comment if $comment;
1558 $conf->{snapshots
}->{$snapname} = $snap;
1560 write_config
($vmid, $conf);
1563 lock_container
($vmid, 10, $updatefn);
1568 my $snapshot_commit = sub {
1569 my ($vmid, $snapname) = @_;
1571 my $updatefn = sub {
1573 my $conf = load_config
($vmid);
1575 die "missing snapshot lock\n"
1576 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1578 die "snapshot '$snapname' does not exist\n"
1579 if !defined($conf->{snapshots
}->{$snapname});
1581 die "wrong snapshot state\n"
1582 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1583 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1585 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1586 delete $conf->{lock};
1587 $conf->{parent
} = $snapname;
1589 write_config
($vmid, $conf);
1592 lock_container
($vmid, 10 ,$updatefn);
1596 my ($feature, $conf, $storecfg, $snapname) = @_;
1600 foreach_mountpoint
($conf, sub {
1601 my ($ms, $mountpoint) = @_;
1603 return if $err; # skip further test
1605 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1607 # TODO: implement support for mountpoints
1608 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1612 return $err ?
0 : 1;
1615 sub snapshot_create
{
1616 my ($vmid, $snapname, $comment) = @_;
1618 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1620 my $conf = load_config
($vmid);
1622 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1623 my $running = check_running
($vmid);
1626 PVE
::Tools
::run_command
($cmd);
1629 my $storecfg = PVE
::Storage
::config
();
1630 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1631 my $volid = $rootinfo->{volume
};
1633 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1635 PVE
::Tools
::run_command
($cmd);
1638 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1639 &$snapshot_commit($vmid, $snapname);
1642 snapshot_delete
($vmid, $snapname, 1);
1647 sub snapshot_delete
{
1648 my ($vmid, $snapname, $force) = @_;
1654 my $updatefn = sub {
1656 $conf = load_config
($vmid);
1658 die "you can't delete a snapshot if vm is a template\n"
1659 if is_template
($conf);
1661 $snap = $conf->{snapshots
}->{$snapname};
1665 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1667 $snap->{snapstate
} = 'delete';
1669 write_config
($vmid, $conf);
1672 lock_container
($vmid, 10, $updatefn);
1674 my $storecfg = PVE
::Storage
::config
();
1676 my $del_snap = sub {
1680 if ($conf->{parent
} eq $snapname) {
1681 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1682 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1684 delete $conf->{parent
};
1688 delete $conf->{snapshots
}->{$snapname};
1690 write_config
($vmid, $conf);
1693 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1694 my $rootinfo = parse_ct_mountpoint
($rootfs);
1695 my $volid = $rootinfo->{volume
};
1698 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1702 if(!$err || ($err && $force)) {
1703 lock_container
($vmid, 10, $del_snap);
1705 die "Can't delete snapshot: $vmid $snapname $err\n";
1710 sub snapshot_rollback
{
1711 my ($vmid, $snapname) = @_;
1713 my $storecfg = PVE
::Storage
::config
();
1715 my $conf = load_config
($vmid);
1717 die "you can't rollback if vm is a template\n" if is_template
($conf);
1719 my $snap = $conf->{snapshots
}->{$snapname};
1721 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1723 my $rootfs = $snap->{rootfs
};
1724 my $rootinfo = parse_ct_mountpoint
($rootfs);
1725 my $volid = $rootinfo->{volume
};
1727 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1729 my $updatefn = sub {
1731 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1732 if $snap->{snapstate
};
1736 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1738 die "unable to rollback vm $vmid: vm is running\n"
1739 if check_running
($vmid);
1741 $conf->{lock} = 'rollback';
1745 # copy snapshot config to current config
1747 my $tmp_conf = $conf;
1748 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1749 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1750 delete $conf->{snaptime
};
1751 delete $conf->{snapname
};
1752 $conf->{parent
} = $snapname;
1754 write_config
($vmid, $conf);
1757 my $unlockfn = sub {
1758 delete $conf->{lock};
1759 write_config
($vmid, $conf);
1762 lock_container
($vmid, 10, $updatefn);
1764 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1766 lock_container
($vmid, 5, $unlockfn);
1769 sub template_create
{
1770 my ($vmid, $conf) = @_;
1772 my $storecfg = PVE
::Storage
::config
();
1774 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1775 my $volid = $rootinfo->{volume
};
1777 die "Template feature is not available for '$volid'\n"
1778 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1780 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1782 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1783 $rootinfo->{volume
} = $template_volid;
1784 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1786 write_config
($vmid, $conf);
1792 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1795 sub mountpoint_names
{
1798 my @names = ('rootfs');
1800 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1801 push @names, "mp$i";
1804 return $reverse ?
reverse @names : @names;
1807 sub foreach_mountpoint_full
{
1808 my ($conf, $reverse, $func) = @_;
1810 foreach my $key (mountpoint_names
($reverse)) {
1811 my $value = $conf->{$key};
1812 next if !defined($value);
1813 my $mountpoint = parse_ct_mountpoint
($value);
1814 $mountpoint->{mp
} = '/' if $key eq 'rootfs'; # just to be sure
1815 &$func($key, $mountpoint);
1819 sub foreach_mountpoint
{
1820 my ($conf, $func) = @_;
1822 foreach_mountpoint_full
($conf, 0, $func);
1825 sub foreach_mountpoint_reverse
{
1826 my ($conf, $func) = @_;
1828 foreach_mountpoint_full
($conf, 1, $func);
1831 sub check_ct_modify_config_perm
{
1832 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1834 return 1 if $authuser ne 'root@pam';
1836 foreach my $opt (@$key_list) {
1838 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1839 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1840 } elsif ($opt eq 'disk') {
1841 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1842 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1843 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1844 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1845 $opt eq 'searchdomain' || $opt eq 'hostname') {
1846 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1848 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
1856 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
1858 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1859 my $volid_list = get_vm_volumes
($conf);
1861 foreach_mountpoint_reverse
($conf, sub {
1862 my ($ms, $mountpoint) = @_;
1864 my $volid = $mountpoint->{volume
};
1865 my $mount = $mountpoint->{mp
};
1867 return if !$volid || !$mount;
1869 my $mount_path = "$rootdir/$mount";
1870 $mount_path =~ s!/+!/!g;
1872 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
1875 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
1888 my ($vmid, $storage_cfg, $conf, $mkdirs) = @_;
1890 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1892 my $volid_list = get_vm_volumes
($conf);
1893 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
1896 foreach_mountpoint
($conf, sub {
1897 my ($ms, $mountpoint) = @_;
1899 my $volid = $mountpoint->{volume
};
1900 my $mount = $mountpoint->{mp
};
1902 return if !$volid || !$mount;
1904 my $image_path = PVE
::Storage
::path
($storage_cfg, $volid);
1905 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1906 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1908 die "unable to mount base volume - internal error" if $isBase;
1910 File
::Path
::make_path
"$rootdir/$mount" if $mkdirs;
1911 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
1915 warn "mounting container failed - $err";
1916 umount_all
($vmid, $storage_cfg, $conf, 1);
1923 sub mountpoint_mount_path
{
1924 my ($mountpoint, $storage_cfg, $snapname) = @_;
1926 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
1929 # use $rootdir = undef to just return the corresponding mount path
1930 sub mountpoint_mount
{
1931 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
1933 my $volid = $mountpoint->{volume
};
1934 my $mount = $mountpoint->{mp
};
1936 return if !$volid || !$mount;
1940 if (defined($rootdir)) {
1941 $rootdir =~ s!/+$!!;
1942 $mount_path = "$rootdir/$mount";
1943 $mount_path =~ s!/+!/!g;
1944 File
::Path
::mkpath
($mount_path);
1947 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1949 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
1953 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
1954 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
1955 return $path if !$mount_path;
1957 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1958 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1960 if ($format eq 'subvol') {
1962 if ($scfg->{type
} eq 'zfspool') {
1963 my $path_arg = $path;
1964 $path_arg =~ s!^/+!!;
1965 PVE
::Tools
::run_command
(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
1967 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
1970 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
1973 } elsif ($format eq 'raw') {
1975 if ($scfg->{path
}) {
1976 push @extra_opts, '-o', 'loop';
1977 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'rbd') {
1980 die "unsupported storage type '$scfg->{type}'\n";
1982 if ($isBase || defined($snapname)) {
1983 PVE
::Tools
::run_command
(['mount', '-o', "ro", @extra_opts, $path, $mount_path]);
1985 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
1989 die "unsupported image format '$format'\n";
1991 } elsif ($volid =~ m
|^/dev/.+|) {
1992 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
1994 } elsif ($volid !~ m
|^/dev/.+| && $volid =~ m
|^/.+| && -d
$volid) {
1995 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
1999 die "unsupported storage";
2002 sub get_vm_volumes
{
2003 my ($conf, $excludes) = @_;
2007 foreach_mountpoint
($conf, sub {
2008 my ($ms, $mountpoint) = @_;
2010 return if $excludes && $ms eq $excludes;
2012 my $volid = $mountpoint->{volume
};
2014 return if !$volid || $volid =~ m
|^/|;
2016 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2019 push @$vollist, $volid;
2028 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp', $dev]);
2032 my ($storage_cfg, $volid) = @_;
2034 if ($volid =~ m!^/dev/.+!) {
2039 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2041 die "cannot format volume '$volid' with no storage\n" if !$storage;
2043 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2045 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2046 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2048 die "cannot format volume '$volid' (format == $format)\n"
2049 if $format ne 'raw';
2055 my ($storecfg, $vollist) = @_;
2057 foreach my $volid (@$vollist) {
2058 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2064 my ($storecfg, $vmid, $settings, $conf) = @_;
2069 foreach_mountpoint
($settings, sub {
2070 my ($ms, $mountpoint) = @_;
2072 my $volid = $mountpoint->{volume
};
2073 my $mp = $mountpoint->{mp
};
2075 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2077 return if !$storage;
2079 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
2080 my ($storeid, $size) = ($1, $2);
2082 $size = int($size*1024) * 1024;
2084 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2085 # fixme: use better naming ct-$vmid-disk-X.raw?
2087 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2089 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2091 format_disk
($storecfg, $volid);
2093 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2096 } elsif ($scfg->{type
} eq 'zfspool') {
2098 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2100 } elsif ($scfg->{type
} eq 'drbd') {
2102 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size);
2103 format_disk
($storecfg, $volid);
2105 } elsif ($scfg->{type
} eq 'rbd') {
2107 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2108 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size);
2109 format_disk
($storecfg, $volid);
2111 die "unable to create containers on storage type '$scfg->{type}'\n";
2113 push @$vollist, $volid;
2114 my $new_mountpoint = { volume
=> $volid, size
=> $size, mp
=> $mp };
2115 $conf->{$ms} = print_ct_mountpoint
($new_mountpoint, $ms eq 'rootfs');
2117 # use specified/existing volid
2121 # free allocated images on error
2123 destroy_disks
($storecfg, $vollist);
2129 # bash completion helper
2131 sub complete_os_templates
{
2132 my ($cmdname, $pname, $cvalue) = @_;
2134 my $cfg = PVE
::Storage
::config
();
2138 if ($cvalue =~ m/^([^:]+):/) {
2142 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2143 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2146 foreach my $id (keys %$data) {
2147 foreach my $item (@{$data->{$id}}) {
2148 push @$res, $item->{volid
} if defined($item->{volid
});
2155 my $complete_ctid_full = sub {
2158 my $idlist = vmstatus
();
2160 my $active_hash = list_active_containers
();
2164 foreach my $id (keys %$idlist) {
2165 my $d = $idlist->{$id};
2166 if (defined($running)) {
2167 next if $d->{template
};
2168 next if $running && !$active_hash->{$id};
2169 next if !$running && $active_hash->{$id};
2178 return &$complete_ctid_full();
2181 sub complete_ctid_stopped
{
2182 return &$complete_ctid_full(0);
2185 sub complete_ctid_running
{
2186 return &$complete_ctid_full(1);