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';
842 sub parse_ct_mountpoint
{
843 my ($data, $noerr) = @_;
848 eval { $res = PVE
::JSONSchema
::parse_property_string
($mp_desc, $data) };
850 return undef if $noerr;
854 if (defined(my $size = $res->{size
})) {
855 $size = PVE
::JSONSchema
::parse_size
($size);
856 if (!defined($size)) {
857 return undef if $noerr;
858 die "invalid size: $size\n";
860 $res->{size
} = $size;
866 sub print_ct_mountpoint
{
867 my ($info, $nomp) = @_;
868 my $skip = $nomp ?
['mp'] : [];
869 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
872 sub print_lxc_network
{
874 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
877 sub parse_lxc_network
{
882 return $res if !$data;
884 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
886 $res->{type
} = 'veth';
887 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
892 sub read_cgroup_value
{
893 my ($group, $vmid, $name, $full) = @_;
895 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
897 return PVE
::Tools
::file_get_contents
($path) if $full;
899 return PVE
::Tools
::file_read_firstline
($path);
902 sub write_cgroup_value
{
903 my ($group, $vmid, $name, $value) = @_;
905 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
906 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
910 sub find_lxc_console_pids
{
914 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
917 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
920 my @args = split(/\0/, $cmdline);
922 # serach for lxc-console -n <vmid>
923 return if scalar(@args) != 3;
924 return if $args[1] ne '-n';
925 return if $args[2] !~ m/^\d+$/;
926 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
930 push @{$res->{$vmid}}, $pid;
942 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
944 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid, '-p'], outfunc
=> $parser);
946 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
951 # Note: we cannot use Net:IP, because that only allows strict
953 sub parse_ipv4_cidr
{
954 my ($cidr, $noerr) = @_;
956 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 <= 32)) {
957 return { address
=> $1, netmask
=> $PVE::Network
::ipv4_reverse_mask-
>[$2] };
960 return undef if $noerr;
962 die "unable to parse ipv4 address/mask\n";
968 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
971 sub check_protection
{
972 my ($vm_conf, $err_msg) = @_;
974 if ($vm_conf->{protection
}) {
975 die "$err_msg - protection mode enabled\n";
979 sub update_lxc_config
{
980 my ($storage_cfg, $vmid, $conf) = @_;
982 my $dir = "/var/lib/lxc/$vmid";
984 if ($conf->{template
}) {
986 unlink "$dir/config";
993 die "missing 'arch' - internal error" if !$conf->{arch
};
994 $raw .= "lxc.arch = $conf->{arch}\n";
996 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
997 if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
998 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
1003 if (!has_dev_console
($conf)) {
1004 $raw .= "lxc.console = none\n";
1005 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1008 my $ttycount = get_tty_count
($conf);
1009 $raw .= "lxc.tty = $ttycount\n";
1011 # some init scripts expects a linux terminal (turnkey).
1012 $raw .= "lxc.environment = TERM=linux\n";
1014 my $utsname = $conf->{hostname
} || "CT$vmid";
1015 $raw .= "lxc.utsname = $utsname\n";
1017 my $memory = $conf->{memory
} || 512;
1018 my $swap = $conf->{swap
} // 0;
1020 my $lxcmem = int($memory*1024*1024);
1021 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1023 my $lxcswap = int(($memory + $swap)*1024*1024);
1024 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1026 if (my $cpulimit = $conf->{cpulimit
}) {
1027 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1028 my $value = int(100000*$cpulimit);
1029 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1032 my $shares = $conf->{cpuunits
} || 1024;
1033 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1035 my $mountpoint = parse_ct_mountpoint
($conf->{rootfs
});
1036 $mountpoint->{mp
} = '/';
1038 my ($path, $use_loopdev) = mountpoint_mount_path
($mountpoint, $storage_cfg);
1039 $path = "loop:$path" if $use_loopdev;
1041 $raw .= "lxc.rootfs = $path\n";
1044 foreach my $k (keys %$conf) {
1045 next if $k !~ m/^net(\d+)$/;
1047 my $d = parse_lxc_network
($conf->{$k});
1049 $raw .= "lxc.network.type = veth\n";
1050 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1051 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1052 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1053 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1056 if (my $lxcconf = $conf->{lxc
}) {
1057 foreach my $entry (@$lxcconf) {
1058 my ($k, $v) = @$entry;
1059 $netcount++ if $k eq 'lxc.network.type';
1060 $raw .= "$k = $v\n";
1064 $raw .= "lxc.network.type = empty\n" if !$netcount;
1066 File
::Path
::mkpath
("$dir/rootfs");
1068 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1071 # verify and cleanup nameserver list (replace \0 with ' ')
1072 sub verify_nameserver_list
{
1073 my ($nameserver_list) = @_;
1076 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1077 PVE
::JSONSchema
::pve_verify_ip
($server);
1078 push @list, $server;
1081 return join(' ', @list);
1084 sub verify_searchdomain_list
{
1085 my ($searchdomain_list) = @_;
1088 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1089 # todo: should we add checks for valid dns domains?
1090 push @list, $server;
1093 return join(' ', @list);
1096 sub add_unused_volume
{
1097 my ($config, $volid) = @_;
1099 # skip bind mounts and block devices
1100 return if $volid =~ m
|^/|;
1103 for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
1104 my $test = "unused$ind";
1105 if (my $vid = $config->{$test}) {
1106 return if $vid eq $volid; # do not add duplicates
1112 die "To many unused volume - please delete them first.\n" if !$key;
1114 $config->{$key} = $volid;
1119 sub update_pct_config
{
1120 my ($vmid, $conf, $running, $param, $delete) = @_;
1125 my @deleted_volumes;
1129 my $pid = find_lxc_pid
($vmid);
1130 $rootdir = "/proc/$pid/root";
1133 my $hotplug_error = sub {
1135 push @nohotplug, @_;
1142 if (defined($delete)) {
1143 foreach my $opt (@$delete) {
1144 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1145 die "unable to delete required option '$opt'\n";
1146 } elsif ($opt eq 'swap') {
1147 delete $conf->{$opt};
1148 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1149 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1150 delete $conf->{$opt};
1151 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1152 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1153 next if $hotplug_error->($opt);
1154 delete $conf->{$opt};
1155 } elsif ($opt =~ m/^net(\d)$/) {
1156 delete $conf->{$opt};
1159 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1160 } elsif ($opt eq 'protection') {
1161 delete $conf->{$opt};
1162 } elsif ($opt =~ m/^unused(\d+)$/) {
1163 next if $hotplug_error->($opt);
1164 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1165 push @deleted_volumes, $conf->{$opt};
1166 delete $conf->{$opt};
1167 } elsif ($opt =~ m/^mp(\d+)$/) {
1168 next if $hotplug_error->($opt);
1169 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1170 my $mountpoint = parse_ct_mountpoint
($conf->{$opt});
1171 add_unused_volume
($conf, $mountpoint->{volume
});
1172 delete $conf->{$opt};
1176 write_config
($vmid, $conf) if $running;
1180 # There's no separate swap size to configure, there's memory and "total"
1181 # memory (iow. memory+swap). This means we have to change them together.
1182 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1183 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1184 if (defined($wanted_memory) || defined($wanted_swap)) {
1186 $wanted_memory //= ($conf->{memory
} || 512);
1187 $wanted_swap //= ($conf->{swap
} || 0);
1189 my $total = $wanted_memory + $wanted_swap;
1191 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1192 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1194 $conf->{memory
} = $wanted_memory;
1195 $conf->{swap
} = $wanted_swap;
1197 write_config
($vmid, $conf) if $running;
1200 foreach my $opt (keys %$param) {
1201 my $value = $param->{$opt};
1202 if ($opt eq 'hostname') {
1203 $conf->{$opt} = $value;
1204 } elsif ($opt eq 'onboot') {
1205 $conf->{$opt} = $value ?
1 : 0;
1206 } elsif ($opt eq 'startup') {
1207 $conf->{$opt} = $value;
1208 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1209 next if $hotplug_error->($opt);
1210 $conf->{$opt} = $value;
1211 } elsif ($opt eq 'nameserver') {
1212 next if $hotplug_error->($opt);
1213 my $list = verify_nameserver_list
($value);
1214 $conf->{$opt} = $list;
1215 } elsif ($opt eq 'searchdomain') {
1216 next if $hotplug_error->($opt);
1217 my $list = verify_searchdomain_list
($value);
1218 $conf->{$opt} = $list;
1219 } elsif ($opt eq 'cpulimit') {
1220 next if $hotplug_error->($opt); # FIXME: hotplug
1221 $conf->{$opt} = $value;
1222 } elsif ($opt eq 'cpuunits') {
1223 $conf->{$opt} = $value;
1224 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1225 } elsif ($opt eq 'description') {
1226 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1227 } elsif ($opt =~ m/^net(\d+)$/) {
1229 my $net = parse_lxc_network
($value);
1231 $conf->{$opt} = print_lxc_network
($net);
1233 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1235 } elsif ($opt eq 'protection') {
1236 $conf->{$opt} = $value ?
1 : 0;
1237 } elsif ($opt =~ m/^mp(\d+)$/) {
1238 next if $hotplug_error->($opt);
1239 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1240 $conf->{$opt} = $value;
1242 } elsif ($opt eq 'rootfs') {
1243 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1244 die "implement me: $opt";
1246 die "implement me: $opt";
1248 write_config
($vmid, $conf) if $running;
1251 if (@deleted_volumes) {
1252 my $storage_cfg = PVE
::Storage
::config
();
1253 foreach my $volume (@deleted_volumes) {
1254 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1259 my $storage_cfg = PVE
::Storage
::config
();
1260 create_disks
($storage_cfg, $vmid, $conf, $conf);
1263 # This should be the last thing we do here
1264 if ($running && scalar(@nohotplug)) {
1265 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1269 sub has_dev_console
{
1272 return !(defined($conf->{console
}) && !$conf->{console
});
1278 return $conf->{tty
} // $confdesc->{tty
}->{default};
1284 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1287 sub get_console_command
{
1288 my ($vmid, $conf) = @_;
1290 my $cmode = get_cmode
($conf);
1292 if ($cmode eq 'console') {
1293 return ['lxc-console', '-n', $vmid, '-t', 0];
1294 } elsif ($cmode eq 'tty') {
1295 return ['lxc-console', '-n', $vmid];
1296 } elsif ($cmode eq 'shell') {
1297 return ['lxc-attach', '--clear-env', '-n', $vmid];
1299 die "internal error";
1303 sub get_primary_ips
{
1306 # return data from net0
1308 return undef if !defined($conf->{net0
});
1309 my $net = parse_lxc_network
($conf->{net0
});
1311 my $ipv4 = $net->{ip
};
1313 if ($ipv4 =~ /^(dhcp|manual)$/) {
1319 my $ipv6 = $net->{ip6
};
1321 if ($ipv6 =~ /^(dhcp|manual)$/) {
1328 return ($ipv4, $ipv6);
1331 sub delete_mountpoint_volume
{
1332 my ($storage_cfg, $vmid, $volume) = @_;
1334 # skip bind mounts and block devices
1335 if ($volume =~ m
|^/|) {
1339 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1340 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1343 sub destroy_lxc_container
{
1344 my ($storage_cfg, $vmid, $conf) = @_;
1346 foreach_mountpoint
($conf, sub {
1347 my ($ms, $mountpoint) = @_;
1348 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1351 rmdir "/var/lib/lxc/$vmid/rootfs";
1352 unlink "/var/lib/lxc/$vmid/config";
1353 rmdir "/var/lib/lxc/$vmid";
1354 destroy_config
($vmid);
1356 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1357 #PVE::Tools::run_command($cmd);
1360 sub vm_stop_cleanup
{
1361 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1366 my $vollist = get_vm_volumes
($conf);
1367 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1370 warn $@ if $@; # avoid errors - just warn
1373 my $safe_num_ne = sub {
1376 return 0 if !defined($a) && !defined($b);
1377 return 1 if !defined($a);
1378 return 1 if !defined($b);
1383 my $safe_string_ne = sub {
1386 return 0 if !defined($a) && !defined($b);
1387 return 1 if !defined($a);
1388 return 1 if !defined($b);
1394 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1396 if ($newnet->{type
} ne 'veth') {
1397 # for when there are physical interfaces
1398 die "cannot update interface of type $newnet->{type}";
1401 my $veth = "veth${vmid}i${netid}";
1402 my $eth = $newnet->{name
};
1404 if (my $oldnetcfg = $conf->{$opt}) {
1405 my $oldnet = parse_lxc_network
($oldnetcfg);
1407 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1408 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1410 PVE
::Network
::veth_delete
($veth);
1411 delete $conf->{$opt};
1412 write_config
($vmid, $conf);
1414 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1416 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1417 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1418 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1420 if ($oldnet->{bridge
}) {
1421 PVE
::Network
::tap_unplug
($veth);
1422 foreach (qw(bridge tag firewall)) {
1423 delete $oldnet->{$_};
1425 $conf->{$opt} = print_lxc_network
($oldnet);
1426 write_config
($vmid, $conf);
1429 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1430 foreach (qw(bridge tag firewall)) {
1431 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1433 $conf->{$opt} = print_lxc_network
($oldnet);
1434 write_config
($vmid, $conf);
1437 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1440 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1444 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1446 my $veth = "veth${vmid}i${netid}";
1447 my $vethpeer = $veth . "p";
1448 my $eth = $newnet->{name
};
1450 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1451 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1453 # attach peer in container
1454 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1455 PVE
::Tools
::run_command
($cmd);
1457 # link up peer in container
1458 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1459 PVE
::Tools
::run_command
($cmd);
1461 my $done = { type
=> 'veth' };
1462 foreach (qw(bridge tag firewall hwaddr name)) {
1463 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1465 $conf->{$opt} = print_lxc_network
($done);
1467 write_config
($vmid, $conf);
1470 sub update_ipconfig
{
1471 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1473 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1475 my $optdata = parse_lxc_network
($conf->{$opt});
1479 my $cmdargs = shift;
1480 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1482 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1484 my $change_ip_config = sub {
1485 my ($ipversion) = @_;
1487 my $family_opt = "-$ipversion";
1488 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1489 my $gw= "gw$suffix";
1490 my $ip= "ip$suffix";
1492 my $newip = $newnet->{$ip};
1493 my $newgw = $newnet->{$gw};
1494 my $oldip = $optdata->{$ip};
1496 my $change_ip = &$safe_string_ne($oldip, $newip);
1497 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1499 return if !$change_ip && !$change_gw;
1501 # step 1: add new IP, if this fails we cancel
1502 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1503 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1510 # step 2: replace gateway
1511 # If this fails we delete the added IP and cancel.
1512 # If it succeeds we save the config and delete the old IP, ignoring
1513 # errors. The config is then saved.
1514 # Note: 'ip route replace' can add
1517 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1520 # the route was not replaced, the old IP is still available
1521 # rollback (delete new IP) and cancel
1523 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1524 warn $@ if $@; # no need to die here
1529 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1530 # if the route was not deleted, the guest might have deleted it manually
1536 # from this point on we save the configuration
1537 # step 3: delete old IP ignoring errors
1538 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1539 # We need to enable promote_secondaries, otherwise our newly added
1540 # address will be removed along with the old one.
1543 if ($ipversion == 4) {
1544 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1545 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1546 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1548 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1550 warn $@ if $@; # no need to die here
1552 if ($ipversion == 4) {
1553 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1557 foreach my $property ($ip, $gw) {
1558 if ($newnet->{$property}) {
1559 $optdata->{$property} = $newnet->{$property};
1561 delete $optdata->{$property};
1564 $conf->{$opt} = print_lxc_network
($optdata);
1565 write_config
($vmid, $conf);
1566 $lxc_setup->setup_network($conf);
1569 &$change_ip_config(4);
1570 &$change_ip_config(6);
1574 # Internal snapshots
1576 # NOTE: Snapshot create/delete involves several non-atomic
1577 # action, and can take a long time.
1578 # So we try to avoid locking the file and use 'lock' variable
1579 # inside the config file instead.
1581 my $snapshot_copy_config = sub {
1582 my ($source, $dest) = @_;
1584 foreach my $k (keys %$source) {
1585 next if $k eq 'snapshots';
1586 next if $k eq 'snapstate';
1587 next if $k eq 'snaptime';
1588 next if $k eq 'vmstate';
1589 next if $k eq 'lock';
1590 next if $k eq 'digest';
1591 next if $k eq 'description';
1593 $dest->{$k} = $source->{$k};
1597 my $snapshot_prepare = sub {
1598 my ($vmid, $snapname, $comment) = @_;
1602 my $updatefn = sub {
1604 my $conf = load_config
($vmid);
1606 die "you can't take a snapshot if it's a template\n"
1607 if is_template
($conf);
1611 $conf->{lock} = 'snapshot';
1613 die "snapshot name '$snapname' already used\n"
1614 if defined($conf->{snapshots
}->{$snapname});
1616 my $storecfg = PVE
::Storage
::config
();
1617 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1619 $snap = $conf->{snapshots
}->{$snapname} = {};
1621 &$snapshot_copy_config($conf, $snap);
1623 $snap->{'snapstate'} = "prepare";
1624 $snap->{'snaptime'} = time();
1625 $snap->{'description'} = $comment if $comment;
1626 $conf->{snapshots
}->{$snapname} = $snap;
1628 write_config
($vmid, $conf);
1631 lock_container
($vmid, 10, $updatefn);
1636 my $snapshot_commit = sub {
1637 my ($vmid, $snapname) = @_;
1639 my $updatefn = sub {
1641 my $conf = load_config
($vmid);
1643 die "missing snapshot lock\n"
1644 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1646 die "snapshot '$snapname' does not exist\n"
1647 if !defined($conf->{snapshots
}->{$snapname});
1649 die "wrong snapshot state\n"
1650 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1651 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1653 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1654 delete $conf->{lock};
1655 $conf->{parent
} = $snapname;
1657 write_config
($vmid, $conf);
1660 lock_container
($vmid, 10 ,$updatefn);
1664 my ($feature, $conf, $storecfg, $snapname) = @_;
1668 foreach_mountpoint
($conf, sub {
1669 my ($ms, $mountpoint) = @_;
1671 return if $err; # skip further test
1673 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1675 # TODO: implement support for mountpoints
1676 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1680 return $err ?
0 : 1;
1683 sub snapshot_create
{
1684 my ($vmid, $snapname, $comment) = @_;
1686 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1688 my $conf = load_config
($vmid);
1690 my $running = check_running
($vmid);
1693 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1694 PVE
::Tools
::run_command
(['/bin/sync']);
1697 my $storecfg = PVE
::Storage
::config
();
1698 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1699 my $volid = $rootinfo->{volume
};
1702 PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]);
1705 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1706 &$snapshot_commit($vmid, $snapname);
1709 snapshot_delete
($vmid, $snapname, 1);
1714 sub snapshot_delete
{
1715 my ($vmid, $snapname, $force) = @_;
1721 my $updatefn = sub {
1723 $conf = load_config
($vmid);
1725 die "you can't delete a snapshot if vm is a template\n"
1726 if is_template
($conf);
1728 $snap = $conf->{snapshots
}->{$snapname};
1732 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1734 $snap->{snapstate
} = 'delete';
1736 write_config
($vmid, $conf);
1739 lock_container
($vmid, 10, $updatefn);
1741 my $storecfg = PVE
::Storage
::config
();
1743 my $del_snap = sub {
1747 if ($conf->{parent
} eq $snapname) {
1748 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1749 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1751 delete $conf->{parent
};
1755 delete $conf->{snapshots
}->{$snapname};
1757 write_config
($vmid, $conf);
1760 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1761 my $rootinfo = parse_ct_mountpoint
($rootfs);
1762 my $volid = $rootinfo->{volume
};
1765 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1769 if(!$err || ($err && $force)) {
1770 lock_container
($vmid, 10, $del_snap);
1772 die "Can't delete snapshot: $vmid $snapname $err\n";
1777 sub snapshot_rollback
{
1778 my ($vmid, $snapname) = @_;
1780 my $storecfg = PVE
::Storage
::config
();
1782 my $conf = load_config
($vmid);
1784 die "you can't rollback if vm is a template\n" if is_template
($conf);
1786 my $snap = $conf->{snapshots
}->{$snapname};
1788 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1790 my $rootfs = $snap->{rootfs
};
1791 my $rootinfo = parse_ct_mountpoint
($rootfs);
1792 my $volid = $rootinfo->{volume
};
1794 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1796 my $updatefn = sub {
1798 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1799 if $snap->{snapstate
};
1803 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1805 die "unable to rollback vm $vmid: vm is running\n"
1806 if check_running
($vmid);
1808 $conf->{lock} = 'rollback';
1812 # copy snapshot config to current config
1814 my $tmp_conf = $conf;
1815 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1816 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1817 delete $conf->{snaptime
};
1818 delete $conf->{snapname
};
1819 $conf->{parent
} = $snapname;
1821 write_config
($vmid, $conf);
1824 my $unlockfn = sub {
1825 delete $conf->{lock};
1826 write_config
($vmid, $conf);
1829 lock_container
($vmid, 10, $updatefn);
1831 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1833 lock_container
($vmid, 5, $unlockfn);
1836 sub template_create
{
1837 my ($vmid, $conf) = @_;
1839 my $storecfg = PVE
::Storage
::config
();
1841 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1842 my $volid = $rootinfo->{volume
};
1844 die "Template feature is not available for '$volid'\n"
1845 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1847 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1849 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1850 $rootinfo->{volume
} = $template_volid;
1851 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1853 write_config
($vmid, $conf);
1859 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1862 sub mountpoint_names
{
1865 my @names = ('rootfs');
1867 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1868 push @names, "mp$i";
1871 return $reverse ?
reverse @names : @names;
1874 # The container might have *different* symlinks than the host. realpath/abs_path
1875 # use the actual filesystem to resolve links.
1876 sub sanitize_mountpoint
{
1878 $mp = '/' . $mp; # we always start with a slash
1879 $mp =~ s
@/{2,}@/@g; # collapse sequences of slashes
1880 $mp =~ s
@/\./@@g; # collapse /./
1881 $mp =~ s
@/\.(/)?
$@$1@; # collapse a trailing /. or /./
1882 $mp =~ s
@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
1883 $mp =~ s
@/\.\
.(/)?
$@$1@; # collapse trailing /.. or /../ disregarding symlinks
1887 sub foreach_mountpoint_full
{
1888 my ($conf, $reverse, $func) = @_;
1890 foreach my $key (mountpoint_names
($reverse)) {
1891 my $value = $conf->{$key};
1892 next if !defined($value);
1893 my $mountpoint = parse_ct_mountpoint
($value, 1);
1894 next if !defined($mountpoint);
1896 # just to be sure: rootfs is /
1897 my $path = $key eq 'rootfs' ?
'/' : $mountpoint->{mp
};
1898 $mountpoint->{mp
} = sanitize_mountpoint
($path);
1900 $path = $mountpoint->{volume
};
1901 $mountpoint->{volume
} = sanitize_mountpoint
($path) if $path =~ m
|^/|;
1903 &$func($key, $mountpoint);
1907 sub foreach_mountpoint
{
1908 my ($conf, $func) = @_;
1910 foreach_mountpoint_full
($conf, 0, $func);
1913 sub foreach_mountpoint_reverse
{
1914 my ($conf, $func) = @_;
1916 foreach_mountpoint_full
($conf, 1, $func);
1919 sub check_ct_modify_config_perm
{
1920 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1922 return 1 if $authuser ne 'root@pam';
1924 foreach my $opt (@$key_list) {
1926 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1927 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1928 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
1929 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1930 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1931 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1932 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1933 $opt eq 'searchdomain' || $opt eq 'hostname') {
1934 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1936 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
1944 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
1946 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1947 my $volid_list = get_vm_volumes
($conf);
1949 foreach_mountpoint_reverse
($conf, sub {
1950 my ($ms, $mountpoint) = @_;
1952 my $volid = $mountpoint->{volume
};
1953 my $mount = $mountpoint->{mp
};
1955 return if !$volid || !$mount;
1957 my $mount_path = "$rootdir/$mount";
1958 $mount_path =~ s!/+!/!g;
1960 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
1963 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
1976 my ($vmid, $storage_cfg, $conf) = @_;
1978 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1979 File
::Path
::make_path
($rootdir);
1981 my $volid_list = get_vm_volumes
($conf);
1982 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
1985 foreach_mountpoint
($conf, sub {
1986 my ($ms, $mountpoint) = @_;
1988 my $volid = $mountpoint->{volume
};
1989 my $mount = $mountpoint->{mp
};
1991 return if !$volid || !$mount;
1993 my $image_path = PVE
::Storage
::path
($storage_cfg, $volid);
1994 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1995 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1997 die "unable to mount base volume - internal error" if $isBase;
1999 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2003 warn "mounting container failed - $err";
2004 umount_all
($vmid, $storage_cfg, $conf, 1);
2011 sub mountpoint_mount_path
{
2012 my ($mountpoint, $storage_cfg, $snapname) = @_;
2014 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2017 my $check_mount_path = sub {
2019 $path = File
::Spec-
>canonpath($path);
2020 my $real = Cwd
::realpath
($path);
2021 if ($real ne $path) {
2022 die "mount path modified by symlink: $path != $real";
2026 # use $rootdir = undef to just return the corresponding mount path
2027 sub mountpoint_mount
{
2028 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2030 my $volid = $mountpoint->{volume
};
2031 my $mount = $mountpoint->{mp
};
2033 return if !$volid || !$mount;
2037 if (defined($rootdir)) {
2038 $rootdir =~ s!/+$!!;
2039 $mount_path = "$rootdir/$mount";
2040 $mount_path =~ s!/+!/!g;
2041 &$check_mount_path($mount_path);
2042 File
::Path
::mkpath
($mount_path);
2045 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2047 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2051 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2052 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2054 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2055 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2057 if ($format eq 'subvol') {
2060 if ($scfg->{type
} eq 'zfspool') {
2061 my $path_arg = $path;
2062 $path_arg =~ s!^/+!!;
2063 PVE
::Tools
::run_command
(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
2065 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2068 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
2071 return wantarray ?
($path, 0) : $path;
2072 } elsif ($format eq 'raw') {
2073 my $use_loopdev = 0;
2075 if ($scfg->{path
}) {
2076 push @extra_opts, '-o', 'loop';
2078 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'rbd') {
2081 die "unsupported storage type '$scfg->{type}'\n";
2084 if ($isBase || defined($snapname)) {
2085 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2087 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2090 return wantarray ?
($path, $use_loopdev) : $path;
2092 die "unsupported image format '$format'\n";
2094 } elsif ($volid =~ m
|^/dev/.+|) {
2095 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
2096 return wantarray ?
($volid, 0) : $volid;
2097 } elsif ($volid !~ m
|^/dev/.+| && $volid =~ m
|^/.+| && -d
$volid) {
2098 &$check_mount_path($volid);
2099 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2100 return wantarray ?
($volid, 0) : $volid;
2103 die "unsupported storage";
2106 sub get_vm_volumes
{
2107 my ($conf, $excludes) = @_;
2111 foreach_mountpoint
($conf, sub {
2112 my ($ms, $mountpoint) = @_;
2114 return if $excludes && $ms eq $excludes;
2116 my $volid = $mountpoint->{volume
};
2118 return if !$volid || $volid =~ m
|^/|;
2120 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2123 push @$vollist, $volid;
2132 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp', $dev]);
2136 my ($storage_cfg, $volid) = @_;
2138 if ($volid =~ m!^/dev/.+!) {
2143 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2145 die "cannot format volume '$volid' with no storage\n" if !$storage;
2147 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2149 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2151 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2152 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2154 die "cannot format volume '$volid' (format == $format)\n"
2155 if $format ne 'raw';
2161 my ($storecfg, $vollist) = @_;
2163 foreach my $volid (@$vollist) {
2164 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2170 my ($storecfg, $vmid, $settings, $conf) = @_;
2175 foreach_mountpoint
($settings, sub {
2176 my ($ms, $mountpoint) = @_;
2178 my $volid = $mountpoint->{volume
};
2179 my $mp = $mountpoint->{mp
};
2181 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2183 return if !$storage;
2185 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
2186 my ($storeid, $size_gb) = ($1, $2);
2188 my $size_kb = int(${size_gb
}*1024) * 1024;
2190 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2191 # fixme: use better naming ct-$vmid-disk-X.raw?
2193 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2195 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2197 format_disk
($storecfg, $volid);
2199 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2202 } elsif ($scfg->{type
} eq 'zfspool') {
2204 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2206 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm') {
2208 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2209 format_disk
($storecfg, $volid);
2211 } elsif ($scfg->{type
} eq 'rbd') {
2213 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2214 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2215 format_disk
($storecfg, $volid);
2217 die "unable to create containers on storage type '$scfg->{type}'\n";
2219 push @$vollist, $volid;
2220 my $new_mountpoint = { volume
=> $volid, size
=> $size_kb*1024, mp
=> $mp };
2221 $conf->{$ms} = print_ct_mountpoint
($new_mountpoint, $ms eq 'rootfs');
2223 # use specified/existing volid
2227 # free allocated images on error
2229 destroy_disks
($storecfg, $vollist);
2235 # bash completion helper
2237 sub complete_os_templates
{
2238 my ($cmdname, $pname, $cvalue) = @_;
2240 my $cfg = PVE
::Storage
::config
();
2244 if ($cvalue =~ m/^([^:]+):/) {
2248 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2249 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2252 foreach my $id (keys %$data) {
2253 foreach my $item (@{$data->{$id}}) {
2254 push @$res, $item->{volid
} if defined($item->{volid
});
2261 my $complete_ctid_full = sub {
2264 my $idlist = vmstatus
();
2266 my $active_hash = list_active_containers
();
2270 foreach my $id (keys %$idlist) {
2271 my $d = $idlist->{$id};
2272 if (defined($running)) {
2273 next if $d->{template
};
2274 next if $running && !$active_hash->{$id};
2275 next if !$running && $active_hash->{$id};
2284 return &$complete_ctid_full();
2287 sub complete_ctid_stopped
{
2288 return &$complete_ctid_full(0);
2291 sub complete_ctid_running
{
2292 return &$complete_ctid_full(1);