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) = @_;
1169 for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
1170 my $test = "unused$ind";
1171 if (my $vid = $config->{$test}) {
1172 return if $vid eq $volid; # do not add duplicates
1178 die "To many unused volume - please delete them first.\n" if !$key;
1180 $config->{$key} = $volid;
1185 sub update_pct_config
{
1186 my ($vmid, $conf, $running, $param, $delete) = @_;
1191 my @deleted_volumes;
1195 my $pid = find_lxc_pid
($vmid);
1196 $rootdir = "/proc/$pid/root";
1199 my $hotplug_error = sub {
1201 push @nohotplug, @_;
1208 if (defined($delete)) {
1209 foreach my $opt (@$delete) {
1210 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1211 die "unable to delete required option '$opt'\n";
1212 } elsif ($opt eq 'swap') {
1213 delete $conf->{$opt};
1214 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1215 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1216 delete $conf->{$opt};
1217 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1218 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1219 next if $hotplug_error->($opt);
1220 delete $conf->{$opt};
1221 } elsif ($opt =~ m/^net(\d)$/) {
1222 delete $conf->{$opt};
1225 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1226 } elsif ($opt eq 'protection') {
1227 delete $conf->{$opt};
1228 } elsif ($opt =~ m/^unused(\d+)$/) {
1229 next if $hotplug_error->($opt);
1230 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1231 push @deleted_volumes, $conf->{$opt};
1232 delete $conf->{$opt};
1233 } elsif ($opt =~ m/^mp(\d+)$/) {
1234 next if $hotplug_error->($opt);
1235 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1236 my $mountpoint = parse_ct_mountpoint
($conf->{$opt});
1237 add_unused_volume
($conf, $mountpoint->{volume
});
1238 delete $conf->{$opt};
1242 write_config
($vmid, $conf) if $running;
1246 # There's no separate swap size to configure, there's memory and "total"
1247 # memory (iow. memory+swap). This means we have to change them together.
1248 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1249 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1250 if (defined($wanted_memory) || defined($wanted_swap)) {
1252 $wanted_memory //= ($conf->{memory
} || 512);
1253 $wanted_swap //= ($conf->{swap
} || 0);
1255 my $total = $wanted_memory + $wanted_swap;
1257 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1258 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1260 $conf->{memory
} = $wanted_memory;
1261 $conf->{swap
} = $wanted_swap;
1263 write_config
($vmid, $conf) if $running;
1266 foreach my $opt (keys %$param) {
1267 my $value = $param->{$opt};
1268 if ($opt eq 'hostname') {
1269 $conf->{$opt} = $value;
1270 } elsif ($opt eq 'onboot') {
1271 $conf->{$opt} = $value ?
1 : 0;
1272 } elsif ($opt eq 'startup') {
1273 $conf->{$opt} = $value;
1274 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1275 next if $hotplug_error->($opt);
1276 $conf->{$opt} = $value;
1277 } elsif ($opt eq 'nameserver') {
1278 next if $hotplug_error->($opt);
1279 my $list = verify_nameserver_list
($value);
1280 $conf->{$opt} = $list;
1281 } elsif ($opt eq 'searchdomain') {
1282 next if $hotplug_error->($opt);
1283 my $list = verify_searchdomain_list
($value);
1284 $conf->{$opt} = $list;
1285 } elsif ($opt eq 'cpulimit') {
1286 next if $hotplug_error->($opt); # FIXME: hotplug
1287 $conf->{$opt} = $value;
1288 } elsif ($opt eq 'cpuunits') {
1289 $conf->{$opt} = $value;
1290 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1291 } elsif ($opt eq 'description') {
1292 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1293 } elsif ($opt =~ m/^net(\d+)$/) {
1295 my $net = parse_lxc_network
($value);
1297 $conf->{$opt} = print_lxc_network
($net);
1299 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1301 } elsif ($opt eq 'protection') {
1302 $conf->{$opt} = $value ?
1 : 0;
1303 } elsif ($opt =~ m/^mp(\d+)$/) {
1304 next if $hotplug_error->($opt);
1305 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1306 $conf->{$opt} = $value;
1308 } elsif ($opt eq 'rootfs') {
1309 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1310 die "implement me: $opt";
1312 die "implement me: $opt";
1314 write_config
($vmid, $conf) if $running;
1317 if (@deleted_volumes) {
1318 my $storage_cfg = PVE
::Storage
::config
();
1319 foreach my $volume (@deleted_volumes) {
1320 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1325 my $storage_cfg = PVE
::Storage
::config
();
1326 create_disks
($storage_cfg, $vmid, $conf, $conf);
1329 # This should be the last thing we do here
1330 if ($running && scalar(@nohotplug)) {
1331 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1335 sub has_dev_console
{
1338 return !(defined($conf->{console
}) && !$conf->{console
});
1344 return $conf->{tty
} // $confdesc->{tty
}->{default};
1350 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1353 sub get_console_command
{
1354 my ($vmid, $conf) = @_;
1356 my $cmode = get_cmode
($conf);
1358 if ($cmode eq 'console') {
1359 return ['lxc-console', '-n', $vmid, '-t', 0];
1360 } elsif ($cmode eq 'tty') {
1361 return ['lxc-console', '-n', $vmid];
1362 } elsif ($cmode eq 'shell') {
1363 return ['lxc-attach', '--clear-env', '-n', $vmid];
1365 die "internal error";
1369 sub get_primary_ips
{
1372 # return data from net0
1374 return undef if !defined($conf->{net0
});
1375 my $net = parse_lxc_network
($conf->{net0
});
1377 my $ipv4 = $net->{ip
};
1379 if ($ipv4 =~ /^(dhcp|manual)$/) {
1385 my $ipv6 = $net->{ip6
};
1387 if ($ipv6 =~ /^(dhcp|manual)$/) {
1394 return ($ipv4, $ipv6);
1397 sub delete_mountpoint_volume
{
1398 my ($storage_cfg, $vmid, $volume) = @_;
1400 # skip bind mounts and block devices
1401 if ($volume =~ m
|^/|) {
1405 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1406 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1409 sub destroy_lxc_container
{
1410 my ($storage_cfg, $vmid, $conf) = @_;
1412 foreach_mountpoint
($conf, sub {
1413 my ($ms, $mountpoint) = @_;
1414 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1417 rmdir "/var/lib/lxc/$vmid/rootfs";
1418 unlink "/var/lib/lxc/$vmid/config";
1419 rmdir "/var/lib/lxc/$vmid";
1420 destroy_config
($vmid);
1422 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1423 #PVE::Tools::run_command($cmd);
1426 sub vm_stop_cleanup
{
1427 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1432 my $vollist = get_vm_volumes
($conf);
1433 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1436 warn $@ if $@; # avoid errors - just warn
1439 my $safe_num_ne = sub {
1442 return 0 if !defined($a) && !defined($b);
1443 return 1 if !defined($a);
1444 return 1 if !defined($b);
1449 my $safe_string_ne = sub {
1452 return 0 if !defined($a) && !defined($b);
1453 return 1 if !defined($a);
1454 return 1 if !defined($b);
1460 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1462 if ($newnet->{type
} ne 'veth') {
1463 # for when there are physical interfaces
1464 die "cannot update interface of type $newnet->{type}";
1467 my $veth = "veth${vmid}i${netid}";
1468 my $eth = $newnet->{name
};
1470 if (my $oldnetcfg = $conf->{$opt}) {
1471 my $oldnet = parse_lxc_network
($oldnetcfg);
1473 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1474 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1476 PVE
::Network
::veth_delete
($veth);
1477 delete $conf->{$opt};
1478 write_config
($vmid, $conf);
1480 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1482 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1483 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1484 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1486 if ($oldnet->{bridge
}) {
1487 PVE
::Network
::tap_unplug
($veth);
1488 foreach (qw(bridge tag firewall)) {
1489 delete $oldnet->{$_};
1491 $conf->{$opt} = print_lxc_network
($oldnet);
1492 write_config
($vmid, $conf);
1495 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1496 foreach (qw(bridge tag firewall)) {
1497 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1499 $conf->{$opt} = print_lxc_network
($oldnet);
1500 write_config
($vmid, $conf);
1503 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1506 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1510 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1512 my $veth = "veth${vmid}i${netid}";
1513 my $vethpeer = $veth . "p";
1514 my $eth = $newnet->{name
};
1516 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1517 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1519 # attach peer in container
1520 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1521 PVE
::Tools
::run_command
($cmd);
1523 # link up peer in container
1524 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1525 PVE
::Tools
::run_command
($cmd);
1527 my $done = { type
=> 'veth' };
1528 foreach (qw(bridge tag firewall hwaddr name)) {
1529 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1531 $conf->{$opt} = print_lxc_network
($done);
1533 write_config
($vmid, $conf);
1536 sub update_ipconfig
{
1537 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1539 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1541 my $optdata = parse_lxc_network
($conf->{$opt});
1545 my $cmdargs = shift;
1546 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1548 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1550 my $change_ip_config = sub {
1551 my ($ipversion) = @_;
1553 my $family_opt = "-$ipversion";
1554 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1555 my $gw= "gw$suffix";
1556 my $ip= "ip$suffix";
1558 my $newip = $newnet->{$ip};
1559 my $newgw = $newnet->{$gw};
1560 my $oldip = $optdata->{$ip};
1562 my $change_ip = &$safe_string_ne($oldip, $newip);
1563 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1565 return if !$change_ip && !$change_gw;
1567 # step 1: add new IP, if this fails we cancel
1568 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1569 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1576 # step 2: replace gateway
1577 # If this fails we delete the added IP and cancel.
1578 # If it succeeds we save the config and delete the old IP, ignoring
1579 # errors. The config is then saved.
1580 # Note: 'ip route replace' can add
1583 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1586 # the route was not replaced, the old IP is still available
1587 # rollback (delete new IP) and cancel
1589 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1590 warn $@ if $@; # no need to die here
1595 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1596 # if the route was not deleted, the guest might have deleted it manually
1602 # from this point on we save the configuration
1603 # step 3: delete old IP ignoring errors
1604 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1605 # We need to enable promote_secondaries, otherwise our newly added
1606 # address will be removed along with the old one.
1609 if ($ipversion == 4) {
1610 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1611 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1612 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1614 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1616 warn $@ if $@; # no need to die here
1618 if ($ipversion == 4) {
1619 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1623 foreach my $property ($ip, $gw) {
1624 if ($newnet->{$property}) {
1625 $optdata->{$property} = $newnet->{$property};
1627 delete $optdata->{$property};
1630 $conf->{$opt} = print_lxc_network
($optdata);
1631 write_config
($vmid, $conf);
1632 $lxc_setup->setup_network($conf);
1635 &$change_ip_config(4);
1636 &$change_ip_config(6);
1640 # Internal snapshots
1642 # NOTE: Snapshot create/delete involves several non-atomic
1643 # action, and can take a long time.
1644 # So we try to avoid locking the file and use 'lock' variable
1645 # inside the config file instead.
1647 my $snapshot_copy_config = sub {
1648 my ($source, $dest) = @_;
1650 foreach my $k (keys %$source) {
1651 next if $k eq 'snapshots';
1652 next if $k eq 'snapstate';
1653 next if $k eq 'snaptime';
1654 next if $k eq 'vmstate';
1655 next if $k eq 'lock';
1656 next if $k eq 'digest';
1657 next if $k eq 'description';
1659 $dest->{$k} = $source->{$k};
1663 my $snapshot_prepare = sub {
1664 my ($vmid, $snapname, $comment) = @_;
1668 my $updatefn = sub {
1670 my $conf = load_config
($vmid);
1672 die "you can't take a snapshot if it's a template\n"
1673 if is_template
($conf);
1677 $conf->{lock} = 'snapshot';
1679 die "snapshot name '$snapname' already used\n"
1680 if defined($conf->{snapshots
}->{$snapname});
1682 my $storecfg = PVE
::Storage
::config
();
1683 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1685 $snap = $conf->{snapshots
}->{$snapname} = {};
1687 &$snapshot_copy_config($conf, $snap);
1689 $snap->{'snapstate'} = "prepare";
1690 $snap->{'snaptime'} = time();
1691 $snap->{'description'} = $comment if $comment;
1692 $conf->{snapshots
}->{$snapname} = $snap;
1694 write_config
($vmid, $conf);
1697 lock_container
($vmid, 10, $updatefn);
1702 my $snapshot_commit = sub {
1703 my ($vmid, $snapname) = @_;
1705 my $updatefn = sub {
1707 my $conf = load_config
($vmid);
1709 die "missing snapshot lock\n"
1710 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1712 die "snapshot '$snapname' does not exist\n"
1713 if !defined($conf->{snapshots
}->{$snapname});
1715 die "wrong snapshot state\n"
1716 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1717 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1719 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1720 delete $conf->{lock};
1721 $conf->{parent
} = $snapname;
1723 write_config
($vmid, $conf);
1726 lock_container
($vmid, 10 ,$updatefn);
1730 my ($feature, $conf, $storecfg, $snapname) = @_;
1734 foreach_mountpoint
($conf, sub {
1735 my ($ms, $mountpoint) = @_;
1737 return if $err; # skip further test
1739 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1741 # TODO: implement support for mountpoints
1742 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1746 return $err ?
0 : 1;
1749 sub snapshot_create
{
1750 my ($vmid, $snapname, $comment) = @_;
1752 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1754 my $conf = load_config
($vmid);
1756 my $running = check_running
($vmid);
1759 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1760 PVE
::Tools
::run_command
(['/bin/sync']);
1763 my $storecfg = PVE
::Storage
::config
();
1764 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1765 my $volid = $rootinfo->{volume
};
1768 PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]);
1771 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1772 &$snapshot_commit($vmid, $snapname);
1775 snapshot_delete
($vmid, $snapname, 1);
1780 sub snapshot_delete
{
1781 my ($vmid, $snapname, $force) = @_;
1787 my $updatefn = sub {
1789 $conf = load_config
($vmid);
1791 die "you can't delete a snapshot if vm is a template\n"
1792 if is_template
($conf);
1794 $snap = $conf->{snapshots
}->{$snapname};
1798 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1800 $snap->{snapstate
} = 'delete';
1802 write_config
($vmid, $conf);
1805 lock_container
($vmid, 10, $updatefn);
1807 my $storecfg = PVE
::Storage
::config
();
1809 my $del_snap = sub {
1813 if ($conf->{parent
} eq $snapname) {
1814 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1815 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1817 delete $conf->{parent
};
1821 delete $conf->{snapshots
}->{$snapname};
1823 write_config
($vmid, $conf);
1826 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1827 my $rootinfo = parse_ct_mountpoint
($rootfs);
1828 my $volid = $rootinfo->{volume
};
1831 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1835 if(!$err || ($err && $force)) {
1836 lock_container
($vmid, 10, $del_snap);
1838 die "Can't delete snapshot: $vmid $snapname $err\n";
1843 sub snapshot_rollback
{
1844 my ($vmid, $snapname) = @_;
1846 my $storecfg = PVE
::Storage
::config
();
1848 my $conf = load_config
($vmid);
1850 die "you can't rollback if vm is a template\n" if is_template
($conf);
1852 my $snap = $conf->{snapshots
}->{$snapname};
1854 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1856 my $rootfs = $snap->{rootfs
};
1857 my $rootinfo = parse_ct_mountpoint
($rootfs);
1858 my $volid = $rootinfo->{volume
};
1860 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1862 my $updatefn = sub {
1864 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1865 if $snap->{snapstate
};
1869 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1871 die "unable to rollback vm $vmid: vm is running\n"
1872 if check_running
($vmid);
1874 $conf->{lock} = 'rollback';
1878 # copy snapshot config to current config
1880 my $tmp_conf = $conf;
1881 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1882 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1883 delete $conf->{snaptime
};
1884 delete $conf->{snapname
};
1885 $conf->{parent
} = $snapname;
1887 write_config
($vmid, $conf);
1890 my $unlockfn = sub {
1891 delete $conf->{lock};
1892 write_config
($vmid, $conf);
1895 lock_container
($vmid, 10, $updatefn);
1897 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1899 lock_container
($vmid, 5, $unlockfn);
1902 sub template_create
{
1903 my ($vmid, $conf) = @_;
1905 my $storecfg = PVE
::Storage
::config
();
1907 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1908 my $volid = $rootinfo->{volume
};
1910 die "Template feature is not available for '$volid'\n"
1911 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1913 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1915 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1916 $rootinfo->{volume
} = $template_volid;
1917 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1919 write_config
($vmid, $conf);
1925 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1928 sub mountpoint_names
{
1931 my @names = ('rootfs');
1933 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1934 push @names, "mp$i";
1937 return $reverse ?
reverse @names : @names;
1940 # The container might have *different* symlinks than the host. realpath/abs_path
1941 # use the actual filesystem to resolve links.
1942 sub sanitize_mountpoint
{
1944 $mp = '/' . $mp; # we always start with a slash
1945 $mp =~ s
@/{2,}@/@g; # collapse sequences of slashes
1946 $mp =~ s
@/\./@@g; # collapse /./
1947 $mp =~ s
@/\.(/)?
$@$1@; # collapse a trailing /. or /./
1948 $mp =~ s
@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
1949 $mp =~ s
@/\.\
.(/)?
$@$1@; # collapse trailing /.. or /../ disregarding symlinks
1953 sub foreach_mountpoint_full
{
1954 my ($conf, $reverse, $func) = @_;
1956 foreach my $key (mountpoint_names
($reverse)) {
1957 my $value = $conf->{$key};
1958 next if !defined($value);
1959 my $mountpoint = parse_ct_mountpoint
($value);
1961 # just to be sure: rootfs is /
1962 my $path = $key eq 'rootfs' ?
'/' : $mountpoint->{mp
};
1963 $mountpoint->{mp
} = sanitize_mountpoint
($path);
1965 $path = $mountpoint->{volume
};
1966 $mountpoint->{volume
} = sanitize_mountpoint
($path) if $path =~ m
|^/|;
1968 &$func($key, $mountpoint);
1972 sub foreach_mountpoint
{
1973 my ($conf, $func) = @_;
1975 foreach_mountpoint_full
($conf, 0, $func);
1978 sub foreach_mountpoint_reverse
{
1979 my ($conf, $func) = @_;
1981 foreach_mountpoint_full
($conf, 1, $func);
1984 sub check_ct_modify_config_perm
{
1985 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1987 return 1 if $authuser ne 'root@pam';
1989 foreach my $opt (@$key_list) {
1991 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1992 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1993 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
1994 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1995 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1996 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1997 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1998 $opt eq 'searchdomain' || $opt eq 'hostname') {
1999 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
2001 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
2009 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
2011 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2012 my $volid_list = get_vm_volumes
($conf);
2014 foreach_mountpoint_reverse
($conf, sub {
2015 my ($ms, $mountpoint) = @_;
2017 my $volid = $mountpoint->{volume
};
2018 my $mount = $mountpoint->{mp
};
2020 return if !$volid || !$mount;
2022 my $mount_path = "$rootdir/$mount";
2023 $mount_path =~ s!/+!/!g;
2025 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
2028 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2041 my ($vmid, $storage_cfg, $conf) = @_;
2043 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2044 File
::Path
::make_path
($rootdir);
2046 my $volid_list = get_vm_volumes
($conf);
2047 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2050 foreach_mountpoint
($conf, sub {
2051 my ($ms, $mountpoint) = @_;
2053 my $volid = $mountpoint->{volume
};
2054 my $mount = $mountpoint->{mp
};
2056 return if !$volid || !$mount;
2058 my $image_path = PVE
::Storage
::path
($storage_cfg, $volid);
2059 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2060 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2062 die "unable to mount base volume - internal error" if $isBase;
2064 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2068 warn "mounting container failed - $err";
2069 umount_all
($vmid, $storage_cfg, $conf, 1);
2076 sub mountpoint_mount_path
{
2077 my ($mountpoint, $storage_cfg, $snapname) = @_;
2079 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2082 my $check_mount_path = sub {
2084 $path = File
::Spec-
>canonpath($path);
2085 my $real = Cwd
::realpath
($path);
2086 if ($real ne $path) {
2087 die "mount path modified by symlink: $path != $real";
2091 # use $rootdir = undef to just return the corresponding mount path
2092 sub mountpoint_mount
{
2093 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2095 my $volid = $mountpoint->{volume
};
2096 my $mount = $mountpoint->{mp
};
2098 return if !$volid || !$mount;
2102 if (defined($rootdir)) {
2103 $rootdir =~ s!/+$!!;
2104 $mount_path = "$rootdir/$mount";
2105 $mount_path =~ s!/+!/!g;
2106 &$check_mount_path($mount_path);
2107 File
::Path
::mkpath
($mount_path);
2110 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2112 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2116 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2117 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2119 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2120 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2122 if ($format eq 'subvol') {
2125 if ($scfg->{type
} eq 'zfspool') {
2126 my $path_arg = $path;
2127 $path_arg =~ s!^/+!!;
2128 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', '-t', 'zfs', $path_arg, $mount_path]);
2130 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2133 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
2136 return wantarray ?
($path, 0) : $path;
2137 } elsif ($format eq 'raw') {
2138 my $use_loopdev = 0;
2140 if ($scfg->{path
}) {
2141 push @extra_opts, '-o', 'loop';
2143 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'rbd') {
2146 die "unsupported storage type '$scfg->{type}'\n";
2149 if ($isBase || defined($snapname)) {
2150 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2152 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2155 return wantarray ?
($path, $use_loopdev) : $path;
2157 die "unsupported image format '$format'\n";
2159 } elsif ($volid =~ m
|^/dev/.+|) {
2160 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
2161 return wantarray ?
($volid, 0) : $volid;
2162 } elsif ($volid !~ m
|^/dev/.+| && $volid =~ m
|^/.+| && -d
$volid) {
2163 &$check_mount_path($volid);
2164 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2165 return wantarray ?
($volid, 0) : $volid;
2168 die "unsupported storage";
2171 sub get_vm_volumes
{
2172 my ($conf, $excludes) = @_;
2176 foreach_mountpoint
($conf, sub {
2177 my ($ms, $mountpoint) = @_;
2179 return if $excludes && $ms eq $excludes;
2181 my $volid = $mountpoint->{volume
};
2183 return if !$volid || $volid =~ m
|^/|;
2185 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2188 push @$vollist, $volid;
2197 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp', $dev]);
2201 my ($storage_cfg, $volid) = @_;
2203 if ($volid =~ m!^/dev/.+!) {
2208 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2210 die "cannot format volume '$volid' with no storage\n" if !$storage;
2212 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2214 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2216 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2217 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2219 die "cannot format volume '$volid' (format == $format)\n"
2220 if $format ne 'raw';
2226 my ($storecfg, $vollist) = @_;
2228 foreach my $volid (@$vollist) {
2229 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2235 my ($storecfg, $vmid, $settings, $conf) = @_;
2240 foreach_mountpoint
($settings, sub {
2241 my ($ms, $mountpoint) = @_;
2243 my $volid = $mountpoint->{volume
};
2244 my $mp = $mountpoint->{mp
};
2246 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2248 return if !$storage;
2250 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
2251 my ($storeid, $size_gb) = ($1, $2);
2253 my $size_kb = int(${size_gb
}*1024) * 1024;
2255 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2256 # fixme: use better naming ct-$vmid-disk-X.raw?
2258 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2260 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2262 format_disk
($storecfg, $volid);
2264 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2267 } elsif ($scfg->{type
} eq 'zfspool') {
2269 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2271 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm') {
2273 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2274 format_disk
($storecfg, $volid);
2276 } elsif ($scfg->{type
} eq 'rbd') {
2278 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2279 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2280 format_disk
($storecfg, $volid);
2282 die "unable to create containers on storage type '$scfg->{type}'\n";
2284 push @$vollist, $volid;
2285 my $new_mountpoint = { volume
=> $volid, size
=> $size_kb*1024, mp
=> $mp };
2286 $conf->{$ms} = print_ct_mountpoint
($new_mountpoint, $ms eq 'rootfs');
2288 # use specified/existing volid
2292 # free allocated images on error
2294 destroy_disks
($storecfg, $vollist);
2300 # bash completion helper
2302 sub complete_os_templates
{
2303 my ($cmdname, $pname, $cvalue) = @_;
2305 my $cfg = PVE
::Storage
::config
();
2309 if ($cvalue =~ m/^([^:]+):/) {
2313 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2314 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2317 foreach my $id (keys %$data) {
2318 foreach my $item (@{$data->{$id}}) {
2319 push @$res, $item->{volid
} if defined($item->{volid
});
2326 my $complete_ctid_full = sub {
2329 my $idlist = vmstatus
();
2331 my $active_hash = list_active_containers
();
2335 foreach my $id (keys %$idlist) {
2336 my $d = $idlist->{$id};
2337 if (defined($running)) {
2338 next if $d->{template
};
2339 next if $running && !$active_hash->{$id};
2340 next if !$running && $active_hash->{$id};
2349 return &$complete_ctid_full();
2352 sub complete_ctid_stopped
{
2353 return &$complete_ctid_full(0);
2356 sub complete_ctid_running
{
2357 return &$complete_ctid_full(1);