12 use PVE
::Cluster
qw(cfs_register_file cfs_read_file);
16 use PVE
::JSONSchema
qw(get_standard_option);
17 use PVE
::Tools
qw($IPV6RE $IPV4RE dir_glob_foreach);
19 use PVE
::AccessControl
;
24 my $nodename = PVE
::INotify
::nodename
();
26 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
32 format_description
=> 'volume',
33 description
=> 'Volume, device or directory to mount into the container.',
37 format_description
=> '[1|0]',
38 description
=> 'Whether to include the mountpoint in backups.',
43 format
=> 'disk-size',
44 format_description
=> 'DiskSize',
45 description
=> 'Volume size (read only value).',
50 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
51 type
=> 'string', format
=> $rootfs_desc,
52 description
=> "Use volume as container root.",
56 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
57 description
=> "The name of the snapshot.",
58 type
=> 'string', format
=> 'pve-configid',
66 description
=> "Lock/unlock the VM.",
67 enum
=> [qw(migrate backup snapshot rollback)],
72 description
=> "Specifies whether a VM will be started during system bootup.",
75 startup
=> get_standard_option
('pve-startup-order'),
79 description
=> "Enable/disable Template.",
85 enum
=> ['amd64', 'i386'],
86 description
=> "OS architecture type.",
92 enum
=> ['debian', 'ubuntu', 'centos', 'archlinux'],
93 description
=> "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
98 description
=> "Attach a console device (/dev/console) to the container.",
104 description
=> "Specify the number of tty available to the container",
112 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.",
120 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.",
128 description
=> "Amount of RAM for the VM in MB.",
135 description
=> "Amount of SWAP for the VM in MB.",
141 description
=> "Set a host name for the container.",
142 type
=> 'string', format
=> 'dns-name',
148 description
=> "Container description. Only used on the configuration web interface.",
152 type
=> 'string', format
=> 'dns-name-list',
153 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
157 type
=> 'string', format
=> 'address-list',
158 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.",
160 rootfs
=> get_standard_option
('pve-ct-rootfs'),
163 type
=> 'string', format
=> 'pve-configid',
165 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
169 description
=> "Timestamp for snapshots.",
175 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).",
177 enum
=> ['shell', 'console', 'tty'],
183 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.",
188 my $valid_lxc_conf_keys = {
192 'lxc.haltsignal' => 1,
193 'lxc.rebootsignal' => 1,
194 'lxc.stopsignal' => 1,
196 'lxc.network.type' => 1,
197 'lxc.network.flags' => 1,
198 'lxc.network.link' => 1,
199 'lxc.network.mtu' => 1,
200 'lxc.network.name' => 1,
201 'lxc.network.hwaddr' => 1,
202 'lxc.network.ipv4' => 1,
203 'lxc.network.ipv4.gateway' => 1,
204 'lxc.network.ipv6' => 1,
205 'lxc.network.ipv6.gateway' => 1,
206 'lxc.network.script.up' => 1,
207 'lxc.network.script.down' => 1,
209 'lxc.console.logfile' => 1,
212 'lxc.devttydir' => 1,
213 'lxc.hook.autodev' => 1,
217 'lxc.mount.entry' => 1,
218 'lxc.mount.auto' => 1,
220 'lxc.rootfs.mount' => 1,
221 'lxc.rootfs.options' => 1,
225 'lxc.aa_profile' => 1,
226 'lxc.aa_allow_incomplete' => 1,
227 'lxc.se_context' => 1,
230 'lxc.hook.pre-start' => 1,
231 'lxc.hook.pre-mount' => 1,
232 'lxc.hook.mount' => 1,
233 'lxc.hook.start' => 1,
234 'lxc.hook.stop' => 1,
235 'lxc.hook.post-stop' => 1,
236 'lxc.hook.clone' => 1,
237 'lxc.hook.destroy' => 1,
240 'lxc.start.auto' => 1,
241 'lxc.start.delay' => 1,
242 'lxc.start.order' => 1,
244 'lxc.environment' => 1,
255 description
=> "Network interface type.",
260 format_description
=> 'String',
261 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
262 pattern
=> '[-_.\w\d]+',
266 format_description
=> 'vmbr<Number>',
267 description
=> 'Bridge to attach the network device to.',
268 pattern
=> '[-_.\w\d]+',
273 format_description
=> 'MAC',
274 description
=> 'Bridge to attach the network device to. (lxc.network.hwaddr)',
275 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
280 format_description
=> 'Number',
281 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
282 minimum
=> 64, # minimum ethernet frame is 64 bytes
287 format
=> 'pve-ipv4-config',
288 format_description
=> 'IPv4Format/CIDR',
289 description
=> 'IPv4 address in CIDR format.',
295 format_description
=> 'GatewayIPv4',
296 description
=> 'Default gateway for IPv4 traffic.',
301 format
=> 'pve-ipv6-config',
302 format_description
=> 'IPv6Format/CIDR',
303 description
=> 'IPv6 address in CIDR format.',
309 format_description
=> 'GatewayIPv6',
310 description
=> 'Default gateway for IPv6 traffic.',
315 format_description
=> '[1|0]',
316 description
=> "Controls whether this interface's firewall rules should be used.",
321 format_description
=> 'VlanNo',
324 description
=> "VLAN tag foro this interface.",
328 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
330 my $MAX_LXC_NETWORKS = 10;
331 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
332 $confdesc->{"net$i"} = {
334 type
=> 'string', format
=> $netconf_desc,
335 description
=> "Specifies network interfaces for the container.",
343 format_description
=> 'Path',
344 description
=> 'Path to the mountpoint as seen from inside the container.',
348 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
352 type
=> 'string', format
=> 'pve-volume-id',
353 description
=> "Reference to unused volumes.",
356 my $MAX_MOUNT_POINTS = 10;
357 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
358 $confdesc->{"mp$i"} = {
360 type
=> 'string', format
=> $mp_desc,
361 description
=> "Use volume as container mount point (experimental feature).",
366 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
367 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
368 $confdesc->{"unused$i"} = $unuseddesc;
371 sub write_pct_config
{
372 my ($filename, $conf) = @_;
374 delete $conf->{snapstate
}; # just to be sure
376 my $generate_raw_config = sub {
381 # add description as comment to top of file
382 my $descr = $conf->{description
} || '';
383 foreach my $cl (split(/\n/, $descr)) {
384 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
387 foreach my $key (sort keys %$conf) {
388 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
389 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
390 my $value = $conf->{$key};
391 die "detected invalid newline inside property '$key'\n" if $value =~ m/\n/;
392 $raw .= "$key: $value\n";
395 if (my $lxcconf = $conf->{lxc
}) {
396 foreach my $entry (@$lxcconf) {
397 my ($k, $v) = @$entry;
405 my $raw = &$generate_raw_config($conf);
407 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
408 $raw .= "\n[$snapname]\n";
409 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
416 my ($key, $value) = @_;
418 die "unknown setting '$key'\n" if !$confdesc->{$key};
420 my $type = $confdesc->{$key}->{type
};
422 if (!defined($value)) {
423 die "got undefined value\n";
426 if ($value =~ m/[\n\r]/) {
427 die "property contains a line feed\n";
430 if ($type eq 'boolean') {
431 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
432 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
433 die "type check ('boolean') failed - got '$value'\n";
434 } elsif ($type eq 'integer') {
435 return int($1) if $value =~ m/^(\d+)$/;
436 die "type check ('integer') failed - got '$value'\n";
437 } elsif ($type eq 'number') {
438 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
439 die "type check ('number') failed - got '$value'\n";
440 } elsif ($type eq 'string') {
441 if (my $fmt = $confdesc->{$key}->{format
}) {
442 PVE
::JSONSchema
::check_format
($fmt, $value);
451 sub parse_pct_config
{
452 my ($filename, $raw) = @_;
454 return undef if !defined($raw);
457 digest
=> Digest
::SHA
::sha1_hex
($raw),
461 $filename =~ m
|/lxc/(\d
+).conf
$|
462 || die "got strange filename '$filename'";
470 my @lines = split(/\n/, $raw);
471 foreach my $line (@lines) {
472 next if $line =~ m/^\s*$/;
474 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
476 $conf->{description
} = $descr if $descr;
478 $conf = $res->{snapshots
}->{$section} = {};
482 if ($line =~ m/^\#(.*)\s*$/) {
483 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
487 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
490 if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
491 push @{$conf->{lxc
}}, [$key, $value];
493 warn "vm $vmid - unable to parse config: $line\n";
495 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
496 $descr .= PVE
::Tools
::decode_text
($2);
497 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
498 $conf->{snapstate
} = $1;
499 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
502 eval { $value = check_type
($key, $value); };
503 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
504 $conf->{$key} = $value;
506 warn "vm $vmid - unable to parse config: $line\n";
510 $conf->{description
} = $descr if $descr;
512 delete $res->{snapstate
}; # just to be sure
518 my $vmlist = PVE
::Cluster
::get_vmlist
();
520 return $res if !$vmlist || !$vmlist->{ids
};
521 my $ids = $vmlist->{ids
};
523 foreach my $vmid (keys %$ids) {
524 next if !$vmid; # skip CT0
525 my $d = $ids->{$vmid};
526 next if !$d->{node
} || $d->{node
} ne $nodename;
527 next if !$d->{type
} || $d->{type
} ne 'lxc';
528 $res->{$vmid}->{type
} = 'lxc';
533 sub cfs_config_path
{
534 my ($vmid, $node) = @_;
536 $node = $nodename if !$node;
537 return "nodes/$node/lxc/$vmid.conf";
541 my ($vmid, $node) = @_;
543 my $cfspath = cfs_config_path
($vmid, $node);
544 return "/etc/pve/$cfspath";
548 my ($vmid, $node) = @_;
550 $node = $nodename if !$node;
551 my $cfspath = cfs_config_path
($vmid, $node);
553 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
554 die "container $vmid does not exists\n" if !defined($conf);
560 my ($vmid, $conf) = @_;
562 my $dir = "/etc/pve/nodes/$nodename/lxc";
565 write_config
($vmid, $conf);
571 unlink config_file
($vmid, $nodename);
575 my ($vmid, $conf) = @_;
577 my $cfspath = cfs_config_path
($vmid);
579 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
582 # flock: we use one file handle per process, so lock file
583 # can be called multiple times and succeeds for the same process.
585 my $lock_handles = {};
586 my $lockdir = "/run/lock/lxc";
591 return "$lockdir/pve-config-${vmid}.lock";
595 my ($vmid, $timeout) = @_;
597 $timeout = 10 if !$timeout;
600 my $filename = lock_filename
($vmid);
602 mkdir $lockdir if !-d
$lockdir;
604 my $lock_func = sub {
605 if (!$lock_handles->{$$}->{$filename}) {
606 my $fh = new IO
::File
(">>$filename") ||
607 die "can't open file - $!\n";
608 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
611 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
612 print STDERR
"trying to aquire lock...";
615 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
616 # try again on EINTR (see bug #273)
617 if ($success || ($! != EINTR
)) {
622 print STDERR
" failed\n";
623 die "can't aquire lock - $!\n";
626 print STDERR
" OK\n";
629 $lock_handles->{$$}->{$filename}->{refcount
}++;
632 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
635 die "can't lock file '$filename' - $err";
642 my $filename = lock_filename
($vmid);
644 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
645 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
646 if ($refcount <= 0) {
647 $lock_handles->{$$}->{$filename} = undef;
654 my ($vmid, $timeout, $code, @param) = @_;
658 lock_aquire
($vmid, $timeout);
659 eval { $res = &$code(@param) };
671 return defined($confdesc->{$name});
674 # add JSON properties for create and set function
675 sub json_config_properties
{
678 foreach my $opt (keys %$confdesc) {
679 next if $opt eq 'parent' || $opt eq 'snaptime';
680 next if $prop->{$opt};
681 $prop->{$opt} = $confdesc->{$opt};
687 sub json_config_properties_no_rootfs
{
690 foreach my $opt (keys %$confdesc) {
691 next if $prop->{$opt};
692 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
693 $prop->{$opt} = $confdesc->{$opt};
699 # container status helpers
701 sub list_active_containers
{
703 my $filename = "/proc/net/unix";
705 # similar test is used by lcxcontainers.c: list_active_containers
708 my $fh = IO
::File-
>new ($filename, "r");
711 while (defined(my $line = <$fh>)) {
712 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
714 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
725 # warning: this is slow
729 my $active_hash = list_active_containers
();
731 return 1 if defined($active_hash->{$vmid});
736 sub get_container_disk_usage
{
739 my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/'];
749 if (my ($fsid, $total, $used, $avail) = $line =~
750 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
758 eval { PVE
::Tools
::run_command
($cmd, timeout
=> 1, outfunc
=> $parser); };
767 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
769 my $active_hash = list_active_containers
();
771 foreach my $vmid (keys %$list) {
772 my $d = $list->{$vmid};
774 my $running = defined($active_hash->{$vmid});
776 $d->{status
} = $running ?
'running' : 'stopped';
778 my $cfspath = cfs_config_path
($vmid);
779 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
781 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
782 $d->{name
} =~ s/[\s]//g;
784 $d->{cpus
} = $conf->{cpulimit
} // 0;
787 my $res = get_container_disk_usage
($vmid);
788 $d->{disk
} = $res->{used
};
789 $d->{maxdisk
} = $res->{total
};
792 # use 4GB by default ??
793 if (my $rootfs = $conf->{rootfs
}) {
794 my $rootinfo = parse_ct_mountpoint
($rootfs);
795 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
797 $d->{maxdisk
} = 4*1024*1024*1024;
803 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
804 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
815 $d->{template
} = is_template
($conf);
818 foreach my $vmid (keys %$list) {
819 my $d = $list->{$vmid};
820 next if $d->{status
} ne 'running';
822 my $pid = find_lxc_pid
($vmid);
823 my $ctime = (stat("/proc/$pid"))[10]; # 10 = ctime
824 $d->{uptime
} = time - $ctime; # the method lxcfs uses
826 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
827 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
829 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
830 my @bytes = split(/\n/, $blkio_bytes);
831 foreach my $byte (@bytes) {
832 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
833 $d->{diskread
} = $2 if $key eq 'Read';
834 $d->{diskwrite
} = $2 if $key eq 'Write';
839 my $netdev = PVE
::ProcFSTools
::read_proc_net_dev
();
841 foreach my $dev (keys %$netdev) {
842 next if $dev !~ m/^veth([1-9]\d*)i/;
844 my $d = $list->{$vmid};
848 $d->{netout
} += $netdev->{$dev}->{receive
};
849 $d->{netin
} += $netdev->{$dev}->{transmit
};
856 sub parse_ct_mountpoint
{
857 my ($data, $noerr) = @_;
862 eval { $res = PVE
::JSONSchema
::parse_property_string
($mp_desc, $data) };
864 return undef if $noerr;
868 if (defined(my $size = $res->{size
})) {
869 $size = PVE
::JSONSchema
::parse_size
($size);
870 if (!defined($size)) {
871 return undef if $noerr;
872 die "invalid size: $size\n";
874 $res->{size
} = $size;
880 sub print_ct_mountpoint
{
881 my ($info, $nomp) = @_;
882 my $skip = $nomp ?
['mp'] : [];
883 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
886 sub print_lxc_network
{
888 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
891 sub parse_lxc_network
{
896 return $res if !$data;
898 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
900 $res->{type
} = 'veth';
901 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
906 sub read_cgroup_value
{
907 my ($group, $vmid, $name, $full) = @_;
909 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
911 return PVE
::Tools
::file_get_contents
($path) if $full;
913 return PVE
::Tools
::file_read_firstline
($path);
916 sub write_cgroup_value
{
917 my ($group, $vmid, $name, $value) = @_;
919 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
920 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
924 sub find_lxc_console_pids
{
928 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
931 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
934 my @args = split(/\0/, $cmdline);
936 # serach for lxc-console -n <vmid>
937 return if scalar(@args) != 3;
938 return if $args[1] ne '-n';
939 return if $args[2] !~ m/^\d+$/;
940 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
944 push @{$res->{$vmid}}, $pid;
956 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
958 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid, '-p'], outfunc
=> $parser);
960 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
965 # Note: we cannot use Net:IP, because that only allows strict
967 sub parse_ipv4_cidr
{
968 my ($cidr, $noerr) = @_;
970 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 <= 32)) {
971 return { address
=> $1, netmask
=> $PVE::Network
::ipv4_reverse_mask-
>[$2] };
974 return undef if $noerr;
976 die "unable to parse ipv4 address/mask\n";
982 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
985 sub check_protection
{
986 my ($vm_conf, $err_msg) = @_;
988 if ($vm_conf->{protection
}) {
989 die "$err_msg - protection mode enabled\n";
993 sub update_lxc_config
{
994 my ($storage_cfg, $vmid, $conf) = @_;
996 my $dir = "/var/lib/lxc/$vmid";
998 if ($conf->{template
}) {
1000 unlink "$dir/config";
1007 die "missing 'arch' - internal error" if !$conf->{arch
};
1008 $raw .= "lxc.arch = $conf->{arch}\n";
1010 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
1011 if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
1012 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
1017 if (!has_dev_console
($conf)) {
1018 $raw .= "lxc.console = none\n";
1019 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1022 my $ttycount = get_tty_count
($conf);
1023 $raw .= "lxc.tty = $ttycount\n";
1025 # some init scripts expects a linux terminal (turnkey).
1026 $raw .= "lxc.environment = TERM=linux\n";
1028 my $utsname = $conf->{hostname
} || "CT$vmid";
1029 $raw .= "lxc.utsname = $utsname\n";
1031 my $memory = $conf->{memory
} || 512;
1032 my $swap = $conf->{swap
} // 0;
1034 my $lxcmem = int($memory*1024*1024);
1035 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1037 my $lxcswap = int(($memory + $swap)*1024*1024);
1038 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1040 if (my $cpulimit = $conf->{cpulimit
}) {
1041 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1042 my $value = int(100000*$cpulimit);
1043 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1046 my $shares = $conf->{cpuunits
} || 1024;
1047 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1049 my $mountpoint = parse_ct_mountpoint
($conf->{rootfs
});
1050 $mountpoint->{mp
} = '/';
1052 my ($path, $use_loopdev) = mountpoint_mount_path
($mountpoint, $storage_cfg);
1053 $path = "loop:$path" if $use_loopdev;
1055 $raw .= "lxc.rootfs = $path\n";
1058 foreach my $k (keys %$conf) {
1059 next if $k !~ m/^net(\d+)$/;
1061 my $d = parse_lxc_network
($conf->{$k});
1063 $raw .= "lxc.network.type = veth\n";
1064 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1065 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1066 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1067 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1070 if (my $lxcconf = $conf->{lxc
}) {
1071 foreach my $entry (@$lxcconf) {
1072 my ($k, $v) = @$entry;
1073 $netcount++ if $k eq 'lxc.network.type';
1074 $raw .= "$k = $v\n";
1078 $raw .= "lxc.network.type = empty\n" if !$netcount;
1080 File
::Path
::mkpath
("$dir/rootfs");
1082 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1085 # verify and cleanup nameserver list (replace \0 with ' ')
1086 sub verify_nameserver_list
{
1087 my ($nameserver_list) = @_;
1090 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1091 PVE
::JSONSchema
::pve_verify_ip
($server);
1092 push @list, $server;
1095 return join(' ', @list);
1098 sub verify_searchdomain_list
{
1099 my ($searchdomain_list) = @_;
1102 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1103 # todo: should we add checks for valid dns domains?
1104 push @list, $server;
1107 return join(' ', @list);
1110 sub add_unused_volume
{
1111 my ($config, $volid) = @_;
1113 # skip bind mounts and block devices
1114 return if $volid =~ m
|^/|;
1117 for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
1118 my $test = "unused$ind";
1119 if (my $vid = $config->{$test}) {
1120 return if $vid eq $volid; # do not add duplicates
1126 die "To many unused volume - please delete them first.\n" if !$key;
1128 $config->{$key} = $volid;
1133 sub update_pct_config
{
1134 my ($vmid, $conf, $running, $param, $delete) = @_;
1139 my @deleted_volumes;
1143 my $pid = find_lxc_pid
($vmid);
1144 $rootdir = "/proc/$pid/root";
1147 my $hotplug_error = sub {
1149 push @nohotplug, @_;
1156 if (defined($delete)) {
1157 foreach my $opt (@$delete) {
1158 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1159 die "unable to delete required option '$opt'\n";
1160 } elsif ($opt eq 'swap') {
1161 delete $conf->{$opt};
1162 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1163 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1164 delete $conf->{$opt};
1165 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1166 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1167 next if $hotplug_error->($opt);
1168 delete $conf->{$opt};
1169 } elsif ($opt =~ m/^net(\d)$/) {
1170 delete $conf->{$opt};
1173 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1174 } elsif ($opt eq 'protection') {
1175 delete $conf->{$opt};
1176 } elsif ($opt =~ m/^unused(\d+)$/) {
1177 next if $hotplug_error->($opt);
1178 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1179 push @deleted_volumes, $conf->{$opt};
1180 delete $conf->{$opt};
1181 } elsif ($opt =~ m/^mp(\d+)$/) {
1182 next if $hotplug_error->($opt);
1183 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1184 my $mountpoint = parse_ct_mountpoint
($conf->{$opt});
1185 add_unused_volume
($conf, $mountpoint->{volume
});
1186 delete $conf->{$opt};
1190 write_config
($vmid, $conf) if $running;
1194 # There's no separate swap size to configure, there's memory and "total"
1195 # memory (iow. memory+swap). This means we have to change them together.
1196 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1197 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1198 if (defined($wanted_memory) || defined($wanted_swap)) {
1200 $wanted_memory //= ($conf->{memory
} || 512);
1201 $wanted_swap //= ($conf->{swap
} || 0);
1203 my $total = $wanted_memory + $wanted_swap;
1205 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1206 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1208 $conf->{memory
} = $wanted_memory;
1209 $conf->{swap
} = $wanted_swap;
1211 write_config
($vmid, $conf) if $running;
1214 foreach my $opt (keys %$param) {
1215 my $value = $param->{$opt};
1216 if ($opt eq 'hostname') {
1217 $conf->{$opt} = $value;
1218 } elsif ($opt eq 'onboot') {
1219 $conf->{$opt} = $value ?
1 : 0;
1220 } elsif ($opt eq 'startup') {
1221 $conf->{$opt} = $value;
1222 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1223 next if $hotplug_error->($opt);
1224 $conf->{$opt} = $value;
1225 } elsif ($opt eq 'nameserver') {
1226 next if $hotplug_error->($opt);
1227 my $list = verify_nameserver_list
($value);
1228 $conf->{$opt} = $list;
1229 } elsif ($opt eq 'searchdomain') {
1230 next if $hotplug_error->($opt);
1231 my $list = verify_searchdomain_list
($value);
1232 $conf->{$opt} = $list;
1233 } elsif ($opt eq 'cpulimit') {
1234 next if $hotplug_error->($opt); # FIXME: hotplug
1235 $conf->{$opt} = $value;
1236 } elsif ($opt eq 'cpuunits') {
1237 $conf->{$opt} = $value;
1238 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1239 } elsif ($opt eq 'description') {
1240 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1241 } elsif ($opt =~ m/^net(\d+)$/) {
1243 my $net = parse_lxc_network
($value);
1245 $conf->{$opt} = print_lxc_network
($net);
1247 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1249 } elsif ($opt eq 'protection') {
1250 $conf->{$opt} = $value ?
1 : 0;
1251 } elsif ($opt =~ m/^mp(\d+)$/) {
1252 next if $hotplug_error->($opt);
1253 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1254 $conf->{$opt} = $value;
1256 } elsif ($opt eq 'rootfs') {
1257 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1258 die "implement me: $opt";
1260 die "implement me: $opt";
1262 write_config
($vmid, $conf) if $running;
1265 if (@deleted_volumes) {
1266 my $storage_cfg = PVE
::Storage
::config
();
1267 foreach my $volume (@deleted_volumes) {
1268 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1273 my $storage_cfg = PVE
::Storage
::config
();
1274 create_disks
($storage_cfg, $vmid, $conf, $conf);
1277 # This should be the last thing we do here
1278 if ($running && scalar(@nohotplug)) {
1279 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1283 sub has_dev_console
{
1286 return !(defined($conf->{console
}) && !$conf->{console
});
1292 return $conf->{tty
} // $confdesc->{tty
}->{default};
1298 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1301 sub get_console_command
{
1302 my ($vmid, $conf) = @_;
1304 my $cmode = get_cmode
($conf);
1306 if ($cmode eq 'console') {
1307 return ['lxc-console', '-n', $vmid, '-t', 0];
1308 } elsif ($cmode eq 'tty') {
1309 return ['lxc-console', '-n', $vmid];
1310 } elsif ($cmode eq 'shell') {
1311 return ['lxc-attach', '--clear-env', '-n', $vmid];
1313 die "internal error";
1317 sub get_primary_ips
{
1320 # return data from net0
1322 return undef if !defined($conf->{net0
});
1323 my $net = parse_lxc_network
($conf->{net0
});
1325 my $ipv4 = $net->{ip
};
1327 if ($ipv4 =~ /^(dhcp|manual)$/) {
1333 my $ipv6 = $net->{ip6
};
1335 if ($ipv6 =~ /^(auto|dhcp|manual)$/) {
1342 return ($ipv4, $ipv6);
1345 sub delete_mountpoint_volume
{
1346 my ($storage_cfg, $vmid, $volume) = @_;
1348 # skip bind mounts and block devices
1349 if ($volume =~ m
|^/|) {
1353 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1354 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1357 sub destroy_lxc_container
{
1358 my ($storage_cfg, $vmid, $conf) = @_;
1360 foreach_mountpoint
($conf, sub {
1361 my ($ms, $mountpoint) = @_;
1362 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1365 rmdir "/var/lib/lxc/$vmid/rootfs";
1366 unlink "/var/lib/lxc/$vmid/config";
1367 rmdir "/var/lib/lxc/$vmid";
1368 destroy_config
($vmid);
1370 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1371 #PVE::Tools::run_command($cmd);
1374 sub vm_stop_cleanup
{
1375 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1380 my $vollist = get_vm_volumes
($conf);
1381 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1384 warn $@ if $@; # avoid errors - just warn
1387 my $safe_num_ne = sub {
1390 return 0 if !defined($a) && !defined($b);
1391 return 1 if !defined($a);
1392 return 1 if !defined($b);
1397 my $safe_string_ne = sub {
1400 return 0 if !defined($a) && !defined($b);
1401 return 1 if !defined($a);
1402 return 1 if !defined($b);
1408 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1410 if ($newnet->{type
} ne 'veth') {
1411 # for when there are physical interfaces
1412 die "cannot update interface of type $newnet->{type}";
1415 my $veth = "veth${vmid}i${netid}";
1416 my $eth = $newnet->{name
};
1418 if (my $oldnetcfg = $conf->{$opt}) {
1419 my $oldnet = parse_lxc_network
($oldnetcfg);
1421 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1422 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1424 PVE
::Network
::veth_delete
($veth);
1425 delete $conf->{$opt};
1426 write_config
($vmid, $conf);
1428 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1430 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1431 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1432 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1434 if ($oldnet->{bridge
}) {
1435 PVE
::Network
::tap_unplug
($veth);
1436 foreach (qw(bridge tag firewall)) {
1437 delete $oldnet->{$_};
1439 $conf->{$opt} = print_lxc_network
($oldnet);
1440 write_config
($vmid, $conf);
1443 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1444 foreach (qw(bridge tag firewall)) {
1445 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1447 $conf->{$opt} = print_lxc_network
($oldnet);
1448 write_config
($vmid, $conf);
1451 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1454 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1458 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1460 my $veth = "veth${vmid}i${netid}";
1461 my $vethpeer = $veth . "p";
1462 my $eth = $newnet->{name
};
1464 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1465 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1467 # attach peer in container
1468 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1469 PVE
::Tools
::run_command
($cmd);
1471 # link up peer in container
1472 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1473 PVE
::Tools
::run_command
($cmd);
1475 my $done = { type
=> 'veth' };
1476 foreach (qw(bridge tag firewall hwaddr name)) {
1477 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1479 $conf->{$opt} = print_lxc_network
($done);
1481 write_config
($vmid, $conf);
1484 sub update_ipconfig
{
1485 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1487 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1489 my $optdata = parse_lxc_network
($conf->{$opt});
1493 my $cmdargs = shift;
1494 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1496 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1498 my $change_ip_config = sub {
1499 my ($ipversion) = @_;
1501 my $family_opt = "-$ipversion";
1502 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1503 my $gw= "gw$suffix";
1504 my $ip= "ip$suffix";
1506 my $newip = $newnet->{$ip};
1507 my $newgw = $newnet->{$gw};
1508 my $oldip = $optdata->{$ip};
1510 my $change_ip = &$safe_string_ne($oldip, $newip);
1511 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1513 return if !$change_ip && !$change_gw;
1515 # step 1: add new IP, if this fails we cancel
1516 my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/);
1517 if ($change_ip && $is_real_ip) {
1518 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1525 # step 2: replace gateway
1526 # If this fails we delete the added IP and cancel.
1527 # If it succeeds we save the config and delete the old IP, ignoring
1528 # errors. The config is then saved.
1529 # Note: 'ip route replace' can add
1533 if ($is_real_ip && !PVE
::Network
::is_ip_in_cidr
($newgw, $newip, $ipversion)) {
1534 &$ipcmd($family_opt, 'route', 'add', $newgw, 'dev', $eth);
1536 &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw);
1540 # the route was not replaced, the old IP is still available
1541 # rollback (delete new IP) and cancel
1543 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1544 warn $@ if $@; # no need to die here
1549 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1550 # if the route was not deleted, the guest might have deleted it manually
1556 # from this point on we save the configuration
1557 # step 3: delete old IP ignoring errors
1558 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1559 # We need to enable promote_secondaries, otherwise our newly added
1560 # address will be removed along with the old one.
1563 if ($ipversion == 4) {
1564 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1565 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1566 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1568 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1570 warn $@ if $@; # no need to die here
1572 if ($ipversion == 4) {
1573 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1577 foreach my $property ($ip, $gw) {
1578 if ($newnet->{$property}) {
1579 $optdata->{$property} = $newnet->{$property};
1581 delete $optdata->{$property};
1584 $conf->{$opt} = print_lxc_network
($optdata);
1585 write_config
($vmid, $conf);
1586 $lxc_setup->setup_network($conf);
1589 &$change_ip_config(4);
1590 &$change_ip_config(6);
1594 # Internal snapshots
1596 # NOTE: Snapshot create/delete involves several non-atomic
1597 # action, and can take a long time.
1598 # So we try to avoid locking the file and use 'lock' variable
1599 # inside the config file instead.
1601 my $snapshot_copy_config = sub {
1602 my ($source, $dest) = @_;
1604 foreach my $k (keys %$source) {
1605 next if $k eq 'snapshots';
1606 next if $k eq 'snapstate';
1607 next if $k eq 'snaptime';
1608 next if $k eq 'vmstate';
1609 next if $k eq 'lock';
1610 next if $k eq 'digest';
1611 next if $k eq 'description';
1613 $dest->{$k} = $source->{$k};
1617 my $snapshot_prepare = sub {
1618 my ($vmid, $snapname, $comment) = @_;
1622 my $updatefn = sub {
1624 my $conf = load_config
($vmid);
1626 die "you can't take a snapshot if it's a template\n"
1627 if is_template
($conf);
1631 $conf->{lock} = 'snapshot';
1633 die "snapshot name '$snapname' already used\n"
1634 if defined($conf->{snapshots
}->{$snapname});
1636 my $storecfg = PVE
::Storage
::config
();
1637 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1639 $snap = $conf->{snapshots
}->{$snapname} = {};
1641 &$snapshot_copy_config($conf, $snap);
1643 $snap->{'snapstate'} = "prepare";
1644 $snap->{'snaptime'} = time();
1645 $snap->{'description'} = $comment if $comment;
1646 $conf->{snapshots
}->{$snapname} = $snap;
1648 write_config
($vmid, $conf);
1651 lock_container
($vmid, 10, $updatefn);
1656 my $snapshot_commit = sub {
1657 my ($vmid, $snapname) = @_;
1659 my $updatefn = sub {
1661 my $conf = load_config
($vmid);
1663 die "missing snapshot lock\n"
1664 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1666 die "snapshot '$snapname' does not exist\n"
1667 if !defined($conf->{snapshots
}->{$snapname});
1669 die "wrong snapshot state\n"
1670 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1671 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1673 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1674 delete $conf->{lock};
1675 $conf->{parent
} = $snapname;
1677 write_config
($vmid, $conf);
1680 lock_container
($vmid, 10 ,$updatefn);
1684 my ($feature, $conf, $storecfg, $snapname) = @_;
1688 foreach_mountpoint
($conf, sub {
1689 my ($ms, $mountpoint) = @_;
1691 return if $err; # skip further test
1693 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1695 # TODO: implement support for mountpoints
1696 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1700 return $err ?
0 : 1;
1703 sub snapshot_create
{
1704 my ($vmid, $snapname, $comment) = @_;
1706 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1708 my $conf = load_config
($vmid);
1710 my $running = check_running
($vmid);
1713 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1714 PVE
::Tools
::run_command
(['/bin/sync']);
1717 my $storecfg = PVE
::Storage
::config
();
1718 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1719 my $volid = $rootinfo->{volume
};
1722 PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]);
1725 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1726 &$snapshot_commit($vmid, $snapname);
1729 snapshot_delete
($vmid, $snapname, 1);
1734 sub snapshot_delete
{
1735 my ($vmid, $snapname, $force) = @_;
1741 my $updatefn = sub {
1743 $conf = load_config
($vmid);
1745 die "you can't delete a snapshot if vm is a template\n"
1746 if is_template
($conf);
1748 $snap = $conf->{snapshots
}->{$snapname};
1752 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1754 $snap->{snapstate
} = 'delete';
1756 write_config
($vmid, $conf);
1759 lock_container
($vmid, 10, $updatefn);
1761 my $storecfg = PVE
::Storage
::config
();
1763 my $del_snap = sub {
1767 if ($conf->{parent
} eq $snapname) {
1768 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1769 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1771 delete $conf->{parent
};
1775 delete $conf->{snapshots
}->{$snapname};
1777 write_config
($vmid, $conf);
1780 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1781 my $rootinfo = parse_ct_mountpoint
($rootfs);
1782 my $volid = $rootinfo->{volume
};
1785 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1789 if(!$err || ($err && $force)) {
1790 lock_container
($vmid, 10, $del_snap);
1792 die "Can't delete snapshot: $vmid $snapname $err\n";
1797 sub snapshot_rollback
{
1798 my ($vmid, $snapname) = @_;
1800 my $storecfg = PVE
::Storage
::config
();
1802 my $conf = load_config
($vmid);
1804 die "you can't rollback if vm is a template\n" if is_template
($conf);
1806 my $snap = $conf->{snapshots
}->{$snapname};
1808 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1810 my $rootfs = $snap->{rootfs
};
1811 my $rootinfo = parse_ct_mountpoint
($rootfs);
1812 my $volid = $rootinfo->{volume
};
1814 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1816 my $updatefn = sub {
1818 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1819 if $snap->{snapstate
};
1823 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1825 die "unable to rollback vm $vmid: vm is running\n"
1826 if check_running
($vmid);
1828 $conf->{lock} = 'rollback';
1832 # copy snapshot config to current config
1834 my $tmp_conf = $conf;
1835 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1836 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1837 delete $conf->{snaptime
};
1838 delete $conf->{snapname
};
1839 $conf->{parent
} = $snapname;
1841 write_config
($vmid, $conf);
1844 my $unlockfn = sub {
1845 delete $conf->{lock};
1846 write_config
($vmid, $conf);
1849 lock_container
($vmid, 10, $updatefn);
1851 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1853 lock_container
($vmid, 5, $unlockfn);
1856 sub template_create
{
1857 my ($vmid, $conf) = @_;
1859 my $storecfg = PVE
::Storage
::config
();
1861 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1862 my $volid = $rootinfo->{volume
};
1864 die "Template feature is not available for '$volid'\n"
1865 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1867 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1869 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1870 $rootinfo->{volume
} = $template_volid;
1871 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1873 write_config
($vmid, $conf);
1879 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1882 sub mountpoint_names
{
1885 my @names = ('rootfs');
1887 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1888 push @names, "mp$i";
1891 return $reverse ?
reverse @names : @names;
1894 # The container might have *different* symlinks than the host. realpath/abs_path
1895 # use the actual filesystem to resolve links.
1896 sub sanitize_mountpoint
{
1898 $mp = '/' . $mp; # we always start with a slash
1899 $mp =~ s
@/{2,}@/@g; # collapse sequences of slashes
1900 $mp =~ s
@/\./@@g; # collapse /./
1901 $mp =~ s
@/\.(/)?
$@$1@; # collapse a trailing /. or /./
1902 $mp =~ s
@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
1903 $mp =~ s
@/\.\
.(/)?
$@$1@; # collapse trailing /.. or /../ disregarding symlinks
1907 sub foreach_mountpoint_full
{
1908 my ($conf, $reverse, $func) = @_;
1910 foreach my $key (mountpoint_names
($reverse)) {
1911 my $value = $conf->{$key};
1912 next if !defined($value);
1913 my $mountpoint = parse_ct_mountpoint
($value, 1);
1914 next if !defined($mountpoint);
1916 # just to be sure: rootfs is /
1917 my $path = $key eq 'rootfs' ?
'/' : $mountpoint->{mp
};
1918 $mountpoint->{mp
} = sanitize_mountpoint
($path);
1920 $path = $mountpoint->{volume
};
1921 $mountpoint->{volume
} = sanitize_mountpoint
($path) if $path =~ m
|^/|;
1923 &$func($key, $mountpoint);
1927 sub foreach_mountpoint
{
1928 my ($conf, $func) = @_;
1930 foreach_mountpoint_full
($conf, 0, $func);
1933 sub foreach_mountpoint_reverse
{
1934 my ($conf, $func) = @_;
1936 foreach_mountpoint_full
($conf, 1, $func);
1939 sub check_ct_modify_config_perm
{
1940 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1942 return 1 if $authuser ne 'root@pam';
1944 foreach my $opt (@$key_list) {
1946 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1947 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1948 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
1949 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1950 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1951 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1952 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1953 $opt eq 'searchdomain' || $opt eq 'hostname') {
1954 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1956 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
1964 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
1966 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1967 my $volid_list = get_vm_volumes
($conf);
1969 foreach_mountpoint_reverse
($conf, sub {
1970 my ($ms, $mountpoint) = @_;
1972 my $volid = $mountpoint->{volume
};
1973 my $mount = $mountpoint->{mp
};
1975 return if !$volid || !$mount;
1977 my $mount_path = "$rootdir/$mount";
1978 $mount_path =~ s!/+!/!g;
1980 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
1983 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
1996 my ($vmid, $storage_cfg, $conf) = @_;
1998 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1999 File
::Path
::make_path
($rootdir);
2001 my $volid_list = get_vm_volumes
($conf);
2002 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2005 foreach_mountpoint
($conf, sub {
2006 my ($ms, $mountpoint) = @_;
2008 my $volid = $mountpoint->{volume
};
2009 my $mount = $mountpoint->{mp
};
2011 return if !$volid || !$mount;
2013 my $image_path = PVE
::Storage
::path
($storage_cfg, $volid);
2014 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2015 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2017 die "unable to mount base volume - internal error" if $isBase;
2019 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2023 warn "mounting container failed - $err";
2024 umount_all
($vmid, $storage_cfg, $conf, 1);
2031 sub mountpoint_mount_path
{
2032 my ($mountpoint, $storage_cfg, $snapname) = @_;
2034 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2037 my $check_mount_path = sub {
2039 $path = File
::Spec-
>canonpath($path);
2040 my $real = Cwd
::realpath
($path);
2041 if ($real ne $path) {
2042 die "mount path modified by symlink: $path != $real";
2046 # use $rootdir = undef to just return the corresponding mount path
2047 sub mountpoint_mount
{
2048 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2050 my $volid = $mountpoint->{volume
};
2051 my $mount = $mountpoint->{mp
};
2053 return if !$volid || !$mount;
2057 if (defined($rootdir)) {
2058 $rootdir =~ s!/+$!!;
2059 $mount_path = "$rootdir/$mount";
2060 $mount_path =~ s!/+!/!g;
2061 &$check_mount_path($mount_path);
2062 File
::Path
::mkpath
($mount_path);
2065 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2067 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2071 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2072 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2074 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2075 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2077 $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
2079 if ($format eq 'subvol') {
2082 if ($scfg->{type
} eq 'zfspool') {
2083 my $path_arg = $path;
2084 $path_arg =~ s!^/+!!;
2085 PVE
::Tools
::run_command
(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
2087 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2090 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
2093 return wantarray ?
($path, 0) : $path;
2094 } elsif ($format eq 'raw' || $format eq 'iso') {
2095 my $use_loopdev = 0;
2097 if ($scfg->{path
}) {
2098 push @extra_opts, '-o', 'loop';
2100 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'rbd') {
2103 die "unsupported storage type '$scfg->{type}'\n";
2106 if ($format eq 'iso') {
2107 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
2108 } elsif ($isBase || defined($snapname)) {
2109 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2111 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2114 return wantarray ?
($path, $use_loopdev) : $path;
2116 die "unsupported image format '$format'\n";
2118 } elsif ($volid =~ m
|^/dev/.+|) {
2119 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
2120 return wantarray ?
($volid, 0) : $volid;
2121 } elsif ($volid !~ m
|^/dev/.+| && $volid =~ m
|^/.+| && -d
$volid) {
2122 &$check_mount_path($volid);
2123 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2124 return wantarray ?
($volid, 0) : $volid;
2127 die "unsupported storage";
2130 sub get_vm_volumes
{
2131 my ($conf, $excludes) = @_;
2135 foreach_mountpoint
($conf, sub {
2136 my ($ms, $mountpoint) = @_;
2138 return if $excludes && $ms eq $excludes;
2140 my $volid = $mountpoint->{volume
};
2142 return if !$volid || $volid =~ m
|^/|;
2144 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2147 push @$vollist, $volid;
2156 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp', $dev]);
2160 my ($storage_cfg, $volid) = @_;
2162 if ($volid =~ m!^/dev/.+!) {
2167 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2169 die "cannot format volume '$volid' with no storage\n" if !$storage;
2171 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2173 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2175 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2176 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2178 die "cannot format volume '$volid' (format == $format)\n"
2179 if $format ne 'raw';
2185 my ($storecfg, $vollist) = @_;
2187 foreach my $volid (@$vollist) {
2188 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2194 my ($storecfg, $vmid, $settings, $conf) = @_;
2199 foreach_mountpoint
($settings, sub {
2200 my ($ms, $mountpoint) = @_;
2202 my $volid = $mountpoint->{volume
};
2203 my $mp = $mountpoint->{mp
};
2205 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2207 return if !$storage;
2209 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
2210 my ($storeid, $size_gb) = ($1, $2);
2212 my $size_kb = int(${size_gb
}*1024) * 1024;
2214 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2215 # fixme: use better naming ct-$vmid-disk-X.raw?
2217 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2219 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2221 format_disk
($storecfg, $volid);
2223 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2226 } elsif ($scfg->{type
} eq 'zfspool') {
2228 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2230 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm') {
2232 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2233 format_disk
($storecfg, $volid);
2235 } elsif ($scfg->{type
} eq 'rbd') {
2237 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2238 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2239 format_disk
($storecfg, $volid);
2241 die "unable to create containers on storage type '$scfg->{type}'\n";
2243 push @$vollist, $volid;
2244 my $new_mountpoint = { volume
=> $volid, size
=> $size_kb*1024, mp
=> $mp };
2245 $conf->{$ms} = print_ct_mountpoint
($new_mountpoint, $ms eq 'rootfs');
2247 # use specified/existing volid
2251 # free allocated images on error
2253 destroy_disks
($storecfg, $vollist);
2259 # bash completion helper
2261 sub complete_os_templates
{
2262 my ($cmdname, $pname, $cvalue) = @_;
2264 my $cfg = PVE
::Storage
::config
();
2268 if ($cvalue =~ m/^([^:]+):/) {
2272 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2273 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2276 foreach my $id (keys %$data) {
2277 foreach my $item (@{$data->{$id}}) {
2278 push @$res, $item->{volid
} if defined($item->{volid
});
2285 my $complete_ctid_full = sub {
2288 my $idlist = vmstatus
();
2290 my $active_hash = list_active_containers
();
2294 foreach my $id (keys %$idlist) {
2295 my $d = $idlist->{$id};
2296 if (defined($running)) {
2297 next if $d->{template
};
2298 next if $running && !$active_hash->{$id};
2299 next if !$running && $active_hash->{$id};
2308 return &$complete_ctid_full();
2311 sub complete_ctid_stopped
{
2312 return &$complete_ctid_full(0);
2315 sub complete_ctid_running
{
2316 return &$complete_ctid_full(1);