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]+',
271 format_description
=> 'MAC',
272 description
=> 'Bridge to attach the network device to. (lxc.network.hwaddr)',
273 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
278 format_description
=> 'Number',
279 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
280 minimum
=> 64, # minimum ethernet frame is 64 bytes
285 format
=> 'pve-ipv4-config',
286 format_description
=> 'IPv4Format/CIDR',
287 description
=> 'IPv4 address in CIDR format.',
293 format_description
=> 'GatewayIPv4',
294 description
=> 'Default gateway for IPv4 traffic.',
299 format
=> 'pve-ipv6-config',
300 format_description
=> 'IPv6Format/CIDR',
301 description
=> 'IPv6 address in CIDR format.',
307 format_description
=> 'GatewayIPv6',
308 description
=> 'Default gateway for IPv6 traffic.',
313 format_description
=> '[1|0]',
314 description
=> "Controls whether this interface's firewall rules should be used.",
319 format_description
=> 'VlanNo',
322 description
=> "VLAN tag foro this interface.",
326 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
328 my $MAX_LXC_NETWORKS = 10;
329 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
330 $confdesc->{"net$i"} = {
332 type
=> 'string', format
=> $netconf_desc,
333 description
=> "Specifies network interfaces for the container.",
341 format_description
=> 'Path',
342 description
=> 'Path to the mountpoint as seen from inside the container.',
346 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
350 type
=> 'string', format
=> 'pve-volume-id',
351 description
=> "Reference to unused volumes.",
354 my $MAX_MOUNT_POINTS = 10;
355 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
356 $confdesc->{"mp$i"} = {
358 type
=> 'string', format
=> $mp_desc,
359 description
=> "Use volume as container mount point (experimental feature).",
364 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
365 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
366 $confdesc->{"unused$i"} = $unuseddesc;
369 sub write_pct_config
{
370 my ($filename, $conf) = @_;
372 delete $conf->{snapstate
}; # just to be sure
374 my $generate_raw_config = sub {
379 # add description as comment to top of file
380 my $descr = $conf->{description
} || '';
381 foreach my $cl (split(/\n/, $descr)) {
382 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
385 foreach my $key (sort keys %$conf) {
386 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
387 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
388 my $value = $conf->{$key};
389 die "detected invalid newline inside property '$key'\n" if $value =~ m/\n/;
390 $raw .= "$key: $value\n";
393 if (my $lxcconf = $conf->{lxc
}) {
394 foreach my $entry (@$lxcconf) {
395 my ($k, $v) = @$entry;
403 my $raw = &$generate_raw_config($conf);
405 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
406 $raw .= "\n[$snapname]\n";
407 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
414 my ($key, $value) = @_;
416 die "unknown setting '$key'\n" if !$confdesc->{$key};
418 my $type = $confdesc->{$key}->{type
};
420 if (!defined($value)) {
421 die "got undefined value\n";
424 if ($value =~ m/[\n\r]/) {
425 die "property contains a line feed\n";
428 if ($type eq 'boolean') {
429 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
430 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
431 die "type check ('boolean') failed - got '$value'\n";
432 } elsif ($type eq 'integer') {
433 return int($1) if $value =~ m/^(\d+)$/;
434 die "type check ('integer') failed - got '$value'\n";
435 } elsif ($type eq 'number') {
436 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
437 die "type check ('number') failed - got '$value'\n";
438 } elsif ($type eq 'string') {
439 if (my $fmt = $confdesc->{$key}->{format
}) {
440 PVE
::JSONSchema
::check_format
($fmt, $value);
449 sub parse_pct_config
{
450 my ($filename, $raw) = @_;
452 return undef if !defined($raw);
455 digest
=> Digest
::SHA
::sha1_hex
($raw),
459 $filename =~ m
|/lxc/(\d
+).conf
$|
460 || die "got strange filename '$filename'";
468 my @lines = split(/\n/, $raw);
469 foreach my $line (@lines) {
470 next if $line =~ m/^\s*$/;
472 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
474 $conf->{description
} = $descr if $descr;
476 $conf = $res->{snapshots
}->{$section} = {};
480 if ($line =~ m/^\#(.*)\s*$/) {
481 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
485 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
488 if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
489 push @{$conf->{lxc
}}, [$key, $value];
491 warn "vm $vmid - unable to parse config: $line\n";
493 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
494 $descr .= PVE
::Tools
::decode_text
($2);
495 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
496 $conf->{snapstate
} = $1;
497 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
500 eval { $value = check_type
($key, $value); };
501 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
502 $conf->{$key} = $value;
504 warn "vm $vmid - unable to parse config: $line\n";
508 $conf->{description
} = $descr if $descr;
510 delete $res->{snapstate
}; # just to be sure
516 my $vmlist = PVE
::Cluster
::get_vmlist
();
518 return $res if !$vmlist || !$vmlist->{ids
};
519 my $ids = $vmlist->{ids
};
521 foreach my $vmid (keys %$ids) {
522 next if !$vmid; # skip CT0
523 my $d = $ids->{$vmid};
524 next if !$d->{node
} || $d->{node
} ne $nodename;
525 next if !$d->{type
} || $d->{type
} ne 'lxc';
526 $res->{$vmid}->{type
} = 'lxc';
531 sub cfs_config_path
{
532 my ($vmid, $node) = @_;
534 $node = $nodename if !$node;
535 return "nodes/$node/lxc/$vmid.conf";
539 my ($vmid, $node) = @_;
541 my $cfspath = cfs_config_path
($vmid, $node);
542 return "/etc/pve/$cfspath";
546 my ($vmid, $node) = @_;
548 $node = $nodename if !$node;
549 my $cfspath = cfs_config_path
($vmid, $node);
551 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
552 die "container $vmid does not exists\n" if !defined($conf);
558 my ($vmid, $conf) = @_;
560 my $dir = "/etc/pve/nodes/$nodename/lxc";
563 write_config
($vmid, $conf);
569 unlink config_file
($vmid, $nodename);
573 my ($vmid, $conf) = @_;
575 my $cfspath = cfs_config_path
($vmid);
577 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
580 # flock: we use one file handle per process, so lock file
581 # can be called multiple times and succeeds for the same process.
583 my $lock_handles = {};
584 my $lockdir = "/run/lock/lxc";
589 return "$lockdir/pve-config-${vmid}.lock";
593 my ($vmid, $timeout) = @_;
595 $timeout = 10 if !$timeout;
598 my $filename = lock_filename
($vmid);
600 mkdir $lockdir if !-d
$lockdir;
602 my $lock_func = sub {
603 if (!$lock_handles->{$$}->{$filename}) {
604 my $fh = new IO
::File
(">>$filename") ||
605 die "can't open file - $!\n";
606 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
609 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
610 print STDERR
"trying to aquire lock...";
613 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
614 # try again on EINTR (see bug #273)
615 if ($success || ($! != EINTR
)) {
620 print STDERR
" failed\n";
621 die "can't aquire lock - $!\n";
624 print STDERR
" OK\n";
627 $lock_handles->{$$}->{$filename}->{refcount
}++;
630 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
633 die "can't lock file '$filename' - $err";
640 my $filename = lock_filename
($vmid);
642 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
643 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
644 if ($refcount <= 0) {
645 $lock_handles->{$$}->{$filename} = undef;
652 my ($vmid, $timeout, $code, @param) = @_;
656 lock_aquire
($vmid, $timeout);
657 eval { $res = &$code(@param) };
669 return defined($confdesc->{$name});
672 # add JSON properties for create and set function
673 sub json_config_properties
{
676 foreach my $opt (keys %$confdesc) {
677 next if $opt eq 'parent' || $opt eq 'snaptime';
678 next if $prop->{$opt};
679 $prop->{$opt} = $confdesc->{$opt};
685 sub json_config_properties_no_rootfs
{
688 foreach my $opt (keys %$confdesc) {
689 next if $prop->{$opt};
690 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
691 $prop->{$opt} = $confdesc->{$opt};
697 # container status helpers
699 sub list_active_containers
{
701 my $filename = "/proc/net/unix";
703 # similar test is used by lcxcontainers.c: list_active_containers
706 my $fh = IO
::File-
>new ($filename, "r");
709 while (defined(my $line = <$fh>)) {
710 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
712 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
723 # warning: this is slow
727 my $active_hash = list_active_containers
();
729 return 1 if defined($active_hash->{$vmid});
734 sub get_container_disk_usage
{
737 my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/'];
747 if (my ($fsid, $total, $used, $avail) = $line =~
748 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
756 eval { PVE
::Tools
::run_command
($cmd, timeout
=> 1, outfunc
=> $parser); };
765 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
767 my $active_hash = list_active_containers
();
769 foreach my $vmid (keys %$list) {
770 my $d = $list->{$vmid};
772 my $running = defined($active_hash->{$vmid});
774 $d->{status
} = $running ?
'running' : 'stopped';
776 my $cfspath = cfs_config_path
($vmid);
777 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
779 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
780 $d->{name
} =~ s/[\s]//g;
782 $d->{cpus
} = $conf->{cpulimit
} // 0;
785 my $res = get_container_disk_usage
($vmid);
786 $d->{disk
} = $res->{used
};
787 $d->{maxdisk
} = $res->{total
};
790 # use 4GB by default ??
791 if (my $rootfs = $conf->{rootfs
}) {
792 my $rootinfo = parse_ct_mountpoint
($rootfs);
793 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
795 $d->{maxdisk
} = 4*1024*1024*1024;
801 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
802 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
813 $d->{template
} = is_template
($conf);
816 foreach my $vmid (keys %$list) {
817 my $d = $list->{$vmid};
818 next if $d->{status
} ne 'running';
820 my $pid = find_lxc_pid
($vmid);
821 my $ctime = (stat("/proc/$pid"))[10]; # 10 = ctime
822 $d->{uptime
} = time - $ctime; # the method lxcfs uses
824 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
825 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
827 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
828 my @bytes = split(/\n/, $blkio_bytes);
829 foreach my $byte (@bytes) {
830 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
831 $d->{diskread
} = $2 if $key eq 'Read';
832 $d->{diskwrite
} = $2 if $key eq 'Write';
840 my $parse_size = sub {
843 return undef if $value !~ m/^(\d+(\.\d+)?)([KMG])?$/;
844 my ($size, $unit) = ($1, $3);
847 $size = $size * 1024;
848 } elsif ($unit eq 'M') {
849 $size = $size * 1024 * 1024;
850 } elsif ($unit eq 'G') {
851 $size = $size * 1024 * 1024 * 1024;
857 my $format_size = sub {
862 my $kb = int($size/1024);
863 return $size if $kb*1024 != $size;
865 my $mb = int($kb/1024);
866 return "${kb}K" if $mb*1024 != $kb;
868 my $gb = int($mb/1024);
869 return "${mb}M" if $gb*1024 != $mb;
874 sub parse_ct_mountpoint
{
880 eval { $res = PVE
::JSONSchema
::parse_property_string
($mp_desc, $data) };
886 return undef if !defined($res->{volume
});
889 return undef if !defined($res->{size
} = &$parse_size($res->{size
}));
895 sub print_ct_mountpoint
{
896 my ($info, $nomp) = @_;
897 my $skip = $nomp ?
['mp'] : [];
898 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
901 sub print_lxc_network
{
903 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
906 sub parse_lxc_network
{
911 return $res if !$data;
913 eval { $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data) };
919 $res->{type
} = 'veth';
920 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
925 sub read_cgroup_value
{
926 my ($group, $vmid, $name, $full) = @_;
928 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
930 return PVE
::Tools
::file_get_contents
($path) if $full;
932 return PVE
::Tools
::file_read_firstline
($path);
935 sub write_cgroup_value
{
936 my ($group, $vmid, $name, $value) = @_;
938 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
939 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
943 sub find_lxc_console_pids
{
947 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
950 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
953 my @args = split(/\0/, $cmdline);
955 # serach for lxc-console -n <vmid>
956 return if scalar(@args) != 3;
957 return if $args[1] ne '-n';
958 return if $args[2] !~ m/^\d+$/;
959 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
963 push @{$res->{$vmid}}, $pid;
975 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
977 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid, '-p'], outfunc
=> $parser);
979 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
984 my $ipv4_reverse_mask = [
1020 # Note: we cannot use Net:IP, because that only allows strict
1022 sub parse_ipv4_cidr
{
1023 my ($cidr, $noerr) = @_;
1025 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
1026 return { address
=> $1, netmask
=> $ipv4_reverse_mask->[$2] };
1029 return undef if $noerr;
1031 die "unable to parse ipv4 address/mask\n";
1037 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
1040 sub check_protection
{
1041 my ($vm_conf, $err_msg) = @_;
1043 if ($vm_conf->{protection
}) {
1044 die "$err_msg - protection mode enabled\n";
1048 sub update_lxc_config
{
1049 my ($storage_cfg, $vmid, $conf) = @_;
1051 my $dir = "/var/lib/lxc/$vmid";
1053 if ($conf->{template
}) {
1055 unlink "$dir/config";
1062 die "missing 'arch' - internal error" if !$conf->{arch
};
1063 $raw .= "lxc.arch = $conf->{arch}\n";
1065 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
1066 if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
1067 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
1072 if (!has_dev_console
($conf)) {
1073 $raw .= "lxc.console = none\n";
1074 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1077 my $ttycount = get_tty_count
($conf);
1078 $raw .= "lxc.tty = $ttycount\n";
1080 # some init scripts expects a linux terminal (turnkey).
1081 $raw .= "lxc.environment = TERM=linux\n";
1083 my $utsname = $conf->{hostname
} || "CT$vmid";
1084 $raw .= "lxc.utsname = $utsname\n";
1086 my $memory = $conf->{memory
} || 512;
1087 my $swap = $conf->{swap
} // 0;
1089 my $lxcmem = int($memory*1024*1024);
1090 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1092 my $lxcswap = int(($memory + $swap)*1024*1024);
1093 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1095 if (my $cpulimit = $conf->{cpulimit
}) {
1096 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1097 my $value = int(100000*$cpulimit);
1098 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1101 my $shares = $conf->{cpuunits
} || 1024;
1102 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1104 my $mountpoint = parse_ct_mountpoint
($conf->{rootfs
});
1105 $mountpoint->{mp
} = '/';
1107 my ($path, $use_loopdev) = mountpoint_mount_path
($mountpoint, $storage_cfg);
1108 $path = "loop:$path" if $use_loopdev;
1110 $raw .= "lxc.rootfs = $path\n";
1113 foreach my $k (keys %$conf) {
1114 next if $k !~ m/^net(\d+)$/;
1116 my $d = parse_lxc_network
($conf->{$k});
1118 $raw .= "lxc.network.type = veth\n";
1119 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1120 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1121 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1122 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1125 if (my $lxcconf = $conf->{lxc
}) {
1126 foreach my $entry (@$lxcconf) {
1127 my ($k, $v) = @$entry;
1128 $netcount++ if $k eq 'lxc.network.type';
1129 $raw .= "$k = $v\n";
1133 $raw .= "lxc.network.type = empty\n" if !$netcount;
1135 File
::Path
::mkpath
("$dir/rootfs");
1137 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1140 # verify and cleanup nameserver list (replace \0 with ' ')
1141 sub verify_nameserver_list
{
1142 my ($nameserver_list) = @_;
1145 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1146 PVE
::JSONSchema
::pve_verify_ip
($server);
1147 push @list, $server;
1150 return join(' ', @list);
1153 sub verify_searchdomain_list
{
1154 my ($searchdomain_list) = @_;
1157 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1158 # todo: should we add checks for valid dns domains?
1159 push @list, $server;
1162 return join(' ', @list);
1165 sub add_unused_volume
{
1166 my ($config, $volid) = @_;
1168 # skip bind mounts and block devices
1169 return if $volid =~ m
|^/|;
1172 for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
1173 my $test = "unused$ind";
1174 if (my $vid = $config->{$test}) {
1175 return if $vid eq $volid; # do not add duplicates
1181 die "To many unused volume - please delete them first.\n" if !$key;
1183 $config->{$key} = $volid;
1188 sub update_pct_config
{
1189 my ($vmid, $conf, $running, $param, $delete) = @_;
1194 my @deleted_volumes;
1198 my $pid = find_lxc_pid
($vmid);
1199 $rootdir = "/proc/$pid/root";
1202 my $hotplug_error = sub {
1204 push @nohotplug, @_;
1211 if (defined($delete)) {
1212 foreach my $opt (@$delete) {
1213 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1214 die "unable to delete required option '$opt'\n";
1215 } elsif ($opt eq 'swap') {
1216 delete $conf->{$opt};
1217 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1218 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1219 delete $conf->{$opt};
1220 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1221 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1222 next if $hotplug_error->($opt);
1223 delete $conf->{$opt};
1224 } elsif ($opt =~ m/^net(\d)$/) {
1225 delete $conf->{$opt};
1228 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1229 } elsif ($opt eq 'protection') {
1230 delete $conf->{$opt};
1231 } elsif ($opt =~ m/^unused(\d+)$/) {
1232 next if $hotplug_error->($opt);
1233 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1234 push @deleted_volumes, $conf->{$opt};
1235 delete $conf->{$opt};
1236 } elsif ($opt =~ m/^mp(\d+)$/) {
1237 next if $hotplug_error->($opt);
1238 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1239 my $mountpoint = parse_ct_mountpoint
($conf->{$opt});
1240 add_unused_volume
($conf, $mountpoint->{volume
});
1241 delete $conf->{$opt};
1245 write_config
($vmid, $conf) if $running;
1249 # There's no separate swap size to configure, there's memory and "total"
1250 # memory (iow. memory+swap). This means we have to change them together.
1251 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1252 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1253 if (defined($wanted_memory) || defined($wanted_swap)) {
1255 $wanted_memory //= ($conf->{memory
} || 512);
1256 $wanted_swap //= ($conf->{swap
} || 0);
1258 my $total = $wanted_memory + $wanted_swap;
1260 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1261 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1263 $conf->{memory
} = $wanted_memory;
1264 $conf->{swap
} = $wanted_swap;
1266 write_config
($vmid, $conf) if $running;
1269 foreach my $opt (keys %$param) {
1270 my $value = $param->{$opt};
1271 if ($opt eq 'hostname') {
1272 $conf->{$opt} = $value;
1273 } elsif ($opt eq 'onboot') {
1274 $conf->{$opt} = $value ?
1 : 0;
1275 } elsif ($opt eq 'startup') {
1276 $conf->{$opt} = $value;
1277 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1278 next if $hotplug_error->($opt);
1279 $conf->{$opt} = $value;
1280 } elsif ($opt eq 'nameserver') {
1281 next if $hotplug_error->($opt);
1282 my $list = verify_nameserver_list
($value);
1283 $conf->{$opt} = $list;
1284 } elsif ($opt eq 'searchdomain') {
1285 next if $hotplug_error->($opt);
1286 my $list = verify_searchdomain_list
($value);
1287 $conf->{$opt} = $list;
1288 } elsif ($opt eq 'cpulimit') {
1289 next if $hotplug_error->($opt); # FIXME: hotplug
1290 $conf->{$opt} = $value;
1291 } elsif ($opt eq 'cpuunits') {
1292 $conf->{$opt} = $value;
1293 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1294 } elsif ($opt eq 'description') {
1295 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1296 } elsif ($opt =~ m/^net(\d+)$/) {
1298 my $net = parse_lxc_network
($value);
1300 $conf->{$opt} = print_lxc_network
($net);
1302 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1304 } elsif ($opt eq 'protection') {
1305 $conf->{$opt} = $value ?
1 : 0;
1306 } elsif ($opt =~ m/^mp(\d+)$/) {
1307 next if $hotplug_error->($opt);
1308 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1309 $conf->{$opt} = $value;
1311 } elsif ($opt eq 'rootfs') {
1312 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1313 die "implement me: $opt";
1315 die "implement me: $opt";
1317 write_config
($vmid, $conf) if $running;
1320 if (@deleted_volumes) {
1321 my $storage_cfg = PVE
::Storage
::config
();
1322 foreach my $volume (@deleted_volumes) {
1323 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1328 my $storage_cfg = PVE
::Storage
::config
();
1329 create_disks
($storage_cfg, $vmid, $conf, $conf);
1332 # This should be the last thing we do here
1333 if ($running && scalar(@nohotplug)) {
1334 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1338 sub has_dev_console
{
1341 return !(defined($conf->{console
}) && !$conf->{console
});
1347 return $conf->{tty
} // $confdesc->{tty
}->{default};
1353 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1356 sub get_console_command
{
1357 my ($vmid, $conf) = @_;
1359 my $cmode = get_cmode
($conf);
1361 if ($cmode eq 'console') {
1362 return ['lxc-console', '-n', $vmid, '-t', 0];
1363 } elsif ($cmode eq 'tty') {
1364 return ['lxc-console', '-n', $vmid];
1365 } elsif ($cmode eq 'shell') {
1366 return ['lxc-attach', '--clear-env', '-n', $vmid];
1368 die "internal error";
1372 sub get_primary_ips
{
1375 # return data from net0
1377 return undef if !defined($conf->{net0
});
1378 my $net = parse_lxc_network
($conf->{net0
});
1380 my $ipv4 = $net->{ip
};
1382 if ($ipv4 =~ /^(dhcp|manual)$/) {
1388 my $ipv6 = $net->{ip6
};
1390 if ($ipv6 =~ /^(dhcp|manual)$/) {
1397 return ($ipv4, $ipv6);
1400 sub delete_mountpoint_volume
{
1401 my ($storage_cfg, $vmid, $volume) = @_;
1403 # skip bind mounts and block devices
1404 if ($volume =~ m
|^/|) {
1408 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1409 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1412 sub destroy_lxc_container
{
1413 my ($storage_cfg, $vmid, $conf) = @_;
1415 foreach_mountpoint
($conf, sub {
1416 my ($ms, $mountpoint) = @_;
1417 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1420 rmdir "/var/lib/lxc/$vmid/rootfs";
1421 unlink "/var/lib/lxc/$vmid/config";
1422 rmdir "/var/lib/lxc/$vmid";
1423 destroy_config
($vmid);
1425 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1426 #PVE::Tools::run_command($cmd);
1429 sub vm_stop_cleanup
{
1430 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1435 my $vollist = get_vm_volumes
($conf);
1436 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1439 warn $@ if $@; # avoid errors - just warn
1442 my $safe_num_ne = sub {
1445 return 0 if !defined($a) && !defined($b);
1446 return 1 if !defined($a);
1447 return 1 if !defined($b);
1452 my $safe_string_ne = sub {
1455 return 0 if !defined($a) && !defined($b);
1456 return 1 if !defined($a);
1457 return 1 if !defined($b);
1463 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1465 if ($newnet->{type
} ne 'veth') {
1466 # for when there are physical interfaces
1467 die "cannot update interface of type $newnet->{type}";
1470 my $veth = "veth${vmid}i${netid}";
1471 my $eth = $newnet->{name
};
1473 if (my $oldnetcfg = $conf->{$opt}) {
1474 my $oldnet = parse_lxc_network
($oldnetcfg);
1476 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1477 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1479 PVE
::Network
::veth_delete
($veth);
1480 delete $conf->{$opt};
1481 write_config
($vmid, $conf);
1483 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1485 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1486 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1487 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1489 if ($oldnet->{bridge
}) {
1490 PVE
::Network
::tap_unplug
($veth);
1491 foreach (qw(bridge tag firewall)) {
1492 delete $oldnet->{$_};
1494 $conf->{$opt} = print_lxc_network
($oldnet);
1495 write_config
($vmid, $conf);
1498 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1499 foreach (qw(bridge tag firewall)) {
1500 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1502 $conf->{$opt} = print_lxc_network
($oldnet);
1503 write_config
($vmid, $conf);
1506 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1509 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1513 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1515 my $veth = "veth${vmid}i${netid}";
1516 my $vethpeer = $veth . "p";
1517 my $eth = $newnet->{name
};
1519 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1520 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1522 # attach peer in container
1523 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1524 PVE
::Tools
::run_command
($cmd);
1526 # link up peer in container
1527 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1528 PVE
::Tools
::run_command
($cmd);
1530 my $done = { type
=> 'veth' };
1531 foreach (qw(bridge tag firewall hwaddr name)) {
1532 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1534 $conf->{$opt} = print_lxc_network
($done);
1536 write_config
($vmid, $conf);
1539 sub update_ipconfig
{
1540 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1542 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1544 my $optdata = parse_lxc_network
($conf->{$opt});
1548 my $cmdargs = shift;
1549 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1551 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1553 my $change_ip_config = sub {
1554 my ($ipversion) = @_;
1556 my $family_opt = "-$ipversion";
1557 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1558 my $gw= "gw$suffix";
1559 my $ip= "ip$suffix";
1561 my $newip = $newnet->{$ip};
1562 my $newgw = $newnet->{$gw};
1563 my $oldip = $optdata->{$ip};
1565 my $change_ip = &$safe_string_ne($oldip, $newip);
1566 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1568 return if !$change_ip && !$change_gw;
1570 # step 1: add new IP, if this fails we cancel
1571 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1572 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1579 # step 2: replace gateway
1580 # If this fails we delete the added IP and cancel.
1581 # If it succeeds we save the config and delete the old IP, ignoring
1582 # errors. The config is then saved.
1583 # Note: 'ip route replace' can add
1586 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1589 # the route was not replaced, the old IP is still available
1590 # rollback (delete new IP) and cancel
1592 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1593 warn $@ if $@; # no need to die here
1598 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1599 # if the route was not deleted, the guest might have deleted it manually
1605 # from this point on we save the configuration
1606 # step 3: delete old IP ignoring errors
1607 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1608 # We need to enable promote_secondaries, otherwise our newly added
1609 # address will be removed along with the old one.
1612 if ($ipversion == 4) {
1613 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1614 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1615 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1617 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1619 warn $@ if $@; # no need to die here
1621 if ($ipversion == 4) {
1622 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1626 foreach my $property ($ip, $gw) {
1627 if ($newnet->{$property}) {
1628 $optdata->{$property} = $newnet->{$property};
1630 delete $optdata->{$property};
1633 $conf->{$opt} = print_lxc_network
($optdata);
1634 write_config
($vmid, $conf);
1635 $lxc_setup->setup_network($conf);
1638 &$change_ip_config(4);
1639 &$change_ip_config(6);
1643 # Internal snapshots
1645 # NOTE: Snapshot create/delete involves several non-atomic
1646 # action, and can take a long time.
1647 # So we try to avoid locking the file and use 'lock' variable
1648 # inside the config file instead.
1650 my $snapshot_copy_config = sub {
1651 my ($source, $dest) = @_;
1653 foreach my $k (keys %$source) {
1654 next if $k eq 'snapshots';
1655 next if $k eq 'snapstate';
1656 next if $k eq 'snaptime';
1657 next if $k eq 'vmstate';
1658 next if $k eq 'lock';
1659 next if $k eq 'digest';
1660 next if $k eq 'description';
1662 $dest->{$k} = $source->{$k};
1666 my $snapshot_prepare = sub {
1667 my ($vmid, $snapname, $comment) = @_;
1671 my $updatefn = sub {
1673 my $conf = load_config
($vmid);
1675 die "you can't take a snapshot if it's a template\n"
1676 if is_template
($conf);
1680 $conf->{lock} = 'snapshot';
1682 die "snapshot name '$snapname' already used\n"
1683 if defined($conf->{snapshots
}->{$snapname});
1685 my $storecfg = PVE
::Storage
::config
();
1686 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1688 $snap = $conf->{snapshots
}->{$snapname} = {};
1690 &$snapshot_copy_config($conf, $snap);
1692 $snap->{'snapstate'} = "prepare";
1693 $snap->{'snaptime'} = time();
1694 $snap->{'description'} = $comment if $comment;
1695 $conf->{snapshots
}->{$snapname} = $snap;
1697 write_config
($vmid, $conf);
1700 lock_container
($vmid, 10, $updatefn);
1705 my $snapshot_commit = sub {
1706 my ($vmid, $snapname) = @_;
1708 my $updatefn = sub {
1710 my $conf = load_config
($vmid);
1712 die "missing snapshot lock\n"
1713 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1715 die "snapshot '$snapname' does not exist\n"
1716 if !defined($conf->{snapshots
}->{$snapname});
1718 die "wrong snapshot state\n"
1719 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1720 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1722 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1723 delete $conf->{lock};
1724 $conf->{parent
} = $snapname;
1726 write_config
($vmid, $conf);
1729 lock_container
($vmid, 10 ,$updatefn);
1733 my ($feature, $conf, $storecfg, $snapname) = @_;
1737 foreach_mountpoint
($conf, sub {
1738 my ($ms, $mountpoint) = @_;
1740 return if $err; # skip further test
1742 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1744 # TODO: implement support for mountpoints
1745 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1749 return $err ?
0 : 1;
1752 sub snapshot_create
{
1753 my ($vmid, $snapname, $comment) = @_;
1755 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1757 my $conf = load_config
($vmid);
1759 my $running = check_running
($vmid);
1762 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1763 PVE
::Tools
::run_command
(['/bin/sync']);
1766 my $storecfg = PVE
::Storage
::config
();
1767 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1768 my $volid = $rootinfo->{volume
};
1771 PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]);
1774 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1775 &$snapshot_commit($vmid, $snapname);
1778 snapshot_delete
($vmid, $snapname, 1);
1783 sub snapshot_delete
{
1784 my ($vmid, $snapname, $force) = @_;
1790 my $updatefn = sub {
1792 $conf = load_config
($vmid);
1794 die "you can't delete a snapshot if vm is a template\n"
1795 if is_template
($conf);
1797 $snap = $conf->{snapshots
}->{$snapname};
1801 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1803 $snap->{snapstate
} = 'delete';
1805 write_config
($vmid, $conf);
1808 lock_container
($vmid, 10, $updatefn);
1810 my $storecfg = PVE
::Storage
::config
();
1812 my $del_snap = sub {
1816 if ($conf->{parent
} eq $snapname) {
1817 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1818 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1820 delete $conf->{parent
};
1824 delete $conf->{snapshots
}->{$snapname};
1826 write_config
($vmid, $conf);
1829 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1830 my $rootinfo = parse_ct_mountpoint
($rootfs);
1831 my $volid = $rootinfo->{volume
};
1834 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1838 if(!$err || ($err && $force)) {
1839 lock_container
($vmid, 10, $del_snap);
1841 die "Can't delete snapshot: $vmid $snapname $err\n";
1846 sub snapshot_rollback
{
1847 my ($vmid, $snapname) = @_;
1849 my $storecfg = PVE
::Storage
::config
();
1851 my $conf = load_config
($vmid);
1853 die "you can't rollback if vm is a template\n" if is_template
($conf);
1855 my $snap = $conf->{snapshots
}->{$snapname};
1857 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1859 my $rootfs = $snap->{rootfs
};
1860 my $rootinfo = parse_ct_mountpoint
($rootfs);
1861 my $volid = $rootinfo->{volume
};
1863 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1865 my $updatefn = sub {
1867 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1868 if $snap->{snapstate
};
1872 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1874 die "unable to rollback vm $vmid: vm is running\n"
1875 if check_running
($vmid);
1877 $conf->{lock} = 'rollback';
1881 # copy snapshot config to current config
1883 my $tmp_conf = $conf;
1884 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1885 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1886 delete $conf->{snaptime
};
1887 delete $conf->{snapname
};
1888 $conf->{parent
} = $snapname;
1890 write_config
($vmid, $conf);
1893 my $unlockfn = sub {
1894 delete $conf->{lock};
1895 write_config
($vmid, $conf);
1898 lock_container
($vmid, 10, $updatefn);
1900 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1902 lock_container
($vmid, 5, $unlockfn);
1905 sub template_create
{
1906 my ($vmid, $conf) = @_;
1908 my $storecfg = PVE
::Storage
::config
();
1910 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1911 my $volid = $rootinfo->{volume
};
1913 die "Template feature is not available for '$volid'\n"
1914 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1916 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1918 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1919 $rootinfo->{volume
} = $template_volid;
1920 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1922 write_config
($vmid, $conf);
1928 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1931 sub mountpoint_names
{
1934 my @names = ('rootfs');
1936 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1937 push @names, "mp$i";
1940 return $reverse ?
reverse @names : @names;
1943 # The container might have *different* symlinks than the host. realpath/abs_path
1944 # use the actual filesystem to resolve links.
1945 sub sanitize_mountpoint
{
1947 $mp = '/' . $mp; # we always start with a slash
1948 $mp =~ s
@/{2,}@/@g; # collapse sequences of slashes
1949 $mp =~ s
@/\./@@g; # collapse /./
1950 $mp =~ s
@/\.(/)?
$@$1@; # collapse a trailing /. or /./
1951 $mp =~ s
@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
1952 $mp =~ s
@/\.\
.(/)?
$@$1@; # collapse trailing /.. or /../ disregarding symlinks
1956 sub foreach_mountpoint_full
{
1957 my ($conf, $reverse, $func) = @_;
1959 foreach my $key (mountpoint_names
($reverse)) {
1960 my $value = $conf->{$key};
1961 next if !defined($value);
1962 my $mountpoint = parse_ct_mountpoint
($value);
1964 # just to be sure: rootfs is /
1965 my $path = $key eq 'rootfs' ?
'/' : $mountpoint->{mp
};
1966 $mountpoint->{mp
} = sanitize_mountpoint
($path);
1968 $path = $mountpoint->{volume
};
1969 $mountpoint->{volume
} = sanitize_mountpoint
($path) if $path =~ m
|^/|;
1971 &$func($key, $mountpoint);
1975 sub foreach_mountpoint
{
1976 my ($conf, $func) = @_;
1978 foreach_mountpoint_full
($conf, 0, $func);
1981 sub foreach_mountpoint_reverse
{
1982 my ($conf, $func) = @_;
1984 foreach_mountpoint_full
($conf, 1, $func);
1987 sub check_ct_modify_config_perm
{
1988 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1990 return 1 if $authuser ne 'root@pam';
1992 foreach my $opt (@$key_list) {
1994 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1995 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1996 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
1997 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1998 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1999 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
2000 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
2001 $opt eq 'searchdomain' || $opt eq 'hostname') {
2002 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
2004 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
2012 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
2014 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2015 my $volid_list = get_vm_volumes
($conf);
2017 foreach_mountpoint_reverse
($conf, sub {
2018 my ($ms, $mountpoint) = @_;
2020 my $volid = $mountpoint->{volume
};
2021 my $mount = $mountpoint->{mp
};
2023 return if !$volid || !$mount;
2025 my $mount_path = "$rootdir/$mount";
2026 $mount_path =~ s!/+!/!g;
2028 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
2031 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2044 my ($vmid, $storage_cfg, $conf) = @_;
2046 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2047 File
::Path
::make_path
($rootdir);
2049 my $volid_list = get_vm_volumes
($conf);
2050 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2053 foreach_mountpoint
($conf, sub {
2054 my ($ms, $mountpoint) = @_;
2056 my $volid = $mountpoint->{volume
};
2057 my $mount = $mountpoint->{mp
};
2059 return if !$volid || !$mount;
2061 my $image_path = PVE
::Storage
::path
($storage_cfg, $volid);
2062 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2063 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2065 die "unable to mount base volume - internal error" if $isBase;
2067 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2071 warn "mounting container failed - $err";
2072 umount_all
($vmid, $storage_cfg, $conf, 1);
2079 sub mountpoint_mount_path
{
2080 my ($mountpoint, $storage_cfg, $snapname) = @_;
2082 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2085 my $check_mount_path = sub {
2087 $path = File
::Spec-
>canonpath($path);
2088 my $real = Cwd
::realpath
($path);
2089 if ($real ne $path) {
2090 die "mount path modified by symlink: $path != $real";
2094 # use $rootdir = undef to just return the corresponding mount path
2095 sub mountpoint_mount
{
2096 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2098 my $volid = $mountpoint->{volume
};
2099 my $mount = $mountpoint->{mp
};
2101 return if !$volid || !$mount;
2105 if (defined($rootdir)) {
2106 $rootdir =~ s!/+$!!;
2107 $mount_path = "$rootdir/$mount";
2108 $mount_path =~ s!/+!/!g;
2109 &$check_mount_path($mount_path);
2110 File
::Path
::mkpath
($mount_path);
2113 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2115 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2119 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2120 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2122 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2123 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2125 if ($format eq 'subvol') {
2128 if ($scfg->{type
} eq 'zfspool') {
2129 my $path_arg = $path;
2130 $path_arg =~ s!^/+!!;
2131 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', '-t', 'zfs', $path_arg, $mount_path]);
2133 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2136 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
2139 return wantarray ?
($path, 0) : $path;
2140 } elsif ($format eq 'raw') {
2141 my $use_loopdev = 0;
2143 if ($scfg->{path
}) {
2144 push @extra_opts, '-o', 'loop';
2146 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'rbd') {
2149 die "unsupported storage type '$scfg->{type}'\n";
2152 if ($isBase || defined($snapname)) {
2153 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2155 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2158 return wantarray ?
($path, $use_loopdev) : $path;
2160 die "unsupported image format '$format'\n";
2162 } elsif ($volid =~ m
|^/dev/.+|) {
2163 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
2164 return wantarray ?
($volid, 0) : $volid;
2165 } elsif ($volid !~ m
|^/dev/.+| && $volid =~ m
|^/.+| && -d
$volid) {
2166 &$check_mount_path($volid);
2167 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2168 return wantarray ?
($volid, 0) : $volid;
2171 die "unsupported storage";
2174 sub get_vm_volumes
{
2175 my ($conf, $excludes) = @_;
2179 foreach_mountpoint
($conf, sub {
2180 my ($ms, $mountpoint) = @_;
2182 return if $excludes && $ms eq $excludes;
2184 my $volid = $mountpoint->{volume
};
2186 return if !$volid || $volid =~ m
|^/|;
2188 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2191 push @$vollist, $volid;
2200 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp', $dev]);
2204 my ($storage_cfg, $volid) = @_;
2206 if ($volid =~ m!^/dev/.+!) {
2211 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2213 die "cannot format volume '$volid' with no storage\n" if !$storage;
2215 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2217 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2219 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2220 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2222 die "cannot format volume '$volid' (format == $format)\n"
2223 if $format ne 'raw';
2229 my ($storecfg, $vollist) = @_;
2231 foreach my $volid (@$vollist) {
2232 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2238 my ($storecfg, $vmid, $settings, $conf) = @_;
2243 foreach_mountpoint
($settings, sub {
2244 my ($ms, $mountpoint) = @_;
2246 my $volid = $mountpoint->{volume
};
2247 my $mp = $mountpoint->{mp
};
2249 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2251 return if !$storage;
2253 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
2254 my ($storeid, $size_gb) = ($1, $2);
2256 my $size_kb = int(${size_gb
}*1024) * 1024;
2258 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2259 # fixme: use better naming ct-$vmid-disk-X.raw?
2261 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2263 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2265 format_disk
($storecfg, $volid);
2267 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2270 } elsif ($scfg->{type
} eq 'zfspool') {
2272 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2274 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm') {
2276 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2277 format_disk
($storecfg, $volid);
2279 } elsif ($scfg->{type
} eq 'rbd') {
2281 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2282 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2283 format_disk
($storecfg, $volid);
2285 die "unable to create containers on storage type '$scfg->{type}'\n";
2287 push @$vollist, $volid;
2288 my $new_mountpoint = { volume
=> $volid, size
=> $size_kb*1024, mp
=> $mp };
2289 $conf->{$ms} = print_ct_mountpoint
($new_mountpoint, $ms eq 'rootfs');
2291 # use specified/existing volid
2295 # free allocated images on error
2297 destroy_disks
($storecfg, $vollist);
2303 # bash completion helper
2305 sub complete_os_templates
{
2306 my ($cmdname, $pname, $cvalue) = @_;
2308 my $cfg = PVE
::Storage
::config
();
2312 if ($cvalue =~ m/^([^:]+):/) {
2316 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2317 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2320 foreach my $id (keys %$data) {
2321 foreach my $item (@{$data->{$id}}) {
2322 push @$res, $item->{volid
} if defined($item->{volid
});
2329 my $complete_ctid_full = sub {
2332 my $idlist = vmstatus
();
2334 my $active_hash = list_active_containers
();
2338 foreach my $id (keys %$idlist) {
2339 my $d = $idlist->{$id};
2340 if (defined($running)) {
2341 next if $d->{template
};
2342 next if $running && !$active_hash->{$id};
2343 next if !$running && $active_hash->{$id};
2352 return &$complete_ctid_full();
2355 sub complete_ctid_stopped
{
2356 return &$complete_ctid_full(0);
2359 sub complete_ctid_running
{
2360 return &$complete_ctid_full(1);