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.stop' => 1,
234 'lxc.hook.post-stop' => 1,
235 'lxc.hook.clone' => 1,
236 'lxc.hook.destroy' => 1,
239 'lxc.start.auto' => 1,
240 'lxc.start.delay' => 1,
241 'lxc.start.order' => 1,
243 'lxc.environment' => 1,
254 description
=> "Network interface type.",
259 format_description
=> 'String',
260 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
261 pattern
=> '[-_.\w\d]+',
265 format_description
=> 'vmbr<Number>',
266 description
=> 'Bridge to attach the network device to.',
267 pattern
=> '[-_.\w\d]+',
272 format_description
=> 'MAC',
273 description
=> 'Bridge to attach the network device to. (lxc.network.hwaddr)',
274 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
279 format_description
=> 'Number',
280 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
281 minimum
=> 64, # minimum ethernet frame is 64 bytes
286 format
=> 'pve-ipv4-config',
287 format_description
=> 'IPv4Format/CIDR',
288 description
=> 'IPv4 address in CIDR format.',
294 format_description
=> 'GatewayIPv4',
295 description
=> 'Default gateway for IPv4 traffic.',
300 format
=> 'pve-ipv6-config',
301 format_description
=> 'IPv6Format/CIDR',
302 description
=> 'IPv6 address in CIDR format.',
308 format_description
=> 'GatewayIPv6',
309 description
=> 'Default gateway for IPv6 traffic.',
314 format_description
=> '[1|0]',
315 description
=> "Controls whether this interface's firewall rules should be used.",
320 format_description
=> 'VlanNo',
323 description
=> "VLAN tag foro this interface.",
327 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
329 my $MAX_LXC_NETWORKS = 10;
330 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
331 $confdesc->{"net$i"} = {
333 type
=> 'string', format
=> $netconf_desc,
334 description
=> "Specifies network interfaces for the container.",
342 format_description
=> 'Path',
343 description
=> 'Path to the mountpoint as seen from inside the container.',
347 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
351 type
=> 'string', format
=> 'pve-volume-id',
352 description
=> "Reference to unused volumes.",
355 my $MAX_MOUNT_POINTS = 10;
356 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
357 $confdesc->{"mp$i"} = {
359 type
=> 'string', format
=> $mp_desc,
360 description
=> "Use volume as container mount point (experimental feature).",
365 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
366 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
367 $confdesc->{"unused$i"} = $unuseddesc;
370 sub write_pct_config
{
371 my ($filename, $conf) = @_;
373 delete $conf->{snapstate
}; # just to be sure
375 my $generate_raw_config = sub {
380 # add description as comment to top of file
381 my $descr = $conf->{description
} || '';
382 foreach my $cl (split(/\n/, $descr)) {
383 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
386 foreach my $key (sort keys %$conf) {
387 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
388 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
389 my $value = $conf->{$key};
390 die "detected invalid newline inside property '$key'\n" if $value =~ m/\n/;
391 $raw .= "$key: $value\n";
394 if (my $lxcconf = $conf->{lxc
}) {
395 foreach my $entry (@$lxcconf) {
396 my ($k, $v) = @$entry;
404 my $raw = &$generate_raw_config($conf);
406 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
407 $raw .= "\n[$snapname]\n";
408 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
415 my ($key, $value) = @_;
417 die "unknown setting '$key'\n" if !$confdesc->{$key};
419 my $type = $confdesc->{$key}->{type
};
421 if (!defined($value)) {
422 die "got undefined value\n";
425 if ($value =~ m/[\n\r]/) {
426 die "property contains a line feed\n";
429 if ($type eq 'boolean') {
430 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
431 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
432 die "type check ('boolean') failed - got '$value'\n";
433 } elsif ($type eq 'integer') {
434 return int($1) if $value =~ m/^(\d+)$/;
435 die "type check ('integer') failed - got '$value'\n";
436 } elsif ($type eq 'number') {
437 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
438 die "type check ('number') failed - got '$value'\n";
439 } elsif ($type eq 'string') {
440 if (my $fmt = $confdesc->{$key}->{format
}) {
441 PVE
::JSONSchema
::check_format
($fmt, $value);
450 sub parse_pct_config
{
451 my ($filename, $raw) = @_;
453 return undef if !defined($raw);
456 digest
=> Digest
::SHA
::sha1_hex
($raw),
460 $filename =~ m
|/lxc/(\d
+).conf
$|
461 || die "got strange filename '$filename'";
469 my @lines = split(/\n/, $raw);
470 foreach my $line (@lines) {
471 next if $line =~ m/^\s*$/;
473 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
475 $conf->{description
} = $descr if $descr;
477 $conf = $res->{snapshots
}->{$section} = {};
481 if ($line =~ m/^\#(.*)\s*$/) {
482 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
486 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
489 if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
490 push @{$conf->{lxc
}}, [$key, $value];
492 warn "vm $vmid - unable to parse config: $line\n";
494 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
495 $descr .= PVE
::Tools
::decode_text
($2);
496 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
497 $conf->{snapstate
} = $1;
498 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
501 eval { $value = check_type
($key, $value); };
502 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
503 $conf->{$key} = $value;
505 warn "vm $vmid - unable to parse config: $line\n";
509 $conf->{description
} = $descr if $descr;
511 delete $res->{snapstate
}; # just to be sure
517 my $vmlist = PVE
::Cluster
::get_vmlist
();
519 return $res if !$vmlist || !$vmlist->{ids
};
520 my $ids = $vmlist->{ids
};
522 foreach my $vmid (keys %$ids) {
523 next if !$vmid; # skip CT0
524 my $d = $ids->{$vmid};
525 next if !$d->{node
} || $d->{node
} ne $nodename;
526 next if !$d->{type
} || $d->{type
} ne 'lxc';
527 $res->{$vmid}->{type
} = 'lxc';
532 sub cfs_config_path
{
533 my ($vmid, $node) = @_;
535 $node = $nodename if !$node;
536 return "nodes/$node/lxc/$vmid.conf";
540 my ($vmid, $node) = @_;
542 my $cfspath = cfs_config_path
($vmid, $node);
543 return "/etc/pve/$cfspath";
547 my ($vmid, $node) = @_;
549 $node = $nodename if !$node;
550 my $cfspath = cfs_config_path
($vmid, $node);
552 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
553 die "container $vmid does not exists\n" if !defined($conf);
559 my ($vmid, $conf) = @_;
561 my $dir = "/etc/pve/nodes/$nodename/lxc";
564 write_config
($vmid, $conf);
570 unlink config_file
($vmid, $nodename);
574 my ($vmid, $conf) = @_;
576 my $cfspath = cfs_config_path
($vmid);
578 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
581 # flock: we use one file handle per process, so lock file
582 # can be called multiple times and succeeds for the same process.
584 my $lock_handles = {};
585 my $lockdir = "/run/lock/lxc";
590 return "$lockdir/pve-config-${vmid}.lock";
594 my ($vmid, $timeout) = @_;
596 $timeout = 10 if !$timeout;
599 my $filename = lock_filename
($vmid);
601 mkdir $lockdir if !-d
$lockdir;
603 my $lock_func = sub {
604 if (!$lock_handles->{$$}->{$filename}) {
605 my $fh = new IO
::File
(">>$filename") ||
606 die "can't open file - $!\n";
607 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
610 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
611 print STDERR
"trying to aquire lock...";
614 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
615 # try again on EINTR (see bug #273)
616 if ($success || ($! != EINTR
)) {
621 print STDERR
" failed\n";
622 die "can't aquire lock - $!\n";
625 print STDERR
" OK\n";
628 $lock_handles->{$$}->{$filename}->{refcount
}++;
631 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
634 die "can't lock file '$filename' - $err";
641 my $filename = lock_filename
($vmid);
643 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
644 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
645 if ($refcount <= 0) {
646 $lock_handles->{$$}->{$filename} = undef;
653 my ($vmid, $timeout, $code, @param) = @_;
657 lock_aquire
($vmid, $timeout);
658 eval { $res = &$code(@param) };
670 return defined($confdesc->{$name});
673 # add JSON properties for create and set function
674 sub json_config_properties
{
677 foreach my $opt (keys %$confdesc) {
678 next if $opt eq 'parent' || $opt eq 'snaptime';
679 next if $prop->{$opt};
680 $prop->{$opt} = $confdesc->{$opt};
686 sub json_config_properties_no_rootfs
{
689 foreach my $opt (keys %$confdesc) {
690 next if $prop->{$opt};
691 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
692 $prop->{$opt} = $confdesc->{$opt};
698 # container status helpers
700 sub list_active_containers
{
702 my $filename = "/proc/net/unix";
704 # similar test is used by lcxcontainers.c: list_active_containers
707 my $fh = IO
::File-
>new ($filename, "r");
710 while (defined(my $line = <$fh>)) {
711 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
713 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
724 # warning: this is slow
728 my $active_hash = list_active_containers
();
730 return 1 if defined($active_hash->{$vmid});
735 sub get_container_disk_usage
{
738 my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/'];
748 if (my ($fsid, $total, $used, $avail) = $line =~
749 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
757 eval { PVE
::Tools
::run_command
($cmd, timeout
=> 1, outfunc
=> $parser); };
766 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
768 my $active_hash = list_active_containers
();
770 foreach my $vmid (keys %$list) {
771 my $d = $list->{$vmid};
773 my $running = defined($active_hash->{$vmid});
775 $d->{status
} = $running ?
'running' : 'stopped';
777 my $cfspath = cfs_config_path
($vmid);
778 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
780 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
781 $d->{name
} =~ s/[\s]//g;
783 $d->{cpus
} = $conf->{cpulimit
} // 0;
786 my $res = get_container_disk_usage
($vmid);
787 $d->{disk
} = $res->{used
};
788 $d->{maxdisk
} = $res->{total
};
791 # use 4GB by default ??
792 if (my $rootfs = $conf->{rootfs
}) {
793 my $rootinfo = parse_ct_mountpoint
($rootfs);
794 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
796 $d->{maxdisk
} = 4*1024*1024*1024;
802 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
803 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
814 $d->{template
} = is_template
($conf);
817 foreach my $vmid (keys %$list) {
818 my $d = $list->{$vmid};
819 next if $d->{status
} ne 'running';
821 my $pid = find_lxc_pid
($vmid);
822 my $ctime = (stat("/proc/$pid"))[10]; # 10 = ctime
823 $d->{uptime
} = time - $ctime; # the method lxcfs uses
825 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
826 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
828 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
829 my @bytes = split(/\n/, $blkio_bytes);
830 foreach my $byte (@bytes) {
831 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
832 $d->{diskread
} = $2 if $key eq 'Read';
833 $d->{diskwrite
} = $2 if $key eq 'Write';
841 sub parse_ct_mountpoint
{
842 my ($data, $noerr) = @_;
847 eval { $res = PVE
::JSONSchema
::parse_property_string
($mp_desc, $data) };
849 return undef if $noerr;
853 if (defined(my $size = $res->{size
})) {
854 $size = PVE
::JSONSchema
::parse_size
($size);
855 if (!defined($size)) {
856 return undef if $noerr;
857 die "invalid size: $size\n";
859 $res->{size
} = $size;
865 sub print_ct_mountpoint
{
866 my ($info, $nomp) = @_;
867 my $skip = $nomp ?
['mp'] : [];
868 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
871 sub print_lxc_network
{
873 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
876 sub parse_lxc_network
{
881 return $res if !$data;
883 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
885 $res->{type
} = 'veth';
886 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
891 sub read_cgroup_value
{
892 my ($group, $vmid, $name, $full) = @_;
894 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
896 return PVE
::Tools
::file_get_contents
($path) if $full;
898 return PVE
::Tools
::file_read_firstline
($path);
901 sub write_cgroup_value
{
902 my ($group, $vmid, $name, $value) = @_;
904 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
905 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
909 sub find_lxc_console_pids
{
913 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
916 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
919 my @args = split(/\0/, $cmdline);
921 # serach for lxc-console -n <vmid>
922 return if scalar(@args) != 3;
923 return if $args[1] ne '-n';
924 return if $args[2] !~ m/^\d+$/;
925 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
929 push @{$res->{$vmid}}, $pid;
941 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
943 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid, '-p'], outfunc
=> $parser);
945 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
950 my $ipv4_reverse_mask = [
986 # Note: we cannot use Net:IP, because that only allows strict
988 sub parse_ipv4_cidr
{
989 my ($cidr, $noerr) = @_;
991 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
992 return { address
=> $1, netmask
=> $ipv4_reverse_mask->[$2] };
995 return undef if $noerr;
997 die "unable to parse ipv4 address/mask\n";
1003 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
1006 sub check_protection
{
1007 my ($vm_conf, $err_msg) = @_;
1009 if ($vm_conf->{protection
}) {
1010 die "$err_msg - protection mode enabled\n";
1014 sub update_lxc_config
{
1015 my ($storage_cfg, $vmid, $conf) = @_;
1017 my $dir = "/var/lib/lxc/$vmid";
1019 if ($conf->{template
}) {
1021 unlink "$dir/config";
1028 die "missing 'arch' - internal error" if !$conf->{arch
};
1029 $raw .= "lxc.arch = $conf->{arch}\n";
1031 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
1032 if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
1033 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
1038 if (!has_dev_console
($conf)) {
1039 $raw .= "lxc.console = none\n";
1040 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1043 my $ttycount = get_tty_count
($conf);
1044 $raw .= "lxc.tty = $ttycount\n";
1046 # some init scripts expects a linux terminal (turnkey).
1047 $raw .= "lxc.environment = TERM=linux\n";
1049 my $utsname = $conf->{hostname
} || "CT$vmid";
1050 $raw .= "lxc.utsname = $utsname\n";
1052 my $memory = $conf->{memory
} || 512;
1053 my $swap = $conf->{swap
} // 0;
1055 my $lxcmem = int($memory*1024*1024);
1056 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1058 my $lxcswap = int(($memory + $swap)*1024*1024);
1059 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1061 if (my $cpulimit = $conf->{cpulimit
}) {
1062 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1063 my $value = int(100000*$cpulimit);
1064 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1067 my $shares = $conf->{cpuunits
} || 1024;
1068 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1070 my $mountpoint = parse_ct_mountpoint
($conf->{rootfs
});
1071 $mountpoint->{mp
} = '/';
1073 my ($path, $use_loopdev) = mountpoint_mount_path
($mountpoint, $storage_cfg);
1074 $path = "loop:$path" if $use_loopdev;
1076 $raw .= "lxc.rootfs = $path\n";
1079 foreach my $k (keys %$conf) {
1080 next if $k !~ m/^net(\d+)$/;
1082 my $d = parse_lxc_network
($conf->{$k});
1084 $raw .= "lxc.network.type = veth\n";
1085 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1086 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1087 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1088 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1091 if (my $lxcconf = $conf->{lxc
}) {
1092 foreach my $entry (@$lxcconf) {
1093 my ($k, $v) = @$entry;
1094 $netcount++ if $k eq 'lxc.network.type';
1095 $raw .= "$k = $v\n";
1099 $raw .= "lxc.network.type = empty\n" if !$netcount;
1101 File
::Path
::mkpath
("$dir/rootfs");
1103 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1106 # verify and cleanup nameserver list (replace \0 with ' ')
1107 sub verify_nameserver_list
{
1108 my ($nameserver_list) = @_;
1111 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1112 PVE
::JSONSchema
::pve_verify_ip
($server);
1113 push @list, $server;
1116 return join(' ', @list);
1119 sub verify_searchdomain_list
{
1120 my ($searchdomain_list) = @_;
1123 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1124 # todo: should we add checks for valid dns domains?
1125 push @list, $server;
1128 return join(' ', @list);
1131 sub add_unused_volume
{
1132 my ($config, $volid) = @_;
1134 # skip bind mounts and block devices
1135 return if $volid =~ m
|^/|;
1138 for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
1139 my $test = "unused$ind";
1140 if (my $vid = $config->{$test}) {
1141 return if $vid eq $volid; # do not add duplicates
1147 die "To many unused volume - please delete them first.\n" if !$key;
1149 $config->{$key} = $volid;
1154 sub update_pct_config
{
1155 my ($vmid, $conf, $running, $param, $delete) = @_;
1160 my @deleted_volumes;
1164 my $pid = find_lxc_pid
($vmid);
1165 $rootdir = "/proc/$pid/root";
1168 my $hotplug_error = sub {
1170 push @nohotplug, @_;
1177 if (defined($delete)) {
1178 foreach my $opt (@$delete) {
1179 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1180 die "unable to delete required option '$opt'\n";
1181 } elsif ($opt eq 'swap') {
1182 delete $conf->{$opt};
1183 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1184 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1185 delete $conf->{$opt};
1186 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1187 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1188 next if $hotplug_error->($opt);
1189 delete $conf->{$opt};
1190 } elsif ($opt =~ m/^net(\d)$/) {
1191 delete $conf->{$opt};
1194 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1195 } elsif ($opt eq 'protection') {
1196 delete $conf->{$opt};
1197 } elsif ($opt =~ m/^unused(\d+)$/) {
1198 next if $hotplug_error->($opt);
1199 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1200 push @deleted_volumes, $conf->{$opt};
1201 delete $conf->{$opt};
1202 } elsif ($opt =~ m/^mp(\d+)$/) {
1203 next if $hotplug_error->($opt);
1204 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1205 my $mountpoint = parse_ct_mountpoint
($conf->{$opt});
1206 add_unused_volume
($conf, $mountpoint->{volume
});
1207 delete $conf->{$opt};
1211 write_config
($vmid, $conf) if $running;
1215 # There's no separate swap size to configure, there's memory and "total"
1216 # memory (iow. memory+swap). This means we have to change them together.
1217 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1218 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1219 if (defined($wanted_memory) || defined($wanted_swap)) {
1221 $wanted_memory //= ($conf->{memory
} || 512);
1222 $wanted_swap //= ($conf->{swap
} || 0);
1224 my $total = $wanted_memory + $wanted_swap;
1226 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1227 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1229 $conf->{memory
} = $wanted_memory;
1230 $conf->{swap
} = $wanted_swap;
1232 write_config
($vmid, $conf) if $running;
1235 foreach my $opt (keys %$param) {
1236 my $value = $param->{$opt};
1237 if ($opt eq 'hostname') {
1238 $conf->{$opt} = $value;
1239 } elsif ($opt eq 'onboot') {
1240 $conf->{$opt} = $value ?
1 : 0;
1241 } elsif ($opt eq 'startup') {
1242 $conf->{$opt} = $value;
1243 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1244 next if $hotplug_error->($opt);
1245 $conf->{$opt} = $value;
1246 } elsif ($opt eq 'nameserver') {
1247 next if $hotplug_error->($opt);
1248 my $list = verify_nameserver_list
($value);
1249 $conf->{$opt} = $list;
1250 } elsif ($opt eq 'searchdomain') {
1251 next if $hotplug_error->($opt);
1252 my $list = verify_searchdomain_list
($value);
1253 $conf->{$opt} = $list;
1254 } elsif ($opt eq 'cpulimit') {
1255 next if $hotplug_error->($opt); # FIXME: hotplug
1256 $conf->{$opt} = $value;
1257 } elsif ($opt eq 'cpuunits') {
1258 $conf->{$opt} = $value;
1259 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1260 } elsif ($opt eq 'description') {
1261 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1262 } elsif ($opt =~ m/^net(\d+)$/) {
1264 my $net = parse_lxc_network
($value);
1266 $conf->{$opt} = print_lxc_network
($net);
1268 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1270 } elsif ($opt eq 'protection') {
1271 $conf->{$opt} = $value ?
1 : 0;
1272 } elsif ($opt =~ m/^mp(\d+)$/) {
1273 next if $hotplug_error->($opt);
1274 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1275 $conf->{$opt} = $value;
1277 } elsif ($opt eq 'rootfs') {
1278 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1279 die "implement me: $opt";
1281 die "implement me: $opt";
1283 write_config
($vmid, $conf) if $running;
1286 if (@deleted_volumes) {
1287 my $storage_cfg = PVE
::Storage
::config
();
1288 foreach my $volume (@deleted_volumes) {
1289 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1294 my $storage_cfg = PVE
::Storage
::config
();
1295 create_disks
($storage_cfg, $vmid, $conf, $conf);
1298 # This should be the last thing we do here
1299 if ($running && scalar(@nohotplug)) {
1300 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1304 sub has_dev_console
{
1307 return !(defined($conf->{console
}) && !$conf->{console
});
1313 return $conf->{tty
} // $confdesc->{tty
}->{default};
1319 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1322 sub get_console_command
{
1323 my ($vmid, $conf) = @_;
1325 my $cmode = get_cmode
($conf);
1327 if ($cmode eq 'console') {
1328 return ['lxc-console', '-n', $vmid, '-t', 0];
1329 } elsif ($cmode eq 'tty') {
1330 return ['lxc-console', '-n', $vmid];
1331 } elsif ($cmode eq 'shell') {
1332 return ['lxc-attach', '--clear-env', '-n', $vmid];
1334 die "internal error";
1338 sub get_primary_ips
{
1341 # return data from net0
1343 return undef if !defined($conf->{net0
});
1344 my $net = parse_lxc_network
($conf->{net0
});
1346 my $ipv4 = $net->{ip
};
1348 if ($ipv4 =~ /^(dhcp|manual)$/) {
1354 my $ipv6 = $net->{ip6
};
1356 if ($ipv6 =~ /^(dhcp|manual)$/) {
1363 return ($ipv4, $ipv6);
1366 sub delete_mountpoint_volume
{
1367 my ($storage_cfg, $vmid, $volume) = @_;
1369 # skip bind mounts and block devices
1370 if ($volume =~ m
|^/|) {
1374 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1375 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1378 sub destroy_lxc_container
{
1379 my ($storage_cfg, $vmid, $conf) = @_;
1381 foreach_mountpoint
($conf, sub {
1382 my ($ms, $mountpoint) = @_;
1383 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1386 rmdir "/var/lib/lxc/$vmid/rootfs";
1387 unlink "/var/lib/lxc/$vmid/config";
1388 rmdir "/var/lib/lxc/$vmid";
1389 destroy_config
($vmid);
1391 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1392 #PVE::Tools::run_command($cmd);
1395 sub vm_stop_cleanup
{
1396 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1401 my $vollist = get_vm_volumes
($conf);
1402 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1405 warn $@ if $@; # avoid errors - just warn
1408 my $safe_num_ne = sub {
1411 return 0 if !defined($a) && !defined($b);
1412 return 1 if !defined($a);
1413 return 1 if !defined($b);
1418 my $safe_string_ne = sub {
1421 return 0 if !defined($a) && !defined($b);
1422 return 1 if !defined($a);
1423 return 1 if !defined($b);
1429 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1431 if ($newnet->{type
} ne 'veth') {
1432 # for when there are physical interfaces
1433 die "cannot update interface of type $newnet->{type}";
1436 my $veth = "veth${vmid}i${netid}";
1437 my $eth = $newnet->{name
};
1439 if (my $oldnetcfg = $conf->{$opt}) {
1440 my $oldnet = parse_lxc_network
($oldnetcfg);
1442 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1443 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1445 PVE
::Network
::veth_delete
($veth);
1446 delete $conf->{$opt};
1447 write_config
($vmid, $conf);
1449 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1451 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1452 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1453 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1455 if ($oldnet->{bridge
}) {
1456 PVE
::Network
::tap_unplug
($veth);
1457 foreach (qw(bridge tag firewall)) {
1458 delete $oldnet->{$_};
1460 $conf->{$opt} = print_lxc_network
($oldnet);
1461 write_config
($vmid, $conf);
1464 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1465 foreach (qw(bridge tag firewall)) {
1466 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1468 $conf->{$opt} = print_lxc_network
($oldnet);
1469 write_config
($vmid, $conf);
1472 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1475 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1479 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1481 my $veth = "veth${vmid}i${netid}";
1482 my $vethpeer = $veth . "p";
1483 my $eth = $newnet->{name
};
1485 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1486 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1488 # attach peer in container
1489 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1490 PVE
::Tools
::run_command
($cmd);
1492 # link up peer in container
1493 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1494 PVE
::Tools
::run_command
($cmd);
1496 my $done = { type
=> 'veth' };
1497 foreach (qw(bridge tag firewall hwaddr name)) {
1498 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1500 $conf->{$opt} = print_lxc_network
($done);
1502 write_config
($vmid, $conf);
1505 sub update_ipconfig
{
1506 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1508 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1510 my $optdata = parse_lxc_network
($conf->{$opt});
1514 my $cmdargs = shift;
1515 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1517 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1519 my $change_ip_config = sub {
1520 my ($ipversion) = @_;
1522 my $family_opt = "-$ipversion";
1523 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1524 my $gw= "gw$suffix";
1525 my $ip= "ip$suffix";
1527 my $newip = $newnet->{$ip};
1528 my $newgw = $newnet->{$gw};
1529 my $oldip = $optdata->{$ip};
1531 my $change_ip = &$safe_string_ne($oldip, $newip);
1532 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1534 return if !$change_ip && !$change_gw;
1536 # step 1: add new IP, if this fails we cancel
1537 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1538 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1545 # step 2: replace gateway
1546 # If this fails we delete the added IP and cancel.
1547 # If it succeeds we save the config and delete the old IP, ignoring
1548 # errors. The config is then saved.
1549 # Note: 'ip route replace' can add
1552 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1555 # the route was not replaced, the old IP is still available
1556 # rollback (delete new IP) and cancel
1558 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1559 warn $@ if $@; # no need to die here
1564 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1565 # if the route was not deleted, the guest might have deleted it manually
1571 # from this point on we save the configuration
1572 # step 3: delete old IP ignoring errors
1573 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1574 # We need to enable promote_secondaries, otherwise our newly added
1575 # address will be removed along with the old one.
1578 if ($ipversion == 4) {
1579 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1580 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1581 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1583 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1585 warn $@ if $@; # no need to die here
1587 if ($ipversion == 4) {
1588 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1592 foreach my $property ($ip, $gw) {
1593 if ($newnet->{$property}) {
1594 $optdata->{$property} = $newnet->{$property};
1596 delete $optdata->{$property};
1599 $conf->{$opt} = print_lxc_network
($optdata);
1600 write_config
($vmid, $conf);
1601 $lxc_setup->setup_network($conf);
1604 &$change_ip_config(4);
1605 &$change_ip_config(6);
1609 # Internal snapshots
1611 # NOTE: Snapshot create/delete involves several non-atomic
1612 # action, and can take a long time.
1613 # So we try to avoid locking the file and use 'lock' variable
1614 # inside the config file instead.
1616 my $snapshot_copy_config = sub {
1617 my ($source, $dest) = @_;
1619 foreach my $k (keys %$source) {
1620 next if $k eq 'snapshots';
1621 next if $k eq 'snapstate';
1622 next if $k eq 'snaptime';
1623 next if $k eq 'vmstate';
1624 next if $k eq 'lock';
1625 next if $k eq 'digest';
1626 next if $k eq 'description';
1628 $dest->{$k} = $source->{$k};
1632 my $snapshot_prepare = sub {
1633 my ($vmid, $snapname, $comment) = @_;
1637 my $updatefn = sub {
1639 my $conf = load_config
($vmid);
1641 die "you can't take a snapshot if it's a template\n"
1642 if is_template
($conf);
1646 $conf->{lock} = 'snapshot';
1648 die "snapshot name '$snapname' already used\n"
1649 if defined($conf->{snapshots
}->{$snapname});
1651 my $storecfg = PVE
::Storage
::config
();
1652 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1654 $snap = $conf->{snapshots
}->{$snapname} = {};
1656 &$snapshot_copy_config($conf, $snap);
1658 $snap->{'snapstate'} = "prepare";
1659 $snap->{'snaptime'} = time();
1660 $snap->{'description'} = $comment if $comment;
1661 $conf->{snapshots
}->{$snapname} = $snap;
1663 write_config
($vmid, $conf);
1666 lock_container
($vmid, 10, $updatefn);
1671 my $snapshot_commit = sub {
1672 my ($vmid, $snapname) = @_;
1674 my $updatefn = sub {
1676 my $conf = load_config
($vmid);
1678 die "missing snapshot lock\n"
1679 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1681 die "snapshot '$snapname' does not exist\n"
1682 if !defined($conf->{snapshots
}->{$snapname});
1684 die "wrong snapshot state\n"
1685 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1686 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1688 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1689 delete $conf->{lock};
1690 $conf->{parent
} = $snapname;
1692 write_config
($vmid, $conf);
1695 lock_container
($vmid, 10 ,$updatefn);
1699 my ($feature, $conf, $storecfg, $snapname) = @_;
1703 foreach_mountpoint
($conf, sub {
1704 my ($ms, $mountpoint) = @_;
1706 return if $err; # skip further test
1708 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1710 # TODO: implement support for mountpoints
1711 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1715 return $err ?
0 : 1;
1718 sub snapshot_create
{
1719 my ($vmid, $snapname, $comment) = @_;
1721 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1723 my $conf = load_config
($vmid);
1725 my $running = check_running
($vmid);
1728 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1729 PVE
::Tools
::run_command
(['/bin/sync']);
1732 my $storecfg = PVE
::Storage
::config
();
1733 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1734 my $volid = $rootinfo->{volume
};
1737 PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]);
1740 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1741 &$snapshot_commit($vmid, $snapname);
1744 snapshot_delete
($vmid, $snapname, 1);
1749 sub snapshot_delete
{
1750 my ($vmid, $snapname, $force) = @_;
1756 my $updatefn = sub {
1758 $conf = load_config
($vmid);
1760 die "you can't delete a snapshot if vm is a template\n"
1761 if is_template
($conf);
1763 $snap = $conf->{snapshots
}->{$snapname};
1767 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1769 $snap->{snapstate
} = 'delete';
1771 write_config
($vmid, $conf);
1774 lock_container
($vmid, 10, $updatefn);
1776 my $storecfg = PVE
::Storage
::config
();
1778 my $del_snap = sub {
1782 if ($conf->{parent
} eq $snapname) {
1783 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1784 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1786 delete $conf->{parent
};
1790 delete $conf->{snapshots
}->{$snapname};
1792 write_config
($vmid, $conf);
1795 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1796 my $rootinfo = parse_ct_mountpoint
($rootfs);
1797 my $volid = $rootinfo->{volume
};
1800 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1804 if(!$err || ($err && $force)) {
1805 lock_container
($vmid, 10, $del_snap);
1807 die "Can't delete snapshot: $vmid $snapname $err\n";
1812 sub snapshot_rollback
{
1813 my ($vmid, $snapname) = @_;
1815 my $storecfg = PVE
::Storage
::config
();
1817 my $conf = load_config
($vmid);
1819 die "you can't rollback if vm is a template\n" if is_template
($conf);
1821 my $snap = $conf->{snapshots
}->{$snapname};
1823 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1825 my $rootfs = $snap->{rootfs
};
1826 my $rootinfo = parse_ct_mountpoint
($rootfs);
1827 my $volid = $rootinfo->{volume
};
1829 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1831 my $updatefn = sub {
1833 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1834 if $snap->{snapstate
};
1838 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1840 die "unable to rollback vm $vmid: vm is running\n"
1841 if check_running
($vmid);
1843 $conf->{lock} = 'rollback';
1847 # copy snapshot config to current config
1849 my $tmp_conf = $conf;
1850 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1851 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1852 delete $conf->{snaptime
};
1853 delete $conf->{snapname
};
1854 $conf->{parent
} = $snapname;
1856 write_config
($vmid, $conf);
1859 my $unlockfn = sub {
1860 delete $conf->{lock};
1861 write_config
($vmid, $conf);
1864 lock_container
($vmid, 10, $updatefn);
1866 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1868 lock_container
($vmid, 5, $unlockfn);
1871 sub template_create
{
1872 my ($vmid, $conf) = @_;
1874 my $storecfg = PVE
::Storage
::config
();
1876 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1877 my $volid = $rootinfo->{volume
};
1879 die "Template feature is not available for '$volid'\n"
1880 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1882 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1884 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1885 $rootinfo->{volume
} = $template_volid;
1886 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1888 write_config
($vmid, $conf);
1894 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1897 sub mountpoint_names
{
1900 my @names = ('rootfs');
1902 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1903 push @names, "mp$i";
1906 return $reverse ?
reverse @names : @names;
1909 # The container might have *different* symlinks than the host. realpath/abs_path
1910 # use the actual filesystem to resolve links.
1911 sub sanitize_mountpoint
{
1913 $mp = '/' . $mp; # we always start with a slash
1914 $mp =~ s
@/{2,}@/@g; # collapse sequences of slashes
1915 $mp =~ s
@/\./@@g; # collapse /./
1916 $mp =~ s
@/\.(/)?
$@$1@; # collapse a trailing /. or /./
1917 $mp =~ s
@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
1918 $mp =~ s
@/\.\
.(/)?
$@$1@; # collapse trailing /.. or /../ disregarding symlinks
1922 sub foreach_mountpoint_full
{
1923 my ($conf, $reverse, $func) = @_;
1925 foreach my $key (mountpoint_names
($reverse)) {
1926 my $value = $conf->{$key};
1927 next if !defined($value);
1928 my $mountpoint = parse_ct_mountpoint
($value, 1);
1929 next if !defined($mountpoint);
1931 # just to be sure: rootfs is /
1932 my $path = $key eq 'rootfs' ?
'/' : $mountpoint->{mp
};
1933 $mountpoint->{mp
} = sanitize_mountpoint
($path);
1935 $path = $mountpoint->{volume
};
1936 $mountpoint->{volume
} = sanitize_mountpoint
($path) if $path =~ m
|^/|;
1938 &$func($key, $mountpoint);
1942 sub foreach_mountpoint
{
1943 my ($conf, $func) = @_;
1945 foreach_mountpoint_full
($conf, 0, $func);
1948 sub foreach_mountpoint_reverse
{
1949 my ($conf, $func) = @_;
1951 foreach_mountpoint_full
($conf, 1, $func);
1954 sub check_ct_modify_config_perm
{
1955 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1957 return 1 if $authuser ne 'root@pam';
1959 foreach my $opt (@$key_list) {
1961 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1962 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1963 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
1964 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1965 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1966 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1967 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1968 $opt eq 'searchdomain' || $opt eq 'hostname') {
1969 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1971 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
1979 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
1981 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1982 my $volid_list = get_vm_volumes
($conf);
1984 foreach_mountpoint_reverse
($conf, sub {
1985 my ($ms, $mountpoint) = @_;
1987 my $volid = $mountpoint->{volume
};
1988 my $mount = $mountpoint->{mp
};
1990 return if !$volid || !$mount;
1992 my $mount_path = "$rootdir/$mount";
1993 $mount_path =~ s!/+!/!g;
1995 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
1998 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2011 my ($vmid, $storage_cfg, $conf) = @_;
2013 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2014 File
::Path
::make_path
($rootdir);
2016 my $volid_list = get_vm_volumes
($conf);
2017 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2020 foreach_mountpoint
($conf, sub {
2021 my ($ms, $mountpoint) = @_;
2023 my $volid = $mountpoint->{volume
};
2024 my $mount = $mountpoint->{mp
};
2026 return if !$volid || !$mount;
2028 my $image_path = PVE
::Storage
::path
($storage_cfg, $volid);
2029 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2030 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2032 die "unable to mount base volume - internal error" if $isBase;
2034 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2038 warn "mounting container failed - $err";
2039 umount_all
($vmid, $storage_cfg, $conf, 1);
2046 sub mountpoint_mount_path
{
2047 my ($mountpoint, $storage_cfg, $snapname) = @_;
2049 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2052 my $check_mount_path = sub {
2054 $path = File
::Spec-
>canonpath($path);
2055 my $real = Cwd
::realpath
($path);
2056 if ($real ne $path) {
2057 die "mount path modified by symlink: $path != $real";
2061 # use $rootdir = undef to just return the corresponding mount path
2062 sub mountpoint_mount
{
2063 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2065 my $volid = $mountpoint->{volume
};
2066 my $mount = $mountpoint->{mp
};
2068 return if !$volid || !$mount;
2072 if (defined($rootdir)) {
2073 $rootdir =~ s!/+$!!;
2074 $mount_path = "$rootdir/$mount";
2075 $mount_path =~ s!/+!/!g;
2076 &$check_mount_path($mount_path);
2077 File
::Path
::mkpath
($mount_path);
2080 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2082 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2086 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2087 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2089 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2090 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2092 if ($format eq 'subvol') {
2095 if ($scfg->{type
} eq 'zfspool') {
2096 my $path_arg = $path;
2097 $path_arg =~ s!^/+!!;
2098 PVE
::Tools
::run_command
(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
2100 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2103 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
2106 return wantarray ?
($path, 0) : $path;
2107 } elsif ($format eq 'raw') {
2108 my $use_loopdev = 0;
2110 if ($scfg->{path
}) {
2111 push @extra_opts, '-o', 'loop';
2113 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'rbd') {
2116 die "unsupported storage type '$scfg->{type}'\n";
2119 if ($isBase || defined($snapname)) {
2120 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2122 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2125 return wantarray ?
($path, $use_loopdev) : $path;
2127 die "unsupported image format '$format'\n";
2129 } elsif ($volid =~ m
|^/dev/.+|) {
2130 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
2131 return wantarray ?
($volid, 0) : $volid;
2132 } elsif ($volid !~ m
|^/dev/.+| && $volid =~ m
|^/.+| && -d
$volid) {
2133 &$check_mount_path($volid);
2134 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2135 return wantarray ?
($volid, 0) : $volid;
2138 die "unsupported storage";
2141 sub get_vm_volumes
{
2142 my ($conf, $excludes) = @_;
2146 foreach_mountpoint
($conf, sub {
2147 my ($ms, $mountpoint) = @_;
2149 return if $excludes && $ms eq $excludes;
2151 my $volid = $mountpoint->{volume
};
2153 return if !$volid || $volid =~ m
|^/|;
2155 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2158 push @$vollist, $volid;
2167 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp', $dev]);
2171 my ($storage_cfg, $volid) = @_;
2173 if ($volid =~ m!^/dev/.+!) {
2178 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2180 die "cannot format volume '$volid' with no storage\n" if !$storage;
2182 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2184 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2186 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2187 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2189 die "cannot format volume '$volid' (format == $format)\n"
2190 if $format ne 'raw';
2196 my ($storecfg, $vollist) = @_;
2198 foreach my $volid (@$vollist) {
2199 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2205 my ($storecfg, $vmid, $settings, $conf) = @_;
2210 foreach_mountpoint
($settings, sub {
2211 my ($ms, $mountpoint) = @_;
2213 my $volid = $mountpoint->{volume
};
2214 my $mp = $mountpoint->{mp
};
2216 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2218 return if !$storage;
2220 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
2221 my ($storeid, $size_gb) = ($1, $2);
2223 my $size_kb = int(${size_gb
}*1024) * 1024;
2225 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2226 # fixme: use better naming ct-$vmid-disk-X.raw?
2228 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2230 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2232 format_disk
($storecfg, $volid);
2234 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2237 } elsif ($scfg->{type
} eq 'zfspool') {
2239 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2241 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm') {
2243 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2244 format_disk
($storecfg, $volid);
2246 } elsif ($scfg->{type
} eq 'rbd') {
2248 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2249 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2250 format_disk
($storecfg, $volid);
2252 die "unable to create containers on storage type '$scfg->{type}'\n";
2254 push @$vollist, $volid;
2255 my $new_mountpoint = { volume
=> $volid, size
=> $size_kb*1024, mp
=> $mp };
2256 $conf->{$ms} = print_ct_mountpoint
($new_mountpoint, $ms eq 'rootfs');
2258 # use specified/existing volid
2262 # free allocated images on error
2264 destroy_disks
($storecfg, $vollist);
2270 # bash completion helper
2272 sub complete_os_templates
{
2273 my ($cmdname, $pname, $cvalue) = @_;
2275 my $cfg = PVE
::Storage
::config
();
2279 if ($cvalue =~ m/^([^:]+):/) {
2283 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2284 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2287 foreach my $id (keys %$data) {
2288 foreach my $item (@{$data->{$id}}) {
2289 push @$res, $item->{volid
} if defined($item->{volid
});
2296 my $complete_ctid_full = sub {
2299 my $idlist = vmstatus
();
2301 my $active_hash = list_active_containers
();
2305 foreach my $id (keys %$idlist) {
2306 my $d = $idlist->{$id};
2307 if (defined($running)) {
2308 next if $d->{template
};
2309 next if $running && !$active_hash->{$id};
2310 next if !$running && $active_hash->{$id};
2319 return &$complete_ctid_full();
2322 sub complete_ctid_stopped
{
2323 return &$complete_ctid_full(0);
2326 sub complete_ctid_running
{
2327 return &$complete_ctid_full(1);