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 my ($path, $use_loopdev) = mountpoint_mount_path
($mountpoint, $storage_cfg);
1096 $path = "loop:$path" if $use_loopdev;
1098 $raw .= "lxc.rootfs = $path\n";
1101 foreach my $k (keys %$conf) {
1102 next if $k !~ m/^net(\d+)$/;
1104 my $d = parse_lxc_network
($conf->{$k});
1106 $raw .= "lxc.network.type = veth\n";
1107 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1108 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1109 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1110 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1113 if (my $lxcconf = $conf->{lxc
}) {
1114 foreach my $entry (@$lxcconf) {
1115 my ($k, $v) = @$entry;
1116 $netcount++ if $k eq 'lxc.network.type';
1117 $raw .= "$k = $v\n";
1121 $raw .= "lxc.network.type = empty\n" if !$netcount;
1123 File
::Path
::mkpath
("$dir/rootfs");
1125 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1128 # verify and cleanup nameserver list (replace \0 with ' ')
1129 sub verify_nameserver_list
{
1130 my ($nameserver_list) = @_;
1133 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1134 PVE
::JSONSchema
::pve_verify_ip
($server);
1135 push @list, $server;
1138 return join(' ', @list);
1141 sub verify_searchdomain_list
{
1142 my ($searchdomain_list) = @_;
1145 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1146 # todo: should we add checks for valid dns domains?
1147 push @list, $server;
1150 return join(' ', @list);
1153 sub add_unused_volume
{
1154 my ($config, $volid) = @_;
1156 # skip bind mounts and block devices
1157 return if $volid =~ m
|^/|;
1160 for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
1161 my $test = "unused$ind";
1162 if (my $vid = $config->{$test}) {
1163 return if $vid eq $volid; # do not add duplicates
1169 die "To many unused volume - please delete them first.\n" if !$key;
1171 $config->{$key} = $volid;
1176 sub update_pct_config
{
1177 my ($vmid, $conf, $running, $param, $delete) = @_;
1182 my @deleted_volumes;
1186 my $pid = find_lxc_pid
($vmid);
1187 $rootdir = "/proc/$pid/root";
1190 my $hotplug_error = sub {
1192 push @nohotplug, @_;
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 next if $hotplug_error->($opt);
1211 delete $conf->{$opt};
1212 } elsif ($opt =~ m/^net(\d)$/) {
1213 delete $conf->{$opt};
1216 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1217 } elsif ($opt eq 'protection') {
1218 delete $conf->{$opt};
1219 } elsif ($opt =~ m/^unused(\d+)$/) {
1220 next if $hotplug_error->($opt);
1221 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1222 push @deleted_volumes, $conf->{$opt};
1223 delete $conf->{$opt};
1224 } elsif ($opt =~ m/^mp(\d+)$/) {
1225 next if $hotplug_error->($opt);
1226 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1227 my $mountpoint = parse_ct_mountpoint
($conf->{$opt});
1228 add_unused_volume
($conf, $mountpoint->{volume
});
1229 delete $conf->{$opt};
1233 write_config
($vmid, $conf) if $running;
1237 # There's no separate swap size to configure, there's memory and "total"
1238 # memory (iow. memory+swap). This means we have to change them together.
1239 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1240 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1241 if (defined($wanted_memory) || defined($wanted_swap)) {
1243 $wanted_memory //= ($conf->{memory
} || 512);
1244 $wanted_swap //= ($conf->{swap
} || 0);
1246 my $total = $wanted_memory + $wanted_swap;
1248 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1249 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1251 $conf->{memory
} = $wanted_memory;
1252 $conf->{swap
} = $wanted_swap;
1254 write_config
($vmid, $conf) if $running;
1257 foreach my $opt (keys %$param) {
1258 my $value = $param->{$opt};
1259 if ($opt eq 'hostname') {
1260 $conf->{$opt} = $value;
1261 } elsif ($opt eq 'onboot') {
1262 $conf->{$opt} = $value ?
1 : 0;
1263 } elsif ($opt eq 'startup') {
1264 $conf->{$opt} = $value;
1265 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1266 next if $hotplug_error->($opt);
1267 $conf->{$opt} = $value;
1268 } elsif ($opt eq 'nameserver') {
1269 next if $hotplug_error->($opt);
1270 my $list = verify_nameserver_list
($value);
1271 $conf->{$opt} = $list;
1272 } elsif ($opt eq 'searchdomain') {
1273 next if $hotplug_error->($opt);
1274 my $list = verify_searchdomain_list
($value);
1275 $conf->{$opt} = $list;
1276 } elsif ($opt eq 'cpulimit') {
1277 next if $hotplug_error->($opt); # FIXME: hotplug
1278 $conf->{$opt} = $value;
1279 } elsif ($opt eq 'cpuunits') {
1280 $conf->{$opt} = $value;
1281 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1282 } elsif ($opt eq 'description') {
1283 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1284 } elsif ($opt =~ m/^net(\d+)$/) {
1286 my $net = parse_lxc_network
($value);
1288 $conf->{$opt} = print_lxc_network
($net);
1290 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1292 } elsif ($opt eq 'protection') {
1293 $conf->{$opt} = $value ?
1 : 0;
1294 } elsif ($opt =~ m/^mp(\d+)$/) {
1295 next if $hotplug_error->($opt);
1296 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1297 $conf->{$opt} = $value;
1299 } elsif ($opt eq 'rootfs') {
1300 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1301 die "implement me: $opt";
1303 die "implement me: $opt";
1305 write_config
($vmid, $conf) if $running;
1308 if (@deleted_volumes) {
1309 my $storage_cfg = PVE
::Storage
::config
();
1310 foreach my $volume (@deleted_volumes) {
1311 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1316 my $storage_cfg = PVE
::Storage
::config
();
1317 create_disks
($storage_cfg, $vmid, $conf, $conf);
1320 # This should be the last thing we do here
1321 if ($running && scalar(@nohotplug)) {
1322 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1326 sub has_dev_console
{
1329 return !(defined($conf->{console
}) && !$conf->{console
});
1335 return $conf->{tty
} // $confdesc->{tty
}->{default};
1341 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1344 sub get_console_command
{
1345 my ($vmid, $conf) = @_;
1347 my $cmode = get_cmode
($conf);
1349 if ($cmode eq 'console') {
1350 return ['lxc-console', '-n', $vmid, '-t', 0];
1351 } elsif ($cmode eq 'tty') {
1352 return ['lxc-console', '-n', $vmid];
1353 } elsif ($cmode eq 'shell') {
1354 return ['lxc-attach', '--clear-env', '-n', $vmid];
1356 die "internal error";
1360 sub get_primary_ips
{
1363 # return data from net0
1365 return undef if !defined($conf->{net0
});
1366 my $net = parse_lxc_network
($conf->{net0
});
1368 my $ipv4 = $net->{ip
};
1370 if ($ipv4 =~ /^(dhcp|manual)$/) {
1376 my $ipv6 = $net->{ip6
};
1378 if ($ipv6 =~ /^(auto|dhcp|manual)$/) {
1385 return ($ipv4, $ipv6);
1388 sub delete_mountpoint_volume
{
1389 my ($storage_cfg, $vmid, $volume) = @_;
1391 # skip bind mounts and block devices
1392 if ($volume =~ m
|^/|) {
1396 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1397 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1400 sub destroy_lxc_container
{
1401 my ($storage_cfg, $vmid, $conf) = @_;
1403 foreach_mountpoint
($conf, sub {
1404 my ($ms, $mountpoint) = @_;
1405 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1408 rmdir "/var/lib/lxc/$vmid/rootfs";
1409 unlink "/var/lib/lxc/$vmid/config";
1410 rmdir "/var/lib/lxc/$vmid";
1411 destroy_config
($vmid);
1413 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1414 #PVE::Tools::run_command($cmd);
1417 sub vm_stop_cleanup
{
1418 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1423 my $vollist = get_vm_volumes
($conf);
1424 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1427 warn $@ if $@; # avoid errors - just warn
1430 my $safe_num_ne = sub {
1433 return 0 if !defined($a) && !defined($b);
1434 return 1 if !defined($a);
1435 return 1 if !defined($b);
1440 my $safe_string_ne = sub {
1443 return 0 if !defined($a) && !defined($b);
1444 return 1 if !defined($a);
1445 return 1 if !defined($b);
1451 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1453 if ($newnet->{type
} ne 'veth') {
1454 # for when there are physical interfaces
1455 die "cannot update interface of type $newnet->{type}";
1458 my $veth = "veth${vmid}i${netid}";
1459 my $eth = $newnet->{name
};
1461 if (my $oldnetcfg = $conf->{$opt}) {
1462 my $oldnet = parse_lxc_network
($oldnetcfg);
1464 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1465 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1467 PVE
::Network
::veth_delete
($veth);
1468 delete $conf->{$opt};
1469 write_config
($vmid, $conf);
1471 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1473 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1474 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1475 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1477 if ($oldnet->{bridge
}) {
1478 PVE
::Network
::tap_unplug
($veth);
1479 foreach (qw(bridge tag firewall)) {
1480 delete $oldnet->{$_};
1482 $conf->{$opt} = print_lxc_network
($oldnet);
1483 write_config
($vmid, $conf);
1486 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1487 foreach (qw(bridge tag firewall)) {
1488 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1490 $conf->{$opt} = print_lxc_network
($oldnet);
1491 write_config
($vmid, $conf);
1494 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1497 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1501 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1503 my $veth = "veth${vmid}i${netid}";
1504 my $vethpeer = $veth . "p";
1505 my $eth = $newnet->{name
};
1507 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1508 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1510 # attach peer in container
1511 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1512 PVE
::Tools
::run_command
($cmd);
1514 # link up peer in container
1515 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1516 PVE
::Tools
::run_command
($cmd);
1518 my $done = { type
=> 'veth' };
1519 foreach (qw(bridge tag firewall hwaddr name)) {
1520 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1522 $conf->{$opt} = print_lxc_network
($done);
1524 write_config
($vmid, $conf);
1527 sub update_ipconfig
{
1528 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1530 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1532 my $optdata = parse_lxc_network
($conf->{$opt});
1536 my $cmdargs = shift;
1537 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1539 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1541 my $change_ip_config = sub {
1542 my ($ipversion) = @_;
1544 my $family_opt = "-$ipversion";
1545 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1546 my $gw= "gw$suffix";
1547 my $ip= "ip$suffix";
1549 my $newip = $newnet->{$ip};
1550 my $newgw = $newnet->{$gw};
1551 my $oldip = $optdata->{$ip};
1553 my $change_ip = &$safe_string_ne($oldip, $newip);
1554 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1556 return if !$change_ip && !$change_gw;
1558 # step 1: add new IP, if this fails we cancel
1559 my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/);
1560 if ($change_ip && $is_real_ip) {
1561 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1568 # step 2: replace gateway
1569 # If this fails we delete the added IP and cancel.
1570 # If it succeeds we save the config and delete the old IP, ignoring
1571 # errors. The config is then saved.
1572 # Note: 'ip route replace' can add
1576 if ($is_real_ip && !PVE
::Network
::is_ip_in_cidr
($newgw, $newip, $ipversion)) {
1577 &$ipcmd($family_opt, 'route', 'add', $newgw, 'dev', $eth);
1579 &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw);
1583 # the route was not replaced, the old IP is still available
1584 # rollback (delete new IP) and cancel
1586 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1587 warn $@ if $@; # no need to die here
1592 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1593 # if the route was not deleted, the guest might have deleted it manually
1599 # from this point on we save the configuration
1600 # step 3: delete old IP ignoring errors
1601 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1602 # We need to enable promote_secondaries, otherwise our newly added
1603 # address will be removed along with the old one.
1606 if ($ipversion == 4) {
1607 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1608 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1609 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1611 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1613 warn $@ if $@; # no need to die here
1615 if ($ipversion == 4) {
1616 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1620 foreach my $property ($ip, $gw) {
1621 if ($newnet->{$property}) {
1622 $optdata->{$property} = $newnet->{$property};
1624 delete $optdata->{$property};
1627 $conf->{$opt} = print_lxc_network
($optdata);
1628 write_config
($vmid, $conf);
1629 $lxc_setup->setup_network($conf);
1632 &$change_ip_config(4);
1633 &$change_ip_config(6);
1637 # Internal snapshots
1639 # NOTE: Snapshot create/delete involves several non-atomic
1640 # action, and can take a long time.
1641 # So we try to avoid locking the file and use 'lock' variable
1642 # inside the config file instead.
1644 my $snapshot_copy_config = sub {
1645 my ($source, $dest) = @_;
1647 foreach my $k (keys %$source) {
1648 next if $k eq 'snapshots';
1649 next if $k eq 'snapstate';
1650 next if $k eq 'snaptime';
1651 next if $k eq 'vmstate';
1652 next if $k eq 'lock';
1653 next if $k eq 'digest';
1654 next if $k eq 'description';
1656 $dest->{$k} = $source->{$k};
1660 my $snapshot_prepare = sub {
1661 my ($vmid, $snapname, $comment) = @_;
1665 my $updatefn = sub {
1667 my $conf = load_config
($vmid);
1669 die "you can't take a snapshot if it's a template\n"
1670 if is_template
($conf);
1674 $conf->{lock} = 'snapshot';
1676 die "snapshot name '$snapname' already used\n"
1677 if defined($conf->{snapshots
}->{$snapname});
1679 my $storecfg = PVE
::Storage
::config
();
1680 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1682 $snap = $conf->{snapshots
}->{$snapname} = {};
1684 &$snapshot_copy_config($conf, $snap);
1686 $snap->{'snapstate'} = "prepare";
1687 $snap->{'snaptime'} = time();
1688 $snap->{'description'} = $comment if $comment;
1689 $conf->{snapshots
}->{$snapname} = $snap;
1691 write_config
($vmid, $conf);
1694 lock_container
($vmid, 10, $updatefn);
1699 my $snapshot_commit = sub {
1700 my ($vmid, $snapname) = @_;
1702 my $updatefn = sub {
1704 my $conf = load_config
($vmid);
1706 die "missing snapshot lock\n"
1707 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1709 die "snapshot '$snapname' does not exist\n"
1710 if !defined($conf->{snapshots
}->{$snapname});
1712 die "wrong snapshot state\n"
1713 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1714 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1716 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1717 delete $conf->{lock};
1718 $conf->{parent
} = $snapname;
1720 write_config
($vmid, $conf);
1723 lock_container
($vmid, 10 ,$updatefn);
1727 my ($feature, $conf, $storecfg, $snapname) = @_;
1731 foreach_mountpoint
($conf, sub {
1732 my ($ms, $mountpoint) = @_;
1734 return if $err; # skip further test
1736 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1738 # TODO: implement support for mountpoints
1739 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1743 return $err ?
0 : 1;
1746 sub snapshot_create
{
1747 my ($vmid, $snapname, $comment) = @_;
1749 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1751 my $conf = load_config
($vmid);
1753 my $running = check_running
($vmid);
1756 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1757 PVE
::Tools
::run_command
(['/bin/sync']);
1760 my $storecfg = PVE
::Storage
::config
();
1761 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1762 my $volid = $rootinfo->{volume
};
1765 PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]);
1768 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1769 &$snapshot_commit($vmid, $snapname);
1772 snapshot_delete
($vmid, $snapname, 1);
1777 sub snapshot_delete
{
1778 my ($vmid, $snapname, $force) = @_;
1784 my $updatefn = sub {
1786 $conf = load_config
($vmid);
1788 die "you can't delete a snapshot if vm is a template\n"
1789 if is_template
($conf);
1791 $snap = $conf->{snapshots
}->{$snapname};
1795 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1797 $snap->{snapstate
} = 'delete';
1799 write_config
($vmid, $conf);
1802 lock_container
($vmid, 10, $updatefn);
1804 my $storecfg = PVE
::Storage
::config
();
1806 my $del_snap = sub {
1810 if ($conf->{parent
} eq $snapname) {
1811 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1812 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1814 delete $conf->{parent
};
1818 delete $conf->{snapshots
}->{$snapname};
1820 write_config
($vmid, $conf);
1823 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1824 my $rootinfo = parse_ct_mountpoint
($rootfs);
1825 my $volid = $rootinfo->{volume
};
1828 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1832 if(!$err || ($err && $force)) {
1833 lock_container
($vmid, 10, $del_snap);
1835 die "Can't delete snapshot: $vmid $snapname $err\n";
1840 sub snapshot_rollback
{
1841 my ($vmid, $snapname) = @_;
1843 my $storecfg = PVE
::Storage
::config
();
1845 my $conf = load_config
($vmid);
1847 die "you can't rollback if vm is a template\n" if is_template
($conf);
1849 my $snap = $conf->{snapshots
}->{$snapname};
1851 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1853 my $rootfs = $snap->{rootfs
};
1854 my $rootinfo = parse_ct_mountpoint
($rootfs);
1855 my $volid = $rootinfo->{volume
};
1857 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1859 my $updatefn = sub {
1861 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1862 if $snap->{snapstate
};
1866 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1868 die "unable to rollback vm $vmid: vm is running\n"
1869 if check_running
($vmid);
1871 $conf->{lock} = 'rollback';
1875 # copy snapshot config to current config
1877 my $tmp_conf = $conf;
1878 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1879 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1880 delete $conf->{snaptime
};
1881 delete $conf->{snapname
};
1882 $conf->{parent
} = $snapname;
1884 write_config
($vmid, $conf);
1887 my $unlockfn = sub {
1888 delete $conf->{lock};
1889 write_config
($vmid, $conf);
1892 lock_container
($vmid, 10, $updatefn);
1894 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1896 lock_container
($vmid, 5, $unlockfn);
1899 sub template_create
{
1900 my ($vmid, $conf) = @_;
1902 my $storecfg = PVE
::Storage
::config
();
1904 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1905 my $volid = $rootinfo->{volume
};
1907 die "Template feature is not available for '$volid'\n"
1908 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1910 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1912 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1913 $rootinfo->{volume
} = $template_volid;
1914 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1916 write_config
($vmid, $conf);
1922 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1925 sub mountpoint_names
{
1928 my @names = ('rootfs');
1930 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1931 push @names, "mp$i";
1934 return $reverse ?
reverse @names : @names;
1937 # The container might have *different* symlinks than the host. realpath/abs_path
1938 # use the actual filesystem to resolve links.
1939 sub sanitize_mountpoint
{
1941 $mp = '/' . $mp; # we always start with a slash
1942 $mp =~ s
@/{2,}@/@g; # collapse sequences of slashes
1943 $mp =~ s
@/\./@@g; # collapse /./
1944 $mp =~ s
@/\.(/)?
$@$1@; # collapse a trailing /. or /./
1945 $mp =~ s
@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
1946 $mp =~ s
@/\.\
.(/)?
$@$1@; # collapse trailing /.. or /../ disregarding symlinks
1950 sub foreach_mountpoint_full
{
1951 my ($conf, $reverse, $func) = @_;
1953 foreach my $key (mountpoint_names
($reverse)) {
1954 my $value = $conf->{$key};
1955 next if !defined($value);
1956 my $mountpoint = parse_ct_mountpoint
($value, 1);
1957 next if !defined($mountpoint);
1959 # just to be sure: rootfs is /
1960 my $path = $key eq 'rootfs' ?
'/' : $mountpoint->{mp
};
1961 $mountpoint->{mp
} = sanitize_mountpoint
($path);
1963 $path = $mountpoint->{volume
};
1964 $mountpoint->{volume
} = sanitize_mountpoint
($path) if $path =~ m
|^/|;
1966 &$func($key, $mountpoint);
1970 sub foreach_mountpoint
{
1971 my ($conf, $func) = @_;
1973 foreach_mountpoint_full
($conf, 0, $func);
1976 sub foreach_mountpoint_reverse
{
1977 my ($conf, $func) = @_;
1979 foreach_mountpoint_full
($conf, 1, $func);
1982 sub check_ct_modify_config_perm
{
1983 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1985 return 1 if $authuser ne 'root@pam';
1987 foreach my $opt (@$key_list) {
1989 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1990 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1991 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
1992 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1993 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1994 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1995 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1996 $opt eq 'searchdomain' || $opt eq 'hostname') {
1997 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1999 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
2007 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
2009 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2010 my $volid_list = get_vm_volumes
($conf);
2012 foreach_mountpoint_reverse
($conf, sub {
2013 my ($ms, $mountpoint) = @_;
2015 my $volid = $mountpoint->{volume
};
2016 my $mount = $mountpoint->{mp
};
2018 return if !$volid || !$mount;
2020 my $mount_path = "$rootdir/$mount";
2021 $mount_path =~ s!/+!/!g;
2023 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
2026 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2039 my ($vmid, $storage_cfg, $conf) = @_;
2041 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2042 File
::Path
::make_path
($rootdir);
2044 my $volid_list = get_vm_volumes
($conf);
2045 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2048 foreach_mountpoint
($conf, sub {
2049 my ($ms, $mountpoint) = @_;
2051 my $volid = $mountpoint->{volume
};
2052 my $mount = $mountpoint->{mp
};
2054 return if !$volid || !$mount;
2056 my $image_path = PVE
::Storage
::path
($storage_cfg, $volid);
2057 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2058 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2060 die "unable to mount base volume - internal error" if $isBase;
2062 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2066 warn "mounting container failed - $err";
2067 umount_all
($vmid, $storage_cfg, $conf, 1);
2074 sub mountpoint_mount_path
{
2075 my ($mountpoint, $storage_cfg, $snapname) = @_;
2077 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2080 my $check_mount_path = sub {
2082 $path = File
::Spec-
>canonpath($path);
2083 my $real = Cwd
::realpath
($path);
2084 if ($real ne $path) {
2085 die "mount path modified by symlink: $path != $real";
2089 # use $rootdir = undef to just return the corresponding mount path
2090 sub mountpoint_mount
{
2091 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2093 my $volid = $mountpoint->{volume
};
2094 my $mount = $mountpoint->{mp
};
2096 return if !$volid || !$mount;
2100 if (defined($rootdir)) {
2101 $rootdir =~ s!/+$!!;
2102 $mount_path = "$rootdir/$mount";
2103 $mount_path =~ s!/+!/!g;
2104 &$check_mount_path($mount_path);
2105 File
::Path
::mkpath
($mount_path);
2108 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2110 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2114 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2115 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2117 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2118 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2120 $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
2122 if ($format eq 'subvol') {
2125 if ($scfg->{type
} eq 'zfspool') {
2126 my $path_arg = $path;
2127 $path_arg =~ s!^/+!!;
2128 PVE
::Tools
::run_command
(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
2130 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2133 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
2136 return wantarray ?
($path, 0) : $path;
2137 } elsif ($format eq 'raw' || $format eq 'iso') {
2138 my $use_loopdev = 0;
2140 if ($scfg->{path
}) {
2141 push @extra_opts, '-o', 'loop';
2143 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'rbd') {
2146 die "unsupported storage type '$scfg->{type}'\n";
2149 if ($format eq 'iso') {
2150 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
2151 } elsif ($isBase || defined($snapname)) {
2152 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2154 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2157 return wantarray ?
($path, $use_loopdev) : $path;
2159 die "unsupported image format '$format'\n";
2161 } elsif ($volid =~ m
|^/dev/.+|) {
2162 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
2163 return wantarray ?
($volid, 0) : $volid;
2164 } elsif ($volid !~ m
|^/dev/.+| && $volid =~ m
|^/.+| && -d
$volid) {
2165 &$check_mount_path($volid);
2166 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2167 return wantarray ?
($volid, 0) : $volid;
2170 die "unsupported storage";
2173 sub get_vm_volumes
{
2174 my ($conf, $excludes) = @_;
2178 foreach_mountpoint
($conf, sub {
2179 my ($ms, $mountpoint) = @_;
2181 return if $excludes && $ms eq $excludes;
2183 my $volid = $mountpoint->{volume
};
2185 return if !$volid || $volid =~ m
|^/|;
2187 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2190 push @$vollist, $volid;
2199 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp', $dev]);
2203 my ($storage_cfg, $volid) = @_;
2205 if ($volid =~ m!^/dev/.+!) {
2210 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2212 die "cannot format volume '$volid' with no storage\n" if !$storage;
2214 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2216 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2218 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2219 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2221 die "cannot format volume '$volid' (format == $format)\n"
2222 if $format ne 'raw';
2228 my ($storecfg, $vollist) = @_;
2230 foreach my $volid (@$vollist) {
2231 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2237 my ($storecfg, $vmid, $settings, $conf) = @_;
2242 foreach_mountpoint
($settings, sub {
2243 my ($ms, $mountpoint) = @_;
2245 my $volid = $mountpoint->{volume
};
2246 my $mp = $mountpoint->{mp
};
2248 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2250 return if !$storage;
2252 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
2253 my ($storeid, $size_gb) = ($1, $2);
2255 my $size_kb = int(${size_gb
}*1024) * 1024;
2257 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2258 # fixme: use better naming ct-$vmid-disk-X.raw?
2260 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2262 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2264 format_disk
($storecfg, $volid);
2266 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2269 } elsif ($scfg->{type
} eq 'zfspool') {
2271 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2273 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm') {
2275 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2276 format_disk
($storecfg, $volid);
2278 } elsif ($scfg->{type
} eq 'rbd') {
2280 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2281 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2282 format_disk
($storecfg, $volid);
2284 die "unable to create containers on storage type '$scfg->{type}'\n";
2286 push @$vollist, $volid;
2287 my $new_mountpoint = { volume
=> $volid, size
=> $size_kb*1024, mp
=> $mp };
2288 $conf->{$ms} = print_ct_mountpoint
($new_mountpoint, $ms eq 'rootfs');
2290 # use specified/existing volid
2294 # free allocated images on error
2296 destroy_disks
($storecfg, $vollist);
2302 # bash completion helper
2304 sub complete_os_templates
{
2305 my ($cmdname, $pname, $cvalue) = @_;
2307 my $cfg = PVE
::Storage
::config
();
2311 if ($cvalue =~ m/^([^:]+):/) {
2315 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2316 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2319 foreach my $id (keys %$data) {
2320 foreach my $item (@{$data->{$id}}) {
2321 push @$res, $item->{volid
} if defined($item->{volid
});
2328 my $complete_ctid_full = sub {
2331 my $idlist = vmstatus
();
2333 my $active_hash = list_active_containers
();
2337 foreach my $id (keys %$idlist) {
2338 my $d = $idlist->{$id};
2339 if (defined($running)) {
2340 next if $d->{template
};
2341 next if $running && !$active_hash->{$id};
2342 next if !$running && $active_hash->{$id};
2351 return &$complete_ctid_full();
2354 sub complete_ctid_stopped
{
2355 return &$complete_ctid_full(0);
2358 sub complete_ctid_running
{
2359 return &$complete_ctid_full(1);