12 use PVE
::Cluster
qw(cfs_register_file cfs_read_file);
16 use PVE
::JSONSchema
qw(get_standard_option);
17 use PVE
::Tools
qw($IPV6RE $IPV4RE dir_glob_foreach);
19 use PVE
::AccessControl
;
24 my $nodename = PVE
::INotify
::nodename
();
26 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
32 format_description
=> 'volume',
33 description
=> 'Volume, device or directory to mount into the container.',
37 format_description
=> '[1|0]',
38 description
=> 'Whether to include the mountpoint in backups.',
43 format_description
=> 'DiskSize',
44 description
=> 'Volume size (read only value).',
49 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
50 type
=> 'string', format
=> $rootfs_desc,
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.",
141 type
=> 'string', format
=> 'dns-name',
147 description
=> "Container description. Only used on the configuration web interface.",
151 type
=> 'string', format
=> 'dns-name-list',
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.",
156 type
=> 'string', format
=> 'address-list',
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. This will prevent the CT or CT's disk remove/update 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,
253 description
=> "Network interface type.",
258 format_description
=> 'String',
259 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
260 pattern
=> '[-_.\w\d]+',
264 format_description
=> 'vmbr<Number>',
265 description
=> 'Bridge to attach the network device to.',
266 pattern
=> '[-_.\w\d]+',
270 format_description
=> 'MAC',
271 description
=> 'Bridge to attach the network device to. (lxc.network.hwaddr)',
272 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
277 format_description
=> 'Number',
278 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
279 minimum
=> 64, # minimum ethernet frame is 64 bytes
284 format
=> 'pve-ipv4-config',
285 format_description
=> 'IPv4Format/CIDR',
286 description
=> 'IPv4 address in CIDR format.',
292 format_description
=> 'GatewayIPv4',
293 description
=> 'Default gateway for IPv4 traffic.',
298 format
=> 'pve-ipv6-config',
299 format_description
=> 'IPv6Format/CIDR',
300 description
=> 'IPv6 address in CIDR format.',
306 format_description
=> 'GatewayIPv6',
307 description
=> 'Default gateway for IPv6 traffic.',
312 format_description
=> '[1|0]',
313 description
=> "Controls whether this interface's firewall rules should be used.",
318 format_description
=> 'VlanNo',
321 description
=> "VLAN tag foro this interface.",
325 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
327 my $MAX_LXC_NETWORKS = 10;
328 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
329 $confdesc->{"net$i"} = {
331 type
=> 'string', format
=> $netconf_desc,
332 description
=> "Specifies network interfaces for the container.",
340 format_description
=> 'Path',
341 description
=> 'Path to the mountpoint as seen from inside the container.',
345 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
349 type
=> 'string', format
=> 'pve-volume-id',
350 description
=> "Reference to unused volumes.",
353 my $MAX_MOUNT_POINTS = 10;
354 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
355 $confdesc->{"mp$i"} = {
357 type
=> 'string', format
=> $mp_desc,
358 description
=> "Use volume as container mount point (experimental feature).",
363 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
364 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
365 $confdesc->{"unused$i"} = $unuseddesc;
368 sub write_pct_config
{
369 my ($filename, $conf) = @_;
371 delete $conf->{snapstate
}; # just to be sure
373 my $generate_raw_config = sub {
378 # add description as comment to top of file
379 my $descr = $conf->{description
} || '';
380 foreach my $cl (split(/\n/, $descr)) {
381 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
384 foreach my $key (sort keys %$conf) {
385 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
386 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
387 my $value = $conf->{$key};
388 die "detected invalid newline inside property '$key'\n" if $value =~ m/\n/;
389 $raw .= "$key: $value\n";
392 if (my $lxcconf = $conf->{lxc
}) {
393 foreach my $entry (@$lxcconf) {
394 my ($k, $v) = @$entry;
402 my $raw = &$generate_raw_config($conf);
404 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
405 $raw .= "\n[$snapname]\n";
406 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
413 my ($key, $value) = @_;
415 die "unknown setting '$key'\n" if !$confdesc->{$key};
417 my $type = $confdesc->{$key}->{type
};
419 if (!defined($value)) {
420 die "got undefined value\n";
423 if ($value =~ m/[\n\r]/) {
424 die "property contains a line feed\n";
427 if ($type eq 'boolean') {
428 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
429 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
430 die "type check ('boolean') failed - got '$value'\n";
431 } elsif ($type eq 'integer') {
432 return int($1) if $value =~ m/^(\d+)$/;
433 die "type check ('integer') failed - got '$value'\n";
434 } elsif ($type eq 'number') {
435 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
436 die "type check ('number') failed - got '$value'\n";
437 } elsif ($type eq 'string') {
438 if (my $fmt = $confdesc->{$key}->{format
}) {
439 PVE
::JSONSchema
::check_format
($fmt, $value);
448 sub parse_pct_config
{
449 my ($filename, $raw) = @_;
451 return undef if !defined($raw);
454 digest
=> Digest
::SHA
::sha1_hex
($raw),
458 $filename =~ m
|/lxc/(\d
+).conf
$|
459 || die "got strange filename '$filename'";
467 my @lines = split(/\n/, $raw);
468 foreach my $line (@lines) {
469 next if $line =~ m/^\s*$/;
471 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
473 $conf->{description
} = $descr if $descr;
475 $conf = $res->{snapshots
}->{$section} = {};
479 if ($line =~ m/^\#(.*)\s*$/) {
480 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
484 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
487 if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
488 push @{$conf->{lxc
}}, [$key, $value];
490 warn "vm $vmid - unable to parse config: $line\n";
492 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
493 $descr .= PVE
::Tools
::decode_text
($2);
494 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
495 $conf->{snapstate
} = $1;
496 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
499 eval { $value = check_type
($key, $value); };
500 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
501 $conf->{$key} = $value;
503 warn "vm $vmid - unable to parse config: $line\n";
507 $conf->{description
} = $descr if $descr;
509 delete $res->{snapstate
}; # just to be sure
515 my $vmlist = PVE
::Cluster
::get_vmlist
();
517 return $res if !$vmlist || !$vmlist->{ids
};
518 my $ids = $vmlist->{ids
};
520 foreach my $vmid (keys %$ids) {
521 next if !$vmid; # skip CT0
522 my $d = $ids->{$vmid};
523 next if !$d->{node
} || $d->{node
} ne $nodename;
524 next if !$d->{type
} || $d->{type
} ne 'lxc';
525 $res->{$vmid}->{type
} = 'lxc';
530 sub cfs_config_path
{
531 my ($vmid, $node) = @_;
533 $node = $nodename if !$node;
534 return "nodes/$node/lxc/$vmid.conf";
538 my ($vmid, $node) = @_;
540 my $cfspath = cfs_config_path
($vmid, $node);
541 return "/etc/pve/$cfspath";
545 my ($vmid, $node) = @_;
547 $node = $nodename if !$node;
548 my $cfspath = cfs_config_path
($vmid, $node);
550 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
551 die "container $vmid does not exists\n" if !defined($conf);
557 my ($vmid, $conf) = @_;
559 my $dir = "/etc/pve/nodes/$nodename/lxc";
562 write_config
($vmid, $conf);
568 unlink config_file
($vmid, $nodename);
572 my ($vmid, $conf) = @_;
574 my $cfspath = cfs_config_path
($vmid);
576 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
579 # flock: we use one file handle per process, so lock file
580 # can be called multiple times and succeeds for the same process.
582 my $lock_handles = {};
583 my $lockdir = "/run/lock/lxc";
588 return "$lockdir/pve-config-${vmid}.lock";
592 my ($vmid, $timeout) = @_;
594 $timeout = 10 if !$timeout;
597 my $filename = lock_filename
($vmid);
599 mkdir $lockdir if !-d
$lockdir;
601 my $lock_func = sub {
602 if (!$lock_handles->{$$}->{$filename}) {
603 my $fh = new IO
::File
(">>$filename") ||
604 die "can't open file - $!\n";
605 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
608 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
609 print STDERR
"trying to aquire lock...";
612 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
613 # try again on EINTR (see bug #273)
614 if ($success || ($! != EINTR
)) {
619 print STDERR
" failed\n";
620 die "can't aquire lock - $!\n";
623 print STDERR
" OK\n";
626 $lock_handles->{$$}->{$filename}->{refcount
}++;
629 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
632 die "can't lock file '$filename' - $err";
639 my $filename = lock_filename
($vmid);
641 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
642 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
643 if ($refcount <= 0) {
644 $lock_handles->{$$}->{$filename} = undef;
651 my ($vmid, $timeout, $code, @param) = @_;
655 lock_aquire
($vmid, $timeout);
656 eval { $res = &$code(@param) };
668 return defined($confdesc->{$name});
671 # add JSON properties for create and set function
672 sub json_config_properties
{
675 foreach my $opt (keys %$confdesc) {
676 next if $opt eq 'parent' || $opt eq 'snaptime';
677 next if $prop->{$opt};
678 $prop->{$opt} = $confdesc->{$opt};
684 sub json_config_properties_no_rootfs
{
687 foreach my $opt (keys %$confdesc) {
688 next if $prop->{$opt};
689 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
690 $prop->{$opt} = $confdesc->{$opt};
696 # container status helpers
698 sub list_active_containers
{
700 my $filename = "/proc/net/unix";
702 # similar test is used by lcxcontainers.c: list_active_containers
705 my $fh = IO
::File-
>new ($filename, "r");
708 while (defined(my $line = <$fh>)) {
709 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
711 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
722 # warning: this is slow
726 my $active_hash = list_active_containers
();
728 return 1 if defined($active_hash->{$vmid});
733 sub get_container_disk_usage
{
736 my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/'];
746 if (my ($fsid, $total, $used, $avail) = $line =~
747 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
755 eval { PVE
::Tools
::run_command
($cmd, timeout
=> 1, outfunc
=> $parser); };
764 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
766 my $active_hash = list_active_containers
();
768 foreach my $vmid (keys %$list) {
769 my $d = $list->{$vmid};
771 my $running = defined($active_hash->{$vmid});
773 $d->{status
} = $running ?
'running' : 'stopped';
775 my $cfspath = cfs_config_path
($vmid);
776 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
778 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
779 $d->{name
} =~ s/[\s]//g;
781 $d->{cpus
} = $conf->{cpulimit
} // 0;
784 my $res = get_container_disk_usage
($vmid);
785 $d->{disk
} = $res->{used
};
786 $d->{maxdisk
} = $res->{total
};
789 # use 4GB by default ??
790 if (my $rootfs = $conf->{rootfs
}) {
791 my $rootinfo = parse_ct_mountpoint
($rootfs);
792 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
794 $d->{maxdisk
} = 4*1024*1024*1024;
800 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
801 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
812 $d->{template
} = is_template
($conf);
815 foreach my $vmid (keys %$list) {
816 my $d = $list->{$vmid};
817 next if $d->{status
} ne 'running';
819 my $pid = find_lxc_pid
($vmid);
820 my $ctime = (stat("/proc/$pid"))[10]; # 10 = ctime
821 $d->{uptime
} = time - $ctime; # the method lxcfs uses
823 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
824 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
826 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
827 my @bytes = split(/\n/, $blkio_bytes);
828 foreach my $byte (@bytes) {
829 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
830 $d->{diskread
} = $2 if $key eq 'Read';
831 $d->{diskwrite
} = $2 if $key eq 'Write';
839 my $parse_size = sub {
842 return undef if $value !~ m/^(\d+(\.\d+)?)([KMG])?$/;
843 my ($size, $unit) = ($1, $3);
846 $size = $size * 1024;
847 } elsif ($unit eq 'M') {
848 $size = $size * 1024 * 1024;
849 } elsif ($unit eq 'G') {
850 $size = $size * 1024 * 1024 * 1024;
856 my $format_size = sub {
861 my $kb = int($size/1024);
862 return $size if $kb*1024 != $size;
864 my $mb = int($kb/1024);
865 return "${kb}K" if $mb*1024 != $kb;
867 my $gb = int($mb/1024);
868 return "${mb}M" if $gb*1024 != $mb;
873 sub parse_ct_mountpoint
{
879 eval { $res = PVE
::JSONSchema
::parse_property_string
($mp_desc, $data) };
885 return undef if !defined($res->{volume
});
888 return undef if !defined($res->{size
} = &$parse_size($res->{size
}));
894 sub print_ct_mountpoint
{
895 my ($info, $nomp) = @_;
896 my $skip = $nomp ?
['mp'] : [];
897 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
900 sub print_lxc_network
{
902 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
905 sub parse_lxc_network
{
910 return $res if !$data;
912 eval { $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data) };
918 $res->{type
} = 'veth';
919 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
924 sub read_cgroup_value
{
925 my ($group, $vmid, $name, $full) = @_;
927 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
929 return PVE
::Tools
::file_get_contents
($path) if $full;
931 return PVE
::Tools
::file_read_firstline
($path);
934 sub write_cgroup_value
{
935 my ($group, $vmid, $name, $value) = @_;
937 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
938 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
942 sub find_lxc_console_pids
{
946 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
949 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
952 my @args = split(/\0/, $cmdline);
954 # serach for lxc-console -n <vmid>
955 return if scalar(@args) != 3;
956 return if $args[1] ne '-n';
957 return if $args[2] !~ m/^\d+$/;
958 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
962 push @{$res->{$vmid}}, $pid;
974 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
976 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid, '-p'], outfunc
=> $parser);
978 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
983 my $ipv4_reverse_mask = [
1019 # Note: we cannot use Net:IP, because that only allows strict
1021 sub parse_ipv4_cidr
{
1022 my ($cidr, $noerr) = @_;
1024 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
1025 return { address
=> $1, netmask
=> $ipv4_reverse_mask->[$2] };
1028 return undef if $noerr;
1030 die "unable to parse ipv4 address/mask\n";
1036 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
1039 sub check_protection
{
1040 my ($vm_conf, $err_msg) = @_;
1042 if ($vm_conf->{protection
}) {
1043 die "$err_msg - protection mode enabled\n";
1047 sub update_lxc_config
{
1048 my ($storage_cfg, $vmid, $conf) = @_;
1050 my $dir = "/var/lib/lxc/$vmid";
1052 if ($conf->{template
}) {
1054 unlink "$dir/config";
1061 die "missing 'arch' - internal error" if !$conf->{arch
};
1062 $raw .= "lxc.arch = $conf->{arch}\n";
1064 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
1065 if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
1066 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
1071 if (!has_dev_console
($conf)) {
1072 $raw .= "lxc.console = none\n";
1073 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1076 my $ttycount = get_tty_count
($conf);
1077 $raw .= "lxc.tty = $ttycount\n";
1079 # some init scripts expects a linux terminal (turnkey).
1080 $raw .= "lxc.environment = TERM=linux\n";
1082 my $utsname = $conf->{hostname
} || "CT$vmid";
1083 $raw .= "lxc.utsname = $utsname\n";
1085 my $memory = $conf->{memory
} || 512;
1086 my $swap = $conf->{swap
} // 0;
1088 my $lxcmem = int($memory*1024*1024);
1089 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1091 my $lxcswap = int(($memory + $swap)*1024*1024);
1092 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1094 if (my $cpulimit = $conf->{cpulimit
}) {
1095 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1096 my $value = int(100000*$cpulimit);
1097 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1100 my $shares = $conf->{cpuunits
} || 1024;
1101 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1103 my $mountpoint = parse_ct_mountpoint
($conf->{rootfs
});
1104 $mountpoint->{mp
} = '/';
1106 my ($path, $use_loopdev) = mountpoint_mount_path
($mountpoint, $storage_cfg);
1107 $path = "loop:$path" if $use_loopdev;
1109 $raw .= "lxc.rootfs = $path\n";
1112 foreach my $k (keys %$conf) {
1113 next if $k !~ m/^net(\d+)$/;
1115 my $d = parse_lxc_network
($conf->{$k});
1117 $raw .= "lxc.network.type = veth\n";
1118 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1119 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1120 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1121 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1124 if (my $lxcconf = $conf->{lxc
}) {
1125 foreach my $entry (@$lxcconf) {
1126 my ($k, $v) = @$entry;
1127 $netcount++ if $k eq 'lxc.network.type';
1128 $raw .= "$k = $v\n";
1132 $raw .= "lxc.network.type = empty\n" if !$netcount;
1134 File
::Path
::mkpath
("$dir/rootfs");
1136 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1139 # verify and cleanup nameserver list (replace \0 with ' ')
1140 sub verify_nameserver_list
{
1141 my ($nameserver_list) = @_;
1144 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1145 PVE
::JSONSchema
::pve_verify_ip
($server);
1146 push @list, $server;
1149 return join(' ', @list);
1152 sub verify_searchdomain_list
{
1153 my ($searchdomain_list) = @_;
1156 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1157 # todo: should we add checks for valid dns domains?
1158 push @list, $server;
1161 return join(' ', @list);
1164 sub add_unused_volume
{
1165 my ($config, $volid) = @_;
1168 for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
1169 my $test = "unused$ind";
1170 if (my $vid = $config->{$test}) {
1171 return if $vid eq $volid; # do not add duplicates
1177 die "To many unused volume - please delete them first.\n" if !$key;
1179 $config->{$key} = $volid;
1184 sub update_pct_config
{
1185 my ($vmid, $conf, $running, $param, $delete) = @_;
1190 my @deleted_volumes;
1194 my $pid = find_lxc_pid
($vmid);
1195 $rootdir = "/proc/$pid/root";
1198 if (defined($delete)) {
1199 foreach my $opt (@$delete) {
1200 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1201 die "unable to delete required option '$opt'\n";
1202 } elsif ($opt eq 'swap') {
1203 delete $conf->{$opt};
1204 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1205 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1206 delete $conf->{$opt};
1207 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1208 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1209 delete $conf->{$opt};
1210 push @nohotplug, $opt;
1212 } elsif ($opt =~ m/^net(\d)$/) {
1213 delete $conf->{$opt};
1216 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1217 } elsif ($opt eq 'protection') {
1218 delete $conf->{$opt};
1219 } elsif ($opt =~ m/^unused(\d+)$/) {
1220 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1221 push @deleted_volumes, $conf->{$opt};
1222 delete $conf->{$opt};
1223 push @nohotplug, $opt;
1225 } elsif ($opt =~ m/^mp(\d+)$/) {
1226 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1227 my $mountpoint = parse_ct_mountpoint
($conf->{$opt});
1228 add_unused_volume
($conf, $mountpoint->{volume
});
1229 delete $conf->{$opt};
1230 push @nohotplug, $opt;
1235 write_config
($vmid, $conf) if $running;
1239 # There's no separate swap size to configure, there's memory and "total"
1240 # memory (iow. memory+swap). This means we have to change them together.
1241 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1242 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1243 if (defined($wanted_memory) || defined($wanted_swap)) {
1245 $wanted_memory //= ($conf->{memory
} || 512);
1246 $wanted_swap //= ($conf->{swap
} || 0);
1248 my $total = $wanted_memory + $wanted_swap;
1250 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1251 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1253 $conf->{memory
} = $wanted_memory;
1254 $conf->{swap
} = $wanted_swap;
1256 write_config
($vmid, $conf) if $running;
1259 foreach my $opt (keys %$param) {
1260 my $value = $param->{$opt};
1261 if ($opt eq 'hostname') {
1262 $conf->{$opt} = $value;
1263 } elsif ($opt eq 'onboot') {
1264 $conf->{$opt} = $value ?
1 : 0;
1265 } elsif ($opt eq 'startup') {
1266 $conf->{$opt} = $value;
1267 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1268 $conf->{$opt} = $value;
1269 push @nohotplug, $opt;
1271 } elsif ($opt eq 'nameserver') {
1272 my $list = verify_nameserver_list
($value);
1273 $conf->{$opt} = $list;
1274 push @nohotplug, $opt;
1276 } elsif ($opt eq 'searchdomain') {
1277 my $list = verify_searchdomain_list
($value);
1278 $conf->{$opt} = $list;
1279 push @nohotplug, $opt;
1281 } elsif ($opt eq 'cpulimit') {
1282 $conf->{$opt} = $value;
1283 push @nohotplug, $opt; # fixme: hotplug
1285 } elsif ($opt eq 'cpuunits') {
1286 $conf->{$opt} = $value;
1287 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1288 } elsif ($opt eq 'description') {
1289 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1290 } elsif ($opt =~ m/^net(\d+)$/) {
1292 my $net = parse_lxc_network
($value);
1294 $conf->{$opt} = print_lxc_network
($net);
1296 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1298 } elsif ($opt eq 'protection') {
1299 $conf->{$opt} = $value ?
1 : 0;
1300 } elsif ($opt =~ m/^mp(\d+)$/) {
1301 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1302 $conf->{$opt} = $value;
1304 push @nohotplug, $opt;
1306 } elsif ($opt eq 'rootfs') {
1307 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1308 die "implement me: $opt";
1310 die "implement me: $opt";
1312 write_config
($vmid, $conf) if $running;
1315 if ($running && scalar(@nohotplug)) {
1316 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1319 if (@deleted_volumes) {
1320 my $storage_cfg = PVE
::Storage
::config
();
1321 foreach my $volume (@deleted_volumes) {
1322 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1327 my $storage_cfg = PVE
::Storage
::config
();
1328 create_disks
($storage_cfg, $vmid, $conf, $conf);
1332 sub has_dev_console
{
1335 return !(defined($conf->{console
}) && !$conf->{console
});
1341 return $conf->{tty
} // $confdesc->{tty
}->{default};
1347 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1350 sub get_console_command
{
1351 my ($vmid, $conf) = @_;
1353 my $cmode = get_cmode
($conf);
1355 if ($cmode eq 'console') {
1356 return ['lxc-console', '-n', $vmid, '-t', 0];
1357 } elsif ($cmode eq 'tty') {
1358 return ['lxc-console', '-n', $vmid];
1359 } elsif ($cmode eq 'shell') {
1360 return ['lxc-attach', '--clear-env', '-n', $vmid];
1362 die "internal error";
1366 sub get_primary_ips
{
1369 # return data from net0
1371 return undef if !defined($conf->{net0
});
1372 my $net = parse_lxc_network
($conf->{net0
});
1374 my $ipv4 = $net->{ip
};
1376 if ($ipv4 =~ /^(dhcp|manual)$/) {
1382 my $ipv6 = $net->{ip6
};
1384 if ($ipv6 =~ /^(dhcp|manual)$/) {
1391 return ($ipv4, $ipv6);
1394 sub delete_mountpoint_volume
{
1395 my ($storage_cfg, $vmid, $volume) = @_;
1397 # skip bind mounts and block devices
1398 if ($volume =~ m
|^/|) {
1402 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1403 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1406 sub destroy_lxc_container
{
1407 my ($storage_cfg, $vmid, $conf) = @_;
1409 foreach_mountpoint
($conf, sub {
1410 my ($ms, $mountpoint) = @_;
1411 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1414 rmdir "/var/lib/lxc/$vmid/rootfs";
1415 unlink "/var/lib/lxc/$vmid/config";
1416 rmdir "/var/lib/lxc/$vmid";
1417 destroy_config
($vmid);
1419 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1420 #PVE::Tools::run_command($cmd);
1423 sub vm_stop_cleanup
{
1424 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1429 my $vollist = get_vm_volumes
($conf);
1430 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1433 warn $@ if $@; # avoid errors - just warn
1436 my $safe_num_ne = sub {
1439 return 0 if !defined($a) && !defined($b);
1440 return 1 if !defined($a);
1441 return 1 if !defined($b);
1446 my $safe_string_ne = sub {
1449 return 0 if !defined($a) && !defined($b);
1450 return 1 if !defined($a);
1451 return 1 if !defined($b);
1457 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1459 if ($newnet->{type
} ne 'veth') {
1460 # for when there are physical interfaces
1461 die "cannot update interface of type $newnet->{type}";
1464 my $veth = "veth${vmid}i${netid}";
1465 my $eth = $newnet->{name
};
1467 if (my $oldnetcfg = $conf->{$opt}) {
1468 my $oldnet = parse_lxc_network
($oldnetcfg);
1470 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1471 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1473 PVE
::Network
::veth_delete
($veth);
1474 delete $conf->{$opt};
1475 write_config
($vmid, $conf);
1477 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1479 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1480 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1481 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1483 if ($oldnet->{bridge
}) {
1484 PVE
::Network
::tap_unplug
($veth);
1485 foreach (qw(bridge tag firewall)) {
1486 delete $oldnet->{$_};
1488 $conf->{$opt} = print_lxc_network
($oldnet);
1489 write_config
($vmid, $conf);
1492 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1493 foreach (qw(bridge tag firewall)) {
1494 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1496 $conf->{$opt} = print_lxc_network
($oldnet);
1497 write_config
($vmid, $conf);
1500 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1503 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1507 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1509 my $veth = "veth${vmid}i${netid}";
1510 my $vethpeer = $veth . "p";
1511 my $eth = $newnet->{name
};
1513 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1514 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1516 # attach peer in container
1517 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1518 PVE
::Tools
::run_command
($cmd);
1520 # link up peer in container
1521 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1522 PVE
::Tools
::run_command
($cmd);
1524 my $done = { type
=> 'veth' };
1525 foreach (qw(bridge tag firewall hwaddr name)) {
1526 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1528 $conf->{$opt} = print_lxc_network
($done);
1530 write_config
($vmid, $conf);
1533 sub update_ipconfig
{
1534 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1536 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1538 my $optdata = parse_lxc_network
($conf->{$opt});
1542 my $cmdargs = shift;
1543 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1545 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1547 my $change_ip_config = sub {
1548 my ($ipversion) = @_;
1550 my $family_opt = "-$ipversion";
1551 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1552 my $gw= "gw$suffix";
1553 my $ip= "ip$suffix";
1555 my $newip = $newnet->{$ip};
1556 my $newgw = $newnet->{$gw};
1557 my $oldip = $optdata->{$ip};
1559 my $change_ip = &$safe_string_ne($oldip, $newip);
1560 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1562 return if !$change_ip && !$change_gw;
1564 # step 1: add new IP, if this fails we cancel
1565 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1566 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1573 # step 2: replace gateway
1574 # If this fails we delete the added IP and cancel.
1575 # If it succeeds we save the config and delete the old IP, ignoring
1576 # errors. The config is then saved.
1577 # Note: 'ip route replace' can add
1580 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1583 # the route was not replaced, the old IP is still available
1584 # rollback (delete new IP) and cancel
1586 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1587 warn $@ if $@; # no need to die here
1592 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1593 # if the route was not deleted, the guest might have deleted it manually
1599 # from this point on we save the configuration
1600 # step 3: delete old IP ignoring errors
1601 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1602 # We need to enable promote_secondaries, otherwise our newly added
1603 # address will be removed along with the old one.
1606 if ($ipversion == 4) {
1607 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1608 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1609 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1611 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1613 warn $@ if $@; # no need to die here
1615 if ($ipversion == 4) {
1616 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1620 foreach my $property ($ip, $gw) {
1621 if ($newnet->{$property}) {
1622 $optdata->{$property} = $newnet->{$property};
1624 delete $optdata->{$property};
1627 $conf->{$opt} = print_lxc_network
($optdata);
1628 write_config
($vmid, $conf);
1629 $lxc_setup->setup_network($conf);
1632 &$change_ip_config(4);
1633 &$change_ip_config(6);
1637 # Internal snapshots
1639 # NOTE: Snapshot create/delete involves several non-atomic
1640 # action, and can take a long time.
1641 # So we try to avoid locking the file and use 'lock' variable
1642 # inside the config file instead.
1644 my $snapshot_copy_config = sub {
1645 my ($source, $dest) = @_;
1647 foreach my $k (keys %$source) {
1648 next if $k eq 'snapshots';
1649 next if $k eq 'snapstate';
1650 next if $k eq 'snaptime';
1651 next if $k eq 'vmstate';
1652 next if $k eq 'lock';
1653 next if $k eq 'digest';
1654 next if $k eq 'description';
1656 $dest->{$k} = $source->{$k};
1660 my $snapshot_prepare = sub {
1661 my ($vmid, $snapname, $comment) = @_;
1665 my $updatefn = sub {
1667 my $conf = load_config
($vmid);
1669 die "you can't take a snapshot if it's a template\n"
1670 if is_template
($conf);
1674 $conf->{lock} = 'snapshot';
1676 die "snapshot name '$snapname' already used\n"
1677 if defined($conf->{snapshots
}->{$snapname});
1679 my $storecfg = PVE
::Storage
::config
();
1680 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1682 $snap = $conf->{snapshots
}->{$snapname} = {};
1684 &$snapshot_copy_config($conf, $snap);
1686 $snap->{'snapstate'} = "prepare";
1687 $snap->{'snaptime'} = time();
1688 $snap->{'description'} = $comment if $comment;
1689 $conf->{snapshots
}->{$snapname} = $snap;
1691 write_config
($vmid, $conf);
1694 lock_container
($vmid, 10, $updatefn);
1699 my $snapshot_commit = sub {
1700 my ($vmid, $snapname) = @_;
1702 my $updatefn = sub {
1704 my $conf = load_config
($vmid);
1706 die "missing snapshot lock\n"
1707 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1709 die "snapshot '$snapname' does not exist\n"
1710 if !defined($conf->{snapshots
}->{$snapname});
1712 die "wrong snapshot state\n"
1713 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1714 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1716 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1717 delete $conf->{lock};
1718 $conf->{parent
} = $snapname;
1720 write_config
($vmid, $conf);
1723 lock_container
($vmid, 10 ,$updatefn);
1727 my ($feature, $conf, $storecfg, $snapname) = @_;
1731 foreach_mountpoint
($conf, sub {
1732 my ($ms, $mountpoint) = @_;
1734 return if $err; # skip further test
1736 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1738 # TODO: implement support for mountpoints
1739 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1743 return $err ?
0 : 1;
1746 sub snapshot_create
{
1747 my ($vmid, $snapname, $comment) = @_;
1749 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1751 my $conf = load_config
($vmid);
1753 my $running = check_running
($vmid);
1756 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1757 PVE
::Tools
::run_command
(['/bin/sync']);
1760 my $storecfg = PVE
::Storage
::config
();
1761 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1762 my $volid = $rootinfo->{volume
};
1765 PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]);
1768 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1769 &$snapshot_commit($vmid, $snapname);
1772 snapshot_delete
($vmid, $snapname, 1);
1777 sub snapshot_delete
{
1778 my ($vmid, $snapname, $force) = @_;
1784 my $updatefn = sub {
1786 $conf = load_config
($vmid);
1788 die "you can't delete a snapshot if vm is a template\n"
1789 if is_template
($conf);
1791 $snap = $conf->{snapshots
}->{$snapname};
1795 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1797 $snap->{snapstate
} = 'delete';
1799 write_config
($vmid, $conf);
1802 lock_container
($vmid, 10, $updatefn);
1804 my $storecfg = PVE
::Storage
::config
();
1806 my $del_snap = sub {
1810 if ($conf->{parent
} eq $snapname) {
1811 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1812 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1814 delete $conf->{parent
};
1818 delete $conf->{snapshots
}->{$snapname};
1820 write_config
($vmid, $conf);
1823 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1824 my $rootinfo = parse_ct_mountpoint
($rootfs);
1825 my $volid = $rootinfo->{volume
};
1828 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1832 if(!$err || ($err && $force)) {
1833 lock_container
($vmid, 10, $del_snap);
1835 die "Can't delete snapshot: $vmid $snapname $err\n";
1840 sub snapshot_rollback
{
1841 my ($vmid, $snapname) = @_;
1843 my $storecfg = PVE
::Storage
::config
();
1845 my $conf = load_config
($vmid);
1847 die "you can't rollback if vm is a template\n" if is_template
($conf);
1849 my $snap = $conf->{snapshots
}->{$snapname};
1851 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1853 my $rootfs = $snap->{rootfs
};
1854 my $rootinfo = parse_ct_mountpoint
($rootfs);
1855 my $volid = $rootinfo->{volume
};
1857 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1859 my $updatefn = sub {
1861 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1862 if $snap->{snapstate
};
1866 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1868 die "unable to rollback vm $vmid: vm is running\n"
1869 if check_running
($vmid);
1871 $conf->{lock} = 'rollback';
1875 # copy snapshot config to current config
1877 my $tmp_conf = $conf;
1878 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1879 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1880 delete $conf->{snaptime
};
1881 delete $conf->{snapname
};
1882 $conf->{parent
} = $snapname;
1884 write_config
($vmid, $conf);
1887 my $unlockfn = sub {
1888 delete $conf->{lock};
1889 write_config
($vmid, $conf);
1892 lock_container
($vmid, 10, $updatefn);
1894 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1896 lock_container
($vmid, 5, $unlockfn);
1899 sub template_create
{
1900 my ($vmid, $conf) = @_;
1902 my $storecfg = PVE
::Storage
::config
();
1904 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1905 my $volid = $rootinfo->{volume
};
1907 die "Template feature is not available for '$volid'\n"
1908 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1910 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1912 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1913 $rootinfo->{volume
} = $template_volid;
1914 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1916 write_config
($vmid, $conf);
1922 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1925 sub mountpoint_names
{
1928 my @names = ('rootfs');
1930 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1931 push @names, "mp$i";
1934 return $reverse ?
reverse @names : @names;
1937 # The container might have *different* symlinks than the host. realpath/abs_path
1938 # use the actual filesystem to resolve links.
1939 sub sanitize_mountpoint
{
1941 $mp = '/' . $mp; # we always start with a slash
1942 $mp =~ s
@/{2,}@/@g; # collapse sequences of slashes
1943 $mp =~ s
@/\./@@g; # collapse /./
1944 $mp =~ s
@/\.(/)?
$@$1@; # collapse a trailing /. or /./
1945 $mp =~ s
@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
1946 $mp =~ s
@/\.\
.(/)?
$@$1@; # collapse trailing /.. or /../ disregarding symlinks
1950 sub foreach_mountpoint_full
{
1951 my ($conf, $reverse, $func) = @_;
1953 foreach my $key (mountpoint_names
($reverse)) {
1954 my $value = $conf->{$key};
1955 next if !defined($value);
1956 my $mountpoint = parse_ct_mountpoint
($value);
1958 # just to be sure: rootfs is /
1959 my $path = $key eq 'rootfs' ?
'/' : $mountpoint->{mp
};
1960 $mountpoint->{mp
} = sanitize_mountpoint
($path);
1962 $path = $mountpoint->{volume
};
1963 $mountpoint->{volume
} = sanitize_mountpoint
($path) if $path =~ m
|^/|;
1965 &$func($key, $mountpoint);
1969 sub foreach_mountpoint
{
1970 my ($conf, $func) = @_;
1972 foreach_mountpoint_full
($conf, 0, $func);
1975 sub foreach_mountpoint_reverse
{
1976 my ($conf, $func) = @_;
1978 foreach_mountpoint_full
($conf, 1, $func);
1981 sub check_ct_modify_config_perm
{
1982 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1984 return 1 if $authuser ne 'root@pam';
1986 foreach my $opt (@$key_list) {
1988 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1989 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1990 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
1991 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1992 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1993 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1994 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1995 $opt eq 'searchdomain' || $opt eq 'hostname') {
1996 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1998 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
2006 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
2008 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2009 my $volid_list = get_vm_volumes
($conf);
2011 foreach_mountpoint_reverse
($conf, sub {
2012 my ($ms, $mountpoint) = @_;
2014 my $volid = $mountpoint->{volume
};
2015 my $mount = $mountpoint->{mp
};
2017 return if !$volid || !$mount;
2019 my $mount_path = "$rootdir/$mount";
2020 $mount_path =~ s!/+!/!g;
2022 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
2025 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2038 my ($vmid, $storage_cfg, $conf) = @_;
2040 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2041 File
::Path
::make_path
($rootdir);
2043 my $volid_list = get_vm_volumes
($conf);
2044 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2047 foreach_mountpoint
($conf, sub {
2048 my ($ms, $mountpoint) = @_;
2050 my $volid = $mountpoint->{volume
};
2051 my $mount = $mountpoint->{mp
};
2053 return if !$volid || !$mount;
2055 my $image_path = PVE
::Storage
::path
($storage_cfg, $volid);
2056 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2057 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2059 die "unable to mount base volume - internal error" if $isBase;
2061 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2065 warn "mounting container failed - $err";
2066 umount_all
($vmid, $storage_cfg, $conf, 1);
2073 sub mountpoint_mount_path
{
2074 my ($mountpoint, $storage_cfg, $snapname) = @_;
2076 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2079 my $check_mount_path = sub {
2081 $path = File
::Spec-
>canonpath($path);
2082 my $real = Cwd
::realpath
($path);
2083 if ($real ne $path) {
2084 die "mount path modified by symlink: $path != $real";
2088 # use $rootdir = undef to just return the corresponding mount path
2089 sub mountpoint_mount
{
2090 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2092 my $volid = $mountpoint->{volume
};
2093 my $mount = $mountpoint->{mp
};
2095 return if !$volid || !$mount;
2099 if (defined($rootdir)) {
2100 $rootdir =~ s!/+$!!;
2101 $mount_path = "$rootdir/$mount";
2102 $mount_path =~ s!/+!/!g;
2103 &$check_mount_path($mount_path);
2104 File
::Path
::mkpath
($mount_path);
2107 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2109 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2113 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2114 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2116 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2117 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2119 if ($format eq 'subvol') {
2122 if ($scfg->{type
} eq 'zfspool') {
2123 my $path_arg = $path;
2124 $path_arg =~ s!^/+!!;
2125 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', '-t', 'zfs', $path_arg, $mount_path]);
2127 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2130 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
2133 return wantarray ?
($path, 0) : $path;
2134 } elsif ($format eq 'raw') {
2135 my $use_loopdev = 0;
2137 if ($scfg->{path
}) {
2138 push @extra_opts, '-o', 'loop';
2140 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'rbd') {
2143 die "unsupported storage type '$scfg->{type}'\n";
2146 if ($isBase || defined($snapname)) {
2147 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2149 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2152 return wantarray ?
($path, $use_loopdev) : $path;
2154 die "unsupported image format '$format'\n";
2156 } elsif ($volid =~ m
|^/dev/.+|) {
2157 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
2158 return wantarray ?
($volid, 0) : $volid;
2159 } elsif ($volid !~ m
|^/dev/.+| && $volid =~ m
|^/.+| && -d
$volid) {
2160 &$check_mount_path($volid);
2161 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2162 return wantarray ?
($volid, 0) : $volid;
2165 die "unsupported storage";
2168 sub get_vm_volumes
{
2169 my ($conf, $excludes) = @_;
2173 foreach_mountpoint
($conf, sub {
2174 my ($ms, $mountpoint) = @_;
2176 return if $excludes && $ms eq $excludes;
2178 my $volid = $mountpoint->{volume
};
2180 return if !$volid || $volid =~ m
|^/|;
2182 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2185 push @$vollist, $volid;
2194 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp', $dev]);
2198 my ($storage_cfg, $volid) = @_;
2200 if ($volid =~ m!^/dev/.+!) {
2205 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2207 die "cannot format volume '$volid' with no storage\n" if !$storage;
2209 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2211 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2213 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2214 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2216 die "cannot format volume '$volid' (format == $format)\n"
2217 if $format ne 'raw';
2223 my ($storecfg, $vollist) = @_;
2225 foreach my $volid (@$vollist) {
2226 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2232 my ($storecfg, $vmid, $settings, $conf) = @_;
2237 foreach_mountpoint
($settings, sub {
2238 my ($ms, $mountpoint) = @_;
2240 my $volid = $mountpoint->{volume
};
2241 my $mp = $mountpoint->{mp
};
2243 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2245 return if !$storage;
2247 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
2248 my ($storeid, $size_gb) = ($1, $2);
2250 my $size_kb = int(${size_gb
}*1024) * 1024;
2252 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2253 # fixme: use better naming ct-$vmid-disk-X.raw?
2255 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2257 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2259 format_disk
($storecfg, $volid);
2261 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2264 } elsif ($scfg->{type
} eq 'zfspool') {
2266 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2268 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm') {
2270 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2271 format_disk
($storecfg, $volid);
2273 } elsif ($scfg->{type
} eq 'rbd') {
2275 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2276 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2277 format_disk
($storecfg, $volid);
2279 die "unable to create containers on storage type '$scfg->{type}'\n";
2281 push @$vollist, $volid;
2282 my $new_mountpoint = { volume
=> $volid, size
=> $size_kb*1024, mp
=> $mp };
2283 $conf->{$ms} = print_ct_mountpoint
($new_mountpoint, $ms eq 'rootfs');
2285 # use specified/existing volid
2289 # free allocated images on error
2291 destroy_disks
($storecfg, $vollist);
2297 # bash completion helper
2299 sub complete_os_templates
{
2300 my ($cmdname, $pname, $cvalue) = @_;
2302 my $cfg = PVE
::Storage
::config
();
2306 if ($cvalue =~ m/^([^:]+):/) {
2310 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2311 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2314 foreach my $id (keys %$data) {
2315 foreach my $item (@{$data->{$id}}) {
2316 push @$res, $item->{volid
} if defined($item->{volid
});
2323 my $complete_ctid_full = sub {
2326 my $idlist = vmstatus
();
2328 my $active_hash = list_active_containers
();
2332 foreach my $id (keys %$idlist) {
2333 my $d = $idlist->{$id};
2334 if (defined($running)) {
2335 next if $d->{template
};
2336 next if $running && !$active_hash->{$id};
2337 next if !$running && $active_hash->{$id};
2346 return &$complete_ctid_full();
2349 sub complete_ctid_stopped
{
2350 return &$complete_ctid_full(0);
2353 sub complete_ctid_running
{
2354 return &$complete_ctid_full(1);