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
;
21 use Time
::HiRes qw
(gettimeofday
);
25 my $nodename = PVE
::INotify
::nodename
();
27 my $cpuinfo= PVE
::ProcFSTools
::read_cpuinfo
();
29 our $COMMON_TAR_FLAGS = [ '--totals', '--sparse', '--numeric-owner', '--acls',
31 '--xattrs-include=user.*',
32 '--xattrs-include=security.capability',
33 '--warning=no-xattr-write' ];
35 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
41 format_description
=> 'volume',
42 description
=> 'Volume, device or directory to mount into the container.',
46 format_description
=> '[1|0]',
47 description
=> 'Whether to include the mountpoint in backups.',
52 format
=> 'disk-size',
53 format_description
=> 'DiskSize',
54 description
=> 'Volume size (read only value).',
59 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
60 type
=> 'string', format
=> $rootfs_desc,
61 description
=> "Use volume as container root.",
65 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
66 description
=> "The name of the snapshot.",
67 type
=> 'string', format
=> 'pve-configid',
75 description
=> "Lock/unlock the VM.",
76 enum
=> [qw(migrate backup snapshot rollback)],
81 description
=> "Specifies whether a VM will be started during system bootup.",
84 startup
=> get_standard_option
('pve-startup-order'),
88 description
=> "Enable/disable Template.",
94 enum
=> ['amd64', 'i386'],
95 description
=> "OS architecture type.",
101 enum
=> ['debian', 'ubuntu', 'centos', 'archlinux'],
102 description
=> "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
107 description
=> "Attach a console device (/dev/console) to the container.",
113 description
=> "Specify the number of tty available to the container",
121 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.",
129 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.",
137 description
=> "Amount of RAM for the VM in MB.",
144 description
=> "Amount of SWAP for the VM in MB.",
150 description
=> "Set a host name for the container.",
151 type
=> 'string', format
=> 'dns-name',
157 description
=> "Container description. Only used on the configuration web interface.",
161 type
=> 'string', format
=> 'dns-name-list',
162 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
166 type
=> 'string', format
=> 'address-list',
167 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.",
169 rootfs
=> get_standard_option
('pve-ct-rootfs'),
172 type
=> 'string', format
=> 'pve-configid',
174 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
178 description
=> "Timestamp for snapshots.",
184 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).",
186 enum
=> ['shell', 'console', 'tty'],
192 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.",
197 my $valid_lxc_conf_keys = {
201 'lxc.haltsignal' => 1,
202 'lxc.rebootsignal' => 1,
203 'lxc.stopsignal' => 1,
205 'lxc.network.type' => 1,
206 'lxc.network.flags' => 1,
207 'lxc.network.link' => 1,
208 'lxc.network.mtu' => 1,
209 'lxc.network.name' => 1,
210 'lxc.network.hwaddr' => 1,
211 'lxc.network.ipv4' => 1,
212 'lxc.network.ipv4.gateway' => 1,
213 'lxc.network.ipv6' => 1,
214 'lxc.network.ipv6.gateway' => 1,
215 'lxc.network.script.up' => 1,
216 'lxc.network.script.down' => 1,
218 'lxc.console.logfile' => 1,
221 'lxc.devttydir' => 1,
222 'lxc.hook.autodev' => 1,
226 'lxc.mount.entry' => 1,
227 'lxc.mount.auto' => 1,
229 'lxc.rootfs.mount' => 1,
230 'lxc.rootfs.options' => 1,
234 'lxc.aa_profile' => 1,
235 'lxc.aa_allow_incomplete' => 1,
236 'lxc.se_context' => 1,
239 'lxc.hook.pre-start' => 1,
240 'lxc.hook.pre-mount' => 1,
241 'lxc.hook.mount' => 1,
242 'lxc.hook.start' => 1,
243 'lxc.hook.stop' => 1,
244 'lxc.hook.post-stop' => 1,
245 'lxc.hook.clone' => 1,
246 'lxc.hook.destroy' => 1,
249 'lxc.start.auto' => 1,
250 'lxc.start.delay' => 1,
251 'lxc.start.order' => 1,
253 'lxc.environment' => 1,
264 description
=> "Network interface type.",
269 format_description
=> 'String',
270 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
271 pattern
=> '[-_.\w\d]+',
275 format_description
=> 'vmbr<Number>',
276 description
=> 'Bridge to attach the network device to.',
277 pattern
=> '[-_.\w\d]+',
282 format_description
=> 'MAC',
283 description
=> 'Bridge to attach the network device to. (lxc.network.hwaddr)',
284 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
289 format_description
=> 'Number',
290 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
291 minimum
=> 64, # minimum ethernet frame is 64 bytes
296 format
=> 'pve-ipv4-config',
297 format_description
=> 'IPv4Format/CIDR',
298 description
=> 'IPv4 address in CIDR format.',
304 format_description
=> 'GatewayIPv4',
305 description
=> 'Default gateway for IPv4 traffic.',
310 format
=> 'pve-ipv6-config',
311 format_description
=> 'IPv6Format/CIDR',
312 description
=> 'IPv6 address in CIDR format.',
318 format_description
=> 'GatewayIPv6',
319 description
=> 'Default gateway for IPv6 traffic.',
324 format_description
=> '[1|0]',
325 description
=> "Controls whether this interface's firewall rules should be used.",
330 format_description
=> 'VlanNo',
333 description
=> "VLAN tag foro this interface.",
337 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
339 my $MAX_LXC_NETWORKS = 10;
340 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
341 $confdesc->{"net$i"} = {
343 type
=> 'string', format
=> $netconf_desc,
344 description
=> "Specifies network interfaces for the container.",
352 format_description
=> 'Path',
353 description
=> 'Path to the mountpoint as seen from inside the container.',
357 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
361 type
=> 'string', format
=> 'pve-volume-id',
362 description
=> "Reference to unused volumes.",
365 my $MAX_MOUNT_POINTS = 10;
366 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
367 $confdesc->{"mp$i"} = {
369 type
=> 'string', format
=> $mp_desc,
370 description
=> "Use volume as container mount point (experimental feature).",
375 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
376 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
377 $confdesc->{"unused$i"} = $unuseddesc;
380 sub write_pct_config
{
381 my ($filename, $conf) = @_;
383 delete $conf->{snapstate
}; # just to be sure
385 my $generate_raw_config = sub {
390 # add description as comment to top of file
391 my $descr = $conf->{description
} || '';
392 foreach my $cl (split(/\n/, $descr)) {
393 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
396 foreach my $key (sort keys %$conf) {
397 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
398 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
399 my $value = $conf->{$key};
400 die "detected invalid newline inside property '$key'\n" if $value =~ m/\n/;
401 $raw .= "$key: $value\n";
404 if (my $lxcconf = $conf->{lxc
}) {
405 foreach my $entry (@$lxcconf) {
406 my ($k, $v) = @$entry;
414 my $raw = &$generate_raw_config($conf);
416 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
417 $raw .= "\n[$snapname]\n";
418 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
425 my ($key, $value) = @_;
427 die "unknown setting '$key'\n" if !$confdesc->{$key};
429 my $type = $confdesc->{$key}->{type
};
431 if (!defined($value)) {
432 die "got undefined value\n";
435 if ($value =~ m/[\n\r]/) {
436 die "property contains a line feed\n";
439 if ($type eq 'boolean') {
440 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
441 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
442 die "type check ('boolean') failed - got '$value'\n";
443 } elsif ($type eq 'integer') {
444 return int($1) if $value =~ m/^(\d+)$/;
445 die "type check ('integer') failed - got '$value'\n";
446 } elsif ($type eq 'number') {
447 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
448 die "type check ('number') failed - got '$value'\n";
449 } elsif ($type eq 'string') {
450 if (my $fmt = $confdesc->{$key}->{format
}) {
451 PVE
::JSONSchema
::check_format
($fmt, $value);
460 sub parse_pct_config
{
461 my ($filename, $raw) = @_;
463 return undef if !defined($raw);
466 digest
=> Digest
::SHA
::sha1_hex
($raw),
470 $filename =~ m
|/lxc/(\d
+).conf
$|
471 || die "got strange filename '$filename'";
479 my @lines = split(/\n/, $raw);
480 foreach my $line (@lines) {
481 next if $line =~ m/^\s*$/;
483 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
485 $conf->{description
} = $descr if $descr;
487 $conf = $res->{snapshots
}->{$section} = {};
491 if ($line =~ m/^\#(.*)\s*$/) {
492 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
496 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
499 if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
500 push @{$conf->{lxc
}}, [$key, $value];
502 warn "vm $vmid - unable to parse config: $line\n";
504 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
505 $descr .= PVE
::Tools
::decode_text
($2);
506 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
507 $conf->{snapstate
} = $1;
508 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
511 eval { $value = check_type
($key, $value); };
512 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
513 $conf->{$key} = $value;
515 warn "vm $vmid - unable to parse config: $line\n";
519 $conf->{description
} = $descr if $descr;
521 delete $res->{snapstate
}; # just to be sure
527 my $vmlist = PVE
::Cluster
::get_vmlist
();
529 return $res if !$vmlist || !$vmlist->{ids
};
530 my $ids = $vmlist->{ids
};
532 foreach my $vmid (keys %$ids) {
533 next if !$vmid; # skip CT0
534 my $d = $ids->{$vmid};
535 next if !$d->{node
} || $d->{node
} ne $nodename;
536 next if !$d->{type
} || $d->{type
} ne 'lxc';
537 $res->{$vmid}->{type
} = 'lxc';
542 sub cfs_config_path
{
543 my ($vmid, $node) = @_;
545 $node = $nodename if !$node;
546 return "nodes/$node/lxc/$vmid.conf";
550 my ($vmid, $node) = @_;
552 my $cfspath = cfs_config_path
($vmid, $node);
553 return "/etc/pve/$cfspath";
557 my ($vmid, $node) = @_;
559 $node = $nodename if !$node;
560 my $cfspath = cfs_config_path
($vmid, $node);
562 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
563 die "container $vmid does not exists\n" if !defined($conf);
569 my ($vmid, $conf) = @_;
571 my $dir = "/etc/pve/nodes/$nodename/lxc";
574 write_config
($vmid, $conf);
580 unlink config_file
($vmid, $nodename);
584 my ($vmid, $conf) = @_;
586 my $cfspath = cfs_config_path
($vmid);
588 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
591 # flock: we use one file handle per process, so lock file
592 # can be called multiple times and succeeds for the same process.
594 my $lock_handles = {};
595 my $lockdir = "/run/lock/lxc";
600 return "$lockdir/pve-config-${vmid}.lock";
604 my ($vmid, $timeout) = @_;
606 $timeout = 10 if !$timeout;
609 my $filename = lock_filename
($vmid);
611 mkdir $lockdir if !-d
$lockdir;
613 my $lock_func = sub {
614 if (!$lock_handles->{$$}->{$filename}) {
615 my $fh = new IO
::File
(">>$filename") ||
616 die "can't open file - $!\n";
617 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
620 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
621 print STDERR
"trying to aquire lock...";
624 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
625 # try again on EINTR (see bug #273)
626 if ($success || ($! != EINTR
)) {
631 print STDERR
" failed\n";
632 die "can't aquire lock - $!\n";
635 print STDERR
" OK\n";
638 $lock_handles->{$$}->{$filename}->{refcount
}++;
641 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
644 die "can't lock file '$filename' - $err";
651 my $filename = lock_filename
($vmid);
653 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
654 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
655 if ($refcount <= 0) {
656 $lock_handles->{$$}->{$filename} = undef;
663 my ($vmid, $timeout, $code, @param) = @_;
667 lock_aquire
($vmid, $timeout);
668 eval { $res = &$code(@param) };
680 return defined($confdesc->{$name});
683 # add JSON properties for create and set function
684 sub json_config_properties
{
687 foreach my $opt (keys %$confdesc) {
688 next if $opt eq 'parent' || $opt eq 'snaptime';
689 next if $prop->{$opt};
690 $prop->{$opt} = $confdesc->{$opt};
696 sub json_config_properties_no_rootfs
{
699 foreach my $opt (keys %$confdesc) {
700 next if $prop->{$opt};
701 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
702 $prop->{$opt} = $confdesc->{$opt};
708 # container status helpers
710 sub list_active_containers
{
712 my $filename = "/proc/net/unix";
714 # similar test is used by lcxcontainers.c: list_active_containers
717 my $fh = IO
::File-
>new ($filename, "r");
720 while (defined(my $line = <$fh>)) {
721 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
723 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
734 # warning: this is slow
738 my $active_hash = list_active_containers
();
740 return 1 if defined($active_hash->{$vmid});
745 sub get_container_disk_usage
{
746 my ($vmid, $pid) = @_;
748 return PVE
::Tools
::df
("/proc/$pid/root/", 1);
751 my $last_proc_vmid_stat;
753 my $parse_cpuacct_stat = sub {
756 my $raw = read_cgroup_value
('cpuacct', $vmid, 'cpuacct.stat', 1);
760 if ($raw =~ m/^user (\d+)\nsystem (\d+)\n/) {
773 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
775 my $active_hash = list_active_containers
();
777 my $cpucount = $cpuinfo->{cpus
} || 1;
779 my $cdtime = gettimeofday
;
781 my $uptime = (PVE
::ProcFSTools
::read_proc_uptime
(1))[0];
783 foreach my $vmid (keys %$list) {
784 my $d = $list->{$vmid};
786 eval { $d->{pid
} = find_lxc_pid
($vmid) if defined($active_hash->{$vmid}); };
787 warn $@ if $@; # ignore errors (consider them stopped)
789 $d->{status
} = $d->{pid
} ?
'running' : 'stopped';
791 my $cfspath = cfs_config_path
($vmid);
792 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
794 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
795 $d->{name
} =~ s/[\s]//g;
797 $d->{cpus
} = $conf->{cpulimit
} || $cpucount;
800 my $res = get_container_disk_usage
($vmid, $d->{pid
});
801 $d->{disk
} = $res->{used
};
802 $d->{maxdisk
} = $res->{total
};
805 # use 4GB by default ??
806 if (my $rootfs = $conf->{rootfs
}) {
807 my $rootinfo = parse_ct_mountpoint
($rootfs);
808 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
810 $d->{maxdisk
} = 4*1024*1024*1024;
816 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
817 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
828 $d->{template
} = is_template
($conf);
831 foreach my $vmid (keys %$list) {
832 my $d = $list->{$vmid};
835 next if !$pid; # skip stopped CTs
837 my $ctime = (stat("/proc/$pid"))[10]; # 10 = ctime
838 $d->{uptime
} = time - $ctime; # the method lxcfs uses
840 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
841 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
843 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
844 my @bytes = split(/\n/, $blkio_bytes);
845 foreach my $byte (@bytes) {
846 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
847 $d->{diskread
} = $2 if $key eq 'Read';
848 $d->{diskwrite
} = $2 if $key eq 'Write';
852 my $pstat = &$parse_cpuacct_stat($vmid);
854 my $used = $pstat->{utime} + $pstat->{stime
};
856 my $old = $last_proc_vmid_stat->{$vmid};
858 $last_proc_vmid_stat->{$vmid} = {
866 my $dtime = ($cdtime - $old->{time}) * $cpucount * $cpuinfo->{user_hz
};
869 my $dutime = $used - $old->{used
};
871 $d->{cpu
} = (($dutime/$dtime)* $cpucount) / $d->{cpus
};
872 $last_proc_vmid_stat->{$vmid} = {
878 $d->{cpu
} = $old->{cpu
};
882 my $netdev = PVE
::ProcFSTools
::read_proc_net_dev
();
884 foreach my $dev (keys %$netdev) {
885 next if $dev !~ m/^veth([1-9]\d*)i/;
887 my $d = $list->{$vmid};
891 $d->{netout
} += $netdev->{$dev}->{receive
};
892 $d->{netin
} += $netdev->{$dev}->{transmit
};
899 sub parse_ct_mountpoint
{
900 my ($data, $noerr) = @_;
905 eval { $res = PVE
::JSONSchema
::parse_property_string
($mp_desc, $data) };
907 return undef if $noerr;
911 if (defined(my $size = $res->{size
})) {
912 $size = PVE
::JSONSchema
::parse_size
($size);
913 if (!defined($size)) {
914 return undef if $noerr;
915 die "invalid size: $size\n";
917 $res->{size
} = $size;
923 sub print_ct_mountpoint
{
924 my ($info, $nomp) = @_;
925 my $skip = $nomp ?
['mp'] : [];
926 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
929 sub print_lxc_network
{
931 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
934 sub parse_lxc_network
{
939 return $res if !$data;
941 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
943 $res->{type
} = 'veth';
944 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
949 sub read_cgroup_value
{
950 my ($group, $vmid, $name, $full) = @_;
952 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
954 return PVE
::Tools
::file_get_contents
($path) if $full;
956 return PVE
::Tools
::file_read_firstline
($path);
959 sub write_cgroup_value
{
960 my ($group, $vmid, $name, $value) = @_;
962 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
963 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
967 sub find_lxc_console_pids
{
971 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
974 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
977 my @args = split(/\0/, $cmdline);
979 # serach for lxc-console -n <vmid>
980 return if scalar(@args) != 3;
981 return if $args[1] ne '-n';
982 return if $args[2] !~ m/^\d+$/;
983 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
987 push @{$res->{$vmid}}, $pid;
999 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
1001 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid, '-p'], outfunc
=> $parser);
1003 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
1008 # Note: we cannot use Net:IP, because that only allows strict
1010 sub parse_ipv4_cidr
{
1011 my ($cidr, $noerr) = @_;
1013 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 <= 32)) {
1014 return { address
=> $1, netmask
=> $PVE::Network
::ipv4_reverse_mask-
>[$2] };
1017 return undef if $noerr;
1019 die "unable to parse ipv4 address/mask\n";
1025 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
1028 sub check_protection
{
1029 my ($vm_conf, $err_msg) = @_;
1031 if ($vm_conf->{protection
}) {
1032 die "$err_msg - protection mode enabled\n";
1036 sub update_lxc_config
{
1037 my ($storage_cfg, $vmid, $conf) = @_;
1039 my $dir = "/var/lib/lxc/$vmid";
1041 if ($conf->{template
}) {
1043 unlink "$dir/config";
1050 die "missing 'arch' - internal error" if !$conf->{arch
};
1051 $raw .= "lxc.arch = $conf->{arch}\n";
1053 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
1054 if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
1055 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
1060 if (!has_dev_console
($conf)) {
1061 $raw .= "lxc.console = none\n";
1062 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1065 my $ttycount = get_tty_count
($conf);
1066 $raw .= "lxc.tty = $ttycount\n";
1068 # some init scripts expects a linux terminal (turnkey).
1069 $raw .= "lxc.environment = TERM=linux\n";
1071 my $utsname = $conf->{hostname
} || "CT$vmid";
1072 $raw .= "lxc.utsname = $utsname\n";
1074 my $memory = $conf->{memory
} || 512;
1075 my $swap = $conf->{swap
} // 0;
1077 my $lxcmem = int($memory*1024*1024);
1078 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1080 my $lxcswap = int(($memory + $swap)*1024*1024);
1081 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1083 if (my $cpulimit = $conf->{cpulimit
}) {
1084 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1085 my $value = int(100000*$cpulimit);
1086 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1089 my $shares = $conf->{cpuunits
} || 1024;
1090 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1092 my $mountpoint = parse_ct_mountpoint
($conf->{rootfs
});
1093 $mountpoint->{mp
} = '/';
1095 $raw .= "lxc.rootfs = $dir/rootfs\n";
1098 foreach my $k (keys %$conf) {
1099 next if $k !~ m/^net(\d+)$/;
1101 my $d = parse_lxc_network
($conf->{$k});
1103 $raw .= "lxc.network.type = veth\n";
1104 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1105 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1106 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1107 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1110 if (my $lxcconf = $conf->{lxc
}) {
1111 foreach my $entry (@$lxcconf) {
1112 my ($k, $v) = @$entry;
1113 $netcount++ if $k eq 'lxc.network.type';
1114 $raw .= "$k = $v\n";
1118 $raw .= "lxc.network.type = empty\n" if !$netcount;
1120 File
::Path
::mkpath
("$dir/rootfs");
1122 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1125 # verify and cleanup nameserver list (replace \0 with ' ')
1126 sub verify_nameserver_list
{
1127 my ($nameserver_list) = @_;
1130 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1131 PVE
::JSONSchema
::pve_verify_ip
($server);
1132 push @list, $server;
1135 return join(' ', @list);
1138 sub verify_searchdomain_list
{
1139 my ($searchdomain_list) = @_;
1142 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1143 # todo: should we add checks for valid dns domains?
1144 push @list, $server;
1147 return join(' ', @list);
1150 sub add_unused_volume
{
1151 my ($config, $volid) = @_;
1153 # skip bind mounts and block devices
1154 return if $volid =~ m
|^/|;
1157 for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
1158 my $test = "unused$ind";
1159 if (my $vid = $config->{$test}) {
1160 return if $vid eq $volid; # do not add duplicates
1166 die "To many unused volume - please delete them first.\n" if !$key;
1168 $config->{$key} = $volid;
1173 sub update_pct_config
{
1174 my ($vmid, $conf, $running, $param, $delete) = @_;
1179 my @deleted_volumes;
1183 my $pid = find_lxc_pid
($vmid);
1184 $rootdir = "/proc/$pid/root";
1187 my $hotplug_error = sub {
1189 push @nohotplug, @_;
1196 if (defined($delete)) {
1197 foreach my $opt (@$delete) {
1198 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1199 die "unable to delete required option '$opt'\n";
1200 } elsif ($opt eq 'swap') {
1201 delete $conf->{$opt};
1202 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1203 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1204 delete $conf->{$opt};
1205 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1206 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1207 next if $hotplug_error->($opt);
1208 delete $conf->{$opt};
1209 } elsif ($opt =~ m/^net(\d)$/) {
1210 delete $conf->{$opt};
1213 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1214 } elsif ($opt eq 'protection') {
1215 delete $conf->{$opt};
1216 } elsif ($opt =~ m/^unused(\d+)$/) {
1217 next if $hotplug_error->($opt);
1218 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1219 push @deleted_volumes, $conf->{$opt};
1220 delete $conf->{$opt};
1221 } elsif ($opt =~ m/^mp(\d+)$/) {
1222 next if $hotplug_error->($opt);
1223 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1224 my $mountpoint = parse_ct_mountpoint
($conf->{$opt});
1225 add_unused_volume
($conf, $mountpoint->{volume
});
1226 delete $conf->{$opt};
1230 write_config
($vmid, $conf) if $running;
1234 # There's no separate swap size to configure, there's memory and "total"
1235 # memory (iow. memory+swap). This means we have to change them together.
1236 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1237 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1238 if (defined($wanted_memory) || defined($wanted_swap)) {
1240 $wanted_memory //= ($conf->{memory
} || 512);
1241 $wanted_swap //= ($conf->{swap
} || 0);
1243 my $total = $wanted_memory + $wanted_swap;
1245 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1246 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1248 $conf->{memory
} = $wanted_memory;
1249 $conf->{swap
} = $wanted_swap;
1251 write_config
($vmid, $conf) if $running;
1254 foreach my $opt (keys %$param) {
1255 my $value = $param->{$opt};
1256 if ($opt eq 'hostname') {
1257 $conf->{$opt} = $value;
1258 } elsif ($opt eq 'onboot') {
1259 $conf->{$opt} = $value ?
1 : 0;
1260 } elsif ($opt eq 'startup') {
1261 $conf->{$opt} = $value;
1262 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1263 next if $hotplug_error->($opt);
1264 $conf->{$opt} = $value;
1265 } elsif ($opt eq 'nameserver') {
1266 next if $hotplug_error->($opt);
1267 my $list = verify_nameserver_list
($value);
1268 $conf->{$opt} = $list;
1269 } elsif ($opt eq 'searchdomain') {
1270 next if $hotplug_error->($opt);
1271 my $list = verify_searchdomain_list
($value);
1272 $conf->{$opt} = $list;
1273 } elsif ($opt eq 'cpulimit') {
1274 next if $hotplug_error->($opt); # FIXME: hotplug
1275 $conf->{$opt} = $value;
1276 } elsif ($opt eq 'cpuunits') {
1277 $conf->{$opt} = $value;
1278 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1279 } elsif ($opt eq 'description') {
1280 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1281 } elsif ($opt =~ m/^net(\d+)$/) {
1283 my $net = parse_lxc_network
($value);
1285 $conf->{$opt} = print_lxc_network
($net);
1287 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1289 } elsif ($opt eq 'protection') {
1290 $conf->{$opt} = $value ?
1 : 0;
1291 } elsif ($opt =~ m/^mp(\d+)$/) {
1292 next if $hotplug_error->($opt);
1293 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1294 $conf->{$opt} = $value;
1296 } elsif ($opt eq 'rootfs') {
1297 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1298 die "implement me: $opt";
1300 die "implement me: $opt";
1302 write_config
($vmid, $conf) if $running;
1305 if (@deleted_volumes) {
1306 my $storage_cfg = PVE
::Storage
::config
();
1307 foreach my $volume (@deleted_volumes) {
1308 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1313 my $storage_cfg = PVE
::Storage
::config
();
1314 create_disks
($storage_cfg, $vmid, $conf, $conf);
1317 # This should be the last thing we do here
1318 if ($running && scalar(@nohotplug)) {
1319 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1323 sub has_dev_console
{
1326 return !(defined($conf->{console
}) && !$conf->{console
});
1332 return $conf->{tty
} // $confdesc->{tty
}->{default};
1338 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1341 sub get_console_command
{
1342 my ($vmid, $conf) = @_;
1344 my $cmode = get_cmode
($conf);
1346 if ($cmode eq 'console') {
1347 return ['lxc-console', '-n', $vmid, '-t', 0];
1348 } elsif ($cmode eq 'tty') {
1349 return ['lxc-console', '-n', $vmid];
1350 } elsif ($cmode eq 'shell') {
1351 return ['lxc-attach', '--clear-env', '-n', $vmid];
1353 die "internal error";
1357 sub get_primary_ips
{
1360 # return data from net0
1362 return undef if !defined($conf->{net0
});
1363 my $net = parse_lxc_network
($conf->{net0
});
1365 my $ipv4 = $net->{ip
};
1367 if ($ipv4 =~ /^(dhcp|manual)$/) {
1373 my $ipv6 = $net->{ip6
};
1375 if ($ipv6 =~ /^(auto|dhcp|manual)$/) {
1382 return ($ipv4, $ipv6);
1385 sub delete_mountpoint_volume
{
1386 my ($storage_cfg, $vmid, $volume) = @_;
1388 # skip bind mounts and block devices
1389 if ($volume =~ m
|^/|) {
1393 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1394 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1397 sub destroy_lxc_container
{
1398 my ($storage_cfg, $vmid, $conf) = @_;
1400 foreach_mountpoint
($conf, sub {
1401 my ($ms, $mountpoint) = @_;
1402 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1405 rmdir "/var/lib/lxc/$vmid/rootfs";
1406 unlink "/var/lib/lxc/$vmid/config";
1407 rmdir "/var/lib/lxc/$vmid";
1408 destroy_config
($vmid);
1410 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1411 #PVE::Tools::run_command($cmd);
1414 sub vm_stop_cleanup
{
1415 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1420 my $vollist = get_vm_volumes
($conf);
1421 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1424 warn $@ if $@; # avoid errors - just warn
1427 my $safe_num_ne = sub {
1430 return 0 if !defined($a) && !defined($b);
1431 return 1 if !defined($a);
1432 return 1 if !defined($b);
1437 my $safe_string_ne = sub {
1440 return 0 if !defined($a) && !defined($b);
1441 return 1 if !defined($a);
1442 return 1 if !defined($b);
1448 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1450 if ($newnet->{type
} ne 'veth') {
1451 # for when there are physical interfaces
1452 die "cannot update interface of type $newnet->{type}";
1455 my $veth = "veth${vmid}i${netid}";
1456 my $eth = $newnet->{name
};
1458 if (my $oldnetcfg = $conf->{$opt}) {
1459 my $oldnet = parse_lxc_network
($oldnetcfg);
1461 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1462 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1464 PVE
::Network
::veth_delete
($veth);
1465 delete $conf->{$opt};
1466 write_config
($vmid, $conf);
1468 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1470 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1471 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1472 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1474 if ($oldnet->{bridge
}) {
1475 PVE
::Network
::tap_unplug
($veth);
1476 foreach (qw(bridge tag firewall)) {
1477 delete $oldnet->{$_};
1479 $conf->{$opt} = print_lxc_network
($oldnet);
1480 write_config
($vmid, $conf);
1483 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1484 foreach (qw(bridge tag firewall)) {
1485 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1487 $conf->{$opt} = print_lxc_network
($oldnet);
1488 write_config
($vmid, $conf);
1491 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1494 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1498 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1500 my $veth = "veth${vmid}i${netid}";
1501 my $vethpeer = $veth . "p";
1502 my $eth = $newnet->{name
};
1504 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1505 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1507 # attach peer in container
1508 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1509 PVE
::Tools
::run_command
($cmd);
1511 # link up peer in container
1512 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1513 PVE
::Tools
::run_command
($cmd);
1515 my $done = { type
=> 'veth' };
1516 foreach (qw(bridge tag firewall hwaddr name)) {
1517 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1519 $conf->{$opt} = print_lxc_network
($done);
1521 write_config
($vmid, $conf);
1524 sub update_ipconfig
{
1525 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1527 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1529 my $optdata = parse_lxc_network
($conf->{$opt});
1533 my $cmdargs = shift;
1534 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1536 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1538 my $change_ip_config = sub {
1539 my ($ipversion) = @_;
1541 my $family_opt = "-$ipversion";
1542 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1543 my $gw= "gw$suffix";
1544 my $ip= "ip$suffix";
1546 my $newip = $newnet->{$ip};
1547 my $newgw = $newnet->{$gw};
1548 my $oldip = $optdata->{$ip};
1550 my $change_ip = &$safe_string_ne($oldip, $newip);
1551 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1553 return if !$change_ip && !$change_gw;
1555 # step 1: add new IP, if this fails we cancel
1556 my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/);
1557 if ($change_ip && $is_real_ip) {
1558 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1565 # step 2: replace gateway
1566 # If this fails we delete the added IP and cancel.
1567 # If it succeeds we save the config and delete the old IP, ignoring
1568 # errors. The config is then saved.
1569 # Note: 'ip route replace' can add
1573 if ($is_real_ip && !PVE
::Network
::is_ip_in_cidr
($newgw, $newip, $ipversion)) {
1574 &$ipcmd($family_opt, 'route', 'add', $newgw, 'dev', $eth);
1576 &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw);
1580 # the route was not replaced, the old IP is still available
1581 # rollback (delete new IP) and cancel
1583 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1584 warn $@ if $@; # no need to die here
1589 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1590 # if the route was not deleted, the guest might have deleted it manually
1596 # from this point on we save the configuration
1597 # step 3: delete old IP ignoring errors
1598 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1599 # We need to enable promote_secondaries, otherwise our newly added
1600 # address will be removed along with the old one.
1603 if ($ipversion == 4) {
1604 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1605 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1606 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1608 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1610 warn $@ if $@; # no need to die here
1612 if ($ipversion == 4) {
1613 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1617 foreach my $property ($ip, $gw) {
1618 if ($newnet->{$property}) {
1619 $optdata->{$property} = $newnet->{$property};
1621 delete $optdata->{$property};
1624 $conf->{$opt} = print_lxc_network
($optdata);
1625 write_config
($vmid, $conf);
1626 $lxc_setup->setup_network($conf);
1629 &$change_ip_config(4);
1630 &$change_ip_config(6);
1634 # Internal snapshots
1636 # NOTE: Snapshot create/delete involves several non-atomic
1637 # action, and can take a long time.
1638 # So we try to avoid locking the file and use 'lock' variable
1639 # inside the config file instead.
1641 my $snapshot_copy_config = sub {
1642 my ($source, $dest) = @_;
1644 foreach my $k (keys %$source) {
1645 next if $k eq 'snapshots';
1646 next if $k eq 'snapstate';
1647 next if $k eq 'snaptime';
1648 next if $k eq 'vmstate';
1649 next if $k eq 'lock';
1650 next if $k eq 'digest';
1651 next if $k eq 'description';
1653 $dest->{$k} = $source->{$k};
1657 my $snapshot_prepare = sub {
1658 my ($vmid, $snapname, $comment) = @_;
1662 my $updatefn = sub {
1664 my $conf = load_config
($vmid);
1666 die "you can't take a snapshot if it's a template\n"
1667 if is_template
($conf);
1671 $conf->{lock} = 'snapshot';
1673 die "snapshot name '$snapname' already used\n"
1674 if defined($conf->{snapshots
}->{$snapname});
1676 my $storecfg = PVE
::Storage
::config
();
1677 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1679 $snap = $conf->{snapshots
}->{$snapname} = {};
1681 &$snapshot_copy_config($conf, $snap);
1683 $snap->{'snapstate'} = "prepare";
1684 $snap->{'snaptime'} = time();
1685 $snap->{'description'} = $comment if $comment;
1686 $conf->{snapshots
}->{$snapname} = $snap;
1688 write_config
($vmid, $conf);
1691 lock_container
($vmid, 10, $updatefn);
1696 my $snapshot_commit = sub {
1697 my ($vmid, $snapname) = @_;
1699 my $updatefn = sub {
1701 my $conf = load_config
($vmid);
1703 die "missing snapshot lock\n"
1704 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1706 die "snapshot '$snapname' does not exist\n"
1707 if !defined($conf->{snapshots
}->{$snapname});
1709 die "wrong snapshot state\n"
1710 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1711 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1713 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1714 delete $conf->{lock};
1715 $conf->{parent
} = $snapname;
1717 write_config
($vmid, $conf);
1720 lock_container
($vmid, 10 ,$updatefn);
1724 my ($feature, $conf, $storecfg, $snapname) = @_;
1728 foreach_mountpoint
($conf, sub {
1729 my ($ms, $mountpoint) = @_;
1731 return if $err; # skip further test
1733 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1735 # TODO: implement support for mountpoints
1736 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1740 return $err ?
0 : 1;
1743 sub snapshot_create
{
1744 my ($vmid, $snapname, $comment) = @_;
1746 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1748 my $conf = load_config
($vmid);
1750 my $running = check_running
($vmid);
1753 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1754 PVE
::Tools
::run_command
(['/bin/sync']);
1757 my $storecfg = PVE
::Storage
::config
();
1758 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1759 my $volid = $rootinfo->{volume
};
1762 PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]);
1765 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1766 &$snapshot_commit($vmid, $snapname);
1769 snapshot_delete
($vmid, $snapname, 1);
1774 sub snapshot_delete
{
1775 my ($vmid, $snapname, $force) = @_;
1781 my $updatefn = sub {
1783 $conf = load_config
($vmid);
1785 die "you can't delete a snapshot if vm is a template\n"
1786 if is_template
($conf);
1788 $snap = $conf->{snapshots
}->{$snapname};
1792 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1794 $snap->{snapstate
} = 'delete';
1796 write_config
($vmid, $conf);
1799 lock_container
($vmid, 10, $updatefn);
1801 my $storecfg = PVE
::Storage
::config
();
1803 my $del_snap = sub {
1807 if ($conf->{parent
} eq $snapname) {
1808 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1809 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1811 delete $conf->{parent
};
1815 delete $conf->{snapshots
}->{$snapname};
1817 write_config
($vmid, $conf);
1820 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1821 my $rootinfo = parse_ct_mountpoint
($rootfs);
1822 my $volid = $rootinfo->{volume
};
1825 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1829 if(!$err || ($err && $force)) {
1830 lock_container
($vmid, 10, $del_snap);
1832 die "Can't delete snapshot: $vmid $snapname $err\n";
1837 sub snapshot_rollback
{
1838 my ($vmid, $snapname) = @_;
1840 my $storecfg = PVE
::Storage
::config
();
1842 my $conf = load_config
($vmid);
1844 die "you can't rollback if vm is a template\n" if is_template
($conf);
1846 my $snap = $conf->{snapshots
}->{$snapname};
1848 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1850 my $rootfs = $snap->{rootfs
};
1851 my $rootinfo = parse_ct_mountpoint
($rootfs);
1852 my $volid = $rootinfo->{volume
};
1854 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1856 my $updatefn = sub {
1858 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1859 if $snap->{snapstate
};
1863 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1865 die "unable to rollback vm $vmid: vm is running\n"
1866 if check_running
($vmid);
1868 $conf->{lock} = 'rollback';
1872 # copy snapshot config to current config
1874 my $tmp_conf = $conf;
1875 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1876 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1877 delete $conf->{snaptime
};
1878 delete $conf->{snapname
};
1879 $conf->{parent
} = $snapname;
1881 write_config
($vmid, $conf);
1884 my $unlockfn = sub {
1885 delete $conf->{lock};
1886 write_config
($vmid, $conf);
1889 lock_container
($vmid, 10, $updatefn);
1891 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1893 lock_container
($vmid, 5, $unlockfn);
1896 sub template_create
{
1897 my ($vmid, $conf) = @_;
1899 my $storecfg = PVE
::Storage
::config
();
1901 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1902 my $volid = $rootinfo->{volume
};
1904 die "Template feature is not available for '$volid'\n"
1905 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1907 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1909 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1910 $rootinfo->{volume
} = $template_volid;
1911 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1913 write_config
($vmid, $conf);
1919 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1922 sub mountpoint_names
{
1925 my @names = ('rootfs');
1927 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1928 push @names, "mp$i";
1931 return $reverse ?
reverse @names : @names;
1934 # The container might have *different* symlinks than the host. realpath/abs_path
1935 # use the actual filesystem to resolve links.
1936 sub sanitize_mountpoint
{
1938 $mp = '/' . $mp; # we always start with a slash
1939 $mp =~ s
@/{2,}@/@g; # collapse sequences of slashes
1940 $mp =~ s
@/\./@@g; # collapse /./
1941 $mp =~ s
@/\.(/)?
$@$1@; # collapse a trailing /. or /./
1942 $mp =~ s
@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
1943 $mp =~ s
@/\.\
.(/)?
$@$1@; # collapse trailing /.. or /../ disregarding symlinks
1947 sub foreach_mountpoint_full
{
1948 my ($conf, $reverse, $func) = @_;
1950 foreach my $key (mountpoint_names
($reverse)) {
1951 my $value = $conf->{$key};
1952 next if !defined($value);
1953 my $mountpoint = parse_ct_mountpoint
($value, 1);
1954 next if !defined($mountpoint);
1956 # just to be sure: rootfs is /
1957 my $path = $key eq 'rootfs' ?
'/' : $mountpoint->{mp
};
1958 $mountpoint->{mp
} = sanitize_mountpoint
($path);
1960 $path = $mountpoint->{volume
};
1961 $mountpoint->{volume
} = sanitize_mountpoint
($path) if $path =~ m
|^/|;
1963 &$func($key, $mountpoint);
1967 sub foreach_mountpoint
{
1968 my ($conf, $func) = @_;
1970 foreach_mountpoint_full
($conf, 0, $func);
1973 sub foreach_mountpoint_reverse
{
1974 my ($conf, $func) = @_;
1976 foreach_mountpoint_full
($conf, 1, $func);
1979 sub check_ct_modify_config_perm
{
1980 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1982 return 1 if $authuser ne 'root@pam';
1984 foreach my $opt (@$key_list) {
1986 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1987 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1988 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
1989 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1990 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1991 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1992 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1993 $opt eq 'searchdomain' || $opt eq 'hostname') {
1994 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1996 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
2004 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
2006 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2007 my $volid_list = get_vm_volumes
($conf);
2009 foreach_mountpoint_reverse
($conf, sub {
2010 my ($ms, $mountpoint) = @_;
2012 my $volid = $mountpoint->{volume
};
2013 my $mount = $mountpoint->{mp
};
2015 return if !$volid || !$mount;
2017 my $mount_path = "$rootdir/$mount";
2018 $mount_path =~ s!/+!/!g;
2020 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
2023 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2036 my ($vmid, $storage_cfg, $conf) = @_;
2038 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2039 File
::Path
::make_path
($rootdir);
2041 my $volid_list = get_vm_volumes
($conf);
2042 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2045 foreach_mountpoint
($conf, sub {
2046 my ($ms, $mountpoint) = @_;
2048 my $volid = $mountpoint->{volume
};
2049 my $mount = $mountpoint->{mp
};
2051 return if !$volid || !$mount;
2053 my $image_path = PVE
::Storage
::path
($storage_cfg, $volid);
2054 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2055 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2057 die "unable to mount base volume - internal error" if $isBase;
2059 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2063 warn "mounting container failed - $err";
2064 umount_all
($vmid, $storage_cfg, $conf, 1);
2071 sub mountpoint_mount_path
{
2072 my ($mountpoint, $storage_cfg, $snapname) = @_;
2074 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2077 my $check_mount_path = sub {
2079 $path = File
::Spec-
>canonpath($path);
2080 my $real = Cwd
::realpath
($path);
2081 if ($real ne $path) {
2082 die "mount path modified by symlink: $path != $real";
2086 # use $rootdir = undef to just return the corresponding mount path
2087 sub mountpoint_mount
{
2088 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2090 my $volid = $mountpoint->{volume
};
2091 my $mount = $mountpoint->{mp
};
2093 return if !$volid || !$mount;
2097 if (defined($rootdir)) {
2098 $rootdir =~ s!/+$!!;
2099 $mount_path = "$rootdir/$mount";
2100 $mount_path =~ s!/+!/!g;
2101 &$check_mount_path($mount_path);
2102 File
::Path
::mkpath
($mount_path);
2105 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2107 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2111 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2112 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2114 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2115 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2117 $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
2119 if ($format eq 'subvol') {
2122 if ($scfg->{type
} eq 'zfspool') {
2123 my $path_arg = $path;
2124 $path_arg =~ s!^/+!!;
2125 PVE
::Tools
::run_command
(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
2127 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2130 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
2133 return wantarray ?
($path, 0) : $path;
2134 } elsif ($format eq 'raw' || $format eq 'iso') {
2135 my $use_loopdev = 0;
2137 if ($scfg->{path
}) {
2138 push @extra_opts, '-o', 'loop';
2140 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'rbd') {
2143 die "unsupported storage type '$scfg->{type}'\n";
2146 if ($format eq 'iso') {
2147 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
2148 } elsif ($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);
2366 my $lxc = $conf->{lxc
};
2367 foreach my $entry (@$lxc) {
2368 my ($key, $value) = @$entry;
2369 next if $key ne 'lxc.id_map';
2370 if ($value =~ /^([ug])\s+(\d+)\s+(\d+)\s+(\d+)\s*$/) {
2371 my ($type, $ct, $host, $length) = ($1, $2, $3, $4);
2372 push @$id_map, [$type, $ct, $host, $length];
2374 $rootuid = $host if $type eq 'u';
2375 $rootgid = $host if $type eq 'g';
2378 die "failed to parse id_map: $value\n";
2382 if (!@$id_map && $conf->{unprivileged
}) {
2383 # Should we read them from /etc/subuid?
2384 $id_map = [ ['u', '0', '100000', '65536'],
2385 ['g', '0', '100000', '65536'] ];
2386 $rootuid = $rootgid = 100000;
2389 return ($id_map, $rootuid, $rootgid);