12 use PVE
::Cluster
qw(cfs_register_file cfs_read_file);
16 use PVE
::JSONSchema
qw(get_standard_option);
17 use PVE
::Tools
qw($IPV6RE $IPV4RE dir_glob_foreach);
19 use PVE
::AccessControl
;
24 my $nodename = PVE
::INotify
::nodename
();
26 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
32 format_description
=> 'volume',
33 description
=> 'Volume, device or directory to mount into the container.',
37 format_description
=> '[1|0]',
38 description
=> 'Whether to include the mountpoint in backups.',
43 format_description
=> 'DiskSize',
44 pattern
=> '\d+[TGMK]?',
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.post-stop' => 1,
235 'lxc.hook.clone' => 1,
236 'lxc.hook.destroy' => 1,
239 'lxc.start.auto' => 1,
240 'lxc.start.delay' => 1,
241 'lxc.start.order' => 1,
243 'lxc.environment' => 1,
254 description
=> "Network interface type.",
259 format_description
=> 'String',
260 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
261 pattern
=> '[-_.\w\d]+',
265 format_description
=> 'vmbr<Number>',
266 description
=> 'Bridge to attach the network device to.',
267 pattern
=> '[-_.\w\d]+',
271 format_description
=> 'MAC',
272 description
=> 'Bridge to attach the network device to. (lxc.network.hwaddr)',
273 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
278 format_description
=> 'Number',
279 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
284 format
=> 'pve-ipv4-config',
285 format_description
=> 'IPv4Format/CIDR',
286 description
=> 'IPv4 address in CIDR format.',
292 format_description
=> 'GatewayIPv4',
293 description
=> 'Default gateway for IPv4 traffic.',
298 format
=> 'pve-ipv6-config',
299 format_description
=> 'IPv6Format/CIDR',
300 description
=> 'IPv6 address in CIDR format.',
306 format_description
=> 'GatewayIPv6',
307 description
=> 'Default gateway for IPv6 traffic.',
312 format_description
=> '[1|0]',
313 description
=> "Controls whether this interface's firewall rules should be used.",
318 format_description
=> 'VlanNo',
321 description
=> "VLAN tag foro this interface.",
325 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
327 my $MAX_LXC_NETWORKS = 10;
328 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
329 $confdesc->{"net$i"} = {
331 type
=> 'string', format
=> $netconf_desc,
332 description
=> "Specifies network interfaces for the container.",
340 format_description
=> 'Path',
341 description
=> 'Path to the mountpoint as seen from inside the container.',
345 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
347 my $MAX_MOUNT_POINTS = 10;
348 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
349 $confdesc->{"mp$i"} = {
351 type
=> 'string', format
=> $mp_desc,
352 description
=> "Use volume as container mount point (experimental feature).",
357 sub write_pct_config
{
358 my ($filename, $conf) = @_;
360 delete $conf->{snapstate
}; # just to be sure
362 my $generate_raw_config = sub {
367 # add description as comment to top of file
368 my $descr = $conf->{description
} || '';
369 foreach my $cl (split(/\n/, $descr)) {
370 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
373 foreach my $key (sort keys %$conf) {
374 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
375 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
376 my $value = $conf->{$key};
377 die "detected invalid newline inside property '$key'\n" if $value =~ m/\n/;
378 $raw .= "$key: $value\n";
381 if (my $lxcconf = $conf->{lxc
}) {
382 foreach my $entry (@$lxcconf) {
383 my ($k, $v) = @$entry;
391 my $raw = &$generate_raw_config($conf);
393 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
394 $raw .= "\n[$snapname]\n";
395 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
402 my ($key, $value) = @_;
404 die "unknown setting '$key'\n" if !$confdesc->{$key};
406 my $type = $confdesc->{$key}->{type
};
408 if (!defined($value)) {
409 die "got undefined value\n";
412 if ($value =~ m/[\n\r]/) {
413 die "property contains a line feed\n";
416 if ($type eq 'boolean') {
417 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
418 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
419 die "type check ('boolean') failed - got '$value'\n";
420 } elsif ($type eq 'integer') {
421 return int($1) if $value =~ m/^(\d+)$/;
422 die "type check ('integer') failed - got '$value'\n";
423 } elsif ($type eq 'number') {
424 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
425 die "type check ('number') failed - got '$value'\n";
426 } elsif ($type eq 'string') {
427 if (my $fmt = $confdesc->{$key}->{format
}) {
428 PVE
::JSONSchema
::check_format
($fmt, $value);
437 sub parse_pct_config
{
438 my ($filename, $raw) = @_;
440 return undef if !defined($raw);
443 digest
=> Digest
::SHA
::sha1_hex
($raw),
447 $filename =~ m
|/lxc/(\d
+).conf
$|
448 || die "got strange filename '$filename'";
456 my @lines = split(/\n/, $raw);
457 foreach my $line (@lines) {
458 next if $line =~ m/^\s*$/;
460 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
462 $conf->{description
} = $descr if $descr;
464 $conf = $res->{snapshots
}->{$section} = {};
468 if ($line =~ m/^\#(.*)\s*$/) {
469 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
473 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
476 if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
477 push @{$conf->{lxc
}}, [$key, $value];
479 warn "vm $vmid - unable to parse config: $line\n";
481 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
482 $descr .= PVE
::Tools
::decode_text
($2);
483 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
484 $conf->{snapstate
} = $1;
485 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
488 eval { $value = check_type
($key, $value); };
489 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
490 $conf->{$key} = $value;
492 warn "vm $vmid - unable to parse config: $line\n";
496 $conf->{description
} = $descr if $descr;
498 delete $res->{snapstate
}; # just to be sure
504 my $vmlist = PVE
::Cluster
::get_vmlist
();
506 return $res if !$vmlist || !$vmlist->{ids
};
507 my $ids = $vmlist->{ids
};
509 foreach my $vmid (keys %$ids) {
510 next if !$vmid; # skip CT0
511 my $d = $ids->{$vmid};
512 next if !$d->{node
} || $d->{node
} ne $nodename;
513 next if !$d->{type
} || $d->{type
} ne 'lxc';
514 $res->{$vmid}->{type
} = 'lxc';
519 sub cfs_config_path
{
520 my ($vmid, $node) = @_;
522 $node = $nodename if !$node;
523 return "nodes/$node/lxc/$vmid.conf";
527 my ($vmid, $node) = @_;
529 my $cfspath = cfs_config_path
($vmid, $node);
530 return "/etc/pve/$cfspath";
534 my ($vmid, $node) = @_;
536 $node = $nodename if !$node;
537 my $cfspath = cfs_config_path
($vmid, $node);
539 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
540 die "container $vmid does not exists\n" if !defined($conf);
546 my ($vmid, $conf) = @_;
548 my $dir = "/etc/pve/nodes/$nodename/lxc";
551 write_config
($vmid, $conf);
557 unlink config_file
($vmid, $nodename);
561 my ($vmid, $conf) = @_;
563 my $cfspath = cfs_config_path
($vmid);
565 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
568 # flock: we use one file handle per process, so lock file
569 # can be called multiple times and succeeds for the same process.
571 my $lock_handles = {};
572 my $lockdir = "/run/lock/lxc";
577 return "$lockdir/pve-config-${vmid}.lock";
581 my ($vmid, $timeout) = @_;
583 $timeout = 10 if !$timeout;
586 my $filename = lock_filename
($vmid);
588 mkdir $lockdir if !-d
$lockdir;
590 my $lock_func = sub {
591 if (!$lock_handles->{$$}->{$filename}) {
592 my $fh = new IO
::File
(">>$filename") ||
593 die "can't open file - $!\n";
594 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
597 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
598 print STDERR
"trying to aquire lock...";
601 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
602 # try again on EINTR (see bug #273)
603 if ($success || ($! != EINTR
)) {
608 print STDERR
" failed\n";
609 die "can't aquire lock - $!\n";
612 print STDERR
" OK\n";
615 $lock_handles->{$$}->{$filename}->{refcount
}++;
618 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
621 die "can't lock file '$filename' - $err";
628 my $filename = lock_filename
($vmid);
630 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
631 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
632 if ($refcount <= 0) {
633 $lock_handles->{$$}->{$filename} = undef;
640 my ($vmid, $timeout, $code, @param) = @_;
644 lock_aquire
($vmid, $timeout);
645 eval { $res = &$code(@param) };
657 return defined($confdesc->{$name});
660 # add JSON properties for create and set function
661 sub json_config_properties
{
664 foreach my $opt (keys %$confdesc) {
665 next if $opt eq 'parent' || $opt eq 'snaptime';
666 next if $prop->{$opt};
667 $prop->{$opt} = $confdesc->{$opt};
673 sub json_config_properties_no_rootfs
{
676 foreach my $opt (keys %$confdesc) {
677 next if $prop->{$opt};
678 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
679 $prop->{$opt} = $confdesc->{$opt};
685 # container status helpers
687 sub list_active_containers
{
689 my $filename = "/proc/net/unix";
691 # similar test is used by lcxcontainers.c: list_active_containers
694 my $fh = IO
::File-
>new ($filename, "r");
697 while (defined(my $line = <$fh>)) {
698 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
700 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
711 # warning: this is slow
715 my $active_hash = list_active_containers
();
717 return 1 if defined($active_hash->{$vmid});
722 sub get_container_disk_usage
{
725 my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/'];
735 if (my ($fsid, $total, $used, $avail) = $line =~
736 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
744 eval { PVE
::Tools
::run_command
($cmd, timeout
=> 1, outfunc
=> $parser); };
753 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
755 my $active_hash = list_active_containers
();
757 foreach my $vmid (keys %$list) {
758 my $d = $list->{$vmid};
760 my $running = defined($active_hash->{$vmid});
762 $d->{status
} = $running ?
'running' : 'stopped';
764 my $cfspath = cfs_config_path
($vmid);
765 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
767 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
768 $d->{name
} =~ s/[\s]//g;
770 $d->{cpus
} = $conf->{cpulimit
} // 0;
773 my $res = get_container_disk_usage
($vmid);
774 $d->{disk
} = $res->{used
};
775 $d->{maxdisk
} = $res->{total
};
778 # use 4GB by default ??
779 if (my $rootfs = $conf->{rootfs
}) {
780 my $rootinfo = parse_ct_mountpoint
($rootfs);
781 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
783 $d->{maxdisk
} = 4*1024*1024*1024;
789 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
790 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
801 $d->{template
} = is_template
($conf);
804 foreach my $vmid (keys %$list) {
805 my $d = $list->{$vmid};
806 next if $d->{status
} ne 'running';
808 $d->{uptime
} = 100; # fixme:
810 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
811 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
813 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
814 my @bytes = split(/\n/, $blkio_bytes);
815 foreach my $byte (@bytes) {
816 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
817 $d->{diskread
} = $2 if $key eq 'Read';
818 $d->{diskwrite
} = $2 if $key eq 'Write';
826 my $parse_size = sub {
829 return undef if $value !~ m/^(\d+(\.\d+)?)([KMG])?$/;
830 my ($size, $unit) = ($1, $3);
833 $size = $size * 1024;
834 } elsif ($unit eq 'M') {
835 $size = $size * 1024 * 1024;
836 } elsif ($unit eq 'G') {
837 $size = $size * 1024 * 1024 * 1024;
843 my $format_size = sub {
848 my $kb = int($size/1024);
849 return $size if $kb*1024 != $size;
851 my $mb = int($kb/1024);
852 return "${kb}K" if $mb*1024 != $kb;
854 my $gb = int($mb/1024);
855 return "${mb}M" if $gb*1024 != $mb;
860 sub parse_ct_mountpoint
{
866 eval { $res = PVE
::JSONSchema
::parse_property_string
($mp_desc, $data) };
872 return undef if !defined($res->{volume
});
875 return undef if !defined($res->{size
} = &$parse_size($res->{size
}));
881 sub print_ct_mountpoint
{
882 my ($info, $nomp) = @_;
886 die "missing volume\n" if !$info->{volume
};
888 foreach my $o (qw(backup)) {
889 $opts .= ",$o=$info->{$o}" if defined($info->{$o});
893 $opts .= ",size=" . &$format_size($info->{size
});
896 $opts .= ",mp=$info->{mp}" if !$nomp;
898 return "$info->{volume}$opts";
901 sub print_lxc_network
{
904 die "no network name defined\n" if !$net->{name
};
906 my $res = "name=$net->{name}";
908 foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
909 next if !defined($net->{$k});
910 $res .= ",$k=$net->{$k}";
916 sub parse_lxc_network
{
921 return $res if !$data;
923 eval { $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data) };
929 $res->{type
} = 'veth';
930 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
935 sub read_cgroup_value
{
936 my ($group, $vmid, $name, $full) = @_;
938 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
940 return PVE
::Tools
::file_get_contents
($path) if $full;
942 return PVE
::Tools
::file_read_firstline
($path);
945 sub write_cgroup_value
{
946 my ($group, $vmid, $name, $value) = @_;
948 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
949 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
953 sub find_lxc_console_pids
{
957 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
960 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
963 my @args = split(/\0/, $cmdline);
965 # serach for lxc-console -n <vmid>
966 return if scalar(@args) != 3;
967 return if $args[1] ne '-n';
968 return if $args[2] !~ m/^\d+$/;
969 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
973 push @{$res->{$vmid}}, $pid;
985 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
987 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid], outfunc
=> $parser);
989 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
994 my $ipv4_reverse_mask = [
1030 # Note: we cannot use Net:IP, because that only allows strict
1032 sub parse_ipv4_cidr
{
1033 my ($cidr, $noerr) = @_;
1035 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
1036 return { address
=> $1, netmask
=> $ipv4_reverse_mask->[$2] };
1039 return undef if $noerr;
1041 die "unable to parse ipv4 address/mask\n";
1047 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
1050 sub check_protection
{
1051 my ($vm_conf, $err_msg) = @_;
1053 if ($vm_conf->{protection
}) {
1054 die "$err_msg - protection mode enabled\n";
1058 sub update_lxc_config
{
1059 my ($storage_cfg, $vmid, $conf) = @_;
1061 my $dir = "/var/lib/lxc/$vmid";
1063 if ($conf->{template
}) {
1065 unlink "$dir/config";
1072 die "missing 'arch' - internal error" if !$conf->{arch
};
1073 $raw .= "lxc.arch = $conf->{arch}\n";
1075 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
1076 if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
1077 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
1082 if (!has_dev_console
($conf)) {
1083 $raw .= "lxc.console = none\n";
1084 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1087 my $ttycount = get_tty_count
($conf);
1088 $raw .= "lxc.tty = $ttycount\n";
1090 my $utsname = $conf->{hostname
} || "CT$vmid";
1091 $raw .= "lxc.utsname = $utsname\n";
1093 my $memory = $conf->{memory
} || 512;
1094 my $swap = $conf->{swap
} // 0;
1096 my $lxcmem = int($memory*1024*1024);
1097 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1099 my $lxcswap = int(($memory + $swap)*1024*1024);
1100 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1102 if (my $cpulimit = $conf->{cpulimit
}) {
1103 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1104 my $value = int(100000*$cpulimit);
1105 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1108 my $shares = $conf->{cpuunits
} || 1024;
1109 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1111 my $mountpoint = parse_ct_mountpoint
($conf->{rootfs
});
1112 $mountpoint->{mp
} = '/';
1114 my ($path, $use_loopdev) = mountpoint_mount_path
($mountpoint, $storage_cfg);
1115 $path = "loop:$path" if $use_loopdev;
1117 $raw .= "lxc.rootfs = $path\n";
1120 foreach my $k (keys %$conf) {
1121 next if $k !~ m/^net(\d+)$/;
1123 my $d = parse_lxc_network
($conf->{$k});
1125 $raw .= "lxc.network.type = veth\n";
1126 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1127 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1128 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1129 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1132 if (my $lxcconf = $conf->{lxc
}) {
1133 foreach my $entry (@$lxcconf) {
1134 my ($k, $v) = @$entry;
1135 $netcount++ if $k eq 'lxc.network.type';
1136 $raw .= "$k = $v\n";
1140 $raw .= "lxc.network.type = empty\n" if !$netcount;
1142 File
::Path
::mkpath
("$dir/rootfs");
1144 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1147 # verify and cleanup nameserver list (replace \0 with ' ')
1148 sub verify_nameserver_list
{
1149 my ($nameserver_list) = @_;
1152 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1153 PVE
::JSONSchema
::pve_verify_ip
($server);
1154 push @list, $server;
1157 return join(' ', @list);
1160 sub verify_searchdomain_list
{
1161 my ($searchdomain_list) = @_;
1164 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1165 # todo: should we add checks for valid dns domains?
1166 push @list, $server;
1169 return join(' ', @list);
1172 sub update_pct_config
{
1173 my ($vmid, $conf, $running, $param, $delete) = @_;
1181 my $pid = find_lxc_pid
($vmid);
1182 $rootdir = "/proc/$pid/root";
1185 if (defined($delete)) {
1186 foreach my $opt (@$delete) {
1187 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1188 die "unable to delete required option '$opt'\n";
1189 } elsif ($opt eq 'swap') {
1190 delete $conf->{$opt};
1191 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1192 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1193 delete $conf->{$opt};
1194 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1195 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1196 delete $conf->{$opt};
1197 push @nohotplug, $opt;
1199 } elsif ($opt =~ m/^net(\d)$/) {
1200 delete $conf->{$opt};
1203 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1204 } elsif ($opt eq 'protection') {
1205 delete $conf->{$opt};
1206 } elsif ($opt =~ m/^mp(\d+)$/) {
1207 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1208 delete $conf->{$opt};
1209 push @nohotplug, $opt;
1214 write_config
($vmid, $conf) if $running;
1218 # There's no separate swap size to configure, there's memory and "total"
1219 # memory (iow. memory+swap). This means we have to change them together.
1220 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1221 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1222 if (defined($wanted_memory) || defined($wanted_swap)) {
1224 $wanted_memory //= ($conf->{memory
} || 512);
1225 $wanted_swap //= ($conf->{swap
} || 0);
1227 my $total = $wanted_memory + $wanted_swap;
1229 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1230 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1232 $conf->{memory
} = $wanted_memory;
1233 $conf->{swap
} = $wanted_swap;
1235 write_config
($vmid, $conf) if $running;
1238 foreach my $opt (keys %$param) {
1239 my $value = $param->{$opt};
1240 if ($opt eq 'hostname') {
1241 $conf->{$opt} = $value;
1242 } elsif ($opt eq 'onboot') {
1243 $conf->{$opt} = $value ?
1 : 0;
1244 } elsif ($opt eq 'startup') {
1245 $conf->{$opt} = $value;
1246 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1247 $conf->{$opt} = $value;
1248 push @nohotplug, $opt;
1250 } elsif ($opt eq 'nameserver') {
1251 my $list = verify_nameserver_list
($value);
1252 $conf->{$opt} = $list;
1253 push @nohotplug, $opt;
1255 } elsif ($opt eq 'searchdomain') {
1256 my $list = verify_searchdomain_list
($value);
1257 $conf->{$opt} = $list;
1258 push @nohotplug, $opt;
1260 } elsif ($opt eq 'cpulimit') {
1261 $conf->{$opt} = $value;
1262 push @nohotplug, $opt; # fixme: hotplug
1264 } elsif ($opt eq 'cpuunits') {
1265 $conf->{$opt} = $value;
1266 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1267 } elsif ($opt eq 'description') {
1268 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1269 } elsif ($opt =~ m/^net(\d+)$/) {
1271 my $net = parse_lxc_network
($value);
1273 $conf->{$opt} = print_lxc_network
($net);
1275 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1277 } elsif ($opt eq 'protection') {
1278 $conf->{$opt} = $value ?
1 : 0;
1279 } elsif ($opt =~ m/^mp(\d+)$/) {
1280 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1281 $conf->{$opt} = $value;
1283 push @nohotplug, $opt;
1285 } elsif ($opt eq 'rootfs') {
1286 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1287 die "implement me: $opt";
1289 die "implement me: $opt";
1291 write_config
($vmid, $conf) if $running;
1294 if ($running && scalar(@nohotplug)) {
1295 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1299 my $storage_cfg = PVE
::Storage
::config
();
1300 create_disks
($storage_cfg, $vmid, $conf, $conf);
1304 sub has_dev_console
{
1307 return !(defined($conf->{console
}) && !$conf->{console
});
1313 return $conf->{tty
} // $confdesc->{tty
}->{default};
1319 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1322 sub get_console_command
{
1323 my ($vmid, $conf) = @_;
1325 my $cmode = get_cmode
($conf);
1327 if ($cmode eq 'console') {
1328 return ['lxc-console', '-n', $vmid, '-t', 0];
1329 } elsif ($cmode eq 'tty') {
1330 return ['lxc-console', '-n', $vmid];
1331 } elsif ($cmode eq 'shell') {
1332 return ['lxc-attach', '--clear-env', '-n', $vmid];
1334 die "internal error";
1338 sub get_primary_ips
{
1341 # return data from net0
1343 return undef if !defined($conf->{net0
});
1344 my $net = parse_lxc_network
($conf->{net0
});
1346 my $ipv4 = $net->{ip
};
1348 if ($ipv4 =~ /^(dhcp|manual)$/) {
1354 my $ipv6 = $net->{ip6
};
1356 if ($ipv6 =~ /^(dhcp|manual)$/) {
1363 return ($ipv4, $ipv6);
1367 sub destroy_lxc_container
{
1368 my ($storage_cfg, $vmid, $conf) = @_;
1370 foreach_mountpoint
($conf, sub {
1371 my ($ms, $mountpoint) = @_;
1373 # skip bind mounts and block devices
1374 if ($mountpoint->{volume
} =~ m
|^/|) {
1378 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $mountpoint->{volume
});
1379 PVE
::Storage
::vdisk_free
($storage_cfg, $mountpoint->{volume
}) if $vmid == $owner;
1382 rmdir "/var/lib/lxc/$vmid/rootfs";
1383 unlink "/var/lib/lxc/$vmid/config";
1384 rmdir "/var/lib/lxc/$vmid";
1385 destroy_config
($vmid);
1387 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1388 #PVE::Tools::run_command($cmd);
1391 sub vm_stop_cleanup
{
1392 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1397 my $vollist = get_vm_volumes
($conf);
1398 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1401 warn $@ if $@; # avoid errors - just warn
1404 my $safe_num_ne = sub {
1407 return 0 if !defined($a) && !defined($b);
1408 return 1 if !defined($a);
1409 return 1 if !defined($b);
1414 my $safe_string_ne = sub {
1417 return 0 if !defined($a) && !defined($b);
1418 return 1 if !defined($a);
1419 return 1 if !defined($b);
1425 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1427 if ($newnet->{type
} ne 'veth') {
1428 # for when there are physical interfaces
1429 die "cannot update interface of type $newnet->{type}";
1432 my $veth = "veth${vmid}i${netid}";
1433 my $eth = $newnet->{name
};
1435 if (my $oldnetcfg = $conf->{$opt}) {
1436 my $oldnet = parse_lxc_network
($oldnetcfg);
1438 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1439 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1441 PVE
::Network
::veth_delete
($veth);
1442 delete $conf->{$opt};
1443 write_config
($vmid, $conf);
1445 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1447 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1448 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1449 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1451 if ($oldnet->{bridge
}) {
1452 PVE
::Network
::tap_unplug
($veth);
1453 foreach (qw(bridge tag firewall)) {
1454 delete $oldnet->{$_};
1456 $conf->{$opt} = print_lxc_network
($oldnet);
1457 write_config
($vmid, $conf);
1460 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1461 foreach (qw(bridge tag firewall)) {
1462 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1464 $conf->{$opt} = print_lxc_network
($oldnet);
1465 write_config
($vmid, $conf);
1468 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1471 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1475 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1477 my $veth = "veth${vmid}i${netid}";
1478 my $vethpeer = $veth . "p";
1479 my $eth = $newnet->{name
};
1481 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1482 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1484 # attach peer in container
1485 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1486 PVE
::Tools
::run_command
($cmd);
1488 # link up peer in container
1489 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1490 PVE
::Tools
::run_command
($cmd);
1492 my $done = { type
=> 'veth' };
1493 foreach (qw(bridge tag firewall hwaddr name)) {
1494 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1496 $conf->{$opt} = print_lxc_network
($done);
1498 write_config
($vmid, $conf);
1501 sub update_ipconfig
{
1502 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1504 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1506 my $optdata = parse_lxc_network
($conf->{$opt});
1510 my $cmdargs = shift;
1511 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1513 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1515 my $change_ip_config = sub {
1516 my ($ipversion) = @_;
1518 my $family_opt = "-$ipversion";
1519 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1520 my $gw= "gw$suffix";
1521 my $ip= "ip$suffix";
1523 my $newip = $newnet->{$ip};
1524 my $newgw = $newnet->{$gw};
1525 my $oldip = $optdata->{$ip};
1527 my $change_ip = &$safe_string_ne($oldip, $newip);
1528 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1530 return if !$change_ip && !$change_gw;
1532 # step 1: add new IP, if this fails we cancel
1533 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1534 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1541 # step 2: replace gateway
1542 # If this fails we delete the added IP and cancel.
1543 # If it succeeds we save the config and delete the old IP, ignoring
1544 # errors. The config is then saved.
1545 # Note: 'ip route replace' can add
1548 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1551 # the route was not replaced, the old IP is still available
1552 # rollback (delete new IP) and cancel
1554 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1555 warn $@ if $@; # no need to die here
1560 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1561 # if the route was not deleted, the guest might have deleted it manually
1567 # from this point on we save the configuration
1568 # step 3: delete old IP ignoring errors
1569 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1570 # We need to enable promote_secondaries, otherwise our newly added
1571 # address will be removed along with the old one.
1574 if ($ipversion == 4) {
1575 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1576 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1577 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1579 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1581 warn $@ if $@; # no need to die here
1583 if ($ipversion == 4) {
1584 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1588 foreach my $property ($ip, $gw) {
1589 if ($newnet->{$property}) {
1590 $optdata->{$property} = $newnet->{$property};
1592 delete $optdata->{$property};
1595 $conf->{$opt} = print_lxc_network
($optdata);
1596 write_config
($vmid, $conf);
1597 $lxc_setup->setup_network($conf);
1600 &$change_ip_config(4);
1601 &$change_ip_config(6);
1605 # Internal snapshots
1607 # NOTE: Snapshot create/delete involves several non-atomic
1608 # action, and can take a long time.
1609 # So we try to avoid locking the file and use 'lock' variable
1610 # inside the config file instead.
1612 my $snapshot_copy_config = sub {
1613 my ($source, $dest) = @_;
1615 foreach my $k (keys %$source) {
1616 next if $k eq 'snapshots';
1617 next if $k eq 'snapstate';
1618 next if $k eq 'snaptime';
1619 next if $k eq 'vmstate';
1620 next if $k eq 'lock';
1621 next if $k eq 'digest';
1622 next if $k eq 'description';
1624 $dest->{$k} = $source->{$k};
1628 my $snapshot_prepare = sub {
1629 my ($vmid, $snapname, $comment) = @_;
1633 my $updatefn = sub {
1635 my $conf = load_config
($vmid);
1637 die "you can't take a snapshot if it's a template\n"
1638 if is_template
($conf);
1642 $conf->{lock} = 'snapshot';
1644 die "snapshot name '$snapname' already used\n"
1645 if defined($conf->{snapshots
}->{$snapname});
1647 my $storecfg = PVE
::Storage
::config
();
1648 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1650 $snap = $conf->{snapshots
}->{$snapname} = {};
1652 &$snapshot_copy_config($conf, $snap);
1654 $snap->{'snapstate'} = "prepare";
1655 $snap->{'snaptime'} = time();
1656 $snap->{'description'} = $comment if $comment;
1657 $conf->{snapshots
}->{$snapname} = $snap;
1659 write_config
($vmid, $conf);
1662 lock_container
($vmid, 10, $updatefn);
1667 my $snapshot_commit = sub {
1668 my ($vmid, $snapname) = @_;
1670 my $updatefn = sub {
1672 my $conf = load_config
($vmid);
1674 die "missing snapshot lock\n"
1675 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1677 die "snapshot '$snapname' does not exist\n"
1678 if !defined($conf->{snapshots
}->{$snapname});
1680 die "wrong snapshot state\n"
1681 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1682 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1684 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1685 delete $conf->{lock};
1686 $conf->{parent
} = $snapname;
1688 write_config
($vmid, $conf);
1691 lock_container
($vmid, 10 ,$updatefn);
1695 my ($feature, $conf, $storecfg, $snapname) = @_;
1699 foreach_mountpoint
($conf, sub {
1700 my ($ms, $mountpoint) = @_;
1702 return if $err; # skip further test
1704 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1706 # TODO: implement support for mountpoints
1707 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1711 return $err ?
0 : 1;
1714 sub snapshot_create
{
1715 my ($vmid, $snapname, $comment) = @_;
1717 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1719 my $conf = load_config
($vmid);
1721 my $running = check_running
($vmid);
1724 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1725 PVE
::Tools
::run_command
(['/bin/sync']);
1728 my $storecfg = PVE
::Storage
::config
();
1729 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1730 my $volid = $rootinfo->{volume
};
1733 PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]);
1736 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1737 &$snapshot_commit($vmid, $snapname);
1740 snapshot_delete
($vmid, $snapname, 1);
1745 sub snapshot_delete
{
1746 my ($vmid, $snapname, $force) = @_;
1752 my $updatefn = sub {
1754 $conf = load_config
($vmid);
1756 die "you can't delete a snapshot if vm is a template\n"
1757 if is_template
($conf);
1759 $snap = $conf->{snapshots
}->{$snapname};
1763 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1765 $snap->{snapstate
} = 'delete';
1767 write_config
($vmid, $conf);
1770 lock_container
($vmid, 10, $updatefn);
1772 my $storecfg = PVE
::Storage
::config
();
1774 my $del_snap = sub {
1778 if ($conf->{parent
} eq $snapname) {
1779 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1780 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1782 delete $conf->{parent
};
1786 delete $conf->{snapshots
}->{$snapname};
1788 write_config
($vmid, $conf);
1791 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1792 my $rootinfo = parse_ct_mountpoint
($rootfs);
1793 my $volid = $rootinfo->{volume
};
1796 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1800 if(!$err || ($err && $force)) {
1801 lock_container
($vmid, 10, $del_snap);
1803 die "Can't delete snapshot: $vmid $snapname $err\n";
1808 sub snapshot_rollback
{
1809 my ($vmid, $snapname) = @_;
1811 my $storecfg = PVE
::Storage
::config
();
1813 my $conf = load_config
($vmid);
1815 die "you can't rollback if vm is a template\n" if is_template
($conf);
1817 my $snap = $conf->{snapshots
}->{$snapname};
1819 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1821 my $rootfs = $snap->{rootfs
};
1822 my $rootinfo = parse_ct_mountpoint
($rootfs);
1823 my $volid = $rootinfo->{volume
};
1825 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1827 my $updatefn = sub {
1829 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1830 if $snap->{snapstate
};
1834 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1836 die "unable to rollback vm $vmid: vm is running\n"
1837 if check_running
($vmid);
1839 $conf->{lock} = 'rollback';
1843 # copy snapshot config to current config
1845 my $tmp_conf = $conf;
1846 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1847 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1848 delete $conf->{snaptime
};
1849 delete $conf->{snapname
};
1850 $conf->{parent
} = $snapname;
1852 write_config
($vmid, $conf);
1855 my $unlockfn = sub {
1856 delete $conf->{lock};
1857 write_config
($vmid, $conf);
1860 lock_container
($vmid, 10, $updatefn);
1862 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1864 lock_container
($vmid, 5, $unlockfn);
1867 sub template_create
{
1868 my ($vmid, $conf) = @_;
1870 my $storecfg = PVE
::Storage
::config
();
1872 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1873 my $volid = $rootinfo->{volume
};
1875 die "Template feature is not available for '$volid'\n"
1876 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1878 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1880 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1881 $rootinfo->{volume
} = $template_volid;
1882 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1884 write_config
($vmid, $conf);
1890 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1893 sub mountpoint_names
{
1896 my @names = ('rootfs');
1898 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1899 push @names, "mp$i";
1902 return $reverse ?
reverse @names : @names;
1905 # The container might have *different* symlinks than the host. realpath/abs_path
1906 # use the actual filesystem to resolve links.
1907 sub sanitize_mountpoint
{
1909 $mp = '/' . $mp; # we always start with a slash
1910 $mp =~ s
@/{2,}@/@g; # collapse sequences of slashes
1911 $mp =~ s
@/\./@@g; # collapse /./
1912 $mp =~ s
@/\.(/)?
$@$1@; # collapse a trailing /. or /./
1913 $mp =~ s
@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
1914 $mp =~ s
@/\.\
.(/)?
$@$1@; # collapse trailing /.. or /../ disregarding symlinks
1918 sub foreach_mountpoint_full
{
1919 my ($conf, $reverse, $func) = @_;
1921 foreach my $key (mountpoint_names
($reverse)) {
1922 my $value = $conf->{$key};
1923 next if !defined($value);
1924 my $mountpoint = parse_ct_mountpoint
($value);
1926 # just to be sure: rootfs is /
1927 my $path = $key eq 'rootfs' ?
'/' : $mountpoint->{mp
};
1928 $mountpoint->{mp
} = sanitize_mountpoint
($path);
1930 $path = $mountpoint->{volume
};
1931 $mountpoint->{volume
} = sanitize_mountpoint
($path) if $path =~ m
|^/|;
1933 &$func($key, $mountpoint);
1937 sub foreach_mountpoint
{
1938 my ($conf, $func) = @_;
1940 foreach_mountpoint_full
($conf, 0, $func);
1943 sub foreach_mountpoint_reverse
{
1944 my ($conf, $func) = @_;
1946 foreach_mountpoint_full
($conf, 1, $func);
1949 sub check_ct_modify_config_perm
{
1950 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1952 return 1 if $authuser ne 'root@pam';
1954 foreach my $opt (@$key_list) {
1956 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1957 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1958 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
1959 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1960 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1961 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1962 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1963 $opt eq 'searchdomain' || $opt eq 'hostname') {
1964 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1966 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
1974 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
1976 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1977 my $volid_list = get_vm_volumes
($conf);
1979 foreach_mountpoint_reverse
($conf, sub {
1980 my ($ms, $mountpoint) = @_;
1982 my $volid = $mountpoint->{volume
};
1983 my $mount = $mountpoint->{mp
};
1985 return if !$volid || !$mount;
1987 my $mount_path = "$rootdir/$mount";
1988 $mount_path =~ s!/+!/!g;
1990 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
1993 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2006 my ($vmid, $storage_cfg, $conf) = @_;
2008 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2009 File
::Path
::make_path
($rootdir);
2011 my $volid_list = get_vm_volumes
($conf);
2012 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2015 foreach_mountpoint
($conf, sub {
2016 my ($ms, $mountpoint) = @_;
2018 my $volid = $mountpoint->{volume
};
2019 my $mount = $mountpoint->{mp
};
2021 return if !$volid || !$mount;
2023 my $image_path = PVE
::Storage
::path
($storage_cfg, $volid);
2024 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2025 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2027 die "unable to mount base volume - internal error" if $isBase;
2029 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2033 warn "mounting container failed - $err";
2034 umount_all
($vmid, $storage_cfg, $conf, 1);
2041 sub mountpoint_mount_path
{
2042 my ($mountpoint, $storage_cfg, $snapname) = @_;
2044 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2047 my $check_mount_path = sub {
2049 $path = File
::Spec-
>canonpath($path);
2050 my $real = Cwd
::realpath
($path);
2051 if ($real ne $path) {
2052 die "mount path modified by symlink: $path != $real";
2056 # use $rootdir = undef to just return the corresponding mount path
2057 sub mountpoint_mount
{
2058 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2060 my $volid = $mountpoint->{volume
};
2061 my $mount = $mountpoint->{mp
};
2063 return if !$volid || !$mount;
2067 if (defined($rootdir)) {
2068 $rootdir =~ s!/+$!!;
2069 $mount_path = "$rootdir/$mount";
2070 $mount_path =~ s!/+!/!g;
2071 &$check_mount_path($mount_path);
2072 File
::Path
::mkpath
($mount_path);
2075 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2077 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2081 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2082 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2084 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2085 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2087 if ($format eq 'subvol') {
2090 if ($scfg->{type
} eq 'zfspool') {
2091 my $path_arg = $path;
2092 $path_arg =~ s!^/+!!;
2093 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', '-t', 'zfs', $path_arg, $mount_path]);
2095 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2098 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
2101 return wantarray ?
($path, 0) : $path;
2102 } elsif ($format eq 'raw') {
2103 my $use_loopdev = 0;
2105 if ($scfg->{path
}) {
2106 push @extra_opts, '-o', 'loop';
2108 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'rbd') {
2111 die "unsupported storage type '$scfg->{type}'\n";
2114 if ($isBase || defined($snapname)) {
2115 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2117 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2120 return wantarray ?
($path, $use_loopdev) : $path;
2122 die "unsupported image format '$format'\n";
2124 } elsif ($volid =~ m
|^/dev/.+|) {
2125 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
2126 return wantarray ?
($volid, 0) : $volid;
2127 } elsif ($volid !~ m
|^/dev/.+| && $volid =~ m
|^/.+| && -d
$volid) {
2128 &$check_mount_path($volid);
2129 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2130 return wantarray ?
($volid, 0) : $volid;
2133 die "unsupported storage";
2136 sub get_vm_volumes
{
2137 my ($conf, $excludes) = @_;
2141 foreach_mountpoint
($conf, sub {
2142 my ($ms, $mountpoint) = @_;
2144 return if $excludes && $ms eq $excludes;
2146 my $volid = $mountpoint->{volume
};
2148 return if !$volid || $volid =~ m
|^/|;
2150 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2153 push @$vollist, $volid;
2162 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp', $dev]);
2166 my ($storage_cfg, $volid) = @_;
2168 if ($volid =~ m!^/dev/.+!) {
2173 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2175 die "cannot format volume '$volid' with no storage\n" if !$storage;
2177 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2179 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2181 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2182 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2184 die "cannot format volume '$volid' (format == $format)\n"
2185 if $format ne 'raw';
2191 my ($storecfg, $vollist) = @_;
2193 foreach my $volid (@$vollist) {
2194 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2200 my ($storecfg, $vmid, $settings, $conf) = @_;
2205 foreach_mountpoint
($settings, sub {
2206 my ($ms, $mountpoint) = @_;
2208 my $volid = $mountpoint->{volume
};
2209 my $mp = $mountpoint->{mp
};
2211 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2213 return if !$storage;
2215 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
2216 my ($storeid, $size_gb) = ($1, $2);
2218 my $size_kb = int(${size_gb
}*1024) * 1024;
2220 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2221 # fixme: use better naming ct-$vmid-disk-X.raw?
2223 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2225 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2227 format_disk
($storecfg, $volid);
2229 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2232 } elsif ($scfg->{type
} eq 'zfspool') {
2234 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2236 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm') {
2238 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2239 format_disk
($storecfg, $volid);
2241 } elsif ($scfg->{type
} eq 'rbd') {
2243 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2244 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2245 format_disk
($storecfg, $volid);
2247 die "unable to create containers on storage type '$scfg->{type}'\n";
2249 push @$vollist, $volid;
2250 my $new_mountpoint = { volume
=> $volid, size
=> $size_kb*1024, mp
=> $mp };
2251 $conf->{$ms} = print_ct_mountpoint
($new_mountpoint, $ms eq 'rootfs');
2253 # use specified/existing volid
2257 # free allocated images on error
2259 destroy_disks
($storecfg, $vollist);
2265 # bash completion helper
2267 sub complete_os_templates
{
2268 my ($cmdname, $pname, $cvalue) = @_;
2270 my $cfg = PVE
::Storage
::config
();
2274 if ($cvalue =~ m/^([^:]+):/) {
2278 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2279 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2282 foreach my $id (keys %$data) {
2283 foreach my $item (@{$data->{$id}}) {
2284 push @$res, $item->{volid
} if defined($item->{volid
});
2291 my $complete_ctid_full = sub {
2294 my $idlist = vmstatus
();
2296 my $active_hash = list_active_containers
();
2300 foreach my $id (keys %$idlist) {
2301 my $d = $idlist->{$id};
2302 if (defined($running)) {
2303 next if $d->{template
};
2304 next if $running && !$active_hash->{$id};
2305 next if !$running && $active_hash->{$id};
2314 return &$complete_ctid_full();
2317 sub complete_ctid_stopped
{
2318 return &$complete_ctid_full(0);
2321 sub complete_ctid_running
{
2322 return &$complete_ctid_full(1);