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 (@deleted_volumes) {
1316 my $storage_cfg = PVE
::Storage
::config
();
1317 foreach my $volume (@deleted_volumes) {
1318 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1323 my $storage_cfg = PVE
::Storage
::config
();
1324 create_disks
($storage_cfg, $vmid, $conf, $conf);
1327 # This should be the last thing we do here
1328 if ($running && scalar(@nohotplug)) {
1329 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1333 sub has_dev_console
{
1336 return !(defined($conf->{console
}) && !$conf->{console
});
1342 return $conf->{tty
} // $confdesc->{tty
}->{default};
1348 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1351 sub get_console_command
{
1352 my ($vmid, $conf) = @_;
1354 my $cmode = get_cmode
($conf);
1356 if ($cmode eq 'console') {
1357 return ['lxc-console', '-n', $vmid, '-t', 0];
1358 } elsif ($cmode eq 'tty') {
1359 return ['lxc-console', '-n', $vmid];
1360 } elsif ($cmode eq 'shell') {
1361 return ['lxc-attach', '--clear-env', '-n', $vmid];
1363 die "internal error";
1367 sub get_primary_ips
{
1370 # return data from net0
1372 return undef if !defined($conf->{net0
});
1373 my $net = parse_lxc_network
($conf->{net0
});
1375 my $ipv4 = $net->{ip
};
1377 if ($ipv4 =~ /^(dhcp|manual)$/) {
1383 my $ipv6 = $net->{ip6
};
1385 if ($ipv6 =~ /^(dhcp|manual)$/) {
1392 return ($ipv4, $ipv6);
1395 sub delete_mountpoint_volume
{
1396 my ($storage_cfg, $vmid, $volume) = @_;
1398 # skip bind mounts and block devices
1399 if ($volume =~ m
|^/|) {
1403 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1404 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1407 sub destroy_lxc_container
{
1408 my ($storage_cfg, $vmid, $conf) = @_;
1410 foreach_mountpoint
($conf, sub {
1411 my ($ms, $mountpoint) = @_;
1412 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1415 rmdir "/var/lib/lxc/$vmid/rootfs";
1416 unlink "/var/lib/lxc/$vmid/config";
1417 rmdir "/var/lib/lxc/$vmid";
1418 destroy_config
($vmid);
1420 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1421 #PVE::Tools::run_command($cmd);
1424 sub vm_stop_cleanup
{
1425 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1430 my $vollist = get_vm_volumes
($conf);
1431 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1434 warn $@ if $@; # avoid errors - just warn
1437 my $safe_num_ne = sub {
1440 return 0 if !defined($a) && !defined($b);
1441 return 1 if !defined($a);
1442 return 1 if !defined($b);
1447 my $safe_string_ne = sub {
1450 return 0 if !defined($a) && !defined($b);
1451 return 1 if !defined($a);
1452 return 1 if !defined($b);
1458 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1460 if ($newnet->{type
} ne 'veth') {
1461 # for when there are physical interfaces
1462 die "cannot update interface of type $newnet->{type}";
1465 my $veth = "veth${vmid}i${netid}";
1466 my $eth = $newnet->{name
};
1468 if (my $oldnetcfg = $conf->{$opt}) {
1469 my $oldnet = parse_lxc_network
($oldnetcfg);
1471 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1472 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1474 PVE
::Network
::veth_delete
($veth);
1475 delete $conf->{$opt};
1476 write_config
($vmid, $conf);
1478 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1480 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1481 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1482 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1484 if ($oldnet->{bridge
}) {
1485 PVE
::Network
::tap_unplug
($veth);
1486 foreach (qw(bridge tag firewall)) {
1487 delete $oldnet->{$_};
1489 $conf->{$opt} = print_lxc_network
($oldnet);
1490 write_config
($vmid, $conf);
1493 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1494 foreach (qw(bridge tag firewall)) {
1495 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1497 $conf->{$opt} = print_lxc_network
($oldnet);
1498 write_config
($vmid, $conf);
1501 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1504 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1508 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1510 my $veth = "veth${vmid}i${netid}";
1511 my $vethpeer = $veth . "p";
1512 my $eth = $newnet->{name
};
1514 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1515 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1517 # attach peer in container
1518 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1519 PVE
::Tools
::run_command
($cmd);
1521 # link up peer in container
1522 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1523 PVE
::Tools
::run_command
($cmd);
1525 my $done = { type
=> 'veth' };
1526 foreach (qw(bridge tag firewall hwaddr name)) {
1527 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1529 $conf->{$opt} = print_lxc_network
($done);
1531 write_config
($vmid, $conf);
1534 sub update_ipconfig
{
1535 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1537 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1539 my $optdata = parse_lxc_network
($conf->{$opt});
1543 my $cmdargs = shift;
1544 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1546 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1548 my $change_ip_config = sub {
1549 my ($ipversion) = @_;
1551 my $family_opt = "-$ipversion";
1552 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1553 my $gw= "gw$suffix";
1554 my $ip= "ip$suffix";
1556 my $newip = $newnet->{$ip};
1557 my $newgw = $newnet->{$gw};
1558 my $oldip = $optdata->{$ip};
1560 my $change_ip = &$safe_string_ne($oldip, $newip);
1561 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1563 return if !$change_ip && !$change_gw;
1565 # step 1: add new IP, if this fails we cancel
1566 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1567 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1574 # step 2: replace gateway
1575 # If this fails we delete the added IP and cancel.
1576 # If it succeeds we save the config and delete the old IP, ignoring
1577 # errors. The config is then saved.
1578 # Note: 'ip route replace' can add
1581 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1584 # the route was not replaced, the old IP is still available
1585 # rollback (delete new IP) and cancel
1587 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1588 warn $@ if $@; # no need to die here
1593 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1594 # if the route was not deleted, the guest might have deleted it manually
1600 # from this point on we save the configuration
1601 # step 3: delete old IP ignoring errors
1602 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1603 # We need to enable promote_secondaries, otherwise our newly added
1604 # address will be removed along with the old one.
1607 if ($ipversion == 4) {
1608 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1609 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1610 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1612 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1614 warn $@ if $@; # no need to die here
1616 if ($ipversion == 4) {
1617 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1621 foreach my $property ($ip, $gw) {
1622 if ($newnet->{$property}) {
1623 $optdata->{$property} = $newnet->{$property};
1625 delete $optdata->{$property};
1628 $conf->{$opt} = print_lxc_network
($optdata);
1629 write_config
($vmid, $conf);
1630 $lxc_setup->setup_network($conf);
1633 &$change_ip_config(4);
1634 &$change_ip_config(6);
1638 # Internal snapshots
1640 # NOTE: Snapshot create/delete involves several non-atomic
1641 # action, and can take a long time.
1642 # So we try to avoid locking the file and use 'lock' variable
1643 # inside the config file instead.
1645 my $snapshot_copy_config = sub {
1646 my ($source, $dest) = @_;
1648 foreach my $k (keys %$source) {
1649 next if $k eq 'snapshots';
1650 next if $k eq 'snapstate';
1651 next if $k eq 'snaptime';
1652 next if $k eq 'vmstate';
1653 next if $k eq 'lock';
1654 next if $k eq 'digest';
1655 next if $k eq 'description';
1657 $dest->{$k} = $source->{$k};
1661 my $snapshot_prepare = sub {
1662 my ($vmid, $snapname, $comment) = @_;
1666 my $updatefn = sub {
1668 my $conf = load_config
($vmid);
1670 die "you can't take a snapshot if it's a template\n"
1671 if is_template
($conf);
1675 $conf->{lock} = 'snapshot';
1677 die "snapshot name '$snapname' already used\n"
1678 if defined($conf->{snapshots
}->{$snapname});
1680 my $storecfg = PVE
::Storage
::config
();
1681 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1683 $snap = $conf->{snapshots
}->{$snapname} = {};
1685 &$snapshot_copy_config($conf, $snap);
1687 $snap->{'snapstate'} = "prepare";
1688 $snap->{'snaptime'} = time();
1689 $snap->{'description'} = $comment if $comment;
1690 $conf->{snapshots
}->{$snapname} = $snap;
1692 write_config
($vmid, $conf);
1695 lock_container
($vmid, 10, $updatefn);
1700 my $snapshot_commit = sub {
1701 my ($vmid, $snapname) = @_;
1703 my $updatefn = sub {
1705 my $conf = load_config
($vmid);
1707 die "missing snapshot lock\n"
1708 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1710 die "snapshot '$snapname' does not exist\n"
1711 if !defined($conf->{snapshots
}->{$snapname});
1713 die "wrong snapshot state\n"
1714 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1715 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1717 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1718 delete $conf->{lock};
1719 $conf->{parent
} = $snapname;
1721 write_config
($vmid, $conf);
1724 lock_container
($vmid, 10 ,$updatefn);
1728 my ($feature, $conf, $storecfg, $snapname) = @_;
1732 foreach_mountpoint
($conf, sub {
1733 my ($ms, $mountpoint) = @_;
1735 return if $err; # skip further test
1737 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1739 # TODO: implement support for mountpoints
1740 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1744 return $err ?
0 : 1;
1747 sub snapshot_create
{
1748 my ($vmid, $snapname, $comment) = @_;
1750 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1752 my $conf = load_config
($vmid);
1754 my $running = check_running
($vmid);
1757 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1758 PVE
::Tools
::run_command
(['/bin/sync']);
1761 my $storecfg = PVE
::Storage
::config
();
1762 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1763 my $volid = $rootinfo->{volume
};
1766 PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]);
1769 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1770 &$snapshot_commit($vmid, $snapname);
1773 snapshot_delete
($vmid, $snapname, 1);
1778 sub snapshot_delete
{
1779 my ($vmid, $snapname, $force) = @_;
1785 my $updatefn = sub {
1787 $conf = load_config
($vmid);
1789 die "you can't delete a snapshot if vm is a template\n"
1790 if is_template
($conf);
1792 $snap = $conf->{snapshots
}->{$snapname};
1796 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1798 $snap->{snapstate
} = 'delete';
1800 write_config
($vmid, $conf);
1803 lock_container
($vmid, 10, $updatefn);
1805 my $storecfg = PVE
::Storage
::config
();
1807 my $del_snap = sub {
1811 if ($conf->{parent
} eq $snapname) {
1812 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1813 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1815 delete $conf->{parent
};
1819 delete $conf->{snapshots
}->{$snapname};
1821 write_config
($vmid, $conf);
1824 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1825 my $rootinfo = parse_ct_mountpoint
($rootfs);
1826 my $volid = $rootinfo->{volume
};
1829 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1833 if(!$err || ($err && $force)) {
1834 lock_container
($vmid, 10, $del_snap);
1836 die "Can't delete snapshot: $vmid $snapname $err\n";
1841 sub snapshot_rollback
{
1842 my ($vmid, $snapname) = @_;
1844 my $storecfg = PVE
::Storage
::config
();
1846 my $conf = load_config
($vmid);
1848 die "you can't rollback if vm is a template\n" if is_template
($conf);
1850 my $snap = $conf->{snapshots
}->{$snapname};
1852 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1854 my $rootfs = $snap->{rootfs
};
1855 my $rootinfo = parse_ct_mountpoint
($rootfs);
1856 my $volid = $rootinfo->{volume
};
1858 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1860 my $updatefn = sub {
1862 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1863 if $snap->{snapstate
};
1867 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1869 die "unable to rollback vm $vmid: vm is running\n"
1870 if check_running
($vmid);
1872 $conf->{lock} = 'rollback';
1876 # copy snapshot config to current config
1878 my $tmp_conf = $conf;
1879 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1880 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1881 delete $conf->{snaptime
};
1882 delete $conf->{snapname
};
1883 $conf->{parent
} = $snapname;
1885 write_config
($vmid, $conf);
1888 my $unlockfn = sub {
1889 delete $conf->{lock};
1890 write_config
($vmid, $conf);
1893 lock_container
($vmid, 10, $updatefn);
1895 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1897 lock_container
($vmid, 5, $unlockfn);
1900 sub template_create
{
1901 my ($vmid, $conf) = @_;
1903 my $storecfg = PVE
::Storage
::config
();
1905 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1906 my $volid = $rootinfo->{volume
};
1908 die "Template feature is not available for '$volid'\n"
1909 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1911 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1913 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1914 $rootinfo->{volume
} = $template_volid;
1915 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1917 write_config
($vmid, $conf);
1923 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1926 sub mountpoint_names
{
1929 my @names = ('rootfs');
1931 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1932 push @names, "mp$i";
1935 return $reverse ?
reverse @names : @names;
1938 # The container might have *different* symlinks than the host. realpath/abs_path
1939 # use the actual filesystem to resolve links.
1940 sub sanitize_mountpoint
{
1942 $mp = '/' . $mp; # we always start with a slash
1943 $mp =~ s
@/{2,}@/@g; # collapse sequences of slashes
1944 $mp =~ s
@/\./@@g; # collapse /./
1945 $mp =~ s
@/\.(/)?
$@$1@; # collapse a trailing /. or /./
1946 $mp =~ s
@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
1947 $mp =~ s
@/\.\
.(/)?
$@$1@; # collapse trailing /.. or /../ disregarding symlinks
1951 sub foreach_mountpoint_full
{
1952 my ($conf, $reverse, $func) = @_;
1954 foreach my $key (mountpoint_names
($reverse)) {
1955 my $value = $conf->{$key};
1956 next if !defined($value);
1957 my $mountpoint = parse_ct_mountpoint
($value);
1959 # just to be sure: rootfs is /
1960 my $path = $key eq 'rootfs' ?
'/' : $mountpoint->{mp
};
1961 $mountpoint->{mp
} = sanitize_mountpoint
($path);
1963 $path = $mountpoint->{volume
};
1964 $mountpoint->{volume
} = sanitize_mountpoint
($path) if $path =~ m
|^/|;
1966 &$func($key, $mountpoint);
1970 sub foreach_mountpoint
{
1971 my ($conf, $func) = @_;
1973 foreach_mountpoint_full
($conf, 0, $func);
1976 sub foreach_mountpoint_reverse
{
1977 my ($conf, $func) = @_;
1979 foreach_mountpoint_full
($conf, 1, $func);
1982 sub check_ct_modify_config_perm
{
1983 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1985 return 1 if $authuser ne 'root@pam';
1987 foreach my $opt (@$key_list) {
1989 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1990 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1991 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
1992 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1993 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1994 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1995 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1996 $opt eq 'searchdomain' || $opt eq 'hostname') {
1997 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1999 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
2007 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
2009 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2010 my $volid_list = get_vm_volumes
($conf);
2012 foreach_mountpoint_reverse
($conf, sub {
2013 my ($ms, $mountpoint) = @_;
2015 my $volid = $mountpoint->{volume
};
2016 my $mount = $mountpoint->{mp
};
2018 return if !$volid || !$mount;
2020 my $mount_path = "$rootdir/$mount";
2021 $mount_path =~ s!/+!/!g;
2023 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
2026 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2039 my ($vmid, $storage_cfg, $conf) = @_;
2041 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2042 File
::Path
::make_path
($rootdir);
2044 my $volid_list = get_vm_volumes
($conf);
2045 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2048 foreach_mountpoint
($conf, sub {
2049 my ($ms, $mountpoint) = @_;
2051 my $volid = $mountpoint->{volume
};
2052 my $mount = $mountpoint->{mp
};
2054 return if !$volid || !$mount;
2056 my $image_path = PVE
::Storage
::path
($storage_cfg, $volid);
2057 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2058 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2060 die "unable to mount base volume - internal error" if $isBase;
2062 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2066 warn "mounting container failed - $err";
2067 umount_all
($vmid, $storage_cfg, $conf, 1);
2074 sub mountpoint_mount_path
{
2075 my ($mountpoint, $storage_cfg, $snapname) = @_;
2077 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2080 my $check_mount_path = sub {
2082 $path = File
::Spec-
>canonpath($path);
2083 my $real = Cwd
::realpath
($path);
2084 if ($real ne $path) {
2085 die "mount path modified by symlink: $path != $real";
2089 # use $rootdir = undef to just return the corresponding mount path
2090 sub mountpoint_mount
{
2091 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2093 my $volid = $mountpoint->{volume
};
2094 my $mount = $mountpoint->{mp
};
2096 return if !$volid || !$mount;
2100 if (defined($rootdir)) {
2101 $rootdir =~ s!/+$!!;
2102 $mount_path = "$rootdir/$mount";
2103 $mount_path =~ s!/+!/!g;
2104 &$check_mount_path($mount_path);
2105 File
::Path
::mkpath
($mount_path);
2108 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2110 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2114 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2115 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2117 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2118 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2120 if ($format eq 'subvol') {
2123 if ($scfg->{type
} eq 'zfspool') {
2124 my $path_arg = $path;
2125 $path_arg =~ s!^/+!!;
2126 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', '-t', 'zfs', $path_arg, $mount_path]);
2128 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2131 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
2134 return wantarray ?
($path, 0) : $path;
2135 } elsif ($format eq 'raw') {
2136 my $use_loopdev = 0;
2138 if ($scfg->{path
}) {
2139 push @extra_opts, '-o', 'loop';
2141 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'rbd') {
2144 die "unsupported storage type '$scfg->{type}'\n";
2147 if ($isBase || defined($snapname)) {
2148 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2150 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2153 return wantarray ?
($path, $use_loopdev) : $path;
2155 die "unsupported image format '$format'\n";
2157 } elsif ($volid =~ m
|^/dev/.+|) {
2158 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
2159 return wantarray ?
($volid, 0) : $volid;
2160 } elsif ($volid !~ m
|^/dev/.+| && $volid =~ m
|^/.+| && -d
$volid) {
2161 &$check_mount_path($volid);
2162 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2163 return wantarray ?
($volid, 0) : $volid;
2166 die "unsupported storage";
2169 sub get_vm_volumes
{
2170 my ($conf, $excludes) = @_;
2174 foreach_mountpoint
($conf, sub {
2175 my ($ms, $mountpoint) = @_;
2177 return if $excludes && $ms eq $excludes;
2179 my $volid = $mountpoint->{volume
};
2181 return if !$volid || $volid =~ m
|^/|;
2183 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2186 push @$vollist, $volid;
2195 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp', $dev]);
2199 my ($storage_cfg, $volid) = @_;
2201 if ($volid =~ m!^/dev/.+!) {
2206 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2208 die "cannot format volume '$volid' with no storage\n" if !$storage;
2210 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2212 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2214 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2215 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2217 die "cannot format volume '$volid' (format == $format)\n"
2218 if $format ne 'raw';
2224 my ($storecfg, $vollist) = @_;
2226 foreach my $volid (@$vollist) {
2227 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2233 my ($storecfg, $vmid, $settings, $conf) = @_;
2238 foreach_mountpoint
($settings, sub {
2239 my ($ms, $mountpoint) = @_;
2241 my $volid = $mountpoint->{volume
};
2242 my $mp = $mountpoint->{mp
};
2244 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2246 return if !$storage;
2248 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
2249 my ($storeid, $size_gb) = ($1, $2);
2251 my $size_kb = int(${size_gb
}*1024) * 1024;
2253 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2254 # fixme: use better naming ct-$vmid-disk-X.raw?
2256 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2258 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2260 format_disk
($storecfg, $volid);
2262 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2265 } elsif ($scfg->{type
} eq 'zfspool') {
2267 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2269 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm') {
2271 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2272 format_disk
($storecfg, $volid);
2274 } elsif ($scfg->{type
} eq 'rbd') {
2276 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2277 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2278 format_disk
($storecfg, $volid);
2280 die "unable to create containers on storage type '$scfg->{type}'\n";
2282 push @$vollist, $volid;
2283 my $new_mountpoint = { volume
=> $volid, size
=> $size_kb*1024, mp
=> $mp };
2284 $conf->{$ms} = print_ct_mountpoint
($new_mountpoint, $ms eq 'rootfs');
2286 # use specified/existing volid
2290 # free allocated images on error
2292 destroy_disks
($storecfg, $vollist);
2298 # bash completion helper
2300 sub complete_os_templates
{
2301 my ($cmdname, $pname, $cvalue) = @_;
2303 my $cfg = PVE
::Storage
::config
();
2307 if ($cvalue =~ m/^([^:]+):/) {
2311 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2312 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2315 foreach my $id (keys %$data) {
2316 foreach my $item (@{$data->{$id}}) {
2317 push @$res, $item->{volid
} if defined($item->{volid
});
2324 my $complete_ctid_full = sub {
2327 my $idlist = vmstatus
();
2329 my $active_hash = list_active_containers
();
2333 foreach my $id (keys %$idlist) {
2334 my $d = $idlist->{$id};
2335 if (defined($running)) {
2336 next if $d->{template
};
2337 next if $running && !$active_hash->{$id};
2338 next if !$running && $active_hash->{$id};
2347 return &$complete_ctid_full();
2350 sub complete_ctid_stopped
{
2351 return &$complete_ctid_full(0);
2354 sub complete_ctid_running
{
2355 return &$complete_ctid_full(1);