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 if (defined($delete)) {
1200 foreach my $opt (@$delete) {
1201 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1202 die "unable to delete required option '$opt'\n";
1203 } elsif ($opt eq 'swap') {
1204 delete $conf->{$opt};
1205 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1206 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1207 delete $conf->{$opt};
1208 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1209 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1210 delete $conf->{$opt};
1211 push @nohotplug, $opt;
1213 } elsif ($opt =~ m/^net(\d)$/) {
1214 delete $conf->{$opt};
1217 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1218 } elsif ($opt eq 'protection') {
1219 delete $conf->{$opt};
1220 } elsif ($opt =~ m/^unused(\d+)$/) {
1221 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1222 push @deleted_volumes, $conf->{$opt};
1223 delete $conf->{$opt};
1224 push @nohotplug, $opt;
1226 } elsif ($opt =~ m/^mp(\d+)$/) {
1227 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1228 my $mountpoint = parse_ct_mountpoint
($conf->{$opt});
1229 add_unused_volume
($conf, $mountpoint->{volume
});
1230 delete $conf->{$opt};
1231 push @nohotplug, $opt;
1236 write_config
($vmid, $conf) if $running;
1240 # There's no separate swap size to configure, there's memory and "total"
1241 # memory (iow. memory+swap). This means we have to change them together.
1242 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1243 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1244 if (defined($wanted_memory) || defined($wanted_swap)) {
1246 $wanted_memory //= ($conf->{memory
} || 512);
1247 $wanted_swap //= ($conf->{swap
} || 0);
1249 my $total = $wanted_memory + $wanted_swap;
1251 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1252 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1254 $conf->{memory
} = $wanted_memory;
1255 $conf->{swap
} = $wanted_swap;
1257 write_config
($vmid, $conf) if $running;
1260 foreach my $opt (keys %$param) {
1261 my $value = $param->{$opt};
1262 if ($opt eq 'hostname') {
1263 $conf->{$opt} = $value;
1264 } elsif ($opt eq 'onboot') {
1265 $conf->{$opt} = $value ?
1 : 0;
1266 } elsif ($opt eq 'startup') {
1267 $conf->{$opt} = $value;
1268 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1269 $conf->{$opt} = $value;
1270 push @nohotplug, $opt;
1272 } elsif ($opt eq 'nameserver') {
1273 my $list = verify_nameserver_list
($value);
1274 $conf->{$opt} = $list;
1275 push @nohotplug, $opt;
1277 } elsif ($opt eq 'searchdomain') {
1278 my $list = verify_searchdomain_list
($value);
1279 $conf->{$opt} = $list;
1280 push @nohotplug, $opt;
1282 } elsif ($opt eq 'cpulimit') {
1283 $conf->{$opt} = $value;
1284 push @nohotplug, $opt; # fixme: hotplug
1286 } elsif ($opt eq 'cpuunits') {
1287 $conf->{$opt} = $value;
1288 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1289 } elsif ($opt eq 'description') {
1290 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1291 } elsif ($opt =~ m/^net(\d+)$/) {
1293 my $net = parse_lxc_network
($value);
1295 $conf->{$opt} = print_lxc_network
($net);
1297 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1299 } elsif ($opt eq 'protection') {
1300 $conf->{$opt} = $value ?
1 : 0;
1301 } elsif ($opt =~ m/^mp(\d+)$/) {
1302 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1303 $conf->{$opt} = $value;
1305 push @nohotplug, $opt;
1307 } elsif ($opt eq 'rootfs') {
1308 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1309 die "implement me: $opt";
1311 die "implement me: $opt";
1313 write_config
($vmid, $conf) if $running;
1316 if (@deleted_volumes) {
1317 my $storage_cfg = PVE
::Storage
::config
();
1318 foreach my $volume (@deleted_volumes) {
1319 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1324 my $storage_cfg = PVE
::Storage
::config
();
1325 create_disks
($storage_cfg, $vmid, $conf, $conf);
1328 # This should be the last thing we do here
1329 if ($running && scalar(@nohotplug)) {
1330 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1334 sub has_dev_console
{
1337 return !(defined($conf->{console
}) && !$conf->{console
});
1343 return $conf->{tty
} // $confdesc->{tty
}->{default};
1349 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1352 sub get_console_command
{
1353 my ($vmid, $conf) = @_;
1355 my $cmode = get_cmode
($conf);
1357 if ($cmode eq 'console') {
1358 return ['lxc-console', '-n', $vmid, '-t', 0];
1359 } elsif ($cmode eq 'tty') {
1360 return ['lxc-console', '-n', $vmid];
1361 } elsif ($cmode eq 'shell') {
1362 return ['lxc-attach', '--clear-env', '-n', $vmid];
1364 die "internal error";
1368 sub get_primary_ips
{
1371 # return data from net0
1373 return undef if !defined($conf->{net0
});
1374 my $net = parse_lxc_network
($conf->{net0
});
1376 my $ipv4 = $net->{ip
};
1378 if ($ipv4 =~ /^(dhcp|manual)$/) {
1384 my $ipv6 = $net->{ip6
};
1386 if ($ipv6 =~ /^(dhcp|manual)$/) {
1393 return ($ipv4, $ipv6);
1396 sub delete_mountpoint_volume
{
1397 my ($storage_cfg, $vmid, $volume) = @_;
1399 # skip bind mounts and block devices
1400 if ($volume =~ m
|^/|) {
1404 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1405 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1408 sub destroy_lxc_container
{
1409 my ($storage_cfg, $vmid, $conf) = @_;
1411 foreach_mountpoint
($conf, sub {
1412 my ($ms, $mountpoint) = @_;
1413 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1416 rmdir "/var/lib/lxc/$vmid/rootfs";
1417 unlink "/var/lib/lxc/$vmid/config";
1418 rmdir "/var/lib/lxc/$vmid";
1419 destroy_config
($vmid);
1421 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1422 #PVE::Tools::run_command($cmd);
1425 sub vm_stop_cleanup
{
1426 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1431 my $vollist = get_vm_volumes
($conf);
1432 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1435 warn $@ if $@; # avoid errors - just warn
1438 my $safe_num_ne = sub {
1441 return 0 if !defined($a) && !defined($b);
1442 return 1 if !defined($a);
1443 return 1 if !defined($b);
1448 my $safe_string_ne = sub {
1451 return 0 if !defined($a) && !defined($b);
1452 return 1 if !defined($a);
1453 return 1 if !defined($b);
1459 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1461 if ($newnet->{type
} ne 'veth') {
1462 # for when there are physical interfaces
1463 die "cannot update interface of type $newnet->{type}";
1466 my $veth = "veth${vmid}i${netid}";
1467 my $eth = $newnet->{name
};
1469 if (my $oldnetcfg = $conf->{$opt}) {
1470 my $oldnet = parse_lxc_network
($oldnetcfg);
1472 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1473 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1475 PVE
::Network
::veth_delete
($veth);
1476 delete $conf->{$opt};
1477 write_config
($vmid, $conf);
1479 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1481 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1482 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1483 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1485 if ($oldnet->{bridge
}) {
1486 PVE
::Network
::tap_unplug
($veth);
1487 foreach (qw(bridge tag firewall)) {
1488 delete $oldnet->{$_};
1490 $conf->{$opt} = print_lxc_network
($oldnet);
1491 write_config
($vmid, $conf);
1494 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1495 foreach (qw(bridge tag firewall)) {
1496 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1498 $conf->{$opt} = print_lxc_network
($oldnet);
1499 write_config
($vmid, $conf);
1502 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1505 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1509 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1511 my $veth = "veth${vmid}i${netid}";
1512 my $vethpeer = $veth . "p";
1513 my $eth = $newnet->{name
};
1515 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1516 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1518 # attach peer in container
1519 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1520 PVE
::Tools
::run_command
($cmd);
1522 # link up peer in container
1523 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1524 PVE
::Tools
::run_command
($cmd);
1526 my $done = { type
=> 'veth' };
1527 foreach (qw(bridge tag firewall hwaddr name)) {
1528 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1530 $conf->{$opt} = print_lxc_network
($done);
1532 write_config
($vmid, $conf);
1535 sub update_ipconfig
{
1536 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1538 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1540 my $optdata = parse_lxc_network
($conf->{$opt});
1544 my $cmdargs = shift;
1545 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1547 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1549 my $change_ip_config = sub {
1550 my ($ipversion) = @_;
1552 my $family_opt = "-$ipversion";
1553 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1554 my $gw= "gw$suffix";
1555 my $ip= "ip$suffix";
1557 my $newip = $newnet->{$ip};
1558 my $newgw = $newnet->{$gw};
1559 my $oldip = $optdata->{$ip};
1561 my $change_ip = &$safe_string_ne($oldip, $newip);
1562 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1564 return if !$change_ip && !$change_gw;
1566 # step 1: add new IP, if this fails we cancel
1567 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1568 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1575 # step 2: replace gateway
1576 # If this fails we delete the added IP and cancel.
1577 # If it succeeds we save the config and delete the old IP, ignoring
1578 # errors. The config is then saved.
1579 # Note: 'ip route replace' can add
1582 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1585 # the route was not replaced, the old IP is still available
1586 # rollback (delete new IP) and cancel
1588 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1589 warn $@ if $@; # no need to die here
1594 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1595 # if the route was not deleted, the guest might have deleted it manually
1601 # from this point on we save the configuration
1602 # step 3: delete old IP ignoring errors
1603 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1604 # We need to enable promote_secondaries, otherwise our newly added
1605 # address will be removed along with the old one.
1608 if ($ipversion == 4) {
1609 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1610 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1611 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1613 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1615 warn $@ if $@; # no need to die here
1617 if ($ipversion == 4) {
1618 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1622 foreach my $property ($ip, $gw) {
1623 if ($newnet->{$property}) {
1624 $optdata->{$property} = $newnet->{$property};
1626 delete $optdata->{$property};
1629 $conf->{$opt} = print_lxc_network
($optdata);
1630 write_config
($vmid, $conf);
1631 $lxc_setup->setup_network($conf);
1634 &$change_ip_config(4);
1635 &$change_ip_config(6);
1639 # Internal snapshots
1641 # NOTE: Snapshot create/delete involves several non-atomic
1642 # action, and can take a long time.
1643 # So we try to avoid locking the file and use 'lock' variable
1644 # inside the config file instead.
1646 my $snapshot_copy_config = sub {
1647 my ($source, $dest) = @_;
1649 foreach my $k (keys %$source) {
1650 next if $k eq 'snapshots';
1651 next if $k eq 'snapstate';
1652 next if $k eq 'snaptime';
1653 next if $k eq 'vmstate';
1654 next if $k eq 'lock';
1655 next if $k eq 'digest';
1656 next if $k eq 'description';
1658 $dest->{$k} = $source->{$k};
1662 my $snapshot_prepare = sub {
1663 my ($vmid, $snapname, $comment) = @_;
1667 my $updatefn = sub {
1669 my $conf = load_config
($vmid);
1671 die "you can't take a snapshot if it's a template\n"
1672 if is_template
($conf);
1676 $conf->{lock} = 'snapshot';
1678 die "snapshot name '$snapname' already used\n"
1679 if defined($conf->{snapshots
}->{$snapname});
1681 my $storecfg = PVE
::Storage
::config
();
1682 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1684 $snap = $conf->{snapshots
}->{$snapname} = {};
1686 &$snapshot_copy_config($conf, $snap);
1688 $snap->{'snapstate'} = "prepare";
1689 $snap->{'snaptime'} = time();
1690 $snap->{'description'} = $comment if $comment;
1691 $conf->{snapshots
}->{$snapname} = $snap;
1693 write_config
($vmid, $conf);
1696 lock_container
($vmid, 10, $updatefn);
1701 my $snapshot_commit = sub {
1702 my ($vmid, $snapname) = @_;
1704 my $updatefn = sub {
1706 my $conf = load_config
($vmid);
1708 die "missing snapshot lock\n"
1709 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1711 die "snapshot '$snapname' does not exist\n"
1712 if !defined($conf->{snapshots
}->{$snapname});
1714 die "wrong snapshot state\n"
1715 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1716 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1718 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1719 delete $conf->{lock};
1720 $conf->{parent
} = $snapname;
1722 write_config
($vmid, $conf);
1725 lock_container
($vmid, 10 ,$updatefn);
1729 my ($feature, $conf, $storecfg, $snapname) = @_;
1733 foreach_mountpoint
($conf, sub {
1734 my ($ms, $mountpoint) = @_;
1736 return if $err; # skip further test
1738 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1740 # TODO: implement support for mountpoints
1741 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1745 return $err ?
0 : 1;
1748 sub snapshot_create
{
1749 my ($vmid, $snapname, $comment) = @_;
1751 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1753 my $conf = load_config
($vmid);
1755 my $running = check_running
($vmid);
1758 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1759 PVE
::Tools
::run_command
(['/bin/sync']);
1762 my $storecfg = PVE
::Storage
::config
();
1763 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1764 my $volid = $rootinfo->{volume
};
1767 PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]);
1770 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1771 &$snapshot_commit($vmid, $snapname);
1774 snapshot_delete
($vmid, $snapname, 1);
1779 sub snapshot_delete
{
1780 my ($vmid, $snapname, $force) = @_;
1786 my $updatefn = sub {
1788 $conf = load_config
($vmid);
1790 die "you can't delete a snapshot if vm is a template\n"
1791 if is_template
($conf);
1793 $snap = $conf->{snapshots
}->{$snapname};
1797 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1799 $snap->{snapstate
} = 'delete';
1801 write_config
($vmid, $conf);
1804 lock_container
($vmid, 10, $updatefn);
1806 my $storecfg = PVE
::Storage
::config
();
1808 my $del_snap = sub {
1812 if ($conf->{parent
} eq $snapname) {
1813 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1814 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1816 delete $conf->{parent
};
1820 delete $conf->{snapshots
}->{$snapname};
1822 write_config
($vmid, $conf);
1825 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1826 my $rootinfo = parse_ct_mountpoint
($rootfs);
1827 my $volid = $rootinfo->{volume
};
1830 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1834 if(!$err || ($err && $force)) {
1835 lock_container
($vmid, 10, $del_snap);
1837 die "Can't delete snapshot: $vmid $snapname $err\n";
1842 sub snapshot_rollback
{
1843 my ($vmid, $snapname) = @_;
1845 my $storecfg = PVE
::Storage
::config
();
1847 my $conf = load_config
($vmid);
1849 die "you can't rollback if vm is a template\n" if is_template
($conf);
1851 my $snap = $conf->{snapshots
}->{$snapname};
1853 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1855 my $rootfs = $snap->{rootfs
};
1856 my $rootinfo = parse_ct_mountpoint
($rootfs);
1857 my $volid = $rootinfo->{volume
};
1859 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1861 my $updatefn = sub {
1863 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1864 if $snap->{snapstate
};
1868 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1870 die "unable to rollback vm $vmid: vm is running\n"
1871 if check_running
($vmid);
1873 $conf->{lock} = 'rollback';
1877 # copy snapshot config to current config
1879 my $tmp_conf = $conf;
1880 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1881 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1882 delete $conf->{snaptime
};
1883 delete $conf->{snapname
};
1884 $conf->{parent
} = $snapname;
1886 write_config
($vmid, $conf);
1889 my $unlockfn = sub {
1890 delete $conf->{lock};
1891 write_config
($vmid, $conf);
1894 lock_container
($vmid, 10, $updatefn);
1896 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1898 lock_container
($vmid, 5, $unlockfn);
1901 sub template_create
{
1902 my ($vmid, $conf) = @_;
1904 my $storecfg = PVE
::Storage
::config
();
1906 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1907 my $volid = $rootinfo->{volume
};
1909 die "Template feature is not available for '$volid'\n"
1910 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1912 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1914 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1915 $rootinfo->{volume
} = $template_volid;
1916 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1918 write_config
($vmid, $conf);
1924 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1927 sub mountpoint_names
{
1930 my @names = ('rootfs');
1932 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1933 push @names, "mp$i";
1936 return $reverse ?
reverse @names : @names;
1939 # The container might have *different* symlinks than the host. realpath/abs_path
1940 # use the actual filesystem to resolve links.
1941 sub sanitize_mountpoint
{
1943 $mp = '/' . $mp; # we always start with a slash
1944 $mp =~ s
@/{2,}@/@g; # collapse sequences of slashes
1945 $mp =~ s
@/\./@@g; # collapse /./
1946 $mp =~ s
@/\.(/)?
$@$1@; # collapse a trailing /. or /./
1947 $mp =~ s
@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
1948 $mp =~ s
@/\.\
.(/)?
$@$1@; # collapse trailing /.. or /../ disregarding symlinks
1952 sub foreach_mountpoint_full
{
1953 my ($conf, $reverse, $func) = @_;
1955 foreach my $key (mountpoint_names
($reverse)) {
1956 my $value = $conf->{$key};
1957 next if !defined($value);
1958 my $mountpoint = parse_ct_mountpoint
($value);
1960 # just to be sure: rootfs is /
1961 my $path = $key eq 'rootfs' ?
'/' : $mountpoint->{mp
};
1962 $mountpoint->{mp
} = sanitize_mountpoint
($path);
1964 $path = $mountpoint->{volume
};
1965 $mountpoint->{volume
} = sanitize_mountpoint
($path) if $path =~ m
|^/|;
1967 &$func($key, $mountpoint);
1971 sub foreach_mountpoint
{
1972 my ($conf, $func) = @_;
1974 foreach_mountpoint_full
($conf, 0, $func);
1977 sub foreach_mountpoint_reverse
{
1978 my ($conf, $func) = @_;
1980 foreach_mountpoint_full
($conf, 1, $func);
1983 sub check_ct_modify_config_perm
{
1984 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1986 return 1 if $authuser ne 'root@pam';
1988 foreach my $opt (@$key_list) {
1990 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1991 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1992 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
1993 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1994 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1995 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1996 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1997 $opt eq 'searchdomain' || $opt eq 'hostname') {
1998 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
2000 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
2008 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
2010 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2011 my $volid_list = get_vm_volumes
($conf);
2013 foreach_mountpoint_reverse
($conf, sub {
2014 my ($ms, $mountpoint) = @_;
2016 my $volid = $mountpoint->{volume
};
2017 my $mount = $mountpoint->{mp
};
2019 return if !$volid || !$mount;
2021 my $mount_path = "$rootdir/$mount";
2022 $mount_path =~ s!/+!/!g;
2024 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
2027 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2040 my ($vmid, $storage_cfg, $conf) = @_;
2042 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2043 File
::Path
::make_path
($rootdir);
2045 my $volid_list = get_vm_volumes
($conf);
2046 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2049 foreach_mountpoint
($conf, sub {
2050 my ($ms, $mountpoint) = @_;
2052 my $volid = $mountpoint->{volume
};
2053 my $mount = $mountpoint->{mp
};
2055 return if !$volid || !$mount;
2057 my $image_path = PVE
::Storage
::path
($storage_cfg, $volid);
2058 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2059 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2061 die "unable to mount base volume - internal error" if $isBase;
2063 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2067 warn "mounting container failed - $err";
2068 umount_all
($vmid, $storage_cfg, $conf, 1);
2075 sub mountpoint_mount_path
{
2076 my ($mountpoint, $storage_cfg, $snapname) = @_;
2078 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2081 my $check_mount_path = sub {
2083 $path = File
::Spec-
>canonpath($path);
2084 my $real = Cwd
::realpath
($path);
2085 if ($real ne $path) {
2086 die "mount path modified by symlink: $path != $real";
2090 # use $rootdir = undef to just return the corresponding mount path
2091 sub mountpoint_mount
{
2092 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2094 my $volid = $mountpoint->{volume
};
2095 my $mount = $mountpoint->{mp
};
2097 return if !$volid || !$mount;
2101 if (defined($rootdir)) {
2102 $rootdir =~ s!/+$!!;
2103 $mount_path = "$rootdir/$mount";
2104 $mount_path =~ s!/+!/!g;
2105 &$check_mount_path($mount_path);
2106 File
::Path
::mkpath
($mount_path);
2109 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2111 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2115 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2116 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2118 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2119 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2121 if ($format eq 'subvol') {
2124 if ($scfg->{type
} eq 'zfspool') {
2125 my $path_arg = $path;
2126 $path_arg =~ s!^/+!!;
2127 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', '-t', 'zfs', $path_arg, $mount_path]);
2129 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2132 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
2135 return wantarray ?
($path, 0) : $path;
2136 } elsif ($format eq 'raw') {
2137 my $use_loopdev = 0;
2139 if ($scfg->{path
}) {
2140 push @extra_opts, '-o', 'loop';
2142 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'rbd') {
2145 die "unsupported storage type '$scfg->{type}'\n";
2148 if ($isBase || defined($snapname)) {
2149 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2151 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2154 return wantarray ?
($path, $use_loopdev) : $path;
2156 die "unsupported image format '$format'\n";
2158 } elsif ($volid =~ m
|^/dev/.+|) {
2159 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
2160 return wantarray ?
($volid, 0) : $volid;
2161 } elsif ($volid !~ m
|^/dev/.+| && $volid =~ m
|^/.+| && -d
$volid) {
2162 &$check_mount_path($volid);
2163 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2164 return wantarray ?
($volid, 0) : $volid;
2167 die "unsupported storage";
2170 sub get_vm_volumes
{
2171 my ($conf, $excludes) = @_;
2175 foreach_mountpoint
($conf, sub {
2176 my ($ms, $mountpoint) = @_;
2178 return if $excludes && $ms eq $excludes;
2180 my $volid = $mountpoint->{volume
};
2182 return if !$volid || $volid =~ m
|^/|;
2184 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2187 push @$vollist, $volid;
2196 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp', $dev]);
2200 my ($storage_cfg, $volid) = @_;
2202 if ($volid =~ m!^/dev/.+!) {
2207 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2209 die "cannot format volume '$volid' with no storage\n" if !$storage;
2211 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2213 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2215 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2216 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2218 die "cannot format volume '$volid' (format == $format)\n"
2219 if $format ne 'raw';
2225 my ($storecfg, $vollist) = @_;
2227 foreach my $volid (@$vollist) {
2228 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2234 my ($storecfg, $vmid, $settings, $conf) = @_;
2239 foreach_mountpoint
($settings, sub {
2240 my ($ms, $mountpoint) = @_;
2242 my $volid = $mountpoint->{volume
};
2243 my $mp = $mountpoint->{mp
};
2245 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2247 return if !$storage;
2249 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
2250 my ($storeid, $size_gb) = ($1, $2);
2252 my $size_kb = int(${size_gb
}*1024) * 1024;
2254 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2255 # fixme: use better naming ct-$vmid-disk-X.raw?
2257 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2259 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2261 format_disk
($storecfg, $volid);
2263 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2266 } elsif ($scfg->{type
} eq 'zfspool') {
2268 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2270 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm') {
2272 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2273 format_disk
($storecfg, $volid);
2275 } elsif ($scfg->{type
} eq 'rbd') {
2277 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2278 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2279 format_disk
($storecfg, $volid);
2281 die "unable to create containers on storage type '$scfg->{type}'\n";
2283 push @$vollist, $volid;
2284 my $new_mountpoint = { volume
=> $volid, size
=> $size_kb*1024, mp
=> $mp };
2285 $conf->{$ms} = print_ct_mountpoint
($new_mountpoint, $ms eq 'rootfs');
2287 # use specified/existing volid
2291 # free allocated images on error
2293 destroy_disks
($storecfg, $vollist);
2299 # bash completion helper
2301 sub complete_os_templates
{
2302 my ($cmdname, $pname, $cvalue) = @_;
2304 my $cfg = PVE
::Storage
::config
();
2308 if ($cvalue =~ m/^([^:]+):/) {
2312 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2313 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2316 foreach my $id (keys %$data) {
2317 foreach my $item (@{$data->{$id}}) {
2318 push @$res, $item->{volid
} if defined($item->{volid
});
2325 my $complete_ctid_full = sub {
2328 my $idlist = vmstatus
();
2330 my $active_hash = list_active_containers
();
2334 foreach my $id (keys %$idlist) {
2335 my $d = $idlist->{$id};
2336 if (defined($running)) {
2337 next if $d->{template
};
2338 next if $running && !$active_hash->{$id};
2339 next if !$running && $active_hash->{$id};
2348 return &$complete_ctid_full();
2351 sub complete_ctid_stopped
{
2352 return &$complete_ctid_full(0);
2355 sub complete_ctid_running
{
2356 return &$complete_ctid_full(1);